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

neil_a_wilson
05.41.2006 5be072c20e46f0921bb00401ff26d0defb3e8991
Update the base DN registration process to address a number of issues:

- It fixes problems with base DN registration when attempting to use nested
backends.

- It changes the terminology that is in use. Previously, the term "suffix" was
used everywhere, even when it wasn't really correct. Now "naming context" is
used when it's talking about a top-level base DN, and "base DN" is used for
any base DN regardless of whether it's a naming context.

- It adds a new ds-private-naming-contexts operational attribute to the root
DSE to list the private naming contexts defined in the server.

- It updates the backend API to get rid of the abstract supportsControl and
supportsFeature methods and replace them with a default concrete
implementation.


OpenDS Issue Numbers: 546, 750
1 files added
20 files modified
2470 ■■■■ changed files
opends/resource/schema/02-config.ldif 3 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/api/Backend.java 92 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/BackupBackend.java 63 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/MemoryBackend.java 57 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/MonitorBackend.java 63 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/RootDSEBackend.java 85 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/SchemaBackend.java 73 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/jeb/BackendImpl.java 78 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/backends/task/TaskBackend.java 63 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/AddOperation.java 2 ●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/BackendConfigManager.java 157 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/core/DirectoryServer.java 722 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/ConfigFileHandler.java 39 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/extensions/DigestMD5SASLMechanismHandler.java 4 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/messages/BackendMessages.java 14 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/messages/ConfigMessages.java 14 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/messages/CoreMessages.java 136 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/types/DN.java 4 ●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/util/ServerConstants.java 17 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/TestCaseUtils.java 1 ●●●● patch | view | raw | blame | history
opends/tests/unit-tests-testng/src/server/org/opends/server/core/BackendConfigManagerTestCase.java 783 ●●●●● patch | view | raw | blame | history
opends/resource/schema/02-config.ldif
@@ -995,6 +995,9 @@
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.289 NAME 'ds-pwp-password-policy-dn'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 USAGE directoryOperation
  X-ORIGIN 'OpenDS Directory Server' )
attributeTypes: ( 1.3.6.1.4.1.26027.1.1.291 NAME 'ds-private-naming-contexts'
  SYNTAX 1.3.6.1.4.1.1466.115.121.1.12 USAGE directoryOperation
  X-ORIGIN 'OpenDS Directory Server' )
objectClasses: ( 1.3.6.1.4.1.26027.1.2.1
  NAME 'ds-cfg-access-control-handler' SUP top STRUCTURAL
  MUST ( cn $ ds-cfg-acl-handler-class $ ds-cfg-acl-handler-enabled )
opends/src/server/org/opends/server/api/Backend.java
@@ -29,6 +29,7 @@
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.locks.Lock;
@@ -400,10 +401,18 @@
   * @param  controlOID  The OID of the control for which to make the
   *                     determination.
   *
   * @return  <CODE>true</CODE> if this backend does support the
   *          requested control, or <CODE>false</CODE>
   * @return  {@code true} if this backends supports the control with
   *          the specified OID, or {@code false} if it does not.
   */
  public abstract boolean supportsControl(String controlOID);
  public final boolean supportsControl(String controlOID)
  {
    assert debugEnter(CLASS_NAME, "supportsControl",
                      String.valueOf(controlOID));
    Set<String> supportedControls = getSupportedControls();
    return ((supportedControls != null) &&
            supportedControls.contains(controlOID));
  }
@@ -424,10 +433,18 @@
   * @param  featureOID  The OID of the feature for which to make the
   *                     determination.
   *
   * @return  <CODE>true</CODE> if this backend does support the
   *          requested feature, or <CODE>false</CODE>
   * @return  {@code true} if this backend supports the feature with
   *          the specified OID, or {@code false} if it does not.
   */
  public abstract boolean supportsFeature(String featureOID);
  public final boolean supportsFeature(String featureOID)
  {
    assert debugEnter(CLASS_NAME, "supportsFeature",
                      String.valueOf(featureOID));
    Set<String> supportedFeatures = getSupportedFeatures();
    return ((supportedFeatures != null) &&
            supportedFeatures.contains(featureOID));
  }
@@ -892,17 +909,66 @@
    synchronized (this)
    {
      LinkedHashSet<Backend> backendSet =
           new LinkedHashSet<Backend>();
      for (Backend b : subordinateBackends)
      {
        backendSet.add(b);
      }
      if (backendSet.add(subordinateBackend))
      {
      Backend[] newSubordinateBackends =
           new Backend[subordinateBackends.length+1];
      System.arraycopy(subordinateBackends, 0, newSubordinateBackends,
                       0, subordinateBackends.length);
      newSubordinateBackends[subordinateBackends.length] =
           subordinateBackend;
             new Backend[backendSet.size()];
        backendSet.toArray(newSubordinateBackends);
      subordinateBackends = newSubordinateBackends;
    }
  }
  }
  /**
   * Removes the provided backend from the set of subordinate backends
   * for this backend.
   *
   * @param  subordinateBackend  The backend to remove from the set of
   *                             subordinate backends for this
   *                             backend.
   */
  public void removeSubordinateBackend(Backend subordinateBackend)
  {
    assert debugEnter(CLASS_NAME, "removeSubordinateBackend",
                      String.valueOf(subordinateBackend));
    synchronized (this)
    {
      ArrayList<Backend> backendList =
           new ArrayList<Backend>(subordinateBackends.length);
      boolean found = false;
      for (Backend b : subordinateBackends)
      {
        if (b.equals(subordinateBackend))
        {
          found = true;
        }
        else
        {
          backendList.add(b);
        }
      }
      if (found)
      {
        Backend[] newSubordinateBackends =
             new Backend[backendList.size()];
        backendList.toArray(newSubordinateBackends);
        subordinateBackends = newSubordinateBackends;
      }
    }
  }
opends/src/server/org/opends/server/backends/BackupBackend.java
@@ -275,7 +275,19 @@
    // Register the backup base as a private suffix.
    DirectoryServer.registerPrivateSuffix(backupBaseDN, this);
    try
    {
      DirectoryServer.registerBaseDN(backupBaseDN, this, true, false);
    }
    catch (Exception e)
    {
      assert debugException(CLASS_NAME, "initializeBackend", e);
      msgID = MSGID_BACKEND_CANNOT_REGISTER_BASEDN;
      String message = getMessage(msgID, backupBaseDN.toString(),
                                  stackTraceToSingleLineString(e));
      throw new InitializationException(msgID, message, e);
    }
  }
@@ -296,6 +308,15 @@
    assert debugEnter(CLASS_NAME, "finalizeBackend");
    DirectoryServer.deregisterConfigurableComponent(this);
    try
    {
      DirectoryServer.deregisterBaseDN(backupBaseDN, false);
    }
    catch (Exception e)
    {
      assert debugException(CLASS_NAME, "finalizeBackend", e);
    }
  }
@@ -1029,26 +1050,6 @@
  /**
   * Indicates whether this backend supports the specified control.
   *
   * @param  controlOID  The OID of the control for which to make the
   *                     determination.
   *
   * @return  <CODE>true</CODE> if this backend does support the requested
   *          control, or <CODE>false</CODE>
   */
  public boolean supportsControl(String controlOID)
  {
    assert debugEnter(CLASS_NAME, "supportsControl",
                      String.valueOf(controlOID));
    // This backend does not provide any special control support.
    return false;
  }
  /**
   * Retrieves the OIDs of the features that may be supported by this backend.
   *
   * @return  The OIDs of the features that may be supported by this backend.
@@ -1063,26 +1064,6 @@
  /**
   * Indicates whether this backend supports the specified feature.
   *
   * @param  featureOID  The OID of the feature for which to make the
   *                     determination.
   *
   * @return  <CODE>true</CODE> if this backend does support the requested
   *          feature, or <CODE>false</CODE>
   */
  public boolean supportsFeature(String featureOID)
  {
    assert debugEnter(CLASS_NAME, "supportsFeature",
                      String.valueOf(featureOID));
    // This backend does not provide any special feature support.
    return false;
  }
  /**
   * Indicates whether this backend provides a mechanism to export the data it
   * contains to an LDIF file.
   *
opends/src/server/org/opends/server/backends/MemoryBackend.java
@@ -48,6 +48,7 @@
import org.opends.server.types.DirectoryException;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.InitializationException;
import org.opends.server.types.LDIFExportConfig;
import org.opends.server.types.LDIFImportConfig;
import org.opends.server.types.RestoreConfig;
@@ -62,6 +63,7 @@
import static org.opends.server.messages.BackendMessages.*;
import static org.opends.server.messages.MessageHandler.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
@@ -148,7 +150,7 @@
   */
  public synchronized void initializeBackend(ConfigEntry configEntry,
                                             DN[] baseDNs)
         throws ConfigException
         throws ConfigException, InitializationException
  {
    assert debugEnter(CLASS_NAME, "initializeBackend",
                      String.valueOf(configEntry), String.valueOf(baseDNs));
@@ -184,7 +186,19 @@
    for (DN dn : baseDNs)
    {
      DirectoryServer.registerSuffix(dn, this);
      try
      {
        DirectoryServer.registerBaseDN(dn, this, false, false);
      }
      catch (Exception e)
      {
        assert debugException(CLASS_NAME, "initializeBackend", e);
        int msgID = MSGID_BACKEND_CANNOT_REGISTER_BASEDN;
        String message = getMessage(msgID, dn.toString(),
                                    stackTraceToSingleLineString(e));
        throw new InitializationException(msgID, message, e);
      }
    }
  }
@@ -209,6 +223,18 @@
    assert debugEnter(CLASS_NAME, "finalizeBackend");
    clearMemoryBackend();
    for (DN dn : baseDNs)
    {
      try
      {
        DirectoryServer.deregisterBaseDN(dn, false);
      }
      catch (Exception e)
      {
        assert debugException(CLASS_NAME, "finalizeBackend", e);
      }
    }
  }
@@ -585,19 +611,6 @@
  /**
   * {@inheritDoc}
   */
  public boolean supportsControl(String controlOID)
  {
    assert debugEnter(CLASS_NAME, "supportsControl",
                      String.valueOf(controlOID));
    return supportedControls.contains(controlOID);
  }
  /**
   * {@inheritDoc}
   */
  public HashSet<String> getSupportedFeatures()
  {
    assert debugEnter(CLASS_NAME, "getSupportedFeatures");
@@ -610,20 +623,6 @@
  /**
   * {@inheritDoc}
   */
  public boolean supportsFeature(String featureOID)
  {
    assert debugEnter(CLASS_NAME, "supportsFeature",
                      String.valueOf(featureOID));
    // This backend does not provide any special feature support.
    return false;
  }
  /**
   * {@inheritDoc}
   */
  public boolean supportsLDIFExport()
  {
    assert debugEnter(CLASS_NAME, "supportsLDIFExport");
opends/src/server/org/opends/server/backends/MonitorBackend.java
@@ -250,7 +250,19 @@
    // Register the monitor base as a private suffix.
    DirectoryServer.registerPrivateSuffix(baseMonitorDN, this);
    try
    {
      DirectoryServer.registerBaseDN(baseMonitorDN, this, true, false);
    }
    catch (Exception e)
    {
      assert debugException(CLASS_NAME, "initializeBackend", e);
      int msgID = MSGID_BACKEND_CANNOT_REGISTER_BASEDN;
      String message = getMessage(msgID, baseMonitorDN.toString(),
                                  stackTraceToSingleLineString(e));
      throw new InitializationException(msgID, message, e);
    }
  }
@@ -271,6 +283,15 @@
    assert debugEnter(CLASS_NAME, "finalizeBackend");
    DirectoryServer.deregisterConfigurableComponent(this);
    try
    {
      DirectoryServer.deregisterBaseDN(baseMonitorDN, false);
    }
    catch (Exception e)
    {
      assert debugException(CLASS_NAME, "finalizeBackend", e);
    }
  }
