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