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 2006-2008 Sun Microsystems, Inc.
015 * Portions Copyright 2014-2016 ForgeRock AS.
016 */
017package org.opends.server.api;
018
019import static org.opends.messages.BackendMessages.*;
020
021import java.util.ArrayList;
022import java.util.Collection;
023import java.util.Collections;
024import java.util.LinkedHashSet;
025import java.util.List;
026import java.util.Queue;
027import java.util.Set;
028import java.util.concurrent.ConcurrentLinkedQueue;
029
030import org.forgerock.i18n.LocalizableMessage;
031import org.forgerock.opendj.config.server.ConfigException;
032import org.forgerock.opendj.ldap.ConditionResult;
033import org.forgerock.opendj.ldap.ResultCode;
034import org.forgerock.opendj.ldap.schema.MatchingRule;
035import org.opends.server.admin.Configuration;
036import org.opends.server.backends.RebuildConfig;
037import org.opends.server.backends.VerifyConfig;
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.PersistentSearch;
044import org.opends.server.core.PersistentSearch.CancellationCallback;
045import org.opends.server.core.SearchOperation;
046import org.opends.server.core.ServerContext;
047import org.opends.server.monitors.BackendMonitor;
048import org.forgerock.opendj.ldap.schema.AttributeType;
049import org.opends.server.types.BackupConfig;
050import org.opends.server.types.BackupDirectory;
051import org.opends.server.types.CanceledOperationException;
052import org.forgerock.opendj.ldap.DN;
053import org.opends.server.types.DirectoryException;
054import org.opends.server.types.Entry;
055import org.opends.server.types.IndexType;
056import org.opends.server.types.InitializationException;
057import org.opends.server.types.LDIFExportConfig;
058import org.opends.server.types.LDIFImportConfig;
059import org.opends.server.types.LDIFImportResult;
060import org.opends.server.types.RestoreConfig;
061import org.opends.server.types.SearchFilter;
062import org.opends.server.types.WritabilityMode;
063/**
064 * This class defines the set of methods and structures that must be
065 * implemented for a Directory Server backend.
066 *
067 * @param <C>
068 *          the type of the BackendCfg for the current backend
069 */
070@org.opends.server.types.PublicAPI(
071     stability=org.opends.server.types.StabilityLevel.VOLATILE,
072     mayInstantiate=false,
073     mayExtend=true,
074     mayInvoke=false)
075public abstract class Backend<C extends Configuration>
076// should have been BackendCfg instead of Configuration
077{
078  /**
079   * The backend that holds a portion of the DIT that is hierarchically above
080   * the information in this backend.
081   */
082  private Backend<?> parentBackend;
083
084  /**
085   * The set of backends that hold portions of the DIT that are hierarchically
086   * below the information in this backend.
087   */
088  private Backend<?>[] subordinateBackends = new Backend[0];
089
090  /** The backend monitor associated with this backend. */
091  private BackendMonitor backendMonitor;
092
093  /** Indicates whether this is a private backend or one that holds user data. */
094  private boolean isPrivateBackend;
095
096  /** The unique identifier for this backend. */
097  private String backendID;
098
099  /** The writability mode for this backend. */
100  private WritabilityMode writabilityMode = WritabilityMode.ENABLED;
101
102  /** The set of persistent searches registered with this backend. */
103  private final ConcurrentLinkedQueue<PersistentSearch> persistentSearches = new ConcurrentLinkedQueue<>();
104
105  /**
106   * Configure this backend based on the information in the provided configuration.
107   * When the method returns, the backend will have been configured (ready to be opened) but still unable
108   * to process operations.
109   *
110   * @param  cfg          The configuration of this backend.
111   * @param  serverContext The server context for this instance
112   * @throws  ConfigException
113   *                      If there is an error in the configuration.
114   */
115  public abstract void configureBackend(C cfg, ServerContext serverContext) throws ConfigException;
116
117  /**
118   * Indicates whether the provided configuration is acceptable for
119   * this backend.  It should be possible to call this method on an
120   * uninitialized backend instance in order to determine whether the
121   * backend would be able to use the provided configuration.
122   * <BR><BR>
123   * Note that implementations which use a subclass of the provided
124   * configuration class will likely need to cast the configuration
125   * to the appropriate subclass type.
126   *
127   * @param  configuration        The backend configuration for which
128   *                              to make the determination.
129   * @param  unacceptableReasons  A list that may be used to hold the
130   *                              reasons that the provided
131   *                              configuration is not acceptable.
132   * @param serverContext         this Directory Server instance's server context
133   * @return  {@code true} if the provided configuration is acceptable
134   *          for this backend, or {@code false} if not.
135   */
136  public boolean isConfigurationAcceptable(
137                      C configuration,
138                      List<LocalizableMessage> unacceptableReasons, ServerContext serverContext)
139  {
140    // This default implementation does not perform any special
141    // validation.  It should be overridden by backend implementations
142    // that wish to perform more detailed validation.
143    return true;
144  }
145
146  /**
147   * Opens this backend based on the information provided when the backend was configured.
148   * It also should open any underlying storage and register all suffixes with the server.
149   *
150   * @see #configureBackend
151   *
152   * @throws  ConfigException  If an unrecoverable problem arises while opening the backend.
153   *
154   * @throws  InitializationException  If a problem occurs during opening that is not
155   *                                   related to the server configuration.
156   */
157  public abstract void openBackend() throws ConfigException, InitializationException;
158
159  /**
160   * Performs any necessary work to finalize this backend. The backend must be an opened backend,
161   * so do not use this method on backends where only <code>configureBackend()</code> has been called.
162   * This may be called during the Directory Server shutdown process or if a backend is disabled
163   * with the server online.
164   * It must not return until the backend is closed.
165   * <p>
166   * This method may not throw any exceptions. If any problems are encountered,
167   * then they may be logged but the closure should progress as completely as
168   * possible.
169   * <p>
170   */
171  public final void finalizeBackend()
172  {
173    for (PersistentSearch psearch : persistentSearches)
174    {
175      psearch.cancel();
176    }
177    persistentSearches.clear();
178    closeBackend();
179  }
180
181  /**
182   * Performs any necessary work to finally close this backend, particularly
183   * closing any underlying databases or connections and deregistering
184   * any suffixes that it manages with the Directory Server.
185   * <p>
186   * It will be called as final step of <code>finalizeBackend()</code>,
187   * so subclasses might override it.
188   * </p>
189   */
190  public void closeBackend()
191  {
192  }
193
194  /**
195   * Retrieves the set of base-level DNs that may be used within this
196   * backend.
197   *
198   * @return  The set of base-level DNs that may be used within this
199   *          backend.
200   */
201  public abstract DN[] getBaseDNs();
202
203  /**
204   * Indicates whether search operations which target the specified
205   * attribute in the indicated manner would be considered indexed
206   * in this backend.  The operation should be considered indexed only
207   * if the specified operation can be completed efficiently within
208   * the backend.
209   * <BR><BR>
210   * Note that this method should return a general result that covers
211   * all values of the specified attribute.  If a the specified
212   * attribute is indexed in the indicated manner but some particular
213   * values may still be treated as unindexed (e.g., if the number of
214   * entries with that attribute value exceeds some threshold), then
215   * this method should still return {@code true} for the specified
216   * attribute and index type.
217   *
218   * @param  attributeType  The attribute type for which to make the
219   *                        determination.
220   * @param  indexType      The index type for which to make the
221   *                        determination.
222   *
223   * @return  {@code true} if search operations targeting the
224   *          specified attribute in the indicated manner should be
225   *          considered indexed, or {@code false} if not.
226   */
227  public abstract boolean isIndexed(AttributeType attributeType, IndexType indexType);
228
229  /**
230   * Indicates whether extensible match search operations that target
231   * the specified attribute with the given matching rule should be
232   * considered indexed in this backend.
233   *
234   * @param  attributeType  The attribute type for which to make the
235   *                        determination.
236   * @param  matchingRule   The matching rule for which to make the
237   *                        determination.
238   *
239   * @return  {@code true} if extensible match search operations
240   *          targeting the specified attribute with the given
241   *          matching rule should be considered indexed, or
242   *          {@code false} if not.
243   */
244  private boolean isIndexed(AttributeType attributeType, MatchingRule matchingRule)
245  {
246    return false; // FIXME This should be overridden by the JE Backend at least!
247  }
248
249  /**
250   * Indicates whether a subtree search using the provided filter
251   * would be indexed in this backend.  This default implementation
252   * uses a rough set of logic that makes a best-effort determination.
253   * Subclasses that provide a more complete indexing mechanism may
254   * wish to override this method and provide a more accurate result.
255   *
256   * @param  filter  The search filter for which to make the
257   *                 determination.
258   *
259   * @return  {@code true} if it is believed that the provided filter
260   *          would be indexed in this backend, or {@code false} if
261   *          not.
262   */
263  public boolean isIndexed(SearchFilter filter)
264  {
265    switch (filter.getFilterType())
266    {
267      case AND:
268        // At least one of the subordinate filter components must be
269        // indexed.
270        for (SearchFilter f : filter.getFilterComponents())
271        {
272          if (isIndexed(f))
273          {
274            return true;
275          }
276        }
277        return false;
278
279
280      case OR:
281        for (SearchFilter f : filter.getFilterComponents())
282        {
283          if (! isIndexed(f))
284          {
285            return false;
286          }
287        }
288        return !filter.getFilterComponents().isEmpty();
289
290
291      case NOT:
292        // NOT filters are not considered indexed by default.
293        return false;
294
295      case EQUALITY:
296        return isIndexed(filter.getAttributeType(), IndexType.EQUALITY);
297
298      case SUBSTRING:
299        return isIndexed(filter.getAttributeType(), IndexType.SUBSTRING);
300
301      case GREATER_OR_EQUAL:
302        return isIndexed(filter.getAttributeType(), IndexType.GREATER_OR_EQUAL);
303
304      case LESS_OR_EQUAL:
305        return isIndexed(filter.getAttributeType(), IndexType.LESS_OR_EQUAL);
306
307      case PRESENT:
308        return isIndexed(filter.getAttributeType(), IndexType.PRESENCE);
309
310      case APPROXIMATE_MATCH:
311        return isIndexed(filter.getAttributeType(), IndexType.APPROXIMATE);
312
313      case EXTENSIBLE_MATCH:
314        // The attribute type must be provided for us to make the
315        // determination.  If a matching rule ID is provided, then
316        // we'll use it as well, but if not then we'll use the
317        // default equality matching rule for the attribute type.
318        AttributeType attrType = filter.getAttributeType();
319        if (attrType == null)
320        {
321          return false;
322        }
323
324        MatchingRule matchingRule;
325        String matchingRuleID = filter.getMatchingRuleID();
326        if (matchingRuleID != null)
327        {
328          matchingRule = DirectoryServer.getMatchingRule(
329                              matchingRuleID.toLowerCase());
330        }
331        else
332        {
333          matchingRule = attrType.getEqualityMatchingRule();
334        }
335        // FIXME isIndexed() always return false down below
336        return matchingRule != null && isIndexed(attrType, matchingRule);
337
338
339      default:
340        return false;
341    }
342  }
343
344  /**
345   * Retrieves the requested entry from this backend. The caller is not required to hold any locks
346   * on the specified DN.
347   *
348   * @param entryDN
349   *          The distinguished name of the entry to retrieve.
350   * @return The requested entry, or {@code null} if the entry does not exist.
351   * @throws DirectoryException
352   *           If a problem occurs while trying to retrieve the entry.
353   */
354  public abstract Entry getEntry(DN entryDN) throws DirectoryException;
355
356  /**
357   * Indicates whether the requested entry has any subordinates.
358   *
359   * @param entryDN The distinguished name of the entry.
360   *
361   * @return {@code ConditionResult.TRUE} if the entry has one or more
362   *         subordinates or {@code ConditionResult.FALSE} otherwise
363   *         or {@code ConditionResult.UNDEFINED} if it can not be
364   *         determined.
365   *
366   * @throws DirectoryException  If a problem occurs while trying to
367   *                              retrieve the entry.
368   */
369  public abstract ConditionResult hasSubordinates(DN entryDN) throws DirectoryException;
370
371  /**
372   * Retrieves the number of subordinates immediately below the requested entry.
373   *
374   * @param parentDN
375   *          The distinguished name of the parent.
376   * @return The number of subordinate entries for the requested entry.
377   * @throws DirectoryException
378   *           If baseDN isn't a base dn managed by this backend or if a problem occurs while trying to retrieve the
379   *           entry.
380   * @throws NullPointerException
381   *           if baseDN is null.
382   */
383  public abstract long getNumberOfChildren(DN parentDN) throws DirectoryException;
384
385  /**
386   * Retrieves the number of entries for the specified base DN including all entries from the requested entry to the
387   * lowest level in the tree.
388   *
389   * @param baseDN
390   *          The base distinguished name.
391   * @return The number of subordinate entries including the base dn.
392   * @throws DirectoryException
393   *           If baseDN isn't a base dn managed by this backend or if a problem occurs while trying to retrieve the
394   *           entry.
395   * @throws NullPointerException
396   *           if baseDN is null.
397   */
398  public abstract long getNumberOfEntriesInBaseDN(DN baseDN) throws DirectoryException;
399
400  /**
401   * Indicates whether an entry with the specified DN exists in the backend. The default
402   * implementation calls {@code getEntry}, but backend implementations may override this with a
403   * more efficient version. The caller is not required to hold any locks on the specified DN.
404   *
405   * @param entryDN
406   *          The DN of the entry for which to determine existence.
407   * @return {@code true} if the specified entry exists in this backend, or {@code false} if it does
408   *         not.
409   * @throws DirectoryException
410   *           If a problem occurs while trying to make the determination.
411   */
412  public boolean entryExists(DN entryDN) throws DirectoryException
413  {
414    return getEntry(entryDN) != null;
415  }
416
417  /**
418   * Adds the provided entry to this backend.  This method must ensure
419   * that the entry is appropriate for the backend and that no entry
420   * already exists with the same DN.  The caller must hold a write
421   * lock on the DN of the provided entry.
422   *
423   * @param  entry         The entry to add to this backend.
424   * @param  addOperation  The add operation with which the new entry
425   *                       is associated.  This may be {@code null}
426   *                       for adds performed internally.
427   *
428   * @throws  DirectoryException  If a problem occurs while trying to
429   *                              add the entry.
430   *
431   * @throws CanceledOperationException  If this backend noticed and
432   *                                       reacted to a request to
433   *                                       cancel or abandon the add
434   *                                       operation.
435   */
436  public abstract void addEntry(Entry entry, AddOperation addOperation)
437         throws DirectoryException, CanceledOperationException;
438
439  /**
440   * Removes the specified entry from this backend.  This method must
441   * ensure that the entry exists and that it does not have any
442   * subordinate entries (unless the backend supports a subtree delete
443   * operation and the client included the appropriate information in
444   * the request).  The caller must hold a write lock on the provided
445   * entry DN.
446   *
447   * @param  entryDN          The DN of the entry to remove from this
448   *                          backend.
449   * @param  deleteOperation  The delete operation with which this
450   *                          action is associated.  This may be
451   *                          {@code null} for deletes performed
452   *                          internally.
453   *
454   * @throws  DirectoryException  If a problem occurs while trying to
455   *                              remove the entry.
456   *
457   * @throws CanceledOperationException  If this backend noticed and
458   *                                       reacted to a request to
459   *                                       cancel or abandon the
460   *                                       delete operation.
461   */
462  public abstract void deleteEntry(DN entryDN, DeleteOperation deleteOperation)
463         throws DirectoryException, CanceledOperationException;
464
465  /**
466   * Replaces the specified entry with the provided entry in this
467   * backend. The backend must ensure that an entry already exists
468   * with the same DN as the provided entry. The caller must hold a
469   * write lock on the DN of the provided entry.
470   *
471   * @param oldEntry
472   *          The original entry that is being replaced.
473   * @param newEntry
474   *          The new entry to use in place of the existing entry with
475   *          the same DN.
476   * @param modifyOperation
477   *          The modify operation with which this action is
478   *          associated. This may be {@code null} for modifications
479   *          performed internally.
480   * @throws DirectoryException
481   *           If a problem occurs while trying to replace the entry.
482   * @throws CanceledOperationException
483   *           If this backend noticed and reacted to a request to
484   *           cancel or abandon the modify operation.
485   */
486  public abstract void replaceEntry(Entry oldEntry, Entry newEntry,
487      ModifyOperation modifyOperation) throws DirectoryException,
488      CanceledOperationException;
489
490  /**
491   * Moves and/or renames the provided entry in this backend, altering
492   * any subordinate entries as necessary. This must ensure that an
493   * entry already exists with the provided current DN, and that no
494   * entry exists with the target DN of the provided entry. The caller
495   * must hold write locks on both the current DN and the new DN for
496   * the entry.
497   *
498   * @param currentDN
499   *          The current DN of the entry to be moved/renamed.
500   * @param entry
501   *          The new content to use for the entry.
502   * @param modifyDNOperation
503   *          The modify DN operation with which this action is
504   *          associated. This may be {@code null} for modify DN
505   *          operations performed internally.
506   * @throws DirectoryException
507   *           If a problem occurs while trying to perform the rename.
508   * @throws CanceledOperationException
509   *           If this backend noticed and reacted to a request to
510   *           cancel or abandon the modify DN operation.
511   */
512  public abstract void renameEntry(DN currentDN, Entry entry, ModifyDNOperation modifyDNOperation)
513         throws DirectoryException, CanceledOperationException;
514
515  /**
516   * Processes the specified search in this backend.  Matching entries
517   * should be provided back to the core server using the
518   * {@code SearchOperation.returnEntry} method.  The caller is not
519   * required to have any locks when calling this operation.
520   *
521   * @param  searchOperation  The search operation to be processed.
522   *
523   * @throws  DirectoryException  If a problem occurs while processing
524   *                              the search.
525   *
526   * @throws CanceledOperationException  If this backend noticed and
527   *                                       reacted to a request to
528   *                                       cancel or abandon the
529   *                                       search operation.
530   */
531  public abstract void search(SearchOperation searchOperation)
532         throws DirectoryException, CanceledOperationException;
533
534  /**
535   * Retrieves the OIDs of the controls that may be supported by this
536   * backend.
537   *
538   * @return  The OIDs of the controls that may be supported by this
539   *          backend.
540   */
541  public abstract Set<String> getSupportedControls();
542
543  /**
544   * Indicates whether this backend supports the specified control.
545   *
546   * @param  controlOID  The OID of the control for which to make the
547   *                     determination.
548   *
549   * @return  {@code true} if this backends supports the control with
550   *          the specified OID, or {@code false} if it does not.
551   */
552  public final boolean supportsControl(String controlOID)
553  {
554    Set<String> supportedControls = getSupportedControls();
555    return supportedControls != null && supportedControls.contains(controlOID);
556  }
557
558  /**
559   * Retrieves the OIDs of the features that may be supported by this
560   * backend.
561   *
562   * @return  The OIDs of the features that may be supported by this
563   *          backend.
564   */
565  public abstract Set<String> getSupportedFeatures();
566
567  /** Enumeration of optional backend operations. */
568  public static enum BackendOperation
569  {
570    /** Indicates whether this backend supports indexing attributes to speed up searches. */
571    INDEXING,
572    /** Indicates whether this backend supports exporting the data it contains to an LDIF file. */
573    LDIF_EXPORT,
574    /** Indicates whether this backend supports importing its data from an LDIF file. */
575    LDIF_IMPORT,
576    /**
577     * Indicates whether this backend provides a backup mechanism of any kind. This method is used
578     * by the backup process when backing up all backends to determine whether this backend is one
579     * that should be skipped. It should only return {@code true} for backends that it is not
580     * possible to archive directly (e.g., those that don't store their data locally, but rather
581     * pass through requests to some other repository).
582     */
583    BACKUP,
584    /** Indicates whether this backend can restore a backup. */
585    RESTORE;
586  }
587
588  /**
589   * Indicates whether this backend supports the provided backend operation.
590   *
591   * @param backendOperation
592   *          the backend operation
593   * @return {@code true} if this backend supports the provided backend operation, {@code false}
594   *         otherwise.
595   */
596  public abstract boolean supports(BackendOperation backendOperation);
597
598  /**
599   * Exports the contents of this backend to LDIF. This method should only be called if
600   * {@link #supports(BackendOperation)} with {@link BackendOperation#LDIF_EXPORT} returns
601   * {@code true}.
602   * <p>
603   * Note that the server will not explicitly initialize this backend before calling this method.
604   *
605   * @param exportConfig
606   *          The configuration to use when performing the export.
607   * @throws DirectoryException
608   *           If a problem occurs while performing the LDIF export.
609   */
610  public abstract void exportLDIF(LDIFExportConfig exportConfig) throws DirectoryException;
611
612  /**
613   * Imports information from an LDIF file into this backend. This method should only be called if
614   * {@link #supports(BackendOperation)} with {@link BackendOperation#LDIF_IMPORT} returns
615   * {@code true}.
616   * <p>
617   * Note that the server will not explicitly initialize this backend before calling this method.
618   *
619   * @param importConfig
620   *          The configuration to use when performing the import.
621   * @param serverContext
622   *          The server context
623   * @return Information about the result of the import processing.
624   * @throws DirectoryException
625   *           If a problem occurs while performing the LDIF import.
626   */
627  public abstract LDIFImportResult importLDIF(LDIFImportConfig importConfig, ServerContext serverContext)
628      throws DirectoryException;
629
630  /**
631   * Verify the integrity of the backend instance.
632   *
633   * @param verifyConfig
634   *          The verify configuration.
635   * @return The results of the operation.
636   * @throws ConfigException
637   *           If an unrecoverable problem arises during initialization.
638   * @throws InitializationException
639   *           If a problem occurs during initialization that is not related to the server
640   *           configuration.
641   * @throws DirectoryException
642   *           If a Directory Server error occurs.
643   */
644  public long verifyBackend(VerifyConfig verifyConfig)
645      throws InitializationException, ConfigException, DirectoryException
646  {
647    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
648        ERR_INDEXES_NOT_SUPPORTED.get(getBackendID()));
649  }
650
651  /**
652   * Rebuild indexes in the backend instance. Note that the server will not explicitly initialize
653   * this backend before calling this method.
654   *
655   * @param rebuildConfig
656   *          The rebuild configuration.
657   * @param serverContext
658   *          The server context for this instance
659   * @throws ConfigException
660   *           If an unrecoverable problem arises during initialization.
661   * @throws InitializationException
662   *           If a problem occurs during initialization that is not related to the server
663   *           configuration.
664   * @throws DirectoryException
665   *           If a Directory Server error occurs.
666   */
667  public void rebuildBackend(RebuildConfig rebuildConfig, ServerContext serverContext)
668      throws InitializationException, ConfigException, DirectoryException
669  {
670    throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
671        ERR_INDEXES_NOT_SUPPORTED.get(getBackendID()));
672  }
673
674  /**
675   * Creates a backup of the contents of this backend in a form that may be restored at a later date
676   * if necessary. This method should only be called if {@link #supports(BackendOperation)} with
677   * {@link BackendOperation#BACKUP} returns {@code true}.
678   * <p>
679   * Note that the server will not explicitly initialize this backend before calling this method.
680   *
681   * @param backupConfig
682   *          The configuration to use when performing the backup.
683   * @throws DirectoryException
684   *           If a problem occurs while performing the backup.
685   */
686  public abstract void createBackup(BackupConfig backupConfig) throws DirectoryException;
687
688  /**
689   * Removes the specified backup if it is possible to do so.
690   *
691   * @param  backupDirectory  The backup directory structure with
692   *                          which the specified backup is
693   *                          associated.
694   * @param  backupID         The backup ID for the backup to be
695   *                          removed.
696   *
697   * @throws  DirectoryException  If it is not possible to remove the
698   *                              specified backup for some reason
699   *                              (e.g., no such backup exists or
700   *                              there are other backups that are
701   *                              dependent upon it).
702   */
703  public abstract void removeBackup(BackupDirectory backupDirectory, String backupID)
704         throws DirectoryException;
705
706  /**
707   * Restores a backup of the contents of this backend. This method should only be called if
708   * {@link #supports(BackendOperation)} with {@link BackendOperation#RESTORE} returns {@code true}.
709   * <p>
710   * Note that the server will not explicitly initialize this backend before calling this method.
711   *
712   * @param restoreConfig
713   *          The configuration to use when performing the restore.
714   * @throws DirectoryException
715   *           If a problem occurs while performing the restore.
716   */
717  public abstract void restoreBackup(RestoreConfig restoreConfig) throws DirectoryException;
718
719  /**
720   * Retrieves the unique identifier for this backend.
721   *
722   * @return  The unique identifier for this backend.
723   */
724  public final String getBackendID()
725  {
726    return backendID;
727  }
728
729  /**
730   * Specifies the unique identifier for this backend.
731   *
732   * @param  backendID  The unique identifier for this backend.
733   */
734  public final void setBackendID(String backendID)
735  {
736    this.backendID = backendID;
737  }
738
739  /**
740   * Indicates whether this backend holds private data or user data.
741   *
742   * @return  {@code true} if this backend holds private data, or
743   *          {@code false} if it holds user data.
744   */
745  public final boolean isPrivateBackend()
746  {
747    return isPrivateBackend;
748  }
749
750  /**
751   * Specifies whether this backend holds private data or user data.
752   *
753   * @param  isPrivateBackend  Specifies whether this backend holds
754   *                           private data or user data.
755   */
756  public final void setPrivateBackend(boolean isPrivateBackend)
757  {
758    this.isPrivateBackend = isPrivateBackend;
759  }
760
761  /**
762   * Retrieves the writability mode for this backend.
763   *
764   * @return  The writability mode for this backend.
765   */
766  public final WritabilityMode getWritabilityMode()
767  {
768    return writabilityMode;
769  }
770
771  /**
772   * Specifies the writability mode for this backend.
773   *
774   * @param  writabilityMode  The writability mode for this backend.
775   */
776  public final void setWritabilityMode(WritabilityMode writabilityMode)
777  {
778    this.writabilityMode = writabilityMode != null ? writabilityMode : WritabilityMode.ENABLED;
779  }
780
781  /**
782   * Retrieves the backend monitor that is associated with this
783   * backend.
784   *
785   * @return  The backend monitor that is associated with this
786   *          backend, or {@code null} if none has been assigned.
787   */
788  public final BackendMonitor getBackendMonitor()
789  {
790    return backendMonitor;
791  }
792
793  /**
794   * Registers the provided persistent search operation with this backend so
795   * that it will be notified of any add, delete, modify, or modify DN
796   * operations that are performed.
797   *
798   * @param persistentSearch
799   *          The persistent search operation to register with this backend
800   * @throws DirectoryException
801   *           If a problem occurs while registering the persistent search
802   */
803  public void registerPersistentSearch(PersistentSearch persistentSearch) throws DirectoryException
804  {
805    persistentSearches.add(persistentSearch);
806
807    persistentSearch.registerCancellationCallback(new CancellationCallback()
808    {
809      @Override
810      public void persistentSearchCancelled(PersistentSearch psearch)
811      {
812        persistentSearches.remove(psearch);
813      }
814    });
815  }
816
817  /**
818   * Returns the persistent searches currently active against this local
819   * backend.
820   *
821   * @return the list of persistent searches currently active against this local
822   *         backend
823   */
824  public Queue<PersistentSearch> getPersistentSearches()
825  {
826    return persistentSearches;
827  }
828
829  /**
830   * Sets the backend monitor for this backend.
831   *
832   * @param  backendMonitor  The backend monitor for this backend.
833   */
834  public final void setBackendMonitor(BackendMonitor backendMonitor)
835  {
836    this.backendMonitor = backendMonitor;
837  }
838
839  /**
840   * Retrieves the total number of entries contained in this backend,
841   * if that information is available.
842   *
843   * @return  The total number of entries contained in this backend,
844   *          or -1 if that information is not available.
845   */
846  public abstract long getEntryCount();
847
848  /**
849   * Retrieves the parent backend for this backend.
850   *
851   * @return  The parent backend for this backend, or {@code null} if
852   *          there is none.
853   */
854  public final Backend<?> getParentBackend()
855  {
856    return parentBackend;
857  }
858
859  /**
860   * Specifies the parent backend for this backend.
861   *
862   * @param  parentBackend  The parent backend for this backend.
863   */
864  public final synchronized void setParentBackend(Backend<?> parentBackend)
865  {
866    this.parentBackend = parentBackend;
867  }
868
869  /**
870   * Retrieves the set of subordinate backends for this backend.
871   *
872   * @return  The set of subordinate backends for this backend, or an
873   *          empty array if none exist.
874   */
875  public final Backend<?>[] getSubordinateBackends()
876  {
877    return subordinateBackends;
878  }
879
880  /**
881   * Adds the provided backend to the set of subordinate backends for
882   * this backend.
883   *
884   * @param  subordinateBackend  The backend to add to the set of
885   *                             subordinate backends for this
886   *                             backend.
887   */
888  public final synchronized void addSubordinateBackend(Backend<?> subordinateBackend)
889  {
890    LinkedHashSet<Backend<?>> backendSet = new LinkedHashSet<>();
891    Collections.addAll(backendSet, subordinateBackends);
892
893    if (backendSet.add(subordinateBackend))
894    {
895      subordinateBackends = backendSet.toArray(new Backend[backendSet.size()]);
896    }
897  }
898
899  /**
900   * Removes the provided backend from the set of subordinate backends
901   * for this backend.
902   *
903   * @param  subordinateBackend  The backend to remove from the set of
904   *                             subordinate backends for this
905   *                             backend.
906   */
907  public final synchronized void removeSubordinateBackend(Backend<?> subordinateBackend)
908  {
909    ArrayList<Backend<?>> backendList = new ArrayList<>(subordinateBackends.length);
910
911    boolean found = false;
912    for (Backend<?> b : subordinateBackends)
913    {
914      if (b.equals(subordinateBackend))
915      {
916        found = true;
917      }
918      else
919      {
920        backendList.add(b);
921      }
922    }
923
924    if (found)
925    {
926      subordinateBackends = backendList.toArray(new Backend[backendList.size()]);
927    }
928  }
929
930  /**
931   * Indicates whether this backend should be used to handle
932   * operations for the provided entry.
933   *
934   * @param  entryDN  The DN of the entry for which to make the
935   *                  determination.
936   *
937   * @return  {@code true} if this backend handles operations for the
938   *          provided entry, or {@code false} if it does not.
939   */
940  public final boolean handlesEntry(DN entryDN)
941  {
942    for (DN dn : getBaseDNs())
943    {
944      if (entryDN.isSubordinateOrEqualTo(dn))
945      {
946        for (Backend<?> b : subordinateBackends)
947        {
948          if (b.handlesEntry(entryDN))
949          {
950            return false;
951          }
952        }
953        return true;
954      }
955    }
956    return false;
957  }
958
959  /**
960   * Indicates whether a backend should be used to handle operations
961   * for the provided entry given the set of base DNs and exclude DNs.
962   *
963   * @param  entryDN     The DN of the entry for which to make the
964   *                     determination.
965   * @param  baseDNs     The set of base DNs for the backend.
966   * @param  excludeDNs  The set of DNs that should be excluded from
967   *                     the backend.
968   *
969   * @return  {@code true} if the backend should handle operations for
970   *          the provided entry, or {@code false} if it does not.
971   */
972  public static boolean handlesEntry(DN entryDN, Collection<DN> baseDNs, Collection<DN> excludeDNs)
973  {
974    for (DN baseDN : baseDNs)
975    {
976      if (entryDN.isSubordinateOrEqualTo(baseDN) && !isExcluded(excludeDNs, entryDN))
977      {
978        return true;
979      }
980    }
981    return false;
982  }
983
984  private static boolean isExcluded(Collection<DN> excludeDNs, DN entryDN)
985  {
986    if (excludeDNs == null || excludeDNs.isEmpty())
987    {
988      return false;
989    }
990    for (DN excludeDN : excludeDNs)
991    {
992      if (entryDN.isSubordinateOrEqualTo(excludeDN))
993      {
994        return true;
995      }
996    }
997    return false;
998  }
999}