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

Jean-Noel Rouvignac
16.49.2014 77131174b396433451592ca82362a3534ce74c80
OPENDJ-1602 (CR-5566) New pluggable storage based backend

Replacing calls to BackendImpl.Storage.openCursor() by calls to ReadOperation.openCursor().
It required to pass ReadOperation down many method calls.
In several applyConfiguration*() methods, created write operations.
In several applyConfiguration*() methods, created a ConfigChangeResult object at the start of the method and accumulated information on it instead of creating it at the end of the method + passed the ConfigChangeResult object down method calls.

DatabaseContainer.java:
In open(), simplified the code.

EntryContainer.java:
Removed useless comments.

ConfigChangeResult.java:
Added default ctor.
Code cleanup.
9 files modified
603 ■■■■ changed files
opendj3-server-dev/src/server/org/opends/server/backends/pluggable/AttributeIndex.java 117 ●●●● patch | view | raw | blame | history
opendj3-server-dev/src/server/org/opends/server/backends/pluggable/DN2URI.java 8 ●●●● patch | view | raw | blame | history
opendj3-server-dev/src/server/org/opends/server/backends/pluggable/DatabaseContainer.java 31 ●●●● patch | view | raw | blame | history
opendj3-server-dev/src/server/org/opends/server/backends/pluggable/EntryContainer.java 307 ●●●● patch | view | raw | blame | history
opendj3-server-dev/src/server/org/opends/server/backends/pluggable/Index.java 11 ●●●●● patch | view | raw | blame | history
opendj3-server-dev/src/server/org/opends/server/backends/pluggable/NullIndex.java 2 ●●● patch | view | raw | blame | history
opendj3-server-dev/src/server/org/opends/server/backends/pluggable/RootContainer.java 26 ●●●●● patch | view | raw | blame | history
opendj3-server-dev/src/server/org/opends/server/backends/pluggable/VLVIndex.java 30 ●●●●● patch | view | raw | blame | history
opendj3-server-dev/src/server/org/opends/server/types/ConfigChangeResult.java 71 ●●●●● patch | view | raw | blame | history
opendj3-server-dev/src/server/org/opends/server/backends/pluggable/AttributeIndex.java
@@ -29,7 +29,6 @@
import java.io.Closeable;
import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.LocalizableMessageBuilder;
@@ -38,7 +37,6 @@
import org.forgerock.opendj.ldap.Assertion;
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.DecodeException;
import org.forgerock.opendj.ldap.ResultCode;
import org.forgerock.opendj.ldap.schema.MatchingRule;
import org.forgerock.opendj.ldap.spi.IndexQueryFactory;
import org.forgerock.opendj.ldap.spi.IndexingOptions;
@@ -48,6 +46,8 @@
import org.opends.server.admin.std.server.LocalDBIndexCfg;
import org.opends.server.backends.pluggable.BackendImpl.StorageRuntimeException;
import org.opends.server.backends.pluggable.BackendImpl.TreeName;
import org.opends.server.backends.pluggable.BackendImpl.WriteOperation;
import org.opends.server.backends.pluggable.BackendImpl.WriteableStorage;
import org.opends.server.core.DirectoryServer;
import org.opends.server.monitors.DatabaseEnvironmentMonitor;
import org.opends.server.types.*;
@@ -133,42 +133,42 @@
   * @param entryContainer The entryContainer of this attribute index.
   * @throws ConfigException if a configuration related error occurs.
   */
  public AttributeIndex(LocalDBIndexCfg indexConfig, EntryContainer entryContainer) throws ConfigException
  public AttributeIndex(LocalDBIndexCfg indexConfig, EntryContainer entryContainer, WriteableStorage txn) throws ConfigException
  {
    this.entryContainer = entryContainer;
    this.indexConfig = indexConfig;
    buildPresenceIndex();
    buildIndexes(IndexType.EQUALITY);
    buildIndexes(IndexType.SUBSTRING);
    buildIndexes(IndexType.ORDERING);
    buildIndexes(IndexType.APPROXIMATE);
    buildExtensibleIndexes();
    buildPresenceIndex(txn);
    buildIndexes(txn, IndexType.EQUALITY);
    buildIndexes(txn, IndexType.SUBSTRING);
    buildIndexes(txn, IndexType.ORDERING);
    buildIndexes(txn, IndexType.APPROXIMATE);
    buildExtensibleIndexes(txn);
    final JEIndexConfig config = new JEIndexConfig(indexConfig.getSubstringLength());
    indexQueryFactory = new IndexQueryFactoryImpl(nameToIndexes, config);
    extensibleIndexesMapping = computeExtensibleIndexesMapping();
  }
  private void buildPresenceIndex()
  private void buildPresenceIndex(WriteableStorage txn)
  {
    final IndexType indexType = IndexType.PRESENCE;
    if (indexConfig.getIndexType().contains(indexType))
    {
      String indexID = indexType.toString();
      nameToIndexes.put(indexID, newPresenceIndex(indexConfig));
      nameToIndexes.put(indexID, newPresenceIndex(txn, indexConfig));
    }
  }
  private Index newPresenceIndex(LocalDBIndexCfg cfg)
  private Index newPresenceIndex(WriteableStorage txn, LocalDBIndexCfg cfg)
  {
    final AttributeType attrType = cfg.getAttribute();
    final TreeName indexName = getIndexName(attrType, IndexType.PRESENCE.toString());
    final PresenceIndexer indexer = new PresenceIndexer(attrType);
    return entryContainer.newIndexForAttribute(indexName, indexer, cfg.getIndexEntryLimit());
    return entryContainer.newIndexForAttribute(txn, indexName, indexer, cfg.getIndexEntryLimit());
  }
  private void buildExtensibleIndexes() throws ConfigException
  private void buildExtensibleIndexes(WriteableStorage txn) throws ConfigException
  {
    final IndexType indexType = IndexType.EXTENSIBLE;
    if (indexConfig.getIndexType().contains(indexType))
@@ -198,14 +198,14 @@
          if (!nameToIndexes.containsKey(indexId))
          {
            // There is no index available for this index id. Create a new index
            nameToIndexes.put(indexId, newAttributeIndex(indexConfig, indexer));
            nameToIndexes.put(indexId, newAttributeIndex(txn, indexConfig, indexer));
          }
        }
      }
    }
  }
  private void buildIndexes(IndexType indexType) throws ConfigException
  private void buildIndexes(WriteableStorage txn, IndexType indexType) throws ConfigException
  {
    if (indexConfig.getIndexType().contains(indexType))
    {
@@ -219,7 +219,7 @@
      for (org.forgerock.opendj.ldap.spi.Indexer indexer : rule.getIndexers())
      {
        nameToIndexes.put(indexID, newAttributeIndex(indexConfig, indexer));
        nameToIndexes.put(indexID, newAttributeIndex(txn, indexConfig, indexer));
      }
    }
  }
