/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE
* or https://OpenDS.dev.java.net/OpenDS.LICENSE.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at
* trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
* add the following below this CDDL HEADER, with the fields enclosed
* by brackets "[]" replaced with your own identifying * information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Portions Copyright 2006 Sun Microsystems, Inc.
*/
package org.opends.server.api;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import org.opends.server.config.ConfigEntry;
import org.opends.server.config.ConfigException;
import org.opends.server.core.AddOperation;
import org.opends.server.core.CancelledOperationException;
import org.opends.server.core.DeleteOperation;
import org.opends.server.core.DirectoryException;
import org.opends.server.core.DirectoryServer;
import org.opends.server.core.InitializationException;
import org.opends.server.core.LockManager;
import org.opends.server.core.ModifyOperation;
import org.opends.server.core.ModifyDNOperation;
import org.opends.server.core.SearchOperation;
import org.opends.server.types.BackupConfig;
import org.opends.server.types.BackupDirectory;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.LDIFExportConfig;
import org.opends.server.types.LDIFImportConfig;
import org.opends.server.types.RestoreConfig;
import org.opends.server.types.WritabilityMode;
import static org.opends.server.loggers.Debug.*;
import static org.opends.server.messages.BackendMessages.*;
import static org.opends.server.messages.MessageHandler.*;
/**
* This class defines the set of methods and structures that must be
* implemented for a Directory Server backend.
*/
public abstract class Backend
{
/**
* The fully-qualified name of this class for debugging purposes.
*/
private static final String CLASS_NAME =
"org.opends.server.api.Backend";
// The backend that holds a portion of the DIT that is
// hierarchically above the information in this backend.
private Backend parentBackend;
// The set of backends that hold portions of the DIT that are
// hierarchically below the information in this backend.
private Backend[] subordinateBackends;
// Indicates whether this is a private backend or one that holds
// user data.
private boolean isPrivateBackend;
// The unique identifier for this backend.
private String backendID;
// The writability mode for this backend.
private WritabilityMode writabilityMode;
/**
* Creates a new backend with the provided information. All backend
* implementations must implement a default constructor that use
* super() to invoke this constructor.
*/
protected Backend()
{
assert debugConstructor(CLASS_NAME);
backendID = null;
parentBackend = null;
subordinateBackends = new Backend[0];
isPrivateBackend = false;
writabilityMode = WritabilityMode.ENABLED;
}
/**
* Initializes this backend based on the information in the provided
* configuration entry.
*
* @param configEntry The configuration entry that contains the
* information to use to initialize this
* backend.
* @param baseDNs The set of base DNs that have been
* configured for this backend.
*
* @throws ConfigException If an unrecoverable problem arises in
* the process of performing the
* initialization.
*
* @throws InitializationException If a problem occurs during
* initialization that is not
* related to the server
* configuration.
*/
public abstract void initializeBackend(ConfigEntry configEntry,
DN[] baseDNs)
throws ConfigException, InitializationException;
/**
* Performs any necessary work to finalize this backend, including
* closing any underlying databases or connections and deregistering
* any suffixes that it manages with the Directory Server. This may
* be called during the Directory Server shutdown process or if a
* backend is disabled with the server online. It must not return
* until the backend is closed.
*
* This method may not throw any exceptions. If any problems are
* encountered, then they may be logged but the closure should
* progress as completely as possible.
*/
public abstract void finalizeBackend();
/**
* Retrieves the set of base-level DNs that may be used within this
* backend.
*
* @return The set of base-level DNs that may be used within this
* backend.
*/
public abstract DN[] getBaseDNs();
/**
* Indicates whether the data associated with this backend may be
* considered local (i.e., in a repository managed by the Directory
* Server) rather than remote (i.e., in an external repository
* accessed by the Directory Server but managed through some other
* means).
*
* @return true if the data associated with this
* backend may be considered local, or false
* if it is remote.
*/
public abstract boolean isLocal();
/**
* Retrieves the requested entry from this backend. Note that the
* caller must hold a read or write lock on the specified DN.
*
* @param entryDN The distinguished name of the entry to retrieve.
*
* @return The requested entry, or null if the entry
* does not exist.
*
* @throws DirectoryException If a problem occurs while trying to
* retrieve the entry.
*/
public abstract Entry getEntry(DN entryDN)
throws DirectoryException;
/**
* Indicates whether an entry with the specified DN exists in the
* backend. The default implementation obtains a read lock and calls
* getEntry, but backend implementations may override
* this with a more efficient version that does not require a lock.
* The caller is not required to hold any locks on the specified DN.
*
* @param entryDN The DN of the entry for which to determine
* existence.
*
* @return true if the specified entry exists in this
* backend, or false if it does not.
*
* @throws DirectoryException If a problem occurs while trying to
* make the determination.
*/
public boolean entryExists(DN entryDN)
throws DirectoryException
{
assert debugEnter(CLASS_NAME, "entryExists",
String.valueOf(entryDN));
Lock lock = null;
for (int i=0; i < 3; i++)
{
lock = LockManager.lockRead(entryDN);
if (lock != null)
{
break;
}
}
if (lock == null)
{
int msgID = MSGID_BACKEND_CANNOT_LOCK_ENTRY;
String message = getMessage(msgID, String.valueOf(entryDN));
throw new DirectoryException(
DirectoryServer.getServerErrorResultCode(),
message, msgID);
}
try
{
return (getEntry(entryDN) != null);
}
finally
{
LockManager.unlock(entryDN, lock);
}
}
/**
* Adds the provided entry to this backend. This method must ensure
* that the entry is appropriate for the backend and that no entry
* already exists with the same DN. The caller must hold a write
* lock on the DN of the provided entry.
*
* @param entry The entry to add to this backend.
* @param addOperation The add operation with which the new entry
* is associated. This may be
* null for adds performed
* internally.
*
* @throws DirectoryException If a problem occurs while trying to
* add the entry.
*
* @throws CancelledOperationException If this backend noticed and
* reacted to a request to
* cancel or abandon the add
* operation.
*/
public abstract void addEntry(Entry entry,
AddOperation addOperation)
throws DirectoryException, CancelledOperationException;
/**
* Removes the specified entry from this backend. This method must
* ensure that the entry exists and that it does not have any
* subordinate entries (unless the backend supports a subtree delete
* operation and the client included the appropriate information in
* the request). The caller must hold a write lock on the provided
* entry DN.
*
* @param entryDN The DN of the entry to remove from this
* backend.
* @param deleteOperation The delete operation with which this
* action is associated. This may be
* null for deletes performed
* internally.
*
* @throws DirectoryException If a problem occurs while trying to
* remove the entry.
*
* @throws CancelledOperationException If this backend noticed and
* reacted to a request to
* cancel or abandon the
* delete operation.
*/
public abstract void deleteEntry(DN entryDN,
DeleteOperation deleteOperation)
throws DirectoryException, CancelledOperationException;
/**
* Replaces the specified entry with the provided entry in this
* backend. The backend must ensure that an entry already exists
* with the same DN as the provided entry. The caller must hold a
* write lock on the DN of the provided entry.
*
* @param entry The new entry to use in place of the
* existing entry with the same DN.
* @param modifyOperation The modify operation with which this
* action is associated. This may be
* null for modifications
* performed internally.
*
* @throws DirectoryException If a problem occurs while trying to
* replace the entry.
*
* @throws CancelledOperationException If this backend noticed and
* reacted to a request to
* cancel or abandon the
* modify operation.
*/
public abstract void replaceEntry(Entry entry,
ModifyOperation modifyOperation)
throws DirectoryException, CancelledOperationException;
/**
* Moves and/or renames the provided entry in this backend, altering
* any subordinate entries as necessary. This must ensure that an
* entry already exists with the provided current DN, and that no
* entry exists with the target DN of the provided entry. The
* caller must hold write locks on both the current DN and the new
* DN for the entry.
*
* @param currentDN The current DN of the entry to be
* replaced.
* @param entry The new content to use for the entry.
* @param modifyDNOperation The modify DN operation with which
* this action is associated. This may
* be null
* for modify DN operations performed
* internally.
*
* @throws DirectoryException If a problem occurs while trying to
* perform the rename.
*
* @throws CancelledOperationException If this backend noticed and
* reacted to a request to
* cancel or abandon the
* modify DN operation.
*/
public abstract void renameEntry(DN currentDN, Entry entry,
ModifyDNOperation modifyDNOperation)
throws DirectoryException, CancelledOperationException;
/**
* Processes the specified search in this backend. Matching entries
* should be provided back to the core server using the
* SearchOperation.returnEntry method. The caller is
* not required to have any locks when calling this operation.
*
* @param searchOperation The search operation to be processed.
*
* @throws DirectoryException If a problem occurs while processing
* the search.
*
* @throws CancelledOperationException If this backend noticed and
* reacted to a request to
* cancel or abandon the
* search operation.
*/
public abstract void search(SearchOperation searchOperation)
throws DirectoryException, CancelledOperationException;
/**
* Retrieves the OIDs of the controls that may be supported by this
* backend.
*
* @return The OIDs of the controls that may be supported by this
* backend.
*/
public abstract Set getSupportedControls();
/**
* Indicates whether this backend supports the specified control.
*
* @param controlOID The OID of the control for which to make the
* determination.
*
* @return true if this backend does support the
* requested control, or false
*/
public abstract boolean supportsControl(String controlOID);
/**
* Retrieves the OIDs of the features that may be supported by this
* backend.
*
* @return The OIDs of the features that may be supported by this
* backend.
*/
public abstract Set getSupportedFeatures();
/**
* Indicates whether this backend supports the specified feature.
*
* @param featureOID The OID of the feature for which to make the
* determination.
*
* @return true if this backend does support the
* requested feature, or false
*/
public abstract boolean supportsFeature(String featureOID);
/**
* Indicates whether this backend provides a mechanism to export the
* data it contains to an LDIF file.
*
* @return true if this backend provides an LDIF
* export mechanism, or false if not.
*/
public abstract boolean supportsLDIFExport();
/**
* Exports the contents of this backend to LDIF. This method should
* only be called if supportsLDIFExport returns
* true. Note that the server will not explicitly
* initialize this backend before calling this method.
*
* @param configEntry The configuration entry for this backend.
* @param baseDNs The set of base DNs configured for this
* backend.
* @param exportConfig The configuration to use when performing
* the export.
*
* @throws DirectoryException If a problem occurs while performing
* the LDIF export.
*/
public abstract void exportLDIF(ConfigEntry configEntry,
DN[] baseDNs,
LDIFExportConfig exportConfig)
throws DirectoryException;
/**
* Indicates whether this backend provides a mechanism to import its
* data from an LDIF file.
*
* @return true if this backend provides an LDIF
* import mechanism, or false if not.
*/
public abstract boolean supportsLDIFImport();
/**
* Imports information from an LDIF file into this backend. This
* method should only be called if supportsLDIFImport
* returns true. Note that the server will not
* explicitly initialize this backend before calling this method.
*
* @param configEntry The configuration entry for this backend.
* @param baseDNs The set of base DNs configured for this
* backend.
* @param importConfig The configuration to use when performing
* the import.
*
* @throws DirectoryException If a problem occurs while performing
* the LDIF import.
*/
public abstract void importLDIF(ConfigEntry configEntry,
DN[] baseDNs,
LDIFImportConfig importConfig)
throws DirectoryException;
/**
* Indicates whether this backend provides a backup mechanism of any
* kind. This method is used by the backup process when backing up
* all backends to determine whether this backend is one that should
* be skipped. It should only return true for backends
* that it is not possible to archive directly (e.g., those that
* don't store their data locally, but rather pass through requests
* to some other repository).
*
* @return true if this backend provides any kind of
* backup mechanism, or false if it does not.
*/
public abstract boolean supportsBackup();
/**
* Indicates whether this backend provides a mechanism to perform a
* backup of its contents in a form that can be restored later,
* based on the provided configuration.
*
* @param backupConfig The configuration of the backup for
* which to make the determination.
* @param unsupportedReason A buffer to which a message can be
* appended
* explaining why the requested backup is
* not supported.
*
* @return true if this backend provides a mechanism
* for performing backups with the provided configuration,
* or false if not.
*/
public abstract boolean supportsBackup(BackupConfig backupConfig,
StringBuilder unsupportedReason);
/**
* Creates a backup of the contents of this backend in a form that
* may be restored at a later date if necessary. This method should
* only be called if supportsBackup returns
* true. Note that the server will not explicitly
* initialize this backend before calling this method.
*
* @param configEntry The configuration entry for this backend.
* @param backupConfig The configuration to use when performing
* the backup.
*
* @throws DirectoryException If a problem occurs while performing
* the backup.
*/
public abstract void createBackup(ConfigEntry configEntry,
BackupConfig backupConfig)
throws DirectoryException;
/**
* Removes the specified backup if it is possible to do so.
*
* @param backupDirectory The backup directory structure with
* which the specified backup is
* associated.
* @param backupID The backup ID for the backup to be
* removed.
*
* @throws DirectoryException If it is not possible to remove the
* specified backup for some reason
* (e.g., no such backup exists or
* there are other backups that are
* dependent upon it).
*/
public abstract void removeBackup(BackupDirectory backupDirectory,
String backupID)
throws DirectoryException;
/**
* Indicates whether this backend provides a mechanism to restore a
* backup.
*
* @return true if this backend provides a mechanism
* for restoring backups, or false if not.
*/
public abstract boolean supportsRestore();
/**
* Restores a backup of the contents of this backend. This method
* should only be called if supportsRestore returns
* true. Note that the server will not explicitly
* initialize this backend before calling this method.
*
* @param configEntry The configuration entry for this backend.
* @param restoreConfig The configuration to use when performing
* the restore.
*
* @throws DirectoryException If a problem occurs while performing
* the restore.
*/
public abstract void restoreBackup(ConfigEntry configEntry,
RestoreConfig restoreConfig)
throws DirectoryException;
/**
* Retrieves the unique identifier for this backend.
*
* @return The unique identifier for this backend.
*/
public String getBackendID()
{
assert debugEnter(CLASS_NAME, "getBackendID");
return backendID;
}
/**
* Specifies the unique identifier for this backend.
*
* @param backendID The unique identifier for this backend.
*/
public void setBackendID(String backendID)
{
assert debugEnter(CLASS_NAME, "setBackendID",
String.valueOf(backendID));
this.backendID = backendID;
}
/**
* Indicates whether this backend holds private data or user data.
*
* @return true if this backend holds private data, or
* false if it holds user data.
*/
public boolean isPrivateBackend()
{
assert debugEnter(CLASS_NAME, "isPrivateBackend");
return isPrivateBackend;
}
/**
* Specifies whether this backend holds private data or user data.
*
* @param isPrivateBackend Specifies whether this backend holds
* private data or user data.
*/
public void setPrivateBackend(boolean isPrivateBackend)
{
this.isPrivateBackend = isPrivateBackend;
}
/**
* Retrieves the writability mode for this backend.
*
* @return The writability mode for this backend.
*/
public WritabilityMode getWritabilityMode()
{
assert debugEnter(CLASS_NAME, "getWritabilityMode");
return writabilityMode;
}
/**
* Specifies the writability mode for this backend.
*
* @param writabilityMode The writability mode for this backend.
*/
public void setWritabilityMode(WritabilityMode writabilityMode)
{
assert debugEnter(CLASS_NAME, "setWritabilityMode",
String.valueOf(writabilityMode));
if (writabilityMode == null)
{
this.writabilityMode = WritabilityMode.ENABLED;
}
else
{
this.writabilityMode = writabilityMode;
}
}
/**
* Retrieves the parent backend for this backend.
*
* @return The parent backend for this backend, or
* null if there is none.
*/
public Backend getParentBackend()
{
assert debugEnter(CLASS_NAME, "getParentBackend");
return parentBackend;
}
/**
* Specifies the parent backend for this backend.
*
* @param parentBackend The parent backend for this backend.
*/
public void setParentBackend(Backend parentBackend)
{
assert debugEnter(CLASS_NAME, "setParentBackend",
String.valueOf(parentBackend));
synchronized (this)
{
this.parentBackend = parentBackend;
}
}
/**
* Retrieves the set of subordinate backends for this backend.
*
* @return The set of subordinate backends for this backend, or an
* empty array if none exist.
*/
public Backend[] getSubordinateBackends()
{
assert debugEnter(CLASS_NAME, "getSubordinateBackends");
return subordinateBackends;
}
/**
* Specifies the set of subordinate backends for this backend.
*
* @param subordinateBackends The set of subordinate backends for
* this backend.
*/
public void setSubordinateBackends(Backend[] subordinateBackends)
{
assert debugEnter(CLASS_NAME, "setSubordinateBackends",
String.valueOf(subordinateBackends));
synchronized (this)
{
this.subordinateBackends = subordinateBackends;
}
}
/**
* Indicates whether this backend has a subordinate backend
* registered with the provided base DN. This may check recursively
* if a subordinate backend has its own subordinate backends.
*
* @param subSuffixDN The DN of the sub-suffix for which to make
* the determination.
*
* @return true if this backend has a subordinate
* backend registered with the provided base DN, or
* false if it does not.
*/
public boolean hasSubSuffix(DN subSuffixDN)
{
assert debugEnter(CLASS_NAME, "hasSubSuffix",
String.valueOf(subSuffixDN));
Backend[] subBackends = subordinateBackends;
for (Backend b : subBackends)
{
for (DN baseDN : b.getBaseDNs())
{
if (baseDN.equals(subSuffixDN))
{
return true;
}
}
if (b.hasSubSuffix(subSuffixDN))
{
return true;
}
}
return false;
}
/**
* Removes the backend associated with the specified sub-suffix if
* it is registered. This may check recursively if a subordinate
* backend has its own subordinate backends.
*
* @param subSuffixDN The DN of the sub-suffix to remove from this
* backend.
* @param parentDN The superior DN for the sub-suffix DN that
* matches one of the subordinate base DNs for
* this backend.
*
* @throws ConfigException If the sub-suffix exists but it is not
* possible to remove it for some reason.
*/
public void removeSubSuffix(DN subSuffixDN, DN parentDN)
throws ConfigException
{
assert debugEnter(CLASS_NAME, "removeSubSuffix",
String.valueOf(subSuffixDN));
synchronized (this)
{
boolean matchFound = false;
ArrayList subBackendList =
new ArrayList(subordinateBackends.length);
for (Backend b : subordinateBackends)
{
boolean thisMatches = false;
DN[] subBaseDNs = b.getBaseDNs();
for (int i=0; i < subBaseDNs.length; i++)
{
if (subBaseDNs[i].equals(subSuffixDN))
{
if (subBaseDNs.length > 1)
{
int msgID =
MSGID_BACKEND_CANNOT_REMOVE_MULTIBASE_SUB_SUFFIX;
String message = getMessage(msgID,
String.valueOf(subSuffixDN));
throw new ConfigException(msgID, message);
}
thisMatches = true;
matchFound = true;
break;
}
}
if (! thisMatches)
{
if (b.hasSubSuffix(subSuffixDN))
{
b.removeSubSuffix(subSuffixDN, parentDN);
}
else
{
subBackendList.add(b);
}
}
}
if (matchFound)
{
Backend[] newSubordinateBackends =
new Backend[subBackendList.size()];
subBackendList.toArray(newSubordinateBackends);
subordinateBackends = newSubordinateBackends;
}
}
}
/**
* Adds the provided backend to the set of subordinate backends for
* this backend.
*
* @param subordinateBackend The backend to add to the set of
* subordinate backends for this
* backend.
*/
public void addSubordinateBackend(Backend subordinateBackend)
{
assert debugEnter(CLASS_NAME, "addSubordinateBackend",
String.valueOf(subordinateBackend));
synchronized (this)
{
Backend[] newSubordinateBackends =
new Backend[subordinateBackends.length+1];
System.arraycopy(subordinateBackends, 0, newSubordinateBackends,
0, subordinateBackends.length);
newSubordinateBackends[subordinateBackends.length] =
subordinateBackend;
subordinateBackends = newSubordinateBackends;
}
}
/**
* Indicates whether this backend should be used to handle
* operations for the provided entry.
*
* @param entryDN The DN of the entry for which to make the
* determination.
*
* @return true if this backend handles operations for
* the provided entry, or false if it does
* not.
*/
public boolean handlesEntry(DN entryDN)
{
assert debugEnter(CLASS_NAME, "handlesEntry",
String.valueOf(entryDN));
DN[] baseDNs = getBaseDNs();
for (int i=0; i < baseDNs.length; i++)
{
if (entryDN.isDescendantOf(baseDNs[i]))
{
Backend[] subBackends = subordinateBackends;
for (int j=0; j < subBackends.length; j++)
{
if (subBackends[j].handlesEntry(entryDN))
{
return false;
}
}
return true;
}
}
return false;
}
/**
* Indicates whether a backend should be used to handle operations
* for the provided entry given the set of base DNs and exclude DNs.
*
* @param entryDN The DN of the entry for which to make the
* determination.
* @param baseDNs The set of base DNs for the backend.
* @param excludeDNs The set of DNs that should be excluded from
* the backend.
*
* @return true if the backend should handle
* operations for the provided entry, or false
* if it does not.
*/
public static final boolean handlesEntry(DN entryDN,
List baseDNs,
List excludeDNs)
{
assert debugEnter(CLASS_NAME, "handlesEntry",
String.valueOf(entryDN));
for (DN baseDN : baseDNs)
{
if (entryDN.isDescendantOf(baseDN))
{
if ((excludeDNs == null) || excludeDNs.isEmpty())
{
return true;
}
boolean isExcluded = false;
for (DN excludeDN : excludeDNs)
{
if (entryDN.isDescendantOf(excludeDN))
{
isExcluded = true;
break;
}
}
if (! isExcluded)
{
return true;
}
}
}
return false;
}
}