mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

boli
21.08.2008 d84ee43ec8ab4e6ecdad2f0ef0d64dac54f7d6af
opendj-sdk/opends/build.xml
@@ -1728,14 +1728,14 @@
    <echo message="      builds the server without the debug logging facility." />
    <echo message="      No debug logging messages will be included on test failures." />
    <echo message=""/>
    <echo message="  -Dorg.opends.test.debug.target=org.opends.server.core:level=verbose,category=data"/>
    <echo message="  -Dorg.opends.server.debug.target=org.opends.server.core:level=verbose,category=data"/>
    <echo message="      for example only include debug messages in the core"/>
    <echo message="      package that are related to data access and at the" />
    <echo message="      verbose level or higher. The syntax of this target" />
    <echo message="      definition is the same as the org.opends.server.debug.target.x" />
    <echo message="      property when starting ${SHORT_NAME}. " />
    <echo message="      Default debug target:"/>
    <echo message="      org.opends.server:level=warning,category=caught|data|database-access|message|protocol,stack" />
    <echo message="      org.opends.server:level=warning,category=caught|data|database-access|message|protocol,stack,cause" />
    <echo message=""/>
    <echo message="  -Dtest.diff.srcpath=src/server/org/opends/server/core"/>
    <echo message="      for example includes only the classes in"/>
@@ -1829,12 +1829,12 @@
      </not>
    </condition>
    <!-- This sets org.opends.test.debug.target if and only if its's not
    <!-- This sets org.opends.server.debug.target if and only if its's not
         already set. -->
    <condition property="org.opends.test.debug.target"
               value="org.opends.server:level=warning,category=caught|data|database-access|message|protocol,stack">
    <condition property="org.opends.server.debug.target"
               value="org.opends.server:level=warning,category=caught|data|database-access|message|protocol,stack,cause">
      <not>
        <isset property="org.opends.test.debug.target" />
        <isset property="org.opends.server.debug.target" />
      </not>
    </condition>
@@ -1932,7 +1932,7 @@
      <jvmarg value="-Dorg.opends.server.snmp.opendmk=${opendmk.lib.dir}"/>
      <jvmarg value="-Dorg.opends.test.suppressOutput=${org.opends.test.suppressOutput}" />
      <jvmarg value="-Dorg.opends.test.pauseOnFailure=${org.opends.test.pauseOnFailure}" />
      <jvmarg value="-Dorg.opends.test.debug.target=${org.opends.test.debug.target}" />
      <jvmarg value="-Dorg.opends.server.debug.target=${org.opends.server.debug.target}" />
      <jvmarg value="-Dorg.opends.test.copyClassesToTestPackage=${org.opends.test.copyClassesToTestPackage}" />
      <jvmarg value="-Dtest.progress=${test.progress}" />
      <jvmarg value="-Xms${MEM}" />
opendj-sdk/opends/src/server/org/opends/server/backends/jeb/AttributeIndex.java
@@ -334,6 +334,68 @@
  /**
   * Update the attribute index for a new entry.
   *
   * @param buffer The index buffer to use to store the added keys
   * @param entryID     The entry ID.
   * @param entry       The contents of the new entry.
   * @return True if all the index keys for the entry are added. False if the
   *         entry ID already exists for some keys.
   * @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.
   */
  public boolean addEntry(IndexBuffer buffer, EntryID entryID,
                          Entry entry)
       throws DatabaseException, DirectoryException, JebException
  {
    boolean success = true;
    if (equalityIndex != null)
    {
      if(!equalityIndex.addEntry(buffer, entryID, entry))
      {
        success = false;
      }
    }
    if (presenceIndex != null)
    {
      if(!presenceIndex.addEntry(buffer, entryID, entry))
      {
        success = false;
      }
    }
    if (substringIndex != null)
    {
      if(!substringIndex.addEntry(buffer, entryID, entry))
      {
        success = false;
      }
    }
    if (orderingIndex != null)
    {
      if(!orderingIndex.addEntry(buffer, entryID, entry))
      {
        success = false;
      }
    }
    if (approximateIndex != null)
    {
      if(!approximateIndex.addEntry(buffer, entryID, entry))
      {
        success = false;
      }
    }
    return success;
  }
  /**
   * Update the attribute index for a new entry.
   *
   * @param txn         The database transaction to be used for the insertions.
   * @param entryID     The entry ID.
   * @param entry       The contents of the new entry.
@@ -394,6 +456,46 @@
  /**
   * Update the attribute index for a deleted entry.
   *
   * @param buffer The index buffer to use to store the deleted keys
   * @param entryID     The entry ID
   * @param entry       The contents of the deleted 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.
   */
  public void removeEntry(IndexBuffer buffer, EntryID entryID,
                          Entry entry)
       throws DatabaseException, DirectoryException, JebException
  {
    if (equalityIndex != null)
    {
      equalityIndex.removeEntry(buffer, entryID, entry);
    }
    if (presenceIndex != null)
    {
      presenceIndex.removeEntry(buffer, entryID, entry);
    }
    if (substringIndex != null)
    {
      substringIndex.removeEntry(buffer, entryID, entry);
    }
    if (orderingIndex != null)
    {
      orderingIndex.removeEntry(buffer, entryID, entry);
    }
    if(approximateIndex != null)
    {
      approximateIndex.removeEntry(buffer, entryID, entry);
    }
  }
  /**
   * Update the attribute index for a deleted entry.
   *
   * @param txn         The database transaction to be used for the deletions
   * @param entryID     The entry ID
   * @param entry       The contents of the deleted entry.
@@ -476,6 +578,51 @@
  }
  /**
   * Update the index to reflect a sequence of modifications in a Modify
   * operation.
   *
   * @param buffer The index buffer used to buffer up the index changes.
   * @param entryID The ID of the entry that was modified.
   * @param oldEntry The entry before the modifications were applied.
   * @param newEntry The entry after the modifications were applied.
   * @param mods The sequence of modifications in the Modify operation.
   * @throws DatabaseException If an error occurs during an operation on a
   * JE database.
   */
  public void modifyEntry(IndexBuffer buffer,
                          EntryID entryID,
                          Entry oldEntry,
                          Entry newEntry,
                          List<Modification> mods)
       throws DatabaseException
  {
    if (equalityIndex != null)
    {
      equalityIndex.modifyEntry(buffer, entryID, oldEntry, newEntry, mods);
    }
    if (presenceIndex != null)
    {
      presenceIndex.modifyEntry(buffer, entryID, oldEntry, newEntry, mods);
    }
    if (substringIndex != null)
    {
      substringIndex.modifyEntry(buffer, entryID, oldEntry, newEntry, mods);
    }
    if (orderingIndex != null)
    {
      orderingIndex.modifyEntry(buffer, entryID, oldEntry, newEntry, mods);
    }
    if (approximateIndex != null)
    {
      approximateIndex.modifyEntry(buffer, entryID, oldEntry, newEntry, mods);
    }
  }
  /**
   * Makes a byte array representing a substring index key for
   * one substring of a value.
   *
@@ -1676,4 +1823,41 @@
  public Index getPresenceIndex() {
    return presenceIndex;
  }
  /**
   * Retrieves all the indexes used by this attribute index.
   *
   * @return A collection of all indexes in use by this attribute
   * index.
   */
  public Collection<Index> getAllIndexes() {
    LinkedHashSet<Index> indexes = new LinkedHashSet<Index>();
    if (equalityIndex != null)
    {
      indexes.add(equalityIndex);
    }
    if (presenceIndex != null)
    {
      indexes.add(presenceIndex);
    }
    if (substringIndex != null)
    {
      indexes.add(substringIndex);
    }
    if (orderingIndex != null)
    {
      indexes.add(orderingIndex);
    }
    if (approximateIndex != null)
    {
      indexes.add(approximateIndex);
    }
    return indexes;
  }
}
opendj-sdk/opends/src/server/org/opends/server/backends/jeb/BackendImpl.java
@@ -30,6 +30,7 @@
import java.io.IOException;
import java.io.File;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.io.FileInputStream;
import java.io.FilenameFilter;
@@ -308,7 +309,7 @@
    {
      EnvironmentConfig envConfig =
          ConfigurableEnvironment.parseConfigEntry(cfg);
      envConfig.setLockTimeout(0);
      rootContainer = initializeRootContainer(envConfig);
    }
@@ -941,9 +942,19 @@
      throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
                                   msg);
    }
    Lock containerLock = currentContainer.sharedLock;
    try
    {
      currentContainer.sharedLock.lock();
      containerLock.lock();
      if(currentContainer.getNumSubordinates(currentDN, true) >
         currentContainer.getSubtreeDeleteBatchSize())
      {
        containerLock.unlock();
        containerLock = currentContainer.exclusiveLock;
        containerLock.lock();
      }
      currentContainer.renameEntry(currentDN, entry, modifyDNOperation);
    }
@@ -966,7 +977,7 @@
    }
    finally
    {
      currentContainer.sharedLock.unlock();
      containerLock.unlock();
      writerEnd();
    }
  }
opendj-sdk/opends/src/server/org/opends/server/backends/jeb/BufferedIndex.java
File was deleted
opendj-sdk/opends/src/server/org/opends/server/backends/jeb/DN2ID.java
@@ -205,10 +205,11 @@
   * @param txn A JE database transaction to be used for the database read, or
   * null if none is required.
   * @param dn The DN for which the entry ID is desired.
   * @param lockMode The JE locking mode to be used for the read.
   * @return The entry ID, or null if the given DN is not in the DN database.
   * @throws DatabaseException If an error occurs in the JE database.
   */
  public EntryID get(Transaction txn, DN dn)
  public EntryID get(Transaction txn, DN dn, LockMode lockMode)
       throws DatabaseException
  {
    DatabaseEntry key = DNdata(dn);
opendj-sdk/opends/src/server/org/opends/server/backends/jeb/EntryContainer.java
@@ -29,10 +29,7 @@
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;
@@ -50,6 +47,7 @@
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;
@@ -202,8 +200,6 @@
  private int subtreeDeleteBatchSize;
  private int indexEntryLimit;
  private String databasePrefix;
  /**
   * This class is responsible for managing the configuraiton for attribute
@@ -314,7 +310,66 @@
    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;
    }
@@ -449,7 +504,6 @@
    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>();
@@ -498,12 +552,12 @@
      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();
@@ -753,7 +807,7 @@
  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 =
@@ -1002,7 +1056,7 @@
      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 =
@@ -1851,8 +1905,6 @@
    public Transaction beginOperationTransaction() throws DatabaseException
    {
      Transaction txn =  beginTransaction();
      // Multiple adds should never encounter a deadlock.
      txn.setLockTimeout(0);
      return txn;
    }
@@ -1878,7 +1930,7 @@
        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());
@@ -1894,7 +1946,7 @@
        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(
@@ -1961,7 +2013,7 @@
             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 =
@@ -2040,212 +2092,6 @@
  }
  /**
   * 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.
   */
@@ -2261,12 +2107,6 @@
     */
    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.
@@ -2281,9 +2121,19 @@
    /**
     * 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.
@@ -2294,7 +2144,6 @@
    {
      this.entryDN = entryDN;
      this.deleteOperation = deleteOperation;
      deletedDNList = new ArrayList<DN>();
    }
    /**
@@ -2322,7 +2171,7 @@
    public void resetBatchSize()
    {
      batchSizeExceeded=false;
      deletedDNList.clear();
      batchDeletedDN = 0;
    }
    /**
@@ -2331,7 +2180,7 @@
     */
    public int getDeletedEntryCount()
    {
      return countDeletedDN;
      return totalDeletedDN;
    }
    /**
@@ -2344,8 +2193,6 @@
    public Transaction beginOperationTransaction() throws DatabaseException
    {
      Transaction txn =  beginTransaction();
      // Multiple deletes should never encounter a deadlock.
      txn.setLockTimeout(0);
      return txn;
    }
@@ -2364,8 +2211,6 @@
      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)
@@ -2403,11 +2248,10 @@
      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;
@@ -2449,30 +2293,38 @@
          }
          // 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);
        }
      }
@@ -2486,41 +2338,178 @@
      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);
      }
    }
    /**
@@ -2529,15 +2518,7 @@
     */
    public void postCommitAction()
    {
      // Update the entry cache.
      EntryCache entryCache = DirectoryServer.getEntryCache();
      if (entryCache != null)
      {
        for (DN dn : deletedDNList)
        {
          entryCache.removeEntry(dn);
        }
      }
    }
  }
