From 3f6373b52c42fd596d3659d335542909cfdd5fbb Mon Sep 17 00:00:00 2001
From: Nicolas Capponi <nicolas.capponi@forgerock.com>
Date: Fri, 20 Dec 2013 14:37:56 +0000
Subject: [PATCH] Checkpoint commit for OPENDJ-1235 : Migrate configuration framework

---
 opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/server/ConfigAddListenerAdaptor.java      |  369 ++--
 opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/server/AdminTestCaseUtils.java            |  117 +
 opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/server/AggregationServerTest.java         |  814 +++++++++++
 opendj-sdk/opendj-admin/src/main/java/org/opends/server/core/DirectoryServer.java                       |    7 
 opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/server/ConfigDeleteListenerAdaptor.java   |   37 
 opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/server/ServerManagementContext.java       |  137 +
 opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/server/ConfigChangeListenerAdaptor.java   |   99 
 opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/server/AbstractConfigListenerAdaptor.java |   58 
 opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/server/DelayedConfigAddListener.java      |   31 
 opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/server/ListenerTest.java                  |  275 +++
 /dev/null                                                                                               |  234 ---
 opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/AggregationPropertyDefinition.java        |    6 
 opendj-sdk/opendj-admin/src/main/java/org/opends/server/api/ConfigDeleteListener.java                   |   69 
 opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/server/DNBuilderTest.java                 |  165 ++
 opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/server/DefaultBehaviorTest.java           |  711 ++++++++++
 opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/server/ConstraintTest.java                |  489 ++++++
 opendj-sdk/opendj-admin/src/main/java/org/opends/server/api/ConfigChangeListener.java                   |    6 
 opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/server/ServerManagedObject.java           |  316 ++--
 opendj-sdk/opendj-admin/src/main/java/org/opends/server/config/ConfigurationRepository.java             |  146 +
 opendj-sdk/opendj-admin/src/main/java/org/opends/server/api/ConfigAddListener.java                      |    6 
 opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/server/MockConstraint.java                |  146 ++
 21 files changed, 3,441 insertions(+), 797 deletions(-)

