| | |
| | | |
| | | import com.sleepycat.je.*; |
| | | |
| | | import org.opends.server.api.AttributeSyntax; |
| | | import org.opends.server.api.Backend; |
| | | import org.opends.server.api.EntryCache; |
| | | import org.opends.server.api.ClientConnection; |
| | | import org.opends.server.api.*; |
| | | import org.opends.server.api.plugin.PluginResult; |
| | | import org.opends.server.core.AddOperation; |
| | | import org.opends.server.core.DeleteOperation; |
| | |
| | | import org.opends.server.types.*; |
| | | import org.opends.server.util.StaticUtils; |
| | | import org.opends.server.util.ServerConstants; |
| | | import static org.opends.server.util.StaticUtils.stackTraceToSingleLineString; |
| | | |
| | | import java.util.*; |
| | | import java.util.concurrent.locks.Lock; |
| | |
| | | |
| | | private int subtreeDeleteBatchSize; |
| | | |
| | | private int indexEntryLimit; |
| | | |
| | | private String databasePrefix; |
| | | /** |
| | | * This class is responsible for managing the configuraiton for attribute |
| | |
| | | public boolean isConfigurationAddAcceptable( |
| | | LocalDBVLVIndexCfg cfg, List<Message> unacceptableReasons) |
| | | { |
| | | // TODO: validate more before returning true? |
| | | SearchFilter filter; |
| | | try |
| | | { |
| | | filter = |
| | | SearchFilter.createFilterFromString(cfg.getFilter()); |
| | | } |
| | | catch(Exception e) |
| | | { |
| | | Message msg = ERR_JEB_CONFIG_VLV_INDEX_BAD_FILTER.get( |
| | | cfg.getFilter(), cfg.getName(), |
| | | stackTraceToSingleLineString(e)); |
| | | unacceptableReasons.add(msg); |
| | | return false; |
| | | } |
| | | |
| | | String[] sortAttrs = cfg.getSortOrder().split(" "); |
| | | SortKey[] sortKeys = new SortKey[sortAttrs.length]; |
| | | OrderingMatchingRule[] orderingRules = |
| | | new OrderingMatchingRule[sortAttrs.length]; |
| | | boolean[] ascending = new boolean[sortAttrs.length]; |
| | | for(int i = 0; i < sortAttrs.length; i++) |
| | | { |
| | | try |
| | | { |
| | | if(sortAttrs[i].startsWith("-")) |
| | | { |
| | | ascending[i] = false; |
| | | sortAttrs[i] = sortAttrs[i].substring(1); |
| | | } |
| | | else |
| | | { |
| | | ascending[i] = true; |
| | | if(sortAttrs[i].startsWith("+")) |
| | | { |
| | | sortAttrs[i] = sortAttrs[i].substring(1); |
| | | } |
| | | } |
| | | } |
| | | catch(Exception e) |
| | | { |
| | | Message msg = |
| | | ERR_JEB_CONFIG_VLV_INDEX_UNDEFINED_ATTR.get( |
| | | String.valueOf(sortKeys[i]), cfg.getName()); |
| | | unacceptableReasons.add(msg); |
| | | return false; |
| | | } |
| | | |
| | | AttributeType attrType = |
| | | DirectoryServer.getAttributeType(sortAttrs[i].toLowerCase()); |
| | | if(attrType == null) |
| | | { |
| | | Message msg = ERR_JEB_CONFIG_VLV_INDEX_UNDEFINED_ATTR.get( |
| | | sortAttrs[i], cfg.getName()); |
| | | unacceptableReasons.add(msg); |
| | | return false; |
| | | } |
| | | sortKeys[i] = new SortKey(attrType, ascending[i]); |
| | | orderingRules[i] = attrType.getOrderingMatchingRule(); |
| | | } |
| | | |
| | | return true; |
| | | } |
| | | |
| | |
| | | this.deadlockRetryLimit = config.getDeadlockRetryLimit(); |
| | | this.subtreeDeleteSizeLimit = config.getSubtreeDeleteSizeLimit(); |
| | | this.subtreeDeleteBatchSize = config.getSubtreeDeleteBatchSize(); |
| | | this.indexEntryLimit = config.getIndexEntryLimit(); |
| | | |
| | | // Instantiate the attribute indexes. |
| | | attrIndexMap = new HashMap<AttributeType, AttributeIndex>(); |
| | |
| | | |
| | | id2children = new Index(databasePrefix + "_" + ID2CHILDREN_DATABASE_NAME, |
| | | new ID2CIndexer(), state, |
| | | indexEntryLimit, 0, true, |
| | | config.getIndexEntryLimit(), 0, true, |
| | | env,this); |
| | | id2children.open(); |
| | | id2subtree = new Index(databasePrefix + "_" + ID2SUBTREE_DATABASE_NAME, |
| | | new ID2SIndexer(), state, |
| | | indexEntryLimit, 0, true, |
| | | config.getIndexEntryLimit(), 0, true, |
| | | env, this); |
| | | id2subtree.open(); |
| | | |
| | |
| | | public long getNumSubordinates(DN entryDN, boolean subtree) |
| | | throws DatabaseException |
| | | { |
| | | EntryID entryID = dn2id.get(null, entryDN); |
| | | EntryID entryID = dn2id.get(null, entryDN, LockMode.DEFAULT); |
| | | if (entryID != null) |
| | | { |
| | | DatabaseEntry key = |
| | |
| | | if (entryIDList.size() > IndexFilter.FILTER_CANDIDATE_THRESHOLD) |
| | | { |
| | | // Read the ID from dn2id. |
| | | EntryID baseID = dn2id.get(null, baseDN); |
| | | EntryID baseID = dn2id.get(null, baseDN, LockMode.DEFAULT); |
| | | if (baseID == null) |
| | | { |
| | | Message message = |
| | |
| | | public Transaction beginOperationTransaction() throws DatabaseException |
| | | { |
| | | Transaction txn = beginTransaction(); |
| | | // Multiple adds should never encounter a deadlock. |
| | | txn.setLockTimeout(0); |
| | | return txn; |
| | | } |
| | | |
| | |
| | | throws DatabaseException, DirectoryException, JebException |
| | | { |
| | | // Check whether the entry already exists. |
| | | if (dn2id.get(txn, entry.getDN()) != null) |
| | | if (dn2id.get(txn, entry.getDN(), LockMode.DEFAULT) != null) |
| | | { |
| | | Message message = |
| | | ERR_JEB_ADD_ENTRY_ALREADY_EXISTS.get(entry.getDN().toString()); |
| | |
| | | dn2uri.targetEntryReferrals(entry.getDN(), null); |
| | | |
| | | // Read the parent ID from dn2id. |
| | | parentID = dn2id.get(txn, parentDN); |
| | | parentID = dn2id.get(txn, parentDN, LockMode.DEFAULT); |
| | | if (parentID == null) |
| | | { |
| | | Message message = ERR_JEB_ADD_NO_SUCH_OBJECT.get( |
| | |
| | | dn = getParentWithinBase(dn)) |
| | | { |
| | | // Read the ID from dn2id. |
| | | EntryID nodeID = dn2id.get(txn, dn); |
| | | EntryID nodeID = dn2id.get(txn, dn, LockMode.DEFAULT); |
| | | if (nodeID == null) |
| | | { |
| | | Message msg = |
| | |
| | | } |
| | | |
| | | /** |
| | | * Delete a leaf entry. |
| | | * The caller must be sure that the entry is indeed a leaf. We cannot |
| | | * rely on id2children to check for children since this entry may at |
| | | * one time have had enough children to exceed the index entry limit, |
| | | * after which the number of children IDs is unknown. |
| | | * |
| | | * @param id2cBuffered A buffered children index. |
| | | * @param id2sBuffered A buffered subtree index. |
| | | * @param txn The database transaction. |
| | | * @param leafDN The DN of the leaf entry to be deleted. |
| | | * @param leafID The ID of the leaf entry. |
| | | * @throws DatabaseException If an error occurs in the JE database. |
| | | * @throws DirectoryException If a Directory Server error occurs. |
| | | * @throws JebException If an error occurs in the JE backend. |
| | | */ |
| | | private void deleteLeaf(BufferedIndex id2cBuffered, |
| | | BufferedIndex id2sBuffered, |
| | | Transaction txn, |
| | | DN leafDN, |
| | | EntryID leafID) |
| | | throws DatabaseException, DirectoryException, JebException |
| | | { |
| | | // Check that the entry exists in id2entry and read its contents. |
| | | Entry entry = id2entry.get(txn, leafID); |
| | | if (entry == null) |
| | | { |
| | | Message msg = ERR_JEB_MISSING_ID2ENTRY_RECORD.get(leafID.toString()); |
| | | throw new JebException(msg); |
| | | } |
| | | |
| | | // Remove from dn2id. |
| | | if (!dn2id.remove(txn, leafDN)) |
| | | { |
| | | // Do not expect to ever come through here. |
| | | Message message = ERR_JEB_DELETE_NO_SUCH_OBJECT.get(leafDN.toString()); |
| | | DN matchedDN = getMatchedDN(baseDN); |
| | | throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, |
| | | message, matchedDN, null); |
| | | } |
| | | |
| | | // Update the referral database. |
| | | dn2uri.deleteEntry(txn, entry); |
| | | |
| | | // Remove from id2entry. |
| | | if (!id2entry.remove(txn, leafID)) |
| | | { |
| | | Message msg = ERR_JEB_MISSING_ID2ENTRY_RECORD.get(leafID.toString()); |
| | | throw new JebException(msg); |
| | | } |
| | | |
| | | // Remove from the indexes, in index config order. |
| | | indexRemoveEntry(txn, entry, leafID); |
| | | |
| | | // Make sure this entry either has no children in id2children, |
| | | // or that the index entry limit has been exceeded. |
| | | byte[] keyID = leafID.getDatabaseEntry().getData(); |
| | | EntryIDSet children = id2cBuffered.get(keyID); |
| | | if (!children.isDefined()) |
| | | { |
| | | id2cBuffered.remove(keyID); |
| | | } |
| | | else if (children.size() != 0) |
| | | { |
| | | Message message = |
| | | ERR_JEB_DELETE_NOT_ALLOWED_ON_NONLEAF.get(leafDN.toString()); |
| | | throw new DirectoryException(ResultCode.NOT_ALLOWED_ON_NONLEAF, |
| | | message); |
| | | } |
| | | |
| | | // Make sure this entry either has no subordinates in id2subtree, |
| | | // or that the index entry limit has been exceeded. |
| | | EntryIDSet subordinates = id2sBuffered.get(keyID); |
| | | if (!subordinates.isDefined()) |
| | | { |
| | | id2sBuffered.remove(keyID); |
| | | } |
| | | else if (subordinates.size() != 0) |
| | | { |
| | | Message message = |
| | | ERR_JEB_DELETE_NOT_ALLOWED_ON_NONLEAF.get(leafDN.toString()); |
| | | throw new DirectoryException(ResultCode.NOT_ALLOWED_ON_NONLEAF, |
| | | message); |
| | | } |
| | | |
| | | // Iterate up through the superior entries. |
| | | boolean isParent = true; |
| | | for (DN dn = getParentWithinBase(leafDN); dn != null; |
| | | dn = getParentWithinBase(dn)) |
| | | { |
| | | // Read the ID from dn2id. |
| | | EntryID nodeID = dn2id.get(txn, dn); |
| | | if (nodeID == null) |
| | | { |
| | | Message msg = ERR_JEB_MISSING_DN2ID_RECORD.get(dn.toNormalizedString()); |
| | | throw new JebException(msg); |
| | | } |
| | | DatabaseEntry nodeIDData = nodeID.getDatabaseEntry(); |
| | | |
| | | // Remove from id2children. |
| | | if (isParent) |
| | | { |
| | | id2cBuffered.removeID(nodeIDData.getData(), leafID); |
| | | isParent = false; |
| | | } |
| | | |
| | | // Remove from id2subtree for this node. |
| | | id2sBuffered.removeID(nodeIDData.getData(), leafID); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 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 manageDsaIT In the case where the target entry is a referral entry, |
| | | * this parameter should be true if the target is to be deleted, or false if |
| | | * the target should generate a referral. |
| | | * @param id2cBuffered A buffered children index. |
| | | * @param id2sBuffered A buffered subtree index. |
| | | * @param txn The database transaction. |
| | | * @param leafDN The DN of the target entry to be deleted. |
| | | * @throws DatabaseException If an error occurs in the JE database. |
| | | * @throws DirectoryException If a Directory Server error occurs. |
| | | * @throws JebException If an error occurs in the JE backend. |
| | | */ |
| | | private void deleteTarget(boolean manageDsaIT, |
| | | BufferedIndex id2cBuffered, |
| | | BufferedIndex id2sBuffered, |
| | | Transaction txn, |
| | | DN leafDN) |
| | | throws DatabaseException, DirectoryException, JebException |
| | | { |
| | | // Read the entry ID from dn2id. |
| | | EntryID leafID = dn2id.get(txn, leafDN); |
| | | if (leafID == null) |
| | | { |
| | | Message message = ERR_JEB_DELETE_NO_SUCH_OBJECT.get(leafDN.toString()); |
| | | DN matchedDN = getMatchedDN(baseDN); |
| | | throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, |
| | | message, matchedDN, null); |
| | | } |
| | | |
| | | // Check that the entry exists in id2entry and read its contents. |
| | | Entry entry = id2entry.get(txn, leafID); |
| | | if (entry == null) |
| | | { |
| | | Message msg = ERR_JEB_MISSING_ID2ENTRY_RECORD.get(leafID.toString()); |
| | | throw new JebException(msg); |
| | | } |
| | | |
| | | if (!manageDsaIT) |
| | | { |
| | | dn2uri.checkTargetForReferral(entry, null); |
| | | } |
| | | |
| | | // Remove from dn2id. |
| | | if (!dn2id.remove(txn, leafDN)) |
| | | { |
| | | // Do not expect to ever come through here. |
| | | Message message = ERR_JEB_DELETE_NO_SUCH_OBJECT.get(leafDN.toString()); |
| | | DN matchedDN = getMatchedDN(baseDN); |
| | | throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, |
| | | message, matchedDN, null); |
| | | } |
| | | |
| | | // Update the referral database. |
| | | dn2uri.deleteEntry(txn, entry); |
| | | |
| | | // Remove from id2entry. |
| | | if (!id2entry.remove(txn, leafID)) |
| | | { |
| | | Message msg = ERR_JEB_MISSING_ID2ENTRY_RECORD.get(leafID.toString()); |
| | | throw new JebException(msg); |
| | | } |
| | | |
| | | // Remove from the indexes, in index config order. |
| | | indexRemoveEntry(txn, entry, leafID); |
| | | |
| | | // Iterate up through the superior entries. |
| | | boolean isParent = true; |
| | | for (DN dn = getParentWithinBase(leafDN); dn != null; |
| | | dn = getParentWithinBase(dn)) |
| | | { |
| | | // Read the ID from dn2id. |
| | | EntryID nodeID = dn2id.get(txn, dn); |
| | | if (nodeID == null) |
| | | { |
| | | Message msg = ERR_JEB_MISSING_DN2ID_RECORD.get(dn.toNormalizedString()); |
| | | throw new JebException(msg); |
| | | } |
| | | DatabaseEntry nodeIDData = nodeID.getDatabaseEntry(); |
| | | |
| | | // Remove from id2children. |
| | | if (isParent) |
| | | { |
| | | id2cBuffered.removeID(nodeIDData.getData(), leafID); |
| | | isParent = false; |
| | | } |
| | | |
| | | // Remove from id2subtree for this node. |
| | | id2sBuffered.removeID(nodeIDData.getData(), leafID); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * This inner class implements the Delete Entry operation through |
| | | * the TransactedOperation interface. |
| | | */ |
| | |
| | | */ |
| | | 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<DN> deletedDNList; |
| | | |
| | | |
| | | /** |
| | | * Indicates whether the subtree delete size limit has been exceeded. |
| | |
| | | |
| | | |
| | | /** |
| | | * Indicates the count of deleted DNs in the Delete Operation. |
| | | * Indicates the total count of deleted DNs in the Delete Operation. |
| | | */ |
| | | private int countDeletedDN; |
| | | private int totalDeletedDN; |
| | | |
| | | /** |
| | | * Indicates the batch count of deleted DNs in the Delete Operation. |
| | | */ |
| | | private int batchDeletedDN; |
| | | |
| | | /** |
| | | * The index buffer used to buffer up the index changes. |
| | | */ |
| | | private IndexBuffer indexBuffer = null; |
| | | |
| | | /** |
| | | * Create a new Delete Entry Transaction. |
| | |
| | | { |
| | | this.entryDN = entryDN; |
| | | this.deleteOperation = deleteOperation; |
| | | deletedDNList = new ArrayList<DN>(); |
| | | } |
| | | |
| | | /** |
| | |
| | | public void resetBatchSize() |
| | | { |
| | | batchSizeExceeded=false; |
| | | deletedDNList.clear(); |
| | | batchDeletedDN = 0; |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | public int getDeletedEntryCount() |
| | | { |
| | | return countDeletedDN; |
| | | return totalDeletedDN; |
| | | } |
| | | |
| | | /** |
| | |
| | | public Transaction beginOperationTransaction() throws DatabaseException |
| | | { |
| | | Transaction txn = beginTransaction(); |
| | | // Multiple deletes should never encounter a deadlock. |
| | | txn.setLockTimeout(0); |
| | | return txn; |
| | | } |
| | | |
| | |
| | | dn2uri.targetEntryReferrals(entryDN, null); |
| | | |
| | | // Determine whether this is a subtree delete. |
| | | int adminSizeLimit = subtreeDeleteSizeLimit; |
| | | int deleteBatchSize = subtreeDeleteBatchSize; |
| | | boolean isSubtreeDelete = false; |
| | | List<Control> controls = deleteOperation.getRequestControls(); |
| | | if (controls != null) |
| | |
| | | |
| | | DatabaseEntry data = new DatabaseEntry(); |
| | | DatabaseEntry key = new DatabaseEntry(begin); |
| | | CursorConfig cursorConfig = new CursorConfig(); |
| | | cursorConfig.setReadCommitted(true); |
| | | |
| | | BufferedIndex id2cBuffered = new BufferedIndex(id2children, txn); |
| | | BufferedIndex id2sBuffered = new BufferedIndex(id2subtree, txn); |
| | | |
| | | Cursor cursor = dn2id.openCursor(txn, null); |
| | | Cursor cursor = dn2id.openCursor(txn, cursorConfig); |
| | | try |
| | | { |
| | | OperationStatus status; |
| | |
| | | } |
| | | |
| | | // Enforce any subtree delete size limit. |
| | | if (adminSizeLimit > 0 && countDeletedDN >= adminSizeLimit) |
| | | if (subtreeDeleteSizeLimit > 0 && |
| | | totalDeletedDN >= subtreeDeleteSizeLimit) |
| | | { |
| | | adminSizeLimitExceeded = true; |
| | | break; |
| | | } |
| | | |
| | | // Enforce any subtree delete batch size. |
| | | if (deleteBatchSize > 0 && deletedDNList.size() >= deleteBatchSize) |
| | | if (subtreeDeleteBatchSize > 0 && |
| | | batchDeletedDN >= subtreeDeleteBatchSize) |
| | | { |
| | | batchSizeExceeded = true; |
| | | break; |
| | | } |
| | | |
| | | // This is a subtree delete so crate a index buffer |
| | | // if it there isn't one. |
| | | if(indexBuffer == null) |
| | | { |
| | | indexBuffer = new IndexBuffer(EntryContainer.this); |
| | | } |
| | | |
| | | /* |
| | | * Delete this entry which by now must be a leaf because |
| | | * we have been deleting from the bottom of the tree upwards. |
| | | */ |
| | | EntryID entryID = new EntryID(data); |
| | | DN subordinateDN = DN.decode(new ASN1OctetString(key.getData())); |
| | | deleteLeaf(id2cBuffered, id2sBuffered, |
| | | txn, subordinateDN, entryID); |
| | | deleteEntry(txn, true, entryDN, subordinateDN, entryID); |
| | | |
| | | deletedDNList.add(subordinateDN); |
| | | countDeletedDN++; |
| | | batchDeletedDN++; |
| | | totalDeletedDN++; |
| | | status = cursor.getPrev(key, data, LockMode.DEFAULT); |
| | | } |
| | | } |
| | |
| | | if (!adminSizeLimitExceeded && !batchSizeExceeded) |
| | | { |
| | | // Enforce any subtree delete size limit. |
| | | if (adminSizeLimit > 0 && countDeletedDN >= adminSizeLimit) |
| | | if (subtreeDeleteSizeLimit > 0 && |
| | | totalDeletedDN >= subtreeDeleteSizeLimit) |
| | | { |
| | | adminSizeLimitExceeded = true; |
| | | } |
| | | else if (deleteBatchSize > 0 && |
| | | deletedDNList.size() >= deleteBatchSize) |
| | | else if (subtreeDeleteBatchSize > 0 && |
| | | batchDeletedDN >= subtreeDeleteBatchSize) |
| | | { |
| | | batchSizeExceeded = true; |
| | | } |
| | | else |
| | | { |
| | | boolean manageDsaIT; |
| | | if (isSubtreeDelete) |
| | | { |
| | | // draft-armijo-ldap-treedelete, 4.1 Tree Delete Semantics: |
| | | // The server MUST NOT chase referrals stored in the tree. If |
| | | // information about referrals is stored in this section of the |
| | | // tree, this pointer will be deleted. |
| | | manageDsaIT = true; |
| | | } |
| | | else |
| | | { |
| | | manageDsaIT = isManageDsaITOperation(deleteOperation); |
| | | } |
| | | deleteTarget(manageDsaIT, id2cBuffered, id2sBuffered, txn, entryDN); |
| | | // draft-armijo-ldap-treedelete, 4.1 Tree Delete Semantics: |
| | | // The server MUST NOT chase referrals stored in the tree. If |
| | | // information about referrals is stored in this section of the |
| | | // tree, this pointer will be deleted. |
| | | deleteEntry(txn, |
| | | isSubtreeDelete || isManageDsaITOperation(deleteOperation), |
| | | entryDN, null, null); |
| | | |
| | | deletedDNList.add(entryDN); |
| | | countDeletedDN++; |
| | | batchDeletedDN++; |
| | | totalDeletedDN++; |
| | | } |
| | | } |
| | | |
| | | // Write out any buffered index values. |
| | | id2cBuffered.flush(); |
| | | id2sBuffered.flush(); |
| | | if(indexBuffer != null) |
| | | { |
| | | indexBuffer.flush(txn); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Delete an entry with appropriate handling of referral entries. |
| | | * The caller must be sure that the entry is indeed a leaf. We cannot |
| | | * rely on id2children to check for children since this entry may at |
| | | * one time have had enough children to exceed the index entry limit, |
| | | * after which the number of children IDs is unknown. |
| | | * |
| | | * @param txn The database transaction. |
| | | * @param manageDsaIT Whether it is an manage DSA IT operation. |
| | | * @param targetDN The DN of the target entry. |
| | | * @param leafDN The DN of the leaf entry to be deleted. |
| | | * @param leafID The ID of the leaf entry. |
| | | * @throws DatabaseException If an error occurs in the JE database. |
| | | * @throws DirectoryException If a Directory Server error occurs. |
| | | * @throws JebException If an error occurs in the JE backend. |
| | | */ |
| | | private void deleteEntry(Transaction txn, |
| | | boolean manageDsaIT, |
| | | DN targetDN, |
| | | DN leafDN, |
| | | EntryID leafID) |
| | | throws DatabaseException, DirectoryException, JebException |
| | | { |
| | | if(leafID == null || leafDN == null) |
| | | { |
| | | // Read the entry ID from dn2id. |
| | | leafDN = targetDN; |
| | | leafID = dn2id.get(txn, leafDN, LockMode.RMW); |
| | | if (leafID == null) |
| | | { |
| | | Message message = |
| | | ERR_JEB_DELETE_NO_SUCH_OBJECT.get(leafDN.toString()); |
| | | DN matchedDN = getMatchedDN(baseDN); |
| | | throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, |
| | | message, matchedDN, null); |
| | | } |
| | | } |
| | | |
| | | // Remove from dn2id. |
| | | if (!dn2id.remove(txn, leafDN)) |
| | | { |
| | | // Do not expect to ever come through here. |
| | | Message message = ERR_JEB_DELETE_NO_SUCH_OBJECT.get(leafDN.toString()); |
| | | DN matchedDN = getMatchedDN(baseDN); |
| | | throw new DirectoryException(ResultCode.NO_SUCH_OBJECT, |
| | | message, matchedDN, null); |
| | | } |
| | | |
| | | // Check that the entry exists in id2entry and read its contents. |
| | | Entry entry = id2entry.get(txn, leafID, LockMode.RMW); |
| | | if (entry == null) |
| | | { |
| | | Message msg = ERR_JEB_MISSING_ID2ENTRY_RECORD.get(leafID.toString()); |
| | | throw new JebException(msg); |
| | | } |
| | | |
| | | if (!manageDsaIT) |
| | | { |
| | | dn2uri.checkTargetForReferral(entry, null); |
| | | } |
| | | |
| | | // Update the referral database. |
| | | dn2uri.deleteEntry(txn, entry); |
| | | |
| | | // Remove from id2entry. |
| | | if (!id2entry.remove(txn, leafID)) |
| | | { |
| | | Message msg = ERR_JEB_MISSING_ID2ENTRY_RECORD.get(leafID.toString()); |
| | | throw new JebException(msg); |
| | | } |
| | | |
| | | // Remove from the indexes, in index config order. |
| | | if(indexBuffer != null) |
| | | { |
| | | indexRemoveEntry(indexBuffer, entry, leafID); |
| | | } |
| | | else |
| | | { |
| | | indexRemoveEntry(txn, entry, leafID); |
| | | } |
| | | |
| | | // Remove the id2c and id2s records for this entry. |
| | | if(indexBuffer != null) |
| | | { |
| | | byte[] leafIDKeyBytes = |
| | | JebFormat.entryIDToDatabase(leafID.longValue()); |
| | | id2children.delete(indexBuffer, leafIDKeyBytes); |
| | | id2subtree.delete(indexBuffer, leafIDKeyBytes); |
| | | } |
| | | else |
| | | { |
| | | DatabaseEntry leafIDKey = leafID.getDatabaseEntry(); |
| | | id2children.delete(txn, leafIDKey); |
| | | id2subtree.delete(txn, leafIDKey); |
| | | } |
| | | |
| | | // Iterate up through the superior entries from the target entry. |
| | | boolean isParent = true; |
| | | for (DN parentDN = getParentWithinBase(targetDN); parentDN != null; |
| | | parentDN = getParentWithinBase(parentDN)) |
| | | { |
| | | // Read the ID from dn2id. |
| | | EntryID parentID = dn2id.get(txn, parentDN, LockMode.DEFAULT); |
| | | if (parentID == null) |
| | | { |
| | | Message msg = |
| | | ERR_JEB_MISSING_DN2ID_RECORD.get(parentDN.toNormalizedString()); |
| | | throw new JebException(msg); |
| | | } |
| | | |
| | | if(indexBuffer != null) |
| | | { |
| | | byte[] parentIDBytes = |
| | | JebFormat.entryIDToDatabase(parentID.longValue()); |
| | | // Remove from id2children. |
| | | if (isParent) |
| | | { |
| | | id2children.removeID(indexBuffer, parentIDBytes, leafID); |
| | | isParent = false; |
| | | } |
| | | id2subtree.removeID(indexBuffer, parentIDBytes, leafID); |
| | | } |
| | | else |
| | | { |
| | | DatabaseEntry nodeIDData = parentID.getDatabaseEntry(); |
| | | // Remove from id2children. |
| | | if(isParent) |
| | | { |
| | | id2children.removeID(txn, nodeIDData, leafID); |
| | | isParent = false; |
| | | } |
| | | id2subtree.removeID(txn, nodeIDData, leafID); |
| | | } |
| | | } |
| | | |
| | | // Remove the entry from the entry cache. |
| | | EntryCache entryCache = DirectoryServer.getEntryCache(); |
| | | if (entryCache != null) |
| | | { |
| | | entryCache.removeEntry(leafDN); |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | public void postCommitAction() |
| | | { |
| | | // Update the entry cache. |
| | | EntryCache entryCache = DirectoryServer.getEntryCache(); |
| | | if (entryCache != null) |
| | | { |
| | | for (DN dn : deletedDNList) |
| | | { |
| | | entryCache.removeEntry(dn); |
| | | } |
| | | } |
| | | |
| | | } |
| | | } |
| | | |
| | |
| | | EntryID id = null; |
| | | try |
| | | { |
| | | id = dn2id.get(null, entryDN); |
| | | id = dn2id.get(null, entryDN, LockMode.DEFAULT); |
| | | } |
| | | catch (DatabaseException e) |
| | | { |
| | |
| | | JebException |
| | | { |
| | | // Read dn2id. |
| | | entryID = dn2id.get(txn, entryDN); |
| | | entryID = dn2id.get(txn, entryDN, LockMode.DEFAULT); |
| | | if (entryID == null) |
| | | { |
| | | // The entryDN does not exist. |
| | |
| | | } |
| | | |
| | | // Read id2entry. |
| | | entry = id2entry.get(txn, entryID); |
| | | entry = id2entry.get(txn, entryID, LockMode.DEFAULT); |
| | | |
| | | if (entry == null) |
| | | { |
| | |
| | | JebException |
| | | { |
| | | // Read id2entry. |
| | | entry = id2entry.get(txn, entryID); |
| | | entry = id2entry.get(txn, entryID, LockMode.DEFAULT); |
| | | } |
| | | |
| | | /** |
| | |
| | | public Transaction beginOperationTransaction() throws DatabaseException |
| | | { |
| | | Transaction txn = beginTransaction(); |
| | | // Multiple replace operations should never encounter a deadlock. |
| | | txn.setLockTimeout(0); |
| | | return txn; |
| | | } |
| | | |
| | |
| | | JebException |
| | | { |
| | | // Read dn2id. |
| | | entryID = dn2id.get(txn, entry.getDN()); |
| | | entryID = dn2id.get(txn, entry.getDN(), LockMode.RMW); |
| | | if (entryID == null) |
| | | { |
| | | // The entry does not exist. |
| | |
| | | } |
| | | |
| | | // Read id2entry for the original entry. |
| | | Entry originalEntry = id2entry.get(txn, entryID); |
| | | Entry originalEntry = id2entry.get(txn, entryID, LockMode.RMW); |
| | | if (originalEntry == null) |
| | | { |
| | | // The entry does not exist. |
| | |
| | | */ |
| | | private ModifyDNOperation modifyDNOperation; |
| | | |
| | | |
| | | /** |
| | | * A buffered children index. |
| | | * Whether the apex entry moved under another parent. |
| | | */ |
| | | private BufferedIndex id2cBuffered; |
| | | |
| | | /** |
| | | * A buffered subtree index. |
| | | */ |
| | | private BufferedIndex id2sBuffered; |
| | | private boolean isApexEntryMoved; |
| | | |
| | | /** |
| | | * Create a new transacted operation for a Modify DN operation. |
| | |
| | | this.newSuperiorDN = getParentWithinBase(entry.getDN()); |
| | | this.newApexEntry = entry; |
| | | this.modifyDNOperation = modifyDNOperation; |
| | | |
| | | if(oldSuperiorDN != null) |
| | | { |
| | | this.isApexEntryMoved = ! oldSuperiorDN.equals(newSuperiorDN); |
| | | } |
| | | else if(newSuperiorDN != null) |
| | | { |
| | | this.isApexEntryMoved = ! newSuperiorDN.equals(oldSuperiorDN); |
| | | } |
| | | else |
| | | { |
| | | this.isApexEntryMoved = false; |
| | | } |
| | | } |
| | | |
| | | /** |
| | |
| | | * @throws DirectoryException If a Directory Server error occurs. |
| | | * @throws JebException If an error occurs in the JE backend. |
| | | */ |
| | | public void invokeOperation(Transaction txn) throws DatabaseException, |
| | | DirectoryException, |
| | | JebException |
| | | public void invokeOperation(Transaction txn) |
| | | throws DatabaseException, DirectoryException, JebException |
| | | { |
| | | DN requestedNewSuperiorDN = null; |
| | | |
| | | if(modifyDNOperation != null) |
| | | { |
| | | requestedNewSuperiorDN = modifyDNOperation.getNewSuperior(); |
| | | } |
| | | IndexBuffer buffer = new IndexBuffer(EntryContainer.this); |
| | | |
| | | // Check whether the renamed entry already exists. |
| | | if (dn2id.get(txn, newApexEntry.getDN()) != null) |
| | | if (dn2id.get(txn, newApexEntry.getDN(), LockMode.DEFAULT) != null) |
| | | { |
| | | Message message = ERR_JEB_MODIFYDN_ALREADY_EXISTS.get( |
| | | newApexEntry.getDN().toString()); |
| | |
| | | message); |
| | | } |
| | | |
| | | EntryID oldApexID = dn2id.get(txn, oldApexDN); |
| | | EntryID oldApexID = dn2id.get(txn, oldApexDN, LockMode.DEFAULT); |
| | | if (oldApexID == null) |
| | | { |
| | | // Check for referral entries above the target entry. |
| | |
| | | message, matchedDN, null); |
| | | } |
| | | |
| | | Entry oldApexEntry = id2entry.get(txn, oldApexID); |
| | | Entry oldApexEntry = id2entry.get(txn, oldApexID, LockMode.DEFAULT); |
| | | if (oldApexEntry == null) |
| | | { |
| | | Message msg = ERR_JEB_MISSING_ID2ENTRY_RECORD.get(oldApexID.toString()); |
| | |
| | | dn2uri.checkTargetForReferral(oldApexEntry, null); |
| | | } |
| | | |
| | | id2cBuffered = new BufferedIndex(id2children, txn); |
| | | id2sBuffered = new BufferedIndex(id2subtree, txn); |
| | | |
| | | EntryID newApexID = oldApexID; |
| | | if (newSuperiorDN != null) |
| | | if (newSuperiorDN != null && isApexEntryMoved) |
| | | { |
| | | /* |
| | | * We want to preserve the invariant that the ID of an |
| | | * entry is greater than its parent, since search |
| | | * results are returned in ID order. |
| | | */ |
| | | EntryID newSuperiorID = dn2id.get(txn, newSuperiorDN); |
| | | EntryID newSuperiorID = dn2id.get(txn, newSuperiorDN, LockMode.DEFAULT); |
| | | if (newSuperiorID == null) |
| | | { |
| | | Message msg = |
| | |
| | | // expensive since every entry has to be deleted from |
| | | // and added back into the attribute indexes. |
| | | newApexID = rootContainer.getNextEntryID(); |
| | | |
| | | if(debugEnabled()) |
| | | { |
| | | TRACER.debugInfo("Move of target entry requires renumbering" + |
| | | "all entries in the subtree. " + |
| | | "Old DN: %s " + |
| | | "New DN: %s " + |
| | | "Old entry ID: %d " + |
| | | "New entry ID: %d " + |
| | | "New Superior ID: %d" + |
| | | oldApexEntry.getDN(), newApexEntry.getDN(), |
| | | oldApexID.longValue(), newApexID.longValue(), |
| | | newSuperiorID.longValue()); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // Move or rename the apex entry. |
| | | if (requestedNewSuperiorDN != null) |
| | | { |
| | | moveApexEntry(txn, oldApexID, newApexID, oldApexEntry, newApexEntry); |
| | | } |
| | | else |
| | | { |
| | | renameApexEntry(txn, oldApexID, oldApexEntry, newApexEntry); |
| | | } |
| | | renameApexEntry(txn, buffer, oldApexID, newApexID, oldApexEntry, |
| | | newApexEntry); |
| | | |
| | | /* |
| | | * We will iterate forwards through a range of the dn2id keys to |
| | |
| | | |
| | | DatabaseEntry data = new DatabaseEntry(); |
| | | DatabaseEntry key = new DatabaseEntry(begin); |
| | | int subordinateEntriesMoved = 0; |
| | | |
| | | Cursor cursor = dn2id.openCursor(txn, null); |
| | | CursorConfig cursorConfig = new CursorConfig(); |
| | | cursorConfig.setReadCommitted(true); |
| | | Cursor cursor = dn2id.openCursor(txn, cursorConfig); |
| | | try |
| | | { |
| | | OperationStatus status; |
| | | |
| | | // Initialize the cursor very close to the starting value. |
| | | status = cursor.getSearchKeyRange(key, data, LockMode.RMW); |
| | | status = cursor.getSearchKeyRange(key, data, LockMode.DEFAULT); |
| | | |
| | | // Step forward until the key is greater than the starting value. |
| | | while (status == OperationStatus.SUCCESS && |
| | | dn2id.getComparator().compare(key.getData(), begin) <= 0) |
| | | { |
| | | status = cursor.getNext(key, data, LockMode.RMW); |
| | | status = cursor.getNext(key, data, LockMode.DEFAULT); |
| | | } |
| | | |
| | | // Step forward until we pass the ending value. |
| | |
| | | // We have found a subordinate entry. |
| | | |
| | | EntryID oldID = new EntryID(data); |
| | | Entry oldEntry = id2entry.get(txn, oldID); |
| | | Entry oldEntry = id2entry.get(txn, oldID, LockMode.DEFAULT); |
| | | |
| | | // 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. |
| | | EntryID newID = oldID; |
| | | if (!newApexID.equals(oldApexID)) |
| | | { |
| | | // Assign a new entry ID if we are renumbering. |
| | | EntryID newID = oldID; |
| | | if (!newApexID.equals(oldApexID)) |
| | | { |
| | | newID = rootContainer.getNextEntryID(); |
| | | } |
| | | newID = rootContainer.getNextEntryID(); |
| | | |
| | | // Move this entry. |
| | | moveSubordinateEntry(txn, oldID, newID, oldEntry, newDN); |
| | | if(debugEnabled()) |
| | | { |
| | | TRACER.debugInfo("Move of subordinate entry requires " + |
| | | "renumbering. " + |
| | | "Old DN: %s " + |
| | | "New DN: %s " + |
| | | "Old entry ID: %d " + |
| | | "New entry ID: %d", |
| | | oldEntry.getDN(), newDN, oldID.longValue(), |
| | | newID.longValue()); |
| | | } |
| | | } |
| | | else |
| | | |
| | | // Move this entry. |
| | | renameSubordinateEntry(txn, buffer, oldID, newID, oldEntry, newDN); |
| | | subordinateEntriesMoved++; |
| | | |
| | | if(subordinateEntriesMoved >= subtreeDeleteBatchSize) |
| | | { |
| | | // Rename this entry. |
| | | renameSubordinateEntry(txn, oldID, oldEntry, newDN); |
| | | buffer.flush(txn); |
| | | subordinateEntriesMoved = 0; |
| | | } |
| | | |
| | | // Get the next DN. |
| | | status = cursor.getNext(key, data, LockMode.RMW); |
| | | status = cursor.getNext(key, data, LockMode.DEFAULT); |
| | | } |
| | | } |
| | | finally |
| | |
| | | cursor.close(); |
| | | } |
| | | |
| | | id2cBuffered.flush(); |
| | | id2sBuffered.flush(); |
| | | buffer.flush(txn); |
| | | } |
| | | |
| | | /** |
| | |
| | | */ |
| | | public Transaction beginOperationTransaction() throws DatabaseException |
| | | { |
| | | Transaction txn = beginTransaction(); |
| | | return txn; |
| | | } |
| | | |
| | | /** |
| | | * Update the database for the target entry of a ModDN operation |
| | | * specifying a new superior. |
| | | * |
| | | * @param txn The database transaction to be used for the updates. |
| | | * @param oldID The original ID of the target entry. |
| | | * @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 JebException If an error occurs in the JE backend. |
| | | * @throws DirectoryException If a Directory Server error occurs. |
| | | * @throws DatabaseException If an error occurs in the JE database. |
| | | */ |
| | | private void moveApexEntry(Transaction txn, |
| | | EntryID oldID, EntryID newID, |
| | | Entry oldEntry, Entry newEntry) |
| | | throws JebException, DirectoryException, DatabaseException |
| | | { |
| | | DN oldDN = oldEntry.getDN(); |
| | | DN newDN = newEntry.getDN(); |
| | | DN newParentDN = getParentWithinBase(newDN); |
| | | |
| | | // Remove the old DN from dn2id. |
| | | dn2id.remove(txn, oldDN); |
| | | |
| | | // Insert the new DN in dn2id. |
| | | if (!dn2id.insert(txn, newDN, newID)) |
| | | { |
| | | Message message = ERR_JEB_MODIFYDN_ALREADY_EXISTS.get(newDN.toString()); |
| | | throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS, |
| | | message); |
| | | } |
| | | |
| | | // Update any referral records. |
| | | dn2uri.replaceEntry(txn, oldEntry, newEntry); |
| | | |
| | | // Remove the old ID from id2entry. |
| | | if (!newID.equals(oldID) || modifyDNOperation == null) |
| | | { |
| | | id2entry.remove(txn, oldID); |
| | | |
| | | // Remove the old ID from the indexes. |
| | | indexRemoveEntry(txn, oldEntry, oldID); |
| | | |
| | | // Insert the new ID into the indexes. |
| | | indexInsertEntry(txn, newEntry, newID); |
| | | } |
| | | else |
| | | { |
| | | // Update indexes only for those attributes that changed. |
| | | indexModifications(txn, oldEntry, newEntry, oldID, |
| | | modifyDNOperation.getModifications()); |
| | | } |
| | | |
| | | // Put the new entry in id2entry. |
| | | id2entry.put(txn, newID, newEntry); |
| | | |
| | | // Remove the old parentID:ID from id2children. |
| | | DN oldParentDN = getParentWithinBase(oldDN); |
| | | if (oldParentDN != null) |
| | | { |
| | | EntryID currentParentID = dn2id.get(txn, oldParentDN); |
| | | id2cBuffered.removeID(currentParentID.getDatabaseEntry().getData(), |
| | | oldID); |
| | | } |
| | | |
| | | // Put the new parentID:ID in id2children. |
| | | if (newParentDN != null) |
| | | { |
| | | EntryID parentID = dn2id.get(txn, newParentDN); |
| | | id2cBuffered.insertID(indexEntryLimit, |
| | | parentID.getDatabaseEntry().getData(), |
| | | newID); |
| | | } |
| | | |
| | | |
| | | // Remove the old nodeID:ID from id2subtree. |
| | | for (DN dn = getParentWithinBase(oldDN); dn != null; |
| | | dn = getParentWithinBase(dn)) |
| | | { |
| | | EntryID nodeID = dn2id.get(txn, dn); |
| | | id2sBuffered.removeID(nodeID.getDatabaseEntry().getData(), oldID); |
| | | } |
| | | |
| | | // Put the new nodeID:ID in id2subtree. |
| | | for (DN dn = newParentDN; dn != null; dn = getParentWithinBase(dn)) |
| | | { |
| | | EntryID nodeID = dn2id.get(txn, dn); |
| | | id2sBuffered.insertID(indexEntryLimit, |
| | | nodeID.getDatabaseEntry().getData(), newID); |
| | | } |
| | | |
| | | if (!newID.equals(oldID)) |
| | | { |
| | | // All the subordinates will be renumbered. |
| | | id2cBuffered.remove(oldID.getDatabaseEntry().getData()); |
| | | id2sBuffered.remove(oldID.getDatabaseEntry().getData()); |
| | | } |
| | | |
| | | // Remove the entry from the entry cache. |
| | | EntryCache entryCache = DirectoryServer.getEntryCache(); |
| | | if (entryCache != null) |
| | | { |
| | | entryCache.removeEntry(oldDN); |
| | | } |
| | | return beginTransaction(); |
| | | } |
| | | |
| | | /** |
| | |
| | | * not specifying a new superior. |
| | | * |
| | | * @param txn The database transaction to be used for the updates. |
| | | * @param entryID The ID of the target entry. |
| | | * @param buffer The index buffer used to buffer up the index changes. |
| | | * @param oldID The old ID of the target entry. |
| | | * @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 DatabaseException If an error occurs in the JE database. |
| | | * @throws JebException if an error occurs in the JE database. |
| | | */ |
| | | private void renameApexEntry(Transaction txn, EntryID entryID, |
| | | private void renameApexEntry(Transaction txn, IndexBuffer buffer, |
| | | EntryID oldID, EntryID newID, |
| | | Entry oldEntry, Entry newEntry) |
| | | throws DirectoryException, DatabaseException, JebException |
| | | { |
| | |
| | | // Remove the old DN from dn2id. |
| | | dn2id.remove(txn, oldDN); |
| | | |
| | | // Insert the new DN in dn2id. |
| | | if (!dn2id.insert(txn, newDN, entryID)) |
| | | // Put the new DN in dn2id. |
| | | if (!dn2id.insert(txn, newDN, newID)) |
| | | { |
| | | Message message = ERR_JEB_MODIFYDN_ALREADY_EXISTS.get(newDN.toString()); |
| | | throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS, |
| | | message); |
| | | message); |
| | | } |
| | | |
| | | // Remove old ID from id2entry and put the new entry |
| | | // (old entry with new DN) in id2entry. |
| | | if (!newID.equals(oldID)) |
| | | { |
| | | id2entry.remove(txn, oldID); |
| | | } |
| | | id2entry.put(txn, newID, newEntry); |
| | | |
| | | // Update any referral records. |
| | | dn2uri.replaceEntry(txn, oldEntry, newEntry); |
| | | |
| | | // Replace the entry in id2entry. |
| | | id2entry.put(txn, entryID, newEntry); |
| | | |
| | | if(modifyDNOperation == null) |
| | | // Remove the old ID from id2children and id2subtree of |
| | | // the old apex parent entry. |
| | | if(oldSuperiorDN != null && isApexEntryMoved) |
| | | { |
| | | // Remove the old ID from the indexes. |
| | | indexRemoveEntry(txn, oldEntry, entryID); |
| | | EntryID parentID; |
| | | byte[] parentIDKeyBytes; |
| | | boolean isParent = true; |
| | | for (DN dn = oldSuperiorDN; dn != null; dn = getParentWithinBase(dn)) |
| | | { |
| | | parentID = dn2id.get(txn, dn, LockMode.DEFAULT); |
| | | parentIDKeyBytes = |
| | | JebFormat.entryIDToDatabase(parentID.longValue()); |
| | | if(isParent) |
| | | { |
| | | id2children.removeID(buffer, parentIDKeyBytes, oldID); |
| | | isParent = false; |
| | | } |
| | | id2subtree.removeID(buffer, parentIDKeyBytes, oldID); |
| | | } |
| | | } |
| | | |
| | | // Insert the new ID into the indexes. |
| | | indexInsertEntry(txn, newEntry, entryID); |
| | | if (!newID.equals(oldID) || modifyDNOperation == null) |
| | | { |
| | | // All the subordinates will be renumbered so we have to rebuild |
| | | // id2c and id2s with the new ID. |
| | | byte[] oldIDKeyBytes = JebFormat.entryIDToDatabase(oldID.longValue()); |
| | | id2children.delete(buffer, oldIDKeyBytes); |
| | | id2subtree.delete(buffer, oldIDKeyBytes); |
| | | |
| | | // Reindex the entry with the new ID. |
| | | indexRemoveEntry(buffer, oldEntry, oldID); |
| | | indexInsertEntry(buffer, newEntry, newID); |
| | | } |
| | | else |
| | | { |
| | | // Update indexes only for those attributes that changed. |
| | | indexModifications(txn, oldEntry, newEntry, entryID, |
| | | modifyDNOperation.getModifications()); |
| | | // Update the indexes if needed. |
| | | indexModifications(buffer, oldEntry, newEntry, oldID, |
| | | modifyDNOperation.getModifications()); |
| | | } |
| | | |
| | | // Add the new ID to id2children and id2subtree of new apex parent entry. |
| | | if(newSuperiorDN != null && isApexEntryMoved) |
| | | { |
| | | EntryID parentID; |
| | | byte[] parentIDKeyBytes; |
| | | boolean isParent = true; |
| | | for (DN dn = newSuperiorDN; dn != null; dn = getParentWithinBase(dn)) |
| | | { |
| | | parentID = dn2id.get(txn, dn, LockMode.DEFAULT); |
| | | parentIDKeyBytes = |
| | | JebFormat.entryIDToDatabase(parentID.longValue()); |
| | | if(isParent) |
| | | { |
| | | id2children.insertID(buffer, parentIDKeyBytes, newID); |
| | | isParent = false; |
| | | } |
| | | id2subtree.insertID(buffer, parentIDKeyBytes, newID); |
| | | } |
| | | } |
| | | |
| | | // Remove the entry from the entry cache. |
| | |
| | | * of a Modify DN operation specifying a new superior. |
| | | * |
| | | * @param txn The database transaction to be used for the updates. |
| | | * @param buffer The index buffer used to buffer up the index changes. |
| | | * @param oldID The original ID of the subordinate entry. |
| | | * @param newID The new ID of the subordinate entry, or the original ID if |
| | | * the ID has not changed. |
| | |
| | | * @throws DirectoryException If a Directory Server error occurs. |
| | | * @throws DatabaseException If an error occurs in the JE database. |
| | | */ |
| | | private void moveSubordinateEntry(Transaction txn, |
| | | EntryID oldID, EntryID newID, |
| | | Entry oldEntry, DN newDN) |
| | | private void renameSubordinateEntry(Transaction txn, IndexBuffer buffer, |
| | | EntryID oldID, EntryID newID, |
| | | Entry oldEntry, DN newDN) |
| | | throws JebException, DirectoryException, DatabaseException |
| | | { |
| | | DN oldDN = oldEntry.getDN(); |
| | | DN newParentDN = getParentWithinBase(newDN); |
| | | Entry newEntry = oldEntry.duplicate(false); |
| | | newEntry.setDN(newDN); |
| | | List<Modification> modifications = |
| | | Collections.unmodifiableList(new ArrayList<Modification>(0)); |
| | | |
| | | // Create a new entry that is a copy of the old entry but with the new DN. |
| | | // Also invoke any subordinate modify DN plugins on the entry. |
| | | // FIXME -- At the present time, we don't support subordinate modify DN |
| | | // plugins that make changes to subordinate entries and therefore |
| | | // provide an unmodifiable list for the modifications element. |
| | | // FIXME -- This will need to be updated appropriately if we decided that |
| | | // these plugins should be invoked for synchronization |
| | | // operations. |
| | | if (! modifyDNOperation.isSynchronizationOperation()) |
| | | { |
| | | PluginConfigManager pluginManager = |
| | | DirectoryServer.getPluginConfigManager(); |
| | | PluginResult.SubordinateModifyDN pluginResult = |
| | | pluginManager.invokeSubordinateModifyDNPlugins( |
| | | modifyDNOperation, oldEntry, newEntry, modifications); |
| | | |
| | | if (!pluginResult.continueProcessing()) |
| | | { |
| | | Message message = ERR_JEB_MODIFYDN_ABORTED_BY_SUBORDINATE_PLUGIN.get( |
| | | oldDN.toString(), newDN.toString()); |
| | | throw new DirectoryException( |
| | | DirectoryServer.getServerErrorResultCode(), message); |
| | | } |
| | | |
| | | if (! modifications.isEmpty()) |
| | | { |
| | | MessageBuilder invalidReason = new MessageBuilder(); |
| | | if (! newEntry.conformsToSchema(null, false, false, false, |
| | | invalidReason)) |
| | | { |
| | | Message message = |
| | | ERR_JEB_MODIFYDN_ABORTED_BY_SUBORDINATE_SCHEMA_ERROR.get( |
| | | oldDN.toString(), |
| | | newDN.toString(), |
| | | invalidReason.toString()); |
| | | throw new DirectoryException( |
| | | DirectoryServer.getServerErrorResultCode(), message); |
| | | } |
| | | } |
| | | } |
| | | |
| | | // Remove the old DN from dn2id. |
| | | dn2id.remove(txn, oldDN); |
| | |
| | | { |
| | | Message message = ERR_JEB_MODIFYDN_ALREADY_EXISTS.get(newDN.toString()); |
| | | throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS, |
| | | message); |
| | | message); |
| | | } |
| | | |
| | | // Delete any existing referral records for the old DN. |
| | | dn2uri.deleteEntry(txn, oldEntry); |
| | | |
| | | // Remove old ID from id2entry. |
| | | // Remove old ID from id2entry and put the new entry |
| | | // (old entry with new DN) in id2entry. |
| | | if (!newID.equals(oldID)) |
| | | { |
| | | id2entry.remove(txn, oldID); |
| | | |
| | | // Update the attribute indexes. |
| | | for (AttributeIndex index : attrIndexMap.values()) |
| | | { |
| | | index.removeEntry(txn, oldID, oldEntry); |
| | | |
| | | index.addEntry(txn, newID, oldEntry); |
| | | } |
| | | |
| | | } |
| | | |
| | | |
| | | // Create a new entry that is a copy of the old entry but with the new DN. |
| | | // Also invoke any subordinate modify DN plugins on the entry. |
| | | // FIXME -- At the present time, we don't support subordinate modify DN |
| | | // plugins that make changes to subordinate entries and therefore |
| | | // provide an unmodifiable list for the modifications element. |
| | | // FIXME -- This will need to be updated appropriately if we decided that |
| | | // these plugins should be invoked for synchronization |
| | | // operations. |
| | | Entry newEntry = oldEntry.duplicate(false); |
| | | newEntry.setDN(newDN); |
| | | |
| | | if (! modifyDNOperation.isSynchronizationOperation()) |
| | | { |
| | | PluginConfigManager pluginManager = |
| | | DirectoryServer.getPluginConfigManager(); |
| | | List<Modification> modifications = |
| | | Collections.unmodifiableList(new ArrayList<Modification>(0)); |
| | | PluginResult.SubordinateModifyDN pluginResult = |
| | | pluginManager.invokeSubordinateModifyDNPlugins( |
| | | modifyDNOperation, oldEntry, newEntry, modifications); |
| | | |
| | | if (!pluginResult.continueProcessing()) |
| | | { |
| | | Message message = ERR_JEB_MODIFYDN_ABORTED_BY_SUBORDINATE_PLUGIN.get( |
| | | oldDN.toString(), newDN.toString()); |
| | | throw new DirectoryException( |
| | | DirectoryServer.getServerErrorResultCode(), message); |
| | | } |
| | | |
| | | if (! modifications.isEmpty()) |
| | | { |
| | | indexModifications(txn, oldEntry, newEntry, newID, modifications); |
| | | |
| | | MessageBuilder invalidReason = new MessageBuilder(); |
| | | if (! newEntry.conformsToSchema(null, false, false, false, |
| | | invalidReason)) |
| | | { |
| | | Message message = |
| | | ERR_JEB_MODIFYDN_ABORTED_BY_SUBORDINATE_SCHEMA_ERROR.get( |
| | | oldDN.toString(), |
| | | newDN.toString(), |
| | | invalidReason.toString()); |
| | | throw new DirectoryException( |
| | | DirectoryServer.getServerErrorResultCode(), message); |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | // Add any referral records for the new DN. |
| | | dn2uri.addEntry(txn, newEntry); |
| | | |
| | | // Put the new entry (old entry with new DN) in id2entry. |
| | | id2entry.put(txn, newID, newEntry); |
| | | |
| | | // Update any referral records. |
| | | dn2uri.replaceEntry(txn, oldEntry, newEntry); |
| | | |
| | | if(isApexEntryMoved) |
| | | { |
| | | // Remove the old ID from id2subtree of old apex superior entries. |
| | | for (DN dn = oldSuperiorDN; dn != null; dn = getParentWithinBase(dn)) |
| | | { |
| | | EntryID parentID = dn2id.get(txn, dn, LockMode.DEFAULT); |
| | | byte[] parentIDKeyBytes = |
| | | JebFormat.entryIDToDatabase(parentID.longValue()); |
| | | id2subtree.removeID(buffer, parentIDKeyBytes, oldID); |
| | | } |
| | | } |
| | | |
| | | if (!newID.equals(oldID)) |
| | | { |
| | | // All the subordinates will be renumbered. |
| | | id2cBuffered.remove(oldID.getDatabaseEntry().getData()); |
| | | id2sBuffered.remove(oldID.getDatabaseEntry().getData()); |
| | | // All the subordinates will be renumbered so we have to rebuild |
| | | // id2c and id2s with the new ID. |
| | | byte[] oldIDKeyBytes = JebFormat.entryIDToDatabase(oldID.longValue()); |
| | | id2children.delete(buffer, oldIDKeyBytes); |
| | | id2subtree.delete(buffer, oldIDKeyBytes); |
| | | |
| | | // Put the new parentID:ID in id2children. |
| | | if (newParentDN != null) |
| | | // Add new ID to the id2c and id2s of our new parent and |
| | | // new ID to id2s up the tree. |
| | | EntryID newParentID; |
| | | byte[] parentIDKeyBytes; |
| | | boolean isParent = true; |
| | | for (DN superiorDN = newDN; superiorDN != null; |
| | | superiorDN = getParentWithinBase(superiorDN)) |
| | | { |
| | | EntryID parentID = dn2id.get(txn, newParentDN); |
| | | id2cBuffered.insertID(indexEntryLimit, |
| | | parentID.getDatabaseEntry().getData(), |
| | | newID); |
| | | } |
| | | } |
| | | |
| | | // Remove the old nodeID:ID from id2subtree |
| | | for (DN dn = oldSuperiorDN; dn != null; dn = getParentWithinBase(dn)) |
| | | { |
| | | EntryID nodeID = dn2id.get(txn, dn); |
| | | id2sBuffered.removeID(nodeID.getDatabaseEntry().getData(), |
| | | oldID); |
| | | } |
| | | |
| | | // Put the new nodeID:ID in id2subtree. |
| | | for (DN dn = newParentDN; dn != null; dn = getParentWithinBase(dn)) |
| | | { |
| | | if (!newID.equals(oldID) || dn.isAncestorOf(newSuperiorDN)) |
| | | { |
| | | EntryID nodeID = dn2id.get(txn, dn); |
| | | id2sBuffered.insertID(indexEntryLimit, |
| | | nodeID.getDatabaseEntry().getData(), newID); |
| | | } |
| | | } |
| | | |
| | | // Remove the entry from the entry cache. |
| | | EntryCache entryCache = DirectoryServer.getEntryCache(); |
| | | if (entryCache != null) |
| | | { |
| | | entryCache.removeEntry(oldDN); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Update the database for a subordinate entry of the target entry |
| | | * of a Modify DN operation not specifying a new superior. |
| | | * |
| | | * @param txn The database 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 DatabaseException If an error occurs in the JE database. |
| | | */ |
| | | private void renameSubordinateEntry(Transaction txn, EntryID entryID, |
| | | Entry oldEntry, DN newDN) |
| | | throws DirectoryException, JebException, DatabaseException |
| | | { |
| | | DN oldDN = oldEntry.getDN(); |
| | | |
| | | // Remove the old DN from dn2id. |
| | | dn2id.remove(txn, oldDN); |
| | | |
| | | // Insert the new DN in dn2id. |
| | | if (!dn2id.insert(txn, newDN, entryID)) |
| | | { |
| | | Message message = ERR_JEB_MODIFYDN_ALREADY_EXISTS.get(newDN.toString()); |
| | | throw new DirectoryException(ResultCode.ENTRY_ALREADY_EXISTS, |
| | | message); |
| | | } |
| | | |
| | | // Delete any existing referral records for the old DN. |
| | | dn2uri.deleteEntry(txn, oldEntry); |
| | | |
| | | |
| | | // Create a new entry that is a copy of the old entry but with the new DN. |
| | | // Also invoke any subordinate modify DN plugins on the entry. |
| | | // FIXME -- At the present time, we don't support subordinate modify DN |
| | | // plugins that make changes to subordinate entries and therefore |
| | | // provide an unmodifiable list for the modifications element. |
| | | // FIXME -- This will need to be updated appropriately if we decided that |
| | | // these plugins should be invoked for synchronization |
| | | // operations. |
| | | Entry newEntry = oldEntry.duplicate(false); |
| | | newEntry.setDN(newDN); |
| | | |
| | | if (! modifyDNOperation.isSynchronizationOperation()) |
| | | { |
| | | PluginConfigManager pluginManager = |
| | | DirectoryServer.getPluginConfigManager(); |
| | | List<Modification> modifications = |
| | | Collections.unmodifiableList(new ArrayList<Modification>(0)); |
| | | PluginResult.SubordinateModifyDN pluginResult = |
| | | pluginManager.invokeSubordinateModifyDNPlugins( |
| | | modifyDNOperation, oldEntry, newEntry, modifications); |
| | | |
| | | if (!pluginResult.continueProcessing()) |
| | | { |
| | | Message message = ERR_JEB_MODIFYDN_ABORTED_BY_SUBORDINATE_PLUGIN.get( |
| | | oldDN.toString(), newDN.toString()); |
| | | throw new DirectoryException( |
| | | DirectoryServer.getServerErrorResultCode(), message); |
| | | } |
| | | |
| | | if (! modifications.isEmpty()) |
| | | { |
| | | indexModifications(txn, oldEntry, newEntry, entryID, modifications); |
| | | |
| | | MessageBuilder invalidReason = new MessageBuilder(); |
| | | if (! newEntry.conformsToSchema(null, false, false, false, |
| | | invalidReason)) |
| | | newParentID = dn2id.get(txn, superiorDN, LockMode.DEFAULT); |
| | | parentIDKeyBytes = |
| | | JebFormat.entryIDToDatabase(newParentID.longValue()); |
| | | if(isParent) |
| | | { |
| | | Message message = |
| | | ERR_JEB_MODIFYDN_ABORTED_BY_SUBORDINATE_SCHEMA_ERROR.get( |
| | | oldDN.toString(), newDN.toString(), |
| | | invalidReason.toString()); |
| | | throw new DirectoryException( |
| | | DirectoryServer.getServerErrorResultCode(), message); |
| | | id2children.insertID(buffer, parentIDKeyBytes, newID); |
| | | isParent = false; |
| | | } |
| | | id2subtree.insertID(buffer, parentIDKeyBytes, newID); |
| | | } |
| | | |
| | | // Reindex the entry with the new ID. |
| | | indexRemoveEntry(buffer, oldEntry, oldID); |
| | | indexInsertEntry(buffer, newEntry, newID); |
| | | } |
| | | else |
| | | { |
| | | // Update the indexes if needed. |
| | | if(! modifications.isEmpty()) |
| | | { |
| | | indexModifications(buffer, oldEntry, newEntry, oldID, modifications); |
| | | } |
| | | |
| | | if(isApexEntryMoved) |
| | | { |
| | | // Add the new ID to the id2s of new apex superior entries. |
| | | for(DN dn = newSuperiorDN; dn != null; dn = getParentWithinBase(dn)) |
| | | { |
| | | EntryID parentID = dn2id.get(txn, dn, LockMode.DEFAULT); |
| | | byte[] parentIDKeyBytes = |
| | | JebFormat.entryIDToDatabase(parentID.longValue()); |
| | | id2subtree.insertID(buffer, parentIDKeyBytes, newID); |
| | | } |
| | | } |
| | | } |
| | | |
| | | |
| | | // Add any referral records for the new DN. |
| | | dn2uri.addEntry(txn, newEntry); |
| | | |
| | | // Replace the entry in id2entry. |
| | | id2entry.put(txn, entryID, newEntry); |
| | | |
| | | // Remove the entry from the entry cache. |
| | | EntryCache entryCache = DirectoryServer.getEntryCache(); |
| | | if (entryCache != null) |
| | |
| | | } |
| | | |
| | | /** |
| | | * Insert a new entry into the attribute indexes. |
| | | * |
| | | * @param buffer The index buffer used to buffer up the index changes. |
| | | * @param entry The entry to be inserted into the indexes. |
| | | * @param entryID The ID of the entry to be inserted into the indexes. |
| | | * @throws DatabaseException If an error occurs in the JE database. |
| | | * @throws DirectoryException If a Directory Server error occurs. |
| | | * @throws JebException If an error occurs in the JE backend. |
| | | */ |
| | | private void indexInsertEntry(IndexBuffer buffer, Entry entry, |
| | | EntryID entryID) |
| | | throws DatabaseException, DirectoryException, JebException |
| | | { |
| | | for (AttributeIndex index : attrIndexMap.values()) |
| | | { |
| | | index.addEntry(buffer, entryID, entry); |
| | | } |
| | | |
| | | for (VLVIndex vlvIndex : vlvIndexMap.values()) |
| | | { |
| | | vlvIndex.addEntry(buffer, entryID, entry); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Remove an entry from the attribute indexes. |
| | | * |
| | | * @param txn The database transaction to be used for the updates. |
| | |
| | | } |
| | | |
| | | /** |
| | | * Remove an entry from the attribute indexes. |
| | | * |
| | | * @param buffer The index buffer used to buffer up the index changes. |
| | | * @param entry The entry to be removed from the indexes. |
| | | * @param entryID The ID of the entry to be removed from the indexes. |
| | | * @throws DatabaseException If an error occurs in the JE database. |
| | | * @throws DirectoryException If a Directory Server error occurs. |
| | | * @throws JebException If an error occurs in the JE backend. |
| | | */ |
| | | private void indexRemoveEntry(IndexBuffer buffer, Entry entry, |
| | | EntryID entryID) |
| | | throws DatabaseException, DirectoryException, JebException |
| | | { |
| | | for (AttributeIndex index : attrIndexMap.values()) |
| | | { |
| | | index.removeEntry(buffer, entryID, entry); |
| | | } |
| | | |
| | | for (VLVIndex vlvIndex : vlvIndexMap.values()) |
| | | { |
| | | vlvIndex.removeEntry(buffer, entryID, entry); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Update the attribute indexes to reflect the changes to the |
| | | * attributes of an entry resulting from a sequence of modifications. |
| | | * |
| | |
| | | } |
| | | |
| | | /** |
| | | * Update the attribute indexes to reflect the changes to the |
| | | * attributes of an entry resulting from a sequence of modifications. |
| | | * |
| | | * @param buffer The index buffer used to buffer up the index changes. |
| | | * @param oldEntry The contents of the entry before the change. |
| | | * @param newEntry The contents of the entry after the change. |
| | | * @param entryID The ID of the entry that was changed. |
| | | * @param mods The sequence of modifications made to the entry. |
| | | * @throws DatabaseException If an error occurs in the JE database. |
| | | * @throws DirectoryException If a Directory Server error occurs. |
| | | * @throws JebException If an error occurs in the JE backend. |
| | | */ |
| | | private void indexModifications(IndexBuffer buffer, Entry oldEntry, |
| | | Entry newEntry, |
| | | EntryID entryID, List<Modification> mods) |
| | | throws DatabaseException, DirectoryException, JebException |
| | | { |
| | | // Process in index configuration order. |
| | | for (AttributeIndex index : attrIndexMap.values()) |
| | | { |
| | | // Check whether any modifications apply to this indexed attribute. |
| | | boolean attributeModified = false; |
| | | AttributeType indexAttributeType = index.getAttributeType(); |
| | | Iterable<AttributeType> subTypes = |
| | | DirectoryServer.getSchema().getSubTypes(indexAttributeType); |
| | | |
| | | for (Modification mod : mods) |
| | | { |
| | | Attribute modAttr = mod.getAttribute(); |
| | | AttributeType modAttrType = modAttr.getAttributeType(); |
| | | if (modAttrType.equals(indexAttributeType)) |
| | | { |
| | | attributeModified = true; |
| | | break; |
| | | } |
| | | for(AttributeType subType : subTypes) |
| | | { |
| | | if(modAttrType.equals(subType)) |
| | | { |
| | | attributeModified = true; |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | if (attributeModified) |
| | | { |
| | | index.modifyEntry(buffer, entryID, oldEntry, newEntry, mods); |
| | | } |
| | | } |
| | | |
| | | for(VLVIndex vlvIndex : vlvIndexMap.values()) |
| | | { |
| | | vlvIndex.modifyEntry(buffer, entryID, oldEntry, newEntry, mods); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Get a count of the number of entries stored in this entry entryContainer. |
| | | * |
| | | * @return The number of entries stored in this entry entryContainer. |
| | |
| | | */ |
| | | public long getEntryCount() throws DatabaseException |
| | | { |
| | | EntryID entryID = dn2id.get(null, baseDN); |
| | | EntryID entryID = dn2id.get(null, baseDN, LockMode.DEFAULT); |
| | | if (entryID != null) |
| | | { |
| | | DatabaseEntry key = |
| | |
| | | this.deadlockRetryLimit = config.getDeadlockRetryLimit(); |
| | | this.subtreeDeleteSizeLimit = config.getSubtreeDeleteSizeLimit(); |
| | | this.subtreeDeleteBatchSize = config.getSubtreeDeleteBatchSize(); |
| | | this.indexEntryLimit = config.getIndexEntryLimit(); |
| | | return new ConfigChangeResult(ResultCode.SUCCESS, |
| | | adminActionRequired, messages); |
| | | } |
| | |
| | | |
| | | /** |
| | | * Get the exclusive lock. |
| | | * |
| | | */ |
| | | public void lock() { |
| | | exclusiveLock.lock(); |
| | |
| | | exclusiveLock.unlock(); |
| | | } |
| | | |
| | | /** |
| | | * Get the subtree delete batch size. |
| | | * |
| | | * @return The subtree delete batch size. |
| | | */ |
| | | public int getSubtreeDeleteBatchSize() |
| | | { |
| | | return subtreeDeleteBatchSize; |
| | | } |
| | | |
| | | } |