BackendImpl.java revision 5fc3f6aeb6d23f3b163a677519105469cb6defee
/*
* 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
* 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-2007 Sun Microsystems, Inc.
*/
/**
* This is an implementation of a Directory Server Backend which stores entries
* locally in a Berkeley DB JE database.
*/
public class BackendImpl
extends Backend
{
/**
* The tracer object for the debug logger.
*/
/**
* The fully-qualified name of this class.
*/
private static final String CLASS_NAME =
"org.opends.server.backends.jeb.BackendImpl";
/**
* The configuration of this JE backend.
*/
private JEBackendCfg cfg;
/**
* The root JE container to use for this backend.
*/
private RootContainer rootContainer;
/**
* A count of the total operation threads currently in the backend.
*/
/**
* A count of the write operation threads currently in the backend.
*/
/**
* A list of monitor providers created for this backend instance.
*/
new ArrayList<MonitorProvider>();
/**
* The controls supported by this backend.
*/
static
{
// Set our supported controls.
}
/**
* Begin a Backend API method that reads the database.
*/
private void readerBegin()
{
}
/**
* End a Backend API method that reads the database.
*/
private void readerEnd()
{
}
/**
* Begin a Backend API method that writes the database.
*/
private void writerBegin()
{
}
/**
* End a Backend API method that writes the database.
*/
private void writerEnd()
{
}
/**
* Wait until there are no more threads accessing the database. It is assumed
* that new threads have been prevented from entering the database at the time
* this method is called.
*/
private void waitUntilQuiescent()
{
{
// Still have threads in the database so sleep a little
try
{
}
catch (InterruptedException e)
{
if (debugEnabled())
{
}
}
}
}
/**
* This method will attempt to checksum the current JE db environment by
* computing the Adler-32 checksum on the latest JE log file available.
*
* @return The checksum of JE db environment or zero if checksum failed.
*/
private long checksumDbEnv() {
if(backendDirectory.isDirectory())
{
jdbFiles =
}
}));
}
try {
byte[] tempBuf = new byte[8192];
}
} catch (Exception e) {
if (debugEnabled())
{
}
} finally {
try {
} catch (Exception e) {
if (debugEnabled())
{
}
}
}
}
}
return 0;
}
/**
* This method constructs a container name from a base DN. Only alphanumeric
* characters are preserved, all other characters are replaced with an
* underscore.
*
* @param dn The base DN.
* @return The container name for the base DN.
*/
{
{
{
}
else
{
}
}
}
/**
* {@inheritDoc}
*/
throws ConfigException
{
}
/**
* {@inheritDoc}
*/
public void initializeBackend()
{
checksumDbEnv());
if(rootContainer == null)
{
}
// Preload the database cache.
try
{
// Log an informational message about the number of entries.
}
{
if (debugEnabled())
{
}
throw new InitializationException(
}
{
try
{
}
catch (Exception e)
{
if (debugEnabled())
{
}
throw new InitializationException(message, e);
}
}
// Register a monitor provider for the environment.
//Register as an AlertGenerator.
// Register this backend as a change listener.
cfg.addJEChangeListener(this);
}
/**
* 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. <BR><BR> 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 void finalizeBackend()
{
// Deregister as a change listener.
cfg.removeJEChangeListener(this);
// Deregister our base DNs.
{
try
{
}
catch (Exception e)
{
if (debugEnabled())
{
}
}
}
// Deregister our monitor providers.
{
}
// We presume the server will prevent more operations coming into this
// backend, but there may be existing operations already in the
// backend. We need to wait for them to finish.
// Close the database.
try
{
}
catch (DatabaseException e)
{
if (debugEnabled())
{
}
}
checksumDbEnv());
//Deregister the alert generator.
// Make sure the thread counts are zero for next initialization.
}
/**
* 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 <CODE>true</CODE> if the data associated with this backend may be
* considered local, or <CODE>false</CODE> if it is remote.
*/
public boolean isLocal()
{
return true;
}
/**
* Indicates whether this backend provides a mechanism to export the data it
* contains to an LDIF file.
*
* @return <CODE>true</CODE> if this backend provides an LDIF export
* mechanism, or <CODE>false</CODE> if not.
*/
public boolean supportsLDIFExport()
{
return true;
}
/**
* Indicates whether this backend provides a mechanism to import its data from
* an LDIF file.
*
* @return <CODE>true</CODE> if this backend provides an LDIF import
* mechanism, or <CODE>false</CODE> if not.
*/
public boolean supportsLDIFImport()
{
return true;
}
/**
* 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 <CODE>true</CODE> 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 <CODE>true</CODE> if this backend provides any kind of backup
* mechanism, or <CODE>false</CODE> if it does not.
*/
public boolean supportsBackup()
{
return true;
}
/**
* 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 <CODE>true</CODE> if this backend provides a mechanism for
* performing backups with the provided configuration, or
* <CODE>false</CODE> if not.
*/
{
return true;
}
/**
* Indicates whether this backend provides a mechanism to restore a backup.
*
* @return <CODE>true</CODE> if this backend provides a mechanism for
* restoring backups, or <CODE>false</CODE> if not.
*/
public boolean supportsRestore()
{
return true;
}
/**
* 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.
*/
{
}
/**
* 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.
*/
{
return supportedControls;
}
/**
* 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 DN[] getBaseDNs()
{
}
/**
* {@inheritDoc}
*/
public long getEntryCount()
{
if (rootContainer != null)
{
try
{
return rootContainer.getEntryCount();
}
catch (Exception e)
{
if (debugEnabled())
{
}
}
}
return -1;
}
/**
* {@inheritDoc}
*/
{
if(ret < 0)
{
return ConditionResult.UNDEFINED;
}
else if(ret == 0)
{
return ConditionResult.FALSE;
}
else
{
return ConditionResult.TRUE;
}
}
/**
* {@inheritDoc}
*/
{
if (rootContainer != null)
{
}
else
{
message);
}
{
return -1;
}
readerBegin();
try
{
{
// The index entry limit has exceeded and there is no count maintained.
return -1;
}
return count;
}
catch (DatabaseException e)
{
if (debugEnabled())
{
}
throw createDirectoryException(e);
}
finally
{
readerEnd();
}
}
/**
* 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 <CODE>null</CODE> if the entry does not
* exist.
* @throws DirectoryException If a problem occurs while trying to retrieve the
* entry.
*/
{
readerBegin();
if (rootContainer != null)
{
}
else
{
message);
}
try
{
}
catch (DatabaseException e)
{
if (debugEnabled())
{
}
throw createDirectoryException(e);
}
catch (JebException e)
{
if (debugEnabled())
{
}
e.getMessageObject());
}
finally
{
readerEnd();
}
return entry;
}
/**
* 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.
*
* @param entry The entry to add to this backend.
* @param addOperation The add operation with which the new entry is
* associated. This may be <CODE>null</CODE> for adds
* performed internally.
* @throws DirectoryException If a problem occurs while trying to add the
* entry.
*/
throws DirectoryException
{
writerBegin();
if (rootContainer != null)
{
}
else
{
message);
}
try
{
}
catch (DatabaseException e)
{
if (debugEnabled())
{
}
throw createDirectoryException(e);
}
catch (JebException e)
{
if (debugEnabled())
{
}
e.getMessageObject());
}
finally
{
writerEnd();
}
}
/**
* 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).
*
* @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 <CODE>null</CODE> for
* deletes performed internally.
* @throws DirectoryException If a problem occurs while trying to remove the
* entry.
*/
throws DirectoryException
{
writerBegin();
if (rootContainer != null)
{
}
else
{
message);
}
try
{
}
catch (DatabaseException e)
{
if (debugEnabled())
{
}
throw createDirectoryException(e);
}
catch (JebException e)
{
if (debugEnabled())
{
}
e.getMessageObject());
}
finally
{
writerEnd();
}
}
/**
* 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.
*
* @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 <CODE>null</CODE> for
* modifications performed internally.
* @throws DirectoryException If a problem occurs while trying to replace the
* entry.
*/
throws DirectoryException
{
writerBegin();
if (rootContainer != null)
{
}
else
{
message);
}
try
{
}
catch (DatabaseException e)
{
if (debugEnabled())
{
}
throw createDirectoryException(e);
}
catch (JebException e)
{
if (debugEnabled())
{
}
e.getMessageObject());
}
finally
{
writerEnd();
}
}
/**
* 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 <CODE>null</CODE> for
* modify DN operations performed internally.
* @throws org.opends.server.types.DirectoryException
* If a problem occurs while trying to perform the rename.
* @throws org.opends.server.types.CancelledOperationException
* If this backend noticed and reacted to a request to cancel or
* abandon the modify DN operation.
*/
{
writerBegin();
if (rootContainer != null)
{
}
else
{
message);
}
if (currentContainer != container)
{
// FIXME: No reason why we cannot implement a move between containers
// since the containers share the same database environment.
msg);
}
try
{
}
catch (DatabaseException e)
{
if (debugEnabled())
{
}
throw createDirectoryException(e);
}
catch (JebException e)
{
if (debugEnabled())
{
}
e.getMessageObject());
}
finally
{
writerEnd();
}
}
/**
* Processes the specified search in this backend. Matching entries should be
* provided back to the core server using the
* <CODE>SearchOperation.returnEntry</CODE> method.
*
* @param searchOperation The search operation to be processed.
* @throws org.opends.server.types.DirectoryException
* If a problem occurs while processing the search.
*/
throws DirectoryException
{
readerBegin();
if (rootContainer != null)
{
}
else
{
message);
}
try
{
}
catch (DatabaseException e)
{
if (debugEnabled())
{
}
throw createDirectoryException(e);
}
catch (JebException e)
{
if (debugEnabled())
{
}
message);
}
finally
{
readerEnd();
}
}
/**
* {@inheritDoc}
*/
throws DirectoryException
{
// If the backend already has the root container open, we must use the same
// underlying root container
try
{
{
envConfig.setReadOnly(true);
envConfig.setAllowCreate(false);
envConfig.setTransactional(false);
envConfig.setTxnNoSync(false);
}
}
catch (IOException ioe)
{
if (debugEnabled())
{
}
message);
}
catch (JebException je)
{
if (debugEnabled())
{
}
je.getMessageObject());
}
catch (DatabaseException de)
{
if (debugEnabled())
{
}
throw createDirectoryException(de);
}
catch (LDIFException e)
{
if (debugEnabled())
{
}
e.getMessageObject());
}
catch (InitializationException ie)
{
if (debugEnabled())
{
}
ie.getMessageObject());
}
catch (ConfigException ce)
{
if (debugEnabled())
{
}
ce.getMessageObject());
}
finally
{
//If a root container was opened in this method as read only, close it
//to leave the backend in the same state.
{
try
{
}
catch (DatabaseException e)
{
if (debugEnabled())
{
}
}
}
}
}
/**
* {@inheritDoc}
*/
throws DirectoryException
{
// If the backend already has the root container open, we must use the same
// underlying root container
// If the rootContainer is open, the backend is initialized by something
// else.
// We can't do import while the backend is online.
if(!openRootContainer)
{
message);
}
try
{
/**
envConfig.setConfigParam("je.env.runCleaner", "false");
envConfig.setConfigParam("je.log.numBuffers", "2");
envConfig.setConfigParam("je.log.bufferSize", "15000000");
envConfig.setConfigParam("je.log.totalBufferBytes", "30000000");
envConfig.setConfigParam("je.log.fileMax", "100000000");
**/
if (importConfig.appendToExistingData())
{
envConfig.setReadOnly(false);
envConfig.setAllowCreate(true);
envConfig.setTransactional(true);
envConfig.setTxnNoSync(true);
}
{
// We have the writer lock on the environment, now delete the
// environment and re-open it. Only do this when we are
// importing to all the base DNs in the backend or if the backend only
// have one base DN.
envConfig.setReadOnly(false);
envConfig.setAllowCreate(true);
envConfig.setTransactional(false);
envConfig.setTxnNoSync(false);
}
}
catch (IOException ioe)
{
if (debugEnabled())
{
}
message);
}
catch (JebException je)
{
if (debugEnabled())
{
}
je.getMessageObject());
}
catch (DatabaseException de)
{
if (debugEnabled())
{
}
throw createDirectoryException(de);
}
catch (InitializationException ie)
{
if (debugEnabled())
{
}
ie.getMessageObject());
}
catch (ConfigException ce)
{
if (debugEnabled())
{
}
ce.getMessageObject());
}
finally
{
// leave the backend in the same state.
try
{
// Sync the environment to disk.
if (debugEnabled())
{
}
}
catch (DatabaseException de)
{
if (debugEnabled())
{
}
}
}
}
/**
* Verify the integrity of the backend instance.
* @param verifyConfig The verify configuration.
* @param statEntry Optional entry to save stats into.
* @return The error count.
* @throws ConfigException If an unrecoverable problem arises during
* initialization.
* @throws InitializationException If a problem occurs during initialization
* that is not related to the server
* configuration.
* @throws DirectoryException If a Directory Server error occurs.
*/
{
// If the backend already has the root container open, we must use the same
// underlying root container
long errorCount = 0 ;
try
{
{
envConfig.setReadOnly(true);
envConfig.setAllowCreate(false);
envConfig.setTransactional(false);
envConfig.setTxnNoSync(false);
}
}
catch (DatabaseException e)
{
if (debugEnabled())
{
}
throw createDirectoryException(e);
}
catch (JebException e)
{
if (debugEnabled())
{
}
e.getMessageObject());
}
finally
{
//If a root container was opened in this method as read only, close it
//to leave the backend in the same state.
{
try
{
}
catch (DatabaseException e)
{
if (debugEnabled())
{
}
}
}
}
return errorCount;
}
/**
* Rebuild index(es) in the backend instance. Note that the server will not
* explicitly initialize this backend before calling this method.
* @param rebuildConfig The rebuild configuration.
* @throws ConfigException If an unrecoverable problem arises during
* initialization.
* @throws InitializationException If a problem occurs during initialization
* that is not related to the server
* configuration.
* @throws DirectoryException If a Directory Server error occurs.
*/
{
// If the backend already has the root container open, we must use the same
// underlying root container
// If the rootContainer is open, the backend is initialized by something
// else.
// We can't do any rebuild of system indexes while others are using this
// backend. Throw error. TODO: Need to make baseDNs disablable.
{
message);
}
try
{
if (openRootContainer)
{
}
}
catch (DatabaseException e)
{
if (debugEnabled())
{
}
throw createDirectoryException(e);
}
catch (JebException e)
{
if (debugEnabled())
{
}
e.getMessageObject());
}
finally
{
//If a root container was opened in this method as read only, close it
//to leave the backend in the same state.
{
try
{
}
catch (DatabaseException e)
{
if (debugEnabled())
{
}
}
}
}
}
/**
* {@inheritDoc}
*/
throws DirectoryException
{
new BackupManager(getBackendID());
}
/**
* {@inheritDoc}
*/
throws DirectoryException
{
new BackupManager(getBackendID());
}
/**
* {@inheritDoc}
*/
throws DirectoryException
{
new BackupManager(getBackendID());
}
/**
* {@inheritDoc}
*/
@Override()
{
}
/**
* {@inheritDoc}
*/
public boolean isConfigurationChangeAcceptable(
{
// Make sure that the logging level value is acceptable.
{
return false;
}
return true;
}
/**
* {@inheritDoc}
*/
{
try
{
if(rootContainer != null)
{
// Check for changes to the base DNs.
{
boolean found = false;
{
{
found = true;
}
}
if (!found)
{
// The base DN was deleted.
}
}
{
{
try
{
// The base DN was added.
}
catch (Exception e)
{
if (debugEnabled())
{
}
return ccr;
}
}
}
}
// Put the new configuration in place.
}
catch (Exception e)
{
false, messages);
return ccr;
}
return ccr;
}
/**
* Returns a handle to the JE root container currently used by this backend.
* The rootContainer could be NULL if the backend is not initialized.
*
* @return The RootContainer object currently used by this backend.
*/
public RootContainer getRootContainer()
{
return rootContainer;
}
/**
* Returns a new read-only handle to the JE root container for this backend.
* The caller is responsible for closing the root container after use.
*
* @return The read-only RootContainer object for this backend.
*
* @throws ConfigException If an unrecoverable problem arises during
* initialization.
* @throws InitializationException If a problem occurs during initialization
* that is not related to the server
* configuration.
*/
public RootContainer getReadOnlyRootContainer()
{
envConfig.setReadOnly(true);
envConfig.setAllowCreate(false);
envConfig.setTransactional(false);
envConfig.setTxnNoSync(false);
return initializeRootContainer(envConfig);
}
/**
* Clears all the entries from the backend. This method is for test cases
* that use the JE backend.
*
* @throws ConfigException If an unrecoverable problem arises in the
* process of performing the initialization.
*
* @throws JebException If an error occurs while removing the data.
*/
public void clearBackend()
throws ConfigException, JebException
{
// Determine the backend database directory.
}
/**
* Creates a customized DirectoryException from the DatabaseException thrown
* by JE backend.
*
* @param e The DatabaseException to be converted.
* @return DirectoryException created from exception.
*/
{
if(e instanceof RunRecoveryException)
{
}
{
}
}
/**
* Retrieves the fully-qualified name of the Java class for this alert
* generator implementation.
*
* @return The fully-qualified name of the Java class for this alert
* generator implementation.
*/
public String getClassName()
{
return CLASS_NAME;
}
/**
* Retrieves information about the set of alerts that this generator may
* produce. The map returned should be between the notification type for a
* particular notification and the human-readable description for that
* notification. This alert generator must not generate any alerts with types
* that are not contained in this list.
*
* @return Information about the set of alerts that this generator may
* produce.
*/
{
return alerts;
}
/**
* Retrieves the DN of the configuration entry with which this alert generator
* is associated.
*
* @return The DN of the configuration entry with which this alert generator
* is associated.
*/
public DN getComponentEntryDN()
{
}
{
// Open the database environment
try
{
return rc;
}
catch (DatabaseException e)
{
if (debugEnabled())
{
}
throw new InitializationException(message, e);
}
}
}