@@ -2570,7 +2551,7 @@
    EntryID id = null;
    try
    {
      id = dn2id.get(null, entryDN);
      id = dn2id.get(null, entryDN, LockMode.DEFAULT);
    }
    catch (DatabaseException e)
    {
@@ -2707,7 +2688,7 @@
                                                        JebException
    {
      // Read dn2id.
      entryID = dn2id.get(txn, entryDN);
      entryID = dn2id.get(txn, entryDN, LockMode.DEFAULT);
      if (entryID == null)
      {
        // The entryDN does not exist.
@@ -2719,7 +2700,7 @@
      }
      // Read id2entry.
      entry = id2entry.get(txn, entryID);
      entry = id2entry.get(txn, entryID, LockMode.DEFAULT);
      if (entry == null)
      {
@@ -2811,7 +2792,7 @@
                                                        JebException
    {
      // Read id2entry.
      entry = id2entry.get(txn, entryID);
      entry = id2entry.get(txn, entryID, LockMode.DEFAULT);
    }
    /**
@@ -2890,8 +2871,6 @@
    public Transaction beginOperationTransaction() throws DatabaseException
    {
      Transaction txn =  beginTransaction();
      // Multiple replace operations should never encounter a deadlock.
      txn.setLockTimeout(0);
      return txn;
    }
@@ -2908,7 +2887,7 @@
                                                        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.
@@ -2920,7 +2899,7 @@
      }
      // 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.
@@ -3044,16 +3023,10 @@
     */
    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.
@@ -3069,6 +3042,19 @@
      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;
      }
    }
    /**
@@ -3079,19 +3065,13 @@
     * @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());
@@ -3099,7 +3079,7 @@
                                     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.
@@ -3112,7 +3092,7 @@
            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());
@@ -3124,18 +3104,15 @@
        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 =
@@ -3153,18 +3130,26 @@
          // 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
@@ -3186,20 +3171,23 @@
      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.
@@ -3215,33 +3203,44 @@
          // 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
@@ -3249,8 +3248,7 @@
        cursor.close();
      }
      id2cBuffered.flush();
      id2sBuffered.flush();
      buffer.flush(txn);
    }
    /**
@@ -3262,116 +3260,7 @@
     */
    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();
    }
    /**
@@ -3379,14 +3268,17 @@
     * 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
    {
@@ -3396,33 +3288,83 @@
      // 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.
@@ -3438,6 +3380,7 @@
     * 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.
@@ -3447,13 +3390,57 @@
     * @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);
@@ -3463,212 +3450,84 @@
      {
        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)
@@ -3790,6 +3649,31 @@
  }
  /**
   * 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.
@@ -3814,6 +3698,31 @@
  }
  /**
   * 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.
   *
@@ -3871,6 +3780,63 @@
  }
  /**
   * 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.
@@ -3878,7 +3844,7 @@
   */
  public long getEntryCount() throws DatabaseException
  {
    EntryID entryID = dn2id.get(null, baseDN);
    EntryID entryID = dn2id.get(null, baseDN, LockMode.DEFAULT);
    if (entryID != null)
    {
      DatabaseEntry key =
@@ -4386,7 +4352,6 @@
    this.deadlockRetryLimit = config.getDeadlockRetryLimit();
    this.subtreeDeleteSizeLimit = config.getSubtreeDeleteSizeLimit();
    this.subtreeDeleteBatchSize = config.getSubtreeDeleteBatchSize();
    this.indexEntryLimit = config.getIndexEntryLimit();
    return new ConfigChangeResult(ResultCode.SUCCESS,
                                  adminActionRequired, messages);
  }
@@ -4671,7 +4636,6 @@
  /**
   * Get the exclusive lock.
   *
   */
  public void lock() {
    exclusiveLock.lock();
@@ -4684,4 +4648,14 @@
    exclusiveLock.unlock();
  }
  /**
   * Get the subtree delete batch size.
   *
   * @return The subtree delete batch size.
   */
  public int getSubtreeDeleteBatchSize()
  {
    return subtreeDeleteBatchSize;
  }
}
opendj-sdk/opends/src/server/org/opends/server/backends/jeb/EntryIDSet.java
@@ -498,33 +498,22 @@
  /**
   * Add all the IDs from a given set that are not already present.
   *
   * @param that The set of IDs to be added.
   * @param that The set of IDs to be added. It MUST be defined
   */
  public void addAll(EntryIDSet that)
  {
    if (!this.isDefined())
    if(!that.isDefined())
    {
      // Can't simply add the undefined size of this set to that set since
      // we don't know if there are any duplicates. In this case, we can't
      // maintain the undefined size anymore.
      if(undefinedSize != Long.MAX_VALUE && that.size() > 0)
      {
        undefinedSize = Long.MAX_VALUE;
      }
      return;
    }
    if (!that.isDefined())
    if (!this.isDefined())
    {
      if(that.size() == 0)
      // Assume there are no overlap between IDs in that set with this set
      if(undefinedSize != Long.MAX_VALUE && that.size() > 0)
      {
        undefinedSize = values.length;
        undefinedSize += that.size();
      }
      else
      {
        undefinedSize = Long.MAX_VALUE;
      }
      values = null;
      return;
    }
@@ -616,10 +605,15 @@
  /**
   * Delete all IDs in this set that are in a given set.
   *
   * @param that The set of IDs to be deleted.
   * @param that The set of IDs to be deleted. It MUST be defined.
   */
  public void deleteAll(EntryIDSet that)
  {
    if(!that.isDefined())
    {
      return;
    }
    if (!this.isDefined())
    {
      // Can't simply subtract the undefined size of this set to that set since
@@ -632,20 +626,6 @@
      return;
    }
    if (!that.isDefined())
    {
      if(that.size() == 0)
      {
        undefinedSize = values.length;
      }
      else
      {
        undefinedSize = Long.MAX_VALUE;
      }
      values = null;
      return;
    }
    long[] a = this.values;
    long[] b = that.values;
opendj-sdk/opends/src/server/org/opends/server/backends/jeb/EntryIDSetSorter.java
@@ -50,7 +50,7 @@
import static org.opends.messages.JebMessages.*;
import static org.opends.server.util.StaticUtils.*;
import com.sleepycat.je.LockMode;
/**
@@ -98,7 +98,7 @@
    {
      try
      {
        Entry e = id2Entry.get(null, id);
        Entry e = id2Entry.get(null, id, LockMode.DEFAULT);
        if ((! e.matchesBaseAndScope(baseDN, scope)) ||
            (! filter.matchesEntry(e)))
opendj-sdk/opends/src/server/org/opends/server/backends/jeb/ID2Entry.java
@@ -211,11 +211,12 @@
   *
   * @param txn The database transaction or null if none.
   * @param id The desired entry ID which forms the key.
   * @param lockMode The JE locking mode to be used for the read.
   * @return The requested entry, or null if there is no such record.
   * @throws JebException If an error occurs in the JE backend.
   * @throws DatabaseException If an error occurs in the JE database.
   */
  public Entry get(Transaction txn, EntryID id)
  public Entry get(Transaction txn, EntryID id, LockMode lockMode)
       throws JebException, DatabaseException
  {
    DatabaseEntry key = id.getDatabaseEntry();
opendj-sdk/opends/src/server/org/opends/server/backends/jeb/Index.java
@@ -185,6 +185,55 @@
  }
  /**
   * Add an add entry ID operation into a index buffer.
   *
   * @param buffer The index buffer to insert the ID into.
   * @param keyBytes         The index key bytes.
   * @param entryID     The entry ID.
   * @return True if the entry ID is inserted or ignored because the entry limit
   *         count is exceeded. False if it already exists in the entry ID set
   *         for the given key.
   */
  public boolean insertID(IndexBuffer buffer, byte[] keyBytes,
                          EntryID entryID)
  {
    TreeMap<byte[], IndexBuffer.BufferedIndexValues> bufferedOperations =
        buffer.getBufferedIndex(this);
    IndexBuffer.BufferedIndexValues values = null;
    if(bufferedOperations == null)
    {
      bufferedOperations = new TreeMap<byte[],
          IndexBuffer.BufferedIndexValues>(comparator);
      buffer.putBufferedIndex(this, bufferedOperations);
    }
    else
    {
      values = bufferedOperations.get(keyBytes);
    }
    if(values == null)
    {
      values = new IndexBuffer.BufferedIndexValues();
      bufferedOperations.put(keyBytes, values);
    }
    if(values.deletedIDs != null && values.deletedIDs.contains(entryID))
    {
      values.deletedIDs.remove(entryID);
      return true;
    }
    if(values.addedIDs == null)
    {
      values.addedIDs = new EntryIDSet(keyBytes, null);
    }
    values.addedIDs.add(entryID);
    return true;
  }
  /**
   * Insert an entry ID into the set of IDs indexed by a given key.
   *
   * @param txn A database transaction, or null if none is required.
@@ -378,6 +427,276 @@
  }
  /**
   * Update the set of entry IDs for a given key.
   *
   * @param txn A database transaction, or null if none is required.
   * @param key The database key.
   * @param deletedIDs The IDs to remove for the key.
   * @param addedIDs the IDs to add for the key.
   * @throws DatabaseException If a database error occurs.
   */
  void updateKey(Transaction txn, DatabaseEntry key,
                 EntryIDSet deletedIDs, EntryIDSet addedIDs)
      throws DatabaseException
  {
    OperationStatus status;
    DatabaseEntry data = new DatabaseEntry();
    // Handle cases where nothing is changed early to avoid
    // DB access.
    if(deletedIDs != null && deletedIDs.size() == 0 &&
        (addedIDs == null || addedIDs.size() == 0))
    {
      return;
    }
    if(addedIDs != null && addedIDs.size() == 0 &&
        (deletedIDs == null || deletedIDs.size() == 0))
    {
      return;
    }
    if(deletedIDs == null && addedIDs == null)
    {
      status = delete(txn, key);
      if(status != OperationStatus.SUCCESS)
      {
        if(debugEnabled())
        {
          StringBuilder builder = new StringBuilder();
          StaticUtils.byteArrayToHexPlusAscii(builder, key.getData(), 4);
          TRACER.debugError("The expected key does not exist in the " +
              "index %s.\nKey:%s", name, builder.toString());
        }
      }
      return;
    }
    if(maintainCount)
    {
      for(int i = 0; i < phantomWriteRetires; i++)
      {
        if(updateKeyWithRMW(txn, key, data, deletedIDs, addedIDs) ==
            OperationStatus.SUCCESS)
        {
          return;
        }
      }
    }
    else
    {
      status = read(txn, key, data, LockMode.READ_COMMITTED);
      if(status == OperationStatus.SUCCESS)
      {
        EntryIDSet entryIDList =
            new EntryIDSet(key.getData(), data.getData());
        if (entryIDList.isDefined())
        {
          for(int i = 0; i < phantomWriteRetires; i++)
          {
            if(updateKeyWithRMW(txn, key, data, deletedIDs, addedIDs) ==
                OperationStatus.SUCCESS)
            {
              return;
            }
          }
        }
      }
      else
      {
        if(rebuildRunning || trusted)
        {
          if(deletedIDs != null)
          {
            if(debugEnabled())
            {
              StringBuilder builder = new StringBuilder();
              StaticUtils.byteArrayToHexPlusAscii(builder, key.getData(), 4);
              TRACER.debugError("The expected key does not exist in the " +
                  "index %s.\nKey:%s", name, builder.toString());
            }
          }
          data.setData(addedIDs.toDatabase());
          status = insert(txn, key, data);
          if(status == OperationStatus.KEYEXIST)
          {
            for(int i = 1; i < phantomWriteRetires; i++)
            {
              if(updateKeyWithRMW(txn, key, data, deletedIDs, addedIDs) ==
                    OperationStatus.SUCCESS)
              {
                return;
              }
            }
          }
        }
      }
    }
  }
  private OperationStatus updateKeyWithRMW(Transaction txn,
                                           DatabaseEntry key,
                                           DatabaseEntry data,
                                           EntryIDSet deletedIDs,
                                           EntryIDSet addedIDs)
      throws DatabaseException
  {
    OperationStatus status;
    status = read(txn, key, data, LockMode.RMW);
    if(status == OperationStatus.SUCCESS)
    {
      EntryIDSet entryIDList =
          new EntryIDSet(key.getData(), data.getData());
      if(addedIDs != null)
      {
        if(entryIDList.isDefined() && indexEntryLimit > 0)
        {
          long idCountDelta = addedIDs.size();
          if(deletedIDs != null)
          {
            idCountDelta -= deletedIDs.size();
          }
          if(idCountDelta + entryIDList.size() >= indexEntryLimit)
          {
            if(maintainCount)
            {
              entryIDList = new EntryIDSet(entryIDList.size() + idCountDelta);
            }
            else
            {
              entryIDList = new EntryIDSet();
            }
            entryLimitExceededCount++;
            if(debugEnabled())
            {
              StringBuilder builder = new StringBuilder();
              StaticUtils.byteArrayToHexPlusAscii(builder, key.getData(), 4);
              TRACER.debugInfo("Index entry exceeded in index %s. " +
                  "Limit: %d. ID list size: %d.\nKey:",
                  name, indexEntryLimit, idCountDelta + addedIDs.size(),
                  builder);
            }
          }
          else
          {
            entryIDList.addAll(addedIDs);
            if(deletedIDs != null)
            {
              entryIDList.deleteAll(deletedIDs);
            }
          }
        }
        else
        {
          entryIDList.addAll(addedIDs);
          if(deletedIDs != null)
          {
            entryIDList.deleteAll(deletedIDs);
          }
        }
      }
      else if(deletedIDs != null)
      {
        entryIDList.deleteAll(deletedIDs);
      }
      byte[] after = entryIDList.toDatabase();
      if (after == null)
      {
        // No more IDs, so remove the key. If index is not
        // trusted then this will cause all subsequent reads
        // for this key to return undefined set.
        return delete(txn, key);
      }
      else
      {
        data.setData(after);
        return put(txn, key, data);
      }
    }
    else
    {
      if(rebuildRunning || trusted)
      {
        if(deletedIDs != null)
        {
          if(debugEnabled())
          {
            StringBuilder builder = new StringBuilder();
            StaticUtils.byteArrayToHexPlusAscii(builder, key.getData(), 4);
            TRACER.debugError("The expected key does not exist in the " +
                "index %s.\nKey:%s", name, builder.toString());
          }
        }
        data.setData(addedIDs.toDatabase());
        return insert(txn, key, data);
      }
      else
      {
        return OperationStatus.SUCCESS;
      }
    }
  }
  /**
   * Add an remove entry ID operation into a index buffer.
   *
   * @param buffer The index buffer to insert the ID into.
   * @param keyBytes    The index key bytes.
   * @param entryID     The entry ID.
   * @return True if the entry ID is inserted or ignored because the entry limit
   *         count is exceeded. False if it already exists in the entry ID set
   *         for the given key.
   */
  public boolean removeID(IndexBuffer buffer, byte[] keyBytes,
                          EntryID entryID)
  {
    TreeMap<byte[], IndexBuffer.BufferedIndexValues> bufferedOperations =
        buffer.getBufferedIndex(this);
    IndexBuffer.BufferedIndexValues values = null;
    if(bufferedOperations == null)
    {
      bufferedOperations = new TreeMap<byte[],
          IndexBuffer.BufferedIndexValues>(comparator);
      buffer.putBufferedIndex(this, bufferedOperations);
    }
    else
    {
      values = bufferedOperations.get(keyBytes);
    }
    if(values == null)
    {
      values = new IndexBuffer.BufferedIndexValues();
      bufferedOperations.put(keyBytes, values);
    }
    if(values.addedIDs != null && values.addedIDs.contains(entryID))
    {
      values.addedIDs.remove(entryID);
      return true;
    }
    if(values.deletedIDs == null)
    {
      values.deletedIDs = new EntryIDSet(keyBytes, null);
    }
    values.deletedIDs.add(entryID);
    return true;
  }
  /**
   * Remove an entry ID from the set of IDs indexed by a given key.
   *
   * @param txn A database transaction, or null if none is required.
@@ -516,6 +835,35 @@
  }
  /**
   * Buffered delete of a key from the JE database.
   * @param buffer The index buffer to use to store the deleted keys
   * @param keyBytes The index key bytes.
   */
  public void delete(IndexBuffer buffer, byte[] keyBytes)
  {
    TreeMap<byte[], IndexBuffer.BufferedIndexValues> bufferedOperations =
        buffer.getBufferedIndex(this);
    IndexBuffer.BufferedIndexValues values = null;
    if(bufferedOperations == null)
    {
      bufferedOperations = new TreeMap<byte[],
          IndexBuffer.BufferedIndexValues>(comparator);
      buffer.putBufferedIndex(this, bufferedOperations);
    }
    else
    {
      values = bufferedOperations.get(keyBytes);
    }
    if(values == null)
    {
      values = new IndexBuffer.BufferedIndexValues();
      bufferedOperations.put(keyBytes, values);
    }
  }
  /**
   * Check if an entry ID is in the set of IDs indexed by a given key.
   *
   * @param txn A database transaction, or null if none is required.
@@ -790,6 +1138,36 @@
  }
  /**
   * Update the index buffer for a deleted entry.
   *
   * @param buffer The index buffer to use to store the deleted keys
   * @param entryID     The entry ID.
   * @param entry       The entry to be indexed.
   * @return True if all the indexType keys for the entry are added. False if
   *         the entry ID already exists for some keys.
   * @throws DatabaseException If an error occurs in the JE database.
   * @throws DirectoryException If a Directory Server error occurs.
   */
  public boolean addEntry(IndexBuffer buffer, EntryID entryID, Entry entry)
       throws DatabaseException, DirectoryException
  {
    HashSet<byte[]> addKeys = new HashSet<byte[]>();
    boolean success = true;
    indexer.indexEntry(null, entry, addKeys);
    for (byte[] keyBytes : addKeys)
    {
      if(!insertID(buffer, keyBytes, entryID))
      {
        success = false;
      }
    }
    return success;
  }
  /**
   * Update the index for a new entry.
   *
   * @param txn A database transaction, or null if none is required.
@@ -821,6 +1199,27 @@
    return success;
  }
  /**
   * Update the index buffer for a deleted entry.
   *
   * @param buffer The index buffer to use to store the deleted keys
   * @param entryID     The entry ID
   * @param entry       The contents of the deleted entry.
   * @throws DatabaseException If an error occurs in the JE database.
   * @throws DirectoryException If a Directory Server error occurs.
   */
  public void removeEntry(IndexBuffer buffer, EntryID entryID, Entry entry)
       throws DatabaseException, DirectoryException
  {
    HashSet<byte[]> delKeys = new HashSet<byte[]>();
    indexer.indexEntry(null, entry, delKeys);
    for (byte[] keyBytes : delKeys)
    {
      removeID(buffer, keyBytes, entryID);
    }
  }
  /**
   * Update the index for a deleted entry.
@@ -885,6 +1284,40 @@
  }
  /**
   * Update the index to reflect a sequence of modifications in a Modify
   * operation.
   *
   * @param buffer The index buffer to use to store the deleted keys
   * @param entryID The ID of the entry that was modified.
   * @param oldEntry The entry before the modifications were applied.
   * @param newEntry The entry after the modifications were applied.
   * @param mods The sequence of modifications in the Modify operation.
   * @throws DatabaseException If an error occurs in the JE database.
   */
  public void modifyEntry(IndexBuffer buffer,
                          EntryID entryID,
                          Entry oldEntry,
                          Entry newEntry,
                          List<Modification> mods)
       throws DatabaseException
  {
    TreeSet<byte[]> addKeys = new TreeSet<byte[]>(indexer.getComparator());
    TreeSet<byte[]> delKeys = new TreeSet<byte[]>(indexer.getComparator());
    indexer.modifyEntry(null, oldEntry, newEntry, mods, addKeys, delKeys);
    for (byte[] keyBytes : delKeys)
    {
      removeID(buffer, keyBytes, entryID);
    }
    for (byte[] keyBytes : addKeys)
    {
      insertID(buffer, keyBytes, entryID);
    }
  }
  /**
   * Set the index entry limit.
   *
   * @param indexEntryLimit The index entry limit to set.
opendj-sdk/opends/src/server/org/opends/server/backends/jeb/IndexRebuildThread.java
@@ -569,7 +569,7 @@
            dn2uri.targetEntryReferrals(entry.getDN(), null);
            // Read the parent ID from dn2id.
            EntryID parentID = dn2id.get(txn, parentDN);
            EntryID parentID = dn2id.get(txn, parentDN, LockMode.DEFAULT);
            if (parentID != null)
            {
              // Insert into id2children for parent ID.
@@ -684,7 +684,7 @@
            dn2uri.targetEntryReferrals(entry.getDN(), null);
            // Read the parent ID from dn2id.
            EntryID parentID = dn2id.get(txn, parentDN);
            EntryID parentID = dn2id.get(txn, parentDN, LockMode.DEFAULT);
            if (parentID != null)
            {
              // Insert into id2subtree for parent ID.
@@ -700,7 +700,7 @@
                   dn = ec.getParentWithinBase(dn))
              {
                // Read the ID from dn2id.
                EntryID nodeID = dn2id.get(null, dn);
                EntryID nodeID = dn2id.get(null, dn, LockMode.DEFAULT);
                if (nodeID != null)
                {
                  // Insert into id2subtree for this node.
@@ -921,7 +921,7 @@
          skippedEntries++;
          Message message = ERR_JEB_REBUILD_INSERT_ENTRY_FAILED.get(
              index.getName(), stackTraceToSingleLineString(e));
              vlvIndex.getName(), stackTraceToSingleLineString(e));
          logError(message);
          if (debugEnabled())
opendj-sdk/opends/src/server/org/opends/server/backends/jeb/SortValuesSet.java
@@ -28,8 +28,9 @@
import org.opends.server.types.*;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.protocols.asn1.ASN1Element;
import java.util.HashMap;
import java.util.LinkedList;
import com.sleepycat.je.DatabaseException;
@@ -39,38 +40,28 @@
 */
public class SortValuesSet
{
  private static final int ENCODED_VALUE_SIZE = 16;
  private static final int ENCODED_VALUE_LENGTH_SIZE =
      encodedLengthSize(ENCODED_VALUE_SIZE);
  private static final int ENCODED_ATTRIBUTE_VALUE_SIZE =
      ENCODED_VALUE_LENGTH_SIZE + ENCODED_VALUE_SIZE;
  private ID2Entry id2entry;
  private long[] entryIDs;
  private int[] valuesBytesOffsets;
  private byte[] valuesBytes;
  private byte[] keyBytes;
  private HashMap<EntryID, AttributeValue[]> cachedAttributeValues;
  private VLVIndex vlvIndex;
  /**
   * Construct an empty sort values set with the given information.
   *
   * @param vlvIndex The VLV index using this set.
   * @param id2entry The ID2Entry database.
   */
  public SortValuesSet(VLVIndex vlvIndex, ID2Entry id2entry)
  public SortValuesSet(VLVIndex vlvIndex)
  {
    this.keyBytes = new byte[0];
    this.entryIDs = null;
    this.valuesBytes = null;
    this.id2entry = id2entry;
    this.valuesBytesOffsets = null;
    this.vlvIndex = vlvIndex;
    this.cachedAttributeValues = new HashMap<EntryID, AttributeValue[]>();
  }
  /**
@@ -79,16 +70,11 @@
   * @param keyBytes The database key used to locate this set.
   * @param dataBytes The bytes to decode and construct this set.
   * @param vlvIndex The VLV index using this set.
   * @param id2entry The ID2Entry database.
   */
  public SortValuesSet(byte[] keyBytes, byte[] dataBytes, VLVIndex vlvIndex,
                       ID2Entry id2entry)
  public SortValuesSet(byte[] keyBytes, byte[] dataBytes, VLVIndex vlvIndex)
  {
    this.keyBytes = keyBytes;
    this.id2entry = id2entry;
    this.vlvIndex = vlvIndex;
    this.cachedAttributeValues = new HashMap<EntryID, AttributeValue[]>();
    if(dataBytes == null)
    {
      entryIDs = new long[0];
@@ -96,22 +82,16 @@
    }
    entryIDs = getEncodedIDs(dataBytes, 0);
    valuesBytes = new byte[entryIDs.length * ENCODED_ATTRIBUTE_VALUE_SIZE *
        vlvIndex.sortOrder.getSortKeys().length];
    System.arraycopy(dataBytes, entryIDs.length * 8 + 4, valuesBytes, 0,
                     valuesBytes.length);
    int valuesBytesOffset = entryIDs.length * 8 + 4;
    int valuesBytesLength = dataBytes.length - valuesBytesOffset;
    valuesBytes = new byte[valuesBytesLength];
    System.arraycopy(dataBytes, valuesBytesOffset, valuesBytes, 0,
                     valuesBytesLength);
    this.valuesBytesOffsets = null;
  }
  private SortValuesSet(long[] entryIDs, byte[] keyBytes, byte[] valuesBytes,
                        VLVIndex vlvIndex, ID2Entry id2entry)
  {
    this.keyBytes = keyBytes;
    this.id2entry = id2entry;
    this.entryIDs = entryIDs;
    this.valuesBytes = valuesBytes;
    this.vlvIndex = vlvIndex;
    this.cachedAttributeValues = new HashMap<EntryID, AttributeValue[]>();
  }
  private SortValuesSet()
  {}
  /**
   * Add the given entryID and values from this VLV idnex.
@@ -137,10 +117,14 @@
      entryIDs = new long[1];
      entryIDs[0] = entryID;
      valuesBytes = attributeValuesToDatabase(values);
      if(valuesBytesOffsets != null)
      {
        valuesBytesOffsets = new int[1];
        valuesBytesOffsets[0] = 0;
      }
      return true;
    }
    if(vlvIndex.comparator.compareValuesInSet(this,
                                              entryIDs.length - 1, entryID,
    if(vlvIndex.comparator.compare(this, entryIDs.length - 1, entryID,
                                              values) < 0)
    {
      long[] updatedEntryIDs = new long[entryIDs.length + 1];
@@ -156,6 +140,17 @@
                       valuesBytes.length,
                       newValuesBytes.length);
      if(valuesBytesOffsets != null)
      {
        int[] updatedValuesBytesOffsets =
            new int[valuesBytesOffsets.length + 1];
        System.arraycopy(valuesBytesOffsets, 0, updatedValuesBytesOffsets,
            0, valuesBytesOffsets.length);
        updatedValuesBytesOffsets[valuesBytesOffsets.length] =
            updatedValuesBytes.length - newValuesBytes.length;
        valuesBytesOffsets = updatedValuesBytesOffsets;
      }
      entryIDs = updatedEntryIDs;
      valuesBytes = updatedValuesBytes;
      return true;
@@ -186,7 +181,7 @@
      updatedEntryIDs[pos] = entryID;
      byte[] newValuesBytes = attributeValuesToDatabase(values);
      int valuesPos = pos * newValuesBytes.length;
      int valuesPos = valuesBytesOffsets[pos];
      byte[] updatedValuesBytes = new byte[valuesBytes.length +
          newValuesBytes.length];
      System.arraycopy(valuesBytes, 0, updatedValuesBytes, 0, valuesPos);
@@ -196,6 +191,22 @@
      System.arraycopy(newValuesBytes, 0, updatedValuesBytes, valuesPos,
                       newValuesBytes.length);
      if(valuesBytesOffsets != null)
      {
        int[] updatedValuesBytesOffsets =
            new int[valuesBytesOffsets.length + 1];
        System.arraycopy(valuesBytesOffsets, 0, updatedValuesBytesOffsets,
            0, pos);
        // Update the rest of the offsets one by one - Expensive!
        for(int i = pos; i < valuesBytesOffsets.length; i++)
        {
          updatedValuesBytesOffsets[i+1] =
              valuesBytesOffsets[i] + newValuesBytes.length;
        }
        updatedValuesBytesOffsets[pos] = valuesBytesOffsets[pos];
        valuesBytesOffsets = updatedValuesBytesOffsets;
      }
      entryIDs = updatedEntryIDs;
      valuesBytes = updatedValuesBytes;
    }
@@ -222,6 +233,11 @@
      return false;
    }
    if(valuesBytesOffsets == null)
    {
      updateValuesBytesOffsets();
    }
    int pos = binarySearch(entryID, values);
    if(pos < 0)
    {
@@ -235,54 +251,93 @@
      System.arraycopy(entryIDs, 0, updatedEntryIDs, 0, pos);
      System.arraycopy(entryIDs, pos+1, updatedEntryIDs, pos,
                       entryIDs.length-pos-1);
      int valuesLength = ENCODED_ATTRIBUTE_VALUE_SIZE *
          vlvIndex.sortOrder.getSortKeys().length;
      int valuesPos = pos * valuesLength;
      int valuesLength;
      int valuesPos = valuesBytesOffsets[pos];
      if(pos < valuesBytesOffsets.length - 1)
      {
        valuesLength = valuesBytesOffsets[pos+1] - valuesPos;
      }
      else
      {
        valuesLength = valuesBytes.length - valuesPos;
      }
      byte[] updatedValuesBytes = new byte[valuesBytes.length - valuesLength];
      System.arraycopy(valuesBytes, 0, updatedValuesBytes, 0, valuesPos);
      System.arraycopy(valuesBytes, valuesPos + valuesLength,
                       updatedValuesBytes, valuesPos,
                       valuesBytes.length - valuesPos - valuesLength);
      int[] updatedValuesBytesOffsets = new int[valuesBytesOffsets.length - 1];
      System.arraycopy(valuesBytesOffsets, 0, updatedValuesBytesOffsets,
          0, pos);
      // Update the rest of the offsets one by one - Expensive!
      for(int i = pos + 1; i < valuesBytesOffsets.length; i++)
      {
        updatedValuesBytesOffsets[i-1] =
            valuesBytesOffsets[i] - valuesLength;
      }
      entryIDs = updatedEntryIDs;
      valuesBytes = updatedValuesBytes;
      valuesBytesOffsets = updatedValuesBytesOffsets;
      return true;
    }
  }
  /**
   * Split portions of this set into another set. The values of the new set is
   * from the front of this set.
   * from the end of this set.
   *
   * @param splitLength The size of the new set.
   * @return The split set.
   */
  public SortValuesSet split(int splitLength)
  {
    if(valuesBytesOffsets == null)
    {
      updateValuesBytesOffsets();
    }
    long[] splitEntryIDs = new long[splitLength];
    byte[] splitValuesBytes = new byte[splitLength *
        ENCODED_ATTRIBUTE_VALUE_SIZE * vlvIndex.sortOrder.getSortKeys().length];
    byte[] splitValuesBytes = new byte[valuesBytes.length -
        valuesBytesOffsets[valuesBytesOffsets.length - splitLength]];
    int[] splitValuesBytesOffsets = new int[splitLength];
    long[] updatedEntryIDs = new long[entryIDs.length - splitEntryIDs.length];
    System.arraycopy(entryIDs, 0, updatedEntryIDs, 0, updatedEntryIDs.length);
    System.arraycopy(entryIDs, updatedEntryIDs.length, splitEntryIDs, 0,
                     splitEntryIDs.length);
    byte[] updatedValuesBytes = new byte[valuesBytes.length -
        splitValuesBytes.length];
    byte[] updatedValuesBytes =
        new byte[valuesBytesOffsets[valuesBytesOffsets.length - splitLength]];
    System.arraycopy(valuesBytes, 0, updatedValuesBytes, 0,
                     updatedValuesBytes.length);
    System.arraycopy(valuesBytes, updatedValuesBytes.length, splitValuesBytes,
                     0, splitValuesBytes.length);
    SortValuesSet splitValuesSet = new SortValuesSet(splitEntryIDs,
                                                     keyBytes,
                                                     splitValuesBytes,
                                                     vlvIndex, id2entry);
    int[] updatedValuesBytesOffsets =
        new int[valuesBytesOffsets.length - splitValuesBytesOffsets.length];
    System.arraycopy(valuesBytesOffsets, 0, updatedValuesBytesOffsets,
        0, updatedValuesBytesOffsets.length);
    for(int i = updatedValuesBytesOffsets.length;
        i < valuesBytesOffsets.length; i++)
    {
      splitValuesBytesOffsets[i - updatedValuesBytesOffsets.length] =
          valuesBytesOffsets[i] -
              valuesBytesOffsets[updatedValuesBytesOffsets.length];
    }
    SortValuesSet splitValuesSet = new SortValuesSet();
    splitValuesSet.entryIDs = splitEntryIDs;
    splitValuesSet.keyBytes = this.keyBytes;
    splitValuesSet.valuesBytes = splitValuesBytes;
    splitValuesSet.valuesBytesOffsets = splitValuesBytesOffsets;
    splitValuesSet.vlvIndex = this.vlvIndex;
    entryIDs = updatedEntryIDs;
    valuesBytes = updatedValuesBytes;
    valuesBytesOffsets = updatedValuesBytesOffsets;
    keyBytes = null;
    return splitValuesSet;
@@ -369,7 +424,7 @@
    for(int j = entryIDs.length - 1; i <= j;)
    {
      int k = i + j >> 1;
      int l = vlvIndex.comparator.compareValuesInSet(this, k, entryID, values);
      int l = vlvIndex.comparator.compare(this, k, entryID, values);
      if(l < 0)
        i = k + 1;
      else
@@ -407,71 +462,38 @@
    return entryIDs;
  }
  private static int encodedLengthSize(int length)
  {
    if ((length & 0x000000FF) == length)
    {
      return 1;
    }
    else if ((length & 0x0000FFFF) == length)
    {
      return 2;
    }
    else if ((length & 0x00FFFFFF) == length)
    {
      return 3;
    }
    else
    {
      return 4;
    }
  }
  private byte[] attributeValuesToDatabase(AttributeValue[] values)
      throws DirectoryException
  {
    byte[] valuesBytes = new byte[values.length *
        (ENCODED_ATTRIBUTE_VALUE_SIZE)];
    for(int i = 0; i < values.length; i++)
    int totalValueBytes = 0;
    LinkedList<byte[]> valueBytes = new LinkedList<byte[]>();
    for (AttributeValue v : values)
    {
      AttributeValue value = values[i];
      int length;
      byte[] lengthBytes = new byte[ENCODED_VALUE_LENGTH_SIZE];
      if(value == null)
      byte[] vBytes;
      if(v == null)
      {
        length = 0;
        vBytes = new byte[0];
      }
      else
      {
        byte[] valueBytes = value.getNormalizedValueBytes();
        length = valueBytes.length;
        if(valueBytes.length > ENCODED_VALUE_SIZE)
        {
          System.arraycopy(valueBytes, 0, valuesBytes,
                           i * ENCODED_ATTRIBUTE_VALUE_SIZE +
                               ENCODED_VALUE_LENGTH_SIZE,
                           ENCODED_VALUE_SIZE);
        }
        else
        {
          System.arraycopy(valueBytes, 0, valuesBytes,
                           i * ENCODED_ATTRIBUTE_VALUE_SIZE +
                               ENCODED_VALUE_LENGTH_SIZE,
                           valueBytes.length);
        }
        vBytes = v.getNormalizedValueBytes();
      }
      for (int j = ENCODED_VALUE_LENGTH_SIZE - 1; j >= 0; j--)
      {
        lengthBytes[j] = (byte) (length & 0xFF);
        length >>>= 8;
      }
      System.arraycopy(lengthBytes, 0, valuesBytes,
                       i * (ENCODED_ATTRIBUTE_VALUE_SIZE),
                       lengthBytes.length);
      byte[] vLength = ASN1Element.encodeLength(vBytes.length);
      valueBytes.add(vLength);
      valueBytes.add(vBytes);
      totalValueBytes += vLength.length + vBytes.length;
    }
    return valuesBytes;
    byte[] attrBytes = new byte[totalValueBytes];
    int pos = 0;
    for (byte[] b : valueBytes)
    {
      System.arraycopy(b, 0, attrBytes, pos, b.length);
      pos += b.length;
    }
    return attrBytes;
  }
  /**
@@ -496,18 +518,22 @@
      return keyBytes;
    }
    SortKey[] sortKeys = vlvIndex.sortOrder.getSortKeys();
    int numValues = sortKeys.length;
    AttributeValue[] values =
        new AttributeValue[numValues];
    for (int i = (entryIDs.length - 1) * numValues, j = 0;
         i < entryIDs.length * numValues;
         i++, j++)
    if(valuesBytesOffsets == null)
    {
      values[j] = new AttributeValue(sortKeys[j].getAttributeType(),
                                     new ASN1OctetString(getValue(i)));
      updateValuesBytesOffsets();
    }
    keyBytes = vlvIndex.encodeKey(entryIDs[entryIDs.length - 1], values);
    int vBytesPos = valuesBytesOffsets[valuesBytesOffsets.length - 1];
    int vBytesLength = valuesBytes.length - vBytesPos;
    byte[] idBytes =
        JebFormat.entryIDToDatabase(entryIDs[entryIDs.length - 1]);
    keyBytes =
        new byte[vBytesLength + idBytes.length];
    System.arraycopy(valuesBytes, vBytesPos, keyBytes, 0, vBytesLength);
    System.arraycopy(idBytes, 0, keyBytes, vBytesLength, idBytes.length);
    return keyBytes;
  }
@@ -587,6 +613,34 @@
    return new SortValues(id, values, vlvIndex.sortOrder);
  }
  private void updateValuesBytesOffsets()
  {
    valuesBytesOffsets = new int[entryIDs.length];
    int vBytesPos = 0;
    int numAttributes = vlvIndex.sortOrder.getSortKeys().length;
    for(int pos = 0; pos < entryIDs.length; pos++)
    {
      valuesBytesOffsets[pos] = vBytesPos;
      for(int i = 0; i < numAttributes; i++)
      {
        int valueLength = valuesBytes[vBytesPos] & 0x7F;
        if (valueLength != valuesBytes[vBytesPos++])
        {
          int valueLengthBytes = valueLength;
          valueLength = 0;
          for (int j=0; j < valueLengthBytes; j++, vBytesPos++)
          {
            valueLength = (valueLength << 8) | (valuesBytes[vBytesPos] & 0xFF);
          }
        }
        vBytesPos += valueLength;
      }
    }
  }
  /**
   * Retrieve an attribute value from this values set. The index is the
   * absolute index. (ie. for a sort on 3 attributes per entry, an vlvIndex of 6
@@ -601,52 +655,47 @@
  public byte[] getValue(int index)
      throws JebException, DatabaseException, DirectoryException
  {
    // If values bytes is null, we have to get the value by getting the
    // entry by ID and getting the value.
    if(valuesBytes != null)
    if(valuesBytesOffsets == null)
    {
      int pos = index * ENCODED_ATTRIBUTE_VALUE_SIZE;
      int length = 0;
      byte[] valueBytes;
      for (int k = 0; k < ENCODED_VALUE_LENGTH_SIZE; k++, pos++)
      updateValuesBytesOffsets();
    }
    int numAttributes = vlvIndex.sortOrder.getSortKeys().length;
    int vIndex = index / numAttributes;
    int vOffset = index % numAttributes;
    int vBytesPos = valuesBytesOffsets[vIndex];
    // Find the desired value in the sort order set.
    for(int i = 0; i <= vOffset; i++)
    {
      int valueLength = valuesBytes[vBytesPos] & 0x7F;
      if (valueLength != valuesBytes[vBytesPos++])
      {
        length <<= 8;
        length |= (valuesBytes[pos] & 0xFF);
        int valueLengthBytes = valueLength;
        valueLength = 0;
        for (int j=0; j < valueLengthBytes; j++, vBytesPos++)
        {
          valueLength = (valueLength << 8) | (valuesBytes[vBytesPos] & 0xFF);
        }
      }
      if(length == 0)
      if(i == vOffset)
      {
        return null;
        if(valueLength == 0)
        {
          return null;
        }
        else
        {
          byte[] valueBytes = new byte[valueLength];
          System.arraycopy(valuesBytes, vBytesPos, valueBytes, 0, valueLength);
          return valueBytes;
        }
      }
      // If the value has exceeded the max value size, we have to get the
      // value by getting the entry by ID.
      else if(length <= ENCODED_VALUE_SIZE && length > 0)
      else
      {
        valueBytes = new byte[length];
        System.arraycopy(valuesBytes, pos, valueBytes, 0, length);
        return valueBytes;
        vBytesPos += valueLength;
      }
    }
    if(id2entry == null)
    {
      return new byte[0];
    }
    // Get the entry from id2entry and assign the values from the entry.
    // Once the values are assigned from the retrieved entry, it will
    // not be retrieve again from future compares.
    EntryID id = new EntryID(entryIDs[index /
        vlvIndex.sortOrder.getSortKeys().length]);
    AttributeValue[] values = cachedAttributeValues.get(id);
    if(values == null)
    {
      values = vlvIndex.getSortValues(id2entry.get(null, id));
      cachedAttributeValues.put(id, values);
    }
    int offset = index % values.length;
    return values[offset].getNormalizedValueBytes();
    return new byte[0];
  }
}
opendj-sdk/opends/src/server/org/opends/server/backends/jeb/VLVIndex.java
@@ -52,15 +52,13 @@
import org.opends.server.api.OrderingMatchingRule;
import org.opends.server.config.ConfigException;
import org.opends.server.protocols.asn1.ASN1Element;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.protocols.ldap.LDAPResultCode;
import org.opends.server.controls.VLVRequestControl;
import org.opends.server.controls.VLVResponseControl;
import org.opends.server.controls.ServerSideSortRequestControl;
import java.util.List;
import java.util.LinkedList;
import java.util.Iterator;
import java.util.ArrayList;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
/**
@@ -202,8 +200,7 @@
      if(attrType == null)
      {
        Message msg =
            ERR_JEB_CONFIG_VLV_INDEX_UNDEFINED_ATTR.get(
                    String.valueOf(sortKeys[i]), name);
            ERR_JEB_CONFIG_VLV_INDEX_UNDEFINED_ATTR.get(sortAttrs[i], name);
        throw new ConfigException(msg);
      }
      sortKeys[i] = new SortKey(attrType, ascending[i]);
@@ -323,6 +320,53 @@
  }
  /**
   * Update the vlvIndex for a new entry.
   *
   * @param buffer      The index buffer to buffer the changes.
   * @param entryID     The entry ID.
   * @param entry       The entry to be indexed.
   * @return True if the entry ID for the entry are added. False if
   *         the entry ID already exists.
   * @throws DirectoryException If a Directory Server
   * error occurs.
   */
  public boolean addEntry(IndexBuffer buffer, EntryID entryID, Entry entry)
      throws DirectoryException
  {
    DN entryDN = entry.getDN();
    if(entryDN.matchesBaseAndScope(baseDN, scope) && filter.matchesEntry(entry))
    {
      SortValues sortValues = new SortValues(entryID, entry, sortOrder);
      IndexBuffer.BufferedVLVValues bufferedValues =
          buffer.getVLVIndex(this);
      if(bufferedValues == null)
      {
        bufferedValues = new IndexBuffer.BufferedVLVValues();
        buffer.putBufferedVLVIndex(this, bufferedValues);
      }
      if(bufferedValues.deletedValues != null &&
          bufferedValues.deletedValues.contains(sortValues))
      {
        bufferedValues.deletedValues.remove(sortValues);
        return true;
      }
      TreeSet<SortValues> bufferedAddedValues = bufferedValues.addedValues;
      if(bufferedAddedValues == null)
      {
        bufferedAddedValues = new TreeSet<SortValues>();
        bufferedValues.addedValues = bufferedAddedValues;
      }
      bufferedAddedValues.add(sortValues);
      return true;
    }
    return false;
  }
  /**
   * Update the vlvIndex for a deleted entry.
   *
   * @param txn         The database transaction to be used for the deletions
@@ -346,6 +390,52 @@
  }
  /**
   * Update the vlvIndex for a deleted entry.
   *
   * @param buffer      The database transaction to be used for the deletions
   * @param entryID     The entry ID
   * @param entry       The contents of the deleted entry.
   * @return True if the entry was successfully removed from this VLV index
   * or False otherwise.
   * @throws DirectoryException If a Directory Server error occurs.
   */
  public boolean removeEntry(IndexBuffer buffer, EntryID entryID, Entry entry)
      throws DirectoryException
  {
    DN entryDN = entry.getDN();
    if(entryDN.matchesBaseAndScope(baseDN, scope) && filter.matchesEntry(entry))
    {
      SortValues sortValues = new SortValues(entryID, entry, sortOrder);
      IndexBuffer.BufferedVLVValues bufferedValues =
          buffer.getVLVIndex(this);
      if(bufferedValues == null)
      {
        bufferedValues = new IndexBuffer.BufferedVLVValues();
        buffer.putBufferedVLVIndex(this, bufferedValues);
      }
      if(bufferedValues.addedValues != null &&
          bufferedValues.addedValues.contains(sortValues))
      {
        bufferedValues.addedValues.remove(sortValues);
        return true;
      }
      TreeSet<SortValues> bufferedDeletedValues = bufferedValues.deletedValues;
      if(bufferedDeletedValues == null)
      {
        bufferedDeletedValues = new TreeSet<SortValues>();
        bufferedValues.deletedValues = bufferedDeletedValues;
      }
      bufferedDeletedValues.add(sortValues);
      return true;
    }
    return false;
  }
  /**
   * Update the vlvIndex to reflect a sequence of modifications in a Modify
   * operation.
   *
@@ -440,6 +530,100 @@
  }
  /**
   * Update the vlvIndex to reflect a sequence of modifications in a Modify
   * operation.
   *
   * @param buffer The database transaction to be used for the deletions
   * @param entryID The ID of the entry that was modified.
   * @param oldEntry The entry before the modifications were applied.
   * @param newEntry The entry after the modifications were applied.
   * @param mods The sequence of modifications in the Modify operation.
   * @return True if the modification was successfully processed or False
   * otherwise.
   * @throws JebException If an error occurs during an operation on a
   * JE database.
   * @throws DatabaseException If an error occurs during an operation on a
   * JE database.
   * @throws DirectoryException If a Directory Server error occurs.
   */
  public boolean modifyEntry(IndexBuffer buffer,
                          EntryID entryID,
                          Entry oldEntry,
                          Entry newEntry,
                          List<Modification> mods)
       throws DatabaseException, DirectoryException, JebException
  {
    DN oldEntryDN = oldEntry.getDN();
    DN newEntryDN = newEntry.getDN();
    if(oldEntryDN.matchesBaseAndScope(baseDN, scope) &&
        filter.matchesEntry(oldEntry))
    {
      if(newEntryDN.matchesBaseAndScope(baseDN, scope) &&
          filter.matchesEntry(newEntry))
      {
        // The entry should still be indexed. See if any sorted attributes are
        // changed.
        boolean sortAttributeModified = false;
        SortKey[] sortKeys = sortOrder.getSortKeys();
        for(SortKey sortKey : sortKeys)
        {
          AttributeType attributeType = sortKey.getAttributeType();
          Iterable<AttributeType> subTypes =
              DirectoryServer.getSchema().getSubTypes(attributeType);
          for(Modification mod : mods)
          {
            AttributeType modAttrType = mod.getAttribute().getAttributeType();
            if(modAttrType.equals(attributeType))
            {
              sortAttributeModified = true;
              break;
            }
            for(AttributeType subType : subTypes)
            {
              if(modAttrType.equals(subType))
              {
                sortAttributeModified = true;
                break;
              }
            }
          }
          if(sortAttributeModified)
          {
            break;
          }
        }
        if(sortAttributeModified)
        {
          boolean success;
          // Sorted attributes have changed. Reindex the entry;
          success = removeEntry(buffer, entryID, oldEntry);
          success &= addEntry(buffer, entryID, newEntry);
          return success;
        }
      }
      else
      {
        // The modifications caused the new entry to be unindexed. Remove from
        // vlvIndex.
        return removeEntry(buffer, entryID, oldEntry);
      }
    }
    else
    {
      if(newEntryDN.matchesBaseAndScope(baseDN, scope) &&
          filter.matchesEntry(newEntry))
      {
        // The modifications caused the new entry to be indexed. Add to
        // vlvIndex.
        return addEntry(buffer, entryID, newEntry);
      }
    }
    // The modifications does not affect this vlvIndex
    return true;
  }
  /**
   * Put a sort values set in this VLV index.
   *
   * @param txn The transaction to use when retriving the set or NULL if it is
@@ -503,7 +687,7 @@
          TRACER.debugVerbose("No sort values set exist in VLV vlvIndex %s. " +
              "Creating unbound set.", config.getName());
        }
        sortValuesSet = new SortValuesSet(this, id2entry);
        sortValuesSet = new SortValuesSet(this);
      }
      else
      {
@@ -520,7 +704,7 @@
                              foundKeyHex);
        }
        sortValuesSet = new SortValuesSet(key.getData(), data.getData(),
                                          this, id2entry);
                                          this);
      }
    }
    finally
@@ -590,7 +774,7 @@
        TRACER.debugVerbose("No sort values set exist in VLV vlvIndex %s. " +
            "Creating unbound set.", config.getName());
      }
      sortValuesSet = new SortValuesSet(this, id2entry);
      sortValuesSet = new SortValuesSet(this);
      key.setData(new byte[0]);
    }
    else
@@ -608,7 +792,7 @@
                            foundKeyHex);
      }
      sortValuesSet = new SortValuesSet(key.getData(), data.getData(),
                                        this, id2entry);
                                        this);
    }
@@ -643,6 +827,7 @@
      byte[] after = sortValuesSet.toDatabase();
      data.setData(after);
      put(txn, key, data);
      // TODO: What about phantoms?
    }
    if(success)
@@ -690,7 +875,7 @@
                            foundKeyHex);
      }
      sortValuesSet = new SortValuesSet(key.getData(), data.getData(),
                                        this, id2entry);
                                        this);
      boolean success = sortValuesSet.remove(entryID, values);
      byte[] after = sortValuesSet.toDatabase();
      data.setData(after);
@@ -710,6 +895,223 @@
  }
  /**
   * Update the vlvIndex with the specified values to add and delete.
   *
   * @param txn A database transaction, or null if none is required.
   * @param addedValues The values to add to the VLV index.
   * @param deletedValues The values to delete from the VLV index.
   * @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.
   */
  public void updateIndex(Transaction txn,
                          TreeSet<SortValues> addedValues,
                          TreeSet<SortValues> deletedValues)
      throws DirectoryException, DatabaseException, JebException
  {
    // Handle cases where nothing is changed early to avoid
    // DB access.
    if((addedValues == null || addedValues.size() == 0) &&
        (deletedValues == null || deletedValues.size() == 0))
    {
      return;
    }
    DatabaseEntry key = new DatabaseEntry();
    OperationStatus status;
    LockMode lockMode = LockMode.RMW;
    DatabaseEntry data = new DatabaseEntry();
    SortValuesSet sortValuesSet;
    Iterator<SortValues> aValues = null;
    Iterator<SortValues> dValues = null;
    SortValues av = null;
    SortValues dv = null;
    if(addedValues != null)
    {
      aValues = addedValues.iterator();
      av = aValues.next();
    }
    if(deletedValues != null)
    {
      dValues = deletedValues.iterator();
      dv = dValues.next();
    }
    while(true)
    {
      if(av != null)
      {
        if(dv != null)
        {
          // Start from the smallest values from either set.
          if(av.compareTo(dv) < 0)
          {
            key.setData(encodeKey(av.getEntryID(), av.getValues()));
          }
          else
          {
            key.setData(encodeKey(dv.getEntryID(), dv.getValues()));
          }
        }
        else
        {
          key.setData(encodeKey(av.getEntryID(), av.getValues()));
        }
      }
      else if(dv != null)
      {
        key.setData(encodeKey(dv.getEntryID(), dv.getValues()));
      }
      else
      {
        break;
      }
      Cursor cursor = openCursor(txn, CursorConfig.READ_COMMITTED);
      try
      {
        status = cursor.getSearchKeyRange(key, data,lockMode);
      }
      finally
      {
        cursor.close();
      }
      if(status != OperationStatus.SUCCESS)
      {
        // There are no records in the database
        if(debugEnabled())
        {
          TRACER.debugVerbose("No sort values set exist in VLV vlvIndex %s. " +
              "Creating unbound set.", config.getName());
        }
        sortValuesSet = new SortValuesSet(this);
        key.setData(new byte[0]);
      }
      else
      {
        if(debugEnabled())
        {
          StringBuilder searchKeyHex = new StringBuilder();
          StaticUtils.byteArrayToHexPlusAscii(searchKeyHex, key.getData(), 4);
          StringBuilder foundKeyHex = new StringBuilder();
          StaticUtils.byteArrayToHexPlusAscii(foundKeyHex, key.getData(), 4);
          TRACER.debugVerbose("Retrieved a sort values set in VLV vlvIndex " +
              "%s\nSearch Key:%s\nFound Key:%s\n",
              config.getName(),
              searchKeyHex,
              foundKeyHex);
        }
        sortValuesSet = new SortValuesSet(key.getData(), data.getData(),
            this);
      }
      int oldSize = sortValuesSet.size();
      if(key.getData().length == 0)
      {
        // This is the last unbounded set.
        while(av != null)
        {
          sortValuesSet.add(av.getEntryID(), av.getValues());
          aValues.remove();
          if(aValues.hasNext())
          {
            av = aValues.next();
          }
          else
          {
            av = null;
          }
        }
        while(dv != null)
        {
          sortValuesSet.remove(dv.getEntryID(), dv.getValues());
          dValues.remove();
          if(dValues.hasNext())
          {
            dv = dValues.next();
          }
          else
          {
            dv = null;
          }
        }
      }
      else
      {
        SortValues maxValues = decodeKey(sortValuesSet.getKeyBytes());
        while(av != null && av.compareTo(maxValues) <= 0)
        {
          sortValuesSet.add(av.getEntryID(), av.getValues());
          aValues.remove();
          if(aValues.hasNext())
          {
            av = aValues.next();
          }
          else
          {
            av = null;
          }
        }
        while(dv != null && dv.compareTo(maxValues) <= 0)
        {
          sortValuesSet.remove(dv.getEntryID(), dv.getValues());
          dValues.remove();
          if(dValues.hasNext())
          {
            dv = dValues.next();
          }
          else
          {
            dv = null;
          }
        }
      }
      int newSize = sortValuesSet.size();
      if(newSize >= sortedSetCapacity)
      {
        SortValuesSet splitSortValuesSet = sortValuesSet.split(newSize / 2);
        byte[] splitAfter = splitSortValuesSet.toDatabase();
        key.setData(splitSortValuesSet.getKeyBytes());
        data.setData(splitAfter);
        put(txn, key, data);
        byte[] after = sortValuesSet.toDatabase();
        key.setData(sortValuesSet.getKeyBytes());
        data.setData(after);
        put(txn, key, data);
        if(debugEnabled())
        {
          TRACER.debugInfo("SortValuesSet with key %s has reached" +
              " the entry size of %d. Spliting into two sets with " +
              " keys %s and %s.", splitSortValuesSet.getKeySortValues(),
              newSize, sortValuesSet.getKeySortValues(),
              splitSortValuesSet.getKeySortValues());
        }
      }
      else if(newSize == 0)
      {
        delete(txn, key);
      }
      else
      {
        byte[] after = sortValuesSet.toDatabase();
        data.setData(after);
        put(txn, key, data);
      }
      count.getAndAdd(newSize - oldSize);
    }
  }
  /**
   * Evaluate a search with sort control using this VLV index.
   *
   * @param txn The transaction to used when reading the index or NULL if it is
@@ -924,8 +1326,7 @@
                                  foundKeyHex);
            }
            SortValuesSet sortValuesSet =
                new SortValuesSet(key.getData(), data.getData(), this,
                                  id2entry);
                new SortValuesSet(key.getData(), data.getData(), this);
            AttributeValue[] assertionValue = new AttributeValue[1];
            assertionValue[0] =
                new AttributeValue(
@@ -1213,6 +1614,66 @@
  }
  /**
   * Decode a VLV database key.
   *
   * @param  keyBytes The byte array to decode.
   * @return The sort values represented by the key bytes.
   * @throws DirectoryException If a Directory Server error occurs.
   */
  SortValues decodeKey(byte[] keyBytes)
      throws DirectoryException
  {
    if(keyBytes == null || keyBytes.length == 0)
    {
      return null;
    }
    AttributeValue[] attributeValues =
        new AttributeValue[sortOrder.getSortKeys().length];
    int vBytesPos = 0;
    for(int i = 0; i < attributeValues.length; i++)
    {
      int valueLength = keyBytes[vBytesPos] & 0x7F;
      if (valueLength != keyBytes[vBytesPos++])
      {
        int valueLengthBytes = valueLength;
        valueLength = 0;
        for (int j=0; j < valueLengthBytes; j++, vBytesPos++)
        {
          valueLength = (valueLength << 8) | (keyBytes[vBytesPos] & 0xFF);
        }
      }
      if(valueLength == 0)
      {
        attributeValues[i] = null;
      }
      else
      {
        byte[] valueBytes = new byte[valueLength];
        System.arraycopy(keyBytes, vBytesPos, valueBytes, 0, valueLength);
        attributeValues[i] =
            new AttributeValue(sortOrder.getSortKeys()[i].getAttributeType(),
                new ASN1OctetString(valueBytes));
      }
      vBytesPos += valueLength;
    }
    // FIXME: Should pos+offset method for decoding IDs be added to
    // JebFormat?
    long v = 0;
    for (int i = vBytesPos; i < keyBytes.length; i++)
    {
      v <<= 8;
      v |= (keyBytes[i] & 0xFF);
    }
    return new SortValues(new EntryID(v), attributeValues, sortOrder);
  }
  /**
   * Get the sorted set capacity configured for this VLV index.
   *
   * @return The sorted set capacity.
@@ -1298,7 +1759,7 @@
      if(attrType == null)
      {
        Message msg = ERR_JEB_CONFIG_VLV_INDEX_UNDEFINED_ATTR.get(
                String.valueOf(sortKeys[i]), name);
                sortAttrs[i], name);
        unacceptableReasons.add(msg);
        return false;
      }
opendj-sdk/opends/src/server/org/opends/server/backends/jeb/VLVKeyComparator.java
@@ -203,7 +203,7 @@
    // If we've gotten here, then we can't tell a difference between the sets
    // of available values, so sort based on entry ID if its in the key.
    if(b1Pos + 8 < b1.length && b2Pos + 8 < b2.length)
    if(b1Pos + 8 <= b1.length && b2Pos + 8 <= b2.length)
    {
      long b1ID = 0;
      for (int i = b1Pos; i < b1Pos + 8; i++)
@@ -273,7 +273,7 @@
   *                              not acceptable for use with the
   *                              associated equality matching rule).
   */
  public int compareValuesInSet(SortValuesSet set, int index,
  public int compare(SortValuesSet set, int index,
                                long entryID, AttributeValue[] values)
      throws JebException, DatabaseException, DirectoryException
  {
opendj-sdk/opends/src/server/org/opends/server/backends/jeb/VerifyJob.java
@@ -615,7 +615,7 @@
        Entry entry;
        try
        {
          entry = id2entry.get(null, entryID);
          entry = id2entry.get(null, entryID, LockMode.DEFAULT);
        }
        catch (Exception e)
        {
@@ -725,7 +725,7 @@
          Entry entry;
          try
          {
            entry = id2entry.get(null, entryID);
            entry = id2entry.get(null, entryID, LockMode.DEFAULT);
          }
          catch (Exception e)
          {
@@ -753,7 +753,7 @@
            Entry childEntry;
            try
            {
              childEntry = id2entry.get(null, id);
              childEntry = id2entry.get(null, id, LockMode.DEFAULT);
            }
            catch (Exception e)
            {
@@ -866,7 +866,7 @@
          Entry entry;
          try
          {
            entry = id2entry.get(null, entryID);
            entry = id2entry.get(null, entryID, LockMode.DEFAULT);
          }
          catch (Exception e)
          {
@@ -894,7 +894,7 @@
            Entry subordEntry;
            try
            {
              subordEntry = id2entry.get(null, id);
              subordEntry = id2entry.get(null, id, LockMode.DEFAULT);
            }
            catch (Exception e)
            {
@@ -1022,8 +1022,7 @@
      while(status == OperationStatus.SUCCESS)
      {
        SortValuesSet sortValuesSet =
            new SortValuesSet(key.getData(), data.getData(), vlvIndex,
                              id2entry);
            new SortValuesSet(key.getData(), data.getData(), vlvIndex);
        for(int i = 0; i < sortValuesSet.getEntryIDs().length; i++)
        {
          keyCount++;
@@ -1065,7 +1064,7 @@
            EntryID id = new EntryID(values.getEntryID());
            try
            {
              entry = id2entry.get(null, id);
              entry = id2entry.get(null, id, LockMode.DEFAULT);
            }
            catch (Exception e)
            {
@@ -1279,7 +1278,7 @@
            Entry entry;
            try
            {
              entry = id2entry.get(null, id);
              entry = id2entry.get(null, id, LockMode.DEFAULT);
            }
            catch (Exception e)
            {
@@ -1400,7 +1399,7 @@
    // Check the ID is in dn2id with the correct DN.
    try
    {
      EntryID id = dn2id.get(null, dn);
      EntryID id = dn2id.get(null, dn, LockMode.DEFAULT);
      if (id == null)
      {
        if (debugEnabled())
@@ -1441,7 +1440,7 @@
    {
      try
      {
        EntryID id = dn2id.get(null, parentDN);
        EntryID id = dn2id.get(null, parentDN, LockMode.DEFAULT);
        if (id == null)
        {
          if (debugEnabled())
@@ -1483,7 +1482,7 @@
      EntryID parentID = null;
      try
      {
        parentID = dn2id.get(null, parentDN);
        parentID = dn2id.get(null, parentDN, LockMode.DEFAULT);
        if (parentID == null)
        {
          if (debugEnabled())
@@ -1555,7 +1554,7 @@
      EntryID id = null;
      try
      {
        id = dn2id.get(null, dn);
        id = dn2id.get(null, dn, LockMode.DEFAULT);
        if (id == null)
        {
          if (debugEnabled())
opendj-sdk/opends/src/server/org/opends/server/backends/jeb/importLDIF/DNContext.java
@@ -35,6 +35,7 @@
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Transaction;
import com.sleepycat.je.LockMode;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
@@ -468,7 +469,7 @@
          return null;
        }
      }
      parentID = dn2id.get(txn, parentDN);
      parentID = dn2id.get(txn, parentDN, LockMode.DEFAULT);
      //If the parent is in dn2id, add it to the cache.
      if (parentID != null) {
        synchronized(synchObject) {
opendj-sdk/opends/src/server/org/opends/server/backends/jeb/importLDIF/Importer.java
@@ -829,7 +829,9 @@
            DN dn = DN.decode(new ASN1OctetString(key.getData()));
            if(!context.getIncludeBranches().contains(dn)) {
              EntryID id = new EntryID(data);
              Entry entry = srcEntryContainer.getID2Entry().get(null, id);
              Entry entry =
                  srcEntryContainer.getID2Entry().get(null,
                      id, LockMode.DEFAULT);
              processEntry(context, entry);
              migratedCount++;
              status = cursor.getNext(key, data, lockMode);
@@ -909,7 +911,8 @@
                  throw new JebException(message);
                }
                EntryID id = new EntryID(data);
                Entry entry = srcEntryContainer.getID2Entry().get(null, id);
                Entry entry = srcEntryContainer.getID2Entry().get(null,
                    id, LockMode.DEFAULT);
                processEntry(importContext, entry);
                migratedCount++;
                status = cursor.getNext(key, data, lockMode);
opendj-sdk/opends/src/server/org/opends/server/backends/jeb/importLDIF/WorkThread.java
@@ -39,6 +39,7 @@
import java.util.*;
import com.sleepycat.je.DatabaseException;
import com.sleepycat.je.Transaction;
import com.sleepycat.je.LockMode;
/**
 * A thread to process import entries from a queue.  Multiple instances of
@@ -410,9 +411,9 @@
  private EntryID getAncestorID(DN2ID dn2id, DN dn, Transaction txn)
          throws DatabaseException {
    int i=0;
    EntryID nodeID = dn2id.get(txn, dn);
    EntryID nodeID = dn2id.get(txn, dn, LockMode.DEFAULT);
    if(nodeID == null) {
      while((nodeID = dn2id.get(txn, dn)) == null) {
      while((nodeID = dn2id.get(txn, dn, LockMode.DEFAULT)) == null) {
        try {
          Thread.sleep(50);
          if(i == 3) {
@@ -444,12 +445,12 @@
    DN2ID dn2id = context.getEntryContainer().getDN2ID();
    LDIFImportConfig ldifImportConfig = context.getLDIFImportConfig();
    DN entryDN = entry.getDN();
    EntryID entryID = dn2id.get(txn, entryDN);
    EntryID entryID = dn2id.get(txn, entryDN, LockMode.DEFAULT);
    if (entryID != null) {
      if (ldifImportConfig.appendToExistingData() &&
              ldifImportConfig.replaceExistingEntries()) {
        ID2Entry id2entry = context.getEntryContainer().getID2Entry();
        Entry existingEntry = id2entry.get(txn, entryID);
        Entry existingEntry = id2entry.get(txn, entryID, LockMode.DEFAULT);
        element.setExistingEntry(existingEntry);
      } else {
        Message msg = WARN_JEB_IMPORT_ENTRY_EXISTS.get();
opendj-sdk/opends/src/server/org/opends/server/core/PluginConfigManager.java
@@ -5066,7 +5066,7 @@
   *                            subordinate entry is associated.
   * @param  oldEntry           The subordinate entry prior to the move/rename
   *                            operation.
   * @param  newEntry           The subordinate enry after the move/rename
   * @param  newEntry           The subordinate entry after the move/rename
   *                            operation.
   * @param  modifications      A list into which any modifications made to the
   *                            target entry should be placed.
opendj-sdk/opends/src/server/org/opends/server/tools/DBTest.java
@@ -1460,8 +1460,7 @@
                StringBuilder builder = new StringBuilder();
                SortValuesSet svs = new SortValuesSet(key.getData(),
                                                      data.getData(),
                                                      index,
                                                      null);
                                                      index);
                long[] entryIDs = svs.getEntryIDs();
                for(int i = 0; i < entryIDs.length; i++)
                {
opendj-sdk/opends/tests/unit-tests-testng/resource/config-changes.ldif
@@ -1435,3 +1435,13 @@
ds-cfg-base-dn: o=ldif
ds-cfg-ldif-file: config/ldif-backend.ldif
dn: cn=File-Based Debug Logger,cn=Loggers,cn=config
changetype: modify
replace: ds-cfg-enabled
ds-cfg-enabled: true
-
replace: ds-cfg-default-include-throwable-cause
ds-cfg-default-include-throwable-cause: true
-
replace: ds-cfg-default-throwable-stack-frames
ds-cfg-default-throwable-stack-frames: 500
opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestBackendImpl.java
@@ -37,7 +37,6 @@
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.protocols.internal.InternalSearchOperation;
import org.opends.server.protocols.ldap.LDAPFilter;
import org.opends.server.protocols.asn1.ASN1OctetString;
import org.opends.server.types.*;
import org.opends.server.util.Base64;
import static
@@ -50,6 +49,7 @@
import static org.testng.Assert.*;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.LockMode;
import org.opends.server.core.DeleteOperationBasis;
import org.opends.server.core.ModifyOperationBasis;
@@ -819,7 +819,7 @@
    {
      entry = ec.getEntry(DN.decode("uid=user.539,ou=People,dc=test,dc=com"));
      entryID = ec.getDN2ID().get(null,
          DN.decode("uid=user.539,ou=People,dc=test,dc=com"));
          DN.decode("uid=user.539,ou=People,dc=test,dc=com"), LockMode.DEFAULT);
      DeleteOperationBasis delete = new DeleteOperationBasis(conn,
        conn.nextOperationID(),
@@ -835,7 +835,7 @@
      assertFalse(ec.entryExists(DN.decode("uid=user.539,ou=People,dc=test,dc=com")));
      assertNull(ec.getDN2ID().get(null,
          DN.decode("uid=user.539,ou=People,dc=test,dc=com")));
          DN.decode("uid=user.539,ou=People,dc=test,dc=com"), LockMode.DEFAULT));
      assertFalse(ec.getDN2URI().delete(null,
          DN.decode("uid=user.539,ou=People,dc=test,dc=com")));
@@ -919,7 +919,7 @@
      entry = ec.getEntry(DN.decode("uid=user.0,ou=People,dc=test,dc=com"));
      oldEntry = entries.get(0);
      entryID = ec.getDN2ID().get(null,
          DN.decode("uid=user.0,ou=People,dc=test,dc=com"));
          DN.decode("uid=user.0,ou=People,dc=test,dc=com"), LockMode.DEFAULT);
      assertNotNull(entry);
      LinkedHashSet<AttributeValue> values =
@@ -1086,7 +1086,7 @@
      newEntry = entries.get(1);
      newEntry.applyModifications(modifications);
      entry = ec.getEntry(DN.decode("uid=user.1,ou=People,dc=test,dc=com"));
      entryID = ec.getDN2ID().get(null, DN.decode("uid=user.1,ou=People,dc=test,dc=com"));
      entryID = ec.getDN2ID().get(null, DN.decode("uid=user.1,ou=People,dc=test,dc=com"), LockMode.DEFAULT);
      assertNotNull(entryID);
@@ -1290,12 +1290,12 @@
          entry, null);
      assertNotNull(backend.getEntry(DN.decode("cn=Abbey Abbie,ou=People,dc=test,dc=com")));
      assertNotNull(ec.getDN2ID().get(null, DN.decode("cn=Abbey Abbie,ou=People,dc=test,dc=com")));
      assertNotNull(ec.getDN2ID().get(null, DN.decode("cn=Abbey Abbie,ou=People,dc=test,dc=com"), LockMode.DEFAULT));
      assertNull(backend.getEntry(DN.decode("uid=user.2,ou=People,dc=test,dc=com")));
      assertNull(ec.getDN2ID().get(null,
          DN.decode("uid=user.2,ou=People,dc=test,dc=com")));
          DN.decode("uid=user.2,ou=People,dc=test,dc=com"), LockMode.DEFAULT));
    }
    finally
    {
@@ -1317,9 +1317,9 @@
    ec.sharedLock.lock();
    try
    {
      EntryID newSuperiorID = ec.getDN2ID().get(null, DN.decode("ou=JEB Testers,dc=test,dc=com"));
      EntryID newSuperiorID = ec.getDN2ID().get(null, DN.decode("ou=JEB Testers,dc=test,dc=com"), LockMode.DEFAULT);
      EntryID oldID = ec.getDN2ID().get(null,
          DN.decode("ou=People,dc=test,dc=com"));
          DN.decode("ou=People,dc=test,dc=com"), LockMode.DEFAULT);
      assertTrue(newSuperiorID.compareTo(oldID) > 0);
      ArrayList<Control> noControls = new ArrayList<Control>(0);
@@ -1338,17 +1338,17 @@
      modifyDN.run();
      assertNotNull(backend.getEntry(DN.decode("ou=Good People,ou=JEB Testers,dc=test,dc=com")));
      EntryID newID = ec.getDN2ID().get(null, DN.decode("ou=Good People,ou=JEB Testers,dc=test,dc=com"));
      EntryID newID = ec.getDN2ID().get(null, DN.decode("ou=Good People,ou=JEB Testers,dc=test,dc=com"), LockMode.DEFAULT);
      assertNotNull(newID);
      assertTrue(newID.compareTo(newSuperiorID) > 0);
      assertNotNull(backend.getEntry(DN.decode("uid=user.0,ou=Good People,ou=JEB Testers,dc=test,dc=com")));
      EntryID newSubordinateID = ec.getDN2ID().get(null,
          DN.decode("uid=user.0,ou=Good People,ou=JEB Testers,dc=test,dc=com"));
          DN.decode("uid=user.0,ou=Good People,ou=JEB Testers,dc=test,dc=com"), LockMode.DEFAULT);
      assertTrue(newSubordinateID.compareTo(newID) > 0);
      assertNull(backend.getEntry(DN.decode("ou=People,dc=test,dc=com")));
      assertNull(ec.getDN2ID().get(null,
          DN.decode("ou=People,dc=test,dc=com")));
          DN.decode("ou=People,dc=test,dc=com"), LockMode.DEFAULT));
    }
    finally
    {
opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/backends/jeb/TestVerifyJob.java
@@ -39,11 +39,10 @@
import org.testng.annotations.Test;
import static org.testng.Assert.*;
import com.sleepycat.je.Database;
import com.sleepycat.je.DatabaseConfig;
import com.sleepycat.je.DatabaseEntry;
import com.sleepycat.je.OperationStatus;
import com.sleepycat.je.Transaction;
import com.sleepycat.je.LockMode;
import java.util.*;
@@ -390,7 +389,7 @@
      SortValuesSet svs =
          vlvIndex.getSortValuesSet(null, 0, new AttributeValue[3]);
      long id = svs.getEntryIDs()[0];
      Entry entry = eContainer.getID2Entry().get(null, new EntryID(id));
      Entry entry = eContainer.getID2Entry().get(null, new EntryID(id), LockMode.DEFAULT);
      SortValuesSet svs2 = svs.split(2);
      svs2.add(id, vlvIndex.getSortValues(entry));
@@ -400,7 +399,7 @@
      // Muck up the values of another ID
      id = svs.getEntryIDs()[0];
      entry = eContainer.getID2Entry().get(null, new EntryID(id));
      entry = eContainer.getID2Entry().get(null, new EntryID(id), LockMode.DEFAULT);
      AttributeValue[] values = vlvIndex.getSortValues(entry);
      AttributeValue[] badValues = new AttributeValue[values.length];
      System.arraycopy(values, 1, badValues, 0, values.length - 1);
@@ -696,7 +695,7 @@
      SortValuesSet svs =
          vlvIndex.getSortValuesSet(null, 0, new AttributeValue[3]);
      long id = svs.getEntryIDs()[0];
      Entry entry = eContainer.getID2Entry().get(null, new EntryID(id));
      Entry entry = eContainer.getID2Entry().get(null, new EntryID(id), LockMode.DEFAULT);
      svs.remove(id, vlvIndex.getSortValues(entry));
      // Add an incorrectly ordered values.
@@ -705,7 +704,7 @@
      // Muck up the values of another ID
      id = svs.getEntryIDs()[0];
      entry = eContainer.getID2Entry().get(null, new EntryID(id));
      entry = eContainer.getID2Entry().get(null, new EntryID(id), LockMode.DEFAULT);
      AttributeValue[] values = vlvIndex.getSortValues(entry);
      AttributeValue[] badValues = new AttributeValue[values.length];
      System.arraycopy(values, 1, badValues, 0, values.length - 1);