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