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}