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}