@@ -929,26 +950,6 @@
  /**
   * Indicates whether this backend supports the specified control.
   *
   * @param  controlOID  The OID of the control for which to make the
   *                     determination.
   *
   * @return  <CODE>true</CODE> if this backend does support the requested
   *          control, or <CODE>false</CODE>
   */
  public boolean supportsControl(String controlOID)
  {
    assert debugEnter(CLASS_NAME, "supportsControl",
                      String.valueOf(controlOID));
    // This backend does not provide any special control support.
    return false;
  }
  /**
   * Retrieves the OIDs of the features that may be supported by this backend.
   *
   * @return  The OIDs of the features that may be supported by this backend.
@@ -963,26 +964,6 @@
  /**
   * Indicates whether this backend supports the specified feature.
   *
   * @param  featureOID  The OID of the feature for which to make the
   *                     determination.
   *
   * @return  <CODE>true</CODE> if this backend does support the requested
   *          feature, or <CODE>false</CODE>
   */
  public boolean supportsFeature(String featureOID)
  {
    assert debugEnter(CLASS_NAME, "supportsFeature",
                      String.valueOf(featureOID));
    // This backend does not provide any special feature support.
    return false;
  }
  /**
   * Indicates whether this backend provides a mechanism to export the data it
   * contains to an LDIF file.
   *
opends/src/server/org/opends/server/backends/RootDSEBackend.java
@@ -502,21 +502,42 @@
    // Add the "namingContexts" attribute.
    Attribute namingContextAttr =
    Attribute publicNamingContextAttr =
         createDNAttribute(ATTR_NAMING_CONTEXTS, ATTR_NAMING_CONTEXTS_LC,
                           DirectoryServer.getSuffixes().keySet());
    ArrayList<Attribute> namingContextAttrs = new ArrayList<Attribute>(1);
    namingContextAttrs.add(namingContextAttr);
                           DirectoryServer.getPublicNamingContexts().keySet());
    ArrayList<Attribute> publicNamingContextAttrs = new ArrayList<Attribute>(1);
    publicNamingContextAttrs.add(publicNamingContextAttr);
    if (showAllAttributes ||
        (! namingContextAttr.getAttributeType().isOperational()))
        (! publicNamingContextAttr.getAttributeType().isOperational()))
    {
      dseUserAttrs.put(namingContextAttr.getAttributeType(),
                       namingContextAttrs);
      dseUserAttrs.put(publicNamingContextAttr.getAttributeType(),
                       publicNamingContextAttrs);
    }
    else
    {
      dseOperationalAttrs.put(namingContextAttr.getAttributeType(),
                              namingContextAttrs);
      dseOperationalAttrs.put(publicNamingContextAttr.getAttributeType(),
                              publicNamingContextAttrs);
    }
    // Add the "ds-private-naming-contexts" attribute.
    Attribute privateNamingContextAttr =
         createDNAttribute(ATTR_PRIVATE_NAMING_CONTEXTS,
                           ATTR_PRIVATE_NAMING_CONTEXTS,
                           DirectoryServer.getPrivateNamingContexts().keySet());
    ArrayList<Attribute> privateNamingContextAttrs =
         new ArrayList<Attribute>(1);
    privateNamingContextAttrs.add(privateNamingContextAttr);
    if (showAllAttributes ||
        (! privateNamingContextAttr.getAttributeType().isOperational()))
    {
      dseUserAttrs.put(privateNamingContextAttr.getAttributeType(),
                       privateNamingContextAttrs);
    }
    else
    {
      dseOperationalAttrs.put(privateNamingContextAttr.getAttributeType(),
                              privateNamingContextAttrs);
    }
@@ -858,7 +879,7 @@
    Map<DN,Backend> baseMap;
    if (subordinateBaseDNs == null)
    {
      baseMap = DirectoryServer.getSuffixes();
      baseMap = DirectoryServer.getPublicNamingContexts();
    }
    else
    {
@@ -1043,7 +1064,7 @@
        Map<DN,Backend> baseMap;
        if (subordinateBaseDNs == null)
        {
          baseMap = DirectoryServer.getSuffixes();
          baseMap = DirectoryServer.getPublicNamingContexts();
        }
        else
        {
@@ -1072,7 +1093,7 @@
      case SUBORDINATE_SUBTREE:
        if (subordinateBaseDNs == null)
        {
          baseMap = DirectoryServer.getSuffixes();
          baseMap = DirectoryServer.getPublicNamingContexts();
        }
        else
        {
@@ -1158,26 +1179,6 @@
  /**
   * Indicates whether this backend supports the specified control.
   *
   * @param  controlOID  The OID of the control for which to make the
   *                     determination.
   *
   * @return  <CODE>true</CODE> if this backend does support the requested
   *          control, or <CODE>false</CODE>
   */
  public boolean supportsControl(String controlOID)
  {
    assert debugEnter(CLASS_NAME, "supportsControl",
                      String.valueOf(controlOID));
    // This backend does not provide any special control support.
    return false;
  }
  /**
   * Retrieves the OIDs of the features that may be supported by this backend.
   *
   * @return  The OIDs of the features that may be supported by this backend.
@@ -1192,26 +1193,6 @@
  /**
   * Indicates whether this backend supports the specified feature.
   *
   * @param  featureOID  The OID of the feature for which to make the
   *                     determination.
   *
   * @return  <CODE>true</CODE> if this backend does support the requested
   *          feature, or <CODE>false</CODE>
   */
  public boolean supportsFeature(String featureOID)
  {
    assert debugEnter(CLASS_NAME, "supportsFeature",
                      String.valueOf(featureOID));
    // This backend does not provide any special feature support.
    return false;
  }
  /**
   * Indicates whether this backend provides a mechanism to export the data it
   * contains to an LDIF file.
   *
opends/src/server/org/opends/server/backends/SchemaBackend.java
@@ -251,11 +251,22 @@
    // Register each of the suffixes with the Directory Server.  Also, register
    // the first one as the schema base.
    this.baseDNs = baseDNs;
    DirectoryServer.registerPrivateSuffix(baseDNs[0], this);
    DirectoryServer.setSchemaDN(baseDNs[0]);
    for (int i=1; i < baseDNs.length; i++)
    for (int i=0; i < baseDNs.length; i++)
    {
      DirectoryServer.registerPrivateSuffix(baseDNs[i], this);
      try
      {
        DirectoryServer.registerBaseDN(baseDNs[i], this, true, false);
      }
      catch (Exception e)
      {
        assert debugException(CLASS_NAME, "initializeBackend", e);
        msgID = MSGID_BACKEND_CANNOT_REGISTER_BASEDN;
        String message = getMessage(msgID, baseDNs[i].toString(),
                                    stackTraceToSingleLineString(e));
        throw new InitializationException(msgID, message, e);
      }
    }
@@ -306,6 +317,18 @@
    assert debugEnter(CLASS_NAME, "finalizeBackend");
    DirectoryServer.deregisterConfigurableComponent(this);
    for (DN baseDN : baseDNs)
    {
      try
      {
        DirectoryServer.deregisterBaseDN(baseDN, false);
      }
      catch (Exception e)
      {
        assert debugException(CLASS_NAME, "finalizeBackend", e);
      }
    }
  }
@@ -801,26 +824,6 @@
  /**
   * Indicates whether this backend supports the specified control.
   *
   * @param  controlOID  The OID of the control for which to make the
   *                     determination.
   *
   * @return  <CODE>true</CODE> if this backend does support the requested
   *          control, or <CODE>false</CODE>
   */
  public boolean supportsControl(String controlOID)
  {
    assert debugEnter(CLASS_NAME, "supportsControl",
                      String.valueOf(controlOID));
    // This backend does not provide any special control support.
    return false;
  }
  /**
   * Retrieves the OIDs of the features that may be supported by this backend.
   *
   * @return  The OIDs of the features that may be supported by this backend.
@@ -835,26 +838,6 @@
  /**
   * Indicates whether this backend supports the specified feature.
   *
   * @param  featureOID  The OID of the feature for which to make the
   *                     determination.
   *
   * @return  <CODE>true</CODE> if this backend does support the requested
   *          feature, or <CODE>false</CODE>
   */
  public boolean supportsFeature(String featureOID)
  {
    assert debugEnter(CLASS_NAME, "supportsFeature",
                      String.valueOf(featureOID));
    // This backend does not provide any special feature support.
    return false;
  }
  /**
   * Indicates whether this backend provides a mechanism to export the data it
   * contains to an LDIF file.
   *
@@ -2227,7 +2210,7 @@
      {
        try
        {
          DirectoryServer.deregisterSuffix(dn);
          DirectoryServer.deregisterBaseDN(dn, false);
          if (detailedResults)
          {
            msgID = MSGID_SCHEMA_DEREGISTERED_BASE_DN;
@@ -2250,7 +2233,7 @@
      {
        try
        {
          DirectoryServer.registerPrivateSuffix(dn, this);
          DirectoryServer.registerBaseDN(dn, this, true, false);
          if (detailedResults)
          {
            msgID = MSGID_SCHEMA_REGISTERED_BASE_DN;
opends/src/server/org/opends/server/backends/jeb/BackendImpl.java
@@ -63,6 +63,7 @@
import org.opends.server.types.ResultCode;
import org.opends.server.util.LDIFException;
import static org.opends.server.messages.BackendMessages.*;
import static org.opends.server.messages.MessageHandler.*;
import static org.opends.server.messages.JebMessages.*;
import static org.opends.server.loggers.Error.logError;
@@ -293,10 +294,21 @@
    config = new Config();
    config.initializeConfig(configEntry, baseDNs);
    // FIXME: Currently assuming every base DN is also a suffix.
    for (DN dn : baseDNs)
    {
      DirectoryServer.registerSuffix(dn, this);
      try
      {
        DirectoryServer.registerBaseDN(dn, this, false, false);
      }
      catch (Exception e)
      {
        assert debugException(CLASS_NAME, "initializeBackend", e);
        int    msgID   = MSGID_BACKEND_CANNOT_REGISTER_BASEDN;
        String message = getMessage(msgID, String.valueOf(dn),
                                    String.valueOf(e));
        throw new InitializationException(msgID, message, e);
      }
    }
/*
@@ -402,9 +414,9 @@
    {
      try
      {
        DirectoryServer.deregisterSuffix(dn);
        DirectoryServer.deregisterBaseDN(dn, false);
      }
      catch (ConfigException e)
      catch (Exception e)
      {
        assert debugException(CLASS_NAME, "finalizeBackend", e);
      }
@@ -571,23 +583,6 @@
  /**
   * Indicates whether this backend supports the specified feature.
   *
   * @param featureOID The OID of the feature for which to make the
   *                   determination.
   * @return <CODE>true</CODE> if this backend does support the requested
   *         feature, or <CODE>false</CODE>
   */
  public boolean supportsFeature(String featureOID)
  {
    assert debugEnter(CLASS_NAME, "supportsFeature");
    return false;  //NYI
  }
  /**
   * Retrieves the OIDs of the controls that may be supported by this backend.
   *
   * @return The OIDs of the controls that may be supported by this backend.
@@ -602,23 +597,6 @@
  /**
   * Indicates whether this backend supports the specified control.
   *
   * @param controlOID The OID of the control for which to make the
   *                   determination.
   * @return <CODE>true</CODE> if this backend does support the requested
   *         control, or <CODE>false</CODE>
   */
  public boolean supportsControl(String controlOID)
  {
    assert debugEnter(CLASS_NAME, "supportsControl");
    return supportedControls.contains(controlOID);
  }
  /**
   * Retrieves the set of base-level DNs that may be used within this backend.
   *
   * @return The set of base-level DNs that may be used within this backend.
@@ -1365,6 +1343,8 @@
    assert debugEnter(CLASS_NAME, "applyNewConfiguration");
    ConfigChangeResult ccr;
    ResultCode resultCode = ResultCode.SUCCESS;
    ArrayList<String> messages = new ArrayList<String>();
    try
    {
@@ -1397,7 +1377,7 @@
          // Even though access to the entry container map is safe, there may be
          // operation threads with a handle on the entry container being
          // closed.
          DirectoryServer.deregisterSuffix(baseDN);
          DirectoryServer.deregisterBaseDN(baseDN, false);
          rootContainer.removeEntryContainer(baseDN);
        }
      }
@@ -1406,9 +1386,24 @@
      {
        if (!rootContainer.getBaseDNs().contains(baseDN))
        {
          try
          {
          // The base DN was added.
          rootContainer.openEntryContainer(baseDN);
          DirectoryServer.registerSuffix(baseDN, this);
            DirectoryServer.registerBaseDN(baseDN, this, false, false);
          }
          catch (Exception e)
          {
            assert debugException(CLASS_NAME, "applyNewConfiguration", e);
            resultCode = DirectoryServer.getServerErrorResultCode();
            int msgID   = MSGID_BACKEND_CANNOT_REGISTER_BASEDN;
            messages.add(getMessage(msgID, String.valueOf(baseDN),
                                    String.valueOf(e)));
            ccr = new ConfigChangeResult(resultCode, false, messages);
            return ccr;
          }
        }
      }
@@ -1420,14 +1415,13 @@
    }
    catch (Exception e)
    {
      ArrayList<String> messages = new ArrayList<String>();
      messages.add(e.getMessage());
      ccr = new ConfigChangeResult(DirectoryServer.getServerErrorResultCode(),
                                   false, messages);
      return ccr;
    }
    ccr = new ConfigChangeResult(ResultCode.SUCCESS, false);
    ccr = new ConfigChangeResult(resultCode, false, messages);
    return ccr;
  }
opends/src/server/org/opends/server/backends/task/TaskBackend.java
@@ -336,7 +336,19 @@
    // Register the task base as a private suffix.
    DirectoryServer.registerPrivateSuffix(baseDNs[0], this);
    try
    {
      DirectoryServer.registerBaseDN(taskRootDN, this, true, false);
    }
    catch (Exception e)
    {
      assert debugException(CLASS_NAME, "initializeBackend", e);
      msgID = MSGID_BACKEND_CANNOT_REGISTER_BASEDN;
      String message = getMessage(msgID, taskRootDN.toString(),
                                  stackTraceToSingleLineString(e));
      throw new InitializationException(msgID, message, e);
    }
  }
@@ -381,6 +393,15 @@
    {
      assert debugException(CLASS_NAME, "finalizeBackend", e);
    }
    try
    {
      DirectoryServer.deregisterBaseDN(taskRootDN, false);
    }
    catch (Exception e)
    {
      assert debugException(CLASS_NAME, "finalizeBackend", e);
    }
  }
@@ -971,26 +992,6 @@
  /**
   * Indicates whether this backend supports the specified control.
   *
   * @param  controlOID  The OID of the control for which to make the
   *                     determination.
   *
   * @return  <CODE>true</CODE> if this backend does support the requested
   *          control, or <CODE>false</CODE>
   */
  public boolean supportsControl(String controlOID)
  {
    assert debugEnter(CLASS_NAME, "supportsControl",
                      String.valueOf(controlOID));
    // This backend does not provide any special control support.
    return false;
  }
  /**
   * Retrieves the OIDs of the features that may be supported by this backend.
   *
   * @return  The OIDs of the features that may be supported by this backend.
@@ -1005,26 +1006,6 @@
  /**
   * Indicates whether this backend supports the specified feature.
   *
   * @param  featureOID  The OID of the feature for which to make the
   *                     determination.
   *
   * @return  <CODE>true</CODE> if this backend does support the requested
   *          feature, or <CODE>false</CODE>
   */
  public boolean supportsFeature(String featureOID)
  {
    assert debugEnter(CLASS_NAME, "supportsFeature",
                      String.valueOf(featureOID));
    // This backend does not provide any special feature support.
    return false;
  }
  /**
   * Indicates whether this backend provides a mechanism to export the data it
   * contains to an LDIF file.
   *
opends/src/server/org/opends/server/core/AddOperation.java
@@ -1018,7 +1018,7 @@
      if (parentDN == null)
      {
        // Either this entry is a suffix or doesn't belong in the directory.
        if (DirectoryServer.isSuffix(entryDN))
        if (DirectoryServer.isNamingContext(entryDN))
        {
          // This is fine.  This entry is one of the configured suffixes.
          parentLock = null;
opends/src/server/org/opends/server/core/BackendConfigManager.java
@@ -48,6 +48,7 @@
import org.opends.server.config.MultiChoiceConfigAttribute;
import org.opends.server.config.StringConfigAttribute;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.DN;
import org.opends.server.types.ErrorLogCategory;
import org.opends.server.types.ErrorLogSeverity;
@@ -542,7 +543,21 @@
      // Register the backend with the server.
      try
      {
      DirectoryServer.registerBackend(backend);
      }
      catch (Exception e)
      {
        assert debugException(CLASS_NAME, "initializeBackendConfig", e);
        msgID = MSGID_CONFIG_BACKEND_CANNOT_REGISTER_BACKEND;
        String message = getMessage(msgID, backendID,
                                    stackTraceToSingleLineString(e));
        logError(ErrorLogCategory.CONFIGURATION, ErrorLogSeverity.SEVERE_ERROR,
                 message, msgID);
        // FIXME -- Do we need to send an admin alert?
      }
      // Put this backend in the hash so that we will be able to find it if it
@@ -711,6 +726,64 @@
        unacceptableReason.append(getMessage(msgID, String.valueOf(backendDN)));
        return false;
      }
      // See if the backend is registered with the server.  If it is, then
      // see what's changed and whether those changes are acceptable.
      Backend backend = registeredBackends.get(configEntryDN);
      if (backend != null)
      {
        LinkedHashSet<DN> removedDNs = new LinkedHashSet<DN>();
        for (DN dn : backend.getBaseDNs())
        {
          removedDNs.add(dn);
        }
        LinkedHashSet<DN> addedDNs = new LinkedHashSet<DN>();
        for (DN dn : baseDNAttr.pendingValues())
        {
          addedDNs.add(dn);
        }
        Iterator<DN> iterator = removedDNs.iterator();
        while (iterator.hasNext())
        {
          DN dn = iterator.next();
          if (addedDNs.remove(dn))
          {
            iterator.remove();
          }
        }
        for (DN dn : addedDNs)
        {
          try
          {
            DirectoryServer.registerBaseDN(dn, backend, false, true);
          }
          catch (DirectoryException de)
          {
            assert debugException(CLASS_NAME, "configChangeIsAcceptable", de);
            unacceptableReason.append(de.getMessage());
            return false;
          }
        }
        for (DN dn : removedDNs)
        {
          try
          {
            DirectoryServer.deregisterBaseDN(dn, true);
          }
          catch (DirectoryException de)
          {
            assert debugException(CLASS_NAME, "configChangeIsAcceptable", de);
            unacceptableReason.append(de.getMessage());
            return false;
          }
        }
      }
    }
    catch (Exception e)
    {
@@ -1266,7 +1339,28 @@
      }
      // Register the backend with the server.
      try
      {
      DirectoryServer.registerBackend(backend);
      }
      catch (Exception e)
      {
        assert debugException(CLASS_NAME, "applyConfigurationChange", e);
        msgID = MSGID_CONFIG_BACKEND_CANNOT_REGISTER_BACKEND;
        String message = getMessage(msgID, backendID,
                                    stackTraceToSingleLineString(e));
        resultCode = DirectoryServer.getServerErrorResultCode();
        messages.add(message);
        logError(ErrorLogCategory.CONFIGURATION, ErrorLogSeverity.SEVERE_ERROR,
                 message, msgID);
        // FIXME -- Do we need to send an admin alert?
        return new ConfigChangeResult(resultCode, adminActionRequired,
                                      messages);
      }
      registeredBackends.put(backendDN, backend);
@@ -1443,6 +1537,7 @@
    // See if the entry contains an attribute that specifies the set of base DNs
    // for the backend.  If it does not, then skip it.
    List<DN> baseDNs = null;
    msgID = MSGID_CONFIG_BACKEND_ATTR_DESCRIPTION_BASE_DNS;
    DNConfigAttribute baseDNStub =
         new DNConfigAttribute(ATTR_BACKEND_BASE_DN, getMessage(msgID), true,
@@ -1458,6 +1553,10 @@
        unacceptableReason.append(getMessage(msgID, String.valueOf(backendDN)));
        return false;
      }
      else
      {
        baseDNs = baseDNAttr.pendingValues();
      }
    }
    catch (Exception e)
    {
@@ -1554,6 +1653,25 @@
    }
    // Make sure that all of the base DNs are acceptable for use in the server.
    for (DN baseDN : baseDNs)
    {
      try
      {
        DirectoryServer.registerBaseDN(baseDN, backend, false, true);
      }
      catch (DirectoryException de)
      {
        unacceptableReason.append(de.getMessage());
        return false;
      }
      catch (Exception e)
      {
        unacceptableReason.append(stackTraceToSingleLineString(e));
        return false;
      }
    }
    // If we've gotten to this point, then it is acceptable as far as we are
    // concerned.  If it is unacceptable according to the configuration for that
@@ -1960,7 +2078,29 @@
    // At this point, the backend should be online.  Add it as one of the
    // registered backends for this backend config manager.
    try
    {
    DirectoryServer.registerBackend(backend);
    }
    catch (Exception e)
    {
      assert debugException(CLASS_NAME, "applyConfigurationAdd", e);
      msgID = MSGID_CONFIG_BACKEND_CANNOT_REGISTER_BACKEND;
      String message = getMessage(msgID, backendID,
                                  stackTraceToSingleLineString(e));
      resultCode = DirectoryServer.getServerErrorResultCode();
      messages.add(message);
      logError(ErrorLogCategory.CONFIGURATION, ErrorLogSeverity.SEVERE_ERROR,
               message, msgID);
      // FIXME -- Do we need to send an admin alert?
      return new ConfigChangeResult(resultCode, adminActionRequired,
                                    messages);
    }
    registeredBackends.put(backendDN, backend);
    return new ConfigChangeResult(resultCode, adminActionRequired, messages);
  }
@@ -1995,7 +2135,7 @@
    // do know about it, then that means that it is enabled and we will not
    // allow removing a backend that is enabled.
    Backend backend = registeredBackends.get(backendDN);
    if (backendDN == null)
    if (backend == null)
    {
      return true;
    }
@@ -2041,7 +2181,7 @@
    // See if this backend config manager has a backend registered with the
    // provided DN.  If not, then we don't care if the entry is deleted.
    Backend backend = registeredBackends.get(backendDN);
    if (backendDN == null)
    if (backend == null)
    {
      return new ConfigChangeResult(resultCode, adminActionRequired,
                                    messages);
@@ -2053,6 +2193,19 @@
    Backend[] subBackends = backend.getSubordinateBackends();
    if ((subBackends == null) || (subBackends.length == 0))
    {
      registeredBackends.remove(backendDN);
      try
      {
        backend.finalizeBackend();
      }
      catch (Exception e)
      {
        assert debugException(CLASS_NAME, "applyConfigurationDelete", e);
      }
      DirectoryServer.deregisterBackend(backend);
      return new ConfigChangeResult(resultCode, adminActionRequired,
                                    messages);
    }
opends/src/server/org/opends/server/core/DirectoryServer.java
@@ -34,8 +34,11 @@
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -161,6 +164,7 @@
import static org.opends.server.util.DynamicConstants.*;
import static org.opends.server.util.ServerConstants.*;
import static org.opends.server.util.StaticUtils.*;
import static org.opends.server.util.Validator.*;
@@ -398,19 +402,6 @@
  // The key manager provider configuration manager for the Directory Server.
  private KeyManagerProviderConfigManager keyManagerProviderConfigManager;
  // The set of "private" suffixes that will be used to provide server-generated
  // data (e.g., monitor information, schema, etc.) to clients but will not be
  // searchable by default.
  private LinkedHashMap<DN,Backend> privateSuffixes;
  // The set of backends that have been registered with the server (mapped
  // between the normalized suffix and the backend).
  private LinkedHashMap<DN,Backend> suffixes;
  // The set of backends that have been registered with the server (mapped
  // between their backend ID and the backend).
  private LinkedHashMap<String,Backend> backends;
  // The set of connections that are currently established.
  private LinkedHashSet<ClientConnection> establishedConnections;
@@ -498,6 +489,18 @@
  // The thread group for all threads associated with the Directory Server.
  private ThreadGroup directoryThreadGroup;
  // The set of base DNs registered with the server.
  private TreeMap<DN,Backend> baseDNs;
  // The set of private naming contexts registered with the server.
  private TreeMap<DN,Backend> privateNamingContexts;
  // The set of public naming contexts registered with the server.
  private TreeMap<DN,Backend> publicNamingContexts;
  // The set of backends registered with the server.
  private TreeMap<String,Backend> backends;
  // The set of supported controls registered with the Directory Server.
  private TreeSet<String> supportedControls;
@@ -606,9 +609,10 @@
    directoryServer.defaultPasswordPolicy = null;
    directoryServer.monitorProviders =
         new ConcurrentHashMap<String,MonitorProvider>();
    directoryServer.privateSuffixes = new LinkedHashMap<DN,Backend>();
    directoryServer.suffixes = new LinkedHashMap<DN,Backend>();
    directoryServer.backends = new LinkedHashMap<String,Backend>();
    directoryServer.backends = new TreeMap<String,Backend>();
    directoryServer.baseDNs = new TreeMap<DN,Backend>();
    directoryServer.publicNamingContexts = new TreeMap<DN,Backend>();
    directoryServer.privateNamingContexts = new TreeMap<DN,Backend>();
    directoryServer.changeNotificationListeners =
         new CopyOnWriteArrayList<ChangeNotificationListener>();
    directoryServer.persistentSearches =
@@ -5210,12 +5214,12 @@
  /**
   * Retrieves the set of backends that have been registered with the Directory
   * Server.
   * Server, as a mapping between the backend ID and the corresponding backend.
   *
   * @return  The set of backends that have been registered with the Directory
   *          Server.
   */
  public static LinkedHashMap<String,Backend> getBackends()
  public static Map<String,Backend> getBackends()
  {
    assert debugEnter(CLASS_NAME, "getBackends");
@@ -5229,7 +5233,7 @@
   *
   * @param  backendID  The backend ID of the backend to retrieve.
   *
   * @return  The backend with the specified backend ID, or <CODE>null</CODE> if
   * @return  The backend with the specified backend ID, or {@code null} if
   *          there is none.
   */
  public static Backend getBackend(String backendID)
@@ -5247,8 +5251,8 @@
   *
   * @param  backendID  The backend ID for which to make the determination.
   *
   * @return  <CODE>true</CODE> if the Directory Server has a backend with the
   *          specified backend ID, or <CODE>false</CODE> if not.
   * @return  {@code true} if the Directory Server has a backend with the
   *          specified backend ID, or {@code false} if not.
   */
  public static boolean hasBackend(String backendID)
  {
@@ -5264,13 +5268,40 @@
   * will not register the set of configured suffixes with the server, as that
   * must be done by the backend itself.
   *
   * @param  backend  The backend to register with the server.
   * @param  backend  The backend to register with the server.  Neither the
   *                  backend nor its backend ID may be null.
   *
   * @throws  DirectoryException  If the backend ID for the provided backend
   *                              conflicts with the backend ID of an existing
   *                              backend.
   */
  public static void registerBackend(Backend backend)
         throws DirectoryException
  {
    assert debugEnter(CLASS_NAME, "registerBackend", String.valueOf(backend));
    directoryServer.backends.put(backend.getBackendID(), backend);
    ensureNotNull(backend);
    String backendID = backend.getBackendID();
    ensureNotNull(backendID);
    synchronized (directoryServer)
    {
      TreeMap<String,Backend> newBackends =
           new TreeMap<String,Backend>(directoryServer.backends);
      if (newBackends.containsKey(backendID))
      {
        int    msgID   = MSGID_REGISTER_BACKEND_ALREADY_EXISTS;
        String message = getMessage(msgID, backendID);
        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
                                     msgID);
      }
      else
      {
        newBackends.put(backendID, backend);
        directoryServer.backends = newBackends;
      }
    }
  }
@@ -5280,366 +5311,521 @@
   * will not deregister the set of configured suffixes with the server, as that
   * must be done by the backend itself.
   *
   * @param  backend  the backend to deregister with the server.
   * @param  backend  The backend to deregister with the server.  It must not be
   *                  {@code null}.
   */
  public static void deregisterBackend(Backend backend)
  {
    assert debugEnter(CLASS_NAME, "deregisterBackend", String.valueOf(backend));
    directoryServer.backends.remove(backend.getBackendID());
    ensureNotNull(backend);
    synchronized (directoryServer)
    {
      TreeMap<String,Backend> newBackends =
           new TreeMap<String,Backend>(directoryServer.backends);
      newBackends.remove(backend.getBackendID());
      directoryServer.backends = newBackends;
    }
  }
  /**
   * Retrieves the set of suffixes that have been registered with the Directory
   * Server.
   * Retrieves the entire set of base DNs registered with the Directory Server,
   * mapped from the base DN to the backend responsible for that base DN.  The
   * same backend may be present multiple times, mapped from different base DNs.
   *
   * @return  The set of suffixes that have been registered with the Directory
   *          Server.
   * @return  The entire set of base DNs registered with the Directory Server.
   */
  public static LinkedHashMap<DN,Backend> getSuffixes()
  public static Map<DN,Backend> getBaseDNs()
  {
    assert debugEnter(CLASS_NAME, "getSuffixes");
    assert debugEnter(CLASS_NAME, "getBaseDNs");
    return directoryServer.suffixes;
    return directoryServer.baseDNs;
  }
  /**
   * Retrieves the set of "private" suffixes that have been registered with the
   * server that will provide server-specific data to clients (e.g., monitor
   * data, schema, etc.) but should not be considered for normal operations
   * that may target all "user" suffixes.
   * Retrieves the backend with the specified base DN.
   *
   * @return  The set of "private" suffixes that have been registered with the
   *          server.
   * @param  baseDN  The DN that is registered as one of the base DNs for the
   *                 backend to retrieve.
   *
   * @return  The backend with the specified base DN, or {@code null} if there
   *          is no backend registered with the specified base DN.
   */
  public static LinkedHashMap<DN,Backend> getPrivateSuffixes()
  public static Backend getBackendWithBaseDN(DN baseDN)
  {
    assert debugEnter(CLASS_NAME, "getPrivateSuffixes");
    assert debugEnter(CLASS_NAME, "getBackendWithBaseDN",
                      String.valueOf(baseDN));
    return directoryServer.privateSuffixes;
    return directoryServer.baseDNs.get(baseDN);
  }
  /**
   * Indicates whether the provided DN is one of the suffixes defined in the
   * Directory Server.
   * Retrieves the backend that should be used to handle operations on the
   * specified entry.
   *
   * @param  dn  The DN for which to make the determination.
   * @param  entryDN  The DN of the entry for which to retrieve the
   *                  corresponding backend.
   *
   * @return  <CODE>true</CODE> if the provided DN is one of the suffixes
   *          defined in the Directory Server, or <CODE>false</CODE> if not.
   * @return  The backend that should be used to handle operations on the
   *          specified entry, or {@code null} if no appropriate backend is
   *          registered with the server.
   */
  public static boolean isSuffix(DN dn)
  public static Backend getBackend(DN entryDN)
  {
    assert debugEnter(CLASS_NAME, "isSuffix", String.valueOf(dn));
    assert debugEnter(CLASS_NAME, "getBackendForEntry",
                      String.valueOf(entryDN));
    return (directoryServer.suffixes.containsKey(dn) ||
            directoryServer.privateSuffixes.containsKey(dn));
  }
  /**
   * Retrieves the backend that should be used to handle operations for the
   * provided entry DN.
   *
   * @param  dn  The DN of the entry for which to retrieve the appropriate
   *             backend.
   *
   * @return  The backend that should be used to handle the provided DN, or
   *          <CODE>null</CODE> if there is no backend for the provided DN.
   */
  public static Backend getBackend(DN dn)
  {
    assert debugEnter(CLASS_NAME, "getBackend", String.valueOf(dn));
    if (dn.isNullDN())
    if (entryDN.isNullDN())
    {
      return directoryServer.rootDSEBackend;
    }
    Backend backend = directoryServer.suffixes.get(dn);
    if (backend == null)
    TreeMap<DN,Backend> baseDNs = directoryServer.baseDNs;
    Backend b = baseDNs.get(entryDN);
    while (b == null)
    {
      backend = directoryServer.privateSuffixes.get(dn);
      entryDN = entryDN.getParent();
      if (entryDN == null)
      {
        return null;
    }
    while (backend == null)
    {
      dn = dn.getParentDNInSuffix();
      if (dn == null)
      {
        break;
      b = baseDNs.get(entryDN);
      }
      backend = directoryServer.suffixes.get(dn);
      if (backend == null)
      {
        backend = directoryServer.privateSuffixes.get(dn);
      }
    }
    return backend;
    return b;
  }
  /**
   * Registers the specified suffix to be handled by the provided backend.
   * Registers the provided base DN with the server.
   *
   * @param  suffixDN  The base DN for this suffix.
   * @param  backend   The backend to handle operations for the provided base.
   * @param  baseDN     The base DN to register with the server.  It must not be
   *                    {@code null}.
   * @param  backend    The backend responsible for the provided base DN.  It
   *                    must not be {@code null}.
   * @param  isPrivate  Indicates whether the base DN should be considered a
   *                    private base DN.  If the provided base DN is a naming
   *                    context, then this controls whether it is public or
   *                    private.
   * @param  testOnly   Indicates whether to only test whether the new base DN
   *                    registration would be successful without actually
   *                    applying any changes.
   *
   * @throws  ConfigException  If the specified suffix is already registered
   *                           with the Directory Server.
   * @throws  DirectoryException  If a problem occurs while attempting to
   *                              register the provided base DN.
   */
  public static void registerSuffix(DN suffixDN, Backend backend)
         throws ConfigException
  public static void registerBaseDN(DN baseDN, Backend backend,
                                    boolean isPrivate, boolean testOnly)
         throws DirectoryException
  {
    assert debugEnter(CLASS_NAME, "registerSuffix", String.valueOf(suffixDN),
                      String.valueOf(backend));
    assert debugEnter(CLASS_NAME, "registerBaseDN", String.valueOf(baseDN),
                      String.valueOf(backend), String.valueOf(isPrivate),
                      String.valueOf(testOnly));
    backend.setPrivateBackend(false);
    ensureNotNull(baseDN, backend);
    synchronized (directoryServer.suffixes)
    synchronized (directoryServer)
    {
      // Check to see if this suffix is already in use.  It may be a suffix, or
      // it may be a sub-suffix of an existing suffix.
      Backend b = directoryServer.suffixes.get(suffixDN);
      if (b != null)
      {
        int    msgID   = MSGID_CANNOT_REGISTER_DUPLICATE_SUFFIX;
        String message = getMessage(msgID, String.valueOf(suffixDN),
                                    b.getClass().getName());
      TreeMap<DN,Backend> newBaseDNs =
           new TreeMap<DN,Backend>(directoryServer.baseDNs);
      TreeMap<DN,Backend> newPublicNamingContexts =
           new TreeMap<DN,Backend>(directoryServer.publicNamingContexts);
      TreeMap<DN,Backend> newPrivateNamingContexts =
           new TreeMap<DN,Backend>(directoryServer.privateNamingContexts);
        throw new ConfigException(msgID, message);
      // Check to see if the base DN is already registered with the server.
      Backend existingBackend = newBaseDNs.get(baseDN);
      if (existingBackend != null)
      {
        int    msgID   = MSGID_REGISTER_BASEDN_ALREADY_EXISTS;
        String message = getMessage(msgID, String.valueOf(baseDN),
                                    backend.getBackendID(),
                                    existingBackend.getBackendID());
        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
                                     msgID);
      }
      boolean found = false;
      DN parentDN = suffixDN.getParentDNInSuffix();
      // Check to see if the backend is already registered with the server for
      // any other base DN(s).  The new base DN must not have any hierarchical
      // relationship with any other base Dns for the same backend.
      LinkedList<DN> otherBaseDNs = new LinkedList<DN>();
      for (DN dn : newBaseDNs.keySet())
      {
        Backend b = newBaseDNs.get(dn);
        if (b.equals(backend))
        {
          otherBaseDNs.add(dn);
          if (baseDN.isAncestorOf(dn) || baseDN.isDescendantOf(dn))
          {
            int    msgID   = MSGID_REGISTER_BASEDN_HIERARCHY_CONFLICT;
            String message = getMessage(msgID, String.valueOf(baseDN),
                                        backend.getBackendID(),
                                        String.valueOf(dn));
            throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
                                         message, msgID);
          }
        }
      }
      // Check to see if the new base DN is subordinate to any other base DN
      // already defined.  If it is, then any other base DN(s) for the same
      // backend must also be subordinate to the same base DN.
      Backend superiorBackend = null;
      DN      superiorBaseDN  = null;
      DN      parentDN        = baseDN.getParent();
      while (parentDN != null)
      {
        b = directoryServer.suffixes.get(suffixDN);
        if (b != null)
        if (newBaseDNs.containsKey(parentDN))
        {
          if (b.hasSubSuffix(suffixDN))
          {
            int    msgID   = MSGID_CANNOT_REGISTER_DUPLICATE_SUBSUFFIX;
            String message = getMessage(msgID, String.valueOf(suffixDN),
                                        String.valueOf(parentDN));
          superiorBaseDN  = parentDN;
          superiorBackend = newBaseDNs.get(parentDN);
            throw new ConfigException(msgID, message);
          }
          else
          for (DN dn : otherBaseDNs)
          {
            b.addSubordinateBackend(backend);
            found = true;
            if (! dn.isDescendantOf(superiorBaseDN))
            {
              int    msgID   = MSGID_REGISTER_BASEDN_DIFFERENT_PARENT_BASES;
              String message = getMessage(msgID, String.valueOf(baseDN),
                                          backend.getBackendID(),
                                          String.valueOf(dn));
              throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
                                           message, msgID);
            }
          }
            break;
          }
        parentDN = parentDN.getParent();
        }
        parentDN = parentDN.getParentDNInSuffix();
      }
      if (! found)
      if (superiorBackend == null)
      {
        // If we've gotten here, then it is not in use.  Register it.
        directoryServer.suffixes.put(suffixDN, backend);
      }
      // See if there are any supported controls or features that we want to
      // advertise.
      Set<String> supportedControls = backend.getSupportedControls();
      if (supportedControls != null)
        if (backend.getParentBackend() != null)
      {
        for (String controlOID : supportedControls)
          int    msgID   = MSGID_REGISTER_BASEDN_NEW_BASE_NOT_SUBORDINATE;
          String message = getMessage(msgID, String.valueOf(baseDN),
                                backend.getBackendID(),
                                backend.getParentBackend().getBackendID());
          throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM,
                                       message, msgID);
        }
      }
      // Check to see if the new base DN should be the superior base DN for any
      // other base DN(s) already defined.
      LinkedList<Backend> subordinateBackends = new LinkedList<Backend>();
      LinkedList<DN>      subordinateBaseDNs  = new LinkedList<DN>();
      for (DN dn : newBaseDNs.keySet())
        {
          registerSupportedControl(controlOID);
        }
      }
      Set<String> supportedFeatures = backend.getSupportedFeatures();
      if (supportedFeatures != null)
        Backend b = newBaseDNs.get(dn);
        parentDN = dn.getParent();
        while (parentDN != null)
      {
        for (String featureOID : supportedFeatures)
          if (parentDN.equals(baseDN))
        {
          registerSupportedFeature(featureOID);
            subordinateBaseDNs.add(dn);
            subordinateBackends.add(b);
            break;
        }
      }
    }
  }
  /**
   * Registers the specified private suffix to be handled by the provided
   * backend.
   *
   * @param  suffixDN  The base DN for this suffix.
   * @param  backend   The backend to handle operations for the provided base.
   *
   * @throws  ConfigException  If the specified suffix is already registered
   *                           with the Directory Server.
   */
  public static void registerPrivateSuffix(DN suffixDN, Backend backend)
         throws ConfigException
          else if (newBaseDNs.containsKey(parentDN))
  {
    assert debugEnter(CLASS_NAME, "registerPrivateSuffix",
                      String.valueOf(suffixDN), String.valueOf(backend));
            break;
          }
          parentDN = parentDN.getParent();
        }
      }
      // If we've gotten here, then the new base DN is acceptable.  If we should
      // actually apply the changes then do so now.
      if (! testOnly)
      {
        // Check to see if any of the registered backends already contain an
        // entry with the DN specified as the base DN.  This could happen if
        // we're creating a new subordinate backend in an existing directory
        // (e.g., moving the "ou=People,dc=example,dc=com" branch to its own
        // backend when that data already exists under the "dc=example,dc=com"
        // backend).  This condition shouldn't prevent the new base DN from
        // being registered, but it's definitely important enough that we let
        // the administrator know about it and remind them that the existing
        // backend will need to be reinitialized.
        if (superiorBackend != null)
        {
          if (superiorBackend.entryExists(baseDN))
          {
            int    msgID   = MSGID_REGISTER_BASEDN_ENTRIES_IN_MULTIPLE_BACKENDS;
            String message = getMessage(msgID, superiorBackend.getBackendID(),
                                        String.valueOf(baseDN),
                                        backend.getBackendID());
            logError(ErrorLogCategory.CONFIGURATION,
                     ErrorLogSeverity.SEVERE_WARNING, message, msgID);
          }
        }
        newBaseDNs.put(baseDN, backend);
        if (superiorBackend == null)
        {
          if (isPrivate)
          {
    backend.setPrivateBackend(true);
    synchronized (directoryServer.privateSuffixes)
    {
      // Check to see if this suffix is already in use for a "user" suffix.  It
      // may be a suffix, or it may be a sub-suffix of an  existing suffix.
      synchronized (directoryServer.suffixes)
      {
        Backend b = directoryServer.suffixes.get(suffixDN);
        if (b != null)
        {
          int    msgID   = MSGID_CANNOT_REGISTER_DUPLICATE_SUFFIX;
          String message = getMessage(msgID, String.valueOf(suffixDN),
                                      b.getClass().getName());
          throw new ConfigException(msgID, message);
        }
        DN parentDN = suffixDN.getParentDNInSuffix();
        while (parentDN != null)
        {
          b = directoryServer.suffixes.get(suffixDN);
          if (b != null)
          {
            int msgID = MSGID_CANNOT_REGISTER_PRIVATE_SUFFIX_BELOW_USER_PARENT;
            String message = getMessage(msgID, String.valueOf(suffixDN),
                                        String.valueOf(parentDN));
            throw new ConfigException(msgID, message);
          }
          parentDN = suffixDN.getParentDNInSuffix();
        }
      }
      // Check to see if this suffix is already registered as a private suffix
      // or sub-suffix.
      Backend b = directoryServer.privateSuffixes.get(suffixDN);
      if (b != null)
      {
        int    msgID   = MSGID_CANNOT_REGISTER_DUPLICATE_SUFFIX;
        String message = getMessage(msgID, String.valueOf(suffixDN),
                                    b.getClass().getName());
        throw new ConfigException(msgID, message);
      }
      DN parentDN = suffixDN.getParentDNInSuffix();
      while (parentDN != null)
      {
        b = directoryServer.privateSuffixes.get(suffixDN);
        if (b != null)
        {
          if (b.hasSubSuffix(suffixDN))
          {
            int    msgID   = MSGID_CANNOT_REGISTER_DUPLICATE_SUBSUFFIX;
            String message = getMessage(msgID, String.valueOf(suffixDN),
                                        String.valueOf(parentDN));
            throw new ConfigException(msgID, message);
            newPrivateNamingContexts.put(baseDN, backend);
          }
          else
          {
            b.addSubordinateBackend(backend);
            return;
            backend.setPrivateBackend(false);
            newPublicNamingContexts.put(baseDN, backend);
          }
        }
        parentDN = suffixDN.getParentDNInSuffix();
        else if (otherBaseDNs.isEmpty())
        {
          backend.setParentBackend(superiorBackend);
          superiorBackend.addSubordinateBackend(backend);
      }
        for (Backend b : subordinateBackends)
        {
          Backend oldParentBackend = b.getParentBackend();
          if (oldParentBackend != null)
          {
            oldParentBackend.removeSubordinateBackend(b);
          }
      // If we've gotten here, then it is not in use.  Register it.
      directoryServer.privateSuffixes.put(suffixDN, backend);
          b.setParentBackend(backend);
          backend.addSubordinateBackend(b);
        }
        for (DN dn : subordinateBaseDNs)
        {
          newPublicNamingContexts.remove(dn);
          newPrivateNamingContexts.remove(dn);
        }
        directoryServer.baseDNs               = newBaseDNs;
        directoryServer.publicNamingContexts  = newPublicNamingContexts;
        directoryServer.privateNamingContexts = newPrivateNamingContexts;
      }
    }
  }
  /**
   * Deregisters the specified suffix with the Directory Server.  This should
   * work regardless of whether the specified DN is a normal suffix or a private
   * suffix.
   * Deregisters the provided base DN with the server.
   *
   * @param  suffixDN  The suffix DN to deregister with the server.
   * @param  baseDN     The base DN to deregister with the server.  It must not
   *                    be {@code null}.
   * @param  testOnly   Indicates whether to only test whether the new base DN
   *                    registration would be successful without actually
   *                    applying any changes.
   *
   * @throws  ConfigException  If a problem occurs while attempting to
   *                           deregister the specified suffix.
   * @throws  DirectoryException  If a problem occurs while attempting to
   *                              deregister the provided base DN.
   */
  public static void deregisterSuffix(DN suffixDN)
         throws ConfigException
  public static void deregisterBaseDN(DN baseDN, boolean testOnly)
         throws DirectoryException
  {
    assert debugEnter(CLASS_NAME, "deregisterSuffix", String.valueOf(suffixDN));
    assert debugEnter(CLASS_NAME, "deregisterBaseDN", String.valueOf(baseDN));
    ensureNotNull(baseDN);
    synchronized (directoryServer)
    {
      TreeMap<DN,Backend> newBaseDNs =
           new TreeMap<DN,Backend>(directoryServer.baseDNs);
      TreeMap<DN,Backend> newPublicNamingContexts =
           new TreeMap<DN,Backend>(directoryServer.publicNamingContexts);
      TreeMap<DN,Backend> newPrivateNamingContexts =
           new TreeMap<DN,Backend>(directoryServer.privateNamingContexts);
    // First, check to see if it is a "user" suffix or sub-suffix.
    synchronized (directoryServer.suffixes)
      // Make sure that the Directory Server actually contains a backend with
      // the specified base DN.
      Backend backend = newBaseDNs.get(baseDN);
      if (backend == null)
    {
      Backend b = directoryServer.suffixes.remove(suffixDN);
      if (b != null)
      {
        return;
        int    msgID   = MSGID_DEREGISTER_BASEDN_NOT_REGISTERED;
        String message = getMessage(msgID, String.valueOf(baseDN));
        throw new DirectoryException(ResultCode.UNWILLING_TO_PERFORM, message,
                                     msgID);
      }
      DN parentDN = suffixDN.getParentDNInSuffix();
      while (parentDN != null)
      {
        b = directoryServer.suffixes.get(parentDN);
        if (b != null)
        {
          if (b.hasSubSuffix(suffixDN))
          {
            b.removeSubSuffix(suffixDN, parentDN);
          }
          return;
      // Check to see if the backend has a parent backend, and whether it has
      // any subordinates with base DNs that are below the base DN to remove.
      Backend             superiorBackend     = backend.getParentBackend();
      LinkedList<Backend> subordinateBackends = new LinkedList<Backend>();
      if (backend.getSubordinateBackends() != null)
      {
        for (Backend b : backend.getSubordinateBackends())
        {
          for (DN dn : b.getBaseDNs())
          {
            if (dn.isDescendantOf(baseDN))
            {
              subordinateBackends.add(b);
              break;
            }
        }
      }
    }
    // Check the set of private suffixes and sub-suffixes.
    synchronized (directoryServer.privateSuffixes)
      // See if there are any other base DNs registered within the same backend.
      LinkedList<DN> otherBaseDNs = new LinkedList<DN>();
      for (DN dn : newBaseDNs.keySet())
    {
      Backend b = directoryServer.privateSuffixes.remove(suffixDN);
      if (b != null)
        if (dn.equals(baseDN))
      {
        return;
          continue;
      }
      DN parentDN = suffixDN.getParentDNInSuffix();
      while (parentDN != null)
        Backend b = newBaseDNs.get(dn);
        if (backend.equals(b))
      {
        b = directoryServer.privateSuffixes.get(parentDN);
        if (b != null)
        {
          if (b.hasSubSuffix(suffixDN))
          {
            b.removeSubSuffix(suffixDN, parentDN);
          otherBaseDNs.add(dn);
        }
          }
          return;
      // If we've gotten here, then it's OK to make the changes.
      if (! testOnly)
      {
        // Get rid of the references to this base DN in the mapping tree
        // information.
        newBaseDNs.remove(baseDN);
        newPublicNamingContexts.remove(baseDN);
        newPrivateNamingContexts.remove(baseDN);
        if (superiorBackend == null)
        {
          // If there were any subordinate backends, then all of their base DNs
          // will now be promoted to naming contexts.
          for (Backend b : subordinateBackends)
          {
            b.setParentBackend(null);
            backend.removeSubordinateBackend(b);
            for (DN dn : b.getBaseDNs())
            {
              if (b.isPrivateBackend())
              {
                newPrivateNamingContexts.put(dn, b);
              }
              else
              {
                newPublicNamingContexts.put(dn, b);
        }
      }
    }
  }
        else
        {
          // If there are no other base DNs for the associated backend, then
          // remove this backend as a subordinate of the parent backend.
          if (otherBaseDNs.isEmpty())
          {
            superiorBackend.removeSubordinateBackend(backend);
          }
          // If there are any subordinate backends, then they need to be made
          // subordinate to the parent backend.  Also, we should log a warning
          // message indicating that there may be inconsistent search results
          // because some of the structural entries will be missing.
          if (! subordinateBackends.isEmpty())
          {
            int    msgID   = MSGID_DEREGISTER_BASEDN_MISSING_HIERARCHY;
            String message = getMessage(msgID, String.valueOf(baseDN),
                                        backend.getBackendID());
            logError(ErrorLogCategory.CONFIGURATION,
                     ErrorLogSeverity.SEVERE_WARNING, message, msgID);
            for (Backend b : subordinateBackends)
            {
              backend.removeSubordinateBackend(b);
              superiorBackend.addSubordinateBackend(b);
              b.setParentBackend(superiorBackend);
            }
          }
        }
        directoryServer.baseDNs               = newBaseDNs;
        directoryServer.publicNamingContexts  = newPublicNamingContexts;
        directoryServer.privateNamingContexts = newPrivateNamingContexts;
      }
    }
  }
  /**
   * Retrieves the set of public naming contexts defined in the Directory
   * Server, mapped from the naming context DN to the corresponding backend.
   *
   * @return  The set of public naming contexts defined in the Directory Server.
   */
  public static Map<DN,Backend> getPublicNamingContexts()
  {
    assert debugEnter(CLASS_NAME, "getPublicNamingContexts");
    return directoryServer.publicNamingContexts;
  }
  /**
   * Retrieves the set of private naming contexts defined in the Directory
   * Server, mapped from the naming context DN to the corresponding backend.
   *
   * @return  The set of private naming contexts defined in the Directory
   *          Server.
   */
  public static Map<DN,Backend> getPrivateNamingContexts()
  {
    assert debugEnter(CLASS_NAME, "getPrivateNamingContexts");
    return directoryServer.privateNamingContexts;
  }
  /**
   * Indicates whether the specified DN is one of the Directory Server naming
   * contexts.
   *
   * @param  dn  The DN for which to make the determination.
   *
   * @return  {@code true} if the specified DN is a naming context for the
   *          Directory Server, or {@code false} if it is not.
   */
  public static boolean isNamingContext(DN dn)
  {
    assert debugEnter(CLASS_NAME, "isNamingContext");
    return (directoryServer.publicNamingContexts.containsKey(dn) ||
            directoryServer.privateNamingContexts.containsKey(dn));
  }
opends/src/server/org/opends/server/extensions/ConfigFileHandler.java
@@ -598,7 +598,8 @@
    try
    {
      DirectoryServer.registerPrivateSuffix(configRootEntry.getDN(), this);
      DirectoryServer.registerBaseDN(configRootEntry.getDN(), this, true,
                                     false);
    }
    catch (Exception e)
    {
@@ -729,7 +730,7 @@
    try
    {
      DirectoryServer.deregisterSuffix(configRootEntry.getDN());
      DirectoryServer.deregisterBaseDN(configRootEntry.getDN(), false);
    }
    catch (Exception e)
    {
@@ -1796,23 +1797,6 @@
  /**
   * Indicates whether this backend supports the specified control.
   *
   * @param  controlOID  The OID of the control for which to make the
   *                     determination.
   *
   * @return  <CODE>true</CODE> if this backend does support the requested
   *          control, or <CODE>false</CODE>
   */
  public boolean supportsControl(String controlOID)
  {
    // NYI
    return false;
  }
  /**
   * Retrieves the OIDs of the features that may be supported by this backend.
   *
   * @return  The OIDs of the features that may be supported by this backend.
@@ -1826,23 +1810,6 @@
  /**
   * Indicates whether this backend supports the specified feature.
   *
   * @param  featureOID  The OID of the feature for which to make the
   *                     determination.
   *
   * @return  <CODE>true</CODE> if this backend does support the requested
   *          feature, or <CODE>false</CODE>
   */
  public boolean supportsFeature(String featureOID)
  {
    // NYI
    return false;
  }
  /**
   * Indicates whether this backend provides a mechanism to export the data it
   * contains to an LDIF file.
   *
opends/src/server/org/opends/server/extensions/DigestMD5SASLMechanismHandler.java
@@ -35,9 +35,9 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@@ -309,7 +309,7 @@
      // use it.  Otherwise, add a realm for each suffix defined in the server.
      if (realm == null)
      {
        LinkedHashMap<DN,Backend> suffixes = DirectoryServer.getSuffixes();
        Map<DN,Backend> suffixes = DirectoryServer.getPublicNamingContexts();
        if (! suffixes.isEmpty())
        {
          Iterator<DN> iterator = suffixes.keySet().iterator();
opends/src/server/org/opends/server/messages/BackendMessages.java
@@ -2233,6 +2233,17 @@
  /**
   * The message ID for the message that will be used if an error occurs while
   * attempting to register a base DN for use in the server.  This takes two
   * arguments, which are the base DN and a string representation of the
   * exception that was caught.
   */
  public static final int MSGID_BACKEND_CANNOT_REGISTER_BASEDN =
       CATEGORY_MASK_BACKEND | SEVERITY_MASK_FATAL_ERROR | 210;
  /**
   * Associates a set of generic messages with the message IDs defined in this
   * class.
   */
@@ -2252,6 +2263,9 @@
                    "the entry is already locked by a long-running operation " +
                    "or that the entry has previously been locked but was " +
                    "not properly unlocked.");
    registerMessage(MSGID_BACKEND_CANNOT_REGISTER_BASEDN,
                    "An error occurred while attempting to register base DN " +
                    "in the Directory Server:  %s.");
    registerMessage(MSGID_ROOTDSE_CONFIG_ENTRY_NULL,
opends/src/server/org/opends/server/messages/ConfigMessages.java
@@ -6153,6 +6153,17 @@
  /**
   * The message ID for the message that will be used if an error occurs while
   * attempting to register a backend with the Directory Server.  This takes two
   * arguments, which are the backend ID and a string representation of the
   * exception that was caught.
   */
  public static final int MSGID_CONFIG_BACKEND_CANNOT_REGISTER_BACKEND =
       CATEGORY_MASK_CONFIG | SEVERITY_MASK_SEVERE_WARNING | 572;
  /**
   * Associates a set of generic messages with the message IDs defined in this
   * class.
   */
@@ -7020,6 +7031,9 @@
                    "lock for backend %s:  %s.  This may interfere with " +
                    "operations that require exclusive access, including " +
                    "LDIF import and restoring a backup.");
    registerMessage(MSGID_CONFIG_BACKEND_CANNOT_REGISTER_BACKEND,
                    "An error occurred while attempting to register backend " +
                    "%s with the Directory Server:  %s.");
    registerMessage(MSGID_CONFIG_BACKEND_CLASS_NOT_BACKEND,
                    "The class %s specified in configuration entry %s does " +
                    "not contain a valid Directory Server backend " +
opends/src/server/org/opends/server/messages/CoreMessages.java
@@ -5967,6 +5967,98 @@
  /**
   * The message ID for the message that will be used if an attempt is made to
   * register a backend with an ID that is already registered.  This takes a
   * single argument, which is the conflicting backend ID.
   */
  public static final int MSGID_REGISTER_BACKEND_ALREADY_EXISTS =
       CATEGORY_MASK_CORE | SEVERITY_MASK_SEVERE_ERROR | 571;
  /**
   * The message ID for the message that will be used if an attempt is made to
   * register a base DN that is already registered.  This takes three arguments,
   * which are the base DN, the backend ID for the new backend, and the backend
   * ID for the already-registered backend.
   */
  public static final int MSGID_REGISTER_BASEDN_ALREADY_EXISTS =
       CATEGORY_MASK_CORE | SEVERITY_MASK_SEVERE_ERROR | 572;
  /**
   * The message ID for the message that will be used if an attempt is made to
   * register a base DN with a backend that already has another base DN that
   * contains conflicting hierarchy.  This takes three arguments, which are the
   * base DN to be registered, the backend ID, and the already-registered base
   * DN.
   */
  public static final int MSGID_REGISTER_BASEDN_HIERARCHY_CONFLICT =
       CATEGORY_MASK_CORE | SEVERITY_MASK_SEVERE_ERROR | 573;
  /**
   * The message ID for the message that will be used if an attempt is made to
   * register a base DN with a backend that already contains other base DNs
   * that do not share the same parent base DN.  This takes three arguments,
   * which are the base DN to be registered, the backend ID, and the
   * already-registered base DN.
   */
  public static final int MSGID_REGISTER_BASEDN_DIFFERENT_PARENT_BASES =
       CATEGORY_MASK_CORE | SEVERITY_MASK_SEVERE_ERROR | 574;
  /**
   * The message ID for the message that will be used if an attempt is made to
   * register a base DN with a backend that already contains other base DNs
   * that are subordinate to a backend whereas the new base DN is not
   * subordinate to any backend.  This takes three arguments, which are the base
   * DN to be registered, the backend ID, and the backend ID of the parent
   * backend.
   */
  public static final int MSGID_REGISTER_BASEDN_NEW_BASE_NOT_SUBORDINATE =
       CATEGORY_MASK_CORE | SEVERITY_MASK_SEVERE_ERROR | 575;
  /**
   * The message ID for the message that will be used if a newly-registered
   * backend has a base DN for which a corresponding entry already exists in the
   * server.  This takes three arguments, which are the backend ID for the
   * existing backend, the base DN for the new backend, and the backend ID for
   * the new backend.
   */
  public static final int MSGID_REGISTER_BASEDN_ENTRIES_IN_MULTIPLE_BACKENDS =
       CATEGORY_MASK_CORE | SEVERITY_MASK_SEVERE_WARNING | 576;
  /**
   * The message ID for the message that will be used if an attempt is made to
   * deregister a base DN that is not registered.  This takes a single argument,
   * which is the base DN to deregister.
   */
  public static final int MSGID_DEREGISTER_BASEDN_NOT_REGISTERED =
       CATEGORY_MASK_CORE | SEVERITY_MASK_SEVERE_ERROR | 577;
  /**
   * The message ID for the message that will be used if a base DN containing
   * both superior and subordinate backends is deregistered, leaving the
   * possibility for missing entries in the data hierarchy.  This takes two
   * arguments, which are the base DN that has been deregistered and the backend
   * ID of the associated backend.
   */
  public static final int MSGID_DEREGISTER_BASEDN_MISSING_HIERARCHY =
       CATEGORY_MASK_CORE | SEVERITY_MASK_SEVERE_WARNING | 578;
  /**
   * Associates a set of generic messages with the message IDs defined
   * in this class.
   */
@@ -8057,6 +8149,50 @@
                    "The user-specific lookthrough limit value %s contained " +
                    "in user entry %s could not be parsed as an integer.  " +
                    "The default server lookthrough limit will be used.");
    registerMessage(MSGID_REGISTER_BACKEND_ALREADY_EXISTS,
                    "Unable to register backend %s with the Directory Server " +
                    "because another backend with the same backend ID is " +
                    "already registered.");
    registerMessage(MSGID_REGISTER_BASEDN_ALREADY_EXISTS,
                    "Unable to register base DN %s with the Directory Server " +
                    "for backend %s because that base DN is already " +
                    "registered for backend %s.");
    registerMessage(MSGID_REGISTER_BASEDN_HIERARCHY_CONFLICT,
                    "Unable to register base DN %s with the Directory Server " +
                    "for backend %s because that backend already contains " +
                    "another base DN %s that is within the same hierarchical " +
                    "path.");
    registerMessage(MSGID_REGISTER_BASEDN_DIFFERENT_PARENT_BASES,
                    "Unable to register base DN %s with the Directory Server " +
                    "for backend %s because that backend already contains " +
                    "another base DN %s that is not subordinate to the same " +
                    "base DN in the parent backend.");
    registerMessage(MSGID_REGISTER_BASEDN_NEW_BASE_NOT_SUBORDINATE,
                    "Unable to register base DN %s with the Directory Server " +
                    "for backend %s because that backend already contains " +
                    "one or more other base DNs that are subordinate to " +
                    "backend %s but the new base DN is not.");
    registerMessage(MSGID_REGISTER_BASEDN_ENTRIES_IN_MULTIPLE_BACKENDS,
                    "Backend %s already contains entry %s which has just " +
                    "been registered as the base DN for backend %s.  " +
                    "These conflicting entries may cause unexpected or " +
                    "errant search results, and both backends should be " +
                    "reinitialized to ensure that each has the correct " +
                    "content.");
    registerMessage(MSGID_DEREGISTER_BASEDN_NOT_REGISTERED,
                    "Unable to de-register base DN %s with the Directory " +
                    "Server because that base DN is not registered for any " +
                    "active backend.");
    registerMessage(MSGID_DEREGISTER_BASEDN_MISSING_HIERARCHY,
                    "Base DN %s has been deregistered from the Directory " +
                    "Server for backend %s.  This base DN had both superior " +
                    "and subordinate entries in other backends, and there " +
                    "may be inconsistent or unexpected behavior when " +
                    "accessing entries in this portion of the hierarchy " +
                    "because of the missing entries that had been held in " +
                    "the de-registered backend.");
  }
}
opends/src/server/org/opends/server/types/DN.java
@@ -507,7 +507,9 @@
  public DN getParentDNInSuffix() {
    assert debugEnter(CLASS_NAME, "getParentDNInSuffix");
    if ((numComponents <= 1) || (DirectoryServer.isSuffix(this))) {
    if ((numComponents <= 1) ||
        DirectoryServer.isNamingContext(this))
    {
      return null;
    }
opends/src/server/org/opends/server/util/ServerConstants.java
@@ -186,8 +186,8 @@
  /**
   * The name of the standard attribute that is used to specify the set of
   * naming contexts (suffixes) for the Directory Server, formatted in camel
   * case.
   * public naming contexts (suffixes) for the Directory Server, formatted in
   * camel case.
   */
  public static final String ATTR_NAMING_CONTEXTS = "namingContexts";
@@ -195,14 +195,23 @@
  /**
   * The name of the standard attribute that is used to specify the set of
   * naming contexts (suffixes) for the Directory Server, formatted in all
   * lowercase.
   * public naming contexts (suffixes) for the Directory Server, formatted in
   * all lowercase.
   */
  public static final String ATTR_NAMING_CONTEXTS_LC = "namingcontexts";
  /**
   * The name of the attribute used to hold the DNs that constitute the set of
   * "private" naming contexts registered with the server.
   */
  public static final String ATTR_PRIVATE_NAMING_CONTEXTS =
       "ds-private-naming-contexts";
  /**
   * The name of the standard attribute that is used to hold organization names,
   * formatted in all lowercase.
   */
opends/tests/unit-tests-testng/src/server/org/opends/server/TestCaseUtils.java
@@ -348,6 +348,7 @@
    if (memoryBackend == null)
    {
      memoryBackend = new MemoryBackend();
      memoryBackend.setBackendID("test");
      memoryBackend.initializeBackend(null, new DN[] { baseDN });
      DirectoryServer.registerBackend(memoryBackend);
    }
opends/tests/unit-tests-testng/src/server/org/opends/server/core/BackendConfigManagerTestCase.java
New file
@@ -0,0 +1,783 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2006 Sun Microsystems, Inc.
 */
package org.opends.server.core;
import java.util.ArrayList;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.opends.server.TestCaseUtils;
import org.opends.server.api.Backend;
import org.opends.server.protocols.internal.InternalClientConnection;
import org.opends.server.protocols.internal.InternalSearchOperation;
import org.opends.server.types.Attribute;
import org.opends.server.types.DirectoryException;
import org.opends.server.types.DN;
import org.opends.server.types.Entry;
import org.opends.server.types.Modification;
import org.opends.server.types.ModificationType;
import org.opends.server.types.ResultCode;
import org.opends.server.types.SearchFilter;
import org.opends.server.types.SearchScope;
import static org.testng.Assert.*;
import static org.opends.server.util.StaticUtils.*;
/**
 * A set of generic test cases that cover adding, modifying, and removing
 * Directory Server backends.
 */
public class BackendConfigManagerTestCase
       extends CoreTestCase
{
  /**
   * Ensures that the Directory Server is running.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @BeforeClass()
  public void startServer()
         throws Exception
  {
    TestCaseUtils.startServer();
  }
  /**
   * Tests that the server will reject an attempt to register a base DN that is
   * already defined in the server.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testRegisterBaseThatAlreadyExists()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(false);
    DN baseDN = DN.decode("o=test");
    String backendID = createBackendID(baseDN);
    Entry backendEntry = createBackendEntry(backendID, false, baseDN);
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    AddOperation addOperation =
         conn.processAdd(backendEntry.getDN(), backendEntry.getObjectClasses(),
                         backendEntry.getUserAttributes(),
                         backendEntry.getOperationalAttributes());
    assertFalse(addOperation.getResultCode() == ResultCode.SUCCESS);
  }
  /**
   * Tests that the server will reject an attempt to deregister a base DN that
   * is not defined in the server.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test(expectedExceptions = { DirectoryException.class })
  public void testDeregisterNonExistentBaseDN()
         throws Exception
  {
    DirectoryServer.deregisterBaseDN(DN.decode("o=unregistered"), false);
  }
  /**
   * Tests that the server will reject an attempt to register a base DN using a
   * backend with a backend ID that is already defined in the server.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testRegisterBackendIDThatAlreadyExists()
         throws Exception
  {
    TestCaseUtils.initializeTestBackend(false);
    DN baseDN = DN.decode("o=test");
    String backendID = "test";
    Entry backendEntry = createBackendEntry(backendID, false, baseDN);
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    AddOperation addOperation =
         conn.processAdd(backendEntry.getDN(), backendEntry.getObjectClasses(),
                         backendEntry.getUserAttributes(),
                         backendEntry.getOperationalAttributes());
    assertFalse(addOperation.getResultCode() == ResultCode.SUCCESS);
  }
  /**
   * Tests the ability of the server to create and remove a backend that is
   * never enabled.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testAddAndRemoveDisabledBackend()
         throws Exception
  {
    DN baseDN = DN.decode("o=bcmtest");
    String backendID = createBackendID(baseDN);
    Entry backendEntry = createBackendEntry(backendID, false, baseDN);
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    AddOperation addOperation =
         conn.processAdd(backendEntry.getDN(), backendEntry.getObjectClasses(),
                         backendEntry.getUserAttributes(),
                         backendEntry.getOperationalAttributes());
    assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
    assertNull(DirectoryServer.getBackend(backendID));
    assertNull(DirectoryServer.getBackendWithBaseDN(baseDN));
    DeleteOperation deleteOperation = conn.processDelete(backendEntry.getDN());
    assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
  }
  /**
   * Tests the ability of the server to create and remove a backend that is
   * enabled.  It will also test the ability of that backend to hold entries.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testAddAndRemoveEnabledBackend()
         throws Exception
  {
    DN baseDN = DN.decode("o=bcmtest");
    String backendID = createBackendID(baseDN);
    Entry backendEntry = createBackendEntry(backendID, true, baseDN);
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    AddOperation addOperation =
         conn.processAdd(backendEntry.getDN(), backendEntry.getObjectClasses(),
                         backendEntry.getUserAttributes(),
                         backendEntry.getOperationalAttributes());
    assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
    Backend backend = DirectoryServer.getBackend(backendID);
    assertNotNull(backend);
    assertEquals(backend, DirectoryServer.getBackendWithBaseDN(baseDN));
    assertNull(backend.getParentBackend());
    assertTrue(backend.getSubordinateBackends().length == 0);
    assertFalse(backend.entryExists(baseDN));
    assertTrue(DirectoryServer.isNamingContext(baseDN));
    Entry e = createEntry(baseDN);
    addOperation = conn.processAdd(e.getDN(), e.getObjectClasses(),
                                   e.getUserAttributes(),
                                   e.getOperationalAttributes());
    assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
    assertTrue(backend.entryExists(baseDN));
    DeleteOperation deleteOperation = conn.processDelete(backendEntry.getDN());
    assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
    assertNull(DirectoryServer.getBackend(backendID));
  }
  /**
   * Tests the ability of the server to create a backend that is disabled and
   * then enable it through a configuration change, and then subsequently
   * disable it.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testEnableAndDisableBackend()
         throws Exception
  {
    // Create the backend and make it disabled.
    DN baseDN = DN.decode("o=bcmtest");
    String backendID = createBackendID(baseDN);
    Entry backendEntry = createBackendEntry(backendID, false, baseDN);
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    AddOperation addOperation =
         conn.processAdd(backendEntry.getDN(), backendEntry.getObjectClasses(),
                         backendEntry.getUserAttributes(),
                         backendEntry.getOperationalAttributes());
    assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
    assertNull(DirectoryServer.getBackend(backendID));
    assertFalse(DirectoryServer.isNamingContext(baseDN));
    // Modify the backend to enable it.
    ArrayList<Modification> mods = new ArrayList<Modification>();
    mods.add(new Modification(ModificationType.REPLACE,
                              new Attribute("ds-cfg-backend-enabled", "true")));
    ModifyOperation modifyOperation =
         conn.processModify(backendEntry.getDN(), mods);
    assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
    Backend backend = DirectoryServer.getBackend(backendID);
    assertNotNull(backend);
    assertEquals(backend, DirectoryServer.getBackendWithBaseDN(baseDN));
    assertNull(backend.getParentBackend());
    assertTrue(backend.getSubordinateBackends().length == 0);
    assertFalse(backend.entryExists(baseDN));
    assertTrue(DirectoryServer.isNamingContext(baseDN));
    Entry e = createEntry(baseDN);
    addOperation = conn.processAdd(e.getDN(), e.getObjectClasses(),
                                   e.getUserAttributes(),
                                   e.getOperationalAttributes());
    assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
    assertTrue(backend.entryExists(baseDN));
    // Modify the backend to disable it.
    mods = new ArrayList<Modification>();
    mods.add(new Modification(ModificationType.REPLACE,
                              new Attribute("ds-cfg-backend-enabled", "false")));
    modifyOperation =
         conn.processModify(backendEntry.getDN(), mods);
    assertNull(DirectoryServer.getBackend(backendID));
    assertFalse(DirectoryServer.entryExists(baseDN));
    assertFalse(DirectoryServer.isNamingContext(baseDN));
    // Delete the disabled backend.
    DeleteOperation deleteOperation = conn.processDelete(backendEntry.getDN());
    assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
  }
  /**
   * Tests the ability of the Directory Server to work properly when adding
   * nested backends in which the parent is added first and the child second.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testAddNestedBackendParentFirst()
         throws Exception
  {
    // Create the parent backend and the corresponding base entry.
    DN parentBaseDN = DN.decode("o=parent");
    String parentBackendID = createBackendID(parentBaseDN);
    Entry parentBackendEntry = createBackendEntry(parentBackendID, true,
                                                  parentBaseDN);
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    AddOperation addOperation =
         conn.processAdd(parentBackendEntry.getDN(),
                         parentBackendEntry.getObjectClasses(),
                         parentBackendEntry.getUserAttributes(),
                         parentBackendEntry.getOperationalAttributes());
    assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
    Backend parentBackend = DirectoryServer.getBackend(parentBackendID);
    assertNotNull(parentBackend);
    assertEquals(parentBackend,
                 DirectoryServer.getBackendWithBaseDN(parentBaseDN));
    assertNull(parentBackend.getParentBackend());
    assertTrue(parentBackend.getSubordinateBackends().length == 0);
    assertFalse(parentBackend.entryExists(parentBaseDN));
    assertTrue(DirectoryServer.isNamingContext(parentBaseDN));
    Entry e = createEntry(parentBaseDN);
    addOperation = conn.processAdd(e.getDN(), e.getObjectClasses(),
                                   e.getUserAttributes(),
                                   e.getOperationalAttributes());
    assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
    assertTrue(parentBackend.entryExists(parentBaseDN));
    // Create the child backend and the corresponding base entry.
    DN childBaseDN = DN.decode("ou=child,o=parent");
    String childBackendID = createBackendID(childBaseDN);
    Entry childBackendEntry = createBackendEntry(childBackendID, true,
                                                 childBaseDN);
    addOperation =
         conn.processAdd(childBackendEntry.getDN(),
                         childBackendEntry.getObjectClasses(),
                         childBackendEntry.getUserAttributes(),
                         childBackendEntry.getOperationalAttributes());
    assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
    Backend childBackend = DirectoryServer.getBackend(childBackendID);
    assertNotNull(childBackend);
    assertEquals(childBackend,
                 DirectoryServer.getBackendWithBaseDN(childBaseDN));
    assertNotNull(childBackend.getParentBackend());
    assertEquals(parentBackend, childBackend.getParentBackend());
    assertTrue(parentBackend.getSubordinateBackends().length == 1);
    assertFalse(childBackend.entryExists(childBaseDN));
    assertFalse(DirectoryServer.isNamingContext(childBaseDN));
    e = createEntry(childBaseDN);
    addOperation = conn.processAdd(e.getDN(), e.getObjectClasses(),
                                   e.getUserAttributes(),
                                   e.getOperationalAttributes());
    assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
    assertTrue(childBackend.entryExists(childBaseDN));
    // Make sure that both entries exist.
    InternalSearchOperation internalSearch =
         conn.processSearch(parentBaseDN, SearchScope.WHOLE_SUBTREE,
              SearchFilter.createFilterFromString("(objectClass=*)"));
    assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);
    assertEquals(internalSearch.getSearchEntries().size(), 2);
    // Make sure that we can't remove the parent backend with the child still
    // in place.
    DeleteOperation deleteOperation =
         conn.processDelete(parentBackendEntry.getDN());
    assertFalse(deleteOperation.getResultCode() == ResultCode.SUCCESS);
    assertNotNull(DirectoryServer.getBackend(parentBackendID));
    // Delete the child and then delete the parent.
    deleteOperation = conn.processDelete(childBackendEntry.getDN());
    assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
    assertNull(DirectoryServer.getBackend(childBackendID));
    assertTrue(parentBackend.getSubordinateBackends().length == 0);
    deleteOperation = conn.processDelete(parentBackendEntry.getDN());
    assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
    assertNull(DirectoryServer.getBackend(parentBackendID));
  }
  /**
   * Tests the ability of the Directory Server to work properly when adding
   * nested backends in which the child is added first and the parent second.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testAddNestedBackendChildFirst()
         throws Exception
  {
    // Create the child backend and the corresponding base entry (at the time
    // of the creation, it will be a naming context).
    DN childBaseDN = DN.decode("ou=child,o=parent");
    String childBackendID = createBackendID(childBaseDN);
    Entry childBackendEntry = createBackendEntry(childBackendID, true,
                                                 childBaseDN);
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    AddOperation addOperation =
         conn.processAdd(childBackendEntry.getDN(),
                         childBackendEntry.getObjectClasses(),
                         childBackendEntry.getUserAttributes(),
                         childBackendEntry.getOperationalAttributes());
    assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
    Backend childBackend = DirectoryServer.getBackend(childBackendID);
    assertNotNull(childBackend);
    assertEquals(childBackend,
                 DirectoryServer.getBackendWithBaseDN(childBaseDN));
    assertFalse(childBackend.entryExists(childBaseDN));
    assertNull(childBackend.getParentBackend());
    assertTrue(childBackend.getSubordinateBackends().length == 0);
    assertFalse(childBackend.entryExists(childBaseDN));
    Entry e = createEntry(childBaseDN);
    addOperation = conn.processAdd(e.getDN(), e.getObjectClasses(),
                                   e.getUserAttributes(),
                                   e.getOperationalAttributes());
    assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
    assertTrue(childBackend.entryExists(childBaseDN));
    assertTrue(DirectoryServer.isNamingContext(childBaseDN));
    // Create the parent backend and the corresponding entry (and verify that
    // its DN is now a naming context and the child's is not).
    DN parentBaseDN = DN.decode("o=parent");
    String parentBackendID = createBackendID(parentBaseDN);
    Entry parentBackendEntry = createBackendEntry(parentBackendID, true,
                                                  parentBaseDN);
    addOperation =
         conn.processAdd(parentBackendEntry.getDN(),
                         parentBackendEntry.getObjectClasses(),
                         parentBackendEntry.getUserAttributes(),
                         parentBackendEntry.getOperationalAttributes());
    assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
    Backend parentBackend = DirectoryServer.getBackend(parentBackendID);
    assertNotNull(parentBackend);
    assertEquals(parentBackend,
                 DirectoryServer.getBackendWithBaseDN(parentBaseDN));
    assertNotNull(childBackend.getParentBackend());
    assertEquals(parentBackend, childBackend.getParentBackend());
    assertTrue(parentBackend.getSubordinateBackends().length == 1);
    e = createEntry(parentBaseDN);
    addOperation = conn.processAdd(e.getDN(), e.getObjectClasses(),
                                   e.getUserAttributes(),
                                   e.getOperationalAttributes());
    assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
    assertTrue(parentBackend.entryExists(parentBaseDN));
    assertTrue(DirectoryServer.isNamingContext(parentBaseDN));
    assertFalse(DirectoryServer.isNamingContext(childBaseDN));
    // Verify that we can see both entries with a subtree search.
    InternalSearchOperation internalSearch =
         conn.processSearch(parentBaseDN, SearchScope.WHOLE_SUBTREE,
              SearchFilter.createFilterFromString("(objectClass=*)"));
    assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);
    assertEquals(internalSearch.getSearchEntries().size(), 2);
    // Delete the backends from the server.
    DeleteOperation deleteOperation =
         conn.processDelete(childBackendEntry.getDN());
    assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
    assertNull(DirectoryServer.getBackend(childBackendID));
    assertTrue(parentBackend.getSubordinateBackends().length == 0);
    deleteOperation = conn.processDelete(parentBackendEntry.getDN());
    assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
    assertNull(DirectoryServer.getBackend(parentBackendID));
  }
  /**
   * Tests the ability of the Directory Server to work properly when inserting
   * an intermediate backend between a parent backend and an existing nested
   * backend.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  @Test()
  public void testInsertIntermediateBackend()
         throws Exception
  {
    // Add the parent backend to the server and its corresponding base entry.
    DN parentBaseDN = DN.decode("o=parent");
    String parentBackendID = createBackendID(parentBaseDN);
    Entry parentBackendEntry = createBackendEntry(parentBackendID, true,
                                                  parentBaseDN);
    InternalClientConnection conn =
         InternalClientConnection.getRootConnection();
    AddOperation addOperation =
         conn.processAdd(parentBackendEntry.getDN(),
                         parentBackendEntry.getObjectClasses(),
                         parentBackendEntry.getUserAttributes(),
                         parentBackendEntry.getOperationalAttributes());
    assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
    Backend parentBackend = DirectoryServer.getBackend(parentBackendID);
    assertNotNull(parentBackend);
    assertEquals(parentBackend,
                 DirectoryServer.getBackendWithBaseDN(parentBaseDN));
    assertNull(parentBackend.getParentBackend());
    assertTrue(parentBackend.getSubordinateBackends().length == 0);
    assertFalse(parentBackend.entryExists(parentBaseDN));
    Entry e = createEntry(parentBaseDN);
    addOperation = conn.processAdd(e.getDN(), e.getObjectClasses(),
                                   e.getUserAttributes(),
                                   e.getOperationalAttributes());
    assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
    assertTrue(parentBackend.entryExists(parentBaseDN));
    assertTrue(DirectoryServer.isNamingContext(parentBaseDN));
    // Add the grandchild backend to the server.
    DN grandchildBaseDN = DN.decode("ou=grandchild,ou=child,o=parent");
    String grandchildBackendID = createBackendID(grandchildBaseDN);
    Entry grandchildBackendEntry = createBackendEntry(grandchildBackendID, true,
                                                      grandchildBaseDN);
    addOperation =
         conn.processAdd(grandchildBackendEntry.getDN(),
                         grandchildBackendEntry.getObjectClasses(),
                         grandchildBackendEntry.getUserAttributes(),
                         grandchildBackendEntry.getOperationalAttributes());
    assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
    Backend grandchildBackend = DirectoryServer.getBackend(grandchildBackendID);
    assertNotNull(grandchildBackend);
    assertEquals(grandchildBackend,
                 DirectoryServer.getBackendWithBaseDN(grandchildBaseDN));
    assertNotNull(grandchildBackend.getParentBackend());
    assertEquals(grandchildBackend.getParentBackend(), parentBackend);
    assertTrue(parentBackend.getSubordinateBackends().length == 1);
    assertFalse(grandchildBackend.entryExists(grandchildBaseDN));
    // Verify that we can't create the grandchild base entry because its parent
    // doesn't exist.
    e = createEntry(grandchildBaseDN);
    addOperation = conn.processAdd(e.getDN(), e.getObjectClasses(),
                                   e.getUserAttributes(),
                                   e.getOperationalAttributes());
    assertEquals(addOperation.getResultCode(), ResultCode.NO_SUCH_OBJECT);
    assertFalse(grandchildBackend.entryExists(grandchildBaseDN));
    // Add the child backend to the server and create its base entry.
    DN childBaseDN = DN.decode("ou=child,o=parent");
    String childBackendID = createBackendID(childBaseDN);
    Entry childBackendEntry = createBackendEntry(childBackendID, true,
                                                 childBaseDN);
    addOperation =
         conn.processAdd(childBackendEntry.getDN(),
                         childBackendEntry.getObjectClasses(),
                         childBackendEntry.getUserAttributes(),
                         childBackendEntry.getOperationalAttributes());
    assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
    Backend childBackend = DirectoryServer.getBackend(childBackendID);
    assertNotNull(childBackend);
    assertEquals(childBackend,
                 DirectoryServer.getBackendWithBaseDN(childBaseDN));
    assertNotNull(childBackend.getParentBackend());
    assertEquals(parentBackend, childBackend.getParentBackend());
    assertTrue(parentBackend.getSubordinateBackends().length == 1);
    assertFalse(childBackend.entryExists(childBaseDN));
    assertTrue(childBackend.getSubordinateBackends().length == 1);
    assertEquals(childBackend.getSubordinateBackends()[0], grandchildBackend);
    assertEquals(grandchildBackend.getParentBackend(), childBackend);
    e = createEntry(childBaseDN);
    addOperation = conn.processAdd(e.getDN(), e.getObjectClasses(),
                                   e.getUserAttributes(),
                                   e.getOperationalAttributes());
    assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
    assertTrue(childBackend.entryExists(childBaseDN));
    // Now we can create the grandchild base entry.
    e = createEntry(grandchildBaseDN);
    addOperation = conn.processAdd(e.getDN(), e.getObjectClasses(),
                                   e.getUserAttributes(),
                                   e.getOperationalAttributes());
    assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
    assertTrue(grandchildBackend.entryExists(grandchildBaseDN));
    // Verify that a subtree search can see all three entries.
    InternalSearchOperation internalSearch =
         conn.processSearch(parentBaseDN, SearchScope.WHOLE_SUBTREE,
              SearchFilter.createFilterFromString("(objectClass=*)"));
    assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);
    assertEquals(internalSearch.getSearchEntries().size(), 3);
    // Disable the intermediate (child) backend.  This should be allowed.
    ArrayList<Modification> mods = new ArrayList<Modification>();
    mods.add(new Modification(ModificationType.REPLACE,
                              new Attribute("ds-cfg-backend-enabled",
                                            "false")));
    ModifyOperation modifyOperation =
         conn.processModify(childBackendEntry.getDN(), mods);
    assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
    // Make sure that we now only see two entries with the subtree search
    // (and those two entries should be the parent and grandchild base entries).
    internalSearch =
         conn.processSearch(parentBaseDN, SearchScope.WHOLE_SUBTREE,
              SearchFilter.createFilterFromString("(objectClass=*)"));
    assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);
    assertEquals(internalSearch.getSearchEntries().size(), 2);
    // Re-enable the intermediate backend.
    mods = new ArrayList<Modification>();
    mods.add(new Modification(ModificationType.REPLACE,
                              new Attribute("ds-cfg-backend-enabled", "true")));
    modifyOperation = conn.processModify(childBackendEntry.getDN(), mods);
    assertEquals(modifyOperation.getResultCode(), ResultCode.SUCCESS);
    // Update our reference to the child backend since the old one is no longer
    // valid, and make sure that it got re-inserted back into the same place in
    // the hierarchy.
    childBackend = DirectoryServer.getBackend(childBackendID);
    assertNotNull(childBackend);
    assertEquals(childBackend,
                 DirectoryServer.getBackendWithBaseDN(childBaseDN));
    assertNotNull(childBackend.getParentBackend());
    assertEquals(parentBackend, childBackend.getParentBackend());
    assertTrue(parentBackend.getSubordinateBackends().length == 1);
    assertFalse(childBackend.entryExists(childBaseDN));
    assertTrue(childBackend.getSubordinateBackends().length == 1);
    assertEquals(childBackend.getSubordinateBackends()[0], grandchildBackend);
    assertEquals(grandchildBackend.getParentBackend(), childBackend);
    // Since the memory backend that we're using for this test doesn't retain
    // entries across stops and restarts, a subtree search below the parent
    // should still only return two entries, which means that it's going through
    // the entire chain of backends.
    internalSearch =
         conn.processSearch(parentBaseDN, SearchScope.WHOLE_SUBTREE,
              SearchFilter.createFilterFromString("(objectClass=*)"));
    assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);
    assertEquals(internalSearch.getSearchEntries().size(), 2);
    // Add the child entry back into the server to get things back to the way
    // they were before we disabled the backend.
    e = createEntry(childBaseDN);
    addOperation = conn.processAdd(e.getDN(), e.getObjectClasses(),
                                   e.getUserAttributes(),
                                   e.getOperationalAttributes());
    assertEquals(addOperation.getResultCode(), ResultCode.SUCCESS);
    assertTrue(childBackend.entryExists(childBaseDN));
    // We should again be able to see all three entries when performing a
    // search.
    internalSearch =
         conn.processSearch(parentBaseDN, SearchScope.WHOLE_SUBTREE,
              SearchFilter.createFilterFromString("(objectClass=*)"));
    assertEquals(internalSearch.getResultCode(), ResultCode.SUCCESS);
    assertEquals(internalSearch.getSearchEntries().size(), 3);
    // Get rid of the entries in the proper order.
    DeleteOperation deleteOperation =
         conn.processDelete(grandchildBackendEntry.getDN());
    assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
    assertNull(DirectoryServer.getBackend(grandchildBackendID));
    assertTrue(childBackend.getSubordinateBackends().length == 0);
    assertTrue(parentBackend.getSubordinateBackends().length == 1);
    deleteOperation = conn.processDelete(childBackendEntry.getDN());
    assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
    assertNull(DirectoryServer.getBackend(childBackendID));
    assertTrue(parentBackend.getSubordinateBackends().length == 0);
    deleteOperation = conn.processDelete(parentBackendEntry.getDN());
    assertEquals(deleteOperation.getResultCode(), ResultCode.SUCCESS);
    assertNull(DirectoryServer.getBackend(parentBackendID));
  }
  /**
   * Creates an entry that may be used to add a new backend to the server.  It
   * will be an instance of the memory backend.
   *
   * @param  backendID  The backend ID to use for the backend.
   * @param  enabled    Indicates whether the backend should be enabled.
   * @param  baseDNs    The set of base DNs to use for the new backend.
   *
   * @return  An entry that may be used to add a new backend to the server.
   *
   * @throws  Exception  If an unexpected problem occurs.
   */
  private Entry createBackendEntry(String backendID, boolean enabled,
                                   DN... baseDNs)
          throws Exception
  {
    assertNotNull(baseDNs);
    assertFalse(baseDNs.length == 0);
    ArrayList<String> lines = new ArrayList<String>();
    lines.add("dn: ds-cfg-backend-id=" + backendID + ",cn=Backends,cn=config");
    lines.add("objectClass: top");
    lines.add("objectClass: ds-cfg-backend");
    lines.add("ds-cfg-backend-id: " + backendID);
    lines.add("ds-cfg-backend-class: org.opends.server.backends.MemoryBackend");
    lines.add("ds-cfg-backend-enabled: " + String.valueOf(enabled));
    lines.add("ds-cfg-backend-writability-mode: enabled");
    for (DN dn : baseDNs)
    {
      lines.add("ds-cfg-backend-base-dn: " + dn.toString());
    }
    String[] lineArray = new String[lines.size()];
    lines.toArray(lineArray);
    return TestCaseUtils.makeEntry(lineArray);
  }
  /**
   * Constructs a backend ID to use for a backend with the provided set of base
   * DNs.
   *
   * @param  baseDNs  The set of base DNs to use when creating the backend ID.
   *
   * @return  The constructed backend ID based on the given base DNs.
   */
  private String createBackendID(DN... baseDNs)
  {
    StringBuilder buffer = new StringBuilder();
    for (DN dn : baseDNs)
    {
      if (buffer.length() > 0)
      {
        buffer.append("___");
      }
      String ndn = dn.toNormalizedString();
      for (int i=0; i < ndn.length(); i++)
      {
        char c = ndn.charAt(i);
        if (Character.isLetterOrDigit(c))
        {
          buffer.append(c);
        }
        else
        {
          buffer.append('_');
        }
      }
    }
    return buffer.toString();
  }
}