001/* 002 * CDDL HEADER START 003 * 004 * The contents of this file are subject to the terms of the 005 * Common Development and Distribution License, Version 1.0 only 006 * (the "License"). You may not use this file except in compliance 007 * with the License. 008 * 009 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Copyright 2008 Sun Microsystems, Inc. 025 * Portions Copyright 2014-2015 ForgeRock AS 026 */ 027package org.opends.server.backends; 028 029import static org.opends.messages.BackendMessages.*; 030import static org.opends.server.util.ServerConstants.*; 031import static org.opends.server.util.StaticUtils.*; 032 033import java.util.Collections; 034import java.util.HashMap; 035import java.util.HashSet; 036import java.util.Map; 037import java.util.Set; 038 039import org.forgerock.i18n.LocalizableMessage; 040import org.forgerock.i18n.slf4j.LocalizedLogger; 041import org.forgerock.opendj.config.server.ConfigException; 042import org.forgerock.opendj.ldap.ConditionResult; 043import org.forgerock.opendj.ldap.ResultCode; 044import org.forgerock.opendj.ldap.SearchScope; 045import org.opends.server.admin.std.server.BackendCfg; 046import org.opends.server.api.Backend; 047import org.opends.server.controls.PagedResultsControl; 048import org.opends.server.core.AddOperation; 049import org.opends.server.core.DeleteOperation; 050import org.opends.server.core.DirectoryServer; 051import org.opends.server.core.ModifyDNOperation; 052import org.opends.server.core.ModifyOperation; 053import org.opends.server.core.SearchOperation; 054import org.opends.server.core.ServerContext; 055import org.opends.server.types.AttributeType; 056import org.opends.server.types.BackupConfig; 057import org.opends.server.types.BackupDirectory; 058import org.opends.server.types.DN; 059import org.opends.server.types.DirectoryException; 060import org.opends.server.types.Entry; 061import org.opends.server.types.IndexType; 062import org.opends.server.types.InitializationException; 063import org.opends.server.types.LDIFExportConfig; 064import org.opends.server.types.LDIFImportConfig; 065import org.opends.server.types.LDIFImportResult; 066import org.opends.server.types.ObjectClass; 067import org.opends.server.types.RestoreConfig; 068import org.opends.server.util.CollectionUtils; 069import org.opends.server.util.LDIFException; 070import org.opends.server.util.LDIFReader; 071import org.opends.server.util.LDIFWriter; 072 073/** 074 * This class implements /dev/null like backend for development and testing. 075 * The following behaviors of this backend implementation should be noted: 076 * <ul> 077 * <li>All read operations return success but no data. 078 * <li>All write operations return success but do nothing. 079 * <li>Bind operations fail with invalid credentials. 080 * <li>Compare operations are only possible on objectclass and return 081 * true for the following objectClasses only: top, nullbackendobject, 082 * extensibleobject. Otherwise comparison result is false or comparison 083 * fails altogether. 084 * <li>Controls are supported although this implementation does not 085 * provide any specific emulation for controls. Generally known request 086 * controls are accepted and default response controls returned where applicable. 087 * <li>Searches within this backend are always considered indexed. 088 * <li>Backend Import is supported by iterating over ldif reader on a 089 * single thread and issuing add operations which essentially do nothing at all. 090 * <li>Backend Export is supported but does nothing producing an empty ldif. 091 * <li>Backend Backup and Restore are not supported. 092 * </ul> 093 * This backend implementation is for development and testing only, does 094 * not represent a complete and stable API, should be considered private 095 * and subject to change without notice. 096 */ 097public class NullBackend extends Backend<BackendCfg> 098{ 099 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 100 101 /** The base DNs for this backend. */ 102 private DN[] baseDNs; 103 104 /** The base DNs for this backend, in a hash set. */ 105 private HashSet<DN> baseDNSet; 106 107 /** The set of supported controls for this backend. */ 108 private final Set<String> supportedControls = CollectionUtils.newHashSet( 109 OID_SUBTREE_DELETE_CONTROL, 110 OID_PAGED_RESULTS_CONTROL, 111 OID_MANAGE_DSAIT_CONTROL, 112 OID_SERVER_SIDE_SORT_REQUEST_CONTROL, 113 OID_VLV_REQUEST_CONTROL); 114 115 /** The map of null entry object classes. */ 116 private Map<ObjectClass,String> objectClasses; 117 118 /** 119 * Creates a new backend with the provided information. All backend 120 * implementations must implement a default constructor that use 121 * <CODE>super()</CODE> to invoke this constructor. 122 */ 123 public NullBackend() 124 { 125 super(); 126 127 // Perform all initialization in initializeBackend. 128 } 129 130 /** 131 * Set the base DNs for this backend. This is used by the unit tests 132 * to set the base DNs without having to provide a configuration 133 * object when initializing the backend. 134 * @param baseDNs The set of base DNs to be served by this memory backend. 135 */ 136 public void setBaseDNs(DN[] baseDNs) 137 { 138 this.baseDNs = baseDNs; 139 } 140 141 @Override 142 public void configureBackend(BackendCfg config, ServerContext serverContext) throws ConfigException 143 { 144 if (config != null) 145 { 146 BackendCfg cfg = config; 147 setBaseDNs(cfg.getBaseDN().toArray(new DN[cfg.getBaseDN().size()])); 148 } 149 } 150 151 @Override 152 public synchronized void openBackend() throws ConfigException, InitializationException 153 { 154 baseDNSet = new HashSet<>(); 155 Collections.addAll(baseDNSet, baseDNs); 156 157 // Register base DNs. 158 for (DN dn : baseDNs) 159 { 160 try 161 { 162 DirectoryServer.registerBaseDN(dn, this, false); 163 } 164 catch (Exception e) 165 { 166 logger.traceException(e); 167 168 LocalizableMessage message = ERR_BACKEND_CANNOT_REGISTER_BASEDN.get(dn, getExceptionMessage(e)); 169 throw new InitializationException(message, e); 170 } 171 } 172 173 // Initialize null entry object classes. 174 objectClasses = new HashMap<>(); 175 176 String topOCName = "top"; 177 ObjectClass topOC = DirectoryServer.getObjectClass(topOCName); 178 if (topOC == null) { 179 throw new InitializationException(LocalizableMessage.raw("Unable to locate " + topOCName + 180 " objectclass in the current server schema")); 181 } 182 objectClasses.put(topOC, topOCName); 183 184 String nulOCName = "nullbackendobject"; 185 ObjectClass nulOC = DirectoryServer.getDefaultObjectClass(nulOCName); 186 try { 187 DirectoryServer.registerObjectClass(nulOC, false); 188 } catch (DirectoryException de) { 189 logger.traceException(de); 190 throw new InitializationException(de.getMessageObject()); 191 } 192 objectClasses.put(nulOC, nulOCName); 193 194 String extOCName = "extensibleobject"; 195 ObjectClass extOC = DirectoryServer.getObjectClass(extOCName); 196 if (extOC == null) { 197 throw new InitializationException(LocalizableMessage.raw("Unable to locate " + extOCName + 198 " objectclass in the current server schema")); 199 } 200 objectClasses.put(extOC, extOCName); 201 } 202 203 @Override 204 public synchronized void closeBackend() 205 { 206 for (DN dn : baseDNs) 207 { 208 try 209 { 210 DirectoryServer.deregisterBaseDN(dn); 211 } 212 catch (Exception e) 213 { 214 logger.traceException(e); 215 } 216 } 217 } 218 219 @Override 220 public DN[] getBaseDNs() 221 { 222 return baseDNs; 223 } 224 225 @Override 226 public long getEntryCount() 227 { 228 return -1; 229 } 230 231 @Override 232 public boolean isIndexed(AttributeType attributeType, IndexType indexType) 233 { 234 // All searches in this backend will always be considered indexed. 235 return true; 236 } 237 238 @Override 239 public ConditionResult hasSubordinates(DN entryDN) throws DirectoryException 240 { 241 return ConditionResult.UNDEFINED; 242 } 243 244 @Override 245 public long getNumberOfEntriesInBaseDN(DN baseDN) throws DirectoryException 246 { 247 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, ERR_NUM_SUBORDINATES_NOT_SUPPORTED.get()); 248 } 249 250 @Override 251 public long getNumberOfChildren(DN parentDN) throws DirectoryException 252 { 253 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, ERR_NUM_SUBORDINATES_NOT_SUPPORTED.get()); 254 } 255 256 @Override 257 public Entry getEntry(DN entryDN) 258 { 259 return new Entry(null, objectClasses, null, null); 260 } 261 262 @Override 263 public boolean entryExists(DN entryDN) 264 { 265 return false; 266 } 267 268 @Override 269 public void addEntry(Entry entry, AddOperation addOperation) throws DirectoryException 270 { 271 return; 272 } 273 274 @Override 275 public void deleteEntry(DN entryDN, DeleteOperation deleteOperation) throws DirectoryException 276 { 277 return; 278 } 279 280 @Override 281 public void replaceEntry(Entry oldEntry, Entry newEntry, ModifyOperation modifyOperation) throws DirectoryException 282 { 283 return; 284 } 285 286 @Override 287 public void renameEntry(DN currentDN, Entry entry, ModifyDNOperation modifyDNOperation) throws DirectoryException 288 { 289 return; 290 } 291 292 @Override 293 public void search(SearchOperation searchOperation) throws DirectoryException 294 { 295 PagedResultsControl pageRequest = 296 searchOperation.getRequestControl(PagedResultsControl.DECODER); 297 298 if (pageRequest != null) { 299 // Indicate no more pages. 300 PagedResultsControl control = 301 new PagedResultsControl(pageRequest.isCritical(), 0, null); 302 searchOperation.getResponseControls().add(control); 303 } 304 305 if (SearchScope.BASE_OBJECT.equals(searchOperation.getScope()) 306 && baseDNSet.contains(searchOperation.getBaseDN())) 307 { 308 searchOperation.setResultCode(ResultCode.NO_SUCH_OBJECT); 309 } 310 } 311 312 @Override 313 public Set<String> getSupportedControls() 314 { 315 return supportedControls; 316 } 317 318 @Override 319 public Set<String> getSupportedFeatures() 320 { 321 return Collections.emptySet(); 322 } 323 324 @Override 325 public boolean supports(BackendOperation backendOperation) 326 { 327 switch (backendOperation) 328 { 329 case LDIF_EXPORT: 330 case LDIF_IMPORT: 331 return true; 332 333 default: 334 return false; 335 } 336 } 337 338 @Override 339 public void exportLDIF(LDIFExportConfig exportConfig) throws DirectoryException 340 { 341 try (LDIFWriter ldifWriter = new LDIFWriter(exportConfig)) 342 { 343 // just create it to see if it fails 344 } catch (Exception e) { 345 logger.traceException(e); 346 347 throw newDirectoryException(e); 348 } 349 } 350 351 @Override 352 public LDIFImportResult importLDIF(LDIFImportConfig importConfig, ServerContext serverContext) 353 throws DirectoryException 354 { 355 try (LDIFReader reader = getReader(importConfig)) 356 { 357 while (true) 358 { 359 Entry e = null; 360 try 361 { 362 e = reader.readEntry(); 363 if (e == null) 364 { 365 break; 366 } 367 } 368 catch (LDIFException le) 369 { 370 if (le.canContinueReading()) 371 { 372 continue; 373 } 374 throw newDirectoryException(le); 375 } 376 377 try 378 { 379 addEntry(e, null); 380 } 381 catch (DirectoryException de) 382 { 383 reader.rejectLastEntry(de.getMessageObject()); 384 } 385 } 386 387 return new LDIFImportResult(reader.getEntriesRead(), 388 reader.getEntriesRejected(), 389 reader.getEntriesIgnored()); 390 } 391 catch (DirectoryException de) 392 { 393 throw de; 394 } 395 catch (Exception e) 396 { 397 throw newDirectoryException(e); 398 } 399 } 400 401 private DirectoryException newDirectoryException(Exception e) 402 { 403 LocalizableMessage message = LocalizableMessage.raw(e.getMessage()); 404 return new DirectoryException(DirectoryServer.getServerErrorResultCode(), message, e); 405 } 406 407 private LDIFReader getReader(LDIFImportConfig importConfig) throws DirectoryException 408 { 409 try 410 { 411 return new LDIFReader(importConfig); 412 } 413 catch (Exception e) 414 { 415 throw newDirectoryException(e); 416 } 417 } 418 419 @Override 420 public void createBackup(BackupConfig backupConfig) throws DirectoryException 421 { 422 throw unwillingToPerformOperation("backup"); 423 } 424 425 @Override 426 public void removeBackup(BackupDirectory backupDirectory, String backupID) throws DirectoryException 427 { 428 throw unwillingToPerformOperation("remove backup"); 429 } 430 431 @Override 432 public void restoreBackup(RestoreConfig restoreConfig) throws DirectoryException 433 { 434 throw unwillingToPerformOperation("restore"); 435 } 436 437 private DirectoryException unwillingToPerformOperation(String operationName) 438 { 439 String msg = "The null backend does not support " + operationName + " operation"; 440 return new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, LocalizableMessage.raw(msg)); 441 } 442}