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}