@@ -241,12 +241,13 @@
    }
  }
  private Index newAttributeIndex(LocalDBIndexCfg indexConfig, org.forgerock.opendj.ldap.spi.Indexer indexer)
  private Index newAttributeIndex(WriteableStorage txn, LocalDBIndexCfg indexConfig,
      org.forgerock.opendj.ldap.spi.Indexer indexer)
  {
    final AttributeType attrType = indexConfig.getAttribute();
    final TreeName indexName = getIndexName(attrType, indexer.getIndexID());
    final AttributeIndexer attrIndexer = new AttributeIndexer(attrType, indexer);
    return entryContainer.newIndexForAttribute(indexName, attrIndexer, indexConfig.getIndexEntryLimit());
    return entryContainer.newIndexForAttribute(txn, indexName, attrIndexer, indexConfig.getIndexEntryLimit());
  }
  private TreeName getIndexName(AttributeType attrType, String indexID)
@@ -260,11 +261,11 @@
   * @throws StorageRuntimeException if a JE database error occurs while
   * opening the index.
   */
  public void open() throws StorageRuntimeException
  public void open(WriteableStorage txn) throws StorageRuntimeException
  {
    for (Index index : nameToIndexes.values())
    {
      index.open();
      index.open(txn);
    }
    indexConfig.addChangeListener(this);
  }
@@ -635,35 +636,37 @@
  /** {@inheritDoc} */
  @Override
  public synchronized ConfigChangeResult applyConfigurationChange(LocalDBIndexCfg cfg)
  public synchronized ConfigChangeResult applyConfigurationChange(final LocalDBIndexCfg cfg)
  {
    // this method is not perf sensitive, using an AtomicBoolean will not hurt
    AtomicBoolean adminActionRequired = new AtomicBoolean(false);
    ArrayList<LocalizableMessage> messages = new ArrayList<LocalizableMessage>();
    final ConfigChangeResult ccr = new ConfigChangeResult();
    try
    {
      applyChangeToPresenceIndex(cfg, adminActionRequired, messages);
      applyChangeToIndex(IndexType.EQUALITY, cfg, adminActionRequired, messages);
      applyChangeToIndex(IndexType.SUBSTRING, cfg, adminActionRequired, messages);
      applyChangeToIndex(IndexType.ORDERING, cfg, adminActionRequired, messages);
      applyChangeToIndex(IndexType.APPROXIMATE, cfg, adminActionRequired, messages);
      applyChangeToExtensibleIndexes(cfg, adminActionRequired, messages);
      entryContainer.getStorage().write(new WriteOperation()
      {
        @Override
        public void run(WriteableStorage txn) throws Exception
        {
          applyChangeToPresenceIndex(txn, cfg, ccr);
          applyChangeToIndex(txn, IndexType.EQUALITY, cfg, ccr);
          applyChangeToIndex(txn, IndexType.SUBSTRING, cfg, ccr);
          applyChangeToIndex(txn, IndexType.ORDERING, cfg, ccr);
          applyChangeToIndex(txn, IndexType.APPROXIMATE, cfg, ccr);
          applyChangeToExtensibleIndexes(txn, cfg, ccr);
        }
      });
      extensibleIndexesMapping = computeExtensibleIndexesMapping();
      indexConfig = cfg;
      return new ConfigChangeResult(ResultCode.SUCCESS, adminActionRequired.get(), messages);
    }
    catch(Exception e)
    {
      messages.add(LocalizableMessage.raw(StaticUtils.stackTraceToSingleLineString(e)));
      return new ConfigChangeResult(
          DirectoryServer.getServerErrorResultCode(), adminActionRequired.get(), messages);
      ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
      ccr.addMessage(LocalizableMessage.raw(StaticUtils.stackTraceToSingleLineString(e)));
    }
    return ccr;
  }
  private void applyChangeToExtensibleIndexes(LocalDBIndexCfg cfg,
      AtomicBoolean adminActionRequired, ArrayList<LocalizableMessage> messages)
  private void applyChangeToExtensibleIndexes(WriteableStorage txn, LocalDBIndexCfg cfg, ConfigChangeResult ccr)
  {
    final AttributeType attrType = cfg.getAttribute();
    if (!cfg.getIndexType().contains(IndexType.EXTENSIBLE))
@@ -694,8 +697,8 @@
        validIndexIds.add(indexId);
        if (!nameToIndexes.containsKey(indexId))
        {
          Index index = newAttributeIndex(cfg, indexer);
          openIndex(index, adminActionRequired, messages);
          Index index = newAttributeIndex(txn, cfg, indexer);
          openIndex(txn, index, ccr);
          nameToIndexes.put(indexId, index);
        }
        else
@@ -703,8 +706,8 @@
          Index index = nameToIndexes.get(indexId);
          if (index.setIndexEntryLimit(indexEntryLimit))
          {
            adminActionRequired.set(true);
            messages.add(NOTE_JEB_CONFIG_INDEX_ENTRY_LIMIT_REQUIRES_REBUILD.get(index.getName()));
            ccr.setAdminActionRequired(true);
            ccr.addMessage(NOTE_JEB_CONFIG_INDEX_ENTRY_LIMIT_REQUIRES_REBUILD.get(index.getName()));
          }
          if (indexConfig.getSubstringLength() != cfg.getSubstringLength())
          {
@@ -770,8 +773,7 @@
    return rules;
  }
  private void applyChangeToIndex(IndexType indexType, LocalDBIndexCfg cfg,
      AtomicBoolean adminActionRequired, ArrayList<LocalizableMessage> messages)
  private void applyChangeToIndex(WriteableStorage txn, IndexType indexType, LocalDBIndexCfg cfg, ConfigChangeResult ccr)
  {
    String indexId = indexType.toString();
    Index index = nameToIndexes.get(indexId);
@@ -786,8 +788,8 @@
      final MatchingRule matchingRule = getMatchingRule(indexType, cfg.getAttribute());
      for (org.forgerock.opendj.ldap.spi.Indexer indexer : matchingRule.getIndexers())
      {
        index = newAttributeIndex(cfg, indexer);
        openIndex(index, adminActionRequired, messages);
        index = newAttributeIndex(txn, cfg, indexer);
        openIndex(txn, index, ccr);
        nameToIndexes.put(indexId, index);
      }
    }
@@ -796,14 +798,13 @@
      // already exists. Just update index entry limit.
      if (index.setIndexEntryLimit(cfg.getIndexEntryLimit()))
      {
        adminActionRequired.set(true);
        messages.add(NOTE_JEB_CONFIG_INDEX_ENTRY_LIMIT_REQUIRES_REBUILD.get(index.getName()));
        ccr.setAdminActionRequired(true);
        ccr.addMessage(NOTE_JEB_CONFIG_INDEX_ENTRY_LIMIT_REQUIRES_REBUILD.get(index.getName()));
      }
    }
  }
  private void applyChangeToPresenceIndex(LocalDBIndexCfg cfg, AtomicBoolean adminActionRequired,
      ArrayList<LocalizableMessage> messages)
  private void applyChangeToPresenceIndex(WriteableStorage txn, LocalDBIndexCfg cfg, ConfigChangeResult ccr)
  {
    final IndexType indexType = IndexType.PRESENCE;
    final String indexID = indexType.toString();
@@ -816,8 +817,8 @@
    if (index == null)
    {
      index = newPresenceIndex(cfg);
      openIndex(index, adminActionRequired, messages);
      index = newPresenceIndex(txn, cfg);
      openIndex(txn, index, ccr);
      nameToIndexes.put(indexID, index);
    }
    else
@@ -825,8 +826,8 @@
      // already exists. Just update index entry limit.
      if (index.setIndexEntryLimit(cfg.getIndexEntryLimit()))
      {
        adminActionRequired.set(true);
        messages.add(NOTE_JEB_CONFIG_INDEX_ENTRY_LIMIT_REQUIRES_REBUILD.get(index.getName()));
        ccr.setAdminActionRequired(true);
        ccr.addMessage(NOTE_JEB_CONFIG_INDEX_ENTRY_LIMIT_REQUIRES_REBUILD.get(index.getName()));
      }
    }
  }