diff --git a/opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/AggregationPropertyDefinition.java b/opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/AggregationPropertyDefinition.java
index 29e126b..56c4ef1 100644
--- a/opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/AggregationPropertyDefinition.java
+++ b/opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/AggregationPropertyDefinition.java
@@ -348,7 +348,7 @@
         public boolean isUsable(ServerManagedObject<?> managedObject, Collection<LocalizableMessage> unacceptableReasons)
                 throws ConfigException {
             SortedSet<String> names = managedObject.getPropertyValues(AggregationPropertyDefinition.this);
-            ServerManagementContext context = ServerManagementContext.getInstance();
+            ServerManagementContext context = managedObject.getServerContext();
             LocalizableMessage thisUFN = managedObject.getManagedObjectDefinition().getUserFriendlyName();
             String thisDN = managedObject.getDN().toString();
             LocalizableMessage thatUFN = getRelationDefinition().getUserFriendlyName();
@@ -404,7 +404,7 @@
 
             // Delete listeners need to be registered against the parent
             // entry of the referenced components.
-            ServerManagementContext context = ServerManagementContext.getInstance();
+            ServerManagementContext context = managedObject.getServerContext();
             ManagedObjectPath<?, ?> parentPath = getParentPath();
             ServerManagedObject<?> parent = context.getManagedObject(parentPath);
 
@@ -443,7 +443,7 @@
         @Override
         public void performPostDelete(ServerManagedObject<?> managedObject) throws ConfigException {
             // Remove any registered delete and change listeners.
-            ServerManagementContext context = ServerManagementContext.getInstance();
+            ServerManagementContext context = managedObject.getServerContext();
             DN dn = managedObject.getDN();
 
             // Delete listeners need to be deregistered against the parent
diff --git a/opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/server/AbstractConfigListenerAdaptor.java b/opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/server/AbstractConfigListenerAdaptor.java
index 596ef26..e1c7f7a 100644
--- a/opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/server/AbstractConfigListenerAdaptor.java
+++ b/opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/server/AbstractConfigListenerAdaptor.java
@@ -25,47 +25,41 @@
  */
 package org.opends.server.admin.server;
 
-
-
 import java.util.Collection;
 
 import org.forgerock.i18n.LocalizableMessage;
 import org.forgerock.i18n.LocalizableMessageBuilder;
 
-
-
 /**
  * Common features of config listener adaptors.
  */
 abstract class AbstractConfigListenerAdaptor {
 
-  /**
-   * Create a new config listener adaptor.
-   */
-  protected AbstractConfigListenerAdaptor() {
-    // No implementation required.
-  }
-
-
-
-  /**
-   * Concatenate a list of messages into a single message.
-   *
-   * @param reasons
-   *          The list of messages to concatenate.
-   * @param unacceptableReason
-   *          The single message to which messages should be appended.
-   */
-  protected final void generateUnacceptableReason(Collection<LocalizableMessage> reasons,
-      LocalizableMessageBuilder unacceptableReason) {
-    boolean isFirst = true;
-    for (LocalizableMessage reason : reasons) {
-      if (isFirst) {
-        isFirst = false;
-      } else {
-        unacceptableReason.append("  ");
-      }
-      unacceptableReason.append(reason);
+    /**
+     * Create a new config listener adaptor.
+     */
+    protected AbstractConfigListenerAdaptor() {
+        // No implementation required.
     }
-  }
+
+    /**
+     * Concatenate a list of messages into a single message.
+     *
+     * @param reasons
+     *            The list of messages to concatenate.
+     * @param unacceptableReason
+     *            The single message to which messages should be appended.
+     */
+    protected final void generateUnacceptableReason(Collection<LocalizableMessage> reasons,
+            LocalizableMessageBuilder unacceptableReason) {
+        boolean isFirst = true;
+        for (LocalizableMessage reason : reasons) {
+            if (isFirst) {
+                isFirst = false;
+            } else {
+                unacceptableReason.append("  ");
+            }
+            unacceptableReason.append(reason);
+        }
+    }
 }
diff --git a/opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/server/ConfigAddListenerAdaptor.java b/opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/server/ConfigAddListenerAdaptor.java
index b6b5bea..8976ef1 100644
--- a/opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/server/ConfigAddListenerAdaptor.java
+++ b/opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/server/ConfigAddListenerAdaptor.java
@@ -25,8 +25,6 @@
  */
 package org.opends.server.admin.server;
 
-
-
 import java.util.LinkedList;
 import java.util.List;
 
@@ -43,229 +41,214 @@
 import org.opends.server.admin.SetRelationDefinition;
 import org.opends.server.admin.DefinitionDecodingException.Reason;
 import org.opends.server.api.ConfigAddListener;
-import org.opends.server.config.ConfigEntry;
 import org.opends.server.config.ConfigException;
 import org.opends.server.types.ConfigChangeResult;
 import org.forgerock.opendj.ldap.DN;
+import org.forgerock.opendj.ldap.Entry;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.forgerock.opendj.ldap.ResultCode;
 
-
-
 /**
- * An adaptor class which converts {@link ConfigAddListener} callbacks
- * to {@link ServerManagedObjectAddListener} callbacks.
+ * An adaptor class which converts {@link ConfigAddListener} callbacks to
+ * {@link ServerManagedObjectAddListener} callbacks.
  *
  * @param <S>
- *          The type of server configuration handled by the add
- *          listener.
+ *            The type of server configuration handled by the add listener.
  */
-final class ConfigAddListenerAdaptor<S extends Configuration> extends
-    AbstractConfigListenerAdaptor implements ConfigAddListener {
+final class ConfigAddListenerAdaptor<S extends Configuration> extends AbstractConfigListenerAdaptor implements
+        ConfigAddListener {
 
-  private static final Logger debugLogger = LoggerFactory.getLogger(ConfigAddListenerAdaptor.class);
+    private static final Logger debugLogger = LoggerFactory.getLogger(ConfigAddListenerAdaptor.class);
 
-  // Cached managed object between accept/apply callbacks.
-  private ServerManagedObject<? extends S> cachedManagedObject;
+    // Cached managed object between accept/apply callbacks.
+    private ServerManagedObject<? extends S> cachedManagedObject;
 
-  // The instantiable relation.
-  private final InstantiableRelationDefinition<?, S> instantiableRelation;
+    // The instantiable relation.
+    private final InstantiableRelationDefinition<?, S> instantiableRelation;
 
-  // The set relation.
-  private final SetRelationDefinition<?, S> setRelation;
+    // The set relation.
+    private final SetRelationDefinition<?, S> setRelation;
 
-  // The underlying add listener.
-  private final ServerManagedObjectAddListener<S> listener;
+    // The underlying add listener.
+    private final ServerManagedObjectAddListener<S> listener;
 
-  // The optional relation.
-  private final OptionalRelationDefinition<?, S> optionalRelation;
+    // The optional relation.
+    private final OptionalRelationDefinition<?, S> optionalRelation;
 
-  // The managed object path of the parent.
-  private final ManagedObjectPath<?, ?> path;
+    // The managed object path of the parent.
+    private final ManagedObjectPath<?, ?> path;
 
+    private final ServerManagementContext serverContext;
 
-
-  /**
-   * Create a new configuration add listener adaptor for an
-   * instantiable relation.
-   *
-   * @param path
-   *          The managed object path of the parent.
-   * @param relation
-   *          The instantiable relation.
-   * @param listener
-   *          The underlying add listener.
-   */
-  public ConfigAddListenerAdaptor(ManagedObjectPath<?, ?> path,
-      InstantiableRelationDefinition<?, S> relation,
-      ServerManagedObjectAddListener<S> listener) {
-    this.path = path;
-    this.instantiableRelation = relation;
-    this.optionalRelation = null;
-    this.setRelation = null;
-    this.listener = listener;
-    this.cachedManagedObject = null;
-  }
-
-
-
-  /**
-   * Create a new configuration add listener adaptor for an optional
-   * relation.
-   *
-   * @param path
-   *          The managed object path of the parent.
-   * @param relation
-   *          The optional relation.
-   * @param listener
-   *          The underlying add listener.
-   */
-  public ConfigAddListenerAdaptor(ManagedObjectPath<?, ?> path,
-      OptionalRelationDefinition<?, S> relation,
-      ServerManagedObjectAddListener<S> listener) {
-    this.path = path;
-    this.optionalRelation = relation;
-    this.instantiableRelation = null;
-    this.setRelation = null;
-    this.listener = listener;
-    this.cachedManagedObject = null;
-  }
-
-
-
-  /**
-   * Create a new configuration add listener adaptor for a
-   * set relation.
-   *
-   * @param path
-   *          The managed object path of the parent.
-   * @param relation
-   *          The set relation.
-   * @param listener
-   *          The underlying add listener.
-   */
-  public ConfigAddListenerAdaptor(ManagedObjectPath<?, ?> path,
-      SetRelationDefinition<?, S> relation,
-      ServerManagedObjectAddListener<S> listener) {
-    this.path = path;
-    this.instantiableRelation = null;
-    this.optionalRelation = null;
-    this.setRelation = relation;
-    this.listener = listener;
-    this.cachedManagedObject = null;
-  }
-
-
-
-  /**
-   * {@inheritDoc}
-   */
-  public ConfigChangeResult applyConfigurationAdd(ConfigEntry configEntry) {
-    if (optionalRelation != null) {
-      // Optional managed objects are located directly beneath the
-      // parent and have a well-defined name. We need to make sure
-      // that we are handling the correct entry.
-      ManagedObjectPath<?, ?> childPath = path.child(optionalRelation);
-      DN expectedDN = DNBuilder.create(childPath);
-      if (!configEntry.getDN().equals(expectedDN)) {
-        // Doesn't apply to us.
-        return new ConfigChangeResult(ResultCode.SUCCESS, false);
-      }
+    /**
+     * Create a new configuration add listener adaptor for an instantiable
+     * relation.
+     *
+     * @param context
+     *            The server context.
+     * @param path
+     *            The managed object path of the parent.
+     * @param relation
+     *            The instantiable relation.
+     * @param listener
+     *            The underlying add listener.
+     */
+    public ConfigAddListenerAdaptor(ServerManagementContext context, ManagedObjectPath<?, ?> path,
+            InstantiableRelationDefinition<?, S> relation, ServerManagedObjectAddListener<S> listener) {
+        this.serverContext = context;
+        this.path = path;
+        this.instantiableRelation = relation;
+        this.optionalRelation = null;
+        this.setRelation = null;
+        this.listener = listener;
+        this.cachedManagedObject = null;
     }
 
-    // Cached objects are guaranteed to be from previous acceptable
-    // callback.
-    ConfigChangeResult result = listener
-        .applyConfigurationAdd(cachedManagedObject);
+    /**
+     * Create a new configuration add listener adaptor for an optional relation.
+     *
+     * @param context
+     *            The server context.
+     * @param path
+     *            The managed object path of the parent.
+     * @param relation
+     *            The optional relation.
+     * @param listener
+     *            The underlying add listener.
+     */
+    public ConfigAddListenerAdaptor(ServerManagementContext context, ManagedObjectPath<?, ?> path,
+            OptionalRelationDefinition<?, S> relation, ServerManagedObjectAddListener<S> listener) {
+        this.serverContext = context;
+        this.path = path;
+        this.optionalRelation = relation;
+        this.instantiableRelation = null;
+        this.setRelation = null;
+        this.listener = listener;
+        this.cachedManagedObject = null;
+    }
 
-    // Now apply post constraint call-backs.
-    if (result.getResultCode() == ResultCode.SUCCESS) {
-      ManagedObjectDefinition<?, ?> d = cachedManagedObject
-          .getManagedObjectDefinition();
-      for (Constraint constraint : d.getAllConstraints()) {
-        for (ServerConstraintHandler handler : constraint
-            .getServerConstraintHandlers()) {
-          try {
-            handler.performPostAdd(cachedManagedObject);
-          } catch (ConfigException e) {
-              debugLogger.trace("Unable to perform post add", e);
-          }
+    /**
+     * Create a new configuration add listener adaptor for a set relation.
+     *
+     * @param context
+     *            The server context.
+     * @param path
+     *            The managed object path of the parent.
+     * @param relation
+     *            The set relation.
+     * @param listener
+     *            The underlying add listener.
+     */
+    public ConfigAddListenerAdaptor(ServerManagementContext context, ManagedObjectPath<?, ?> path,
+            SetRelationDefinition<?, S> relation, ServerManagedObjectAddListener<S> listener) {
+        this.serverContext = context;
+        this.path = path;
+        this.instantiableRelation = null;
+        this.optionalRelation = null;
+        this.setRelation = relation;
+        this.listener = listener;
+        this.cachedManagedObject = null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public ConfigChangeResult applyConfigurationAdd(Entry configEntry) {
+        if (optionalRelation != null) {
+            // Optional managed objects are located directly beneath the
+            // parent and have a well-defined name. We need to make sure
+            // that we are handling the correct entry.
+            ManagedObjectPath<?, ?> childPath = path.child(optionalRelation);
+            DN expectedDN = DNBuilder.create(childPath);
+            if (!configEntry.getName().equals(expectedDN)) {
+                // Doesn't apply to us.
+                return new ConfigChangeResult(ResultCode.SUCCESS, false);
+            }
         }
-      }
+
+        // Cached objects are guaranteed to be from previous acceptable
+        // callback.
+        ConfigChangeResult result = listener.applyConfigurationAdd(cachedManagedObject);
+
+        // Now apply post constraint call-backs.
+        if (result.getResultCode() == ResultCode.SUCCESS) {
+            ManagedObjectDefinition<?, ?> d = cachedManagedObject.getManagedObjectDefinition();
+            for (Constraint constraint : d.getAllConstraints()) {
+                for (ServerConstraintHandler handler : constraint.getServerConstraintHandlers()) {
+                    try {
+                        handler.performPostAdd(cachedManagedObject);
+                    } catch (ConfigException e) {
+                        debugLogger.trace("Unable to perform post add", e);
+                    }
+                }
+            }
+        }
+
+        return result;
     }
 
-    return result;
-  }
+    /**
+     * {@inheritDoc}
+     */
+    public boolean configAddIsAcceptable(Entry configEntry, LocalizableMessageBuilder unacceptableReason) {
+        DN dn = configEntry.getName();
+        String name = dn.rdn().getFirstAVA().getAttributeValue().toString().trim();
 
-
-
-  /**
-   * {@inheritDoc}
-   */
-  public boolean configAddIsAcceptable(ConfigEntry configEntry,
-      LocalizableMessageBuilder unacceptableReason) {
-    DN dn = configEntry.getDN();
-    String name = dn.rdn().getFirstAVA().getAttributeValue().toString().trim();
-
-    try {
-      ManagedObjectPath<?, ? extends S> childPath;
-      if (instantiableRelation != null) {
-        childPath = path.child(instantiableRelation, name);
-      } else if (setRelation != null) {
         try {
-          childPath = path.child(setRelation, name);
-        } catch (IllegalArgumentException e) {
-          throw new DefinitionDecodingException(setRelation
-              .getChildDefinition(), Reason.WRONG_TYPE_INFORMATION);
+            ManagedObjectPath<?, ? extends S> childPath;
+            if (instantiableRelation != null) {
+                childPath = path.child(instantiableRelation, name);
+            } else if (setRelation != null) {
+                try {
+                    childPath = path.child(setRelation, name);
+                } catch (IllegalArgumentException e) {
+                    throw new DefinitionDecodingException(setRelation.getChildDefinition(),
+                            Reason.WRONG_TYPE_INFORMATION);
+                }
+            } else {
+                // Optional managed objects are located directly beneath the
+                // parent and have a well-defined name. We need to make sure
+                // that we are handling the correct entry.
+                childPath = path.child(optionalRelation);
+                DN expectedDN = DNBuilder.create(childPath);
+                if (!dn.equals(expectedDN)) {
+                    // Doesn't apply to us.
+                    return true;
+                }
+            }
+
+            cachedManagedObject = serverContext.decode(childPath, configEntry, configEntry);
+        } catch (DecodingException e) {
+            unacceptableReason.append(e.getMessageObject());
+            return false;
         }
-      } else {
-        // Optional managed objects are located directly beneath the
-        // parent and have a well-defined name. We need to make sure
-        // that we are handling the correct entry.
-        childPath = path.child(optionalRelation);
-        DN expectedDN = DNBuilder.create(childPath);
-        if (!dn.equals(expectedDN)) {
-          // Doesn't apply to us.
-          return true;
+
+        // Give up immediately if a constraint violation occurs.
+        try {
+            cachedManagedObject.ensureIsUsable();
+        } catch (ConstraintViolationException e) {
+            generateUnacceptableReason(e.getMessages(), unacceptableReason);
+            return false;
         }
-      }
 
-      ServerManagementContext context = ServerManagementContext.getInstance();
-      cachedManagedObject = context.decode(childPath, configEntry, configEntry);
-    } catch (DecodingException e) {
-      unacceptableReason.append(e.getMessageObject());
-      return false;
+        // Let the add listener decide.
+        List<LocalizableMessage> reasons = new LinkedList<LocalizableMessage>();
+        if (listener.isConfigurationAddAcceptable(cachedManagedObject, reasons)) {
+            return true;
+        } else {
+            generateUnacceptableReason(reasons, unacceptableReason);
+            return false;
+        }
     }
 
-    // Give up immediately if a constraint violation occurs.
-    try {
-      cachedManagedObject.ensureIsUsable();
-    } catch (ConstraintViolationException e) {
-      generateUnacceptableReason(e.getMessages(), unacceptableReason);
-      return false;
+    /**
+     * Get the server managed object add listener associated with this adaptor.
+     *
+     * @return Returns the server managed object add listener associated with
+     *         this adaptor.
+     */
+    ServerManagedObjectAddListener<S> getServerManagedObjectAddListener() {
+        return listener;
     }
-
-    // Let the add listener decide.
-    List<LocalizableMessage> reasons = new LinkedList<LocalizableMessage>();
-    if (listener.isConfigurationAddAcceptable(cachedManagedObject, reasons)) {
-      return true;
-    } else {
-      generateUnacceptableReason(reasons, unacceptableReason);
-      return false;
-    }
-  }
-
-
-
-  /**
-   * Get the server managed object add listener associated with this
-   * adaptor.
-   *
-   * @return Returns the server managed object add listener associated
-   *         with this adaptor.
-   */
-  ServerManagedObjectAddListener<S> getServerManagedObjectAddListener() {
-    return listener;
-  }
 }
diff --git a/opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/server/ConfigChangeListenerAdaptor.java b/opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/server/ConfigChangeListenerAdaptor.java
index ff7c543..8092a17 100644
--- a/opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/server/ConfigChangeListenerAdaptor.java
+++ b/opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/server/ConfigChangeListenerAdaptor.java
@@ -54,13 +54,13 @@
 import org.opends.server.admin.UndefinedDefaultBehaviorProvider;
 import org.opends.server.api.ConfigChangeListener;
 import org.opends.server.api.ConfigDeleteListener;
-import org.opends.server.config.ConfigEntry;
 import org.opends.server.config.ConfigException;
-import org.opends.server.core.DirectoryServer;
+import org.opends.server.config.ConfigurationRepository;
 import org.opends.server.types.ConfigChangeResult;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.forgerock.opendj.ldap.DN;
+import org.forgerock.opendj.ldap.Entry;
 import org.forgerock.opendj.ldap.ResultCode;
 
 /**
@@ -97,7 +97,8 @@
          * @param dependencies
          *            Add dependencies names to this collection.
          */
-        public static <T> void find(ManagedObjectPath<?, ?> path, PropertyDefinition<T> pd, Collection<DN> dependencies) {
+        public static <T> void find(ManagedObjectPath<?, ?> path, PropertyDefinition<T> pd,
+                Collection<DN> dependencies) {
             Visitor<T> v = new Visitor<T>(dependencies);
             DefaultBehaviorProvider<T> db = pd.getDefaultBehaviorProvider();
             db.accept(v, path);
@@ -167,39 +168,52 @@
         }
     }
 
-    // Cached managed object between accept/apply call-backs.
+    /** Cached managed object between accept/apply call-backs. */
     private ServerManagedObject<? extends S> cachedManagedObject;
 
-    // The delete listener which is used to remove this listener and any
-    // dependencies.
+    /**
+     * The delete listener which is used to remove this listener and any
+     * dependencies.
+     */
     private final ConfigDeleteListener cleanerListener;
 
-    // The names of entries that this change listener depends on.
+    /** The names of entries that this change listener depends on. */
     private final Set<DN> dependencies;
 
-    // The listener used to notify this listener when dependency entries
-    // are modified.
+    /**
+     * The listener used to notify this listener when dependency entries are
+     * modified.
+     */
     private final ConfigChangeListener dependencyListener;
 
-    // The DN associated with this listener.
+    /** The DN associated with this listener. */
     private final DN dn;
 
-    // The underlying change listener.
+    /** The underlying change listener. */
     private final ServerManagedObjectChangeListener<? super S> listener;
 
-    // The managed object path.
+    /** The managed object path. */
     private final ManagedObjectPath<?, S> path;
 
+    /** Repository of configuration entries */
+    private final ConfigurationRepository configRepository;
+
+    private final ServerManagementContext serverContext;
+
     /**
      * Create a new configuration change listener adaptor.
      *
+     * @param serverContext
+     *            The server context.
      * @param path
      *            The managed object path.
      * @param listener
      *            The underlying change listener.
      */
-    public ConfigChangeListenerAdaptor(ManagedObjectPath<?, S> path,
-            ServerManagedObjectChangeListener<? super S> listener) {
+    public ConfigChangeListenerAdaptor(final ServerManagementContext serverContext,
+            final ManagedObjectPath<?, S> path, final ServerManagedObjectChangeListener<? super S> listener) {
+        this.serverContext = serverContext;
+        configRepository = serverContext.getConfigRepository();
         this.path = path;
         this.dn = DNBuilder.create(path);
         this.listener = listener;
@@ -211,26 +225,25 @@
         this.dependencies = new HashSet<DN>();
         this.dependencyListener = new ConfigChangeListener() {
 
-            public ConfigChangeResult applyConfigurationChange(ConfigEntry configEntry) {
-                ConfigEntry dependentConfigEntry = getConfigEntry(dn);
+            public ConfigChangeResult applyConfigurationChange(Entry configEntry) {
+                Entry dependentConfigEntry = getConfigEntry(dn);
                 if (dependentConfigEntry != null) {
                     return ConfigChangeListenerAdaptor.this.applyConfigurationChange(dependentConfigEntry);
                 } else {
                     // The dependent entry was not found.
-                    configEntry.deregisterChangeListener(this);
+                    configRepository.deregisterChangeListener(configEntry.getName(), this);
                     return new ConfigChangeResult(ResultCode.SUCCESS, false);
                 }
             }
 
-            public boolean configChangeIsAcceptable(ConfigEntry configEntry,
-                    LocalizableMessageBuilder unacceptableReason) {
-                ConfigEntry dependentConfigEntry = getConfigEntry(dn);
+            public boolean configChangeIsAcceptable(Entry configEntry, LocalizableMessageBuilder unacceptableReason) {
+                Entry dependentConfigEntry = getConfigEntry(dn);
                 if (dependentConfigEntry != null) {
                     return ConfigChangeListenerAdaptor.this.configChangeIsAcceptable(dependentConfigEntry,
                             unacceptableReason, configEntry);
                 } else {
                     // The dependent entry was not found.
-                    configEntry.deregisterChangeListener(this);
+                    configRepository.deregisterChangeListener(configEntry.getName(), this);
                     return true;
                 }
             }
@@ -246,9 +259,9 @@
             // Be careful not to register listeners against the dependent
             // entry itself.
             if (!entryDN.equals(dn)) {
-                ConfigEntry configEntry = getConfigEntry(entryDN);
+                Entry configEntry = getConfigEntry(entryDN);
                 if (configEntry != null) {
-                    configEntry.registerChangeListener(dependencyListener);
+                    configRepository.registerChangeListener(configEntry.getName(), dependencyListener);
                 }
             }
         }
@@ -258,17 +271,16 @@
         // entry is removed.
         this.cleanerListener = new ConfigDeleteListener() {
 
-            public ConfigChangeResult applyConfigurationDelete(ConfigEntry configEntry) {
+            public ConfigChangeResult applyConfigurationDelete(Entry configEntry) {
                 // Perform finalization if the deleted entry is the monitored
                 // entry.
-                if (configEntry.getDN().equals(dn)) {
+                if (configEntry.getName().equals(dn)) {
                     finalizeChangeListener();
                 }
                 return new ConfigChangeResult(ResultCode.SUCCESS, false);
             }
 
-            public boolean configDeleteIsAcceptable(ConfigEntry configEntry,
-                    LocalizableMessageBuilder unacceptableReason) {
+            public boolean configDeleteIsAcceptable(Entry configEntry, LocalizableMessageBuilder unacceptableReason) {
                 // Always acceptable.
                 return true;
             }
@@ -277,9 +289,9 @@
 
         DN parent = dn.parent();
         if (parent != null) {
-            ConfigEntry configEntry = getConfigEntry(dn.parent());
+            Entry configEntry = getConfigEntry(dn.parent());
             if (configEntry != null) {
-                configEntry.registerDeleteListener(cleanerListener);
+                configRepository.registerDeleteListener(configEntry.getName(), cleanerListener);
             }
         }
     }
@@ -287,13 +299,13 @@
     /**
      * {@inheritDoc}
      */
-    public ConfigChangeResult applyConfigurationChange(ConfigEntry configEntry) {
+    public ConfigChangeResult applyConfigurationChange(Entry configEntry) {
         // Looking at the ConfigFileHandler implementation reveals
         // that this ConfigEntry will actually be a different object to
         // the one passed in the previous call-back (it will have the same
         // content though). This configuration entry has the correct
         // listener lists.
-        cachedManagedObject.setConfigEntry(configEntry);
+        cachedManagedObject.setConfigDN(configEntry.getName());
 
         ConfigChangeResult result = listener.applyConfigurationChange(cachedManagedObject);
 
@@ -317,7 +329,7 @@
     /**
      * {@inheritDoc}
      */
-    public boolean configChangeIsAcceptable(ConfigEntry configEntry, LocalizableMessageBuilder unacceptableReason) {
+    public boolean configChangeIsAcceptable(Entry configEntry, LocalizableMessageBuilder unacceptableReason) {
         return configChangeIsAcceptable(configEntry, unacceptableReason, configEntry);
     }
 
@@ -338,11 +350,10 @@
      * @return <CODE>true</CODE> if the proposed entry contains an acceptable
      *         configuration, or <CODE>false</CODE> if it does not.
      */
-    public boolean configChangeIsAcceptable(ConfigEntry configEntry, LocalizableMessageBuilder unacceptableReason,
-            ConfigEntry newConfigEntry) {
+    public boolean configChangeIsAcceptable(Entry configEntry, LocalizableMessageBuilder unacceptableReason,
+            Entry newConfigEntry) {
         try {
-            ServerManagementContext context = ServerManagementContext.getInstance();
-            cachedManagedObject = context.decode(path, configEntry, newConfigEntry);
+            cachedManagedObject = serverContext.decode(path, configEntry, newConfigEntry);
         } catch (DecodingException e) {
             unacceptableReason.append(e.getMessageObject());
             return false;
@@ -373,17 +384,17 @@
     public void finalizeChangeListener() {
         // Remove the dependency listeners.
         for (DN dependency : dependencies) {
-            ConfigEntry listenerConfigEntry = getConfigEntry(dependency);
+            Entry listenerConfigEntry = getConfigEntry(dependency);
             if (listenerConfigEntry != null) {
-                listenerConfigEntry.deregisterChangeListener(dependencyListener);
+                configRepository.deregisterChangeListener(listenerConfigEntry.getName(), dependencyListener);
             }
         }
 
         // Now remove the cleaner listener as it will no longer be
         // needed.
-        ConfigEntry parentConfigEntry = getConfigEntry(dn.parent());
+        Entry parentConfigEntry = getConfigEntry(dn.parent());
         if (parentConfigEntry != null) {
-            parentConfigEntry.deregisterDeleteListener(cleanerListener);
+            configRepository.deregisterDeleteListener(parentConfigEntry.getName(), cleanerListener);
         }
 
     }
@@ -401,11 +412,10 @@
 
     // Returns the named configuration entry or null if it could not be
     // retrieved.
-    private ConfigEntry getConfigEntry(DN dn) {
+    private Entry getConfigEntry(DN dn) {
         try {
-            ConfigEntry configEntry = DirectoryServer.getConfigEntry(dn);
-            if (configEntry != null) {
-                return configEntry;
+            if (configRepository.hasEntry(dn)) {
+                return configRepository.getEntry(dn);
             } else {
                 adminLogger.error(ERR_ADMIN_MANAGED_OBJECT_DOES_NOT_EXIST, String.valueOf(dn));
             }
@@ -414,7 +424,6 @@
             adminLogger.error(ERR_ADMIN_CANNOT_GET_MANAGED_OBJECT, String.valueOf(dn),
                     StaticUtils.getExceptionMessage(e));
         }
-
         return null;
     }
 
diff --git a/opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/server/ConfigDeleteListenerAdaptor.java b/opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/server/ConfigDeleteListenerAdaptor.java
index 81a8c02..6edff02 100644
--- a/opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/server/ConfigDeleteListenerAdaptor.java
+++ b/opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/server/ConfigDeleteListenerAdaptor.java
@@ -43,10 +43,10 @@
 import org.opends.server.admin.SetRelationDefinition;
 import org.opends.server.admin.DefinitionDecodingException.Reason;
 import org.opends.server.api.ConfigDeleteListener;
-import org.opends.server.config.ConfigEntry;
 import org.opends.server.config.ConfigException;
 import org.opends.server.types.ConfigChangeResult;
 import org.forgerock.opendj.ldap.DN;
+import org.forgerock.opendj.ldap.Entry;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.forgerock.opendj.ldap.ResultCode;
@@ -81,10 +81,14 @@
     // The managed object path of the parent.
     private final ManagedObjectPath<?, ?> path;
 
+    private final ServerManagementContext serverContext;
+
     /**
      * Create a new configuration delete listener adaptor for an instantiable
      * relation.
      *
+     * @param serverContext
+     *            The server context.
      * @param path
      *            The managed object path of the parent.
      * @param relation
@@ -92,8 +96,9 @@
      * @param listener
      *            The underlying delete listener.
      */
-    public ConfigDeleteListenerAdaptor(ManagedObjectPath<?, ?> path, InstantiableRelationDefinition<?, S> relation,
-            ServerManagedObjectDeleteListener<S> listener) {
+    public ConfigDeleteListenerAdaptor(ServerManagementContext serverContext, ManagedObjectPath<?, ?> path,
+            InstantiableRelationDefinition<?, S> relation, ServerManagedObjectDeleteListener<S> listener) {
+        this.serverContext = serverContext;
         this.path = path;
         this.optionalRelation = null;
         this.instantiableRelation = relation;
@@ -105,7 +110,7 @@
     /**
      * Create a new configuration delete listener adaptor for an optional
      * relation.
-     *
+     * @param serverContext TODO
      * @param path
      *            The managed object path of the parent.
      * @param relation
@@ -113,8 +118,9 @@
      * @param listener
      *            The underlying delete listener.
      */
-    public ConfigDeleteListenerAdaptor(ManagedObjectPath<?, ?> path, OptionalRelationDefinition<?, S> relation,
-            ServerManagedObjectDeleteListener<S> listener) {
+    public ConfigDeleteListenerAdaptor(ServerManagementContext serverContext, ManagedObjectPath<?, ?> path,
+            OptionalRelationDefinition<?, S> relation, ServerManagedObjectDeleteListener<S> listener) {
+        this.serverContext = serverContext;
         this.path = path;
         this.optionalRelation = relation;
         this.instantiableRelation = null;
@@ -125,7 +131,7 @@
 
     /**
      * Create a new configuration delete listener adaptor for an set relation.
-     *
+     * @param serverContext TODO
      * @param path
      *            The managed object path of the parent.
      * @param relation
@@ -133,8 +139,9 @@
      * @param listener
      *            The underlying delete listener.
      */
-    public ConfigDeleteListenerAdaptor(ManagedObjectPath<?, ?> path, SetRelationDefinition<?, S> relation,
-            ServerManagedObjectDeleteListener<S> listener) {
+    public ConfigDeleteListenerAdaptor(ServerManagementContext serverContext, ManagedObjectPath<?, ?> path,
+            SetRelationDefinition<?, S> relation, ServerManagedObjectDeleteListener<S> listener) {
+        this.serverContext = serverContext;
         this.path = path;
         this.optionalRelation = null;
         this.instantiableRelation = null;
@@ -146,14 +153,15 @@
     /**
      * {@inheritDoc}
      */
-    public ConfigChangeResult applyConfigurationDelete(ConfigEntry configEntry) {
+    @Override
+    public ConfigChangeResult applyConfigurationDelete(Entry configEntry) {
         if (optionalRelation != null) {
             // Optional managed objects are located directly beneath the
             // parent and have a well-defined name. We need to make sure
             // that we are handling the correct entry.
             ManagedObjectPath<?, ?> childPath = path.child(optionalRelation);
             DN expectedDN = DNBuilder.create(childPath);
-            if (!configEntry.getDN().equals(expectedDN)) {
+            if (!configEntry.getName().equals(expectedDN)) {
                 // Doesn't apply to us.
                 return new ConfigChangeResult(ResultCode.SUCCESS, false);
             }
@@ -183,8 +191,8 @@
     /**
      * {@inheritDoc}
      */
-    public boolean configDeleteIsAcceptable(ConfigEntry configEntry, LocalizableMessageBuilder unacceptableReason) {
-        DN dn = configEntry.getDN();
+    public boolean configDeleteIsAcceptable(Entry configEntry, LocalizableMessageBuilder unacceptableReason) {
+        DN dn = configEntry.getName();
         String name = dn.rdn().getFirstAVA().getAttributeValue().toString().trim();
 
         try {
@@ -210,8 +218,7 @@
                 }
             }
 
-            ServerManagementContext context = ServerManagementContext.getInstance();
-            cachedManagedObject = context.decode(childPath, configEntry);
+            cachedManagedObject = serverContext.decode(childPath, configEntry);
         } catch (DecodingException e) {
             unacceptableReason.append(e.getMessageObject());
             return false;
diff --git a/opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/server/DelayedConfigAddListener.java b/opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/server/DelayedConfigAddListener.java
index eb17266..6188729 100644
--- a/opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/server/DelayedConfigAddListener.java
+++ b/opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/server/DelayedConfigAddListener.java
@@ -27,13 +27,13 @@
 
 import org.opends.server.api.ConfigAddListener;
 import org.opends.server.api.ConfigDeleteListener;
-import org.opends.server.config.ConfigEntry;
 import org.opends.server.config.ConfigException;
-import org.opends.server.core.DirectoryServer;
+import org.opends.server.config.ConfigurationRepository;
 import org.opends.server.types.ConfigChangeResult;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.forgerock.opendj.ldap.DN;
+import org.forgerock.opendj.ldap.Entry;
 import org.forgerock.opendj.ldap.ResultCode;
 import org.forgerock.i18n.LocalizableMessageBuilder;
 
@@ -62,6 +62,8 @@
     // registered).
     private final ConfigDeleteListener delayedDeleteListener;
 
+    private final ConfigurationRepository configRepository;
+
     /**
      * Create a new delayed add listener which will register an add listener
      * with the specified entry when it is added.
@@ -72,12 +74,14 @@
      * @param addListener
      *            The add listener to be added to the subordinate entry when it
      *            is added.
+     * @param configRepository TODO
      */
-    public DelayedConfigAddListener(DN child, ConfigAddListener addListener) {
+    public DelayedConfigAddListener(DN child, ConfigAddListener addListener, ConfigurationRepository configRepository) {
         this.parent = child.parent();
         this.child = child;
         this.delayedAddListener = addListener;
         this.delayedDeleteListener = null;
+        this.configRepository = configRepository;
     }
 
     /**
@@ -90,34 +94,35 @@
      * @param deleteListener
      *            The delete listener to be added to the subordinate entry when
      *            it is added.
+     * @param configRepository TODO
      */
-    public DelayedConfigAddListener(DN child, ConfigDeleteListener deleteListener) {
+    public DelayedConfigAddListener(DN child, ConfigDeleteListener deleteListener, ConfigurationRepository configRepository) {
         this.parent = child.parent();
         this.child = child;
         this.delayedAddListener = null;
+        this.configRepository = configRepository;
         this.delayedDeleteListener = deleteListener;
     }
 
     /**
      * {@inheritDoc}
      */
-    public ConfigChangeResult applyConfigurationAdd(ConfigEntry configEntry) {
-        if (configEntry.getDN().equals(child)) {
+    public ConfigChangeResult applyConfigurationAdd(Entry configEntry) {
+        if (configEntry.getName().equals(child)) {
             // The subordinate entry matched our criteria so register the
             // listener(s).
             if (delayedAddListener != null) {
-                configEntry.registerAddListener(delayedAddListener);
+                configRepository.registerAddListener(configEntry.getName(), delayedAddListener);
             }
 
             if (delayedDeleteListener != null) {
-                configEntry.registerDeleteListener(delayedDeleteListener);
+                configRepository.registerDeleteListener(configEntry.getName(), delayedDeleteListener);
             }
 
-            // We are no longer needed.
             try {
-                ConfigEntry myEntry = DirectoryServer.getConfigEntry(parent);
-                if (myEntry != null) {
-                    myEntry.deregisterAddListener(this);
+                // We are no longer needed.
+                if (configRepository.hasEntry(parent)) {
+                    configRepository.deregisterAddListener(parent, this);
                 }
             } catch (ConfigException e) {
                 debugLogger.trace("Unable to deregister add listener", e);
@@ -132,7 +137,7 @@
     /**
      * {@inheritDoc}
      */
-    public boolean configAddIsAcceptable(ConfigEntry configEntry, LocalizableMessageBuilder unacceptableReason) {
+    public boolean configAddIsAcceptable(Entry configEntry, LocalizableMessageBuilder unacceptableReason) {
         // Always acceptable.
         return true;
     }
diff --git a/opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/server/ServerManagedObject.java b/opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/server/ServerManagedObject.java
index 1ea7c61..5d82dee 100644
--- a/opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/server/ServerManagedObject.java
+++ b/opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/server/ServerManagedObject.java
@@ -27,7 +27,6 @@
 package org.opends.server.admin.server;
 
 import static com.forgerock.opendj.ldap.AdminMessages.*;
-import static com.forgerock.opendj.util.StaticUtils.*;
 
 import java.util.Collections;
 import java.util.LinkedList;
@@ -52,13 +51,13 @@
 import org.opends.server.api.ConfigAddListener;
 import org.opends.server.api.ConfigChangeListener;
 import org.opends.server.api.ConfigDeleteListener;
-import org.opends.server.config.ConfigEntry;
 import org.opends.server.config.ConfigException;
-import org.opends.server.core.DirectoryServer;
-import org.opends.server.util.DynamicConstants;
+import org.opends.server.config.ConfigurationRepository;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.forgerock.opendj.util.Pair;
+
 
 /**
  * A server-side managed object.
@@ -71,21 +70,21 @@
 
     private static final Logger logger = LoggerFactory.getLogger(ServerManagedObject.class);
 
-    // The configuration entry associated with this server managed
-    // object (null if root).
-    private ConfigEntry configEntry;
+    /**
+     * The DN of configuration entry associated with this server managed object,
+     * which is {@code null} for root.
+     */
+    private DN configDN;
 
-    // The management context.
-    private final ServerManagementContext context = ServerManagementContext.getInstance();
+    private final ServerManagementContext serverContext;
 
-    // The managed object's definition.
+    private final ConfigurationRepository configRepository;
+
     private final ManagedObjectDefinition<?, S> definition;
 
-    // The managed object path identifying this managed object's
-    // location.
+    /** The managed object path identifying this managed object's location */
     private final ManagedObjectPath<?, S> path;
 
-    // The managed object's properties.
     private final Map<PropertyDefinition<?>, SortedSet<?>> properties;
 
     /**
@@ -97,15 +96,20 @@
      *            The managed object definition.
      * @param properties
      *            The managed object's properties.
-     * @param configEntry
+     * @param configDN
      *            The configuration entry associated with the managed object.
+     * @param context
+     *            The server management context.
      */
-    ServerManagedObject(ManagedObjectPath<?, S> path, ManagedObjectDefinition<?, S> d,
-            Map<PropertyDefinition<?>, SortedSet<?>> properties, ConfigEntry configEntry) {
+    ServerManagedObject(final ManagedObjectPath<?, S> path, final ManagedObjectDefinition<?, S> d,
+            final Map<PropertyDefinition<?>, SortedSet<?>> properties, final DN configDN,
+            final ServerManagementContext context) {
         this.definition = d;
         this.path = path;
         this.properties = properties;
-        this.configEntry = configEntry;
+        this.configDN = configDN;
+        this.serverContext = context;
+        this.configRepository = context.getConfigRepository();
     }
 
     /**
@@ -124,7 +128,6 @@
     public <M extends Configuration> void deregisterAddListener(InstantiableRelationDefinition<?, M> d,
             ConfigurationAddListener<M> listener) throws IllegalArgumentException {
         validateRelationDefinition(d);
-
         DN baseDN = DNBuilder.create(path, d);
         deregisterAddListener(baseDN, listener);
     }
@@ -145,7 +148,6 @@
     public <M extends Configuration> void deregisterAddListener(InstantiableRelationDefinition<?, M> d,
             ServerManagedObjectAddListener<M> listener) throws IllegalArgumentException {
         validateRelationDefinition(d);
-
         DN baseDN = DNBuilder.create(path, d);
         deregisterAddListener(baseDN, listener);
     }
@@ -166,7 +168,6 @@
     public <M extends Configuration> void deregisterAddListener(OptionalRelationDefinition<?, M> d,
             ConfigurationAddListener<M> listener) throws IllegalArgumentException {
         validateRelationDefinition(d);
-
         DN baseDN = DNBuilder.create(path, d).parent();
         deregisterAddListener(baseDN, listener);
     }
@@ -187,7 +188,6 @@
     public <M extends Configuration> void deregisterAddListener(OptionalRelationDefinition<?, M> d,
             ServerManagedObjectAddListener<M> listener) throws IllegalArgumentException {
         validateRelationDefinition(d);
-
         DN baseDN = DNBuilder.create(path, d).parent();
         deregisterAddListener(baseDN, listener);
     }
@@ -208,7 +208,6 @@
     public <M extends Configuration> void deregisterAddListener(SetRelationDefinition<?, M> d,
             ConfigurationAddListener<M> listener) throws IllegalArgumentException {
         validateRelationDefinition(d);
-
         DN baseDN = DNBuilder.create(path, d);
         deregisterAddListener(baseDN, listener);
     }
@@ -229,7 +228,6 @@
     public <M extends Configuration> void deregisterAddListener(SetRelationDefinition<?, M> d,
             ServerManagedObjectAddListener<M> listener) throws IllegalArgumentException {
         validateRelationDefinition(d);
-
         DN baseDN = DNBuilder.create(path, d);
         deregisterAddListener(baseDN, listener);
     }
@@ -241,7 +239,7 @@
      *            The configuration change listener.
      */
     public void deregisterChangeListener(ConfigurationChangeListener<? super S> listener) {
-        for (ConfigChangeListener l : configEntry.getChangeListeners()) {
+        for (ConfigChangeListener l : configRepository.getChangeListeners(configDN)) {
             if (l instanceof ConfigChangeListenerAdaptor) {
                 ConfigChangeListenerAdaptor<?> adaptor = (ConfigChangeListenerAdaptor<?>) l;
                 ServerManagedObjectChangeListener<?> l2 = adaptor.getServerManagedObjectChangeListener();
@@ -249,7 +247,7 @@
                     ServerManagedObjectChangeListenerAdaptor<?> adaptor2 = (ServerManagedObjectChangeListenerAdaptor<?>) l2;
                     if (adaptor2.getConfigurationChangeListener() == listener) {
                         adaptor.finalizeChangeListener();
-                        configEntry.deregisterChangeListener(adaptor);
+                        configRepository.deregisterChangeListener(configDN, adaptor);
                     }
                 }
             }
@@ -263,12 +261,12 @@
      *            The server managed object change listener.
      */
     public void deregisterChangeListener(ServerManagedObjectChangeListener<? super S> listener) {
-        for (ConfigChangeListener l : configEntry.getChangeListeners()) {
+        for (ConfigChangeListener l : configRepository.getChangeListeners(configDN)) {
             if (l instanceof ConfigChangeListenerAdaptor) {
                 ConfigChangeListenerAdaptor<?> adaptor = (ConfigChangeListenerAdaptor<?>) l;
                 if (adaptor.getServerManagedObjectChangeListener() == listener) {
                     adaptor.finalizeChangeListener();
-                    configEntry.deregisterChangeListener(adaptor);
+                    configRepository.deregisterChangeListener(configDN, adaptor);
                 }
             }
         }
@@ -421,7 +419,7 @@
     public <M extends Configuration> ServerManagedObject<? extends M> getChild(InstantiableRelationDefinition<?, M> d,
             String name) throws IllegalArgumentException, ConfigException {
         validateRelationDefinition(d);
-        return context.getManagedObject(path.child(d, name));
+        return serverContext.getManagedObject(path.child(d, name));
     }
 
     /**
@@ -443,7 +441,7 @@
     public <M extends Configuration> ServerManagedObject<? extends M> getChild(OptionalRelationDefinition<?, M> d)
             throws IllegalArgumentException, ConfigException {
         validateRelationDefinition(d);
-        return context.getManagedObject(path.child(d));
+        return serverContext.getManagedObject(path.child(d));
     }
 
     /**
@@ -470,7 +468,7 @@
             String name) throws IllegalArgumentException, ConfigException {
         validateRelationDefinition(d);
 
-        return context.getManagedObject(path.child(d, name));
+        return serverContext.getManagedObject(path.child(d, name));
     }
 
     /**
@@ -492,7 +490,16 @@
     public <M extends Configuration> ServerManagedObject<? extends M> getChild(SingletonRelationDefinition<?, M> d)
             throws IllegalArgumentException, ConfigException {
         validateRelationDefinition(d);
-        return context.getManagedObject(path.child(d));
+        return serverContext.getManagedObject(path.child(d));
+    }
+
+    /**
+     * Returns the server management context used by this object.
+     *
+     * @return the context
+     */
+    public ServerManagementContext getServerContext() {
+        return serverContext;
     }
 
     /**
@@ -511,8 +518,8 @@
      *         managed object, or an null DN if this is the root managed object.
      */
     public DN getDN() {
-        if (configEntry != null) {
-            return configEntry.getDN();
+        if (configDN != null) {
+            return configDN;
         } else {
             return DN.rootDN();
         }
@@ -600,7 +607,7 @@
      */
     public boolean hasChild(OptionalRelationDefinition<?, ?> d) throws IllegalArgumentException {
         validateRelationDefinition(d);
-        return context.managedObjectExists(path.child(d));
+        return serverContext.managedObjectExists(path.child(d));
     }
 
     /**
@@ -616,7 +623,7 @@
      */
     public String[] listChildren(InstantiableRelationDefinition<?, ?> d) throws IllegalArgumentException {
         validateRelationDefinition(d);
-        return context.listManagedObjects(path, d);
+        return serverContext.listManagedObjects(path, d);
     }
 
     /**
@@ -632,7 +639,7 @@
      */
     public String[] listChildren(SetRelationDefinition<?, ?> d) throws IllegalArgumentException {
         validateRelationDefinition(d);
-        return context.listManagedObjects(path, d);
+        return serverContext.listManagedObjects(path, d);
     }
 
     /**
@@ -678,7 +685,7 @@
             ServerManagedObjectAddListener<M> listener) throws IllegalArgumentException, ConfigException {
         validateRelationDefinition(d);
         DN baseDN = DNBuilder.create(path, d);
-        ConfigAddListener adaptor = new ConfigAddListenerAdaptor<M>(path, d, listener);
+        ConfigAddListener adaptor = new ConfigAddListenerAdaptor<M>(serverContext, path, d, listener);
         registerAddListener(baseDN, adaptor);
     }
 
@@ -725,7 +732,7 @@
             ServerManagedObjectAddListener<M> listener) throws IllegalArgumentException, ConfigException {
         validateRelationDefinition(d);
         DN baseDN = DNBuilder.create(path, d).parent();
-        ConfigAddListener adaptor = new ConfigAddListenerAdaptor<M>(path, d, listener);
+        ConfigAddListener adaptor = new ConfigAddListenerAdaptor<M>(serverContext, path, d, listener);
         registerAddListener(baseDN, adaptor);
     }
 
@@ -772,7 +779,7 @@
             ServerManagedObjectAddListener<M> listener) throws IllegalArgumentException, ConfigException {
         validateRelationDefinition(d);
         DN baseDN = DNBuilder.create(path, d);
-        ConfigAddListener adaptor = new ConfigAddListenerAdaptor<M>(path, d, listener);
+        ConfigAddListener adaptor = new ConfigAddListenerAdaptor<M>(serverContext, path, d, listener);
         registerAddListener(baseDN, adaptor);
     }
 
@@ -793,8 +800,9 @@
      *            The server managed object change listener.
      */
     public void registerChangeListener(ServerManagedObjectChangeListener<? super S> listener) {
-        ConfigChangeListener adaptor = new ConfigChangeListenerAdaptor<S>(path, listener);
-        configEntry.registerChangeListener(adaptor);
+
+        ConfigChangeListener adaptor = new ConfigChangeListenerAdaptor<S>(serverContext, path, listener);
+        configRepository.registerChangeListener(configDN, adaptor);
 
         // TODO : go toward this
         // Entry entry;
@@ -860,7 +868,7 @@
             ServerManagedObjectDeleteListener<M> listener) throws IllegalArgumentException, ConfigException {
         validateRelationDefinition(d);
         DN baseDN = DNBuilder.create(path, d);
-        ConfigDeleteListener adaptor = new ConfigDeleteListenerAdaptor<M>(path, d, listener);
+        ConfigDeleteListener adaptor = new ConfigDeleteListenerAdaptor<M>(serverContext, path, d, listener);
         registerDeleteListener(baseDN, adaptor);
     }
 
@@ -907,7 +915,7 @@
             ServerManagedObjectDeleteListener<M> listener) throws IllegalArgumentException, ConfigException {
         validateRelationDefinition(d);
         DN baseDN = DNBuilder.create(path, d).parent();
-        ConfigDeleteListener adaptor = new ConfigDeleteListenerAdaptor<M>(path, d, listener);
+        ConfigDeleteListener adaptor = new ConfigDeleteListenerAdaptor<M>(serverContext, path, d, listener);
         registerDeleteListener(baseDN, adaptor);
     }
 
@@ -954,7 +962,7 @@
             ServerManagedObjectDeleteListener<M> listener) throws IllegalArgumentException, ConfigException {
         validateRelationDefinition(d);
         DN baseDN = DNBuilder.create(path, d);
-        ConfigDeleteListener adaptor = new ConfigDeleteListenerAdaptor<M>(path, d, listener);
+        ConfigDeleteListener adaptor = new ConfigDeleteListenerAdaptor<M>(serverContext, path, d, listener);
         registerDeleteListener(baseDN, adaptor);
     }
 
@@ -1012,30 +1020,29 @@
     }
 
     /**
-     * Update the config entry associated with this server managed object. This
+     * Update the config DN associated with this server managed object. This
      * is only intended to be used by change listener call backs in order to
-     * update the managed object with the correct config entry.
+     * update the managed object with the correct config DN.
      *
-     * @param configEntry
-     *            The configuration entry.
+     * @param configDN
+     *            The DN of the underlying configuration entry.
      */
-    void setConfigEntry(ConfigEntry configEntry) {
-        this.configEntry = configEntry;
+    void setConfigDN(DN configDN) {
+        this.configDN = configDN;
     }
 
     // Deregister an add listener.
     private <M extends Configuration> void deregisterAddListener(DN baseDN, ConfigurationAddListener<M> listener) {
         try {
-            ConfigEntry configEntry = getListenerConfigEntry(baseDN);
-            if (configEntry != null) {
-                for (ConfigAddListener l : configEntry.getAddListeners()) {
-                    if (l instanceof ConfigAddListenerAdaptor) {
-                        ConfigAddListenerAdaptor<?> adaptor = (ConfigAddListenerAdaptor<?>) l;
-                        ServerManagedObjectAddListener<?> l2 = adaptor.getServerManagedObjectAddListener();
-                        if (l2 instanceof ServerManagedObjectAddListenerAdaptor<?>) {
-                            ServerManagedObjectAddListenerAdaptor<?> adaptor2 = (ServerManagedObjectAddListenerAdaptor<?>) l2;
+            if (configRepository.hasEntry(baseDN)) {
+                for (ConfigAddListener configListener : configRepository.getAddListeners(baseDN)) {
+                    if (configListener instanceof ConfigAddListenerAdaptor) {
+                        ConfigAddListenerAdaptor<?> adaptor = (ConfigAddListenerAdaptor<?>) configListener;
+                        ServerManagedObjectAddListener<?> smoListener = adaptor.getServerManagedObjectAddListener();
+                        if (smoListener instanceof ServerManagedObjectAddListenerAdaptor<?>) {
+                            ServerManagedObjectAddListenerAdaptor<?> adaptor2 = (ServerManagedObjectAddListenerAdaptor<?>) smoListener;
                             if (adaptor2.getConfigurationAddListener() == listener) {
-                                configEntry.deregisterAddListener(adaptor);
+                                configRepository.deregisterAddListener(baseDN, adaptor);
                             }
                         }
                     }
@@ -1054,13 +1061,12 @@
     // Deregister an add listener.
     private <M extends Configuration> void deregisterAddListener(DN baseDN, ServerManagedObjectAddListener<M> listener) {
         try {
-            ConfigEntry configEntry = getListenerConfigEntry(baseDN);
-            if (configEntry != null) {
-                for (ConfigAddListener l : configEntry.getAddListeners()) {
-                    if (l instanceof ConfigAddListenerAdaptor) {
-                        ConfigAddListenerAdaptor<?> adaptor = (ConfigAddListenerAdaptor<?>) l;
+            if (configRepository.hasEntry(baseDN)) {
+                for (ConfigAddListener configListener : configRepository.getAddListeners(baseDN)) {
+                    if (configListener instanceof ConfigAddListenerAdaptor) {
+                        ConfigAddListenerAdaptor<?> adaptor = (ConfigAddListenerAdaptor<?>) configListener;
                         if (adaptor.getServerManagedObjectAddListener() == listener) {
-                            configEntry.deregisterAddListener(adaptor);
+                            configRepository.deregisterAddListener(baseDN, adaptor);
                         }
                     }
                 }
@@ -1075,19 +1081,62 @@
         }
     }
 
+    /**
+     * Convenience method to retrieve the initial listener and its intermediate
+     * adaptor from the provided configListener.
+     *
+     * @param <T>
+     *            Type of the configuration.
+     * @param configListener
+     *            Listener from wich to extract the initial listener.
+     * @return a pair of (intermediate adaptor, intermediate listener) or
+     *         {@code Pair.EMPTY} if listener can't be extracted
+     */
+    static <T extends Configuration> Pair<ConfigAddListenerAdaptor<T>, ConfigurationAddListener<T>>
+        extractInitialListener(ConfigAddListener configListener) {
+        Pair<ConfigAddListenerAdaptor<T>, ServerManagedObjectAddListener<T>> pair =
+                extractIntermediateListener(configListener);
+        if (!pair.equals(Pair.EMPTY) && pair.getSecond() instanceof ServerManagedObjectAddListenerAdaptor) {
+            ServerManagedObjectAddListenerAdaptor<T> adaptor2 = (ServerManagedObjectAddListenerAdaptor<T>)
+                    pair.getSecond();
+            return Pair.of(pair.getFirst(), adaptor2.getConfigurationAddListener());
+        }
+        return Pair.empty();
+    }
+
+    /**
+     * Convenience method to retrieve the intermediate listener and its
+     * intermediate adaptor from the provided configListener.
+     *
+     * @param <T>
+     *            Type of the configuration.
+     * @param configListener
+     *            Listener from wich to extract the initial listener.
+     * @return a pair of (intermediate adaptor, initial listener) or
+     *         {@code Pair.EMPTY} if listener can't be extracted
+     */
+    @SuppressWarnings("unchecked")
+    static <T extends Configuration> Pair<ConfigAddListenerAdaptor<T>, ServerManagedObjectAddListener<T>>
+        extractIntermediateListener(ConfigAddListener configListener) {
+        if (configListener instanceof ConfigAddListenerAdaptor) {
+            ConfigAddListenerAdaptor<T> adaptor = ((ConfigAddListenerAdaptor<T>) configListener);
+            return Pair.of(adaptor, adaptor.getServerManagedObjectAddListener());
+        }
+        return Pair.empty();
+    }
+
     // Deregister a delete listener.
     private <M extends Configuration> void deregisterDeleteListener(DN baseDN, ConfigurationDeleteListener<M> listener) {
         try {
-            ConfigEntry configEntry = getListenerConfigEntry(baseDN);
-            if (configEntry != null) {
-                for (ConfigDeleteListener l : configEntry.getDeleteListeners()) {
+            if (configRepository.hasEntry(baseDN)) {
+                for (ConfigDeleteListener l : configRepository.getDeleteListeners(baseDN)) {
                     if (l instanceof ConfigDeleteListenerAdaptor) {
                         ConfigDeleteListenerAdaptor<?> adaptor = (ConfigDeleteListenerAdaptor<?>) l;
                         ServerManagedObjectDeleteListener<?> l2 = adaptor.getServerManagedObjectDeleteListener();
                         if (l2 instanceof ServerManagedObjectDeleteListenerAdaptor<?>) {
                             ServerManagedObjectDeleteListenerAdaptor<?> adaptor2 = (ServerManagedObjectDeleteListenerAdaptor<?>) l2;
                             if (adaptor2.getConfigurationDeleteListener() == listener) {
-                                configEntry.deregisterDeleteListener(adaptor);
+                                configRepository.deregisterDeleteListener(baseDN, adaptor);
                             }
                         }
                     }
@@ -1107,13 +1156,12 @@
     private <M extends Configuration> void deregisterDeleteListener(DN baseDN,
             ServerManagedObjectDeleteListener<M> listener) {
         try {
-            ConfigEntry configEntry = getListenerConfigEntry(baseDN);
-            if (configEntry != null) {
-                for (ConfigDeleteListener l : configEntry.getDeleteListeners()) {
+            if (configRepository.hasEntry(baseDN)) {
+                for (ConfigDeleteListener l : configRepository.getDeleteListeners(baseDN)) {
                     if (l instanceof ConfigDeleteListenerAdaptor) {
                         ConfigDeleteListenerAdaptor<?> adaptor = (ConfigDeleteListenerAdaptor<?>) l;
                         if (adaptor.getServerManagedObjectDeleteListener() == listener) {
-                            configEntry.deregisterDeleteListener(adaptor);
+                            configRepository.deregisterDeleteListener(baseDN, adaptor);
                         }
                     }
                 }
@@ -1128,34 +1176,15 @@
         }
     }
 
-    // Gets a config entry required for a listener and throws a config
-    // exception on failure or returns null if the entry does not exist.
-    private ConfigEntry getListenerConfigEntry(DN dn) throws ConfigException {
-        // Attempt to retrieve the listener base entry.
-        ConfigEntry configEntry;
-        try {
-            configEntry = DirectoryServer.getConfigEntry(dn);
-        } catch (ConfigException e) {
-            logger.trace("Unable to get listener base entry", e);
-            LocalizableMessage message = ERR_ADMIN_CANNOT_GET_LISTENER_BASE.get(String.valueOf(dn),
-                    stackTraceToSingleLineString(e, DynamicConstants.DEBUG_BUILD));
-            throw new ConfigException(message, e);
-        }
-
-        return configEntry;
-    }
-
     // Register an instantiable or optional relation add listener.
     private void registerAddListener(DN baseDN, ConfigAddListener adaptor) throws IllegalArgumentException,
             ConfigException {
-        ConfigEntry relationEntry = getListenerConfigEntry(baseDN);
-
-        if (relationEntry != null) {
-            relationEntry.registerAddListener(adaptor);
+        if (configRepository.hasEntry(baseDN)) {
+            configRepository.registerAddListener(baseDN, adaptor);
         } else {
-            // The relation entry does not exist yet so register a delayed
-            // add listener.
-            ConfigAddListener delayedListener = new DelayedConfigAddListener(baseDN, adaptor);
+            // The relation entry does not exist yet
+            // so register a delayed add listener.
+            ConfigAddListener delayedListener = new DelayedConfigAddListener(baseDN, adaptor, configRepository);
             registerDelayedListener(baseDN, delayedListener);
         }
     }
@@ -1163,14 +1192,15 @@
     // Register a delayed listener with the nearest existing parent
     // entry to the provided base DN.
     private void registerDelayedListener(DN baseDN, ConfigAddListener delayedListener) throws ConfigException {
-        DN parentDN = baseDN.parent();
-        while (parentDN != null) {
-            ConfigEntry relationEntry = getListenerConfigEntry(parentDN);
-            if (relationEntry == null) {
-                delayedListener = new DelayedConfigAddListener(parentDN, delayedListener);
-                parentDN = parentDN.parent();
+        DN currentDN = baseDN.parent();
+        DN previousDN = currentDN;
+        while (currentDN != null) {
+            if (!configRepository.hasEntry(currentDN)) {
+                delayedListener = new DelayedConfigAddListener(currentDN, delayedListener, configRepository);
+                previousDN = currentDN;
+                currentDN = currentDN.parent();
             } else {
-                relationEntry.registerAddListener(delayedListener);
+                configRepository.registerAddListener(previousDN, delayedListener);
                 return;
             }
         }
@@ -1187,30 +1217,28 @@
         DN parentDN = baseDN.parent();
         int delayWrappers = 0;
         while (parentDN != null) {
-            ConfigEntry relationEntry = getListenerConfigEntry(parentDN);
-            if (relationEntry == null) {
+            if (!configRepository.hasEntry(parentDN)) {
                 parentDN = parentDN.parent();
                 delayWrappers++;
             } else {
-                for (ConfigAddListener l : relationEntry.getAddListeners()) {
-                    if (l instanceof DelayedConfigAddListener) {
-                        DelayedConfigAddListener delayListener = (DelayedConfigAddListener) l;
+                for (ConfigAddListener configListener : configRepository.getAddListeners(parentDN)) {
+                    if (configListener instanceof DelayedConfigAddListener) {
+                        DelayedConfigAddListener delayListener = (DelayedConfigAddListener) configListener;
                         ConfigAddListener wrappedListener;
 
                         int i = delayWrappers;
                         for (; i > 0; i--) {
                             wrappedListener = delayListener.getDelayedAddListener();
                             if (wrappedListener != null && wrappedListener instanceof DelayedConfigAddListener) {
-                                delayListener = (DelayedConfigAddListener) l;
+                                delayListener = (DelayedConfigAddListener) configListener;
                             } else {
                                 break;
                             }
                         }
 
                         if (i > 0) {
-                            // There are not enough level of wrapping so this
-                            // can't be
-                            // the listener we are looking for.
+                            // There are not enough level of wrapping
+                            // so this can't be the listener we are looking for.
                             continue;
                         }
 
@@ -1222,7 +1250,7 @@
                             if (l2 instanceof ServerManagedObjectAddListenerAdaptor<?>) {
                                 ServerManagedObjectAddListenerAdaptor<?> adaptor2 = (ServerManagedObjectAddListenerAdaptor<?>) l2;
                                 if (adaptor2.getConfigurationAddListener() == listener) {
-                                    relationEntry.deregisterAddListener(l);
+                                    configRepository.deregisterAddListener(parentDN, configListener);
                                 }
                             }
                         }
@@ -1240,12 +1268,11 @@
         DN parentDN = baseDN.parent();
         int delayWrappers = 0;
         while (parentDN != null) {
-            ConfigEntry relationEntry = getListenerConfigEntry(parentDN);
-            if (relationEntry == null) {
+            if (!configRepository.hasEntry(parentDN)) {
                 parentDN = parentDN.parent();
                 delayWrappers++;
             } else {
-                for (ConfigAddListener l : relationEntry.getAddListeners()) {
+                for (ConfigAddListener l : configRepository.getAddListeners(parentDN)) {
                     if (l instanceof DelayedConfigAddListener) {
                         DelayedConfigAddListener delayListener = (DelayedConfigAddListener) l;
                         ConfigAddListener wrappedListener;
@@ -1261,9 +1288,8 @@
                         }
 
                         if (i > 0) {
-                            // There are not enough level of wrapping so this
-                            // can't be
-                            // the listener we are looking for.
+                            // There are not enough level of wrapping
+                            // so this can't be the listener we are looking for.
                             continue;
                         }
 
@@ -1275,7 +1301,7 @@
                             if (l2 instanceof ServerManagedObjectDeleteListenerAdaptor<?>) {
                                 ServerManagedObjectDeleteListenerAdaptor<?> adaptor2 = (ServerManagedObjectDeleteListenerAdaptor<?>) l2;
                                 if (adaptor2.getConfigurationDeleteListener() == listener) {
-                                    relationEntry.deregisterAddListener(l);
+                                    configRepository.deregisterAddListener(parentDN, l);
                                 }
                             }
                         }
@@ -1293,30 +1319,28 @@
         DN parentDN = baseDN.parent();
         int delayWrappers = 0;
         while (parentDN != null) {
-            ConfigEntry relationEntry = getListenerConfigEntry(parentDN);
-            if (relationEntry == null) {
+            if (!configRepository.hasEntry(parentDN)) {
                 parentDN = parentDN.parent();
                 delayWrappers++;
             } else {
-                for (ConfigAddListener l : relationEntry.getAddListeners()) {
-                    if (l instanceof DelayedConfigAddListener) {
-                        DelayedConfigAddListener delayListener = (DelayedConfigAddListener) l;
+                for (ConfigAddListener configListener : configRepository.getAddListeners(parentDN)) {
+                    if (configListener instanceof DelayedConfigAddListener) {
+                        DelayedConfigAddListener delayListener = (DelayedConfigAddListener) configListener;
                         ConfigAddListener wrappedListener;
 
                         int i = delayWrappers;
                         for (; i > 0; i--) {
                             wrappedListener = delayListener.getDelayedAddListener();
                             if (wrappedListener != null && wrappedListener instanceof DelayedConfigAddListener) {
-                                delayListener = (DelayedConfigAddListener) l;
+                                delayListener = (DelayedConfigAddListener) configListener;
                             } else {
                                 break;
                             }
                         }
 
                         if (i > 0) {
-                            // There are not enough level of wrapping so this
-                            // can't be
-                            // the listener we are looking for.
+                            // There are not enough level of wrapping
+                            // so this can't be the listener we are looking for.
                             continue;
                         }
 
@@ -1325,7 +1349,7 @@
                         if (delayedListener != null && delayedListener instanceof ConfigAddListenerAdaptor) {
                             ConfigAddListenerAdaptor<?> adaptor = (ConfigAddListenerAdaptor<?>) delayedListener;
                             if (adaptor.getServerManagedObjectAddListener() == listener) {
-                                relationEntry.deregisterAddListener(l);
+                                configRepository.deregisterAddListener(parentDN, configListener);
                             }
                         }
                     }
@@ -1342,30 +1366,28 @@
         DN parentDN = baseDN.parent();
         int delayWrappers = 0;
         while (parentDN != null) {
-            ConfigEntry relationEntry = getListenerConfigEntry(parentDN);
-            if (relationEntry == null) {
+            if (!configRepository.hasEntry(parentDN)) {
                 parentDN = parentDN.parent();
                 delayWrappers++;
             } else {
-                for (ConfigAddListener l : relationEntry.getAddListeners()) {
-                    if (l instanceof DelayedConfigAddListener) {
-                        DelayedConfigAddListener delayListener = (DelayedConfigAddListener) l;
+                for (ConfigAddListener configListener : configRepository.getAddListeners(parentDN)) {
+                    if (configListener instanceof DelayedConfigAddListener) {
+                        DelayedConfigAddListener delayListener = (DelayedConfigAddListener) configListener;
                         ConfigAddListener wrappedListener;
 
                         int i = delayWrappers;
                         for (; i > 0; i--) {
                             wrappedListener = delayListener.getDelayedAddListener();
                             if (wrappedListener != null && wrappedListener instanceof DelayedConfigAddListener) {
-                                delayListener = (DelayedConfigAddListener) l;
+                                delayListener = (DelayedConfigAddListener) configListener;
                             } else {
                                 break;
                             }
                         }
 
                         if (i > 0) {
-                            // There are not enough level of wrapping so this
-                            // can't be
-                            // the listener we are looking for.
+                            // There are not enough level of wrapping
+                            // so this can't be the listener we are looking for.
                             continue;
                         }
 
@@ -1374,7 +1396,7 @@
                         if (delayedListener != null && delayedListener instanceof ConfigDeleteListenerAdaptor) {
                             ConfigDeleteListenerAdaptor<?> adaptor = (ConfigDeleteListenerAdaptor<?>) delayedListener;
                             if (adaptor.getServerManagedObjectDeleteListener() == listener) {
-                                relationEntry.deregisterAddListener(l);
+                                configRepository.deregisterAddListener(parentDN, configListener);
                             }
                         }
                     }
@@ -1386,14 +1408,12 @@
 
     // Register an instantiable or optional relation delete listener.
     private void registerDeleteListener(DN baseDN, ConfigDeleteListener adaptor) throws ConfigException {
-        ConfigEntry relationEntry = getListenerConfigEntry(baseDN);
-
-        if (relationEntry != null) {
-            relationEntry.registerDeleteListener(adaptor);
+        if (configRepository.hasEntry(baseDN)) {
+            configRepository.registerDeleteListener(baseDN, adaptor);
         } else {
-            // The relation entry does not exist yet so register a delayed
-            // add listener.
-            ConfigAddListener delayedListener = new DelayedConfigAddListener(baseDN, adaptor);
+            // The relation entry does not exist yet
+            // so register a delayed add listener.
+            ConfigAddListener delayedListener = new DelayedConfigAddListener(baseDN, adaptor, configRepository);
             registerDelayedListener(baseDN, delayedListener);
         }
     }
diff --git a/opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/server/ServerManagementContext.java b/opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/server/ServerManagementContext.java
index 0cee2a1..f96d156 100644
--- a/opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/server/ServerManagementContext.java
+++ b/opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/server/ServerManagementContext.java
@@ -71,14 +71,15 @@
 import org.opends.server.admin.UndefinedDefaultBehaviorProvider;
 import org.opends.server.admin.UnknownPropertyDefinitionException;
 import org.opends.server.admin.DefinitionDecodingException.Reason;
-import org.opends.server.config.ConfigEntry;
 import org.opends.server.config.ConfigException;
+import org.opends.server.config.ConfigurationRepository;
 import org.opends.server.core.DirectoryServer;
 import org.forgerock.opendj.admin.meta.RootCfgDefn;
 import org.forgerock.opendj.admin.server.RootCfg;
 import org.forgerock.opendj.ldap.Attribute;
 import org.forgerock.opendj.ldap.AttributeDescription;
 import org.forgerock.opendj.ldap.DN;
+import org.forgerock.opendj.ldap.Entry;
 import org.forgerock.opendj.ldap.schema.AttributeType;
 import org.opends.server.util.DynamicConstants;
 import org.slf4j.Logger;
@@ -104,7 +105,7 @@
 
         // Optional new configuration entry which does not yet exist in
         // the configuration back-end.
-        private final ConfigEntry newConfigEntry;
+        private final Entry newConfigEntry;
 
         // The path of the managed object containing the next property.
         private ManagedObjectPath<?, ?> nextPath = null;
@@ -113,7 +114,7 @@
         private PropertyDefinition<T> nextProperty = null;
 
         // Private constructor.
-        private DefaultValueFinder(ConfigEntry newConfigEntry) {
+        private DefaultValueFinder(Entry newConfigEntry) {
             this.newConfigEntry = newConfigEntry;
         }
 
@@ -197,7 +198,7 @@
 
         // Get an inherited property value.
         @SuppressWarnings("unchecked")
-        private Collection<T> getInheritedProperty(ManagedObjectPath target,
+        private Collection<T> getInheritedProperty(ManagedObjectPath<?,?> target,
                 AbstractManagedObjectDefinition<?, ?> definition, String propertyName)
                         throws DefaultBehaviorException {
             // First check that the requested type of managed object
@@ -214,8 +215,8 @@
             try {
                 // Get the actual managed object definition.
                 DN dn = DNBuilder.create(target);
-                ConfigEntry configEntry;
-                if (newConfigEntry != null && newConfigEntry.getDN().equals(dn)) {
+                Entry configEntry;
+                if (newConfigEntry != null && newConfigEntry.getName().equals(dn)) {
                     configEntry = newConfigEntry;
                 } else {
                     configEntry = getManagedObjectConfigEntry(dn);
@@ -273,10 +274,10 @@
     private class MyDefinitionResolver implements DefinitionResolver {
 
         // The config entry.
-        private final ConfigEntry entry;
+        private final Entry entry;
 
         // Private constructor.
-        private MyDefinitionResolver(ConfigEntry entry) {
+        private MyDefinitionResolver(Entry entry) {
             this.entry = entry;
         }
 
@@ -285,7 +286,15 @@
          */
         public boolean matches(AbstractManagedObjectDefinition<?, ?> d) {
             String oc = LDAPProfile.getInstance().getObjectClass(d);
-            return entry.hasObjectClass(oc);
+         // TODO : use the schema to get object class and check it in the entry
+         // Commented because reject any config entry without proper schema loading
+         // Previous code was
+//            ObjectClass oc = DirectoryServer.getObjectClass(name.toLowerCase());
+//            if (oc == null) {
+//              oc = DirectoryServer.getDefaultObjectClass(name);
+//            }
+//            return Entries.containsObjectClass(entry, oc);
+            return entry.containsAttribute("objectClass", oc);
         }
     }
 
@@ -347,28 +356,22 @@
 
     private static final Logger debugLogger = LoggerFactory.getLogger(ServerManagementContext.class);
 
-    /** The singleton instance. **/
-    private final static ServerManagementContext INSTANCE = new ServerManagementContext();
-
     /**
-     * The root server managed object.
+     * The root server managed object, lazily initialized.
      */
-    private static final ServerManagedObject<RootCfg> ROOT = new ServerManagedObject<RootCfg>(
-            ManagedObjectPath.emptyPath(), RootCfgDefn.getInstance(),
-            Collections.<PropertyDefinition<?>, SortedSet<?>> emptyMap(), null);
+    private volatile ServerManagedObject<RootCfg> root;
+
+    /** Repository of configuration entries */
+    private final ConfigurationRepository configRepository;
 
     /**
-     * Get the single server-side management context.
+     * Creates a context from the provided configuration repository.
      *
-     * @return Returns the single server-side management context.
+     * @param repository
+     *          The repository of configuration entries.
      */
-    public static ServerManagementContext getInstance() {
-        return INSTANCE;
-    }
-
-    // Private constructor.
-    private ServerManagementContext() {
-        // No implementation required.
+    ServerManagementContext(ConfigurationRepository repository) {
+        configRepository = repository;
     }
 
     /**
@@ -397,7 +400,7 @@
 
         // Get the configuration entry.
         DN targetDN = DNBuilder.create(path);
-        ConfigEntry configEntry = getManagedObjectConfigEntry(targetDN);
+        Entry configEntry = getManagedObjectConfigEntry(targetDN);
         try {
             ServerManagedObject<? extends S> managedObject;
             managedObject = decode(path, configEntry);
@@ -496,7 +499,7 @@
         // Determine the exact type of managed object referenced by the
         // path.
         DN dn = DNBuilder.create(path);
-        ConfigEntry configEntry = getManagedObjectConfigEntry(dn);
+        Entry configEntry = getManagedObjectConfigEntry(dn);
 
         DefinitionResolver resolver = new MyDefinitionResolver(configEntry);
         ManagedObjectDefinition<? extends C, ? extends S> managedObjDef;
@@ -520,7 +523,7 @@
      * Get the root configuration manager associated with this management
      * context.
      *
-     * @return Returns the root configuration manager associated with this
+     * @return the root configuration manager associated with this
      *         management context.
      */
     public RootCfg getRootConfiguration() {
@@ -531,11 +534,22 @@
      * Get the root configuration server managed object associated with this
      * management context.
      *
-     * @return Returns the root configuration server managed object associated
-     *         with this management context.
+     * @return the root configuration server managed object
      */
     public ServerManagedObject<RootCfg> getRootConfigurationManagedObject() {
-        return ROOT;
+        // Use lazy initialisation because it needs a reference to this server context.
+        ServerManagedObject<RootCfg> rootObject = root;
+        if (rootObject == null) {
+            synchronized(this) {
+                rootObject = root;
+                if (rootObject == null) {
+                    root = rootObject = new ServerManagedObject<RootCfg>(ManagedObjectPath.emptyPath(),
+                            RootCfgDefn.getInstance(), Collections.<PropertyDefinition<?>, SortedSet<?>> emptyMap(),
+                            null, this);
+                }
+            }
+        }
+        return rootObject;
     }
 
     /**
@@ -562,19 +576,12 @@
 
         // Get the target entry.
         DN targetDN = DNBuilder.create(parent, relationDef);
-        ConfigEntry configEntry;
+        Set<DN> children;
         try {
-            configEntry = DirectoryServer.getConfigEntry(targetDN);
+            children = configRepository.getChildren(targetDN);
         } catch (ConfigException e) {
             return new String[0];
         }
-
-        if (configEntry == null) {
-            return new String[0];
-        }
-
-        // Retrieve the children.
-        Set<DN> children = configEntry.getChildren();
         List<String> names = new ArrayList<String>(children.size());
         for (DN child : children) {
             // Assume that RDNs are single-valued and can be trimmed.
@@ -627,7 +634,7 @@
      *             be decoded.
      */
     <C extends ConfigurationClient, S extends Configuration> ServerManagedObject<? extends S> decode(
-            ManagedObjectPath<C, S> path, ConfigEntry configEntry) throws DefinitionDecodingException,
+            ManagedObjectPath<C, S> path, Entry configEntry) throws DefinitionDecodingException,
             ServerManagedObjectDecodingException {
         return decode(path, configEntry, null);
     }
@@ -659,7 +666,7 @@
      *             be decoded.
      */
     <C extends ConfigurationClient, S extends Configuration> ServerManagedObject<? extends S> decode(
-            ManagedObjectPath<C, S> path, ConfigEntry configEntry, ConfigEntry newConfigEntry)
+            ManagedObjectPath<C, S> path, Entry configEntry, Entry newConfigEntry)
             throws DefinitionDecodingException, ServerManagedObjectDecodingException {
         // First determine the correct definition to use for the entry.
         // This could either be the provided definition, or one of its
@@ -683,25 +690,25 @@
 
         // If there were no decoding problems then return the managed
         // object, otherwise throw an operations exception.
-        ServerManagedObject<? extends S> mo = decodeAux(path, mod, properties, configEntry);
+        ServerManagedObject<? extends S> managedObject = decodeAux(path, mod, properties, configEntry.getName());
         if (exceptions.isEmpty()) {
-            return mo;
+            return managedObject;
         } else {
-            throw new ServerManagedObjectDecodingException(mo, exceptions);
+            throw new ServerManagedObjectDecodingException(managedObject, exceptions);
         }
     }
 
     // Decode helper method required to avoid generics warning.
     private <C extends ConfigurationClient, S extends Configuration> ServerManagedObject<S> decodeAux(
             ManagedObjectPath<? super C, ? super S> path, ManagedObjectDefinition<C, S> d,
-            Map<PropertyDefinition<?>, SortedSet<?>> properties, ConfigEntry configEntry) {
+            Map<PropertyDefinition<?>, SortedSet<?>> properties, DN configDN) {
         ManagedObjectPath<C, S> newPath = path.asSubType(d);
-        return new ServerManagedObject<S>(newPath, d, properties, configEntry);
+        return new ServerManagedObject<S>(newPath, d, properties, configDN, this);
     }
 
     // Create a property using the provided string values.
     private <T> SortedSet<T> decodeProperty(ManagedObjectPath<?, ?> path, PropertyDefinition<T> propertyDef,
-            Iterable<Attribute> attributes, ConfigEntry newConfigEntry) throws PropertyException {
+            Iterable<Attribute> attributes, Entry newConfigEntry) throws PropertyException {
         PropertyException exception = null;
         SortedSet<T> pvalues = new TreeSet<T>(propertyDef);
 
@@ -748,28 +755,50 @@
 
     // Gets the attribute associated with a property from a ConfigEntry.
     private Iterable<Attribute> getAttribute(ManagedObjectDefinition<?, ?> d, PropertyDefinition<?> pd,
-            ConfigEntry configEntry) {
+            Entry configEntry) {
         // TODO: we create a default attribute type if it is
         // undefined. We should log a warning here if this is the case
         // since the attribute should have been defined.
         String attrID = LDAPProfile.getInstance().getAttributeName(d, pd);
         AttributeType type = DirectoryServer.getAttributeType(attrID, true);
-        return configEntry.getEntry().getAllAttributes(AttributeDescription.create(type));
+        return configEntry.getAllAttributes(AttributeDescription.create(type));
     }
 
     // Get the default values for the specified property.
     private <T> Collection<T> getDefaultValues(ManagedObjectPath<?, ?> p, PropertyDefinition<T> pd,
-            ConfigEntry newConfigEntry) throws DefaultBehaviorException {
+            Entry newConfigEntry) throws DefaultBehaviorException {
         DefaultValueFinder<T> v = new DefaultValueFinder<T>(newConfigEntry);
         return v.find(p, pd);
     }
 
+    /**
+     * Retrieves a configuration entry corresponding to the provided DN.
+     *
+     * @param dn
+     *            DN of the configuration entry.
+     * @return the configuration entry
+     * @throws ConfigException
+     *             If a problem occurs.
+     */
+    public Entry getConfigEntry(DN dn) throws ConfigException {
+        return configRepository.getEntry(dn);
+    }
+
+    /**
+     * Returns the repository containing all configuration entries.
+     *
+     * @return the repository
+     */
+    public ConfigurationRepository getConfigRepository() {
+        return configRepository;
+    }
+
     // Gets a config entry required for a managed object and throws a
     // config exception on failure.
-    private ConfigEntry getManagedObjectConfigEntry(DN dn) throws ConfigException {
-        ConfigEntry configEntry;
+    private Entry getManagedObjectConfigEntry(DN dn) throws ConfigException {
+        Entry configEntry;
         try {
-            configEntry = DirectoryServer.getConfigEntry(dn);
+            configEntry = configRepository.getEntry(dn);
         } catch (ConfigException e) {
             debugLogger.trace("Unable to perform post add", e);
 
diff --git a/opendj-sdk/opendj-admin/src/main/java/org/opends/server/api/ConfigAddListener.java b/opendj-sdk/opendj-admin/src/main/java/org/opends/server/api/ConfigAddListener.java
index 2f9719e..d73b357 100644
--- a/opendj-sdk/opendj-admin/src/main/java/org/opends/server/api/ConfigAddListener.java
+++ b/opendj-sdk/opendj-admin/src/main/java/org/opends/server/api/ConfigAddListener.java
@@ -26,7 +26,7 @@
 package org.opends.server.api;
 
 import org.forgerock.i18n.LocalizableMessageBuilder;
-import org.opends.server.config.ConfigEntry;
+import org.forgerock.opendj.ldap.Entry;
 import org.opends.server.types.ConfigChangeResult;
 
 /**
@@ -48,7 +48,7 @@
      * @return {@code true} if the proposed entry contains an acceptable
      *         configuration, or {@code false} if it does not.
      */
-    public boolean configAddIsAcceptable(ConfigEntry configEntry, LocalizableMessageBuilder unacceptableReason);
+    public boolean configAddIsAcceptable(Entry configEntry, LocalizableMessageBuilder unacceptableReason);
 
     /**
      * Attempts to apply a new configuration based on the provided added entry.
@@ -59,5 +59,5 @@
      * @return Information about the result of processing the configuration
      *         change.
      */
-    public ConfigChangeResult applyConfigurationAdd(ConfigEntry configEntry);
+    public ConfigChangeResult applyConfigurationAdd(Entry configEntry);
 }
diff --git a/opendj-sdk/opendj-admin/src/main/java/org/opends/server/api/ConfigChangeListener.java b/opendj-sdk/opendj-admin/src/main/java/org/opends/server/api/ConfigChangeListener.java
index 0a67389..d84f0d7 100644
--- a/opendj-sdk/opendj-admin/src/main/java/org/opends/server/api/ConfigChangeListener.java
+++ b/opendj-sdk/opendj-admin/src/main/java/org/opends/server/api/ConfigChangeListener.java
@@ -25,9 +25,9 @@
  */
 package org.opends.server.api;
 
-import org.opends.server.config.ConfigEntry;
 import org.opends.server.types.ConfigChangeResult;
 import org.forgerock.i18n.LocalizableMessageBuilder;
+import org.forgerock.opendj.ldap.Entry;
 
 /**
  * This interface defines the methods that a Directory Server component should
@@ -48,7 +48,7 @@
      * @return {@code true} if the proposed entry contains an acceptable
      *         configuration, or {@code false} if it does not.
      */
-    public boolean configChangeIsAcceptable(ConfigEntry configEntry, LocalizableMessageBuilder unacceptableReason);
+    public boolean configChangeIsAcceptable(Entry configEntry, LocalizableMessageBuilder unacceptableReason);
 
     /**
      * Attempts to apply a new configuration to this Directory Server component
@@ -60,5 +60,5 @@
      * @return Information about the result of processing the configuration
      *         change.
      */
-    public ConfigChangeResult applyConfigurationChange(ConfigEntry configEntry);
+    public ConfigChangeResult applyConfigurationChange(Entry configEntry);
 }
diff --git a/opendj-sdk/opendj-admin/src/main/java/org/opends/server/api/ConfigDeleteListener.java b/opendj-sdk/opendj-admin/src/main/java/org/opends/server/api/ConfigDeleteListener.java
index b413f39..a68d098 100644
--- a/opendj-sdk/opendj-admin/src/main/java/org/opends/server/api/ConfigDeleteListener.java
+++ b/opendj-sdk/opendj-admin/src/main/java/org/opends/server/api/ConfigDeleteListener.java
@@ -25,50 +25,39 @@
  */
 package org.opends.server.api;
 
-
-
-import org.opends.server.config.ConfigEntry;
 import org.opends.server.types.ConfigChangeResult;
 import org.forgerock.i18n.LocalizableMessageBuilder;
-
+import org.forgerock.opendj.ldap.Entry;
 
 /**
- * This interface defines the methods that a Directory Server
- * component should implement if it wishes to be able to receive
- * notification if entries below a configuration entry are removed.
+ * This interface defines the methods that a Directory Server component should
+ * implement if it wishes to be able to receive notification if entries below a
+ * configuration entry are removed.
  */
-public interface ConfigDeleteListener
-{
-  /**
-   * Indicates whether it is acceptable to remove the provided
-   * configuration entry.
-   *
-   * @param  configEntry         The configuration entry that will be
-   *                             removed from the configuration.
-   * @param  unacceptableReason  A buffer to which this method can
-   *                             append a human-readable message
-   *                             explaining why the proposed delete is
-   *                             not acceptable.
-   *
-   * @return  {@code true} if the proposed entry may be removed from
-   *          the configuration, or {@code false} if not.
-   */
-  public boolean configDeleteIsAcceptable(ConfigEntry configEntry,
-                      LocalizableMessageBuilder unacceptableReason);
+public interface ConfigDeleteListener {
+    /**
+     * Indicates whether it is acceptable to remove the provided configuration
+     * entry.
+     *
+     * @param configEntry
+     *            The configuration entry that will be removed from the
+     *            configuration.
+     * @param unacceptableReason
+     *            A buffer to which this method can append a human-readable
+     *            message explaining why the proposed delete is not acceptable.
+     * @return {@code true} if the proposed entry may be removed from the
+     *         configuration, or {@code false} if not.
+     */
+    public boolean configDeleteIsAcceptable(Entry configEntry, LocalizableMessageBuilder unacceptableReason);
 
-
-
-  /**
-   * Attempts to apply a new configuration based on the provided
-   * deleted entry.
-   *
-   * @param  configEntry  The new configuration entry that has been
-   *                      deleted.
-   *
-   * @return  Information about the result of processing the
-   *          configuration change.
-   */
-  public ConfigChangeResult applyConfigurationDelete(
-                                 ConfigEntry configEntry);
+    /**
+     * Attempts to apply a new configuration based on the provided deleted
+     * entry.
+     *
+     * @param configEntry
+     *            The new configuration entry that has been deleted.
+     * @return Information about the result of processing the configuration
+     *         change.
+     */
+    public ConfigChangeResult applyConfigurationDelete(Entry configEntry);
 }
-
diff --git a/opendj-sdk/opendj-admin/src/main/java/org/opends/server/config/ConfigEntry.java b/opendj-sdk/opendj-admin/src/main/java/org/opends/server/config/ConfigEntry.java
deleted file mode 100644
index 89db1b5..0000000
--- a/opendj-sdk/opendj-admin/src/main/java/org/opends/server/config/ConfigEntry.java
+++ /dev/null
@@ -1,234 +0,0 @@
-/*
- * 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 legal-notices/CDDLv1_0.txt
- * or http://forgerock.org/license/CDDLv1.0.html.
- * 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 legal-notices/CDDLv1_0.txt.
- * If applicable, add the following below this CDDL HEADER, with the
- * fields enclosed by brackets "[]" replaced with your own identifying
- * information:
- *      Portions Copyright [yyyy] [name of copyright owner]
- *
- * CDDL HEADER END
- *
- *
- *      Copyright 2006-2008 Sun Microsystems, Inc.
- */
-package org.opends.server.config;
-
-import java.util.Set;
-import java.util.concurrent.CopyOnWriteArrayList;
-
-import org.forgerock.opendj.ldap.DN;
-import org.forgerock.opendj.ldap.Entries;
-import org.forgerock.opendj.ldap.Entry;
-import org.forgerock.opendj.ldap.schema.ObjectClass;
-import org.opends.server.api.ConfigAddListener;
-import org.opends.server.api.ConfigChangeListener;
-import org.opends.server.api.ConfigDeleteListener;
-import org.opends.server.core.DirectoryServer;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * A config entry wraps an entry to provides add, change and delete listeners on
- * it.
- */
-public final class ConfigEntry {
-
-    private static final Logger debugLogger = LoggerFactory.getLogger(ConfigEntry.class);
-
-    /** The set of add listeners that have been registered with this entry. */
-    private final CopyOnWriteArrayList<ConfigAddListener> addListeners;
-
-    /** The set of change listeners that have been registered with this entry. */
-    private final CopyOnWriteArrayList<ConfigChangeListener> changeListeners;
-
-    /** The set of delete listeners that have been registered with this entry. */
-    private final CopyOnWriteArrayList<ConfigDeleteListener> deleteListeners;
-
-    /** The actual entry wrapped by this configuration entry. */
-    private Entry entry;
-
-    private final ConfigurationRepository configRepository;
-
-    /**
-     * Creates a new config entry with the provided entry.
-     *
-     * @param entry
-     *            The entry that will be encapsulated by this config entry.
-     */
-    public ConfigEntry(Entry entry, ConfigurationRepository configRepository) {
-        this.entry = entry;
-        this.configRepository = configRepository;
-        addListeners = new CopyOnWriteArrayList<ConfigAddListener>();
-        changeListeners = new CopyOnWriteArrayList<ConfigChangeListener>();
-        deleteListeners = new CopyOnWriteArrayList<ConfigDeleteListener>();
-    }
-
-    /**
-     * Retrieves the actual entry wrapped by this configuration entry.
-     *
-     * @return The actual entry wrapped by this configuration entry.
-     */
-    public Entry getEntry() {
-        return entry;
-    }
-
-    /**
-     * Retrieves the DN for this configuration entry.
-     *
-     * @return The DN for this configuration entry.
-     */
-    public DN getDN() {
-        return entry.getName();
-    }
-
-    /**
-     * Indicates whether this configuration entry contains the specified
-     * objectclass.
-     *
-     * @param name
-     *            The name of the objectclass for which to make the
-     *            determination.
-     * @return <CODE>true</CODE> if this configuration entry contains the
-     *         specified objectclass, or <CODE>false</CODE> if not.
-     */
-    public boolean hasObjectClass(String name) {
-        // TODO : use the schema to get object class and check it in the entry
-        ObjectClass oc = DirectoryServer.getObjectClass(name.toLowerCase());
-        if (oc == null) {
-            oc = DirectoryServer.getDefaultObjectClass(name);
-        }
-
-        return Entries.containsObjectClass(entry, oc);
-    }
-
-    /**
-     * Retrieves the set of change listeners that have been registered with this
-     * configuration entry.
-     *
-     * @return The set of change listeners that have been registered with this
-     *         configuration entry.
-     */
-    public CopyOnWriteArrayList<ConfigChangeListener> getChangeListeners() {
-        return changeListeners;
-    }
-
-    /**
-     * Registers the provided change listener so that it will be notified of any
-     * changes to this configuration entry. No check will be made to determine
-     * whether the provided listener is already registered.
-     *
-     * @param listener
-     *            The change listener to register with this config entry.
-     */
-    public void registerChangeListener(ConfigChangeListener listener) {
-        changeListeners.add(listener);
-    }
-
-    /**
-     * Attempts to deregister the provided change listener with this
-     * configuration entry.
-     *
-     * @param listener
-     *            The change listener to deregister with this config entry.
-     * @return <CODE>true</CODE> if the specified listener was deregistered, or
-     *         <CODE>false</CODE> if it was not.
-     */
-    public boolean deregisterChangeListener(ConfigChangeListener listener) {
-        return changeListeners.remove(listener);
-    }
-
-    /**
-     * Retrieves the set of config add listeners that have been registered for
-     * this entry.
-     *
-     * @return The set of config add listeners that have been registered for
-     *         this entry.
-     */
-    public CopyOnWriteArrayList<ConfigAddListener> getAddListeners() {
-        return addListeners;
-    }
-
-    /**
-     * Registers the provided add listener so that it will be notified if any
-     * new entries are added immediately below this configuration entry.
-     *
-     * @param listener
-     *            The add listener that should be registered.
-     */
-    public void registerAddListener(ConfigAddListener listener) {
-        addListeners.addIfAbsent(listener);
-    }
-
-    /**
-     * Deregisters the provided add listener so that it will no longer be
-     * notified if any new entries are added immediately below this
-     * configuration entry.
-     *
-     * @param listener
-     *            The add listener that should be deregistered.
-     */
-    public void deregisterAddListener(ConfigAddListener listener) {
-        addListeners.remove(listener);
-    }
-
-    /**
-     * Retrieves the set of config delete listeners that have been registered
-     * for this entry.
-     *
-     * @return The set of config delete listeners that have been registered for
-     *         this entry.
-     */
-    public CopyOnWriteArrayList<ConfigDeleteListener> getDeleteListeners() {
-        return deleteListeners;
-    }
-
-    /**
-     * Registers the provided delete listener so that it will be notified if any
-     * entries are deleted immediately below this configuration entry.
-     *
-     * @param listener
-     *            The delete listener that should be registered.
-     */
-    public void registerDeleteListener(ConfigDeleteListener listener) {
-        deleteListeners.addIfAbsent(listener);
-    }
-
-    /**
-     * Deregisters the provided delete listener so that it will no longer be
-     * notified if any new are removed immediately below this configuration
-     * entry.
-     *
-     * @param listener
-     *            The delete listener that should be deregistered.
-     */
-    public void deregisterDeleteListener(ConfigDeleteListener listener) {
-        deleteListeners.remove(listener);
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public String toString() {
-        return entry.getName().toNormalizedString();
-    }
-
-    /**
-     * Retrieves the set of children associated with this configuration entry.
-     *
-     * @return  The set of children associated with this configuration entry.
-     */
-    public Set<DN> getChildren() {
-        return configRepository.getChildren(entry);
-    }
-}
diff --git a/opendj-sdk/opendj-admin/src/main/java/org/opends/server/config/ConfigurationRepository.java b/opendj-sdk/opendj-admin/src/main/java/org/opends/server/config/ConfigurationRepository.java
index f6eb5cd..801ad01 100644
--- a/opendj-sdk/opendj-admin/src/main/java/org/opends/server/config/ConfigurationRepository.java
+++ b/opendj-sdk/opendj-admin/src/main/java/org/opends/server/config/ConfigurationRepository.java
@@ -25,23 +25,155 @@
  */
 package org.opends.server.config;
 
+import java.util.List;
 import java.util.Set;
-
 import org.forgerock.opendj.ldap.DN;
 import org.forgerock.opendj.ldap.Entry;
+import org.opends.server.api.ConfigAddListener;
+import org.opends.server.api.ConfigChangeListener;
+import org.opends.server.api.ConfigDeleteListener;
 
 /**
- * Provides the configuration elements.
+ * Provides configuration entries and listener registration on the entries.
  */
 public interface ConfigurationRepository {
 
     /**
-     * Returns the set of children of the provided entry.
+     * Returns the set of DNs of children of the entry corresponding to the
+     * provided DN. .
      *
-     * @param entry
-     *            The configuration entry.
-     * @return the set of children of the entry
+     * @param dn
+     *            DN of a configuration entry.
+     * @return the set of DN of children of the corresponding entry
+     * @throws ConfigException
+     *             If a problem occurs during retrieval.
      */
-    Set<DN> getChildren(Entry entry);
+    Set<DN> getChildren(DN dn) throws ConfigException;
+
+    /**
+     * Returns the configuration entry for the provided DN.
+     *
+     * @param dn
+     *            DN of the configuration entry
+     * @return the config entry
+     * @throws ConfigException
+     *             If a problem occurs while trying to retrieve the requested
+     *             entry.
+     */
+    Entry getEntry(DN dn) throws ConfigException;
+
+    /**
+     * Checks if the provided DN corresponds to a configuration entry.
+     *
+     * @param dn
+     *            DN of the configuration entry
+     * @return {@code true} if and only if there is a configuration entry with
+     *         this DN
+     * @throws ConfigException
+     *             If a problem occurs.
+     */
+    boolean hasEntry(DN dn) throws ConfigException;
+
+    /**
+     * Registers the provided add listener so that it will be notified if any
+     * new entries are added immediately below the entry corresponding to the
+     * provided DN.
+     *
+     * @param dn
+     *            The DN of the configuration entry.
+     * @param listener
+     *            The add listener that should be registered.
+     */
+    public void registerAddListener(DN dn, ConfigAddListener listener);
+
+    /**
+     * Registers the provided delete listener so that it will be notified if any
+     * entries are deleted immediately below the entry corresponding to the
+     * provided DN.
+     *
+     * @param dn
+     *            The DN of the configuration entry.
+     * @param listener
+     *            The delete listener that should be registered.
+     */
+    public void registerDeleteListener(DN dn, ConfigDeleteListener listener);
+
+    /**
+     * Registers the provided change listener so that it will be notified of any
+     * changes to the entry corrresponding to provided DN. No check will be made
+     * to determine whether the provided listener is already registered.
+     *
+     * @param dn
+     *            The DN of the configuration entry.
+     * @param listener
+     *            The change listener that should be registered.
+     */
+    public void registerChangeListener(DN dn, ConfigChangeListener listener);
+
+    /**
+     * Deregisters the provided add listener so that it will no longer be
+     * notified if any new entries are added immediately below the entry
+     * corresponding to the provided DN.
+     *
+     * @param dn
+     *            The DN of the configuration entry.
+     * @param listener
+     *            The add listener that should be deregistered.
+     */
+    public void deregisterAddListener(DN dn, ConfigAddListener listener);
+
+    /**
+     * Deregisters the provided delete listener so that it will no longer be
+     * notified if any entries are deleted immediately below the entry
+     * corresponding to the provided DN.
+     *
+     * @param dn
+     *            The DN of the configuration entry.
+     * @param listener
+     *            The delete listener that should be deregistered.
+     */
+    public void deregisterDeleteListener(DN dn, ConfigDeleteListener listener);
+
+    /**
+     * Attempts to deregister the provided change listener with the provided DN.
+     *
+     * @param dn
+     *            The DN of the configuration entry.
+     * @param listener
+     *            The change listener to deregister with this DN.
+     * @return <CODE>true</CODE> if the specified listener was deregistered, or
+     *         <CODE>false</CODE> if it was not.
+     */
+    public boolean deregisterChangeListener(DN dn, ConfigChangeListener listener);
+
+    /**
+     * Retrieves the add listeners that have been registered with the provided
+     * DN.
+     *
+     * @param dn
+     *            The DN of the configuration entry.
+     * @return The list of add listeners.
+     */
+    public List<ConfigAddListener> getAddListeners(DN dn);
+
+    /**
+     * Retrieves the delete listeners that have been registered with the
+     * provided DN.
+     *
+     * @param dn
+     *            The DN of the configuration entry.
+     * @return The list of delete listeners.
+     */
+    public List<ConfigDeleteListener> getDeleteListeners(DN dn);
+
+    /**
+     * Retrieves the change listeners that have been registered with the
+     * provided DN.
+     *
+     * @param dn
+     *            The DN of the configuration entry.
+     * @return The list of change listeners.
+     */
+    public List<ConfigChangeListener> getChangeListeners(DN dn);
 
 }
diff --git a/opendj-sdk/opendj-admin/src/main/java/org/opends/server/core/DirectoryServer.java b/opendj-sdk/opendj-admin/src/main/java/org/opends/server/core/DirectoryServer.java
index 0e97bc7..002a9fa 100644
--- a/opendj-sdk/opendj-admin/src/main/java/org/opends/server/core/DirectoryServer.java
+++ b/opendj-sdk/opendj-admin/src/main/java/org/opends/server/core/DirectoryServer.java
@@ -25,13 +25,10 @@
  */
 package org.opends.server.core;
 
-import org.forgerock.opendj.ldap.DN;
 import org.forgerock.opendj.ldap.schema.AttributeType;
 import org.forgerock.opendj.ldap.schema.ObjectClass;
 import org.forgerock.opendj.ldap.schema.Schema;
 import org.forgerock.opendj.ldap.schema.UnknownSchemaElementException;
-import org.opends.server.config.ConfigEntry;
-import org.opends.server.config.ConfigException;
 
 /**
  * TODO : this is a stub, with some default implementations for some methods,
@@ -116,8 +113,4 @@
         return getObjectClass(name);
     }
 
-    public static ConfigEntry getConfigEntry(DN dn) throws ConfigException {
-        throw new RuntimeException("Not implemented");
-    }
-
 }
diff --git a/opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/server/AdminTestCaseUtils.java b/opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/server/AdminTestCaseUtils.java
new file mode 100644
index 0000000..e6cf3aa
--- /dev/null
+++ b/opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/server/AdminTestCaseUtils.java
@@ -0,0 +1,117 @@
+/*
+ * 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 legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * 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 legal-notices/CDDLv1_0.txt.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2008 Sun Microsystems, Inc.
+ */
+package org.opends.server.admin.server;
+
+import org.forgerock.opendj.admin.meta.RootCfgDefn;
+import org.forgerock.opendj.ldap.Entry;
+import org.opends.server.admin.AbstractManagedObjectDefinition;
+import org.opends.server.admin.Configuration;
+import org.opends.server.admin.ConfigurationClient;
+import org.opends.server.admin.DefinitionDecodingException;
+import org.opends.server.admin.LDAPProfile;
+import org.opends.server.admin.ManagedObjectPath;
+import org.opends.server.admin.RelationDefinition;
+import org.opends.server.admin.SingletonRelationDefinition;
+import org.opends.server.config.ConfigException;
+
+/**
+ * This class defines some utility functions which can be used by test cases
+ * which interact with the admin framework.
+ */
+public final class AdminTestCaseUtils {
+
+    // The relation name which will be used for dummy configurations. A
+    // deliberately obfuscated name is chosen to avoid clashes.
+    private static final String DUMMY_TEST_RELATION = "*dummy*test*relation*";
+
+    // Indicates if the dummy relation profile has been registered.
+    private static boolean isProfileRegistered = false;
+
+    // Prevent instantiation.
+    private AdminTestCaseUtils() {
+        // No implementation required.
+    }
+
+    /**
+     * Decodes a configuration entry into the required type of server
+     * configuration.
+     *
+     * @param <S>
+     *            The type of server configuration to be decoded.
+     * @param definition
+     *            The required definition of the required managed object.
+     * @param entry
+     *            An entry containing the configuration to be decoded.
+     * @return Returns the new server-side configuration.
+     * @throws ConfigException
+     *             If the entry could not be decoded.
+     */
+    public static <S extends Configuration> S getConfiguration(ServerManagementContext context,
+            AbstractManagedObjectDefinition<?, S> definition, Entry entry) throws ConfigException {
+        try {
+            ServerManagedObject<? extends S> mo = context.decode(getPath(definition), entry);
+
+            // Ensure constraints are satisfied.
+            mo.ensureIsUsable();
+
+            return mo.getConfiguration();
+        } catch (DefinitionDecodingException e) {
+            throw ConfigExceptionFactory.getInstance().createDecodingExceptionAdaptor(entry.getName(), e);
+        } catch (ServerManagedObjectDecodingException e) {
+            throw ConfigExceptionFactory.getInstance().createDecodingExceptionAdaptor(e);
+        } catch (ConstraintViolationException e) {
+            throw ConfigExceptionFactory.getInstance().createDecodingExceptionAdaptor(e);
+        }
+    }
+
+    // Construct a dummy path.
+    private synchronized static <C extends ConfigurationClient, S extends Configuration> ManagedObjectPath<C, S> getPath(
+            AbstractManagedObjectDefinition<C, S> d) {
+        if (!isProfileRegistered) {
+            LDAPProfile.Wrapper profile = new LDAPProfile.Wrapper() {
+
+                @Override
+                public String getRelationRDNSequence(RelationDefinition<?, ?> r) {
+                    if (r.getName().equals(DUMMY_TEST_RELATION)) {
+                        return "cn=dummy configuration,cn=config";
+                    } else {
+                        return null;
+                    }
+                }
+
+            };
+
+            LDAPProfile.getInstance().pushWrapper(profile);
+            isProfileRegistered = true;
+        }
+
+        SingletonRelationDefinition.Builder<C, S> builder = new SingletonRelationDefinition.Builder<C, S>(
+                RootCfgDefn.getInstance(), DUMMY_TEST_RELATION, d);
+        ManagedObjectPath<?, ?> root = ManagedObjectPath.emptyPath();
+        return root.child(builder.getInstance());
+
+    }
+}
diff --git a/opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/server/AggregationServerTest.java b/opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/server/AggregationServerTest.java
new file mode 100644
index 0000000..c351849
--- /dev/null
+++ b/opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/server/AggregationServerTest.java
@@ -0,0 +1,814 @@
+/*
+ * 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 legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * 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 legal-notices/CDDLv1_0.txt.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2007-2008 Sun Microsystems, Inc.
+ *      Portions copyright 2013 ForgeRock AS
+ */
+package org.opends.server.admin.server;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import javax.naming.ldap.LdapName;
+
+import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.opendj.admin.client.ConnectionHandlerCfgClient;
+import org.forgerock.opendj.admin.client.LDAPConnectionHandlerCfgClient;
+import org.forgerock.opendj.admin.client.RootCfgClient;
+import org.forgerock.opendj.admin.meta.LDAPConnectionHandlerCfgDefn;
+import org.forgerock.opendj.admin.server.ConnectionHandlerCfg;
+import org.forgerock.opendj.admin.server.RootCfg;
+import org.forgerock.opendj.ldap.ResultCode;
+import org.forgerock.opendj.ldap.TestCaseUtils;
+import org.opends.server.admin.AdminTestCase;
+import org.opends.server.admin.AdministratorAction;
+import org.opends.server.admin.AggregationPropertyDefinition;
+import org.opends.server.admin.IllegalPropertyValueStringException;
+import org.opends.server.admin.ManagedObjectNotFoundException;
+import org.opends.server.admin.PropertyException;
+import org.opends.server.admin.PropertyOption;
+import org.opends.server.admin.TestCfg;
+import org.opends.server.admin.TestChildCfg;
+import org.opends.server.admin.TestChildCfgDefn;
+import org.opends.server.admin.TestParentCfg;
+import org.opends.server.admin.UndefinedDefaultBehaviorProvider;
+import org.opends.server.admin.client.OperationRejectedException;
+import org.opends.server.admin.condition.Conditions;
+import org.opends.server.config.ConfigException;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.types.ConfigChangeResult;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+/**
+ * Test cases for aggregations on the server-side.
+ */
+@Test(singleThreaded = true)
+public final class AggregationServerTest extends AdminTestCase {
+
+    /**
+     * Dummy change listener for triggering change constraint call-backs.
+     */
+    private static final class DummyChangeListener implements ConfigurationChangeListener<TestChildCfg> {
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public ConfigChangeResult applyConfigurationChange(TestChildCfg configuration) {
+            return new ConfigChangeResult(ResultCode.SUCCESS, false);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public boolean isConfigurationChangeAcceptable(TestChildCfg configuration, List<LocalizableMessage> unacceptableReasons) {
+            return true;
+        }
+    }
+
+    /**
+     * Dummy delete listener for triggering delete constraint call-backs.
+     */
+    private static final class DummyDeleteListener implements ConfigurationDeleteListener<TestChildCfg> {
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public ConfigChangeResult applyConfigurationDelete(TestChildCfg configuration) {
+            return new ConfigChangeResult(ResultCode.SUCCESS, false);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public boolean isConfigurationDeleteAcceptable(TestChildCfg configuration, List<LocalizableMessage> unacceptableReasons) {
+            return true;
+        }
+    }
+
+    private static final String TEST_CHILD_7_DN = "cn=test child 7,cn=test children,cn=test parent 1,cn=test parents,cn=config";
+
+    private static final String TEST_CHILD_6_DN = "cn=test child 6,cn=test children,cn=test parent 1,cn=test parents,cn=config";
+
+    /** The name of the test connection handler. */
+    private static final String TEST_CONNECTION_HANDLER_NAME = "Test Connection Handler";
+
+    /** Test child 1 LDIF. */
+    private static final String[] TEST_CHILD_1 = new String[] {
+            "dn: cn=test child 1,cn=test children,cn=test parent 1,cn=test parents,cn=config", "objectclass: top",
+            "objectclass: ds-cfg-test-child-dummy", "cn: test child 1", "ds-cfg-enabled: true",
+            "ds-cfg-java-class: org.opends.server.extensions.UserDefinedVirtualAttributeProvider",
+            "ds-cfg-attribute-type: description", "ds-cfg-conflict-behavior: virtual-overrides-real" };
+
+    /** Test child 2 LDIF. */
+    private static final String[] TEST_CHILD_2 = new String[] {
+            "dn: cn=test child 2,cn=test children,cn=test parent 1,cn=test parents,cn=config", "objectclass: top",
+            "objectclass: ds-cfg-test-child-dummy", "cn: test child 2", "ds-cfg-enabled: true",
+            "ds-cfg-java-class: org.opends.server.extensions.UserDefinedVirtualAttributeProvider",
+            "ds-cfg-attribute-type: description", "ds-cfg-conflict-behavior: virtual-overrides-real",
+            "ds-cfg-rotation-policy: cn=LDAP Connection Handler, cn=connection handlers, cn=config" };
+
+    /** Test child 3 LDIF (invalid reference). */
+    private static final String[] TEST_CHILD_3 = new String[] {
+            "dn: cn=test child 3,cn=test children,cn=test parent 1,cn=test parents,cn=config", "objectclass: top",
+            "objectclass: ds-cfg-test-child-dummy", "cn: test child 3", "ds-cfg-enabled: true",
+            "ds-cfg-java-class: org.opends.server.extensions.UserDefinedVirtualAttributeProvider",
+            "ds-cfg-attribute-type: description", "ds-cfg-conflict-behavior: virtual-overrides-real",
+            "ds-cfg-rotation-policy: cn=LDAP Connection Handler, cn=bad rdn, cn=config" };
+
+    /** Test child 4 LDIF. */
+    private static final String[] TEST_CHILD_4 = new String[] {
+            "dn: cn=test child 4,cn=test children,cn=test parent 1,cn=test parents,cn=config", "objectclass: top",
+            "objectclass: ds-cfg-test-child-dummy", "cn: test child 4", "ds-cfg-enabled: true",
+            "ds-cfg-java-class: org.opends.server.extensions.UserDefinedVirtualAttributeProvider",
+            "ds-cfg-attribute-type: description", "ds-cfg-conflict-behavior: virtual-overrides-real",
+            "ds-cfg-rotation-policy: cn=LDAP Connection Handler, cn=connection handlers, cn=config",
+            "ds-cfg-rotation-policy: cn=LDAPS Connection Handler, cn=connection handlers, cn=config" };
+
+    /** Test child 5 LDIF. */
+    private static final String[] TEST_CHILD_5 = new String[] {
+            "dn: cn=test child 5,cn=test children,cn=test parent 1,cn=test parents,cn=config", "objectclass: top",
+            "objectclass: ds-cfg-test-child-dummy", "cn: test child 5", "ds-cfg-enabled: true",
+            "ds-cfg-java-class: org.opends.server.extensions.UserDefinedVirtualAttributeProvider",
+            "ds-cfg-attribute-type: description", "ds-cfg-conflict-behavior: virtual-overrides-real",
+            "ds-cfg-rotation-policy: cn=BAD Connection Handler 1, cn=connection handlers, cn=config",
+            "ds-cfg-rotation-policy: cn=BAD Connection Handler 2, cn=connection handlers, cn=config",
+            "ds-cfg-rotation-policy: cn=LDAP Connection Handler, cn=connection handlers, cn=config" };
+
+    /** Test child 6 LDIF. */
+    private static final String[] TEST_CHILD_6 = new String[] {
+            "dn: cn=test child 6,cn=test children,cn=test parent 1,cn=test parents,cn=config", "objectclass: top",
+            "objectclass: ds-cfg-test-child-dummy", "cn: test child 6", "ds-cfg-enabled: true",
+            "ds-cfg-java-class: org.opends.server.extensions.UserDefinedVirtualAttributeProvider",
+            "ds-cfg-attribute-type: description", "ds-cfg-conflict-behavior: virtual-overrides-real",
+            "ds-cfg-rotation-policy: cn=" + TEST_CONNECTION_HANDLER_NAME + ", cn=connection handlers, cn=config" };
+
+    /** Test child 7 LDIF. */
+    private static final String[] TEST_CHILD_7 = new String[] {
+            "dn: cn=test child 7,cn=test children,cn=test parent 1,cn=test parents,cn=config", "objectclass: top",
+            "objectclass: ds-cfg-test-child-dummy", "cn: test child 7", "ds-cfg-enabled: false",
+            "ds-cfg-java-class: org.opends.server.extensions.UserDefinedVirtualAttributeProvider",
+            "ds-cfg-attribute-type: description", "ds-cfg-conflict-behavior: virtual-overrides-real",
+            "ds-cfg-rotation-policy: cn=" + TEST_CONNECTION_HANDLER_NAME + ", cn=connection handlers, cn=config" };
+
+    /** Test LDIF. */
+    private static final String[] TEST_LDIF = new String[] {
+            // Base entries.
+            "dn: cn=test parents,cn=config",
+            "objectclass: top",
+            "objectclass: ds-cfg-branch",
+            "cn: test parents",
+            "",
+            // Parent 1.
+            "dn: cn=test parent 1,cn=test parents,cn=config", "objectclass: top",
+            "objectclass: ds-cfg-test-parent-dummy", "cn: test parent 1", "ds-cfg-enabled: true",
+            "ds-cfg-java-class: org.opends.server.extensions.UserDefinedVirtualAttributeProvider",
+            "ds-cfg-attribute-type: description", "ds-cfg-conflict-behavior: virtual-overrides-real", "",
+            // Child base entries.
+            "dn:cn=test children,cn=test parent 1,cn=test parents,cn=config", "objectclass: top",
+            "objectclass: ds-cfg-branch", "cn: test children", "" };
+
+    /**
+     * The saved test child configuration "aggregation-property" property
+     * definition.
+     */
+    private AggregationPropertyDefinition<ConnectionHandlerCfgClient, ConnectionHandlerCfg>
+        aggregationPropertyDefinitionDefault = null;
+
+    /**
+     * An aggregation where the target must be enabled if the source is enabled.
+     */
+    private AggregationPropertyDefinition<ConnectionHandlerCfgClient, ConnectionHandlerCfg>
+        aggregationPropertyDefinitionTargetAndSourceMustBeEnabled = null;
+
+    /** An aggregation where the target must be enabled. */
+    private AggregationPropertyDefinition<ConnectionHandlerCfgClient, ConnectionHandlerCfg>
+        aggregationPropertyDefinitionTargetMustBeEnabled = null;
+
+    /**
+     * Sets up tests
+     *
+     * @throws Exception
+     *             If the server could not be initialized.
+     */
+    @BeforeClass
+    public void setUp() throws Exception {
+        TestCfg.setUp();
+
+        // Add test managed objects.
+        TestCaseUtils.addEntries(TEST_LDIF);
+
+        // Save the aggregation property definition so that it can be
+        // replaced and restored later.
+        aggregationPropertyDefinitionDefault = TestChildCfgDefn.getInstance()
+                .getAggregationPropertyPropertyDefinition();
+
+        // Create the two test aggregation properties.
+        AggregationPropertyDefinition.Builder<ConnectionHandlerCfgClient, ConnectionHandlerCfg> builder;
+        TestChildCfgDefn d = TestChildCfgDefn.getInstance();
+        builder = AggregationPropertyDefinition.createBuilder(d, "aggregation-property");
+        builder.setOption(PropertyOption.MULTI_VALUED);
+        builder.setAdministratorAction(new AdministratorAction(AdministratorAction.Type.NONE, d,
+                "aggregation-property"));
+        builder.setDefaultBehaviorProvider(new UndefinedDefaultBehaviorProvider<String>());
+        builder.setParentPath("/");
+        builder.setRelationDefinition("connection-handler");
+        builder.setTargetIsEnabledCondition(Conditions.contains("enabled", "true"));
+        aggregationPropertyDefinitionTargetMustBeEnabled = builder.getInstance();
+        TestCfg.initializePropertyDefinition(aggregationPropertyDefinitionTargetMustBeEnabled);
+
+        builder = AggregationPropertyDefinition.createBuilder(d, "aggregation-property");
+        builder.setOption(PropertyOption.MULTI_VALUED);
+        builder.setAdministratorAction(new AdministratorAction(AdministratorAction.Type.NONE, d,
+                "aggregation-property"));
+        builder.setDefaultBehaviorProvider(new UndefinedDefaultBehaviorProvider<String>());
+        builder.setParentPath("/");
+        builder.setRelationDefinition("connection-handler");
+        builder.setTargetIsEnabledCondition(Conditions.contains("enabled", "true"));
+        builder.setTargetNeedsEnablingCondition(Conditions.contains("mandatory-boolean-property", "true"));
+        aggregationPropertyDefinitionTargetAndSourceMustBeEnabled = builder.getInstance();
+        TestCfg.initializePropertyDefinition(aggregationPropertyDefinitionTargetAndSourceMustBeEnabled);
+    }
+
+    /**
+     * Tears down test environment.
+     *
+     * @throws Exception
+     *             If the test entries could not be removed.
+     */
+    @AfterClass
+    public void tearDown() throws Exception {
+        TestCfg.cleanup();
+
+        // Restore the test child aggregation definition.
+        TestCfg.addPropertyDefinition(aggregationPropertyDefinitionDefault);
+
+        // Remove test entries.
+        deleteSubtree("cn=test parents,cn=config");
+    }
+
+    /**
+     * Tests that aggregation is rejected when the LDAP DN contains a valid RDN
+     * but an invalid parent DN.
+     *
+     * @throws Exception
+     *             If the test unexpectedly fails.
+     */
+    @Test
+    public void testAggregationBadBaseDN() throws Exception {
+        // Add the entry.
+        TestCaseUtils.addEntry(TEST_CHILD_3);
+
+        try {
+            TestParentCfg parent = getParent("test parent 1");
+            parent.getTestChild("test child 3");
+            Assert.fail("Unexpectedly added test child 3 when it had a bad aggregation value");
+        } catch (ConfigException e) {
+            // Check that we have a decoding exception as the cause and
+            // there was only one cause the illegal property value.
+            Throwable cause = e.getCause();
+            if (cause instanceof ServerManagedObjectDecodingException) {
+                ServerManagedObjectDecodingException de = (ServerManagedObjectDecodingException) cause;
+
+                Collection<PropertyException> causes = de.getCauses();
+                Assert.assertEquals(causes.size(), 1);
+
+                cause = causes.iterator().next();
+                if (cause instanceof IllegalPropertyValueStringException) {
+                    IllegalPropertyValueStringException pe = (IllegalPropertyValueStringException) cause;
+                    Assert.assertEquals(pe.getPropertyDefinition(), TestChildCfgDefn.getInstance()
+                            .getAggregationPropertyPropertyDefinition());
+                    Assert.assertEquals(pe.getIllegalValueString(),
+                            "cn=LDAP Connection Handler, cn=bad rdn, cn=config");
+                } else {
+                    // Got an unexpected cause.
+                    throw e;
+                }
+            } else {
+                // Got an unexpected cause.
+                throw e;
+            }
+        } finally {
+            deleteSubtree("cn=test child 3,cn=test children,cn=test parent 1,cn=test parents,cn=config");
+        }
+    }
+
+    /**
+     * Tests that aggregation is rejected by a constraint violation when the DN
+     * values are dangling.
+     *
+     * @throws Exception
+     *             If the test unexpectedly fails.
+     */
+    @Test
+    public void testAggregationDanglingReference() throws Exception {
+        // Add the entry.
+        TestCaseUtils.addEntry(TEST_CHILD_5);
+
+        try {
+            TestParentCfg parent = getParent("test parent 1");
+            parent.getTestChild("test child 5");
+            Assert.fail("Unexpectedly added test child 5 when it had a dangling reference");
+        } catch (ConfigException e) {
+            // Check that we have a constraint violation as the cause.
+            Throwable cause = e.getCause();
+            if (cause instanceof ConstraintViolationException) {
+                ConstraintViolationException cve = (ConstraintViolationException) cause;
+                Collection<LocalizableMessage> causes = cve.getMessages();
+                Assert.assertEquals(causes.size(), 2);
+            } else {
+                // Got an unexpected cause.
+                throw e;
+            }
+        } finally {
+            deleteSubtree("cn=test child 5,cn=test children,cn=test parent 1,cn=test parents,cn=config");
+        }
+    }
+
+    /**
+     * Tests that aggregation is rejected by a constraint violation when an
+     * enabled component references a disabled component and the referenced
+     * component must always be enabled.
+     *
+     * @throws Exception
+     *             If the test unexpectedly fails.
+     */
+    @Test
+    public void testAggregationDisabledReference1() throws Exception {
+        // Add the entry and the connection handler.
+        TestCaseUtils.addEntry(TEST_CHILD_6);
+        try {
+            createConnectionHandler(false);
+        } catch (Exception e) {
+            deleteSubtree(TEST_CHILD_6_DN);
+            throw e;
+        }
+
+        // Register the temporary aggregation definition.
+        TestCfg.removeConstraint(aggregationPropertyDefinitionDefault.getSourceConstraint());
+        TestCfg.addPropertyDefinition(aggregationPropertyDefinitionTargetMustBeEnabled);
+        TestCfg.addConstraint(aggregationPropertyDefinitionTargetMustBeEnabled.getSourceConstraint());
+
+        try {
+            TestParentCfg parent = getParent("test parent 1");
+            parent.getTestChild("test child 6");
+            Assert.fail("Unexpectedly added test child 6 when it had a disabled reference");
+        } catch (ConfigException e) {
+            // Check that we have a constraint violation as the cause.
+            Throwable cause = e.getCause();
+            if (cause instanceof ConstraintViolationException) {
+                ConstraintViolationException cve = (ConstraintViolationException) cause;
+                Collection<LocalizableMessage> causes = cve.getMessages();
+                Assert.assertEquals(causes.size(), 1);
+            } else {
+                // Got an unexpected cause.
+                throw e;
+            }
+        } finally {
+            // Put back the default aggregation definition.
+            TestCfg.removeConstraint(aggregationPropertyDefinitionTargetMustBeEnabled.getSourceConstraint());
+            TestCfg.addPropertyDefinition(aggregationPropertyDefinitionDefault);
+            TestCfg.addConstraint(aggregationPropertyDefinitionDefault.getSourceConstraint());
+
+            try {
+                deleteSubtree(TEST_CHILD_6_DN);
+            } finally {
+                deleteConnectionHandler();
+            }
+        }
+    }
+
+    /**
+     * Tests that aggregation is rejected by a constraint violation when a
+     * disabled component references a disabled component and the referenced
+     * component must always be enabled.
+     *
+     * @throws Exception
+     *             If the test unexpectedly fails.
+     */
+    @Test
+    public void testAggregationDisabledReference2() throws Exception {
+        // Add the entry.
+        TestCaseUtils.addEntry(TEST_CHILD_7);
+        try {
+            createConnectionHandler(false);
+        } catch (Exception e) {
+            deleteSubtree(TEST_CHILD_7_DN);
+            throw e;
+        }
+
+        // Register the temporary aggregation definition.
+        TestCfg.removeConstraint(aggregationPropertyDefinitionDefault.getSourceConstraint());
+        TestCfg.addPropertyDefinition(aggregationPropertyDefinitionTargetMustBeEnabled);
+        TestCfg.addConstraint(aggregationPropertyDefinitionTargetMustBeEnabled.getSourceConstraint());
+
+        try {
+            TestParentCfg parent = getParent("test parent 1");
+            parent.getTestChild("test child 7");
+            Assert.fail("Unexpectedly added test child 7 when it had a disabled reference");
+        } catch (ConfigException e) {
+            // Check that we have a constraint violation as the cause.
+            Throwable cause = e.getCause();
+            if (cause instanceof ConstraintViolationException) {
+                ConstraintViolationException cve = (ConstraintViolationException) cause;
+                Collection<LocalizableMessage> causes = cve.getMessages();
+                Assert.assertEquals(causes.size(), 1);
+            } else {
+                // Got an unexpected cause.
+                throw e;
+            }
+        } finally {
+            // Put back the default aggregation definition.
+            TestCfg.removeConstraint(aggregationPropertyDefinitionTargetMustBeEnabled.getSourceConstraint());
+            TestCfg.addPropertyDefinition(aggregationPropertyDefinitionDefault);
+            TestCfg.addConstraint(aggregationPropertyDefinitionDefault.getSourceConstraint());
+
+            try {
+                deleteSubtree(TEST_CHILD_7_DN);
+            } finally {
+                deleteConnectionHandler();
+            }
+        }
+    }
+
+    /**
+     * Tests that aggregation is rejected by a constraint violation when an
+     * enabled component references a disabled component and the referenced
+     * component must always be enabled when the referencing component is
+     * enabled.
+     *
+     * @throws Exception
+     *             If the test unexpectedly fails.
+     */
+    @Test
+    public void testAggregationDisabledReference3() throws Exception {
+        // Add the entry.
+        TestCaseUtils.addEntry(TEST_CHILD_6);
+        try {
+            createConnectionHandler(false);
+        } catch (Exception e) {
+            deleteSubtree(TEST_CHILD_6_DN);
+            throw e;
+        }
+
+        // Register the temporary aggregation definition.
+        TestCfg.removeConstraint(aggregationPropertyDefinitionDefault.getSourceConstraint());
+        TestCfg.addPropertyDefinition(aggregationPropertyDefinitionTargetAndSourceMustBeEnabled);
+        TestCfg.addConstraint(aggregationPropertyDefinitionTargetAndSourceMustBeEnabled.getSourceConstraint());
+
+        try {
+            TestParentCfg parent = getParent("test parent 1");
+            parent.getTestChild("test child 6");
+            Assert.fail("Unexpectedly added test child 6 when it had a disabled reference");
+        } catch (ConfigException e) {
+            // Check that we have a constraint violation as the cause.
+            Throwable cause = e.getCause();
+            if (cause instanceof ConstraintViolationException) {
+                ConstraintViolationException cve = (ConstraintViolationException) cause;
+                Collection<LocalizableMessage> causes = cve.getMessages();
+                Assert.assertEquals(causes.size(), 1);
+            } else {
+                // Got an unexpected cause.
+                throw e;
+            }
+        } finally {
+            // Put back the default aggregation definition.
+            TestCfg.removeConstraint(aggregationPropertyDefinitionTargetAndSourceMustBeEnabled.getSourceConstraint());
+            TestCfg.addPropertyDefinition(aggregationPropertyDefinitionDefault);
+            TestCfg.addConstraint(aggregationPropertyDefinitionDefault.getSourceConstraint());
+
+            try {
+                deleteSubtree(TEST_CHILD_6_DN);
+            } finally {
+                deleteConnectionHandler();
+            }
+        }
+    }
+
+    /**
+     * Tests that aggregation is allowed when a disabled component references a
+     * disabled component and the referenced component must always be enabled
+     * when the referencing component is enabled.
+     *
+     * @throws Exception
+     *             If the test unexpectedly fails.
+     */
+    @Test
+    public void testAggregationDisabledReference4() throws Exception {
+        // Add the entry.
+        TestCaseUtils.addEntry(TEST_CHILD_7);
+        try {
+            createConnectionHandler(false);
+        } catch (Exception e) {
+            deleteSubtree(TEST_CHILD_7_DN);
+            throw e;
+        }
+
+        // Register the temporary aggregation definition.
+        TestCfg.removeConstraint(aggregationPropertyDefinitionDefault.getSourceConstraint());
+        TestCfg.addPropertyDefinition(aggregationPropertyDefinitionTargetAndSourceMustBeEnabled);
+        TestCfg.addConstraint(aggregationPropertyDefinitionTargetAndSourceMustBeEnabled.getSourceConstraint());
+
+        try {
+            TestParentCfg parent = getParent("test parent 1");
+            parent.getTestChild("test child 7");
+        } finally {
+            // Put back the default aggregation definition.
+            TestCfg.removeConstraint(aggregationPropertyDefinitionTargetAndSourceMustBeEnabled.getSourceConstraint());
+            TestCfg.addPropertyDefinition(aggregationPropertyDefinitionDefault);
+            TestCfg.addConstraint(aggregationPropertyDefinitionDefault.getSourceConstraint());
+
+            try {
+                deleteSubtree(TEST_CHILD_7_DN);
+            } finally {
+                deleteConnectionHandler();
+            }
+        }
+    }
+
+    /**
+     * Tests that aggregation contains no values when it contains does not
+     * contain any DN attribute values.
+     *
+     * @throws Exception
+     *             If the test unexpectedly fails.
+     */
+    @Test
+    public void testAggregationEmpty() throws Exception {
+        // Add the entry.
+        TestCaseUtils.addEntry(TEST_CHILD_1);
+
+        try {
+            TestParentCfg parent = getParent("test parent 1");
+            assertChild1(parent.getTestChild("test child 1"));
+        } finally {
+            deleteSubtree("cn=test child 1,cn=test children,cn=test parent 1,cn=test parents,cn=config");
+        }
+    }
+
+    /**
+     * Tests that aggregation contains multiple valid values when it contains a
+     * multiple valid DN attribute values.
+     *
+     * @throws Exception
+     *             If the test unexpectedly fails.
+     */
+    @Test
+    public void testAggregationMultipleValues() throws Exception {
+        // Add the entry.
+        TestCaseUtils.addEntry(TEST_CHILD_4);
+
+        try {
+            TestParentCfg parent = getParent("test parent 1");
+            assertChild4(parent.getTestChild("test child 4"));
+        } finally {
+            deleteSubtree("cn=test child 4,cn=test children,cn=test parent 1,cn=test parents,cn=config");
+        }
+    }
+
+    /**
+     * Tests that aggregation contains single valid value when it contains a
+     * single valid DN attribute values.
+     *
+     * @throws Exception
+     *             If the test unexpectedly fails.
+     */
+    @Test
+    public void testAggregationSingle() throws Exception {
+        // Add the entry.
+        TestCaseUtils.addEntry(TEST_CHILD_2);
+
+        try {
+            TestParentCfg parent = getParent("test parent 1");
+            assertChild2(parent.getTestChild("test child 2"));
+        } finally {
+            deleteSubtree("cn=test child 2,cn=test children,cn=test parent 1,cn=test parents,cn=config");
+        }
+    }
+
+    /**
+     * Tests that it is impossible to delete a referenced component when the
+     * referenced component must always exist regardless of whether the
+     * referencing component is enabled or not.
+     *
+     * @throws Exception
+     *             If the test unexpectedly fails.
+     */
+    @Test
+    public void testCannotDeleteReferencedComponent() throws Exception {
+        // Add the entry.
+        TestCaseUtils.addEntry(TEST_CHILD_7);
+        try {
+            createConnectionHandler(true);
+        } catch (Exception e) {
+            deleteSubtree(TEST_CHILD_7_DN);
+            throw e;
+        }
+
+        // Register the temporary aggregation definition.
+        TestCfg.removeConstraint(aggregationPropertyDefinitionDefault.getSourceConstraint());
+        TestCfg.addPropertyDefinition(aggregationPropertyDefinitionTargetMustBeEnabled);
+        TestCfg.addConstraint(aggregationPropertyDefinitionTargetMustBeEnabled.getSourceConstraint());
+
+        ConfigurationDeleteListener<TestChildCfg> dl = new DummyDeleteListener();
+        ConfigurationChangeListener<TestChildCfg> cl = new DummyChangeListener();
+        try {
+            // Retrieve the parent and child managed objects and register
+            // delete and change listeners respectively in order to trigger
+            // the constraint call-backs.
+            TestParentCfg parent = getParent("test parent 1");
+            parent.addTestChildDeleteListener(dl);
+
+            TestChildCfg child = parent.getTestChild("test child 7");
+            child.addChangeListener(cl);
+
+            // Now attempt to delete the referenced connection handler.
+            // This should fail.
+            try {
+                deleteConnectionHandler();
+                Assert.fail("Successfully deleted a referenced component");
+            } catch (OperationRejectedException e) {
+                // This is the expected exception - do nothing.
+            }
+        } finally {
+            try {
+                deleteSubtree(TEST_CHILD_7_DN);
+            } finally {
+                try {
+                    deleteConnectionHandler();
+                } catch (ManagedObjectNotFoundException e) {
+                    // Ignore as it may have been deleted already.
+                } finally {
+                    // Remove the temporary delete listener.
+                    TestParentCfg parent = getParent("test parent 1");
+                    parent.removeTestChildDeleteListener(dl);
+
+                    // Put back the default aggregation definition.
+                    TestCfg.removeConstraint(aggregationPropertyDefinitionTargetMustBeEnabled.getSourceConstraint());
+                    TestCfg.addPropertyDefinition(aggregationPropertyDefinitionDefault);
+                    TestCfg.addConstraint(aggregationPropertyDefinitionDefault.getSourceConstraint());
+                }
+            }
+        }
+    }
+
+    /**
+     * Tests that it is impossible to disable a referenced component when the
+     * referenced component must always be enabled regardless of whether the
+     * referencing component is enabled or not.
+     *
+     * @throws Exception
+     *             If the test unexpectedly fails.
+     */
+    @Test
+    public void testCannotDisableReferencedComponent() throws Exception {
+        // Add the entry.
+        TestCaseUtils.addEntry(TEST_CHILD_7);
+        try {
+            createConnectionHandler(true);
+        } catch (Exception e) {
+            deleteSubtree(TEST_CHILD_7_DN);
+            throw e;
+        }
+
+        // Register the temporary aggregation definition.
+        TestCfg.removeConstraint(aggregationPropertyDefinitionDefault.getSourceConstraint());
+        TestCfg.addPropertyDefinition(aggregationPropertyDefinitionTargetMustBeEnabled);
+        TestCfg.addConstraint(aggregationPropertyDefinitionTargetMustBeEnabled.getSourceConstraint());
+
+        ConfigurationDeleteListener<TestChildCfg> dl = new DummyDeleteListener();
+        ConfigurationChangeListener<TestChildCfg> cl = new DummyChangeListener();
+        try {
+            // Retrieve the parent and child managed objects and register
+            // delete and change listeners respectively in order to trigger
+            // the constraint call-backs.
+            TestParentCfg parent = getParent("test parent 1");
+            parent.addTestChildDeleteListener(dl);
+
+            TestChildCfg child = parent.getTestChild("test child 7");
+            child.addChangeListener(cl);
+
+            // Now attempt to disable the referenced connection handler.
+            // This should fail.
+            try {
+                RootCfgClient root = TestCaseUtils.getRootConfiguration();
+                ConnectionHandlerCfgClient client = root.getConnectionHandler(TEST_CONNECTION_HANDLER_NAME);
+                client.setEnabled(false);
+                client.commit();
+                Assert.fail("Successfully disabled a referenced component");
+            } catch (OperationRejectedException e) {
+                // This is the expected exception - do nothing.
+            }
+        } finally {
+            try {
+                deleteSubtree(TEST_CHILD_7_DN);
+            } finally {
+                try {
+                    deleteConnectionHandler();
+                } finally {
+                    // Remove the temporary delete listener.
+                    TestParentCfg parent = getParent("test parent 1");
+                    parent.removeTestChildDeleteListener(dl);
+
+                    // Put back the default aggregation definition.
+                    TestCfg.removeConstraint(aggregationPropertyDefinitionTargetMustBeEnabled.getSourceConstraint());
+                    TestCfg.addPropertyDefinition(aggregationPropertyDefinitionDefault);
+                    TestCfg.addConstraint(aggregationPropertyDefinitionDefault.getSourceConstraint());
+                }
+            }
+        }
+    }
+
+    /** Assert that the values of child 1 are correct. */
+    private void assertChild1(TestChildCfg child) {
+        Assert.assertEquals(child.getMandatoryClassProperty(),
+                "org.opends.server.extensions.UserDefinedVirtualAttributeProvider");
+        Assert.assertEquals(child.getMandatoryReadOnlyAttributeTypeProperty(),
+                DirectoryServer.getAttributeType("description"));
+        assertSetEquals(child.getAggregationProperty(), new String[0]);
+    }
+
+    /** Assert that the values of child 2 are correct. */
+    private void assertChild2(TestChildCfg child) {
+        Assert.assertEquals(child.getMandatoryClassProperty(),
+                "org.opends.server.extensions.UserDefinedVirtualAttributeProvider");
+        Assert.assertEquals(child.getMandatoryReadOnlyAttributeTypeProperty(),
+                DirectoryServer.getAttributeType("description"));
+
+        // Test normalization.
+        assertSetEquals(child.getAggregationProperty(), "LDAP Connection Handler");
+        assertSetEquals(child.getAggregationProperty(), "  LDAP   Connection  Handler ");
+        assertSetEquals(child.getAggregationProperty(), "  ldap connection HANDLER ");
+    }
+
+    /** Assert that the values of child 4 are correct. */
+    private void assertChild4(TestChildCfg child) {
+        Assert.assertEquals(child.getMandatoryClassProperty(),
+                "org.opends.server.extensions.UserDefinedVirtualAttributeProvider");
+        Assert.assertEquals(child.getMandatoryReadOnlyAttributeTypeProperty(),
+                DirectoryServer.getAttributeType("description"));
+        assertSetEquals(child.getAggregationProperty(), "LDAPS Connection Handler", "LDAP Connection Handler");
+    }
+
+    /** Asserts that the actual set of DNs contains the expected values. */
+    private void assertSetEquals(SortedSet<String> actual, String... expected) {
+        SortedSet<String> values = new TreeSet<String>(TestChildCfgDefn.getInstance()
+                .getAggregationPropertyPropertyDefinition());
+        if (expected != null) {
+            for (String value : expected) {
+                values.add(value);
+            }
+        }
+        Assert.assertEquals((Object) actual, (Object) values);
+    }
+
+    /** Creates a test connection handler for testing. */
+    private void createConnectionHandler(boolean enabled) throws Exception {
+        RootCfgClient root = TestCaseUtils.getRootConfiguration();
+        LDAPConnectionHandlerCfgClient client = root.createConnectionHandler(
+                LDAPConnectionHandlerCfgDefn.getInstance(), TEST_CONNECTION_HANDLER_NAME, null);
+        client.setEnabled(enabled);
+        client.setListenPort(1000);
+        client.commit();
+    }
+
+    /** Deletes the test connection handler after testing. */
+    private void deleteConnectionHandler() throws Exception {
+        RootCfgClient root = TestCaseUtils.getRootConfiguration();
+        root.removeConnectionHandler(TEST_CONNECTION_HANDLER_NAME);
+    }
+
+    /** Gets the named parent configuration. */
+    private TestParentCfg getParent(String name) throws IllegalArgumentException, ConfigException {
+        ServerManagedObject<RootCfg> root = serverContext.getRootConfigurationManagedObject();
+        return root.getChild(TestCfg.getTestOneToManyParentRelationDefinition(), name).getConfiguration();
+    }
+}
diff --git a/opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/server/ConstraintTest.java b/opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/server/ConstraintTest.java
new file mode 100644
index 0000000..813a3a3
--- /dev/null
+++ b/opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/server/ConstraintTest.java
@@ -0,0 +1,489 @@
+/*
+ * 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 legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * 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 legal-notices/CDDLv1_0.txt.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2008 Sun Microsystems, Inc.
+ */
+package org.opends.server.admin.server;
+
+import java.util.List;
+
+import javax.naming.OperationNotSupportedException;
+import javax.naming.ldap.LdapName;
+
+import org.opends.server.admin.AdminTestCase;
+import org.opends.server.admin.TestCfg;
+import org.opends.server.admin.TestChildCfg;
+import org.opends.server.admin.TestChildCfgDefn;
+import org.opends.server.admin.TestParentCfg;
+import org.opends.server.config.ConfigException;
+import org.opends.server.types.ConfigChangeResult;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+/**
+ * Test cases for constraints on the server-side.
+ */
+public final class ConstraintTest extends AdminTestCase {
+
+    // Child DN.
+    private static final String TEST_CHILD_1_DN = "cn=test child 1,cn=test children,cn=test parent 1,cn=test parents,cn=config";
+
+    /**
+     * A test child add listener.
+     */
+    private static class AddListener implements ConfigurationAddListener<TestChildCfg> {
+
+        /**
+         * {@inheritDoc}
+         */
+        public ConfigChangeResult applyConfigurationAdd(TestChildCfg configuration) {
+            return new ConfigChangeResult(ResultCode.SUCCESS, false);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public boolean isConfigurationAddAcceptable(TestChildCfg configuration, List<Message> unacceptableReasons) {
+            return true;
+        }
+
+    }
+
+    /**
+     * A test child delete listener.
+     */
+    private static class DeleteListener implements ConfigurationDeleteListener<TestChildCfg> {
+
+        /**
+         * {@inheritDoc}
+         */
+        public ConfigChangeResult applyConfigurationDelete(TestChildCfg configuration) {
+            return new ConfigChangeResult(ResultCode.SUCCESS, false);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public boolean isConfigurationDeleteAcceptable(TestChildCfg configuration, List<Message> unacceptableReasons) {
+            return true;
+        }
+
+    }
+
+    /**
+     * A test child change listener.
+     */
+    private static class ChangeListener implements ConfigurationChangeListener<TestChildCfg> {
+
+        /**
+         * {@inheritDoc}
+         */
+        public ConfigChangeResult applyConfigurationChange(TestChildCfg configuration) {
+            return new ConfigChangeResult(ResultCode.SUCCESS, false);
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public boolean isConfigurationChangeAcceptable(TestChildCfg configuration, List<Message> unacceptableReasons) {
+            return true;
+        }
+
+    }
+
+    // Test child 1 LDIF.
+    private static final String[] TEST_CHILD_1 = new String[] {
+            "dn: cn=test child 1,cn=test children,cn=test parent 1,cn=test parents,cn=config", "objectclass: top",
+            "objectclass: ds-cfg-test-child-dummy", "cn: test child 1", "ds-cfg-enabled: true",
+            "ds-cfg-java-class: org.opends.server.extensions.UserDefinedVirtualAttributeProvider",
+            "ds-cfg-attribute-type: description", "ds-cfg-conflict-behavior: virtual-overrides-real" };
+
+    // Test LDIF.
+    private static final String[] TEST_LDIF = new String[] {
+            // Base entries.
+            "dn: cn=test parents,cn=config",
+            "objectclass: top",
+            "objectclass: ds-cfg-branch",
+            "cn: test parents",
+            "",
+            // Parent 1 - uses default values for
+            // optional-multi-valued-dn-property.
+            "dn: cn=test parent 1,cn=test parents,cn=config", "objectclass: top",
+            "objectclass: ds-cfg-test-parent-dummy", "cn: test parent 1", "ds-cfg-enabled: true",
+            "ds-cfg-java-class: org.opends.server.extensions.UserDefinedVirtualAttributeProvider",
+            "ds-cfg-attribute-type: description", "ds-cfg-conflict-behavior: virtual-overrides-real", "",
+            // Child base entries.
+            "dn:cn=test children,cn=test parent 1,cn=test parents,cn=config", "objectclass: top",
+            "objectclass: ds-cfg-branch", "cn: test children", "", };
+
+    // JNDI LDAP context.
+    private JNDIDirContextAdaptor adaptor = null;
+
+    /**
+     * Sets up tests
+     *
+     * @throws Exception
+     *             If the server could not be initialized.
+     */
+    @BeforeClass
+    public void setUp() throws Exception {
+        // This test suite depends on having the schema available, so
+        // we'll start the server.
+        TestCaseUtils.startServer();
+        TestCfg.setUp();
+
+        // Add test managed objects.
+        TestCaseUtils.addEntries(TEST_LDIF);
+    }
+
+    /**
+     * Tears down test environment.
+     *
+     * @throws Exception
+     *             If the test entries could not be removed.
+     */
+    @AfterClass
+    public void tearDown() throws Exception {
+        TestCfg.cleanup();
+
+        // Remove test entries.
+        deleteSubtree("cn=test parents,cn=config");
+    }
+
+    /**
+     * Tests that retrieval can succeed.
+     *
+     * @throws Exception
+     *             If the test unexpectedly fails.
+     */
+    @Test
+    public void testGetManagedObjectSuccess() throws Exception {
+        MockConstraint constraint = new MockConstraint(true, false);
+
+        try {
+            TestCaseUtils.addEntry(TEST_CHILD_1);
+            TestCfg.addConstraint(constraint);
+
+            TestParentCfg parent = getParent("test parent 1");
+            parent.getTestChild("test child 1");
+        } finally {
+            TestCfg.removeConstraint(constraint);
+
+            try {
+                deleteSubtree(TEST_CHILD_1_DN);
+            } catch (Exception e) {
+                // Do nothing.
+            }
+        }
+    }
+
+    /**
+     * Tests that retrieval can fail.
+     *
+     * @throws Exception
+     *             If the test unexpectedly fails.
+     */
+    @Test
+    public void testGetManagedObjectFail() throws Exception {
+        MockConstraint constraint = new MockConstraint(false, true);
+
+        try {
+            TestCaseUtils.addEntry(TEST_CHILD_1);
+            TestCfg.addConstraint(constraint);
+
+            TestParentCfg parent = getParent("test parent 1");
+            parent.getTestChild("test child 1");
+        } catch (ConfigException e) {
+            Throwable cause = e.getCause();
+            if (cause instanceof ConstraintViolationException) {
+                ConstraintViolationException cve = (ConstraintViolationException) cause;
+                Assert.assertEquals(cve.getMessages().size(), 1);
+                Assert.assertSame(cve.getManagedObject().getManagedObjectDefinition(), TestChildCfgDefn.getInstance());
+            } else {
+                // Wrong cause.
+                throw e;
+            }
+        } finally {
+            TestCfg.removeConstraint(constraint);
+
+            try {
+                deleteSubtree(TEST_CHILD_1_DN);
+            } catch (Exception e) {
+                // Do nothing.
+            }
+        }
+    }
+
+    /**
+     * Tests that an add constraint can succeed.
+     *
+     * @throws Exception
+     *             If the test unexpectedly fails.
+     */
+    @Test
+    public void testAddConstraintSuccess() throws Exception {
+        TestParentCfg parent = getParent("test parent 1");
+        AddListener listener = new AddListener();
+        parent.addTestChildAddListener(listener);
+
+        MockConstraint constraint = new MockConstraint(true, false);
+        TestCfg.addConstraint(constraint);
+
+        try {
+            try {
+                // Add the entry.
+                addEntry(ResultCode.SUCCESS, TEST_CHILD_1);
+            } finally {
+                try {
+                    deleteSubtree(TEST_CHILD_1_DN);
+                } catch (Exception e) {
+                    // Do nothing.
+                }
+            }
+        } finally {
+            TestCfg.removeConstraint(constraint);
+            parent.removeTestChildAddListener(listener);
+        }
+    }
+
+    /**
+     * Tests that an add constraint can fail.
+     *
+     * @throws Exception
+     *             If the test unexpectedly fails.
+     */
+    @Test
+    public void testAddConstraintFail() throws Exception {
+        TestParentCfg parent = getParent("test parent 1");
+        AddListener listener = new AddListener();
+        parent.addTestChildAddListener(listener);
+
+        MockConstraint constraint = new MockConstraint(false, true);
+        TestCfg.addConstraint(constraint);
+
+        try {
+            try {
+                // Add the entry.
+                addEntry(ResultCode.UNWILLING_TO_PERFORM, TEST_CHILD_1);
+            } finally {
+                try {
+                    deleteSubtree(TEST_CHILD_1_DN);
+                } catch (Exception e) {
+                    // Do nothing.
+                }
+            }
+        } finally {
+            TestCfg.removeConstraint(constraint);
+            parent.removeTestChildAddListener(listener);
+        }
+    }
+
+    /**
+     * Tests that a delete constraint can succeed.
+     *
+     * @throws Exception
+     *             If the test unexpectedly fails.
+     */
+    @Test
+    public void testDeleteConstraintSuccess() throws Exception {
+        TestParentCfg parent = getParent("test parent 1");
+        DeleteListener listener = new DeleteListener();
+        parent.addTestChildDeleteListener(listener);
+
+        MockConstraint constraint = new MockConstraint(false, true);
+        TestCfg.addConstraint(constraint);
+
+        try {
+            // Add the entry.
+            TestCaseUtils.addEntry(TEST_CHILD_1);
+
+            // Now delete it - this should trigger the constraint.
+            deleteSubtree(TEST_CHILD_1_DN);
+        } finally {
+            TestCfg.removeConstraint(constraint);
+            parent.removeTestChildDeleteListener(listener);
+
+            try {
+                // Clean up.
+                deleteSubtree(TEST_CHILD_1_DN);
+            } catch (Exception e) {
+                // Ignore.
+            }
+        }
+    }
+
+    /**
+     * Tests that a delete constraint can fail.
+     *
+     * @throws Exception
+     *             If the test unexpectedly fails.
+     */
+    @Test
+    public void testDeleteConstraintFail() throws Exception {
+        TestParentCfg parent = getParent("test parent 1");
+        DeleteListener listener = new DeleteListener();
+        parent.addTestChildDeleteListener(listener);
+
+        MockConstraint constraint = new MockConstraint(true, false);
+        TestCfg.addConstraint(constraint);
+
+        try {
+            // Add the entry.
+            TestCaseUtils.addEntry(TEST_CHILD_1);
+            try {
+                // Now delete it - this should trigger the constraint.
+                deleteSubtree(TEST_CHILD_1_DN);
+
+                // Should not have succeeded.
+                Assert.fail("Delete constraint failed to prevent deletion");
+            } catch (OperationNotSupportedException e) {
+                // Ignore - this is the expected exception.
+            }
+        } finally {
+            TestCfg.removeConstraint(constraint);
+            parent.removeTestChildDeleteListener(listener);
+
+            try {
+                // Clean up.
+                deleteSubtree(TEST_CHILD_1_DN);
+            } catch (Exception e) {
+                // Ignore.
+            }
+        }
+    }
+
+    /**
+     * Tests that a modify constraint can succeed.
+     *
+     * @throws Exception
+     *             If the test unexpectedly fails.
+     */
+    @Test
+    public void testChangeConstraintSuccess() throws Exception {
+        TestParentCfg parent = getParent("test parent 1");
+
+        MockConstraint constraint = new MockConstraint(true, false);
+
+        try {
+            // Add the entry.
+            TestCaseUtils.addEntry(TEST_CHILD_1);
+            TestChildCfg child = parent.getTestChild("test child 1");
+
+            TestCfg.addConstraint(constraint);
+            ChangeListener listener = new ChangeListener();
+            child.addChangeListener(listener);
+
+            // Now modify it.
+            String[] changes = new String[] {
+                    "dn: cn=test child 1,cn=test children,cn=test parent 1,cn=test parents,cn=config",
+                    "changetype: modify", "replace: ds-cfg-base-dn", "ds-cfg-base-dn: dc=new value 1,dc=com",
+                    "ds-cfg-base-dn: dc=new value 2,dc=com", "-", "replace: ds-cfg-group-dn",
+                    "ds-cfg-group-dn: dc=new value 3,dc=com", "ds-cfg-group-dn: dc=new value 4,dc=com" };
+
+            int result = TestCaseUtils.applyModifications(true, changes);
+            Assert.assertEquals(result, ResultCode.SUCCESS.getIntValue());
+        } finally {
+            TestCfg.removeConstraint(constraint);
+            try {
+                deleteSubtree(TEST_CHILD_1_DN);
+            } catch (Exception e) {
+                // Ignore.
+            }
+        }
+    }
+
+    /**
+     * Tests that a modify constraint can fail.
+     *
+     * @throws Exception
+     *             If the test unexpectedly fails.
+     */
+    @Test
+    public void testChangeConstraintFail() throws Exception {
+        TestParentCfg parent = getParent("test parent 1");
+        MockConstraint constraint = new MockConstraint(false, true);
+
+        try {
+            // Add the entry.
+            TestCaseUtils.addEntry(TEST_CHILD_1);
+            TestChildCfg child = parent.getTestChild("test child 1");
+
+            TestCfg.addConstraint(constraint);
+            ChangeListener listener = new ChangeListener();
+            child.addChangeListener(listener);
+
+            // Now modify it.
+            String[] changes = new String[] {
+                    "dn: cn=test child 1,cn=test children,cn=test parent 1,cn=test parents,cn=config",
+                    "changetype: modify", "replace: ds-cfg-base-dn", "ds-cfg-base-dn: dc=new value 1,dc=com",
+                    "ds-cfg-base-dn: dc=new value 2,dc=com", "-", "replace: ds-cfg-group-dn",
+                    "ds-cfg-group-dn: dc=new value 3,dc=com", "ds-cfg-group-dn: dc=new value 4,dc=com" };
+
+            int result = TestCaseUtils.applyModifications(true, changes);
+            Assert.assertEquals(result, ResultCode.UNWILLING_TO_PERFORM.getIntValue());
+        } finally {
+            TestCfg.removeConstraint(constraint);
+            try {
+                deleteSubtree(TEST_CHILD_1_DN);
+            } catch (Exception e) {
+                // Ignore.
+            }
+        }
+    }
+
+    // Add an entry and check its result.
+    private void addEntry(ResultCode expected, String... lines) throws Exception {
+        Entry entry = TestCaseUtils.makeEntry(lines);
+
+        InternalClientConnection conn = InternalClientConnection.getRootConnection();
+
+        AddOperation add = conn.processAdd(entry.getDN(), entry.getObjectClasses(), entry.getUserAttributes(),
+                entry.getOperationalAttributes());
+
+        Assert.assertEquals(add.getResultCode(), expected, add.getErrorMessage().toString());
+    }
+
+    // Deletes the named sub-tree.
+    private void deleteSubtree(String dn) throws Exception {
+        getAdaptor().deleteSubtree(new LdapName(dn));
+    }
+
+    // Gets the JNDI connection for the test server instance.
+    private synchronized JNDIDirContextAdaptor getAdaptor() throws Exception {
+        if (adaptor == null) {
+            adaptor = JNDIDirContextAdaptor.simpleSSLBind("127.0.0.1", TestCaseUtils.getServerAdminPort(),
+                    "cn=directory manager", "password");
+        }
+        return adaptor;
+    }
+
+    // Gets the named parent configuration.
+    private TestParentCfg getParent(String name) throws IllegalArgumentException, ConfigException {
+        ServerManagedObject<RootCfg> root = serverContext.getRootConfigurationManagedObject();
+        TestParentCfg parent = root.getChild(TestCfg.getTestOneToManyParentRelationDefinition(), name)
+                .getConfiguration();
+        return parent;
+    }
+}
diff --git a/opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/server/DNBuilderTest.java b/opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/server/DNBuilderTest.java
new file mode 100644
index 0000000..f3e8eb3
--- /dev/null
+++ b/opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/server/DNBuilderTest.java
@@ -0,0 +1,165 @@
+/*
+ * 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 legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * 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 legal-notices/CDDLv1_0.txt.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2008 Sun Microsystems, Inc.
+ */
+package org.opends.server.admin.server;
+
+import static org.testng.Assert.*;
+
+import org.forgerock.opendj.ldap.DN;
+import org.opends.server.admin.AdminTestCase;
+import org.opends.server.admin.Configuration;
+import org.opends.server.admin.ConfigurationClient;
+import org.opends.server.admin.LDAPProfile;
+import org.opends.server.admin.ManagedObjectPath;
+import org.opends.server.admin.RelationDefinition;
+import org.opends.server.admin.SingletonRelationDefinition;
+import org.opends.server.admin.TestCfg;
+import org.opends.server.admin.TestChildCfg;
+import org.opends.server.admin.TestChildCfgClient;
+import org.opends.server.admin.TestChildCfgDefn;
+import org.opends.server.admin.TestParentCfgDefn;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+/**
+ * Test cases for the server DNBuilder class.
+ */
+public final class DNBuilderTest extends AdminTestCase {
+
+    /**
+     * Sets up tests
+     *
+     * @throws Exception
+     *             If the server could not be initialized.
+     */
+    @BeforeClass
+    public void setUp() throws Exception {
+        disableClassValidationForProperties();
+        TestCfg.setUp();
+    }
+
+    /**
+     * Tears down test environment.
+     */
+    @AfterClass
+    public void tearDown() {
+        TestCfg.cleanup();
+    }
+
+    /**
+     * Tests construction of a DN from a managed object path containing a
+     * subordinate one-to-many relationship.
+     *
+     * @throws Exception
+     *             If an unexpected exception occurred.
+     */
+    @Test
+    public void testCreateOneToMany() throws Exception {
+        // First create the path.
+        ManagedObjectPath<? extends ConfigurationClient, ? extends Configuration> path = ManagedObjectPath
+                .emptyPath();
+
+        path = path.child(TestCfg.getTestOneToManyParentRelationDefinition(), "test-parent-1");
+        path = path.child(TestParentCfgDefn.getInstance().getTestChildrenRelationDefinition(), "test-child-1");
+
+        // Now serialize it.
+        DN actual = DNBuilder.create(path);
+        DN expected = DN.valueOf("cn=test-child-1,cn=test children,cn=test-parent-1,cn=test parents,cn=config");
+
+        assertEquals(actual, expected);
+    }
+
+    /**
+     * Tests construction of a DN from a managed object path containing a
+     * subordinate one-to-one relationship.
+     *
+     * @throws Exception
+     *             If an unexpected exception occurred.
+     */
+    @Test
+    public void testCreateOneToOne() throws Exception {
+        // First create the path.
+        ManagedObjectPath<? extends ConfigurationClient, ? extends Configuration> path = ManagedObjectPath
+                .emptyPath();
+
+        SingletonRelationDefinition.Builder<TestChildCfgClient, TestChildCfg> b = new SingletonRelationDefinition.Builder<TestChildCfgClient, TestChildCfg>(
+                TestParentCfgDefn.getInstance(), "singleton-test-child", TestChildCfgDefn.getInstance());
+        final SingletonRelationDefinition<TestChildCfgClient, TestChildCfg> r2 = b.getInstance();
+        LDAPProfile.Wrapper wrapper = new LDAPProfile.Wrapper() {
+
+            /**
+             * {@inheritDoc}
+             */
+            @Override
+            public String getRelationRDNSequence(RelationDefinition<?, ?> r) {
+                if (r == r2) {
+                    return "cn=singleton-test-child";
+                } else {
+                    return null;
+                }
+            }
+
+        };
+
+        path = path.child(TestCfg.getTestOneToManyParentRelationDefinition(), "test-parent-1");
+        path = path.child(r2);
+
+        // Now serialize it.
+        LDAPProfile.getInstance().pushWrapper(wrapper);
+        try {
+            DN actual = DNBuilder.create(path);
+            DN expected = DN.valueOf("cn=singleton-test-child,cn=test-parent-1,cn=test parents,cn=config");
+
+            assertEquals(actual, expected);
+        } finally {
+            LDAPProfile.getInstance().popWrapper();
+        }
+    }
+
+    /**
+     * Tests construction of a DN from a managed object path containing a
+     * subordinate one-to-zero-or-one relationship.
+     *
+     * @throws Exception
+     *             If an unexpected exception occurred.
+     */
+    @Test
+    public void testCreateOneToZeroOrOne() throws Exception {
+        // First create the path.
+        ManagedObjectPath<? extends ConfigurationClient, ? extends Configuration> path = ManagedObjectPath
+                .emptyPath();
+
+        path = path.child(TestCfg.getTestOneToManyParentRelationDefinition(), "test-parent-1");
+        path = path.child(TestParentCfgDefn.getInstance().getOptionalTestChildRelationDefinition());
+
+        // Now serialize it.
+        DN actual = DNBuilder.create(path);
+        DN expected = DN.valueOf("cn=optional test child,cn=test-parent-1,cn=test parents,cn=config");
+
+        assertEquals(actual, expected);
+    }
+
+}
diff --git a/opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/server/DefaultBehaviorTest.java b/opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/server/DefaultBehaviorTest.java
new file mode 100644
index 0000000..b37a014
--- /dev/null
+++ b/opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/server/DefaultBehaviorTest.java
@@ -0,0 +1,711 @@
+/*
+ * 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 legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * 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 legal-notices/CDDLv1_0.txt.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2008 Sun Microsystems, Inc.
+ */
+package org.opends.server.admin.server;
+
+import java.util.List;
+import java.util.SortedSet;
+
+import javax.naming.ldap.LdapName;
+
+import org.opends.server.admin.AdminTestCase;
+import org.opends.server.admin.TestCfg;
+import org.opends.server.admin.TestChildCfg;
+import org.opends.server.admin.TestParentCfg;
+import org.opends.server.config.ConfigException;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.types.ConfigChangeResult;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+/**
+ * Test cases for default behavior on the server-side.
+ */
+public final class DefaultBehaviorTest extends AdminTestCase {
+
+    /**
+     * A test child add listener.
+     */
+    private static class AddListener implements ConfigurationAddListener<TestChildCfg> {
+
+        // The child configuration that was added.
+        private TestChildCfg child;
+
+        /**
+         * Creates a new add listener.
+         */
+        public AddListener() {
+            // No implementation required.
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public ConfigChangeResult applyConfigurationAdd(TestChildCfg configuration) {
+            return new ConfigChangeResult(ResultCode.SUCCESS, false);
+        }
+
+        /**
+         * Gets the child configuration checking that it has the expected name.
+         *
+         * @param expectedName
+         *            The child's expected name.
+         * @return Returns the child configuration.
+         */
+        public TestChildCfg getChild(String expectedName) {
+            Assert.assertNotNull(child);
+            Assert.assertEquals(child.dn().getRDN().getAttributeValue(0).getValue().toString(), expectedName);
+            return child;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public boolean isConfigurationAddAcceptable(TestChildCfg configuration, List<Message> unacceptableReasons) {
+            child = configuration;
+            return true;
+        }
+
+    }
+
+    /**
+     * A test child change listener.
+     */
+    private static class ChangeListener implements ConfigurationChangeListener<TestChildCfg> {
+
+        // The child configuration that was changed.
+        private TestChildCfg child;
+
+        /**
+         * Creates a new change listener.
+         */
+        public ChangeListener() {
+            // No implementation required.
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public ConfigChangeResult applyConfigurationChange(TestChildCfg configuration) {
+            return new ConfigChangeResult(ResultCode.SUCCESS, false);
+        }
+
+        /**
+         * Gets the child configuration checking that it has the expected name.
+         *
+         * @param expectedName
+         *            The child's expected name.
+         * @return Returns the child configuration.
+         */
+        public TestChildCfg getChild(String expectedName) {
+            Assert.assertNotNull(child);
+            Assert.assertEquals(child.dn().getRDN().getAttributeValue(0).getValue().toString(), expectedName);
+            return child;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public boolean isConfigurationChangeAcceptable(TestChildCfg configuration, List<Message> unacceptableReasons) {
+            child = configuration;
+            return true;
+        }
+
+    }
+
+    // Test child 1 LDIF.
+    private static final String[] TEST_CHILD_1 = new String[] {
+            "dn: cn=test child 1,cn=test children,cn=test parent 1,cn=test parents,cn=config", "objectclass: top",
+            "objectclass: ds-cfg-test-child-dummy", "cn: test child 1", "ds-cfg-enabled: true",
+            "ds-cfg-java-class: org.opends.server.extensions.UserDefinedVirtualAttributeProvider",
+            "ds-cfg-attribute-type: description", "ds-cfg-conflict-behavior: virtual-overrides-real" };
+
+    // Test child 2 LDIF.
+    private static final String[] TEST_CHILD_2 = new String[] {
+            "dn: cn=test child 2,cn=test children,cn=test parent 1,cn=test parents,cn=config", "objectclass: top",
+            "objectclass: ds-cfg-test-child-dummy", "cn: test child 2", "ds-cfg-enabled: true",
+            "ds-cfg-java-class: org.opends.server.extensions.UserDefinedVirtualAttributeProvider",
+            "ds-cfg-attribute-type: description", "ds-cfg-conflict-behavior: virtual-overrides-real",
+            "ds-cfg-base-dn: dc=default value c2v1,dc=com", "ds-cfg-base-dn: dc=default value c2v2,dc=com" };
+
+    // Test child 3 LDIF.
+    private static final String[] TEST_CHILD_3 = new String[] {
+            "dn: cn=test child 3,cn=test children,cn=test parent 1,cn=test parents,cn=config", "objectclass: top",
+            "objectclass: ds-cfg-test-child-dummy", "cn: test child 3", "ds-cfg-enabled: true",
+            "ds-cfg-java-class: org.opends.server.extensions.UserDefinedVirtualAttributeProvider",
+            "ds-cfg-attribute-type: description", "ds-cfg-conflict-behavior: virtual-overrides-real",
+            "ds-cfg-base-dn: dc=default value c3v1,dc=com", "ds-cfg-base-dn: dc=default value c3v2,dc=com",
+            "ds-cfg-group-dn: dc=default value c3v3,dc=com", "ds-cfg-group-dn: dc=default value c3v4,dc=com" };
+
+    // Test child 4 LDIF.
+    private static final String[] TEST_CHILD_4 = new String[] {
+            "dn: cn=test child 4,cn=test children,cn=test parent 2,cn=test parents,cn=config", "objectclass: top",
+            "objectclass: ds-cfg-test-child-dummy", "cn: test child 4", "ds-cfg-enabled: true",
+            "ds-cfg-java-class: org.opends.server.extensions.UserDefinedVirtualAttributeProvider",
+            "ds-cfg-attribute-type: description", "ds-cfg-conflict-behavior: virtual-overrides-real" };
+
+    // Test LDIF.
+    private static final String[] TEST_LDIF = new String[] {
+            // Base entries.
+            "dn: cn=test parents,cn=config",
+            "objectclass: top",
+            "objectclass: ds-cfg-branch",
+            "cn: test parents",
+            "",
+            // Parent 1 - uses default values for
+            // optional-multi-valued-dn-property.
+            "dn: cn=test parent 1,cn=test parents,cn=config",
+            "objectclass: top",
+            "objectclass: ds-cfg-test-parent-dummy",
+            "cn: test parent 1",
+            "ds-cfg-enabled: true",
+            "ds-cfg-java-class: org.opends.server.extensions.UserDefinedVirtualAttributeProvider",
+            "ds-cfg-attribute-type: description",
+            "ds-cfg-conflict-behavior: virtual-overrides-real",
+            "",
+            // Parent 2 - overrides default values for
+            // optional-multi-valued-dn-property.
+            "dn: cn=test parent 2,cn=test parents,cn=config", "objectclass: top",
+            "objectclass: ds-cfg-test-parent-dummy", "cn: test parent 2", "ds-cfg-enabled: true",
+            "ds-cfg-java-class: org.opends.server.extensions.UserDefinedVirtualAttributeProvider",
+            "ds-cfg-attribute-type: description", "ds-cfg-conflict-behavior: virtual-overrides-real",
+            "ds-cfg-base-dn: dc=default value p2v1,dc=com",
+            "ds-cfg-base-dn: dc=default value p2v2,dc=com",
+            "",
+            // Child base entries.
+            "dn:cn=test children,cn=test parent 1,cn=test parents,cn=config", "objectclass: top",
+            "objectclass: ds-cfg-branch", "cn: test children", "",
+            "dn:cn=test children,cn=test parent 2,cn=test parents,cn=config", "objectclass: top",
+            "objectclass: ds-cfg-branch", "cn: test children", "" };
+
+    // JNDI LDAP context.
+    private JNDIDirContextAdaptor adaptor = null;
+
+    /**
+     * Sets up tests
+     *
+     * @throws Exception
+     *             If the server could not be initialized.
+     */
+    @BeforeClass
+    public void setUp() throws Exception {
+        // This test suite depends on having the schema available, so
+        // we'll start the server.
+        TestCaseUtils.startServer();
+        TestCfg.setUp();
+
+        // Add test managed objects.
+        TestCaseUtils.addEntries(TEST_LDIF);
+    }
+
+    /**
+     * Tears down test environment.
+     *
+     * @throws Exception
+     *             If the test entries could not be removed.
+     */
+    @AfterClass
+    public void tearDown() throws Exception {
+        TestCfg.cleanup();
+
+        // Remove test entries.
+        deleteSubtree("cn=test parents,cn=config");
+    }
+
+    /**
+     * Tests that children have correct values when accessed through an add
+     * listener.
+     *
+     * @throws Exception
+     *             If the test unexpectedly fails.
+     */
+    @Test
+    public void testAddListenerChildValues1() throws Exception {
+        TestParentCfg parent = getParent("test parent 1");
+        AddListener listener = new AddListener();
+        parent.addTestChildAddListener(listener);
+
+        try {
+            // Add the entry.
+            TestCaseUtils.addEntry(TEST_CHILD_1);
+            try {
+                assertChild1(listener.getChild("test child 1"));
+            } finally {
+                deleteSubtree("cn=test child 1,cn=test children,cn=test parent 1,cn=test parents,cn=config");
+            }
+        } finally {
+            parent.removeTestChildAddListener(listener);
+        }
+    }
+
+    /**
+     * Tests that children have correct values when accessed through an add
+     * listener.
+     *
+     * @throws Exception
+     *             If the test unexpectedly fails.
+     */
+    @Test
+    public void testAddListenerChildValues2() throws Exception {
+        TestParentCfg parent = getParent("test parent 1");
+        AddListener listener = new AddListener();
+        parent.addTestChildAddListener(listener);
+
+        try {
+            // Add the entry.
+            TestCaseUtils.addEntry(TEST_CHILD_2);
+            try {
+                assertChild2(listener.getChild("test child 2"));
+            } finally {
+                deleteSubtree("cn=test child 2,cn=test children,cn=test parent 1,cn=test parents,cn=config");
+            }
+        } finally {
+            parent.removeTestChildAddListener(listener);
+        }
+    }
+
+    /**
+     * Tests that children have correct values when accessed through an add
+     * listener.
+     *
+     * @throws Exception
+     *             If the test unexpectedly fails.
+     */
+    @Test
+    public void testAddListenerChildValues3() throws Exception {
+        TestParentCfg parent = getParent("test parent 1");
+        AddListener listener = new AddListener();
+        parent.addTestChildAddListener(listener);
+
+        try {
+            // Add the entry.
+            TestCaseUtils.addEntry(TEST_CHILD_3);
+            try {
+                assertChild3(listener.getChild("test child 3"));
+            } finally {
+                deleteSubtree("cn=test child 3,cn=test children,cn=test parent 1,cn=test parents,cn=config");
+            }
+        } finally {
+            parent.removeTestChildAddListener(listener);
+        }
+    }
+
+    /**
+     * Tests that children have correct values when accessed through an add
+     * listener.
+     *
+     * @throws Exception
+     *             If the test unexpectedly fails.
+     */
+    @Test
+    public void testAddListenerChildValues4() throws Exception {
+        TestParentCfg parent = getParent("test parent 2");
+        AddListener listener = new AddListener();
+        parent.addTestChildAddListener(listener);
+
+        try {
+            // Add the entry.
+            TestCaseUtils.addEntry(TEST_CHILD_4);
+            try {
+                assertChild4(listener.getChild("test child 4"));
+            } finally {
+                deleteSubtree("cn=test child 4,cn=test children,cn=test parent 2,cn=test parents,cn=config");
+            }
+        } finally {
+            parent.removeTestChildAddListener(listener);
+        }
+    }
+
+    /**
+     * Tests that children have correct values when accessed through a change
+     * listener. This test replaces the defaulted properties with real values.
+     *
+     * @throws Exception
+     *             If the test unexpectedly fails.
+     */
+    @Test
+    public void testChangeListenerChildValues1() throws Exception {
+        TestParentCfg parent = getParent("test parent 1");
+
+        try {
+            // Add the entry.
+            TestCaseUtils.addEntry(TEST_CHILD_1);
+            TestChildCfg child = parent.getTestChild("test child 1");
+            ChangeListener listener = new ChangeListener();
+            child.addChangeListener(listener);
+
+            // Now modify it.
+            String[] changes = new String[] {
+                    "dn: cn=test child 1,cn=test children,cn=test parent 1,cn=test parents,cn=config",
+                    "changetype: modify", "replace: ds-cfg-base-dn", "ds-cfg-base-dn: dc=new value 1,dc=com",
+                    "ds-cfg-base-dn: dc=new value 2,dc=com", "-", "replace: ds-cfg-group-dn",
+                    "ds-cfg-group-dn: dc=new value 3,dc=com", "ds-cfg-group-dn: dc=new value 4,dc=com" };
+            TestCaseUtils.applyModifications(true, changes);
+
+            // Make sure that the change listener was notified and the
+            // modified child contains the correct values.
+            child = listener.getChild("test child 1");
+
+            Assert.assertEquals(child.getMandatoryClassProperty(),
+                    "org.opends.server.extensions.UserDefinedVirtualAttributeProvider");
+            Assert.assertEquals(child.getMandatoryReadOnlyAttributeTypeProperty(),
+                    DirectoryServer.getAttributeType("description"));
+            assertDNSetEquals(child.getOptionalMultiValuedDNProperty1(), "dc=new value 1,dc=com",
+                    "dc=new value 2,dc=com");
+            assertDNSetEquals(child.getOptionalMultiValuedDNProperty2(), "dc=new value 3,dc=com",
+                    "dc=new value 4,dc=com");
+        } finally {
+            deleteSubtree("cn=test child 1,cn=test children,cn=test parent 1,cn=test parents,cn=config");
+        }
+    }
+
+    /**
+     * Tests that children have correct values when accessed through a change
+     * listener. This test makes sure that default values inherited from within
+     * the modified component itself behave as expected.
+     *
+     * @throws Exception
+     *             If the test unexpectedly fails.
+     */
+    @Test
+    public void testChangeListenerChildValues2() throws Exception {
+        TestParentCfg parent = getParent("test parent 1");
+
+        try {
+            // Add the entry.
+            TestCaseUtils.addEntry(TEST_CHILD_1);
+            TestChildCfg child = parent.getTestChild("test child 1");
+            ChangeListener listener = new ChangeListener();
+            child.addChangeListener(listener);
+
+            // Now modify it.
+            String[] changes = new String[] {
+                    "dn: cn=test child 1,cn=test children,cn=test parent 1,cn=test parents,cn=config",
+                    "changetype: modify", "replace: ds-cfg-base-dn", "ds-cfg-base-dn: dc=new value 1,dc=com",
+                    "ds-cfg-base-dn: dc=new value 2,dc=com" };
+            TestCaseUtils.applyModifications(true, changes);
+
+            // Make sure that the change listener was notified and the
+            // modified child contains the correct values.
+            child = listener.getChild("test child 1");
+
+            Assert.assertEquals(child.getMandatoryClassProperty(),
+                    "org.opends.server.extensions.UserDefinedVirtualAttributeProvider");
+            Assert.assertEquals(child.getMandatoryReadOnlyAttributeTypeProperty(),
+                    DirectoryServer.getAttributeType("description"));
+            assertDNSetEquals(child.getOptionalMultiValuedDNProperty1(), "dc=new value 1,dc=com",
+                    "dc=new value 2,dc=com");
+            assertDNSetEquals(child.getOptionalMultiValuedDNProperty2(), "dc=new value 1,dc=com",
+                    "dc=new value 2,dc=com");
+        } finally {
+            deleteSubtree("cn=test child 1,cn=test children,cn=test parent 1,cn=test parents,cn=config");
+        }
+    }
+
+    /**
+     * Tests that children have correct values when accessed through a change
+     * listener. This test makes sure that default values inherited from outside
+     * the modified component behave as expected.
+     *
+     * @throws Exception
+     *             If the test unexpectedly fails.
+     */
+    @Test
+    public void testChangeListenerChildValues3() throws Exception {
+        TestParentCfg parent = getParent("test parent 1");
+
+        try {
+            // Add the entry.
+            TestCaseUtils.addEntry(TEST_CHILD_1);
+            TestChildCfg child = parent.getTestChild("test child 1");
+            ChangeListener listener = new ChangeListener();
+            child.addChangeListener(listener);
+
+            // Now modify it.
+            String[] changes = new String[] {
+                    "dn: cn=test child 1,cn=test children,cn=test parent 1,cn=test parents,cn=config",
+                    "changetype: modify", "replace: ds-cfg-group-dn", "ds-cfg-group-dn: dc=new value 1,dc=com",
+                    "ds-cfg-group-dn: dc=new value 2,dc=com" };
+            TestCaseUtils.applyModifications(true, changes);
+
+            // Make sure that the change listener was notified and the
+            // modified child contains the correct values.
+            child = listener.getChild("test child 1");
+
+            Assert.assertEquals(child.getMandatoryClassProperty(),
+                    "org.opends.server.extensions.UserDefinedVirtualAttributeProvider");
+            Assert.assertEquals(child.getMandatoryReadOnlyAttributeTypeProperty(),
+                    DirectoryServer.getAttributeType("description"));
+            assertDNSetEquals(child.getOptionalMultiValuedDNProperty1(), "dc=domain1,dc=com", "dc=domain2,dc=com",
+                    "dc=domain3,dc=com");
+            assertDNSetEquals(child.getOptionalMultiValuedDNProperty2(), "dc=new value 1,dc=com",
+                    "dc=new value 2,dc=com");
+        } finally {
+            deleteSubtree("cn=test child 1,cn=test children,cn=test parent 1,cn=test parents,cn=config");
+        }
+    }
+
+    /**
+     * Tests that children have correct values when accessed through a change
+     * listener. This test makes sure that a component is notified when the
+     * default values it inherits from another component are modified.
+     *
+     * @throws Exception
+     *             If the test unexpectedly fails.
+     */
+    @Test
+    public void testChangeListenerChildValues4() throws Exception {
+        TestParentCfg parent = getParent("test parent 1");
+
+        try {
+            // Add the entry.
+            TestCaseUtils.addEntry(TEST_CHILD_1);
+            TestChildCfg child = parent.getTestChild("test child 1");
+            ChangeListener listener = new ChangeListener();
+            child.addChangeListener(listener);
+
+            // Now modify the parent.
+            String[] changes = new String[] { "dn: cn=test parent 1,cn=test parents,cn=config", "changetype: modify",
+                    "replace: ds-cfg-base-dn", "ds-cfg-base-dn: dc=new value 1,dc=com",
+                    "ds-cfg-base-dn: dc=new value 2,dc=com" };
+            TestCaseUtils.applyModifications(true, changes);
+
+            // Make sure that the change listener was notified and the
+            // modified child contains the correct values.
+            child = listener.getChild("test child 1");
+
+            Assert.assertEquals(child.getMandatoryClassProperty(),
+                    "org.opends.server.extensions.UserDefinedVirtualAttributeProvider");
+            Assert.assertEquals(child.getMandatoryReadOnlyAttributeTypeProperty(),
+                    DirectoryServer.getAttributeType("description"));
+            assertDNSetEquals(child.getOptionalMultiValuedDNProperty1(), "dc=new value 1,dc=com",
+                    "dc=new value 2,dc=com");
+            assertDNSetEquals(child.getOptionalMultiValuedDNProperty2(), "dc=new value 1,dc=com",
+                    "dc=new value 2,dc=com");
+        } finally {
+            deleteSubtree("cn=test child 1,cn=test children,cn=test parent 1,cn=test parents,cn=config");
+
+            // Undo the modifications.
+            String[] changes = new String[] { "dn: cn=test parent 1,cn=test parents,cn=config", "changetype: modify",
+                    "delete: ds-cfg-base-dn" };
+            TestCaseUtils.applyModifications(true, changes);
+        }
+    }
+
+    /**
+     * Tests that children have correct values.
+     *
+     * @throws Exception
+     *             If the test unexpectedly fails.
+     */
+    @Test
+    public void testChildValues1() throws Exception {
+        // Add the entry.
+        TestCaseUtils.addEntry(TEST_CHILD_1);
+
+        try {
+            TestParentCfg parent = getParent("test parent 1");
+            assertChild1(parent.getTestChild("test child 1"));
+        } finally {
+            deleteSubtree("cn=test child 1,cn=test children,cn=test parent 1,cn=test parents,cn=config");
+        }
+    }
+
+    /**
+     * Tests that children have correct values.
+     *
+     * @throws Exception
+     *             If the test unexpectedly fails.
+     */
+    @Test
+    public void testChildValues2() throws Exception {
+        // Add the entry.
+        TestCaseUtils.addEntry(TEST_CHILD_2);
+
+        try {
+            TestParentCfg parent = getParent("test parent 1");
+            assertChild2(parent.getTestChild("test child 2"));
+        } finally {
+            deleteSubtree("cn=test child 2,cn=test children,cn=test parent 1,cn=test parents,cn=config");
+        }
+    }
+
+    /**
+     * Tests that children have correct values.
+     *
+     * @throws Exception
+     *             If the test unexpectedly fails.
+     */
+    @Test
+    public void testChildValues3() throws Exception {
+        // Add the entry.
+        TestCaseUtils.addEntry(TEST_CHILD_3);
+
+        try {
+            TestParentCfg parent = getParent("test parent 1");
+            assertChild3(parent.getTestChild("test child 3"));
+        } finally {
+            deleteSubtree("cn=test child 3,cn=test children,cn=test parent 1,cn=test parents,cn=config");
+        }
+    }
+
+    /**
+     * Tests that children have correct values.
+     *
+     * @throws Exception
+     *             If the test unexpectedly fails.
+     */
+    @Test
+    public void testChildValues4() throws Exception {
+        // Add the entry.
+        TestCaseUtils.addEntry(TEST_CHILD_4);
+
+        try {
+            TestParentCfg parent = getParent("test parent 2");
+            assertChild4(parent.getTestChild("test child 4"));
+        } finally {
+            deleteSubtree("cn=test child 4,cn=test children,cn=test parent 2,cn=test parents,cn=config");
+        }
+    }
+
+    /**
+     * Tests that parent 1 has correct values.
+     *
+     * @throws Exception
+     *             If the test unexpectedly fails.
+     */
+    @Test
+    public void testParentValues1() throws Exception {
+        TestParentCfg parent = getParent("test parent 1");
+
+        Assert.assertEquals(parent.getMandatoryClassProperty(),
+                "org.opends.server.extensions.UserDefinedVirtualAttributeProvider");
+        Assert.assertEquals(parent.getMandatoryReadOnlyAttributeTypeProperty(),
+                DirectoryServer.getAttributeType("description"));
+        assertDNSetEquals(parent.getOptionalMultiValuedDNProperty(), "dc=domain1,dc=com", "dc=domain2,dc=com",
+                "dc=domain3,dc=com");
+    }
+
+    /**
+     * Tests that parent 2 has correct values.
+     *
+     * @throws Exception
+     *             If the test unexpectedly fails.
+     */
+    @Test
+    public void testParentValues2() throws Exception {
+        TestParentCfg parent = getParent("test parent 2");
+
+        Assert.assertEquals(parent.getMandatoryClassProperty(),
+                "org.opends.server.extensions.UserDefinedVirtualAttributeProvider");
+        Assert.assertEquals(parent.getMandatoryReadOnlyAttributeTypeProperty(),
+                DirectoryServer.getAttributeType("description"));
+        assertDNSetEquals(parent.getOptionalMultiValuedDNProperty(), "dc=default value p2v1,dc=com",
+                "dc=default value p2v2,dc=com");
+    }
+
+    // Assert that the values of child 1 are correct.
+    private void assertChild1(TestChildCfg child) {
+        Assert.assertEquals(child.getMandatoryClassProperty(),
+                "org.opends.server.extensions.UserDefinedVirtualAttributeProvider");
+        Assert.assertEquals(child.getMandatoryReadOnlyAttributeTypeProperty(),
+                DirectoryServer.getAttributeType("description"));
+        assertDNSetEquals(child.getOptionalMultiValuedDNProperty1(), "dc=domain1,dc=com", "dc=domain2,dc=com",
+                "dc=domain3,dc=com");
+        assertDNSetEquals(child.getOptionalMultiValuedDNProperty2(), "dc=domain1,dc=com", "dc=domain2,dc=com",
+                "dc=domain3,dc=com");
+    }
+
+    // Assert that the values of child 2 are correct.
+    private void assertChild2(TestChildCfg child) {
+        Assert.assertEquals(child.getMandatoryClassProperty(),
+                "org.opends.server.extensions.UserDefinedVirtualAttributeProvider");
+        Assert.assertEquals(child.getMandatoryReadOnlyAttributeTypeProperty(),
+                DirectoryServer.getAttributeType("description"));
+        assertDNSetEquals(child.getOptionalMultiValuedDNProperty1(), "dc=default value c2v1,dc=com",
+                "dc=default value c2v2,dc=com");
+        assertDNSetEquals(child.getOptionalMultiValuedDNProperty2(), "dc=default value c2v1,dc=com",
+                "dc=default value c2v2,dc=com");
+    }
+
+    // Assert that the values of child 3 are correct.
+    private void assertChild3(TestChildCfg child) {
+        Assert.assertEquals(child.getMandatoryClassProperty(),
+                "org.opends.server.extensions.UserDefinedVirtualAttributeProvider");
+        Assert.assertEquals(child.getMandatoryReadOnlyAttributeTypeProperty(),
+                DirectoryServer.getAttributeType("description"));
+        assertDNSetEquals(child.getOptionalMultiValuedDNProperty1(), "dc=default value c3v1,dc=com",
+                "dc=default value c3v2,dc=com");
+        assertDNSetEquals(child.getOptionalMultiValuedDNProperty2(), "dc=default value c3v3,dc=com",
+                "dc=default value c3v4,dc=com");
+    }
+
+    // Assert that the values of child 4 are correct.
+    private void assertChild4(TestChildCfg child) {
+        Assert.assertEquals(child.getMandatoryClassProperty(),
+                "org.opends.server.extensions.UserDefinedVirtualAttributeProvider");
+        Assert.assertEquals(child.getMandatoryReadOnlyAttributeTypeProperty(),
+                DirectoryServer.getAttributeType("description"));
+        assertDNSetEquals(child.getOptionalMultiValuedDNProperty1(), "dc=default value p2v1,dc=com",
+                "dc=default value p2v2,dc=com");
+        assertDNSetEquals(child.getOptionalMultiValuedDNProperty2(), "dc=default value p2v1,dc=com",
+                "dc=default value p2v2,dc=com");
+    }
+
+    // Asserts that the actual set of DNs contains the expected values.
+    private void assertDNSetEquals(SortedSet<DN> actual, String... expected) {
+        String[] actualStrings = new String[actual.size()];
+        int i = 0;
+        for (DN dn : actual) {
+            actualStrings[i] = dn.toString();
+            i++;
+        }
+        Assert.assertEqualsNoOrder(actualStrings, expected);
+    }
+
+    // Deletes the named sub-tree.
+    private void deleteSubtree(String dn) throws Exception {
+        getAdaptor().deleteSubtree(new LdapName(dn));
+    }
+
+    // Gets the JNDI connection for the test server instance.
+    private synchronized JNDIDirContextAdaptor getAdaptor() throws Exception {
+        if (adaptor == null) {
+            adaptor = JNDIDirContextAdaptor.simpleSSLBind("127.0.0.1", TestCaseUtils.getServerAdminPort(),
+                    "cn=directory manager", "password");
+        }
+        return adaptor;
+    }
+
+    // Gets the named parent configuration.
+    private TestParentCfg getParent(String name) throws IllegalArgumentException, ConfigException {
+        ServerManagedObject<RootCfg> root = serverContext.getRootConfigurationManagedObject();
+        TestParentCfg parent = root.getChild(TestCfg.getTestOneToManyParentRelationDefinition(), name)
+                .getConfiguration();
+        return parent;
+    }
+}
diff --git a/opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/server/ListenerTest.java b/opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/server/ListenerTest.java
new file mode 100644
index 0000000..cff57d5
--- /dev/null
+++ b/opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/server/ListenerTest.java
@@ -0,0 +1,275 @@
+/*
+ * 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 legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * 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 legal-notices/CDDLv1_0.txt.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2007-2008 Sun Microsystems, Inc.
+ */
+package org.opends.server.admin.server;
+
+import static org.fest.assertions.Assertions.*;
+import static org.mockito.Mockito.*;
+
+import java.util.Arrays;
+import org.forgerock.opendj.admin.server.RootCfg;
+import org.forgerock.opendj.ldap.DN;
+import org.forgerock.opendj.ldap.Entry;
+import org.forgerock.opendj.ldap.TestCaseUtils;
+import org.mockito.ArgumentCaptor;
+import org.opends.server.admin.AdminTestCase;
+import org.opends.server.admin.TestCfg;
+import org.opends.server.admin.TestParentCfg;
+import org.opends.server.api.ConfigAddListener;
+import org.opends.server.api.ConfigChangeListener;
+import org.opends.server.api.ConfigDeleteListener;
+import org.opends.server.config.ConfigException;
+import org.opends.server.config.ConfigurationRepository;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+
+@SuppressWarnings({"javadoc", "rawtypes", "unchecked"})
+public class ListenerTest extends AdminTestCase {
+
+    private static final DN ROOT_CONFIG_DN = DN.valueOf("cn=config");
+    private static final DN TEST_PARENTS_DN = DN.valueOf("cn=test parents,cn=config");
+
+    @BeforeClass
+    public void setUp() throws Exception {
+        disableClassValidationForProperties();
+        TestCfg.setUp();
+    }
+
+    @AfterClass
+    public void tearDown() throws Exception {
+        TestCfg.cleanup();
+    }
+
+    private Entry getTestParentEntry() throws Exception {
+        return TestCaseUtils.makeEntry(
+            "dn: cn=test parents,cn=config",
+            "objectclass: top",
+            "objectclass: ds-cfg-branch",
+            "cn: test parents");
+    }
+
+    /**
+     * Create a mock of ConfigurationRepository with provided DNs registered.
+     */
+    private ConfigurationRepository createConfigRepositoryWithDNs(DN...dns) throws ConfigException {
+        ConfigurationRepository configRepository = mock(ConfigurationRepository.class);
+        for (DN dn : dns) {
+            when(configRepository.hasEntry(dn)).thenReturn(true);
+        }
+        return configRepository;
+    }
+
+    /** Register a listener for test parent entry and return the actual registered listener */
+    private ConfigAddListener registerAddListenerForTestParent(ConfigurationRepository configRepository,
+            ServerManagedObject<RootCfg> root, ConfigurationAddListener<TestParentCfg> parentListener)
+                throws Exception {
+        root.registerAddListener(TestCfg.getTestOneToManyParentRelationDefinition(), parentListener);
+
+        ArgumentCaptor<ConfigAddListener> registered = ArgumentCaptor.forClass(ConfigAddListener.class);
+        verify(configRepository).registerAddListener(eq(TEST_PARENTS_DN), registered.capture());
+        return registered.getValue();
+    }
+
+    /** Register a listener for test parent entry in delayed scenario and return the actual registered listener */
+    private DelayedConfigAddListener registerAddListenerForTestParentDelayed(ConfigurationRepository configRepository,
+            ServerManagedObject<RootCfg> root, ConfigurationAddListener<TestParentCfg> parentListener)
+                throws Exception {
+        root.registerAddListener(TestCfg.getTestOneToManyParentRelationDefinition(), parentListener);
+
+        ArgumentCaptor<DelayedConfigAddListener> registered = ArgumentCaptor.forClass(DelayedConfigAddListener.class);
+        verify(configRepository).registerAddListener(eq(ROOT_CONFIG_DN), registered.capture());
+        return registered.getValue();
+    }
+
+    @Test
+    public void testRegisterAddListenerWithInstantiableRelationImmediate() throws Exception {
+        ConfigurationRepository configRepository = createConfigRepositoryWithDNs(ROOT_CONFIG_DN, TEST_PARENTS_DN);
+        ServerManagementContext context = new ServerManagementContext(configRepository);
+        ServerManagedObject<RootCfg> root = context.getRootConfigurationManagedObject();
+
+        root.registerAddListener(TestCfg.getTestOneToManyParentRelationDefinition(),
+                mock(ConfigurationAddListener.class));
+
+        verify(configRepository).registerAddListener(eq(TEST_PARENTS_DN),
+                isA(ConfigAddListener.class));
+    }
+
+    @Test
+    public void testRegisterAddListenerWithInstantiableRelationDelayed() throws Exception {
+        ConfigurationRepository configRepository = createConfigRepositoryWithDNs(ROOT_CONFIG_DN);
+        ServerManagementContext context = new ServerManagementContext(configRepository);
+        ServerManagedObject<RootCfg> root = context.getRootConfigurationManagedObject();
+
+        ConfigurationAddListener<TestParentCfg> parentListener = mock(ConfigurationAddListener.class);
+        root.registerAddListener(TestCfg.getTestOneToManyParentRelationDefinition(), parentListener);
+
+        ArgumentCaptor<DelayedConfigAddListener> registered = ArgumentCaptor.forClass(DelayedConfigAddListener.class);
+        verify(configRepository).registerAddListener(eq(ROOT_CONFIG_DN), registered.capture());
+        // check that actual listener is the one provided to the root
+        ConfigurationAddListener<?> actualListener =
+            ((ServerManagedObjectAddListenerAdaptor<?>)
+                ((ConfigAddListenerAdaptor<?>) registered.getValue().getDelayedAddListener()).
+                    getServerManagedObjectAddListener()).getConfigurationAddListener();
+        assertThat(actualListener).isEqualTo(parentListener);
+    }
+
+    @Test
+    public void testRegisterAddListenerWithInstantiableRelationDelayedThenActualized() throws Exception {
+        ConfigurationRepository configRepository = createConfigRepositoryWithDNs(ROOT_CONFIG_DN);
+        ServerManagementContext context = new ServerManagementContext(configRepository);
+        ServerManagedObject<RootCfg> root = context.getRootConfigurationManagedObject();
+
+        // register a listener to root
+        ConfigurationAddListener<TestParentCfg> parentListener = mock(ConfigurationAddListener.class);
+        root.registerAddListener(TestCfg.getTestOneToManyParentRelationDefinition(), parentListener);
+
+        // get the delayed listener registered to configuration repository
+        ArgumentCaptor<DelayedConfigAddListener> registered = ArgumentCaptor.forClass(DelayedConfigAddListener.class);
+        verify(configRepository).registerAddListener(eq(ROOT_CONFIG_DN), registered.capture());
+
+        // now simulate the add of target entry
+        String parentDN = "cn=test parents,cn=config";
+        when(configRepository.hasEntry(DN.valueOf(parentDN))).thenReturn(true);
+        registered.getValue().applyConfigurationAdd(getTestParentEntry());
+
+        // check that listener is added for target entry and deleted for its parent entry
+        ConfigAddListenerAdaptor listener =
+                (ConfigAddListenerAdaptor<?>) registered.getValue().getDelayedAddListener();
+        verify(configRepository).registerAddListener(DN.valueOf(parentDN), listener);
+        verify(configRepository).deregisterAddListener(ROOT_CONFIG_DN, registered.getValue());
+    }
+
+    @Test
+    public void testRegisterAddListenerWithOptionalRelation() throws Exception {
+        ConfigurationRepository configRepository = createConfigRepositoryWithDNs(ROOT_CONFIG_DN);
+        ServerManagementContext context = new ServerManagementContext(configRepository);
+        ServerManagedObject<RootCfg> root = context.getRootConfigurationManagedObject();
+
+        root.registerAddListener(TestCfg.getTestOneToZeroOrOneParentRelationDefinition(),
+            mock(ConfigurationAddListener.class));
+
+        verify(configRepository).registerAddListener(eq(ROOT_CONFIG_DN),
+                isA(ConfigAddListener.class));
+    }
+
+    @Test
+    public void testRegisterDeleteListenerWithInstantiableRelationImmediate() throws Exception {
+        ConfigurationRepository configRepository = createConfigRepositoryWithDNs(ROOT_CONFIG_DN, TEST_PARENTS_DN);
+        ServerManagementContext context = new ServerManagementContext(configRepository);
+        ServerManagedObject<RootCfg> root = context.getRootConfigurationManagedObject();
+
+        root.registerDeleteListener(TestCfg.getTestOneToManyParentRelationDefinition(),
+                mock(ConfigurationDeleteListener.class));
+
+        verify(configRepository).registerDeleteListener(eq(TEST_PARENTS_DN),
+                isA(ConfigDeleteListener.class));
+    }
+
+    @Test
+    public void testRegisterDeleteListenerWithInstantiableRelationDelayed() throws Exception {
+        ConfigurationRepository configRepository = createConfigRepositoryWithDNs(ROOT_CONFIG_DN);
+        ServerManagementContext context = new ServerManagementContext(configRepository);
+        ServerManagedObject<RootCfg> root = context.getRootConfigurationManagedObject();
+
+        ConfigurationDeleteListener<TestParentCfg> parentListener = mock(ConfigurationDeleteListener.class);
+        root.registerDeleteListener(TestCfg.getTestOneToManyParentRelationDefinition(), parentListener);
+
+        ArgumentCaptor<DelayedConfigAddListener> argument = ArgumentCaptor.forClass(DelayedConfigAddListener.class);
+        verify(configRepository).registerAddListener(eq(ROOT_CONFIG_DN), argument.capture());
+        // check that actual listener is the one provided to the root
+        ConfigurationDeleteListener actualListener =
+                ((ServerManagedObjectDeleteListenerAdaptor)
+                        ((ConfigDeleteListenerAdaptor)argument.getValue().getDelayedDeleteListener()).
+                            getServerManagedObjectDeleteListener()).getConfigurationDeleteListener();
+        assertThat(actualListener).isEqualTo(parentListener);
+    }
+
+    @Test
+    public void testRegisterDeleteListenerWithOptionalRelation() throws Exception {
+        ConfigurationRepository configRepository = createConfigRepositoryWithDNs(ROOT_CONFIG_DN);
+        ServerManagementContext context = new ServerManagementContext(configRepository);
+        ServerManagedObject<RootCfg> root = context.getRootConfigurationManagedObject();
+
+        root.registerDeleteListener(TestCfg.getTestOneToZeroOrOneParentRelationDefinition(),
+                mock(ConfigurationDeleteListener.class));
+
+        verify(configRepository).registerDeleteListener(eq(ROOT_CONFIG_DN),
+                isA(ConfigDeleteListener.class));
+    }
+
+    @Test
+    public void testRegisterChangeListener() throws Exception {
+        ConfigurationRepository configRepository = createConfigRepositoryWithDNs(ROOT_CONFIG_DN);
+        ServerManagementContext context = new ServerManagementContext(configRepository);
+        ServerManagedObject<RootCfg> root = context.getRootConfigurationManagedObject();
+        root.setConfigDN(ROOT_CONFIG_DN);
+
+        root.registerChangeListener(mock(ConfigurationChangeListener.class));
+
+        verify(configRepository).registerChangeListener(eq(ROOT_CONFIG_DN),
+                isA(ConfigChangeListener.class));
+    }
+
+    @Test
+    public void testDeregisterAddListenerWithInstantiableRelationImmediate() throws Exception {
+        // arrange
+        ConfigurationRepository configRepository = createConfigRepositoryWithDNs(ROOT_CONFIG_DN, TEST_PARENTS_DN);
+        ServerManagementContext context = new ServerManagementContext(configRepository);
+        ServerManagedObject<RootCfg> root = context.getRootConfigurationManagedObject();
+
+        ConfigurationAddListener<TestParentCfg> parentListener = mock(ConfigurationAddListener.class);
+        ConfigAddListener registeredListener =
+                registerAddListenerForTestParent(configRepository, root, parentListener);
+        when(configRepository.getAddListeners(TEST_PARENTS_DN)).thenReturn(Arrays.asList(registeredListener));
+
+        // act
+        root.deregisterAddListener(TestCfg.getTestOneToManyParentRelationDefinition(), parentListener);
+
+        // assert
+        verify(configRepository).deregisterAddListener(eq(TEST_PARENTS_DN), same(registeredListener));
+    }
+
+    @Test
+    public void testDeregisterAddListenerWithInstantiableRelationDelayed() throws Exception {
+        // arrange
+        ConfigurationRepository configRepository = createConfigRepositoryWithDNs(ROOT_CONFIG_DN);
+        ServerManagementContext context = new ServerManagementContext(configRepository);
+        ServerManagedObject<RootCfg> root = context.getRootConfigurationManagedObject();
+
+        ConfigurationAddListener<TestParentCfg> parentListener = mock(ConfigurationAddListener.class);
+        ConfigAddListener registeredListener =
+                registerAddListenerForTestParentDelayed(configRepository, root, parentListener);
+        when(configRepository.getAddListeners(ROOT_CONFIG_DN)).thenReturn(Arrays.asList(registeredListener));
+
+        // act
+        root.deregisterAddListener(TestCfg.getTestOneToManyParentRelationDefinition(), parentListener);
+
+        // assert
+        verify(configRepository).deregisterAddListener(eq(ROOT_CONFIG_DN), same(registeredListener));
+    }
+
+}
\ No newline at end of file
diff --git a/opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/server/MockConstraint.java b/opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/server/MockConstraint.java
new file mode 100644
index 0000000..e04476c
--- /dev/null
+++ b/opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/server/MockConstraint.java
@@ -0,0 +1,146 @@
+/*
+ * 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 legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * 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 legal-notices/CDDLv1_0.txt.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2008 Sun Microsystems, Inc.
+ */
+package org.opends.server.admin.server;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.opendj.ldap.DN;
+import org.opends.server.admin.Constraint;
+import org.opends.server.admin.client.ClientConstraintHandler;
+import org.opends.server.config.ConfigException;
+import org.opends.server.config.ConfigurationRepository;
+import org.testng.Assert;
+
+/**
+ * A mock constraint which can be configured to refuse various types of
+ * operation.
+ */
+public final class MockConstraint extends Constraint {
+
+    /**
+     * Mock server constraint handler.
+     */
+    private class Handler extends ServerConstraintHandler {
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public boolean isDeleteAllowed(ServerManagedObject<?> managedObject,
+                Collection<LocalizableMessage> unacceptableReasons) throws ConfigException {
+            if (!isDeleteAllowed) {
+                unacceptableReasons.add(LocalizableMessage.raw("Configuration cannot be deleted."));
+            }
+
+            return isDeleteAllowed;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public boolean isUsable(ServerManagedObject<?> managedObject,
+                Collection<LocalizableMessage> unacceptableReasons) throws ConfigException {
+            if (!isUsable) {
+                unacceptableReasons.add(LocalizableMessage.raw("Configuration is not usable."));
+            }
+
+            return isUsable;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void performPostAdd(ServerManagedObject<?> managedObject) throws ConfigException {
+            // Make sure that the associated config entry exists.
+            DN targetDN = managedObject.getDN();
+            Assert.assertTrue(configRepository.hasEntry(targetDN));
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void performPostDelete(ServerManagedObject<?> managedObject) throws ConfigException {
+            // Make sure that the associated config entry does not exist.
+            DN targetDN = managedObject.getDN();
+            Assert.assertTrue(configRepository.hasEntry(targetDN));
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public void performPostModify(ServerManagedObject<?> managedObject) throws ConfigException {
+            // Make sure that the associated config entry exists.
+            DN targetDN = managedObject.getDN();
+            Assert.assertTrue(configRepository.hasEntry(targetDN));
+        }
+
+    }
+
+    // Determines if delete operations are allowed.
+    private final boolean isDeleteAllowed;
+
+    // Determines if configurations can be decoded.
+    private final boolean isUsable;
+
+    private final ConfigurationRepository configRepository;
+
+    /**
+     * Creates a new mock constraint.
+     *
+     * @param isUsable
+     *            Determines if configurations can be decoded.
+     * @param isDeleteAllowed
+     *            Determines if delete operations are allowed.
+     * @param configRepository
+     *            Configuration entries.
+     */
+    public MockConstraint(boolean isUsable, boolean isDeleteAllowed, ConfigurationRepository configRepository) {
+        this.isUsable = isUsable;
+        this.isDeleteAllowed = isDeleteAllowed;
+        this.configRepository = configRepository;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Collection<ClientConstraintHandler> getClientConstraintHandlers() {
+        return Collections.emptySet();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Collection<ServerConstraintHandler> getServerConstraintHandlers() {
+        return Collections.<ServerConstraintHandler> singleton(new Handler());
+    }
+
+}

--
Gitblit v1.10.0