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