/* * 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 * * * Copyright 2008-2009 Sun Microsystems, Inc. */ package org.opends.server.backends.ndb; import com.mysql.cluster.ndbj.NdbApiException; import com.mysql.cluster.ndbj.NdbApiPermanentException; import com.mysql.cluster.ndbj.NdbApiTemporaryException; import com.mysql.cluster.ndbj.NdbError; import com.mysql.cluster.ndbj.NdbOperation; import org.opends.messages.Message; import org.opends.server.api.Backend; import org.opends.server.core.AddOperation; import org.opends.server.core.DeleteOperation; import org.opends.server.core.ModifyOperation; import org.opends.server.core.ModifyDNOperation; import org.opends.server.core.SearchOperation; import org.opends.server.types.*; import org.opends.server.util.ServerConstants; import java.util.*; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantReadWriteLock; import static org.opends.messages.NdbMessages.*; import org.opends.messages.MessageBuilder; import static org.opends.server.loggers.debug.DebugLogger.*; import org.opends.server.loggers.debug.DebugTracer; import static org.opends.server.util.ServerConstants.*; import org.opends.server.admin.server.ConfigurationChangeListener; import org.opends.server.admin.std.server.NdbBackendCfg; import org.opends.server.backends.ndb.OperationContainer.DN2IDSearchCursor; import org.opends.server.backends.ndb.OperationContainer.SearchCursorResult; import org.opends.server.config.ConfigException; /** * Storage container for LDAP entries. Each base DN of a NDB backend is given * its own entry container. The entry container is the object that implements * the guts of the backend API methods for LDAP operations. */ public class EntryContainer implements ConfigurationChangeListener { /** * The tracer object for the debug logger. */ private static final DebugTracer TRACER = getTracer(); /** * The backend to which this entry entryContainer belongs. */ private Backend backend; /** * The root container in which this entryContainer belongs. */ private RootContainer rootContainer; /** * The baseDN this entry container is responsible for. */ private DN baseDN; /** * The backend configuration. */ private NdbBackendCfg config; /** * The operation container. */ private OperationContainer dn2id; /** * Cached values from config so they don't have to be retrieved per operation. */ private int deadlockRetryLimit; private int subtreeDeleteSizeLimit; private int subtreeDeleteBatchSize; private String databasePrefix; /** * A read write lock to handle schema changes and bulk changes. */ private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); final Lock sharedLock = lock.readLock(); final Lock exclusiveLock = lock.writeLock(); /** * Create a new entry entryContainer object. * * @param baseDN The baseDN this entry container will be responsible for * storing on disk. * @param databasePrefix The prefix to use in the database names used by * this entry container. * @param backend A reference to the NDB backend that is creating this entry * container. * @param config The configuration of the NDB backend. * @param rootContainer The root container this entry container is in. * @throws ConfigException if a configuration related error occurs. */ public EntryContainer(DN baseDN, String databasePrefix, Backend backend, NdbBackendCfg config, RootContainer rootContainer) throws ConfigException { this.backend = backend; this.baseDN = baseDN; this.config = config; this.rootContainer = rootContainer; StringBuilder builder = new StringBuilder(databasePrefix.length()); for (int i = 0; i < databasePrefix.length(); i++) { char ch = databasePrefix.charAt(i); if (Character.isLetterOrDigit(ch)) { builder.append(ch); } else { builder.append('_'); } } this.databasePrefix = builder.toString(); this.deadlockRetryLimit = config.getDeadlockRetryLimit(); config.addNdbChangeListener(this); } /** * Opens the entryContainer for reading and writing. * * @throws NdbApiException If an error occurs in the NDB database. * @throws ConfigException if a configuration related error occurs. */ public void open() throws NdbApiException, ConfigException { try { dn2id = new OperationContainer(BackendImpl.DN2ID_TABLE, this); } catch (NdbApiException de) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, de); } close(); throw de; } } /** * Closes the entry entryContainer. * * @throws NdbApiException If an error occurs in the NDB database. */ public void close() throws NdbApiException { config.removeNdbChangeListener(this); rootContainer = null; backend = null; config = null; dn2id = null; } /** * Retrieves a reference to the root container in which this entry container * exists. * * @return A reference to the root container in which this entry container * exists. */ public RootContainer getRootContainer() { return rootContainer; } /** * Get the DN database used by this entry entryContainer. The entryContainer * must have been opened. * * @return The DN database. */ public OperationContainer getDN2ID() { return dn2id; } /** * Determine the number of subordinate entries for a given entry. * * @param entryDN The distinguished name of the entry. * @param subtree true will include all the entries * under the given entries. false will only * return the number of entries immediately under the * given entry. * @return The number of subordinate entries for the given entry * or -1 if the entry does not exist. * @throws com.mysql.cluster.ndbj.NdbApiException If an error * occurs in the database. */ public long getNumSubordinates(DN entryDN, boolean subtree) throws NdbApiException { if (baseDN.equals(entryDN)) { // Shortcut to avoid scan. return getEntryCount() - 1; } AbstractTransaction txn = new AbstractTransaction(rootContainer); try { return dn2id.numSubordinates(txn, entryDN, subtree); } finally { if (txn != null) { txn.close(); } } } /** * Processes the specified search in this entryContainer. * Matching entries should be provided back to the core server using the * SearchOperation.returnEntry method. * * @param searchOperation The search operation to be processed. * @throws DirectoryException If a problem occurs while processing * the search. * @throws CanceledOperationException If operation is canceled * while in progress. * @throws NdbApiException If an error occurs in the NDB database. * @throws NDBException If an error occurs in the NDB backend. */ public void search(SearchOperation searchOperation) throws CanceledOperationException, DirectoryException, NdbApiException, NDBException { DN baseDN = searchOperation.getBaseDN(); SearchScope searchScope = searchOperation.getScope(); AbstractTransaction txn = new AbstractTransaction(rootContainer); int txnRetries = 0; boolean completed = false; while (!completed) { try { // Handle base-object search first. if (searchScope == SearchScope.BASE_OBJECT) { // Fetch the base entry. Entry baseEntry = dn2id.get(txn, baseDN, NdbOperation.LockMode.LM_CommittedRead); // The base entry must exist for a successful result. if (baseEntry == null) { // Check for referral entries above the base entry. targetEntryReferrals(txn, baseDN, searchScope); Message message = ERR_NDB_SEARCH_NO_SUCH_OBJECT.get(baseDN.toString()); DN matchedDN = getMatchedDN(txn, baseDN); throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message, matchedDN, null); } if (!isManageDsaITOperation(searchOperation)) { checkTargetForReferral(baseEntry, searchOperation.getScope()); } if (searchOperation.getFilter().matchesEntry(baseEntry)) { searchOperation.returnEntry(baseEntry, null); } completed = true; return; } IndexFilter indexFilter = new IndexFilter(txn, this, searchOperation); if (indexFilter.evaluate()) { searchIndexed(searchOperation, indexFilter); completed = true; } else { DN2IDSearchCursor cursor = dn2id.getSearchCursor(txn, baseDN); searchNotIndexed(searchOperation, cursor); completed = true; } } catch (NdbApiTemporaryException databaseException) { if (txnRetries < BackendImpl.TXN_RETRY_LIMIT) { txnRetries++; continue; } throw databaseException; } finally { if (txn != null) { txn.close(); } } } } /** * We were able to obtain a set of candidate entries for the * search from the indexes. */ private void searchIndexed(SearchOperation searchOperation, IndexFilter indexFilter) throws CanceledOperationException, NdbApiException, DirectoryException, NDBException { DN baseDN = searchOperation.getBaseDN(); SearchScope searchScope = searchOperation.getScope(); boolean manageDsaIT = isManageDsaITOperation(searchOperation); AbstractTransaction txn = new AbstractTransaction(rootContainer); try { // Fetch the base entry. long baseEntryID = 0; Entry baseEntry = null; baseEntry = dn2id.get(txn, baseDN, NdbOperation.LockMode.LM_CommittedRead); // The base entry must exist for a successful result. if (baseEntry == null) { // Check for referral entries above the base entry. targetEntryReferrals(txn, baseDN, searchScope); Message message = ERR_NDB_SEARCH_NO_SUCH_OBJECT.get(baseDN.toString()); DN matchedDN = getMatchedDN(txn, baseDN); throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message, matchedDN, null); } baseEntryID = (Long) baseEntry.getAttachment(); if (!manageDsaIT) { checkTargetForReferral(baseEntry, searchScope); } /* * The base entry is only included for whole subtree search. */ if (searchScope == SearchScope.WHOLE_SUBTREE) { if (searchOperation.getFilter().matchesEntry(baseEntry)) { searchOperation.returnEntry(baseEntry, null); } } int lookthroughCount = 0; int lookthroughLimit = searchOperation.getClientConnection().getLookthroughLimit(); indexFilter.scan(); try { long eid = 0; while ((eid = indexFilter.getNext()) != 0) { if (lookthroughLimit > 0 && lookthroughCount > lookthroughLimit) { // Lookthrough limit exceeded searchOperation.setResultCode(ResultCode.ADMIN_LIMIT_EXCEEDED); searchOperation.appendErrorMessage( NOTE_NDB_LOOKTHROUGH_LIMIT_EXCEEDED.get(lookthroughLimit)); return; } // Skip over base entry. if (eid == baseEntryID) { continue; } // Fetch the candidate entry from the database. Entry entry = null; AbstractTransaction subTxn = new AbstractTransaction(rootContainer); try { entry = dn2id.get(subTxn, eid, NdbOperation.LockMode.LM_CommittedRead); } finally { if (subTxn != null) { subTxn.close(); } } if (entry != null) { // We have found a subordinate entry. DN dn = entry.getDN(); boolean isInScope = false; if (searchScope == SearchScope.SINGLE_LEVEL) { // Check if this entry is an immediate child. if ((dn.getNumComponents() == baseDN.getNumComponents() + 1) && dn.isDescendantOf(baseDN)) { isInScope = true; } } else if (searchScope == SearchScope.WHOLE_SUBTREE) { if (dn.isDescendantOf(baseDN)) { isInScope = true; } } else if (searchScope == SearchScope.SUBORDINATE_SUBTREE) { if ((dn.getNumComponents() > baseDN.getNumComponents()) && dn.isDescendantOf(baseDN)) { isInScope = true; } } if (isInScope) { // Process the candidate entry. lookthroughCount++; if (manageDsaIT || !entry.isReferral()) { // Filter the entry. if (searchOperation.getFilter().matchesEntry(entry)) { if (!searchOperation.returnEntry(entry, null)) { // We have been told to discontinue processing of the // search. This could be due to size limit exceeded or // operation cancelled. return; } } } else { if (entry.isReferral()) { try { checkTargetForReferral(entry, searchScope); } catch (DirectoryException refException) { if (refException.getResultCode() == ResultCode.REFERRAL) { SearchResultReference reference = new SearchResultReference( refException.getReferralURLs()); if (!searchOperation.returnReference(dn, reference)) { // We have been told to discontinue processing of the // search. This could be due to size limit exceeded or // operation cancelled. return; } } else { throw refException; } } } } } } searchOperation.checkIfCanceled(false); } } finally { indexFilter.close(); } } finally { if (txn != null) { txn.close(); } } } /** * We were not able to obtain a set of candidate entries for the * search from the indexes. */ private void searchNotIndexed(SearchOperation searchOperation, DN2IDSearchCursor cursor) throws CanceledOperationException, NdbApiException, DirectoryException, NDBException { DN baseDN = searchOperation.getBaseDN(); SearchScope searchScope = searchOperation.getScope(); boolean manageDsaIT = isManageDsaITOperation(searchOperation); AbstractTransaction txn = new AbstractTransaction(rootContainer); try { // Fetch the base entry. Entry baseEntry = null; baseEntry = dn2id.get(txn, baseDN, NdbOperation.LockMode.LM_CommittedRead); // The base entry must exist for a successful result. if (baseEntry == null) { // Check for referral entries above the base entry. targetEntryReferrals(txn, baseDN, searchScope); Message message = ERR_NDB_SEARCH_NO_SUCH_OBJECT.get(baseDN.toString()); DN matchedDN = getMatchedDN(txn, baseDN); throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message, matchedDN, null); } if (!manageDsaIT) { checkTargetForReferral(baseEntry, searchScope); } /* * The base entry is only included for whole subtree search. */ if (searchScope == SearchScope.WHOLE_SUBTREE) { if (searchOperation.getFilter().matchesEntry(baseEntry)) { searchOperation.returnEntry(baseEntry, null); } } int lookthroughCount = 0; int lookthroughLimit = searchOperation.getClientConnection().getLookthroughLimit(); cursor.open(); try { SearchCursorResult result = null; while ((result = cursor.getNext()) != null) { if (lookthroughLimit > 0 && lookthroughCount > lookthroughLimit) { // Lookthrough limit exceeded searchOperation.setResultCode(ResultCode.ADMIN_LIMIT_EXCEEDED); searchOperation.appendErrorMessage( NOTE_NDB_LOOKTHROUGH_LIMIT_EXCEEDED.get(lookthroughLimit)); return; } // We have found a subordinate entry. DN dn = DN.decode(result.dn); boolean isInScope = true; if (searchScope == SearchScope.SINGLE_LEVEL) { // Check if this entry is an immediate child. if ((dn.getNumComponents() != baseDN.getNumComponents() + 1)) { isInScope = false; } } if (isInScope) { // Fetch the candidate entry from the database. Entry entry = null; AbstractTransaction subTxn = new AbstractTransaction(rootContainer); try { entry = dn2id.get(subTxn, dn, NdbOperation.LockMode.LM_CommittedRead); } finally { if (subTxn != null) { subTxn.close(); } } // Process the candidate entry. if (entry != null) { lookthroughCount++; if (manageDsaIT || !entry.isReferral()) { // Filter the entry. if (searchOperation.getFilter().matchesEntry(entry)) { if (!searchOperation.returnEntry(entry, null)) { // We have been told to discontinue processing of the // search. This could be due to size limit exceeded or // operation cancelled. return; } } } else { if (entry.isReferral()) { try { checkTargetForReferral(entry, searchScope); } catch (DirectoryException refException) { if (refException.getResultCode() == ResultCode.REFERRAL) { SearchResultReference reference = new SearchResultReference( refException.getReferralURLs()); if (!searchOperation.returnReference(dn, reference)) { // We have been told to discontinue processing of the // search. This could be due to size limit exceeded or // operation cancelled. return; } } else { throw refException; } } } } } } searchOperation.checkIfCanceled(false); } } finally { cursor.close(); } } finally { if (txn != null) { txn.close(); } } } /** * Adds the provided entry to this database. This method must ensure that the * entry is appropriate for the database 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 database. * @param addOperation The add operation with which the new entry is * associated. This may be null for adds * performed internally. * @param txn Abstract transaction for this operation. * @throws DirectoryException If a problem occurs while trying to add the * entry. * @throws CanceledOperationException If operation is canceled * while in progress. * @throws NdbApiException If an error occurs in the NDB database. * @throws NDBException If an error occurs in the NDB backend. */ public void addEntry(Entry entry, AddOperation addOperation, AbstractTransaction txn) throws CanceledOperationException, NdbApiException, DirectoryException, NDBException { TransactedOperation operation = new AddEntryTransaction(entry); invokeTransactedOperation(txn, operation, addOperation, true, false); } /** * Adds the provided entry to this database. This method must ensure that the * entry is appropriate for the database 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 database. * @param addOperation The add operation with which the new entry is * associated. This may be null for adds * performed internally. * @param txn Abstract transaction for this operation. * @throws DirectoryException If a problem occurs while trying to add the * entry. * @throws CanceledOperationException If operation is canceled * while in progress. * @throws NdbApiException If an error occurs in the NDB database. * @throws NDBException If an error occurs in the NDB backend. */ public void addEntryNoCommit(Entry entry, AddOperation addOperation, AbstractTransaction txn) throws CanceledOperationException, NdbApiException, DirectoryException, NDBException { TransactedOperation operation = new AddEntryTransaction(entry); invokeTransactedOperation(txn, operation, addOperation, false, false); } /** * This method is common to all operations invoked under a database * transaction. It retries the operation if the transaction is * aborted due to a deadlock condition, up to a configured maximum * number of retries. * * @param operation An object implementing the TransactedOperation interface. * @throws NdbApiException If an error occurs in the NDB database. * @throws DirectoryException If a Directory Server error occurs. * @throws NDBException If an error occurs in the NDB backend. */ private void invokeTransactedOperation(AbstractTransaction txn, TransactedOperation operation, Operation ldapOperation, boolean commit, boolean locked) throws CanceledOperationException, NdbApiException, DirectoryException, NDBException { // Attempt the operation under a transaction until it fails or completes. int txnRetries = 0; boolean completed = false; while (!completed) { try { // Invoke the operation. operation.invokeOperation(txn, commit); // One last check before committing. if (ldapOperation != null) { ldapOperation.checkIfCanceled(true); } // Commit the transaction. if (commit) { txn.commit(); } completed = true; } catch (NdbApiTemporaryException databaseException) { if (!locked) { if (txnRetries < BackendImpl.TXN_RETRY_LIMIT) { if (txn != null) { txn.close(); } txnRetries++; continue; } } if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, databaseException); } throw databaseException; } catch (NdbApiPermanentException databaseException) { throw databaseException; } catch (DirectoryException directoryException) { throw directoryException; } catch (NDBException NDBException) { throw NDBException; } catch (Exception e) { Message message = ERR_NDB_UNCHECKED_EXCEPTION.get(); throw new NDBException(message, e); } finally { if (commit) { if (txn != null) { txn.close(); } } } } // Do any actions necessary after successful commit, // usually to update the entry cache. operation.postCommitAction(); } /** * This interface represents any kind of operation on the database * that must be performed under a transaction. A class which implements * this interface does not need to be concerned with creating the * transaction nor retrying the transaction after deadlock. */ private interface TransactedOperation { /** * Invoke the operation under the given transaction. * * @param txn The transaction to be used to perform the operation. * @param willCommit Indicates whether or not the caller will commit. * @throws NdbApiException If an error occurs in the NDB database. * @throws DirectoryException If a Directory Server error occurs. * @throws NDBException If an error occurs in the NDB backend. */ public abstract void invokeOperation(AbstractTransaction txn, boolean willCommit) throws NdbApiException, DirectoryException, CanceledOperationException, NDBException; /** * This method is called after the transaction has successfully * committed. */ public abstract void postCommitAction(); } /** * This inner class implements the Add Entry operation through * the TransactedOperation interface. */ private class AddEntryTransaction implements TransactedOperation { /** * The entry to be added. */ private Entry entry; /** * The DN of the superior entry of the entry to be added. This can be * null if the entry to be added is a base entry. */ DN parentDN; /** * The ID of the entry once it has been assigned. */ long entryID; /** * Create a new Add Entry NdbTransaction. * @param entry The entry to be added. */ public AddEntryTransaction(Entry entry) { this.entry = entry; this.parentDN = getParentWithinBase(entry.getDN()); } /** * Invoke the operation under the given transaction. * * @param txn The transaction to be used to perform the operation. * @param willCommit Indicates whether or not the caller will commit. * @throws NdbApiException If an error occurs in the NDB database. * @throws DirectoryException If a Directory Server error occurs. * @throws NDBException If an error occurs in the NDB backend. */ public void invokeOperation(AbstractTransaction txn, boolean willCommit) throws NdbApiException, DirectoryException, NDBException { // Check that the parent entry exists. if (parentDN != null) { // Check for referral entries above the target. targetEntryReferrals(txn, entry.getDN(), null); long parentID = dn2id.getID(txn, parentDN, NdbOperation.LockMode.LM_Read); if (parentID == 0) { Message message = ERR_NDB_ADD_NO_SUCH_OBJECT.get( entry.getDN().toString()); DN matchedDN = getMatchedDN(txn, baseDN); throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message, matchedDN, null); } } // First time through, assign the next entryID. if (entryID == 0) { entryID = rootContainer.getNextEntryID(txn.getNdb()); } // Insert. try { dn2id.insert(txn, entry.getDN(), entryID, entry); if (!willCommit) { txn.execute(); } } catch (NdbApiException ne) { if (ne.getErrorObj().getClassification() == NdbError.Classification.ConstraintViolation) { Message message = ERR_NDB_ADD_ENTRY_ALREADY_EXISTS.get(entry.getDN().toString()); throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS, message); } else { throw ne; } } } /** * This method is called after the transaction has successfully * committed. */ public void postCommitAction() { } } /** * Removes the specified entry from this database. This method must ensure * that the entry exists and that it does not have any subordinate entries * (unless the database 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 database. * @param entry The entry to delete. * @param deleteOperation The delete operation with which this action is * associated. This may be null for * deletes performed internally. * @param txn Abstract transaction for this operation. * @throws DirectoryException If a problem occurs while trying to remove the * entry. * @throws CanceledOperationException If operation is canceled * while in progress. * @throws NdbApiException If an error occurs in the NDB database. * @throws NDBException If an error occurs in the NDB backend. */ public void deleteEntry(DN entryDN, Entry entry, DeleteOperation deleteOperation, AbstractTransaction txn) throws CanceledOperationException, DirectoryException, NdbApiException, NDBException { DeleteEntryTransaction operation = new DeleteEntryTransaction(entryDN, entry, deleteOperation); boolean isComplete = false; while(!isComplete) { invokeTransactedOperation(txn, operation, deleteOperation, true, true); if (operation.adminSizeLimitExceeded()) { Message message = NOTE_NDB_SUBTREE_DELETE_SIZE_LIMIT_EXCEEDED.get( operation.getDeletedEntryCount()); throw new DirectoryException( ResultCode.ADMIN_LIMIT_EXCEEDED, message); } if(operation.batchSizeExceeded()) { operation.resetBatchSize(); continue; } isComplete = true; Message message = NOTE_NDB_DELETED_ENTRY_COUNT.get(operation.getDeletedEntryCount()); MessageBuilder errorMessage = new MessageBuilder(); errorMessage.append(message); deleteOperation.setErrorMessage(errorMessage); } } /** * Removes the specified entry from this database. This method must ensure * that the entry exists and that it does not have any subordinate entries * (unless the database 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 database. * @param entry The entry to delete. * @param deleteOperation The delete operation with which this action is * associated. This may be null for * deletes performed internally. * @param txn Abstract transaction for this operation. * @throws DirectoryException If a problem occurs while trying to remove the * entry. * @throws CanceledOperationException If operation is canceled * while in progress. * @throws NdbApiException If an error occurs in the NDB database. * @throws NDBException If an error occurs in the NDB backend. */ public void deleteEntryNoCommit(DN entryDN, Entry entry, DeleteOperation deleteOperation, AbstractTransaction txn) throws CanceledOperationException, DirectoryException, NdbApiException, NDBException { DeleteEntryTransaction operation = new DeleteEntryTransaction(entryDN, entry, deleteOperation); boolean isComplete = false; while(!isComplete) { invokeTransactedOperation(txn, operation, deleteOperation, false, true); if (operation.adminSizeLimitExceeded()) { Message message = NOTE_NDB_SUBTREE_DELETE_SIZE_LIMIT_EXCEEDED.get( operation.getDeletedEntryCount()); throw new DirectoryException( ResultCode.ADMIN_LIMIT_EXCEEDED, message); } if(operation.batchSizeExceeded()) { operation.resetBatchSize(); continue; } isComplete = true; Message message = NOTE_NDB_DELETED_ENTRY_COUNT.get(operation.getDeletedEntryCount()); MessageBuilder errorMessage = new MessageBuilder(); errorMessage.append(message); deleteOperation.setErrorMessage(errorMessage); } } /** * Delete a leaf entry. * The caller must be sure that the entry is indeed a leaf. * * @param txn The abstract transaction. * @param leafDN The DN of the leaf entry to be deleted. * @param leafID The ID of the leaf entry. * @throws NdbApiException If an error occurs in the NDB database. * @throws DirectoryException If a Directory Server error occurs. * @throws NDBException If an error occurs in the NDB backend. */ private void deleteLeaf(AbstractTransaction txn, DN leafDN, long leafID, DeleteOperation operation) throws NdbApiException, DirectoryException, NDBException { Entry entry = dn2id.get(txn, leafDN, NdbOperation.LockMode.LM_Exclusive); // Check that the entry exists. if (entry == null) { return; } if (!isManageDsaITOperation(operation)) { checkTargetForReferral(entry, null); } // Remove from dn2id. if (!dn2id.remove(txn, entry)) { Message msg = ERR_NDB_MISSING_ID2ENTRY_RECORD.get(Long.toString(leafID)); throw new NDBException(msg); } } /** * Delete the target entry of a delete operation, with appropriate handling * of referral entries. The caller must be sure that the entry is indeed a * leaf. * * @param txn The abstract transaction. * @param leafDN The DN of the target entry to be deleted. * @throws NdbApiException If an error occurs in the NDB database. * @throws DirectoryException If a Directory Server error occurs. * @throws NDBException If an error occurs in the NDB backend. */ private void deleteTarget(AbstractTransaction txn, DN leafDN, Entry entry, DeleteOperation operation) throws NdbApiException, DirectoryException, NDBException { // Check that the entry exists. if (entry == null) { Message message = ERR_NDB_DELETE_NO_SUCH_OBJECT.get(leafDN.toString()); DN matchedDN = getMatchedDN(txn, baseDN); throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message, matchedDN, null); } if (!isManageDsaITOperation(operation)) { checkTargetForReferral(entry, null); } // Remove from dn2id. if (!dn2id.remove(txn, entry)) { Message msg = ERR_NDB_MISSING_DN2ID_RECORD.get(leafDN.toString()); throw new NDBException(msg); } } /** * This inner class implements the Delete Entry operation through * the TransactedOperation interface. */ private class DeleteEntryTransaction implements TransactedOperation { /** * The DN of the entry or subtree to be deleted. */ private DN entryDN; /** * The entry itself. */ private Entry entry; /** * The Delete operation. */ private DeleteOperation deleteOperation; /** * A list of the DNs of all entries deleted by this operation in a batch. * The subtree delete control can cause multiple entries to be deleted. */ private ArrayList deletedDNList; /** * Indicates whether the subtree delete size limit has been exceeded. */ private boolean adminSizeLimitExceeded = false; /** * Indicates whether the subtree delete batch size has been exceeded. */ private boolean batchSizeExceeded = false; /** * Indicates the count of deleted DNs in the Delete Operation. */ private int countDeletedDN; /** * Create a new Delete Entry NdbTransaction. * @param entryDN The entry or subtree to be deleted. * @param deleteOperation The Delete operation. */ public DeleteEntryTransaction(DN entryDN, Entry entry, DeleteOperation deleteOperation) { this.entryDN = entryDN; this.entry = entry; this.deleteOperation = deleteOperation; deletedDNList = new ArrayList(); } /** * Determine whether the subtree delete size limit has been exceeded. * @return true if the size limit has been exceeded. */ public boolean adminSizeLimitExceeded() { return adminSizeLimitExceeded; } /** * Determine whether the subtree delete batch size has been exceeded. * @return true if the batch size has been exceeded. */ public boolean batchSizeExceeded() { return batchSizeExceeded; } /** * Resets the batchSizeExceeded parameter to reuse the object * for multiple batches. */ public void resetBatchSize() { batchSizeExceeded=false; deletedDNList.clear(); } /** * Get the number of entries deleted during the operation. * @return The number of entries deleted. */ public int getDeletedEntryCount() { return countDeletedDN; } /** * Invoke the operation under the given transaction. * * @param txn The transaction to be used to perform the operation. * @param willCommit Indicates whether or not the caller will commit. * @throws NdbApiException If an error occurs in the NDB database. * @throws DirectoryException If a Directory Server error occurs. * @throws NDBException If an error occurs in the NDB backend. */ public void invokeOperation(AbstractTransaction txn, boolean willCommit) throws CanceledOperationException, NdbApiException, DirectoryException, NDBException { // Check for referral entries above the target entry. targetEntryReferrals(txn, entryDN, null); // Determine whether this is a subtree delete. int adminSizeLimit = subtreeDeleteSizeLimit; int deleteBatchSize = subtreeDeleteBatchSize; boolean isSubtreeDelete = false; List controls = deleteOperation.getRequestControls(); if (controls != null) { for (Control control : controls) { if (control.getOID().equals(OID_SUBTREE_DELETE_CONTROL)) { isSubtreeDelete = true; } } } if (dn2id.hasSubordinates(txn, entryDN) && !isSubtreeDelete) { // The subtree delete control was not specified and // the target entry is not a leaf. Message message = ERR_NDB_DELETE_NOT_ALLOWED_ON_NONLEAF.get(entryDN.toString()); throw new DirectoryException(ResultCode.NOT_ALLOWED_ON_NONLEAF, message); } if (isSubtreeDelete) { AbstractTransaction cursorTxn = new AbstractTransaction(rootContainer); DN2IDSearchCursor cursor = dn2id.getSearchCursor(cursorTxn, entryDN); cursor.open(); try { SearchCursorResult result = cursor.getNext(); while (result != null) { // We have found a subordinate entry. // Enforce any subtree delete size limit. if (adminSizeLimit > 0 && countDeletedDN >= adminSizeLimit) { adminSizeLimitExceeded = true; break; } // Enforce any subtree delete batch size. if (deleteBatchSize > 0 && deletedDNList.size() >= deleteBatchSize) { batchSizeExceeded = true; break; } /* * Delete this entry which by now must be a leaf because * we have been deleting from the bottom of the tree upwards. */ long entryID = result.id; DN subordinateDN = DN.decode(result.dn); AbstractTransaction subTxn = new AbstractTransaction(rootContainer); try { deleteLeaf(subTxn, subordinateDN, entryID, deleteOperation); } finally { if (subTxn != null) { subTxn.commit(); } } deletedDNList.add(subordinateDN); countDeletedDN++; if (deleteOperation != null) { deleteOperation.checkIfCanceled(false); } result = cursor.getNext(); } } finally { cursor.close(); cursorTxn.close(); } } // Finally delete the target entry as it was not included // in the dn2id iteration. if (!adminSizeLimitExceeded && !batchSizeExceeded) { // Enforce any subtree delete size limit. if (adminSizeLimit > 0 && countDeletedDN >= adminSizeLimit) { adminSizeLimitExceeded = true; } else if (deleteBatchSize > 0 && deletedDNList.size() >= deleteBatchSize) { batchSizeExceeded = true; } else { deleteTarget(txn, entryDN, entry, deleteOperation); deletedDNList.add(entryDN); countDeletedDN++; } } } /** * This method is called after the transaction has successfully * committed. */ public void postCommitAction() { } } /** * Indicates whether an entry with the specified DN exists. * * @param txn Abstract transaction for this operation. * @param entryDN The DN of the entry for which to determine existence. * * @return true if the specified entry exists, * or false if it does not. * * @throws DirectoryException If a problem occurs while trying to make the * determination. * @throws NdbApiException An error occurred during a database operation. */ public boolean entryExists(AbstractTransaction txn, DN entryDN) throws DirectoryException, NdbApiException { // Read the ID from dn2id. long id = 0; try { id = dn2id.getID(txn, entryDN, NdbOperation.LockMode.LM_CommittedRead); } catch (NdbApiException e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } } return id != 0; } /** * Fetch an entry by DN retrieves the requested entry. * 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. * @param txn Abstract transaction for this operation. * @param lockMode Operation lock mode. * @return The requested entry, or null if the entry does not * exist. * @throws DirectoryException If a problem occurs while trying to retrieve * the entry. * @throws NDBException If an error occurs in the NDB backend. * @throws NdbApiException An error occurred during a database operation. */ public Entry getEntryNoCommit(DN entryDN, AbstractTransaction txn, NdbOperation.LockMode lockMode) throws NDBException, NdbApiException, DirectoryException { Entry entry = null; GetEntryByDNOperation operation = new GetEntryByDNOperation(entryDN, lockMode); try { // Fetch the entry from the database. invokeTransactedOperation(txn, operation, null, false, false); } catch (CanceledOperationException ex) { // No LDAP operation, ignore. } entry = operation.getEntry(); return entry; } /** * Fetch an entry by DN retrieves the requested entry. * 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. * @throws NDBException If an error occurs in the NDB backend. * @throws NdbApiException An error occurred during a database operation. */ public Entry getEntry(DN entryDN) throws NDBException, NdbApiException, DirectoryException { Entry entry = null; GetEntryByDNOperation operation = new GetEntryByDNOperation(entryDN, NdbOperation.LockMode.LM_CommittedRead); AbstractTransaction txn = new AbstractTransaction(rootContainer); try { // Fetch the entry from the database. invokeTransactedOperation(txn, operation, null, true, false); } catch (CanceledOperationException ex) { // No LDAP operation, ignore. } entry = operation.getEntry(); return entry; } /** * This inner class gets an entry by DN through * the TransactedOperation interface. */ private class GetEntryByDNOperation implements TransactedOperation { /** * The retrieved entry. */ private Entry entry = null; /** * The ID of the retrieved entry. */ private long entryID = 0; /** * Operation lock mode. */ private NdbOperation.LockMode lockMode; /** * The DN of the entry to be retrieved. */ DN entryDN; /** * Create a new transacted operation to retrieve an entry by DN. * @param entryDN The DN of the entry to be retrieved. */ public GetEntryByDNOperation(DN entryDN, NdbOperation.LockMode lockMode) { this.entryDN = entryDN; this.lockMode = lockMode; } /** * Get the retrieved entry. * @return The retrieved entry. */ public Entry getEntry() { return entry; } /** * Get the ID of the retrieved entry. * @return The ID of the retrieved entry. */ public long getEntryID() { return entryID; } /** * Invoke the operation under the given transaction. * * @param txn The transaction to be used to perform the operation. * @param willCommit Indicates whether or not the caller will commit. * @throws NdbApiException If an error occurs in the NDB database. * @throws DirectoryException If a Directory Server error occurs. * @throws NDBException If an error occurs in the NDB backend. */ public void invokeOperation(AbstractTransaction txn, boolean willCommit) throws NdbApiException, DirectoryException, NDBException { entry = dn2id.get(txn, entryDN, lockMode); if (entry == null) { // Check for referral entries above the target entry. targetEntryReferrals(txn, entryDN, null); } } /** * This method is called after the transaction has successfully * committed. */ public void postCommitAction() { // No implementation required. } } /** * The simplest case of replacing an entry in which the entry DN has * not changed. * * @param oldEntry The old contents of the entry. * @param newEntry The new contents of the entry * @param modifyOperation The modify operation with which this action is * associated. This may be null for * modifications performed internally. * @param txn Abstract transaction for this operation. * @throws NdbApiException If an error occurs in the NDB database. * @throws DirectoryException If a Directory Server error occurs. * @throws CanceledOperationException If operation is canceled * while in progress. * @throws NDBException If an error occurs in the NDB backend. */ public void replaceEntry(Entry oldEntry, Entry newEntry, ModifyOperation modifyOperation, AbstractTransaction txn) throws CanceledOperationException, NdbApiException, DirectoryException, NDBException { TransactedOperation operation = new ReplaceEntryTransaction(oldEntry, newEntry, modifyOperation); invokeTransactedOperation(txn, operation, modifyOperation, true, true); } /** * This inner class implements the Replace Entry operation through * the TransactedOperation interface. */ private class ReplaceEntryTransaction implements TransactedOperation { /** * The new contents of the entry. */ private Entry newEntry; /** * The old contents of the entry. */ private Entry oldEntry; /** * The Modify operation, or null if the replace is not due to a Modify * operation. */ private ModifyOperation modifyOperation; /** * The ID of the entry that was replaced. */ private Long entryID; /** * Create a new transacted operation to replace an entry. * @param entry The new contents of the entry. * @param modifyOperation The Modify operation, or null if the replace is * not due to a Modify operation. */ public ReplaceEntryTransaction(Entry oldEntry, Entry newEntry, ModifyOperation modifyOperation) { this.oldEntry = oldEntry; this.newEntry = newEntry; this.modifyOperation = modifyOperation; } /** * Invoke the operation under the given transaction. * * @param txn The transaction to be used to perform the operation. * @param willCommit Indicates whether or not the caller will commit. * @throws NdbApiException If an error occurs in the NDB database. * @throws DirectoryException If a Directory Server error occurs. * @throws NDBException If an error occurs in the NDB backend. */ public void invokeOperation(AbstractTransaction txn, boolean willCommit) throws NdbApiException, DirectoryException, NDBException { DN entryDN = newEntry.getDN(); entryID = (Long) oldEntry.getAttachment(); if (entryID == 0) { // The entry does not exist. Message message = ERR_NDB_MODIFY_NO_SUCH_OBJECT.get(entryDN.toString()); DN matchedDN = getMatchedDN(txn, baseDN); throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message, matchedDN, null); } if (!isManageDsaITOperation(modifyOperation)) { // Check if the entry is a referral entry. checkTargetForReferral(oldEntry, null); } // List modsList = modifyOperation.getModifications(); // Replace. if (!dn2id.put(txn, entryDN, entryID, newEntry, oldEntry)) { // The entry does not exist. Message msg = ERR_NDB_MISSING_ID2ENTRY_RECORD.get( Long.toString(entryID)); throw new NDBException(msg); } } /** * This method is called after the transaction has successfully * committed. */ public void postCommitAction() { } } /** * 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. * @param txn Abstract transaction for this operation. * @throws org.opends.server.types.DirectoryException * If a problem occurs while trying to perform * the rename. * @throws org.opends.server.types.CanceledOperationException * If this backend noticed and reacted * to a request to cancel or abandon the * modify DN operation. * @throws NdbApiException If an error occurs in the NDB database. * @throws NDBException If an error occurs in the NDB backend. */ public void renameEntry(DN currentDN, Entry entry, ModifyDNOperation modifyDNOperation, AbstractTransaction txn) throws NdbApiException, NDBException, DirectoryException, CanceledOperationException { TransactedOperation operation = new RenameEntryTransaction(currentDN, entry, modifyDNOperation); invokeTransactedOperation(txn, operation, modifyDNOperation, true, true); } /** * This inner class implements the Modify DN operation through * the TransactedOperation interface. */ private class RenameEntryTransaction implements TransactedOperation { /** * The DN of the entry to be renamed. */ private DN oldApexDN; /** * The DN of the superior entry of the entry to be renamed. * This is null if the entry to be renamed is a base entry. */ private DN oldSuperiorDN; /** * The DN of the new superior entry, which can be the same * as the current superior entry. */ private DN newSuperiorDN; /** * The new contents of the entry to be renamed. */ private Entry newApexEntry; /** * The Modify DN operation. */ private ModifyDNOperation modifyDNOperation; /** * Create a new transacted operation for a Modify DN operation. * @param currentDN The DN of the entry to be renamed. * @param entry The new contents of the entry. * @param modifyDNOperation The Modify DN operation to be performed. */ public RenameEntryTransaction(DN currentDN, Entry entry, ModifyDNOperation modifyDNOperation) { this.oldApexDN = currentDN; this.oldSuperiorDN = getParentWithinBase(currentDN); this.newSuperiorDN = getParentWithinBase(entry.getDN()); this.newApexEntry = entry; this.modifyDNOperation = modifyDNOperation; } /** * Invoke the operation under the given transaction. * * @param txn The transaction to be used to perform the operation. * @param willCommit Indicates whether or not the caller will commit. * @throws NdbApiException If an error occurs in the NDB database. * @throws DirectoryException If a Directory Server error occurs. * @throws NDBException If an error occurs in the NDB backend. */ public void invokeOperation(AbstractTransaction txn, boolean willCommit) throws NdbApiException, DirectoryException, CanceledOperationException, NDBException { DN requestedNewSuperiorDN = null; if (modifyDNOperation != null) { requestedNewSuperiorDN = modifyDNOperation.getNewSuperior(); } // Check whether the renamed entry already exists. if (dn2id.getID(txn, newApexEntry.getDN(), NdbOperation.LockMode.LM_Exclusive) != 0) { Message message = ERR_NDB_MODIFYDN_ALREADY_EXISTS.get( newApexEntry.getDN().toString()); throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS, message); } Entry oldApexEntry = dn2id.get(txn, oldApexDN, NdbOperation.LockMode.LM_Exclusive); if (oldApexEntry == null) { // Check for referral entries above the target entry. targetEntryReferrals(txn, oldApexDN, null); Message message = ERR_NDB_MODIFYDN_NO_SUCH_OBJECT.get(oldApexDN.toString()); DN matchedDN = getMatchedDN(txn, baseDN); throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, message, matchedDN, null); } if (!isManageDsaITOperation(modifyDNOperation)) { checkTargetForReferral(oldApexEntry, null); } long oldApexID = (Long) oldApexEntry.getAttachment(); long newApexID = oldApexID; if (newSuperiorDN != null) { long newSuperiorID = dn2id.getID(txn, newSuperiorDN, NdbOperation.LockMode.LM_Exclusive); if (newSuperiorID == 0) { Message msg = ERR_NDB_NEW_SUPERIOR_NO_SUCH_OBJECT.get( newSuperiorDN.toString()); DN matchedDN = getMatchedDN(txn, baseDN); throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, msg, matchedDN, null); } newApexID = rootContainer.getNextEntryID(txn.getNdb()); } // Move or rename the apex entry. if (requestedNewSuperiorDN != null) { moveApexEntry(txn, newApexID, oldApexEntry, newApexEntry); } else { long newID = rootContainer.getNextEntryID(txn.getNdb()); renameApexEntry(txn, newID, oldApexEntry, newApexEntry); } AbstractTransaction cursorTxn = new AbstractTransaction(rootContainer); DN2IDSearchCursor cursor = dn2id.getSearchCursor(cursorTxn, oldApexDN); cursor.open(); try { SearchCursorResult result = null; // Step forward until we pass the ending value. while ((result = cursor.getNext()) != null) { // We have found a subordinate entry. long oldID = result.id; String oldDN = result.dn; Entry oldEntry = null; AbstractTransaction subTxn = new AbstractTransaction(rootContainer); try { oldEntry = dn2id.get(subTxn, DN.decode(oldDN), NdbOperation.LockMode.LM_Exclusive); if (oldEntry != null) { if (!isManageDsaITOperation(modifyDNOperation)) { checkTargetForReferral(oldEntry, null); } // Construct the new DN of the entry. DN newDN = modDN(oldEntry.getDN(), oldApexDN.getNumComponents(), newApexEntry.getDN()); if (requestedNewSuperiorDN != null) { // Assign a new entry ID if we are renumbering. long newID = oldID; if (newApexID != oldApexID) { newID = rootContainer.getNextEntryID(subTxn.getNdb()); } // Move this entry. moveSubordinateEntry(subTxn, newID, oldEntry, newDN); } else { // Rename this entry. renameSubordinateEntry(subTxn, oldID, oldEntry, newDN); } } } finally { if (subTxn != null) { subTxn.commit(); } } if (modifyDNOperation != null) { modifyDNOperation.checkIfCanceled(false); } } } finally { cursor.close(); cursorTxn.close(); } } /** * Update the database for the target entry of a ModDN operation * specifying a new superior. * * @param txn The abstract transaction to be used for the updates. * @param newID The new ID of the target entry, or the original ID if * the ID has not changed. * @param oldEntry The original contents of the target entry. * @param newEntry The new contents of the target entry. * @throws NDBException If an error occurs in the NDB backend. * @throws DirectoryException If a Directory Server error occurs. * @throws NdbApiException If an error occurs in the NDB database. */ private void moveApexEntry(AbstractTransaction txn, long newID, Entry oldEntry, Entry newEntry) throws NDBException, DirectoryException, NdbApiException { // DN oldDN = oldEntry.getDN(); DN newDN = newEntry.getDN(); // Remove the old DN from dn2id. dn2id.remove(txn, oldEntry); // Insert the new DN in dn2id. if (!dn2id.insert(txn, newDN, newID, newEntry)) { Message message = ERR_NDB_MODIFYDN_ALREADY_EXISTS.get(newDN.toString()); throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS, message); } } /** * Update the database for the target entry of a Modify DN operation * not specifying a new superior. * * @param txn The abstract transaction to be used for the updates. * @param newID The new ID of the target entry. * @param oldEntry The original contents of the target entry. * @param newEntry The new contents of the target entry. * @throws DirectoryException If a Directory Server error occurs. * @throws NdbApiException If an error occurs in the NDB database. * @throws NDBException if an error occurs in the NDB Backend. */ private void renameApexEntry(AbstractTransaction txn, long newID, Entry oldEntry, Entry newEntry) throws DirectoryException, NdbApiException, NDBException { // DN oldDN = oldEntry.getDN(); DN newDN = newEntry.getDN(); // Remove the old DN from dn2id. dn2id.remove(txn, oldEntry); // Insert the new DN in dn2id. if (!dn2id.insert(txn, newDN, newID, newEntry)) { Message message = ERR_NDB_MODIFYDN_ALREADY_EXISTS.get(newDN.toString()); throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS, message); } } /** * Update the database for a subordinate entry of the target entry * of a Modify DN operation specifying a new superior. * * @param txn The abstract transaction to be used for the updates. * @param newID The new ID of the subordinate entry, or the original ID if * the ID has not changed. * @param oldEntry The original contents of the subordinate entry. * @param newDN The new DN of the subordinate entry. * @throws NDBException If an error occurs in the NDB backend. * @throws DirectoryException If a Directory Server error occurs. * @throws NdbApiException If an error occurs in the NDB database. */ private void moveSubordinateEntry(AbstractTransaction txn, long newID, Entry oldEntry, DN newDN) throws NDBException, DirectoryException, NdbApiException { // Remove the old DN from dn2id. dn2id.remove(txn, oldEntry); // Create a new entry that is a copy of the old entry but with the new DN. Entry newEntry = oldEntry.duplicate(false); newEntry.setDN(newDN); // Put the new DN in dn2id. if (!dn2id.insert(txn, newDN, newID, newEntry)) { Message message = ERR_NDB_MODIFYDN_ALREADY_EXISTS.get(newDN.toString()); throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS, message); } } /** * Update the database for a subordinate entry of the target entry * of a Modify DN operation not specifying a new superior. * * @param txn The abstract transaction to be used for the updates. * @param entryID The ID of the subordinate entry. * @param oldEntry The original contents of the subordinate entry. * @param newDN The new DN of the subordinate entry. * @throws DirectoryException If a Directory Server error occurs. * @throws NdbApiException If an error occurs in the NDB database. */ private void renameSubordinateEntry(AbstractTransaction txn, long entryID, Entry oldEntry, DN newDN) throws DirectoryException, NDBException, NdbApiException { // Remove the old DN from dn2id. dn2id.remove(txn, oldEntry); // Create a new entry that is a copy of the old entry but with the new DN. Entry newEntry = oldEntry.duplicate(false); newEntry.setDN(newDN); // Insert the new DN in dn2id. if (!dn2id.insert(txn, newDN, entryID, newEntry)) { Message message = ERR_NDB_MODIFYDN_ALREADY_EXISTS.get(newDN.toString()); throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS, message); } } /** * This method is called after the transaction has successfully * committed. */ public void postCommitAction() { // No implementation needed. } } /** * Make a new DN for a subordinate entry of a renamed or moved entry. * * @param oldDN The current DN of the subordinate entry. * @param oldSuffixLen The current DN length of the renamed or moved entry. * @param newSuffixDN The new DN of the renamed or moved entry. * @return The new DN of the subordinate entry. */ public static DN modDN(DN oldDN, int oldSuffixLen, DN newSuffixDN) { int oldDNNumComponents = oldDN.getNumComponents(); int oldDNKeepComponents = oldDNNumComponents - oldSuffixLen; int newSuffixDNComponents = newSuffixDN.getNumComponents(); RDN[] newDNComponents = new RDN[oldDNKeepComponents+newSuffixDNComponents]; for (int i=0; i < oldDNKeepComponents; i++) { newDNComponents[i] = oldDN.getRDN(i); } for (int i=oldDNKeepComponents, j=0; j < newSuffixDNComponents; i++,j++) { newDNComponents[i] = newSuffixDN.getRDN(j); } return new DN(newDNComponents); } /** * Get a count of the number of entries stored in this entry entryContainer. * * @return The number of entries stored in this entry entryContainer. * @throws NdbApiException If an error occurs in the NDB database. */ public long getEntryCount() throws NdbApiException { return dn2id.getRecordCount(); } /** * Get the number of values for which the entry limit has been exceeded * since the entry entryContainer was opened. * @return The number of values for which the entry limit has been exceeded. */ public int getEntryLimitExceededCount() { int count = 0; return count; } /** * Get a list of the databases opened by this entryContainer. * @param dbList A list of database containers. */ public void listDatabases(List dbList) { dbList.add(dn2id); } /** * Determine whether the provided operation has the ManageDsaIT request * control. * @param operation The operation for which the determination is to be made. * @return true if the operation has the ManageDsaIT request control, or false * if not. */ public static boolean isManageDsaITOperation(Operation operation) { if(operation != null) { List controls = operation.getRequestControls(); if (controls != null) { for (Control control : controls) { if (control.getOID().equals(ServerConstants.OID_MANAGE_DSAIT_CONTROL)) { return true; } } } } return false; } /** * This method constructs a container name from a base DN. Only alphanumeric * characters are preserved, all other characters are replaced with an * underscore. * * @return The container name for the base DN. */ public String getDatabasePrefix() { return databasePrefix; } /** * Get the baseDN this entry container is responsible for. * * @return The Base DN for this entry container. */ public DN getBaseDN() { return baseDN; } /** * Get the parent of a DN in the scope of the base DN. * * @param dn A DN which is in the scope of the base DN. * @return The parent DN, or null if the given DN is the base DN. */ public DN getParentWithinBase(DN dn) { if (dn.equals(baseDN)) { return null; } return dn.getParent(); } /** * {@inheritDoc} */ public synchronized boolean isConfigurationChangeAcceptable( NdbBackendCfg cfg, List unacceptableReasons) { // This is always true because only all config attributes used // by the entry container should be validated by the admin framework. return true; } /** * {@inheritDoc} */ public synchronized ConfigChangeResult applyConfigurationChange( NdbBackendCfg cfg) { boolean adminActionRequired = false; ArrayList messages = new ArrayList(); this.config = cfg; this.deadlockRetryLimit = config.getDeadlockRetryLimit(); return new ConfigChangeResult(ResultCode.SUCCESS, adminActionRequired, messages); } /** * Checks whether the target of an operation is a referral entry and throws * a Directory referral exception if it is. * @param entry The target entry of the operation, or the base entry of a * search operation. * @param searchScope The scope of the search operation, or null if the * operation is not a search operation. * @throws DirectoryException If a referral is found at or above the target * DN. The referral URLs will be set appropriately for the references found * in the referral entry. */ public void checkTargetForReferral(Entry entry, SearchScope searchScope) throws DirectoryException { Set referralURLs = entry.getReferralURLs(); if (referralURLs != null) { throwReferralException(entry.getDN(), entry.getDN(), referralURLs, searchScope); } } /** * Throws a Directory referral exception for the case where a referral entry * exists at or above the target DN of an operation. * @param targetDN The target DN of the operation, or the base object of a * search operation. * @param referralDN The DN of the referral entry. * @param labeledURIs The set of labeled URIs in the referral entry. * @param searchScope The scope of the search operation, or null if the * operation is not a search operation. * @throws DirectoryException If a referral is found at or above the target * DN. The referral URLs will be set appropriately for the references found * in the referral entry. */ public void throwReferralException(DN targetDN, DN referralDN, Set labeledURIs, SearchScope searchScope) throws DirectoryException { ArrayList URIList = new ArrayList(labeledURIs.size()); for (String labeledURI : labeledURIs) { // Remove the label part of the labeled URI if there is a label. String uri = labeledURI; int i = labeledURI.indexOf(' '); if (i != -1) { uri = labeledURI.substring(0, i); } try { LDAPURL ldapurl = LDAPURL.decode(uri, false); if (ldapurl.getScheme().equalsIgnoreCase("ldap")) { DN urlBaseDN = targetDN; if (!referralDN.equals(ldapurl.getBaseDN())) { urlBaseDN = EntryContainer.modDN(targetDN, referralDN.getNumComponents(), ldapurl.getBaseDN()); } ldapurl.setBaseDN(urlBaseDN); if (searchScope == null) { // RFC 3296, 5.2. Target Object Considerations: // In cases where the URI to be returned is a LDAP URL, the server // SHOULD trim any present scope, filter, or attribute list from the // URI before returning it. Critical extensions MUST NOT be trimmed // or modified. StringBuilder builder = new StringBuilder(uri.length()); ldapurl.toString(builder, true); uri = builder.toString(); } else { // RFC 3296, 5.3. Base Object Considerations: // In cases where the URI to be returned is a LDAP URL, the server // MUST provide an explicit scope specifier from the LDAP URL prior // to returning it. ldapurl.getAttributes().clear(); ldapurl.setScope(searchScope); ldapurl.setFilter(null); uri = ldapurl.toString(); } } } catch (DirectoryException e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } // Return the non-LDAP URI as is. } URIList.add(uri); } // Throw a directory referral exception containing the URIs. Message msg = NOTE_NDB_REFERRAL_RESULT_MESSAGE.get(String.valueOf(referralDN)); throw new DirectoryException( ResultCode.REFERRAL, msg, referralDN, URIList, null); } /** * Process referral entries that are above the target DN of an operation. * @param txn Abstract transaction for this operation. * @param targetDN The target DN of the operation, or the base object of a * search operation. * @param searchScope The scope of the search operation, or null if the * operation is not a search operation. * @throws DirectoryException If a referral is found at or above the target * DN. The referral URLs will be set appropriately for the references found * in the referral entry. * @throws NdbApiException An error occurred during a database operation. */ public void targetEntryReferrals(AbstractTransaction txn, DN targetDN, SearchScope searchScope) throws DirectoryException, NdbApiException { try { // Go up through the DIT hierarchy until we find a referral. for (DN dn = getParentWithinBase(targetDN); dn != null; dn = getParentWithinBase(dn)) { // Construct a set of all the labeled URIs in the referral. long id = dn2id.getID(txn, dn, NdbOperation.LockMode.LM_Read); Set labeledURIs = dn2id.getReferrals(txn, id); if (!labeledURIs.isEmpty()) { throwReferralException(targetDN, dn, labeledURIs, searchScope); } } } catch (NdbApiException e) { if (debugEnabled()) { TRACER.debugCaught(DebugLogLevel.ERROR, e); } } } /** * Finds an existing entry whose DN is the closest ancestor of a given baseDN. * * @param baseDN the DN for which we are searching a matched DN * @return the DN of the closest ancestor of the baseDN * @throws DirectoryException If an error prevented the check of an * existing entry from being performed */ private DN getMatchedDN(AbstractTransaction txn, DN baseDN) throws DirectoryException, NdbApiException { DN matchedDN = null; DN parentDN = baseDN.getParentDNInSuffix(); while ((parentDN != null) && parentDN.isDescendantOf(getBaseDN())) { if (entryExists(txn, parentDN)) { matchedDN = parentDN; break; } parentDN = parentDN.getParentDNInSuffix(); } return matchedDN; } }