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.opends.server.admin.std.server.BackendCfg; 045import org.opends.server.api.Backend; 046import org.opends.server.controls.PagedResultsControl; 047import org.opends.server.core.AddOperation; 048import org.opends.server.core.DeleteOperation; 049import org.opends.server.core.DirectoryServer; 050import org.opends.server.core.ModifyDNOperation; 051import org.opends.server.core.ModifyOperation; 052import org.opends.server.core.SearchOperation; 053import org.opends.server.core.ServerContext; 054import org.opends.server.types.AttributeType; 055import org.opends.server.types.BackupConfig; 056import org.opends.server.types.BackupDirectory; 057import org.opends.server.types.DN; 058import org.opends.server.types.DirectoryException; 059import org.opends.server.types.Entry; 060import org.opends.server.types.IndexType; 061import org.opends.server.types.InitializationException; 062import org.opends.server.types.LDIFExportConfig; 063import org.opends.server.types.LDIFImportConfig; 064import org.opends.server.types.LDIFImportResult; 065import org.opends.server.types.ObjectClass; 066import org.opends.server.types.RestoreConfig; 067import org.opends.server.util.CollectionUtils; 068import org.opends.server.util.LDIFException; 069import org.opends.server.util.LDIFReader; 070import org.opends.server.util.LDIFWriter; 071 072/** 073 * This class implements /dev/null like backend for development and 074 * testing. The following behaviors of this backend implementation 075 * 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 objeclasses 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 087 * applicable. 088 * <li>Searches within this backend are always considered indexed. 089 * <li>Backend Import is supported by iterating over ldif reader on a 090 * single thread and issuing add operations which essentially do nothing 091 * at all. 092 * <li>Backend Export is supported but does nothing producing an empty 093 * ldif. 094 * <li>Backend Backup and Restore are not supported. 095 * </ul> 096 * This backend implementation is for development and testing only, does 097 * not represent a complete and stable API, should be considered private 098 * and subject to change without notice. 099 */ 100public class NullBackend extends Backend<BackendCfg> 101{ 102 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 103 104 105 106 /** The base DNs for this backend. */ 107 private DN[] baseDNs; 108 109 /** The base DNs for this backend, in a hash set. */ 110 private HashSet<DN> baseDNSet; 111 112 /** The set of supported controls for this backend. */ 113 private final Set<String> supportedControls = CollectionUtils.newHashSet( 114 OID_SUBTREE_DELETE_CONTROL, 115 OID_PAGED_RESULTS_CONTROL, 116 OID_MANAGE_DSAIT_CONTROL, 117 OID_SERVER_SIDE_SORT_REQUEST_CONTROL, 118 OID_VLV_REQUEST_CONTROL); 119 120 /** The map of null entry object classes. */ 121 private Map<ObjectClass,String> objectClasses; 122 123 124 125 /** 126 * Creates a new backend with the provided information. All backend 127 * implementations must implement a default constructor that use 128 * <CODE>super()</CODE> to invoke this constructor. 129 */ 130 public NullBackend() 131 { 132 super(); 133 134 // Perform all initialization in initializeBackend. 135 } 136 137 /** 138 * Set the base DNs for this backend. This is used by the unit tests 139 * to set the base DNs without having to provide a configuration 140 * object when initializing the backend. 141 * @param baseDNs The set of base DNs to be served by this memory backend. 142 */ 143 public void setBaseDNs(DN[] baseDNs) 144 { 145 this.baseDNs = baseDNs; 146 } 147 148 /** {@inheritDoc} */ 149 @Override 150 public void configureBackend(BackendCfg config, ServerContext serverContext) throws ConfigException 151 { 152 if (config != null) 153 { 154 BackendCfg cfg = config; 155 DN[] cfgBaseDNs = new DN[cfg.getBaseDN().size()]; 156 cfg.getBaseDN().toArray(cfgBaseDNs); 157 setBaseDNs(cfgBaseDNs); 158 } 159 } 160 161 /** {@inheritDoc} */ 162 @Override 163 public synchronized void openBackend() throws ConfigException, InitializationException 164 { 165 baseDNSet = new HashSet<>(); 166 Collections.addAll(baseDNSet, baseDNs); 167 168 // Register base DNs. 169 for (DN dn : baseDNs) 170 { 171 try 172 { 173 DirectoryServer.registerBaseDN(dn, this, false); 174 } 175 catch (Exception e) 176 { 177 logger.traceException(e); 178 179 LocalizableMessage message = ERR_BACKEND_CANNOT_REGISTER_BASEDN.get( 180 dn, getExceptionMessage(e)); 181 throw new InitializationException(message, e); 182 } 183 } 184 185 // Initialize null entry object classes. 186 objectClasses = new HashMap<>(); 187 188 String topOCName = "top"; 189 ObjectClass topOC = DirectoryServer.getObjectClass(topOCName); 190 if (topOC == null) { 191 throw new InitializationException(LocalizableMessage.raw("Unable to locate " + topOCName + 192 " objectclass in the current server schema")); 193 } 194 objectClasses.put(topOC, topOCName); 195 196 String nulOCName = "nullbackendobject"; 197 ObjectClass nulOC = DirectoryServer.getDefaultObjectClass(nulOCName); 198 try { 199 DirectoryServer.registerObjectClass(nulOC, false); 200 } catch (DirectoryException de) { 201 logger.traceException(de); 202 throw new InitializationException(de.getMessageObject()); 203 } 204 objectClasses.put(nulOC, nulOCName); 205 206 String extOCName = "extensibleobject"; 207 ObjectClass extOC = DirectoryServer.getObjectClass(extOCName); 208 if (extOC == null) { 209 throw new InitializationException(LocalizableMessage.raw("Unable to locate " + extOCName + 210 " objectclass in the current server schema")); 211 } 212 objectClasses.put(extOC, extOCName); 213 } 214 215 /** {@inheritDoc} */ 216 @Override 217 public synchronized void closeBackend() 218 { 219 for (DN dn : baseDNs) 220 { 221 try 222 { 223 DirectoryServer.deregisterBaseDN(dn); 224 } 225 catch (Exception e) 226 { 227 logger.traceException(e); 228 } 229 } 230 } 231 232 /** {@inheritDoc} */ 233 @Override 234 public DN[] getBaseDNs() 235 { 236 return baseDNs; 237 } 238 239 /** {@inheritDoc} */ 240 @Override 241 public long getEntryCount() 242 { 243 return -1; 244 } 245 246 /** {@inheritDoc} */ 247 @Override 248 public boolean isIndexed(AttributeType attributeType, IndexType indexType) 249 { 250 // All searches in this backend will always be considered indexed. 251 return true; 252 } 253 254 /** {@inheritDoc} */ 255 @Override 256 public ConditionResult hasSubordinates(DN entryDN) 257 throws DirectoryException 258 { 259 return ConditionResult.UNDEFINED; 260 } 261 262 /** {@inheritDoc} */ 263 @Override 264 public long getNumberOfEntriesInBaseDN(DN baseDN) throws DirectoryException 265 { 266 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, ERR_NUM_SUBORDINATES_NOT_SUPPORTED.get()); 267 } 268 269 /** {@inheritDoc} */ 270 @Override 271 public long getNumberOfChildren(DN parentDN) throws DirectoryException 272 { 273 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, ERR_NUM_SUBORDINATES_NOT_SUPPORTED.get()); 274 } 275 276 /** {@inheritDoc} */ 277 @Override 278 public Entry getEntry(DN entryDN) 279 { 280 return new Entry(null, objectClasses, null, null); 281 } 282 283 /** {@inheritDoc} */ 284 @Override 285 public boolean entryExists(DN entryDN) 286 { 287 return false; 288 } 289 290 /** {@inheritDoc} */ 291 @Override 292 public void addEntry(Entry entry, AddOperation addOperation) 293 throws DirectoryException 294 { 295 return; 296 } 297 298 /** {@inheritDoc} */ 299 @Override 300 public void deleteEntry(DN entryDN, DeleteOperation deleteOperation) 301 throws DirectoryException 302 { 303 return; 304 } 305 306 /** {@inheritDoc} */ 307 @Override 308 public void replaceEntry(Entry oldEntry, Entry newEntry, 309 ModifyOperation modifyOperation) throws DirectoryException 310 { 311 return; 312 } 313 314 /** {@inheritDoc} */ 315 @Override 316 public void renameEntry(DN currentDN, Entry entry, 317 ModifyDNOperation modifyDNOperation) 318 throws DirectoryException 319 { 320 return; 321 } 322 323 /** {@inheritDoc} */ 324 @Override 325 public void search(SearchOperation searchOperation) 326 throws DirectoryException 327 { 328 PagedResultsControl pageRequest = 329 searchOperation.getRequestControl(PagedResultsControl.DECODER); 330 331 if (pageRequest != null) { 332 // Indicate no more pages. 333 PagedResultsControl control; 334 control = 335 new PagedResultsControl(pageRequest.isCritical(), 0, null); 336 searchOperation.getResponseControls().add(control); 337 } 338 339 return; 340 } 341 342 /** {@inheritDoc} */ 343 @Override 344 public Set<String> getSupportedControls() 345 { 346 return supportedControls; 347 } 348 349 /** {@inheritDoc} */ 350 @Override 351 public Set<String> getSupportedFeatures() 352 { 353 return Collections.emptySet(); 354 } 355 356 /** {@inheritDoc} */ 357 @Override 358 public boolean supports(BackendOperation backendOperation) 359 { 360 switch (backendOperation) 361 { 362 case LDIF_EXPORT: 363 case LDIF_IMPORT: 364 return true; 365 366 default: 367 return false; 368 } 369 } 370 371 /** {@inheritDoc} */ 372 @Override 373 public void exportLDIF(LDIFExportConfig exportConfig) 374 throws DirectoryException 375 { 376 LDIFWriter ldifWriter; 377 378 try { 379 ldifWriter = new LDIFWriter(exportConfig); 380 } catch (Exception e) { 381 logger.traceException(e); 382 383 LocalizableMessage message = LocalizableMessage.raw(e.getMessage()); 384 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 385 message); 386 } 387 388 close(ldifWriter); 389 } 390 391 /** {@inheritDoc} */ 392 @Override 393 public LDIFImportResult importLDIF(LDIFImportConfig importConfig, ServerContext serverContext) 394 throws DirectoryException 395 { 396 LDIFReader reader; 397 try 398 { 399 reader = new LDIFReader(importConfig); 400 } 401 catch (Exception e) 402 { 403 LocalizableMessage message = LocalizableMessage.raw(e.getMessage()); 404 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 405 message); 406 } 407 408 try 409 { 410 while (true) 411 { 412 Entry e = null; 413 try 414 { 415 e = reader.readEntry(); 416 if (e == null) 417 { 418 break; 419 } 420 } 421 catch (LDIFException le) 422 { 423 if (! le.canContinueReading()) 424 { 425 LocalizableMessage message = LocalizableMessage.raw(le.getMessage()); 426 throw new DirectoryException( 427 DirectoryServer.getServerErrorResultCode(),message); 428 } 429 else 430 { 431 continue; 432 } 433 } 434 435 try 436 { 437 addEntry(e, null); 438 } 439 catch (DirectoryException de) 440 { 441 reader.rejectLastEntry(de.getMessageObject()); 442 } 443 } 444 445 return new LDIFImportResult(reader.getEntriesRead(), 446 reader.getEntriesRejected(), 447 reader.getEntriesIgnored()); 448 } 449 catch (DirectoryException de) 450 { 451 throw de; 452 } 453 catch (Exception e) 454 { 455 LocalizableMessage message = LocalizableMessage.raw(e.getMessage()); 456 throw new DirectoryException(DirectoryServer.getServerErrorResultCode(), 457 message); 458 } 459 finally 460 { 461 reader.close(); 462 } 463 } 464 465 /** {@inheritDoc} */ 466 @Override 467 public void createBackup(BackupConfig backupConfig) 468 throws DirectoryException 469 { 470 LocalizableMessage message = LocalizableMessage.raw("The null backend does not support backup operation"); 471 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 472 } 473 474 /** {@inheritDoc} */ 475 @Override 476 public void removeBackup(BackupDirectory backupDirectory, 477 String backupID) 478 throws DirectoryException 479 { 480 LocalizableMessage message = LocalizableMessage.raw("The null backend does not support remove backup operation"); 481 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 482 } 483 484 /** {@inheritDoc} */ 485 @Override 486 public void restoreBackup(RestoreConfig restoreConfig) 487 throws DirectoryException 488 { 489 LocalizableMessage message = LocalizableMessage.raw("The null backend does not support restore operation"); 490 throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message); 491 } 492}