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