@@ -848,14 +849,14 @@
    }
  }
  private void openIndex(Index index, AtomicBoolean adminActionRequired, ArrayList<LocalizableMessage> messages)
  private void openIndex(WriteableStorage txn, Index index, ConfigChangeResult ccr)
  {
    index.open();
    index.open(txn);
    if (!index.isTrusted())
    {
      adminActionRequired.set(true);
      messages.add(NOTE_JEB_INDEX_ADD_REQUIRES_REBUILD.get(index.getName()));
      ccr.setAdminActionRequired(true);
      ccr.addMessage(NOTE_JEB_INDEX_ADD_REQUIRES_REBUILD.get(index.getName()));
    }
  }
opendj3-server-dev/src/server/org/opends/server/backends/pluggable/DN2URI.java
@@ -497,7 +497,7 @@
   * DN.  The referral URLs will be set appropriately for the references found
   * in the referral entry.
   */
  public void targetEntryReferrals(DN targetDN, SearchScope searchScope)
  public void targetEntryReferrals(ReadableStorage txn, DN targetDN, SearchScope searchScope)
       throws DirectoryException
  {
    if (containsReferrals == ConditionResult.UNDEFINED)
@@ -512,7 +512,7 @@
    try
    {
      Cursor cursor = storage.openCursor(treeName);
      final Cursor cursor = txn.openCursor(treeName);
      try
      {
        // Go up through the DIT hierarchy until we find a referral.
@@ -550,7 +550,7 @@
   *          has been reached or the search has been abandoned).
   * @throws DirectoryException If a Directory Server error occurs.
   */
  public boolean returnSearchReferences(SearchOperation searchOp)
  public boolean returnSearchReferences(ReadableStorage txn, SearchOperation searchOp)
       throws DirectoryException
  {
    if (containsReferrals == ConditionResult.UNDEFINED)
@@ -584,7 +584,7 @@
    ByteSequence startKey = suffix;
    try
    {
      Cursor cursor = storage.openCursor(treeName);
      final Cursor cursor = txn.openCursor(treeName);
      try
      {
        // Initialize the cursor very close to the starting value then
opendj3-server-dev/src/server/org/opends/server/backends/pluggable/DatabaseContainer.java
@@ -78,34 +78,12 @@
   * @throws StorageRuntimeException if a JE database error occurs while
   * opening the index.
   */
  public void open() throws StorageRuntimeException
  public void open(WriteableStorage txn) throws StorageRuntimeException
  {
    if (dbConfig.getTransactional())
    storage.openTree(treeName);
    if (logger.isTraceEnabled())
    {
      // Open the database under a transaction.
      Transaction txn = entryContainer.beginTransaction();
      try
      {
        treeName = storage.openDatabase(txn, treeName, dbConfig);
        if (logger.isTraceEnabled())
        {
          logger.trace("JE database %s opened. txnid=%d", treeName, txn.getId());
        }
        EntryContainer.transactionCommit(txn);
      }
      catch (StorageRuntimeException e)
      {
        EntryContainer.transactionAbort(txn);
        throw e;
      }
    }
    else
    {
      treeName = storage.openDatabase(null, treeName, dbConfig);
      if (logger.isTraceEnabled())
      {
        logger.trace("JE database %s opened. txnid=none", treeName);
      }
      logger.trace("JE database %s opened. txnid=%d", treeName, txn.getId());
    }
  }
@@ -131,7 +109,6 @@
    {
      treeName.sync();
    }
    storage.openTree(treeName)
    treeName.close();
    treeName = null;
opendj3-server-dev/src/server/org/opends/server/backends/pluggable/EntryContainer.java
@@ -152,14 +152,19 @@
  {
    /** {@inheritDoc} */
    @Override
    public boolean isConfigurationAddAcceptable(
        LocalDBIndexCfg cfg,
        List<LocalizableMessage> unacceptableReasons)
    public boolean isConfigurationAddAcceptable(final LocalDBIndexCfg cfg, List<LocalizableMessage> unacceptableReasons)
    {
      try
      {
        //Try creating all the indexes before confirming they are valid ones.
        new AttributeIndex(cfg, EntryContainer.this);
        storage.write(new WriteOperation()
        {
          @Override
          public void run(WriteableStorage txn) throws Exception
          {
            //Try creating all the indexes before confirming they are valid ones.
            new AttributeIndex(cfg, EntryContainer.this, txn);
          }
        });
        return true;
      }
      catch(Exception e)
@@ -171,31 +176,33 @@
    /** {@inheritDoc} */
    @Override
    public ConfigChangeResult applyConfigurationAdd(LocalDBIndexCfg cfg)
    public ConfigChangeResult applyConfigurationAdd(final LocalDBIndexCfg cfg)
    {
      boolean adminActionRequired = false;
      List<LocalizableMessage> messages = new ArrayList<LocalizableMessage>();
      final ConfigChangeResult ccr = new ConfigChangeResult();
      try
      {
        AttributeIndex index = new AttributeIndex(cfg, EntryContainer.this);
        index.open();
        if(!index.isTrusted())
        storage.write(new WriteOperation()
        {
          adminActionRequired = true;
          messages.add(NOTE_JEB_INDEX_ADD_REQUIRES_REBUILD.get(
              cfg.getAttribute().getNameOrOID()));
        }
        attrIndexMap.put(cfg.getAttribute(), index);
          @Override
          public void run(WriteableStorage txn) throws Exception
          {
            final AttributeIndex index = new AttributeIndex(cfg, EntryContainer.this, txn);
            index.open(txn);
            if (!index.isTrusted())
            {
              ccr.setAdminActionRequired(true);
              ccr.addMessage(NOTE_JEB_INDEX_ADD_REQUIRES_REBUILD.get(cfg.getAttribute().getNameOrOID()));
            }
            attrIndexMap.put(cfg.getAttribute(), index);
          }
        });
      }
      catch(Exception e)
      {
        messages.add(LocalizableMessage.raw(e.getLocalizedMessage()));
        return new ConfigChangeResult(
            DirectoryServer.getServerErrorResultCode(), adminActionRequired, messages);
        ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
        ccr.addMessage(LocalizableMessage.raw(e.getLocalizedMessage()));
      }
      return new ConfigChangeResult(ResultCode.SUCCESS, adminActionRequired, messages);
      return ccr;
    }
    /** {@inheritDoc} */
@@ -308,38 +315,38 @@
    /** {@inheritDoc} */
    @Override
    public ConfigChangeResult applyConfigurationAdd(LocalDBVLVIndexCfg cfg)
    public ConfigChangeResult applyConfigurationAdd(final LocalDBVLVIndexCfg cfg)
    {
      boolean adminActionRequired = false;
      ArrayList<LocalizableMessage> messages = new ArrayList<LocalizableMessage>();
      final ConfigChangeResult ccr = new ConfigChangeResult();
      try
      {
        VLVIndex vlvIndex = new VLVIndex(cfg, state, storage, EntryContainer.this);
        vlvIndex.open();
        if(!vlvIndex.isTrusted())
        storage.write(new WriteOperation()
        {
          adminActionRequired = true;
          messages.add(NOTE_JEB_INDEX_ADD_REQUIRES_REBUILD.get(
              cfg.getName()));
        }
        vlvIndexMap.put(cfg.getName().toLowerCase(), vlvIndex);
          @Override
          public void run(WriteableStorage txn) throws Exception
          {
            VLVIndex vlvIndex = new VLVIndex(cfg, state, storage, EntryContainer.this, txn);
            vlvIndex.open(txn);
            if(!vlvIndex.isTrusted())
            {
              ccr.setAdminActionRequired(true);
              ccr.addMessage(NOTE_JEB_INDEX_ADD_REQUIRES_REBUILD.get(cfg.getName()));
            }
            vlvIndexMap.put(cfg.getName().toLowerCase(), vlvIndex);
          }
        });
      }
      catch(Exception e)
      {
        messages.add(LocalizableMessage.raw(StaticUtils.stackTraceToSingleLineString(e)));
        return new ConfigChangeResult(
            DirectoryServer.getServerErrorResultCode(), adminActionRequired, messages);
        ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
        ccr.addMessage(LocalizableMessage.raw(StaticUtils.stackTraceToSingleLineString(e)));
      }
      return new ConfigChangeResult(ResultCode.SUCCESS, adminActionRequired, messages);
      return ccr;
    }
    /** {@inheritDoc} */
    @Override
    public boolean isConfigurationDeleteAcceptable(
        LocalDBVLVIndexCfg cfg,
        List<LocalizableMessage> unacceptableReasons)
    public boolean isConfigurationDeleteAcceptable(LocalDBVLVIndexCfg cfg, List<LocalizableMessage> unacceptableReasons)
    {
      // TODO: validate more before returning true?
      return true;
@@ -425,8 +432,7 @@
   * @throws StorageRuntimeException If an error occurs in the JE database.
   * @throws ConfigException if a configuration related error occurs.
   */
  public void open()
  throws StorageRuntimeException, ConfigException
  public void open(WriteableStorage txn) throws StorageRuntimeException, ConfigException
  {
    try
    {
@@ -437,17 +443,17 @@
      id2entry = new ID2Entry(databasePrefix.child(ID2ENTRY_DATABASE_NAME),
          entryDataConfig, storage, this);
      id2entry.open();
      id2entry.open(txn);
      dn2id = new DN2ID(databasePrefix.child(DN2ID_DATABASE_NAME), storage, this);
      dn2id.open();
      dn2id.open(txn);
      state = new State(databasePrefix.child(STATE_DATABASE_NAME), storage, this);
      state.open();
      state.open(txn);
      if (config.isSubordinateIndexesEnabled())
      {
        openSubordinateIndexes();
        openSubordinateIndexes(txn);
      }
      else
      {
@@ -459,7 +465,7 @@
        {
          state.putIndexTrustState(null, id2children, false);
        }
        id2children.open(); // No-op
        id2children.open(txn); // No-op
        id2subtree = new NullIndex(databasePrefix.child(ID2SUBTREE_DATABASE_NAME),
            new ID2SIndexer(), state, storage, this);
@@ -467,20 +473,20 @@
        {
          state.putIndexTrustState(null, id2subtree, false);
        }
        id2subtree.open(); // No-op
        id2subtree.open(txn); // No-op
        logger.info(NOTE_JEB_SUBORDINATE_INDEXES_DISABLED, backend.getBackendID());
      }
      dn2uri = new DN2URI(databasePrefix.child(REFERRAL_DATABASE_NAME), storage, this);
      dn2uri.open();
      dn2uri.open(txn);
      for (String idx : config.listLocalDBIndexes())
      {
        LocalDBIndexCfg indexCfg = config.getLocalDBIndex(idx);
        AttributeIndex index = new AttributeIndex(indexCfg, this);
        index.open();
        AttributeIndex index = new AttributeIndex(indexCfg, this, txn);
        index.open(txn);
        if(!index.isTrusted())
        {
          logger.info(NOTE_JEB_INDEX_ADD_REQUIRES_REBUILD, index.getName());
@@ -492,8 +498,8 @@
      {
        LocalDBVLVIndexCfg vlvIndexCfg = config.getLocalDBVLVIndex(idx);
        VLVIndex vlvIndex = new VLVIndex(vlvIndexCfg, state, storage, this);
        vlvIndex.open();
        VLVIndex vlvIndex = new VLVIndex(vlvIndexCfg, state, storage, this, txn);
        vlvIndex.open(txn);
        if(!vlvIndex.isTrusted())
        {
@@ -683,9 +689,9 @@
   * @return The highest entry ID.
   * @throws StorageRuntimeException If an error occurs in the JE database.
   */
  public EntryID getHighestEntryID() throws StorageRuntimeException
  public EntryID getHighestEntryID(ReadableStorage txn) throws StorageRuntimeException
  {
    Cursor cursor = storage.openCursor(id2entry.getName());
    Cursor cursor = txn.openCursor(id2entry.getName());
    try
    {
      // Position a cursor on the last data item, and the key should give the highest ID.
@@ -821,9 +827,7 @@
          // Handle base-object search first.
          if (searchScope == SearchScope.BASE_OBJECT)
          {
            // Fetch the base entry.
            Entry baseEntry = fetchBaseEntry(aBaseDN, searchScope);
            final Entry baseEntry = fetchBaseEntry(txn, aBaseDN, searchScope);
            if (!isManageDsaITOperation(searchOperation))
            {
              dn2uri.checkTargetForReferral(baseEntry, searchOperation.getScope());
@@ -1002,7 +1006,7 @@
            {
              rootContainer.getMonitorProvider().updateIndexedSearchCount();
            }
            searchIndexed(entryIDList, candidatesAreInScope, searchOperation, pageRequest);
            searchIndexed(txn, entryIDList, candidatesAreInScope, searchOperation, pageRequest);
          }
          else
          {
@@ -1045,7 +1049,7 @@
              }
            }
            searchNotIndexed(searchOperation, pageRequest);
            searchNotIndexed(txn, searchOperation, pageRequest);
          }
          return null;
        }
@@ -1075,7 +1079,7 @@
   * @throws DirectoryException If an error prevented the search from being
   * processed.
   */
  private void searchNotIndexed(SearchOperation searchOperation, PagedResultsControl pageRequest)
  private void searchNotIndexed(ReadableStorage txn, SearchOperation searchOperation, PagedResultsControl pageRequest)
      throws DirectoryException, CanceledOperationException
  {
    DN aBaseDN = searchOperation.getBaseDN();
@@ -1087,9 +1091,7 @@
    // the base entry processing if the cookie is set.
    if (pageRequest == null || pageRequest.getCookie().length() == 0)
    {
      // Fetch the base entry.
      Entry baseEntry = fetchBaseEntry(aBaseDN, searchScope);
      final Entry baseEntry = fetchBaseEntry(txn, aBaseDN, searchScope);
      if (!manageDsaIT)
      {
        dn2uri.checkTargetForReferral(baseEntry, searchScope);
@@ -1105,7 +1107,7 @@
      }
      if (!manageDsaIT
          && !dn2uri.returnSearchReferences(searchOperation)
          && !dn2uri.returnSearchReferences(txn, searchOperation)
          && pageRequest != null)
      {
        // Indicate no more pages.
@@ -1165,7 +1167,7 @@
    try
    {
      Cursor cursor = storage.openCursor(dn2id.getName());
      final Cursor cursor = txn.openCursor(dn2id.getName());
      try
      {
        // Initialize the cursor very close to the starting value.
@@ -1305,11 +1307,9 @@
   * @throws DirectoryException If an error prevented the search from being
   * processed.
   */
  private void searchIndexed(EntryIDSet entryIDList,
      boolean candidatesAreInScope,
      SearchOperation searchOperation,
      PagedResultsControl pageRequest)
  throws DirectoryException, CanceledOperationException
  private void searchIndexed(ReadableStorage txn, EntryIDSet entryIDList, boolean candidatesAreInScope,
      SearchOperation searchOperation, PagedResultsControl pageRequest) throws DirectoryException,
      CanceledOperationException
  {
    SearchScope searchScope = searchOperation.getScope();
    DN aBaseDN = searchOperation.getBaseDN();
@@ -1336,8 +1336,7 @@
    }
    else if (!manageDsaIT)
    {
      // Return any search result references.
      continueSearch = dn2uri.returnSearchReferences(searchOperation);
      continueSearch = dn2uri.returnSearchReferences(txn, searchOperation);
    }
    // Make sure the candidate list is smaller than the lookthrough limit
@@ -1408,9 +1407,7 @@
    if (searchOperation.getEntriesSent() == 0
        && searchOperation.getReferencesSent() == 0)
    {
      // Fetch the base entry if it exists.
      Entry baseEntry = fetchBaseEntry(aBaseDN, searchScope);
      final Entry baseEntry = fetchBaseEntry(txn, aBaseDN, searchScope);
      if (!manageDsaIT)
      {
        dn2uri.checkTargetForReferral(baseEntry, searchScope);
@@ -1499,7 +1496,7 @@
            if (parentDN != null)
            {
              // Check for referral entries above the target.
              dn2uri.targetEntryReferrals(entry.getName(), null);
              dn2uri.targetEntryReferrals(txn, entry.getName(), null);
              // Read the parent ID from dn2id.
              parentID = dn2id.get(txn, parentDN, false);
@@ -1651,7 +1648,7 @@
          try
          {
            // Check for referral entries above the target entry.
            dn2uri.targetEntryReferrals(entryDN, null);
            dn2uri.targetEntryReferrals(txn, entryDN, null);
            // Determine whether this is a subtree delete.
            boolean isSubtreeDelete =
@@ -1677,8 +1674,6 @@
            ByteSequence startKey = suffix;
            CursorConfig cursorConfig = new CursorConfig();
            cursorConfig.setReadCommitted(true);
            Cursor cursor = dn2id.openCursor(txn);
            try
            {
@@ -1989,7 +1984,7 @@
            {
              // The entryDN does not exist.
              // Check for referral entries above the target entry.
              dn2uri.targetEntryReferrals(entryDN, null);
              dn2uri.targetEntryReferrals(txn, entryDN, null);
              return null;
            }
@@ -2212,7 +2207,7 @@
            if (oldApexID == null)
            {
              // Check for referral entries above the target entry.
              dn2uri.targetEntryReferrals(currentDN, null);
              dn2uri.targetEntryReferrals(txn, currentDN, null);
              LocalizableMessage message = ERR_JEB_MODIFYDN_NO_SUCH_OBJECT.get(currentDN);
              DN matchedDN = getMatchedDN(baseDN);
@@ -3109,7 +3104,7 @@
        {
          TreeName oldName = db.getName();
          String newName = oldName.replace(databasePrefix, newDbPrefix);
          storage.renameDatabase(null, oldName, newName);
          storage.renameDatabase(txn, oldName, newName);
          db.setName(newName);
        }
@@ -3122,7 +3117,7 @@
      // Open the containers backup.
      for(DatabaseContainer db : databases)
      {
        db.open();
        db.open(txn);
      }
    }
  }
@@ -3161,74 +3156,79 @@
  /** {@inheritDoc} */
  @Override
  public ConfigChangeResult applyConfigurationChange(LocalDBBackendCfg cfg)
  public ConfigChangeResult applyConfigurationChange(final LocalDBBackendCfg cfg)
  {
    boolean adminActionRequired = false;
    ArrayList<LocalizableMessage> messages = new ArrayList<LocalizableMessage>();
    final ConfigChangeResult ccr = new ConfigChangeResult();
    exclusiveLock.lock();
    try
    {
      if (config.isSubordinateIndexesEnabled() != cfg.isSubordinateIndexesEnabled())
      storage.write(new WriteOperation()
      {
        if (cfg.isSubordinateIndexesEnabled())
        @Override
        public void run(WriteableStorage txn) throws Exception
        {
          // Re-enabling subordinate indexes.
          openSubordinateIndexes();
          if (config.isSubordinateIndexesEnabled() != cfg.isSubordinateIndexesEnabled())
          {
            if (cfg.isSubordinateIndexesEnabled())
            {
              // Re-enabling subordinate indexes.
              openSubordinateIndexes(txn);
            }
            else
            {
              // Disabling subordinate indexes. Use a null index and ensure that
              // future attempts to use the real indexes will fail.
              id2children.close();
              id2children = new NullIndex(databasePrefix.child(ID2CHILDREN_DATABASE_NAME),
                  new ID2CIndexer(), state, storage, EntryContainer.this);
              state.putIndexTrustState(null, id2children, false);
              id2children.open(txn); // No-op
              id2subtree.close();
              id2subtree = new NullIndex(databasePrefix.child(ID2SUBTREE_DATABASE_NAME),
                  new ID2SIndexer(), state, storage, EntryContainer.this);
              state.putIndexTrustState(null, id2subtree, false);
              id2subtree.open(txn); // No-op
              logger.info(NOTE_JEB_SUBORDINATE_INDEXES_DISABLED, cfg.getBackendId());
            }
          }
          if (config.getIndexEntryLimit() != cfg.getIndexEntryLimit())
          {
            if (id2children.setIndexEntryLimit(cfg.getIndexEntryLimit()))
            {
              ccr.setAdminActionRequired(true);
              ccr.addMessage(NOTE_JEB_CONFIG_INDEX_ENTRY_LIMIT_REQUIRES_REBUILD.get(id2children.getName()));
            }
            if (id2subtree.setIndexEntryLimit(cfg.getIndexEntryLimit()))
            {
              ccr.setAdminActionRequired(true);
              ccr.addMessage(NOTE_JEB_CONFIG_INDEX_ENTRY_LIMIT_REQUIRES_REBUILD.get(id2subtree.getName()));
            }
          }
          DataConfig entryDataConfig = new DataConfig(cfg.isEntriesCompressed(),
              cfg.isCompactEncoding(), rootContainer.getCompressedSchema());
          id2entry.setDataConfig(entryDataConfig);
          EntryContainer.this.config = cfg;
        }
        else
        {
          // Disabling subordinate indexes. Use a null index and ensure that
          // future attempts to use the real indexes will fail.
          id2children.close();
          id2children = new NullIndex(databasePrefix.child(ID2CHILDREN_DATABASE_NAME),
              new ID2CIndexer(), state, storage, this);
          state.putIndexTrustState(null, id2children, false);
          id2children.open(); // No-op
          id2subtree.close();
          id2subtree = new NullIndex(databasePrefix.child(ID2SUBTREE_DATABASE_NAME),
              new ID2SIndexer(), state, storage, this);
          state.putIndexTrustState(null, id2subtree, false);
          id2subtree.open(); // No-op
          logger.info(NOTE_JEB_SUBORDINATE_INDEXES_DISABLED, cfg.getBackendId());
        }
      }
      if (config.getIndexEntryLimit() != cfg.getIndexEntryLimit())
      {
        if (id2children.setIndexEntryLimit(cfg.getIndexEntryLimit()))
        {
          adminActionRequired = true;
          messages.add(NOTE_JEB_CONFIG_INDEX_ENTRY_LIMIT_REQUIRES_REBUILD.get(id2children.getName()));
        }
        if (id2subtree.setIndexEntryLimit(cfg.getIndexEntryLimit()))
        {
          adminActionRequired = true;
          messages.add(NOTE_JEB_CONFIG_INDEX_ENTRY_LIMIT_REQUIRES_REBUILD.get(id2subtree.getName()));
        }
      }
      DataConfig entryDataConfig = new DataConfig(cfg.isEntriesCompressed(),
          cfg.isCompactEncoding(), rootContainer.getCompressedSchema());
      id2entry.setDataConfig(entryDataConfig);
      this.config = cfg;
      });
    }
    catch (StorageRuntimeException e)
    catch (Exception e)
    {
      messages.add(LocalizableMessage.raw(stackTraceToSingleLineString(e)));
      return new ConfigChangeResult(DirectoryServer.getServerErrorResultCode(),
          false, messages);
      ccr.setResultCode(DirectoryServer.getServerErrorResultCode());
      ccr.addMessage(LocalizableMessage.raw(stackTraceToSingleLineString(e)));
    }
    finally
    {
      exclusiveLock.unlock();
    }
    return new ConfigChangeResult(ResultCode.SUCCESS, adminActionRequired, messages);
    return ccr;
  }
  /**
@@ -3294,7 +3294,7 @@
    {
      for(DatabaseContainer db : databases)
      {
        db.open();
        db.open(txn);
      }
      Transaction txn = null;
@@ -3370,7 +3370,7 @@
    }
    finally
    {
      database.open();
      database.open(txn);
    }
    if(logger.isTraceEnabled())
    {
@@ -3401,20 +3401,18 @@
    return null;
  }
  /**
   * Opens the id2children and id2subtree indexes.
   */
  private void openSubordinateIndexes()
  /** Opens the id2children and id2subtree indexes. */
  private void openSubordinateIndexes(WriteableStorage txn)
  {
    id2children = newIndex(ID2CHILDREN_DATABASE_NAME, new ID2CIndexer());
    id2subtree = newIndex(ID2SUBTREE_DATABASE_NAME, new ID2SIndexer());
    id2children = newIndex(txn, ID2CHILDREN_DATABASE_NAME, new ID2CIndexer());
    id2subtree = newIndex(txn, ID2SUBTREE_DATABASE_NAME, new ID2SIndexer());
  }
  private Index newIndex(String name, Indexer indexer)
  private Index newIndex(WriteableStorage txn, String name, Indexer indexer)
  {
    final Index index = new Index(databasePrefix.child(name),
        indexer, state, config.getIndexEntryLimit(), 0, true, storage, this);
    index.open();
        indexer, state, config.getIndexEntryLimit(), 0, true, storage, txn, this);
    index.open(txn);
    if (!index.isTrusted())
    {
      logger.info(NOTE_JEB_INDEX_ADD_REQUIRES_REBUILD, index.getName());
@@ -3430,10 +3428,10 @@
   * @param indexEntryLimit the index entry limit
   * @return a new index
   */
  Index newIndexForAttribute(TreeName indexName, Indexer indexer, int indexEntryLimit)
  Index newIndexForAttribute(WriteableStorage txn, TreeName indexName, Indexer indexer, int indexEntryLimit)
  {
    final int cursorEntryLimit = 100000;
    return new Index(indexName, indexer, state, indexEntryLimit, cursorEntryLimit, false, storage, this);
    return new Index(indexName, indexer, state, indexEntryLimit, cursorEntryLimit, false, storage, txn, this);
  }
@@ -3481,10 +3479,9 @@
   * @return the Entry matching the baseDN.
   * @throws DirectoryException if the baseDN doesn't exist.
   */
  private Entry fetchBaseEntry(DN baseDN, SearchScope searchScope)
          throws DirectoryException
  private Entry fetchBaseEntry(ReadableStorage txn, DN baseDN, SearchScope searchScope)
      throws DirectoryException
  {
    // Fetch the base entry.
    Entry baseEntry = null;
    try
    {
@@ -3499,7 +3496,7 @@
    if (baseEntry == null)
    {
      // Check for referral entries above the base entry.
      dn2uri.targetEntryReferrals(baseDN, searchScope);
      dn2uri.targetEntryReferrals(txn, baseDN, searchScope);
      LocalizableMessage message = ERR_JEB_SEARCH_NO_SUCH_OBJECT.get(baseDN);
      DN matchedDN = getMatchedDN(baseDN);
opendj3-server-dev/src/server/org/opends/server/backends/pluggable/Index.java
@@ -37,13 +37,13 @@
import org.forgerock.opendj.ldap.ByteString;
import org.forgerock.opendj.ldap.ConditionResult;
import org.forgerock.opendj.ldap.spi.IndexingOptions;
import org.opends.server.backends.pluggable.IndexBuffer.BufferedIndexValues;
import org.opends.server.backends.pluggable.BackendImpl.Cursor;
import org.opends.server.backends.pluggable.BackendImpl.ReadableStorage;
import org.opends.server.backends.pluggable.BackendImpl.Storage;
import org.opends.server.backends.pluggable.BackendImpl.StorageRuntimeException;
import org.opends.server.backends.pluggable.BackendImpl.TreeName;
import org.opends.server.backends.pluggable.BackendImpl.WriteableStorage;
import org.opends.server.backends.pluggable.IndexBuffer.BufferedIndexValues;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.Entry;
import org.opends.server.types.Modification;
@@ -77,9 +77,6 @@
   */
  private int entryLimitExceededCount;
  /** The max number of tries to rewrite phantom records. */
  private final int phantomWriteRetries = 3;
  /**
   * Whether to maintain a count of IDs for a key once the entry limit
   * has exceeded.
@@ -129,7 +126,7 @@
   */
  public Index(TreeName name, Indexer indexer, State state,
        int indexEntryLimit, int cursorEntryLimit, boolean maintainCount,
        Storage storage, EntryContainer entryContainer)
        Storage storage, WriteableStorage txn, EntryContainer entryContainer)
      throws StorageRuntimeException
  {
    super(name, storage, entryContainer);
@@ -140,11 +137,11 @@
    this.state = state;
    this.trusted = state.getIndexTrustState(null, this);
    if (!trusted && entryContainer.getHighestEntryID().longValue() == 0)
    if (!trusted && entryContainer.getHighestEntryID(txn).longValue() == 0)
    {
      // If there are no entries in the entry container then there
      // is no reason why this index can't be upgraded to trusted.
      setTrusted(null, true);
      setTrusted(txn, true);
    }
  }
opendj3-server-dev/src/server/org/opends/server/backends/pluggable/NullIndex.java
@@ -208,7 +208,7 @@
  /** {@inheritDoc} */
  @Override
  public void open() throws StorageRuntimeException
  public void open(WriteableStorage txn) throws StorageRuntimeException
  {
    // Do nothing.
  }
opendj3-server-dev/src/server/org/opends/server/backends/pluggable/RootContainer.java
@@ -40,6 +40,8 @@
import org.opends.server.api.Backend;
import org.opends.server.backends.pluggable.BackendImpl.Storage;
import org.opends.server.backends.pluggable.BackendImpl.StorageRuntimeException;
import org.opends.server.backends.pluggable.BackendImpl.WriteOperation;
import org.opends.server.backends.pluggable.BackendImpl.WriteableStorage;
import org.opends.server.core.DirectoryServer;
import org.opends.server.monitors.DatabaseEnvironmentMonitor;
import org.opends.server.types.ConfigChangeResult;
@@ -47,10 +49,6 @@
import org.opends.server.types.FilePermission;
import org.opends.server.types.InitializationException;
import static org.opends.messages.ConfigMessages.*;
import static org.opends.messages.JebMessages.*;
import static org.opends.server.util.StaticUtils.*;
@@ -209,7 +207,15 @@
    }
    compressedSchema = new JECompressedSchema(storage);
    openAndRegisterEntryContainers(config.getBaseDN());
    storage.write(new WriteOperation()
    {
      @Override
      public void run(WriteableStorage txn) throws Exception
      {
        openAndRegisterEntryContainers(txn, config.getBaseDN());
      }
    });
  }
  /**
@@ -229,7 +235,7 @@
   * @throws ConfigException If an configuration error occurs while opening
   *                         the entry container.
   */
  public EntryContainer openEntryContainer(DN baseDN, String name)
  public EntryContainer openEntryContainer(DN baseDN, String name, WriteableStorage txn)
      throws StorageRuntimeException, ConfigException
  {
    String databasePrefix;
@@ -244,7 +250,7 @@
    EntryContainer ec = new EntryContainer(baseDN, databasePrefix,
                                           backend, config, storage, this);
    ec.open();
    ec.open(txn);
    return ec;
  }
@@ -283,15 +289,15 @@
   * @throws ConfigException         If a configuration error occurs while
   *                                 opening the entry container.
   */
  private void openAndRegisterEntryContainers(Set<DN> baseDNs)
  private void openAndRegisterEntryContainers(WriteableStorage txn, Set<DN> baseDNs)
      throws StorageRuntimeException, InitializationException, ConfigException
  {
    EntryID id;
    EntryID highestID = null;
    for(DN baseDN : baseDNs)
    {
      EntryContainer ec = openEntryContainer(baseDN, null);
      id = ec.getHighestEntryID();
      EntryContainer ec = openEntryContainer(baseDN, null, txn);
      id = ec.getHighestEntryID(txn);
      registerEntryContainer(baseDN, ec);
      if(highestID == null || id.compareTo(highestID) > 0)
      {
opendj3-server-dev/src/server/org/opends/server/backends/pluggable/VLVIndex.java
@@ -51,6 +51,7 @@
import org.opends.server.backends.pluggable.BackendImpl.ReadableStorage;
import org.opends.server.backends.pluggable.BackendImpl.Storage;
import org.opends.server.backends.pluggable.BackendImpl.StorageRuntimeException;
import org.opends.server.backends.pluggable.BackendImpl.WriteOperation;
import org.opends.server.backends.pluggable.BackendImpl.WriteableStorage;
import org.opends.server.controls.ServerSideSortRequestControl;
import org.opends.server.controls.VLVRequestControl;
@@ -131,7 +132,7 @@
   * configuration
   */
  public VLVIndex(LocalDBVLVIndexCfg config, State state, Storage env,
                  EntryContainer entryContainer)
                  EntryContainer entryContainer, ReadableStorage txn)
      throws StorageRuntimeException, ConfigException
  {
    super(entryContainer.getDatabasePrefix().child("vlv."+config.getName()),
@@ -197,7 +198,7 @@
    this.state = state;
    this.trusted = state.getIndexTrustState(null, this);
    if (!trusted && entryContainer.getHighestEntryID().longValue() == 0)
    if (!trusted && entryContainer.getHighestEntryID(txn).longValue() == 0)
    {
      // If there are no entries in the entry container then there
      // is no reason why this vlvIndex can't be upgraded to trusted.
@@ -223,11 +224,11 @@
  /** {@inheritDoc} */
  @Override
  public void open() throws StorageRuntimeException
  public void open(WriteableStorage txn) throws StorageRuntimeException
  {
    super.open();
    super.open(txn);
    Cursor cursor = storage.openCursor(treeName);
    final Cursor cursor = txn.openCursor(treeName);
    try
    {
      while (cursor.next())
@@ -1304,8 +1305,8 @@
    {
      this.sortedSetCapacity = cfg.getMaxBlockSize();
      // Require admin action only if the new capacity is larger. Otherwise,
      // we will lazyly update the sorted sets.
      // Require admin action only if the new capacity is larger.
      // Otherwise, we will lazily update the sorted sets.
      if (config.getMaxBlockSize() < cfg.getMaxBlockSize())
      {
        adminActionRequired = true;
@@ -1391,12 +1392,19 @@
      entryContainer.exclusiveLock.lock();
      try
      {
        close();
        open();
        storage.write(new WriteOperation()
        {
          @Override
          public void run(WriteableStorage txn) throws Exception
          {
            close();
            open(txn);
          }
        });
      }
      catch(StorageRuntimeException de)
      catch (Exception e)
      {
        messages.add(LocalizableMessage.raw(StaticUtils.stackTraceToSingleLineString(de)));
        messages.add(LocalizableMessage.raw(StaticUtils.stackTraceToSingleLineString(e)));
        if(resultCode == ResultCode.SUCCESS)
        {
          resultCode = DirectoryServer.getServerErrorResultCode();
opendj3-server-dev/src/server/org/opends/server/types/ConfigChangeResult.java
@@ -44,27 +44,31 @@
     mayInvoke=true)
public final class ConfigChangeResult
{
  // A set of messages describing the changes that were made, any
  // action that may be required, or any problems that were
  // encountered.
  private List<LocalizableMessage> messages;
  // Indicates whether one or more of the changes requires
  // administrative action in order to take effect.
  private boolean adminActionRequired;
  // The result code to return to the client from this configuration
  // change.
  private ResultCode resultCode;
  /**
   * A set of messages describing the changes that were made, any action that
   * may be required, or any problems that were encountered.
   */
  private final List<LocalizableMessage> messages;
  /**
   * Creates a new config change result object with the provided
   * information.
   * Indicates whether one or more of the changes requires administrative action
   * in order to take effect.
   */
  private boolean adminActionRequired;
  /** The result code to return to the client from this configuration change. */
  private ResultCode resultCode;
  /** Creates a new mutable config change result object. */
  public ConfigChangeResult()
  {
    this(ResultCode.SUCCESS, false, new ArrayList<LocalizableMessage>());
  }
  /**
   * Creates a new config change result object with the provided information.
   *
   * @param  resultCode           The result code for this config
   *                              change result.
   * @param  resultCode           The result code for this config change result.
   * @param  adminActionRequired  Indicates whether administrative
   *                              action is required for one or more
   *                              of the changes to take effect.
@@ -72,13 +76,9 @@
  public ConfigChangeResult(ResultCode resultCode,
                            boolean adminActionRequired)
  {
    this.resultCode          = resultCode;
    this.adminActionRequired = adminActionRequired;
    this.messages            = new ArrayList<LocalizableMessage>();
    this(resultCode, adminActionRequired, new ArrayList<LocalizableMessage>());
  }
  /**
   * Creates a new config change result object with the provided
   * information.
@@ -101,8 +101,6 @@
    this.messages            = messages;
  }
  /**
   * Retrieves the result code for this config change result.
   *
@@ -126,8 +124,6 @@
    this.resultCode = resultCode;
  }
  /**
   * Indicates whether administrative action is required before one or
   * more of the changes will take effect.
@@ -141,8 +137,6 @@
    return adminActionRequired;
  }
  /**
   * Specifies whether administrative action is required before one or
   * more of the changes will take effect.
@@ -157,8 +151,6 @@
    this.adminActionRequired = adminActionRequired;
  }
  /**
   * Retrieves the set of messages that provide explanation for the
   * processing of the configuration changes.  This list may be
@@ -172,8 +164,6 @@
    return messages;
  }
  /**
   * Adds the provided message to the set of messages for this config
   * change result.
@@ -186,8 +176,6 @@
    messages.add(message);
  }
  /**
   * Retrieves a string representation of this config change result.
   *
@@ -201,8 +189,6 @@
    return buffer.toString();
  }
  /**
   * Appends a string representation of this config change result to
   * the provided buffer.
@@ -213,18 +199,15 @@
  public void toString(StringBuilder buffer)
  {
    buffer.append("ConfigChangeResult(result=");
    buffer.append(resultCode.toString());
    buffer.append(resultCode);
    buffer.append(", adminActionRequired=");
    buffer.append(adminActionRequired);
    buffer.append(", messages={");
    if (! messages.isEmpty())
    {
      Iterator<LocalizableMessage> iterator = messages.iterator();
      LocalizableMessage firstMessage = iterator.next();
      buffer.append(firstMessage);
      final Iterator<LocalizableMessage> iterator = messages.iterator();
      buffer.append(iterator.next());
      while (iterator.hasNext())
      {
        buffer.append(",");
@@ -232,8 +215,6 @@
      }
    }
    buffer.append("})");
  }
}