From 2249aa0a04b99d513828d8d60c2a8bd7d936b336 Mon Sep 17 00:00:00 2001
From: matthew_swift <matthew_swift@localhost>
Date: Mon, 03 Sep 2007 23:45:45 +0000
Subject: [PATCH] Fix issue 1451: constraint and dependency support.

---
 opends/src/server/org/opends/server/admin/server/ConfigChangeListenerAdaptor.java                 |   84 +
 opends/tests/unit-tests-testng/src/server/org/opends/server/admin/server/MockConstraint.java      |  189 ++++
 opends/src/server/org/opends/server/admin/server/ServerManagedObject.java                         |  631 -------------
 opends/src/server/org/opends/server/admin/server/ServerConstraintHandler.java                     |  207 ++++
 opends/src/server/org/opends/server/admin/server/AbstractConfigListenerAdaptor.java               |   25 
 opends/tests/unit-tests-testng/src/server/org/opends/server/admin/server/AdminTestCaseUtils.java  |   54 +
 opends/src/server/org/opends/server/admin/server/ConfigAddListenerAdaptor.java                    |  111 +
 opends/src/server/org/opends/server/admin/server/ConfigDeleteListenerAdaptor.java                 |  116 +
 opends/src/server/org/opends/server/admin/Constraint.java                                         |   17 
 opends/tests/unit-tests-testng/src/server/org/opends/server/admin/server/ConstraintTest.java      |  519 +++++++++++
 opends/tests/unit-tests-testng/src/server/org/opends/server/admin/client/ldap/MockConstraint.java |   10 
 opends/src/server/org/opends/server/admin/server/ServerManagementContext.java                     |  727 ++++++++++++++++
 12 files changed, 1,998 insertions(+), 692 deletions(-)

diff --git a/opends/src/server/org/opends/server/admin/Constraint.java b/opends/src/server/org/opends/server/admin/Constraint.java
index 85af348..be8e4ca 100644
--- a/opends/src/server/org/opends/server/admin/Constraint.java
+++ b/opends/src/server/org/opends/server/admin/Constraint.java
@@ -31,6 +31,7 @@
 import java.util.Collection;
 
 import org.opends.server.admin.client.ClientConstraintHandler;
+import org.opends.server.admin.server.ServerConstraintHandler;
 
 
 
@@ -68,9 +69,23 @@
    * @return Returns the client-side constraint handlers which will be
    *         used to enforce this constraint in client applications.
    *         The returned collection must not be <code>null</code>
-   *         but maybe empty (indicating that the constrain can only
+   *         but maybe empty (indicating that the constraint can only
    *         be enforced on the server-side).
    */
   Collection<ClientConstraintHandler> getClientConstraintHandlers();
 
+
+
+  /**
+   * Gets the server-side constraint handlers which will be used to
+   * enforce this constraint within the server.
+   *
+   * @return Returns the server-side constraint handlers which will be
+   *         used to enforce this constraint within the server. The
+   *         returned collection must not be <code>null</code> and
+   *         must not be empty, since constraints must always be
+   *         enforced on the server.
+   */
+  Collection<ServerConstraintHandler> getServerConstraintHandlers();
+
 }
diff --git a/opends/src/server/org/opends/server/admin/server/AbstractConfigListenerAdaptor.java b/opends/src/server/org/opends/server/admin/server/AbstractConfigListenerAdaptor.java
index 3f6e405..c70daa6 100644
--- a/opends/src/server/org/opends/server/admin/server/AbstractConfigListenerAdaptor.java
+++ b/opends/src/server/org/opends/server/admin/server/AbstractConfigListenerAdaptor.java
@@ -25,17 +25,16 @@
  *      Portions Copyright 2007 Sun Microsystems, Inc.
  */
 package org.opends.server.admin.server;
-import org.opends.messages.Message;
 
 
 
 import java.util.List;
 
-import org.opends.server.admin.DecodingException;
-
+import org.opends.messages.Message;
 import org.opends.messages.MessageBuilder;
 
 
+
 /**
  * Common features of config listener adaptors.
  */
@@ -51,22 +50,6 @@
 
 
   /**
-   * Convert a decoding exception to an unacceptable reason.
-   *
-   * @param e
-   *          The decoding exception.
-   * @param unacceptableReason
-   *          The builder to which messages should be appended.
-   */
-  protected final void generateUnacceptableReason(
-      DecodingException e, MessageBuilder unacceptableReason) {
-    // FIXME: generate a property OpenDS style message.
-    unacceptableReason.append(e.getLocalizedMessage());
-  }
-
-
-
-  /**
    * Concatenate a list of messages into a single message.
    *
    * @param reasons
@@ -74,8 +57,8 @@
    * @param unacceptableReason
    *          The single message to which messages should be appended.
    */
-  protected final void generateUnacceptableReason(
-      List<Message> reasons, MessageBuilder unacceptableReason) {
+  protected final void generateUnacceptableReason(List<Message> reasons,
+      MessageBuilder unacceptableReason) {
     boolean isFirst = true;
     for (Message reason : reasons) {
       if (isFirst) {
diff --git a/opends/src/server/org/opends/server/admin/server/ConfigAddListenerAdaptor.java b/opends/src/server/org/opends/server/admin/server/ConfigAddListenerAdaptor.java
index 08c9c1d..33017e5 100644
--- a/opends/src/server/org/opends/server/admin/server/ConfigAddListenerAdaptor.java
+++ b/opends/src/server/org/opends/server/admin/server/ConfigAddListenerAdaptor.java
@@ -25,26 +25,34 @@
  *      Portions Copyright 2007 Sun Microsystems, Inc.
  */
 package org.opends.server.admin.server;
-import org.opends.messages.Message;
 
 
 
+import static org.opends.messages.AdminMessages.*;
+import static org.opends.server.loggers.debug.DebugLogger.*;
+
 import java.util.LinkedList;
 import java.util.List;
 
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
 import org.opends.server.admin.Configuration;
+import org.opends.server.admin.Constraint;
 import org.opends.server.admin.DecodingException;
 import org.opends.server.admin.InstantiableRelationDefinition;
+import org.opends.server.admin.ManagedObjectDefinition;
 import org.opends.server.admin.ManagedObjectPath;
 import org.opends.server.admin.OptionalRelationDefinition;
-import org.opends.server.admin.RelationDefinition;
 import org.opends.server.api.ConfigAddListener;
 import org.opends.server.config.ConfigEntry;
+import org.opends.server.config.ConfigException;
+import org.opends.server.loggers.debug.DebugTracer;
 import org.opends.server.types.AttributeValue;
 import org.opends.server.types.ConfigChangeResult;
 import org.opends.server.types.DN;
+import org.opends.server.types.DebugLogLevel;
 import org.opends.server.types.ResultCode;
-import org.opends.messages.MessageBuilder;
+
 
 
 /**
@@ -58,20 +66,28 @@
 final class ConfigAddListenerAdaptor<S extends Configuration> extends
     AbstractConfigListenerAdaptor implements ConfigAddListener {
 
-  // The managed object path of the parent.
-  private final ManagedObjectPath<?, ?> path;
+  /**
+   * The tracer object for the debug logger.
+   */
+  private static final DebugTracer TRACER = getTracer();
+
+  // Cached configuration object between accept/apply callbacks.
+  private S cachedConfiguration;
+
+  // Cached managed object between accept/apply callbacks.
+  private ServerManagedObject<? extends S> cachedManagedObject;
 
   // The instantiable relation.
   private final InstantiableRelationDefinition<?, S> instantiableRelation;
 
-  // The optional relation.
-  private final OptionalRelationDefinition<?, S> optionalRelation;
-
   // The underlying add listener.
   private final ConfigurationAddListener<S> listener;
 
-  // Cached configuration object between accept/apply callbacks.
-  private S cachedConfiguration;
+  // The optional relation.
+  private final OptionalRelationDefinition<?, S> optionalRelation;
+
+  // The managed object path of the parent.
+  private final ManagedObjectPath<?, ?> path;
 
 
 
@@ -94,6 +110,7 @@
     this.optionalRelation = null;
     this.listener = listener;
     this.cachedConfiguration = null;
+    this.cachedManagedObject = null;
   }
 
 
@@ -117,6 +134,7 @@
     this.instantiableRelation = null;
     this.listener = listener;
     this.cachedConfiguration = null;
+    this.cachedManagedObject = null;
   }
 
 
@@ -124,8 +142,7 @@
   /**
    * {@inheritDoc}
    */
-  public ConfigChangeResult applyConfigurationAdd(
-      ConfigEntry configEntry) {
+  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
@@ -140,7 +157,28 @@
 
     // Cached objects are guaranteed to be from previous acceptable
     // callback.
-    return listener.applyConfigurationAdd(cachedConfiguration);
+    ConfigChangeResult result = listener
+        .applyConfigurationAdd(cachedConfiguration);
+
+    // 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.performAddPostCondition(cachedManagedObject);
+          } catch (ConfigException e) {
+            if (debugEnabled()) {
+              TRACER.debugCaught(DebugLogLevel.ERROR, e);
+            }
+          }
+        }
+      }
+    }
+
+    return result;
   }
 
 
@@ -154,11 +192,9 @@
     AttributeValue av = dn.getRDN().getAttributeValue(0);
     String name = av.getStringValue().trim();
 
-    ManagedObjectPath<?, ?> childPath;
-    RelationDefinition<?, S> r;
+    ManagedObjectPath<?, S> childPath;
     if (instantiableRelation != null) {
       childPath = path.child(instantiableRelation, name);
-      r = instantiableRelation;
     } else {
       // Optional managed objects are located directly beneath the
       // parent and have a well-defined name. We need to make sure
@@ -169,21 +205,46 @@
         // Doesn't apply to us.
         return true;
       }
-
-      r = optionalRelation;
     }
 
-    ServerManagedObject<? extends S> mo;
     try {
-      mo = ServerManagedObject.decode(childPath, r
-          .getChildDefinition(), configEntry, configEntry);
+      ServerManagementContext context = ServerManagementContext.getInstance();
+      cachedManagedObject = context.decode(childPath, configEntry, configEntry);
     } catch (DecodingException e) {
-      generateUnacceptableReason(e, unacceptableReason);
+      unacceptableReason.append(e.getMessageObject());
       return false;
     }
 
-    cachedConfiguration = mo.getConfiguration();
+    cachedConfiguration = cachedManagedObject.getConfiguration();
     List<Message> reasons = new LinkedList<Message>();
+
+    // Enforce any constraints.
+    boolean isAcceptable = true;
+    ManagedObjectDefinition<?, ?> d = cachedManagedObject
+        .getManagedObjectDefinition();
+    for (Constraint constraint : d.getAllConstraints()) {
+      for (ServerConstraintHandler handler : constraint
+          .getServerConstraintHandlers()) {
+        try {
+          if (!handler.isAddAcceptable(cachedManagedObject, reasons)) {
+            isAcceptable = false;
+          }
+        } catch (ConfigException e) {
+          Message message = ERR_SERVER_CONSTRAINT_EXCEPTION.get(e
+              .getMessageObject());
+          reasons.add(message);
+          isAcceptable = false;
+        }
+      }
+    }
+
+    // Give up immediately if a constraint violation occurs.
+    if (!isAcceptable) {
+      generateUnacceptableReason(reasons, unacceptableReason);
+      return false;
+    }
+
+    // Let the add listener decide.
     if (listener.isConfigurationAddAcceptable(cachedConfiguration, reasons)) {
       return true;
     } else {
@@ -195,9 +256,9 @@
 
 
   /**
-   * Get the configuiration add listener associated with this adaptor.
+   * Get the configuration add listener associated with this adaptor.
    *
-   * @return Returns the configuiration add listener associated with
+   * @return Returns the configuration add listener associated with
    *         this adaptor.
    */
   ConfigurationAddListener<S> getConfigurationAddListener() {
diff --git a/opends/src/server/org/opends/server/admin/server/ConfigChangeListenerAdaptor.java b/opends/src/server/org/opends/server/admin/server/ConfigChangeListenerAdaptor.java
index f1aa32c..0cc4e9f 100644
--- a/opends/src/server/org/opends/server/admin/server/ConfigChangeListenerAdaptor.java
+++ b/opends/src/server/org/opends/server/admin/server/ConfigChangeListenerAdaptor.java
@@ -25,11 +25,14 @@
  *      Portions Copyright 2007 Sun Microsystems, Inc.
  */
 package org.opends.server.admin.server;
+
+
+
 import org.opends.messages.Message;
 
-
-
+import static org.opends.messages.AdminMessages.*;
 import static org.opends.server.loggers.debug.DebugLogger.*;
+
 import java.util.Collection;
 import java.util.HashSet;
 import java.util.LinkedList;
@@ -40,10 +43,12 @@
 import org.opends.server.admin.AbstractManagedObjectDefinition;
 import org.opends.server.admin.AliasDefaultBehaviorProvider;
 import org.opends.server.admin.Configuration;
+import org.opends.server.admin.Constraint;
 import org.opends.server.admin.DecodingException;
 import org.opends.server.admin.DefaultBehaviorProvider;
 import org.opends.server.admin.DefaultBehaviorProviderVisitor;
 import org.opends.server.admin.DefinedDefaultBehaviorProvider;
+import org.opends.server.admin.ManagedObjectDefinition;
 import org.opends.server.admin.ManagedObjectPath;
 import org.opends.server.admin.PropertyDefinition;
 import org.opends.server.admin.RelativeInheritedDefaultBehaviorProvider;
@@ -60,6 +65,7 @@
 import org.opends.server.types.ConfigChangeResult;
 import org.opends.server.types.DN;
 import org.opends.server.types.DebugLogLevel;
+import org.opends.server.types.ResultCode;
 import org.opends.server.util.StaticUtils;
 
 
@@ -199,9 +205,6 @@
   // Cached managed object between accept/apply call-backs.
   private ServerManagedObject<? extends S> cachedManagedObject;
 
-  // The managed object definition.
-  private final AbstractManagedObjectDefinition<?, S> d;
-
   // The names of entries that this change listener depends on.
   private final Set<DN> dependencies;
 
@@ -216,7 +219,7 @@
   private final ConfigurationChangeListener<? super S> listener;
 
   // The managed object path.
-  private final ManagedObjectPath<?, ?> path;
+  private final ManagedObjectPath<?, S> path;
 
 
 
@@ -225,17 +228,13 @@
    *
    * @param path
    *          The managed object path.
-   * @param d
-   *          The managed object definition.
    * @param listener
    *          The underlying change listener.
    */
-  public ConfigChangeListenerAdaptor(ManagedObjectPath<?, ?> path,
-      AbstractManagedObjectDefinition<?, S> d,
+  public ConfigChangeListenerAdaptor(ManagedObjectPath<?, S> path,
       ConfigurationChangeListener<? super S> listener) {
     this.path = path;
     this.dn = DNBuilder.create(path);
-    this.d = d;
     this.listener = listener;
     this.cachedManagedObject = null;
 
@@ -245,6 +244,7 @@
     this.dependencies = new HashSet<DN>();
     this.dependencyListener = new DependencyConfigChangeListener(dn, this);
 
+    AbstractManagedObjectDefinition<?, ?> d = path.getManagedObjectDefinition();
     for (PropertyDefinition<?> pd : d.getAllPropertyDefinitions()) {
       Visitor.find(path, pd, dependencies);
     }
@@ -311,8 +311,28 @@
     // listener lists.
     cachedManagedObject.setConfigEntry(configEntry);
 
-    return listener.applyConfigurationChange(cachedManagedObject
-        .getConfiguration());
+    ConfigChangeResult result = listener
+        .applyConfigurationChange(cachedManagedObject.getConfiguration());
+
+    // 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.performModifyPostCondition(cachedManagedObject);
+          } catch (ConfigException e) {
+            if (debugEnabled()) {
+              TRACER.debugCaught(DebugLogLevel.ERROR, e);
+            }
+          }
+        }
+      }
+    }
+
+    return result;
   }
 
 
@@ -350,14 +370,42 @@
   public boolean configChangeIsAcceptable(ConfigEntry configEntry,
       MessageBuilder unacceptableReason, ConfigEntry newConfigEntry) {
     try {
-      cachedManagedObject = ServerManagedObject.decode(path, d, configEntry,
-          newConfigEntry);
+      ServerManagementContext context = ServerManagementContext.getInstance();
+      cachedManagedObject = context.decode(path, configEntry, newConfigEntry);
     } catch (DecodingException e) {
-      generateUnacceptableReason(e, unacceptableReason);
+      unacceptableReason.append(e.getMessageObject());
       return false;
     }
 
     List<Message> reasons = new LinkedList<Message>();
+
+    // Enforce any constraints.
+    boolean isAcceptable = true;
+    ManagedObjectDefinition<?, ?> d = cachedManagedObject
+        .getManagedObjectDefinition();
+    for (Constraint constraint : d.getAllConstraints()) {
+      for (ServerConstraintHandler handler : constraint
+          .getServerConstraintHandlers()) {
+        try {
+          if (!handler.isModifyAcceptable(cachedManagedObject, reasons)) {
+            isAcceptable = false;
+          }
+        } catch (ConfigException e) {
+          Message message = ERR_SERVER_CONSTRAINT_EXCEPTION.get(e
+              .getMessageObject());
+          reasons.add(message);
+          isAcceptable = false;
+        }
+      }
+    }
+
+    // Give up immediately if a constraint violation occurs.
+    if (!isAcceptable) {
+      generateUnacceptableReason(reasons, unacceptableReason);
+      return false;
+    }
+
+    // Let the change listener decide.
     if (listener.isConfigurationChangeAcceptable(cachedManagedObject
         .getConfiguration(), reasons)) {
       return true;
@@ -390,8 +438,8 @@
       if (configEntry != null) {
         return configEntry;
       } else {
-        Message message = AdminMessages.ERR_ADMIN_MANAGED_OBJECT_DOES_NOT_EXIST.
-            get(String.valueOf(dn));
+        Message message = AdminMessages.ERR_ADMIN_MANAGED_OBJECT_DOES_NOT_EXIST
+            .get(String.valueOf(dn));
         ErrorLogger.logError(message);
       }
     } catch (ConfigException e) {
diff --git a/opends/src/server/org/opends/server/admin/server/ConfigDeleteListenerAdaptor.java b/opends/src/server/org/opends/server/admin/server/ConfigDeleteListenerAdaptor.java
index 962599a..443a278 100644
--- a/opends/src/server/org/opends/server/admin/server/ConfigDeleteListenerAdaptor.java
+++ b/opends/src/server/org/opends/server/admin/server/ConfigDeleteListenerAdaptor.java
@@ -25,26 +25,34 @@
  *      Portions Copyright 2007 Sun Microsystems, Inc.
  */
 package org.opends.server.admin.server;
-import org.opends.messages.Message;
 
 
 
+import static org.opends.messages.AdminMessages.*;
+import static org.opends.server.loggers.debug.DebugLogger.*;
+
 import java.util.LinkedList;
 import java.util.List;
 
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
 import org.opends.server.admin.Configuration;
+import org.opends.server.admin.Constraint;
 import org.opends.server.admin.DecodingException;
 import org.opends.server.admin.InstantiableRelationDefinition;
+import org.opends.server.admin.ManagedObjectDefinition;
 import org.opends.server.admin.ManagedObjectPath;
 import org.opends.server.admin.OptionalRelationDefinition;
-import org.opends.server.admin.RelationDefinition;
 import org.opends.server.api.ConfigDeleteListener;
 import org.opends.server.config.ConfigEntry;
+import org.opends.server.config.ConfigException;
+import org.opends.server.loggers.debug.DebugTracer;
 import org.opends.server.types.AttributeValue;
 import org.opends.server.types.ConfigChangeResult;
 import org.opends.server.types.DN;
+import org.opends.server.types.DebugLogLevel;
 import org.opends.server.types.ResultCode;
-import org.opends.messages.MessageBuilder;
+
 
 
 /**
@@ -56,24 +64,31 @@
  *          The type of server configuration handled by the delete
  *          listener.
  */
-final class ConfigDeleteListenerAdaptor<S extends Configuration>
-    extends AbstractConfigListenerAdaptor implements
-    ConfigDeleteListener {
+final class ConfigDeleteListenerAdaptor<S extends Configuration> extends
+    AbstractConfigListenerAdaptor implements ConfigDeleteListener {
 
-  // The managed object path of the parent.
-  private final ManagedObjectPath<?, ?> path;
+  /**
+   * The tracer object for the debug logger.
+   */
+  private static final DebugTracer TRACER = getTracer();
+
+  // Cached configuration object between accept/apply callbacks.
+  private S cachedConfiguration;
+
+  // Cached managed object between accept/apply callbacks.
+  private ServerManagedObject<? extends S> cachedManagedObject;
 
   // The instantiable relation.
   private final InstantiableRelationDefinition<?, S> instantiableRelation;
 
-  // The optional relation.
-  private final OptionalRelationDefinition<?, S> optionalRelation;
-
   // The underlying delete listener.
   private final ConfigurationDeleteListener<S> listener;
 
-  // Cached configuration object between accept/apply callbacks.
-  private S cachedConfiguration;
+  // The optional relation.
+  private final OptionalRelationDefinition<?, S> optionalRelation;
+
+  // The managed object path of the parent.
+  private final ManagedObjectPath<?, ?> path;
 
 
 
@@ -96,6 +111,7 @@
     this.instantiableRelation = relation;
     this.listener = listener;
     this.cachedConfiguration = null;
+    this.cachedManagedObject = null;
   }
 
 
@@ -119,6 +135,7 @@
     this.instantiableRelation = null;
     this.listener = listener;
     this.cachedConfiguration = null;
+    this.cachedManagedObject = null;
   }
 
 
@@ -126,8 +143,7 @@
   /**
    * {@inheritDoc}
    */
-  public ConfigChangeResult applyConfigurationDelete(
-      ConfigEntry configEntry) {
+  public ConfigChangeResult applyConfigurationDelete(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
@@ -142,7 +158,28 @@
 
     // Cached objects are guaranteed to be from previous acceptable
     // callback.
-    return listener.applyConfigurationDelete(cachedConfiguration);
+    ConfigChangeResult result = listener
+        .applyConfigurationDelete(cachedConfiguration);
+
+    // 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.performDeletePostCondition(cachedManagedObject);
+          } catch (ConfigException e) {
+            if (debugEnabled()) {
+              TRACER.debugCaught(DebugLogLevel.ERROR, e);
+            }
+          }
+        }
+      }
+    }
+
+    return result;
   }
 
 
@@ -156,11 +193,9 @@
     AttributeValue av = dn.getRDN().getAttributeValue(0);
     String name = av.getStringValue().trim();
 
-    ManagedObjectPath<?, ?> childPath;
-    RelationDefinition<?, S> r;
+    ManagedObjectPath<?, S> childPath;
     if (instantiableRelation != null) {
       childPath = path.child(instantiableRelation, name);
-      r = instantiableRelation;
     } else {
       // Optional managed objects are located directly beneath the
       // parent and have a well-defined name. We need to make sure
@@ -171,21 +206,46 @@
         // Doesn't apply to us.
         return true;
       }
-
-      r = optionalRelation;
     }
 
-    ServerManagedObject<? extends S> mo;
     try {
-      mo = ServerManagedObject.decode(childPath, r
-          .getChildDefinition(), configEntry);
+      ServerManagementContext context = ServerManagementContext.getInstance();
+      cachedManagedObject = context.decode(childPath, configEntry);
     } catch (DecodingException e) {
-      generateUnacceptableReason(e, unacceptableReason);
+      unacceptableReason.append(e.getMessageObject());
       return false;
     }
 
-    cachedConfiguration = mo.getConfiguration();
+    cachedConfiguration = cachedManagedObject.getConfiguration();
     List<Message> reasons = new LinkedList<Message>();
+
+    // Enforce any constraints.
+    boolean isAcceptable = true;
+    ManagedObjectDefinition<?, ?> d = cachedManagedObject
+        .getManagedObjectDefinition();
+    for (Constraint constraint : d.getAllConstraints()) {
+      for (ServerConstraintHandler handler : constraint
+          .getServerConstraintHandlers()) {
+        try {
+          if (!handler.isDeleteAcceptable(cachedManagedObject, reasons)) {
+            isAcceptable = false;
+          }
+        } catch (ConfigException e) {
+          Message message = ERR_SERVER_CONSTRAINT_EXCEPTION.get(e
+              .getMessageObject());
+          reasons.add(message);
+          isAcceptable = false;
+        }
+      }
+    }
+
+    // Give up immediately if a constraint violation occurs.
+    if (!isAcceptable) {
+      generateUnacceptableReason(reasons, unacceptableReason);
+      return false;
+    }
+
+    // Let the delete listener decide.
     if (listener.isConfigurationDeleteAcceptable(cachedConfiguration,
         reasons)) {
       return true;
@@ -201,8 +261,8 @@
    * Get the configuration delete listener associated with this
    * adaptor.
    *
-   * @return Returns the configuration delete listener associated
-   *         with this adaptor.
+   * @return Returns the configuration delete listener associated with
+   *         this adaptor.
    */
   ConfigurationDeleteListener<S> getConfigurationDeleteListener() {
     return listener;
diff --git a/opends/src/server/org/opends/server/admin/server/ServerConstraintHandler.java b/opends/src/server/org/opends/server/admin/server/ServerConstraintHandler.java
new file mode 100644
index 0000000..652d8f8
--- /dev/null
+++ b/opends/src/server/org/opends/server/admin/server/ServerConstraintHandler.java
@@ -0,0 +1,207 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.admin.server;
+
+
+
+import java.util.Collection;
+
+import org.opends.messages.Message;
+import org.opends.server.config.ConfigException;
+
+
+
+/**
+ * An interface for performing server-side constraint validation.
+ * <p>
+ * Constraints are evaluated immediately before and after write
+ * operations are performed. Server-side constraints are evaluated in
+ * two phases: the first phase determines if the proposed add, delete,
+ * or modification is acceptable according to the constraint. If one
+ * or more constraints fails, the write write operation is refused,
+ * and the client will receive an
+ * <code>OperationRejectedException</code> exception. The second
+ * phase is invoked once the add, delete, or modification request has
+ * been allowed and any changes applied. The second phase gives the
+ * constraint handler a chance to register listener call-backs if
+ * required.
+ * <p>
+ * A server constraint handler must override at least one of the
+ * provided methods.
+ *
+ * @see org.opends.server.admin.Constraint
+ */
+public abstract class ServerConstraintHandler {
+
+  /**
+   * Creates a new server constraint handler.
+   */
+  protected ServerConstraintHandler() {
+    // No implementation required.
+  }
+
+
+
+  /**
+   * Determines whether or not the newly created managed object which
+   * is about to be added to the server configuration satisfies this
+   * constraint.
+   * <p>
+   * If the constraint is not satisfied, the implementation must
+   * return <code>false</code> and add a message describing why the
+   * constraint was not satisfied.
+   * <p>
+   * The default implementation is to return <code>true</code>.
+   *
+   * @param managedObject
+   *          The new managed object.
+   * @param unacceptableReasons
+   *          A list of messages to which error messages should be
+   *          added.
+   * @return Returns <code>true</code> if this constraint is
+   *         satisfied, or <code>false</code> if it is not.
+   * @throws ConfigException
+   *           If an configuration exception prevented this constraint
+   *           from being evaluated.
+   */
+  public boolean isAddAcceptable(ServerManagedObject<?> managedObject,
+      Collection<Message> unacceptableReasons) throws ConfigException {
+    return true;
+  }
+
+
+
+  /**
+   * Determines whether or not the existing managed object which is
+   * about to be deleted from the server configuration satisfies this
+   * constraint.
+   * <p>
+   * If the constraint is not satisfied, the implementation must
+   * return <code>false</code> and add a message describing why the
+   * constraint was not satisfied.
+   * <p>
+   * The default implementation is to return <code>true</code>.
+   *
+   * @param managedObject
+   *          The managed object which is about to be deleted.
+   * @param unacceptableReasons
+   *          A list of messages to which error messages should be
+   *          added.
+   * @return Returns <code>true</code> if this constraint is
+   *         satisfied, or <code>false</code> if it is not.
+   * @throws ConfigException
+   *           If an configuration exception prevented this constraint
+   *           from being evaluated.
+   */
+  public boolean isDeleteAcceptable(ServerManagedObject<?> managedObject,
+      Collection<Message> unacceptableReasons) throws ConfigException {
+    return true;
+  }
+
+
+
+  /**
+   * Determines whether or not the changes to an existing managed
+   * object which are about to be committed to the server
+   * configuration satisfies this constraint.
+   * <p>
+   * If the constraint is not satisfied, the implementation must
+   * return <code>false</code> and add a message describing why the
+   * constraint was not satisfied.
+   * <p>
+   * The default implementation is to return <code>true</code>.
+   *
+   * @param managedObject
+   *          The modified managed object.
+   * @param unacceptableReasons
+   *          A list of messages to which error messages should be
+   *          added.
+   * @return Returns <code>true</code> if this modify is satisfied,
+   *         or <code>false</code> if it is not.
+   * @throws ConfigException
+   *           If an configuration exception prevented this constraint
+   *           from being evaluated.
+   */
+  public boolean isModifyAcceptable(ServerManagedObject<?> managedObject,
+      Collection<Message> unacceptableReasons) throws ConfigException {
+    return true;
+  }
+
+
+
+  /**
+   * Perform any add post-condition processing.
+   * <p>
+   * The default implementation is to do nothing.
+   *
+   * @param managedObject
+   *          The managed object which was added.
+   * @throws ConfigException
+   *           If the post-condition processing fails due to a
+   *           configuration exception.
+   */
+  public void performAddPostCondition(ServerManagedObject<?> managedObject)
+      throws ConfigException {
+    // Do nothing.
+  }
+
+
+
+  /**
+   * Perform any delete post-condition processing.
+   * <p>
+   * The default implementation is to do nothing.
+   *
+   * @param managedObject
+   *          The managed object which was deleted.
+   * @throws ConfigException
+   *           If the post-condition processing fails due to a
+   *           configuration exception.
+   */
+  public void performDeletePostCondition(ServerManagedObject<?> managedObject)
+      throws ConfigException {
+    // Do nothing.
+  }
+
+
+
+  /**
+   * Perform any modify post-condition processing.
+   * <p>
+   * The default implementation is to do nothing.
+   *
+   * @param managedObject
+   *          The managed object which was modified.
+   * @throws ConfigException
+   *           If the post-condition processing fails due to a
+   *           configuration exception.
+   */
+  public void performModifyPostCondition(ServerManagedObject<?> managedObject)
+      throws ConfigException {
+    // Do nothing.
+  }
+}
diff --git a/opends/src/server/org/opends/server/admin/server/ServerManagedObject.java b/opends/src/server/org/opends/server/admin/server/ServerManagedObject.java
index c453cbe..0c527d8 100644
--- a/opends/src/server/org/opends/server/admin/server/ServerManagedObject.java
+++ b/opends/src/server/org/opends/server/admin/server/ServerManagedObject.java
@@ -26,55 +26,28 @@
  */
 
 package org.opends.server.admin.server;
-import org.opends.messages.Message;
 
 
 
 import static org.opends.server.loggers.debug.DebugLogger.*;
 import static org.opends.server.util.StaticUtils.*;
 
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.SortedSet;
 import java.util.TreeSet;
 
-import org.opends.server.admin.AbsoluteInheritedDefaultBehaviorProvider;
-import org.opends.server.admin.AbstractManagedObjectDefinition;
-import org.opends.server.admin.AliasDefaultBehaviorProvider;
+import org.opends.messages.AdminMessages;
+import org.opends.messages.Message;
 import org.opends.server.admin.Configuration;
-import org.opends.server.admin.DefaultBehaviorException;
-import org.opends.server.admin.DefaultBehaviorProviderVisitor;
-import org.opends.server.admin.DefinedDefaultBehaviorProvider;
-import org.opends.server.admin.DefinitionDecodingException;
-import org.opends.server.admin.DefinitionResolver;
-import org.opends.server.admin.IllegalPropertyValueException;
-import org.opends.server.admin.IllegalPropertyValueStringException;
 import org.opends.server.admin.InstantiableRelationDefinition;
-import org.opends.server.admin.LDAPProfile;
 import org.opends.server.admin.ManagedObjectDefinition;
 import org.opends.server.admin.ManagedObjectPath;
 import org.opends.server.admin.OptionalRelationDefinition;
 import org.opends.server.admin.PropertyDefinition;
-import org.opends.server.admin.PropertyException;
-import org.opends.server.admin.PropertyIsMandatoryException;
-import org.opends.server.admin.PropertyIsSingleValuedException;
-import org.opends.server.admin.PropertyNotFoundException;
-import org.opends.server.admin.PropertyOption;
 import org.opends.server.admin.PropertyProvider;
 import org.opends.server.admin.RelationDefinition;
-import org.opends.server.admin.RelativeInheritedDefaultBehaviorProvider;
 import org.opends.server.admin.SingletonRelationDefinition;
-import org.opends.server.admin.UndefinedDefaultBehaviorProvider;
-import org.opends.server.admin.DefinitionDecodingException.Reason;
-import org.opends.server.admin.std.meta.RootCfgDefn;
-import org.opends.server.admin.std.server.RootCfg;
-import org.opends.server.api.AttributeValueDecoder;
 import org.opends.server.api.ConfigAddListener;
 import org.opends.server.api.ConfigChangeListener;
 import org.opends.server.api.ConfigDeleteListener;
@@ -82,12 +55,8 @@
 import org.opends.server.config.ConfigException;
 import org.opends.server.core.DirectoryServer;
 import org.opends.server.loggers.debug.DebugTracer;
-import org.opends.messages.AdminMessages;
-import org.opends.server.types.AttributeType;
-import org.opends.server.types.AttributeValue;
 import org.opends.server.types.DN;
 import org.opends.server.types.DebugLogLevel;
-import org.opends.server.types.DirectoryException;
 
 
 
@@ -102,518 +71,44 @@
     PropertyProvider {
 
   /**
-   * A default behavior visitor used for retrieving the default values
-   * of a property.
-   *
-   * @param <T>
-   *          The type of the property.
-   */
-  private static class DefaultValueFinder<T> implements
-      DefaultBehaviorProviderVisitor<T, Collection<T>, Void> {
-
-    /**
-     * Get the default values for the specified property.
-     *
-     * @param <T>
-     *          The type of the property.
-     * @param p
-     *          The managed object path of the current managed object.
-     * @param pd
-     *          The property definition.
-     * @param newConfigEntry
-     *          Optional new configuration entry which does not yet
-     *          exist in the configuration back-end.
-     * @return Returns the default values for the specified property.
-     * @throws DefaultBehaviorException
-     *           If the default values could not be retrieved or
-     *           decoded properly.
-     */
-    public static <T> Collection<T> getDefaultValues(ManagedObjectPath<?, ?> p,
-        PropertyDefinition<T> pd, ConfigEntry newConfigEntry)
-        throws DefaultBehaviorException {
-      DefaultValueFinder<T> v = new DefaultValueFinder<T>(newConfigEntry);
-      return v.find(p, pd);
-    }
-
-    // Any exception that occurred whilst retrieving inherited default
-    // values.
-    private DefaultBehaviorException exception = null;
-
-    // The path of the managed object containing the next property.
-    private ManagedObjectPath<?, ?> nextPath = null;
-
-    // The next property whose default values were required.
-    private PropertyDefinition<T> nextProperty = null;
-
-    // Optional new configuration entry which does not yet exist in
-    // the configuration back-end.
-    private ConfigEntry newConfigEntry;
-
-
-
-    // Private constructor.
-    private DefaultValueFinder(ConfigEntry newConfigEntry) {
-      this.newConfigEntry = newConfigEntry;
-    }
-
-
-
-    /**
-     * {@inheritDoc}
-     */
-    public Collection<T> visitAbsoluteInherited(
-        AbsoluteInheritedDefaultBehaviorProvider<T> d, Void p) {
-      try {
-        return getInheritedProperty(d.getManagedObjectPath(), d
-            .getManagedObjectDefinition(), d.getPropertyName());
-      } catch (DefaultBehaviorException e) {
-        exception = e;
-        return Collections.emptySet();
-      }
-    }
-
-
-
-    /**
-     * {@inheritDoc}
-     */
-    public Collection<T> visitAlias(AliasDefaultBehaviorProvider<T> d, Void p) {
-      return Collections.emptySet();
-    }
-
-
-
-    /**
-     * {@inheritDoc}
-     */
-    public Collection<T> visitDefined(DefinedDefaultBehaviorProvider<T> d,
-        Void p) {
-      Collection<String> stringValues = d.getDefaultValues();
-      List<T> values = new ArrayList<T>(stringValues.size());
-
-      for (String stringValue : stringValues) {
-        try {
-          values.add(nextProperty.decodeValue(stringValue));
-        } catch (IllegalPropertyValueStringException e) {
-          exception = new DefaultBehaviorException(nextProperty, e);
-          break;
-        }
-      }
-
-      return values;
-    }
-
-
-
-    /**
-     * {@inheritDoc}
-     */
-    public Collection<T> visitRelativeInherited(
-        RelativeInheritedDefaultBehaviorProvider<T> d, Void p) {
-      try {
-        return getInheritedProperty(d.getManagedObjectPath(nextPath), d
-            .getManagedObjectDefinition(), d.getPropertyName());
-      } catch (DefaultBehaviorException e) {
-        exception = e;
-        return Collections.emptySet();
-      }
-    }
-
-
-
-    /**
-     * {@inheritDoc}
-     */
-    public Collection<T> visitUndefined(UndefinedDefaultBehaviorProvider<T> d,
-        Void p) {
-      return Collections.emptySet();
-    }
-
-
-
-    // Find the default values for the next path/property.
-    private Collection<T> find(ManagedObjectPath<?, ?> p,
-        PropertyDefinition<T> pd) throws DefaultBehaviorException {
-      nextPath = p;
-      nextProperty = pd;
-
-      Collection<T> values = nextProperty.getDefaultBehaviorProvider().accept(
-          this, null);
-
-      if (exception != null) {
-        throw exception;
-      }
-
-      if (values.size() > 1 && !pd.hasOption(PropertyOption.MULTI_VALUED)) {
-        throw new DefaultBehaviorException(pd,
-            new PropertyIsSingleValuedException(pd));
-      }
-
-      return values;
-    }
-
-
-
-    // Get an inherited property value.
-    @SuppressWarnings("unchecked")
-    private Collection<T> getInheritedProperty(ManagedObjectPath target,
-        AbstractManagedObjectDefinition<?, ?> d, String propertyName)
-        throws DefaultBehaviorException {
-      // First check that the requested type of managed object
-      // corresponds to the path.
-      AbstractManagedObjectDefinition<?, ?> supr = target
-          .getManagedObjectDefinition();
-      if (!supr.isParentOf(d)) {
-        throw new DefaultBehaviorException(
-            nextProperty, new DefinitionDecodingException(supr,
-                Reason.WRONG_TYPE_INFORMATION));
-      }
-
-      // Save the current property in case of recursion.
-      PropertyDefinition<T> pd1 = nextProperty;
-
-      try {
-        // Get the actual managed object definition.
-        DN dn = DNBuilder.create(target);
-        ConfigEntry configEntry;
-        if (newConfigEntry != null && newConfigEntry.getDN().equals(dn)) {
-          configEntry = newConfigEntry;
-        } else {
-          configEntry = getManagedObjectConfigEntry(dn);
-        }
-
-        DefinitionResolver resolver = new MyDefinitionResolver(configEntry);
-        ManagedObjectDefinition<?, ?> mod = d
-            .resolveManagedObjectDefinition(resolver);
-
-        PropertyDefinition<T> pd2;
-        try {
-          PropertyDefinition<?> pdTmp = mod.getPropertyDefinition(propertyName);
-          pd2 = pd1.getClass().cast(pdTmp);
-        } catch (IllegalArgumentException e) {
-          throw new PropertyNotFoundException(propertyName);
-        } catch (ClassCastException e) {
-          // FIXME: would be nice to throw a better exception here.
-          throw new PropertyNotFoundException(propertyName);
-        }
-
-        List<String> stringValues = getAttribute(mod, pd2, configEntry);
-        if (stringValues.isEmpty()) {
-          // Recursively retrieve this property's default values.
-          Collection<T> tmp = find(target, pd2);
-          Collection<T> values = new ArrayList<T>(tmp.size());
-          for (T value : tmp) {
-            pd1.validateValue(value);
-            values.add(value);
-          }
-          return values;
-        } else {
-          Collection<T> values = new ArrayList<T>(stringValues.size());
-          for (String s : stringValues) {
-            values.add(pd1.decodeValue(s));
-          }
-          return values;
-        }
-      } catch (DefinitionDecodingException e) {
-        throw new DefaultBehaviorException(pd1, e);
-      } catch (PropertyNotFoundException e) {
-        throw new DefaultBehaviorException(pd1, e);
-      } catch (IllegalPropertyValueException e) {
-        throw new DefaultBehaviorException(pd1, e);
-      } catch (IllegalPropertyValueStringException e) {
-        throw new DefaultBehaviorException(pd1, e);
-      } catch (ConfigException e) {
-        throw new DefaultBehaviorException(pd1, e);
-      }
-    }
-  }
-
-
-
-  /**
-   * A definition resolver that determines the managed object
-   * definition from the object classes of a ConfigEntry.
-   */
-  private static class MyDefinitionResolver implements DefinitionResolver {
-
-    // The config entry.
-    private final ConfigEntry entry;
-
-
-
-    // Private constructor.
-    private MyDefinitionResolver(ConfigEntry entry) {
-      this.entry = entry;
-    }
-
-
-
-    /**
-     * {@inheritDoc}
-     */
-    public boolean matches(AbstractManagedObjectDefinition<?, ?> d) {
-      String oc = LDAPProfile.getInstance().getObjectClass(d);
-      return entry.hasObjectClass(oc);
-    }
-  };
-
-  /**
-   * The root server managed object.
-   */
-  private static final ServerManagedObject<RootCfg> ROOT =
-    new ServerManagedObject<RootCfg>(
-      ManagedObjectPath.emptyPath(), RootCfgDefn.getInstance(), Collections
-          .<PropertyDefinition<?>, SortedSet<?>> emptyMap(), null);
-
-  /**
    * The tracer object for the debug logger.
    */
   private static final DebugTracer TRACER = getTracer();
 
-
-
-  /**
-   * Decodes a configuration entry into the required type of server
-   * managed object.
-   *
-   * @param <S>
-   *          The type of server configuration represented by the
-   *          decoded server managed object.
-   * @param path
-   *          The location of the server managed object.
-   * @param definition
-   *          The required managed object type.
-   * @param configEntry
-   *          The configuration entry that should be decoded.
-   * @return Returns the new server-side managed object from the
-   *         provided definition and configuration entry.
-   * @throws DefinitionDecodingException
-   *           If the managed object's type could not be determined.
-   * @throws ServerManagedObjectDecodingException
-   *           If one or more of the managed object's properties could
-   *           not be decoded.
-   */
-  static <S extends Configuration> ServerManagedObject<? extends S> decode(
-      ManagedObjectPath<?, ?> path,
-      AbstractManagedObjectDefinition<?, S> definition,
-      ConfigEntry configEntry) throws DefinitionDecodingException,
-      ServerManagedObjectDecodingException {
-    return decode(path, definition, configEntry, null);
-  }
-
-
-
-  /**
-   * Decodes a configuration entry into the required type of server
-   * managed object.
-   *
-   * @param <S>
-   *          The type of server configuration represented by the
-   *          decoded server managed object.
-   * @param path
-   *          The location of the server managed object.
-   * @param definition
-   *          The required managed object type.
-   * @param configEntry
-   *          The configuration entry that should be decoded.
-   * @param newConfigEntry
-   *          Optional new configuration that does not exist yet in
-   *          the configuration back-end. This will be used for
-   *          resolving inherited default values.
-   * @return Returns the new server-side managed object from the
-   *         provided definition and configuration entry.
-   * @throws DefinitionDecodingException
-   *           If the managed object's type could not be determined.
-   * @throws ServerManagedObjectDecodingException
-   *           If one or more of the managed object's properties could
-   *           not be decoded.
-   */
-  static <S extends Configuration> ServerManagedObject<? extends S> decode(
-      ManagedObjectPath<?, ?> path,
-      AbstractManagedObjectDefinition<?, S> definition,
-      ConfigEntry configEntry, ConfigEntry 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
-    // sub-definitions.
-    DefinitionResolver resolver = new MyDefinitionResolver(configEntry);
-    ManagedObjectDefinition<?, ? extends S> mod = definition
-        .resolveManagedObjectDefinition(resolver);
-
-    // Build the managed object's properties.
-    List<PropertyException> exceptions = new LinkedList<PropertyException>();
-    Map<PropertyDefinition<?>, SortedSet<?>> properties =
-      new HashMap<PropertyDefinition<?>, SortedSet<?>>();
-    for (PropertyDefinition<?> pd : mod.getAllPropertyDefinitions()) {
-      List<String> values = getAttribute(mod, pd, configEntry);
-      try {
-        decodeProperty(properties, path, pd, values, newConfigEntry);
-      } catch (PropertyException e) {
-        exceptions.add(e);
-      }
-    }
-
-    // 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);
-    if (exceptions.isEmpty()) {
-      return mo;
-    } else {
-      throw new ServerManagedObjectDecodingException(mo, exceptions);
-    }
-  }
-
-
-
-  /**
-   * Gets the root server managed object.
-   *
-   * @return Returns the root server managed object.
-   */
-  static ServerManagedObject<RootCfg> getRootManagedObject() {
-    return ROOT;
-  }
-
-
-
-  // Decode helper method required to avoid generics warning.
-  private static <S extends Configuration> ServerManagedObject<S> decodeAux(
-      ManagedObjectPath<?, ?> path, ManagedObjectDefinition<?, S> d,
-      Map<PropertyDefinition<?>, SortedSet<?>> properties,
-      ConfigEntry configEntry) {
-    return new ServerManagedObject<S>(path, d, properties, configEntry);
-  }
-
-
-
-  // Create a property using the provided string values.
-  private static <T> void decodeProperty(
-      Map<PropertyDefinition<?>, SortedSet<?>> properties,
-      ManagedObjectPath<?, ?> path, PropertyDefinition<T> pd,
-      List<String> stringValues, ConfigEntry newConfigEntry)
-      throws PropertyException {
-    PropertyException exception = null;
-    SortedSet<T> values = new TreeSet<T>(pd);
-
-    if (!stringValues.isEmpty()) {
-      // The property has values defined for it.
-      for (String value : stringValues) {
-        try {
-          values.add(pd.decodeValue(value));
-        } catch (IllegalPropertyValueStringException e) {
-          exception = e;
-        }
-      }
-    } else {
-      // No values defined so get the defaults.
-      try {
-        values.addAll(DefaultValueFinder.getDefaultValues(path, pd,
-            newConfigEntry));
-      } catch (DefaultBehaviorException e) {
-        exception = e;
-      }
-    }
-
-    if (values.size() > 1 && !pd.hasOption(PropertyOption.MULTI_VALUED)) {
-      // This exception takes precedence over previous exceptions.
-      exception = new PropertyIsSingleValuedException(pd);
-      T value = values.first();
-      values.clear();
-      values.add(value);
-    }
-
-    if (values.isEmpty() && pd.hasOption(PropertyOption.MANDATORY)) {
-      // The values maybe empty because of a previous exception.
-      if (exception == null) {
-        exception = new PropertyIsMandatoryException(pd);
-      }
-    }
-
-    // TODO: If an exception occurs should we leave the property
-    // empty?
-    properties.put(pd, values);
-    if (exception != null) {
-      throw exception;
-    }
-  }
-
-
-
-  // Gets the attribute associated with a property from a ConfigEntry.
-  private static List<String> getAttribute(ManagedObjectDefinition<?, ?> d,
-      PropertyDefinition<?> pd, ConfigEntry 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);
-    AttributeValueDecoder<String> decoder =
-      new AttributeValueDecoder<String>() {
-
-      public String decode(AttributeValue value) throws DirectoryException {
-        return value.getStringValue();
-      }
-    };
-
-    List<String> values = new LinkedList<String>();
-    try {
-      configEntry.getEntry().getAttributeValues(type, decoder, values);
-    } catch (DirectoryException e) {
-      // Should not happen.
-      throw new RuntimeException(e);
-    }
-    return values;
-  }
-
-
-
-  // Gets a config entry required for a managed object and throws a
-  // config exception on failure.
-  private static ConfigEntry getManagedObjectConfigEntry(DN dn)
-      throws ConfigException {
-    ConfigEntry configEntry;
-    try {
-      configEntry = DirectoryServer.getConfigEntry(dn);
-    } catch (ConfigException e) {
-      if (debugEnabled()) {
-        TRACER.debugCaught(DebugLogLevel.ERROR, e);
-      }
-
-      Message message = AdminMessages.ERR_ADMIN_CANNOT_GET_MANAGED_OBJECT.get(
-          String.valueOf(dn), stackTraceToSingleLineString(e));
-      throw new ConfigException(message, e);
-    }
-
-    // The configuration handler is free to return null indicating
-    // that the entry does not exist.
-    if (configEntry == null) {
-      Message message = AdminMessages.ERR_ADMIN_MANAGED_OBJECT_DOES_NOT_EXIST.
-          get(String.valueOf(dn));
-      throw new ConfigException(message);
-    }
-
-    return configEntry;
-  }
-
   // The configuration entry associated with this server managed
   // object (null if root).
   private ConfigEntry configEntry;
 
+  // The management context.
+  private final ServerManagementContext context = ServerManagementContext
+      .getInstance();
+
   // The managed object's definition.
   private final ManagedObjectDefinition<?, S> definition;
 
   // The managed object path identifying this managed object's
   // location.
-  private final ManagedObjectPath<?, ?> path;
+  private final ManagedObjectPath<?, S> path;
 
   // The managed object's properties.
   private final Map<PropertyDefinition<?>, SortedSet<?>> properties;
 
 
 
-  // Create an new server side managed object.
-  private ServerManagedObject(ManagedObjectPath<?, ?> path,
+  /**
+   * Creates an new server side managed object.
+   *
+   * @param path
+   *          The managed object path.
+   * @param d
+   *          The managed object definition.
+   * @param properties
+   *          The managed object's properties.
+   * @param configEntry
+   *          The configuration entry associated with the managed
+   *          object.
+   */
+  ServerManagedObject(ManagedObjectPath<?, S> path,
       ManagedObjectDefinition<?, S> d,
       Map<PropertyDefinition<?>, SortedSet<?>> properties,
       ConfigEntry configEntry) {
@@ -764,8 +259,7 @@
       InstantiableRelationDefinition<?, M> d, String name)
       throws IllegalArgumentException, ConfigException {
     validateRelationDefinition(d);
-    ManagedObjectPath<?, ?> childPath = path.child(d, name);
-    return getChild(childPath, d);
+    return context.getManagedObject(path.child(d, name));
   }
 
 
@@ -790,8 +284,7 @@
       OptionalRelationDefinition<?, M> d) throws IllegalArgumentException,
       ConfigException {
     validateRelationDefinition(d);
-    ManagedObjectPath<?, ?> childPath = path.child(d);
-    return getChild(childPath, d);
+    return context.getManagedObject(path.child(d));
   }
 
 
@@ -816,8 +309,7 @@
       SingletonRelationDefinition<?, M> d) throws IllegalArgumentException,
       ConfigException {
     validateRelationDefinition(d);
-    ManagedObjectPath<?, ?> childPath = path.child(d);
-    return getChild(childPath, d);
+    return context.getManagedObject(path.child(d));
   }
 
 
@@ -869,7 +361,7 @@
    *
    * @return Returns the path of this server managed object.
    */
-  public ManagedObjectPath<?, ?> getManagedObjectPath() {
+  public ManagedObjectPath<?, S> getManagedObjectPath() {
     return path;
   }
 
@@ -949,15 +441,7 @@
   public boolean hasChild(OptionalRelationDefinition<?, ?> d)
       throws IllegalArgumentException {
     validateRelationDefinition(d);
-
-    // Get the configuration entry.
-    DN targetDN = DNBuilder.create(path, d);
-    try {
-      return (getManagedObjectConfigEntry(targetDN) != null);
-    } catch (ConfigException e) {
-      // Assume it doesn't exist.
-      return false;
-    }
+    return context.managedObjectExists(path.child(d));
   }
 
 
@@ -976,30 +460,7 @@
   public String[] listChildren(InstantiableRelationDefinition<?, ?> d)
       throws IllegalArgumentException {
     validateRelationDefinition(d);
-
-    // Get the target entry.
-    DN targetDN = DNBuilder.create(path, d);
-    ConfigEntry configEntry;
-    try {
-      configEntry = DirectoryServer.getConfigEntry(targetDN);
-    } catch (ConfigException e) {
-      return new String[0];
-    }
-
-    if (configEntry == null) {
-      return new String[0];
-    }
-
-    // Retrieve the children.
-    Set<DN> children = configEntry.getChildren().keySet();
-    ArrayList<String> names = new ArrayList<String>(children.size());
-    for (DN child : children) {
-      // Assume that RDNs are single-valued and can be trimmed.
-      AttributeValue av = child.getRDN().getAttributeValue(0);
-      names.add(av.getStringValue().trim());
-    }
-
-    return names.toArray(new String[names.size()]);
+    return context.listManagedObjects(path, d);
   }
 
 
@@ -1048,8 +509,8 @@
    *           If the optional relation definition is not associated
    *           with this managed object's definition.
    * @throws ConfigException
-   *           If the configuration entry associated with the
-   *           optional relation could not be retrieved.
+   *           If the configuration entry associated with the optional
+   *           relation could not be retrieved.
    */
   public <M extends Configuration> void registerAddListener(
       OptionalRelationDefinition<?, M> d, ConfigurationAddListener<M> listener)
@@ -1073,7 +534,7 @@
   public void registerChangeListener(
       ConfigurationChangeListener<? super S> listener) {
     ConfigChangeListener adaptor = new ConfigChangeListenerAdaptor<S>(path,
-        definition, listener);
+        listener);
     configEntry.registerChangeListener(adaptor);
   }
 
@@ -1123,8 +584,8 @@
    *           If the optional relation definition is not associated
    *           with this managed object's definition.
    * @throws ConfigException
-   *           If the configuration entry associated with the
-   *           optional relation could not be retrieved.
+   *           If the configuration entry associated with the optional
+   *           relation could not be retrieved.
    */
   public <M extends Configuration> void registerDeleteListener(
       OptionalRelationDefinition<?, M> d,
@@ -1206,26 +667,6 @@
 
 
 
-  // Get a child managed object.
-  private <M extends Configuration> ServerManagedObject<? extends M> getChild(
-      ManagedObjectPath<?, ?> childPath, RelationDefinition<?, M> d)
-      throws ConfigException {
-    // Get the configuration entry.
-    DN targetDN = DNBuilder.create(childPath);
-    ConfigEntry configEntry = getManagedObjectConfigEntry(targetDN);
-    try {
-      return decode(childPath, d.getChildDefinition(), configEntry);
-    } catch (DefinitionDecodingException e) {
-      throw ConfigExceptionFactory.getInstance()
-          .createDecodingExceptionAdaptor(targetDN, e);
-    } catch (ServerManagedObjectDecodingException e) {
-      throw ConfigExceptionFactory.getInstance()
-          .createDecodingExceptionAdaptor(e);
-    }
-  }
-
-
-
   // 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 {
@@ -1284,8 +725,8 @@
     }
 
     // No parent entry could be found.
-    Message message = AdminMessages.ERR_ADMIN_UNABLE_TO_REGISTER_LISTENER.get(
-        String.valueOf(baseDN));
+    Message message = AdminMessages.ERR_ADMIN_UNABLE_TO_REGISTER_LISTENER
+        .get(String.valueOf(baseDN));
     throw new ConfigException(message);
   }
 
@@ -1313,8 +754,8 @@
   // object.
   private void validateRelationDefinition(RelationDefinition<?, ?> rd)
       throws IllegalArgumentException {
-    RelationDefinition<?, ?> tmp =
-      definition.getRelationDefinition(rd.getName());
+    RelationDefinition<?, ?> tmp = definition.getRelationDefinition(rd
+        .getName());
     if (tmp != rd) {
       throw new IllegalArgumentException("The relation " + rd.getName()
           + " is not associated with a " + definition.getName());
diff --git a/opends/src/server/org/opends/server/admin/server/ServerManagementContext.java b/opends/src/server/org/opends/server/admin/server/ServerManagementContext.java
index fa303aa..ea2f943 100644
--- a/opends/src/server/org/opends/server/admin/server/ServerManagementContext.java
+++ b/opends/src/server/org/opends/server/admin/server/ServerManagementContext.java
@@ -29,8 +29,60 @@
 
 
 
+import static org.opends.server.loggers.debug.DebugLogger.*;
+import static org.opends.server.util.StaticUtils.*;
 
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import org.opends.messages.AdminMessages;
+import org.opends.messages.Message;
+import org.opends.server.admin.AbsoluteInheritedDefaultBehaviorProvider;
+import org.opends.server.admin.AbstractManagedObjectDefinition;
+import org.opends.server.admin.AliasDefaultBehaviorProvider;
+import org.opends.server.admin.Configuration;
+import org.opends.server.admin.ConfigurationClient;
+import org.opends.server.admin.DefaultBehaviorException;
+import org.opends.server.admin.DefaultBehaviorProviderVisitor;
+import org.opends.server.admin.DefinedDefaultBehaviorProvider;
+import org.opends.server.admin.DefinitionDecodingException;
+import org.opends.server.admin.DefinitionResolver;
+import org.opends.server.admin.IllegalPropertyValueException;
+import org.opends.server.admin.IllegalPropertyValueStringException;
+import org.opends.server.admin.InstantiableRelationDefinition;
+import org.opends.server.admin.LDAPProfile;
+import org.opends.server.admin.ManagedObjectDefinition;
+import org.opends.server.admin.ManagedObjectPath;
+import org.opends.server.admin.PropertyDefinition;
+import org.opends.server.admin.PropertyException;
+import org.opends.server.admin.PropertyIsMandatoryException;
+import org.opends.server.admin.PropertyIsSingleValuedException;
+import org.opends.server.admin.PropertyNotFoundException;
+import org.opends.server.admin.PropertyOption;
+import org.opends.server.admin.RelationDefinition;
+import org.opends.server.admin.RelativeInheritedDefaultBehaviorProvider;
+import org.opends.server.admin.UndefinedDefaultBehaviorProvider;
+import org.opends.server.admin.DefinitionDecodingException.Reason;
+import org.opends.server.admin.std.meta.RootCfgDefn;
 import org.opends.server.admin.std.server.RootCfg;
+import org.opends.server.api.AttributeValueDecoder;
+import org.opends.server.config.ConfigEntry;
+import org.opends.server.config.ConfigException;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.loggers.debug.DebugTracer;
+import org.opends.server.types.AttributeType;
+import org.opends.server.types.AttributeValue;
+import org.opends.server.types.DN;
+import org.opends.server.types.DebugLogLevel;
+import org.opends.server.types.DirectoryException;
 
 
 
@@ -39,10 +91,255 @@
  */
 public final class ServerManagementContext {
 
+  /**
+   * A default behavior visitor used for retrieving the default values
+   * of a property.
+   *
+   * @param <T>
+   *          The type of the property.
+   */
+  private class DefaultValueFinder<T> implements
+      DefaultBehaviorProviderVisitor<T, Collection<T>, Void> {
+
+    // Any exception that occurred whilst retrieving inherited default
+    // values.
+    private DefaultBehaviorException exception = null;
+
+    // Optional new configuration entry which does not yet exist in
+    // the configuration back-end.
+    private ConfigEntry newConfigEntry;
+
+    // The path of the managed object containing the next property.
+    private ManagedObjectPath<?, ?> nextPath = null;
+
+    // The next property whose default values were required.
+    private PropertyDefinition<T> nextProperty = null;
+
+
+
+    // Private constructor.
+    private DefaultValueFinder(ConfigEntry newConfigEntry) {
+      this.newConfigEntry = newConfigEntry;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public Collection<T> visitAbsoluteInherited(
+        AbsoluteInheritedDefaultBehaviorProvider<T> d, Void p) {
+      try {
+        return getInheritedProperty(d.getManagedObjectPath(), d
+            .getManagedObjectDefinition(), d.getPropertyName());
+      } catch (DefaultBehaviorException e) {
+        exception = e;
+        return Collections.emptySet();
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public Collection<T> visitAlias(AliasDefaultBehaviorProvider<T> d, Void p) {
+      return Collections.emptySet();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public Collection<T> visitDefined(DefinedDefaultBehaviorProvider<T> d,
+        Void p) {
+      Collection<String> stringValues = d.getDefaultValues();
+      List<T> values = new ArrayList<T>(stringValues.size());
+
+      for (String stringValue : stringValues) {
+        try {
+          values.add(nextProperty.decodeValue(stringValue));
+        } catch (IllegalPropertyValueStringException e) {
+          exception = new DefaultBehaviorException(nextProperty, e);
+          break;
+        }
+      }
+
+      return values;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public Collection<T> visitRelativeInherited(
+        RelativeInheritedDefaultBehaviorProvider<T> d, Void p) {
+      try {
+        return getInheritedProperty(d.getManagedObjectPath(nextPath), d
+            .getManagedObjectDefinition(), d.getPropertyName());
+      } catch (DefaultBehaviorException e) {
+        exception = e;
+        return Collections.emptySet();
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public Collection<T> visitUndefined(UndefinedDefaultBehaviorProvider<T> d,
+        Void p) {
+      return Collections.emptySet();
+    }
+
+
+
+    // Find the default values for the next path/property.
+    private Collection<T> find(ManagedObjectPath<?, ?> p,
+        PropertyDefinition<T> pd) throws DefaultBehaviorException {
+      nextPath = p;
+      nextProperty = pd;
+
+      Collection<T> values = nextProperty.getDefaultBehaviorProvider().accept(
+          this, null);
+
+      if (exception != null) {
+        throw exception;
+      }
+
+      if (values.size() > 1 && !pd.hasOption(PropertyOption.MULTI_VALUED)) {
+        throw new DefaultBehaviorException(pd,
+            new PropertyIsSingleValuedException(pd));
+      }
+
+      return values;
+    }
+
+
+
+    // Get an inherited property value.
+    @SuppressWarnings("unchecked")
+    private Collection<T> getInheritedProperty(ManagedObjectPath target,
+        AbstractManagedObjectDefinition<?, ?> d, String propertyName)
+        throws DefaultBehaviorException {
+      // First check that the requested type of managed object
+      // corresponds to the path.
+      AbstractManagedObjectDefinition<?, ?> supr = target
+          .getManagedObjectDefinition();
+      if (!supr.isParentOf(d)) {
+        throw new DefaultBehaviorException(
+            nextProperty, new DefinitionDecodingException(supr,
+                Reason.WRONG_TYPE_INFORMATION));
+      }
+
+      // Save the current property in case of recursion.
+      PropertyDefinition<T> pd1 = nextProperty;
+
+      try {
+        // Get the actual managed object definition.
+        DN dn = DNBuilder.create(target);
+        ConfigEntry configEntry;
+        if (newConfigEntry != null && newConfigEntry.getDN().equals(dn)) {
+          configEntry = newConfigEntry;
+        } else {
+          configEntry = getManagedObjectConfigEntry(dn);
+        }
+
+        DefinitionResolver resolver = new MyDefinitionResolver(configEntry);
+        ManagedObjectDefinition<?, ?> mod = d
+            .resolveManagedObjectDefinition(resolver);
+
+        PropertyDefinition<T> pd2;
+        try {
+          PropertyDefinition<?> pdTmp = mod.getPropertyDefinition(propertyName);
+          pd2 = pd1.getClass().cast(pdTmp);
+        } catch (IllegalArgumentException e) {
+          throw new PropertyNotFoundException(propertyName);
+        } catch (ClassCastException e) {
+          // FIXME: would be nice to throw a better exception here.
+          throw new PropertyNotFoundException(propertyName);
+        }
+
+        List<String> stringValues = getAttribute(mod, pd2, configEntry);
+        if (stringValues.isEmpty()) {
+          // Recursively retrieve this property's default values.
+          Collection<T> tmp = find(target, pd2);
+          Collection<T> values = new ArrayList<T>(tmp.size());
+          for (T value : tmp) {
+            pd1.validateValue(value);
+            values.add(value);
+          }
+          return values;
+        } else {
+          Collection<T> values = new ArrayList<T>(stringValues.size());
+          for (String s : stringValues) {
+            values.add(pd1.decodeValue(s));
+          }
+          return values;
+        }
+      } catch (DefinitionDecodingException e) {
+        throw new DefaultBehaviorException(pd1, e);
+      } catch (PropertyNotFoundException e) {
+        throw new DefaultBehaviorException(pd1, e);
+      } catch (IllegalPropertyValueException e) {
+        throw new DefaultBehaviorException(pd1, e);
+      } catch (IllegalPropertyValueStringException e) {
+        throw new DefaultBehaviorException(pd1, e);
+      } catch (ConfigException e) {
+        throw new DefaultBehaviorException(pd1, e);
+      }
+    }
+  }
+
+
+
+  /**
+   * A definition resolver that determines the managed object
+   * definition from the object classes of a ConfigEntry.
+   */
+  private class MyDefinitionResolver implements DefinitionResolver {
+
+    // The config entry.
+    private final ConfigEntry entry;
+
+
+
+    // Private constructor.
+    private MyDefinitionResolver(ConfigEntry entry) {
+      this.entry = entry;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean matches(AbstractManagedObjectDefinition<?, ?> d) {
+      String oc = LDAPProfile.getInstance().getObjectClass(d);
+      return entry.hasObjectClass(oc);
+    }
+  };
+
   // Singleton instance.
   private final static ServerManagementContext INSTANCE =
     new ServerManagementContext();
 
+  /**
+   * The root server managed object.
+   */
+  private static final ServerManagedObject<RootCfg> ROOT =
+    new ServerManagedObject<RootCfg>(
+      ManagedObjectPath.emptyPath(), RootCfgDefn.getInstance(), Collections
+          .<PropertyDefinition<?>, SortedSet<?>> emptyMap(), null);
+
+  /**
+   * The tracer object for the debug logger.
+   */
+  private static final DebugTracer TRACER = getTracer();
+
 
 
   /**
@@ -64,6 +361,121 @@
 
 
   /**
+   * Gets the named managed object.
+   *
+   * @param <C>
+   *          The type of client managed object configuration that the
+   *          path definition refers to.
+   * @param <S>
+   *          The type of server managed object configuration that the
+   *          path definition refers to.
+   * @param path
+   *          The path of the managed object.
+   * @return Returns the named managed object.
+   * @throws ConfigException
+   *           If the named managed object could not be found or if it
+   *           could not be decoded.
+   */
+  public <C extends ConfigurationClient, S extends Configuration>
+  ServerManagedObject<? extends S> getManagedObject(
+      ManagedObjectPath<C, S> path) throws ConfigException {
+    // Get the configuration entry.
+    DN targetDN = DNBuilder.create(path);
+    ConfigEntry configEntry = getManagedObjectConfigEntry(targetDN);
+    try {
+      return decode(path, configEntry);
+    } catch (DefinitionDecodingException e) {
+      throw ConfigExceptionFactory.getInstance()
+          .createDecodingExceptionAdaptor(targetDN, e);
+    } catch (ServerManagedObjectDecodingException e) {
+      throw ConfigExceptionFactory.getInstance()
+          .createDecodingExceptionAdaptor(e);
+    }
+  }
+
+
+
+  /**
+   * Gets the effective value of a property in the named managed
+   * object.
+   *
+   * @param <PD>
+   *          The type of the property to be retrieved.
+   * @param path
+   *          The path of the managed object containing the property.
+   * @param pd
+   *          The property to be retrieved.
+   * @return Returns the property's effective value, or
+   *         <code>null</code> if there are no values defined.
+   * @throws IllegalArgumentException
+   *           If the property definition is not associated with the
+   *           referenced managed object's definition.
+   * @throws PropertyException
+   *           If the managed object was found but the requested
+   *           property could not be decoded.
+   * @throws ConfigException
+   *           If the named managed object could not be found or if it
+   *           could not be decoded.
+   */
+  public <PD> PD getPropertyValue(ManagedObjectPath<?, ?> path,
+      PropertyDefinition<PD> pd) throws IllegalArgumentException,
+      ConfigException, PropertyException {
+    SortedSet<PD> values = getPropertyValues(path, pd);
+    if (values.isEmpty()) {
+      return null;
+    } else {
+      return values.first();
+    }
+  }
+
+
+
+  /**
+   * Gets the effective values of a property in the named managed
+   * object.
+   *
+   * @param <PD>
+   *          The type of the property to be retrieved.
+   * @param path
+   *          The path of the managed object containing the property.
+   * @param pd
+   *          The property to be retrieved.
+   * @return Returns the property's effective values, or an empty set
+   *         if there are no values defined.
+   * @throws IllegalArgumentException
+   *           If the property definition is not associated with the
+   *           referenced managed object's definition.
+   * @throws PropertyException
+   *           If the managed object was found but the requested
+   *           property could not be decoded.
+   * @throws ConfigException
+   *           If the named managed object could not be found or if it
+   *           could not be decoded.
+   */
+  public <PD> SortedSet<PD> getPropertyValues(ManagedObjectPath<?, ?> path,
+      PropertyDefinition<PD> pd) throws IllegalArgumentException,
+      ConfigException, PropertyException {
+    DN dn = DNBuilder.create(path);
+    ConfigEntry configEntry = getManagedObjectConfigEntry(dn);
+
+    DefinitionResolver resolver = new MyDefinitionResolver(configEntry);
+    AbstractManagedObjectDefinition<?, ?> d = path.getManagedObjectDefinition();
+    ManagedObjectDefinition<?, ?> mod;
+
+    try {
+      mod = d.resolveManagedObjectDefinition(resolver);
+    } catch (DefinitionDecodingException e) {
+      throw ConfigExceptionFactory.getInstance()
+          .createDecodingExceptionAdaptor(dn, e);
+    }
+
+    List<String> values = getAttribute(mod, pd, configEntry);
+    return decodeProperty(path, pd, values, null);
+  }
+
+
+
+  /**
    * Get the root configuration manager associated with this
    * management context.
    *
@@ -84,6 +496,319 @@
    *         associated with this management context.
    */
   public ServerManagedObject<RootCfg> getRootConfigurationManagedObject() {
-    return ServerManagedObject.getRootManagedObject();
+    return ROOT;
+  }
+
+
+
+  /**
+   * Lists the child managed objects of the named parent managed
+   * object.
+   *
+   * @param <C>
+   *          The type of client managed object configuration that the
+   *          relation definition refers to.
+   * @param <S>
+   *          The type of server managed object configuration that the
+   *          relation definition refers to.
+   * @param parent
+   *          The path of the parent managed object.
+   * @param rd
+   *          The instantiable relation definition.
+   * @return Returns the names of the child managed objects.
+   * @throws IllegalArgumentException
+   *           If the relation definition is not associated with the
+   *           parent managed object's definition.
+   */
+  public <C extends ConfigurationClient, S extends Configuration>
+  String[] listManagedObjects(
+      ManagedObjectPath<?, ?> parent, InstantiableRelationDefinition<C, S> rd)
+      throws IllegalArgumentException {
+    validateRelationDefinition(parent, rd);
+
+    // Get the target entry.
+    DN targetDN = DNBuilder.create(parent, rd);
+    ConfigEntry configEntry;
+    try {
+      configEntry = DirectoryServer.getConfigEntry(targetDN);
+    } catch (ConfigException e) {
+      return new String[0];
+    }
+
+    if (configEntry == null) {
+      return new String[0];
+    }
+
+    // Retrieve the children.
+    Set<DN> children = configEntry.getChildren().keySet();
+    ArrayList<String> names = new ArrayList<String>(children.size());
+    for (DN child : children) {
+      // Assume that RDNs are single-valued and can be trimmed.
+      AttributeValue av = child.getRDN().getAttributeValue(0);
+      names.add(av.getStringValue().trim());
+    }
+
+    return names.toArray(new String[names.size()]);
+  }
+
+
+
+  /**
+   * Determines whether or not the named managed object exists.
+   *
+   * @param path
+   *          The path of the named managed object.
+   * @return Returns <code>true</code> if the named managed object
+   *         exists, <code>false</code> otherwise.
+   */
+  public boolean managedObjectExists(ManagedObjectPath<?, ?> path) {
+    // Get the configuration entry.
+    DN targetDN = DNBuilder.create(path);
+    try {
+      return (getManagedObjectConfigEntry(targetDN) != null);
+    } catch (ConfigException e) {
+      // Assume it doesn't exist.
+      return false;
+    }
+  }
+
+
+
+  /**
+   * Decodes a configuration entry into the required type of server
+   * managed object.
+   *
+   * @param <C>
+   *          The type of client managed object configuration that the
+   *          path definition refers to.
+   * @param <S>
+   *          The type of server managed object configuration that the
+   *          path definition refers to.
+   * @param path
+   *          The location of the server managed object.
+   * @param configEntry
+   *          The configuration entry that should be decoded.
+   * @return Returns the new server-side managed object from the
+   *         provided definition and configuration entry.
+   * @throws DefinitionDecodingException
+   *           If the managed object's type could not be determined.
+   * @throws ServerManagedObjectDecodingException
+   *           If one or more of the managed object's properties could
+   *           not be decoded.
+   */
+  <C extends ConfigurationClient, S extends Configuration>
+  ServerManagedObject<? extends S> decode(
+      ManagedObjectPath<C, S> path, ConfigEntry configEntry)
+      throws DefinitionDecodingException, ServerManagedObjectDecodingException {
+    return decode(path, configEntry, null);
+  }
+
+
+
+  /**
+   * Decodes a configuration entry into the required type of server
+   * managed object.
+   *
+   * @param <C>
+   *          The type of client managed object configuration that the
+   *          path definition refers to.
+   * @param <S>
+   *          The type of server managed object configuration that the
+   *          path definition refers to.
+   * @param path
+   *          The location of the server managed object.
+   * @param configEntry
+   *          The configuration entry that should be decoded.
+   * @param newConfigEntry
+   *          Optional new configuration that does not exist yet in
+   *          the configuration back-end. This will be used for
+   *          resolving inherited default values.
+   * @return Returns the new server-side managed object from the
+   *         provided definition and configuration entry.
+   * @throws DefinitionDecodingException
+   *           If the managed object's type could not be determined.
+   * @throws ServerManagedObjectDecodingException
+   *           If one or more of the managed object's properties could
+   *           not be decoded.
+   */
+  <C extends ConfigurationClient, S extends Configuration>
+  ServerManagedObject<? extends S> decode(
+      ManagedObjectPath<C, S> path, ConfigEntry configEntry,
+      ConfigEntry 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
+    // sub-definitions.
+    DefinitionResolver resolver = new MyDefinitionResolver(configEntry);
+    AbstractManagedObjectDefinition<C, S> d = path.getManagedObjectDefinition();
+    ManagedObjectDefinition<? extends C, ? extends S> mod = d
+        .resolveManagedObjectDefinition(resolver);
+
+    // Build the managed object's properties.
+    List<PropertyException> exceptions = new LinkedList<PropertyException>();
+    Map<PropertyDefinition<?>, SortedSet<?>> properties =
+      new HashMap<PropertyDefinition<?>, SortedSet<?>>();
+    for (PropertyDefinition<?> pd : mod.getAllPropertyDefinitions()) {
+      List<String> values = getAttribute(mod, pd, configEntry);
+      try {
+        SortedSet<?> pvalues = decodeProperty(path, pd, values, newConfigEntry);
+        properties.put(pd, pvalues);
+      } catch (PropertyException e) {
+        exceptions.add(e);
+      }
+    }
+
+    // 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);
+    if (exceptions.isEmpty()) {
+      return mo;
+    } else {
+      throw new ServerManagedObjectDecodingException(mo, 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) {
+    ManagedObjectPath<C, S> newPath = path.asSubType(d);
+    return new ServerManagedObject<S>(newPath, d, properties, configEntry);
+  }
+
+
+
+  // Create a property using the provided string values.
+  private <T> SortedSet<T> decodeProperty(ManagedObjectPath<?, ?> path,
+      PropertyDefinition<T> pd, List<String> stringValues,
+      ConfigEntry newConfigEntry) throws PropertyException {
+    PropertyException exception = null;
+    SortedSet<T> values = new TreeSet<T>(pd);
+
+    if (!stringValues.isEmpty()) {
+      // The property has values defined for it.
+      for (String value : stringValues) {
+        try {
+          values.add(pd.decodeValue(value));
+        } catch (IllegalPropertyValueStringException e) {
+          exception = e;
+        }
+      }
+    } else {
+      // No values defined so get the defaults.
+      try {
+        values.addAll(getDefaultValues(path, pd, newConfigEntry));
+      } catch (DefaultBehaviorException e) {
+        exception = e;
+      }
+    }
+
+    if (values.size() > 1 && !pd.hasOption(PropertyOption.MULTI_VALUED)) {
+      // This exception takes precedence over previous exceptions.
+      exception = new PropertyIsSingleValuedException(pd);
+      T value = values.first();
+      values.clear();
+      values.add(value);
+    }
+
+    if (values.isEmpty() && pd.hasOption(PropertyOption.MANDATORY)) {
+      // The values maybe empty because of a previous exception.
+      if (exception == null) {
+        exception = new PropertyIsMandatoryException(pd);
+      }
+    }
+
+    if (exception != null) {
+      throw exception;
+    } else {
+      return values;
+    }
+  }
+
+
+
+  // Gets the attribute associated with a property from a ConfigEntry.
+  private List<String> getAttribute(ManagedObjectDefinition<?, ?> d,
+      PropertyDefinition<?> pd, ConfigEntry 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);
+    AttributeValueDecoder<String> decoder =
+      new AttributeValueDecoder<String>() {
+
+      public String decode(AttributeValue value) throws DirectoryException {
+        return value.getStringValue();
+      }
+    };
+
+    List<String> values = new LinkedList<String>();
+    try {
+      configEntry.getEntry().getAttributeValues(type, decoder, values);
+    } catch (DirectoryException e) {
+      // Should not happen.
+      throw new RuntimeException(e);
+    }
+    return values;
+  }
+
+
+
+  // Get the default values for the specified property.
+  private <T> Collection<T> getDefaultValues(ManagedObjectPath<?, ?> p,
+      PropertyDefinition<T> pd, ConfigEntry newConfigEntry)
+      throws DefaultBehaviorException {
+    DefaultValueFinder<T> v = new DefaultValueFinder<T>(newConfigEntry);
+    return v.find(p, pd);
+  }
+
+
+
+  // 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;
+    try {
+      configEntry = DirectoryServer.getConfigEntry(dn);
+    } catch (ConfigException e) {
+      if (debugEnabled()) {
+        TRACER.debugCaught(DebugLogLevel.ERROR, e);
+      }
+
+      Message message = AdminMessages.ERR_ADMIN_CANNOT_GET_MANAGED_OBJECT.get(
+          String.valueOf(dn), stackTraceToSingleLineString(e));
+      throw new ConfigException(message, e);
+    }
+
+    // The configuration handler is free to return null indicating
+    // that the entry does not exist.
+    if (configEntry == null) {
+      Message message = AdminMessages.ERR_ADMIN_MANAGED_OBJECT_DOES_NOT_EXIST
+          .get(String.valueOf(dn));
+      throw new ConfigException(message);
+    }
+
+    return configEntry;
+  }
+
+
+
+  // Validate that a relation definition belongs to the path.
+  private void validateRelationDefinition(ManagedObjectPath<?, ?> path,
+      RelationDefinition<?, ?> rd) throws IllegalArgumentException {
+    AbstractManagedObjectDefinition<?, ?> d = path.getManagedObjectDefinition();
+    RelationDefinition<?, ?> tmp = d.getRelationDefinition(rd.getName());
+    if (tmp != rd) {
+      throw new IllegalArgumentException("The relation " + rd.getName()
+          + " is not associated with a " + d.getName());
+    }
   }
 }
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/admin/client/ldap/MockConstraint.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/admin/client/ldap/MockConstraint.java
index ff8f056..a0c6033 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/admin/client/ldap/MockConstraint.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/admin/client/ldap/MockConstraint.java
@@ -39,6 +39,7 @@
 import org.opends.server.admin.client.CommunicationException;
 import org.opends.server.admin.client.ManagedObject;
 import org.opends.server.admin.client.ManagementContext;
+import org.opends.server.admin.server.ServerConstraintHandler;
 
 
 
@@ -138,4 +139,13 @@
     return Collections.<ClientConstraintHandler> singleton(new Handler());
   }
 
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public Collection<ServerConstraintHandler> getServerConstraintHandlers() {
+    return Collections.emptySet();
+  }
+
 }
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/admin/server/AdminTestCaseUtils.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/admin/server/AdminTestCaseUtils.java
index 8526388..b65ad39 100644
--- a/opends/tests/unit-tests-testng/src/server/org/opends/server/admin/server/AdminTestCaseUtils.java
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/admin/server/AdminTestCaseUtils.java
@@ -30,8 +30,13 @@
 
 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.admin.std.meta.RootCfgDefn;
 import org.opends.server.config.ConfigEntry;
 import org.opends.server.config.ConfigException;
 import org.opends.server.types.Entry;
@@ -44,6 +49,15 @@
  */
 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.
@@ -71,9 +85,9 @@
     ConfigEntry configEntry = new ConfigEntry(entry, null);
 
     try {
-      ServerManagedObject<? extends S> mo = ServerManagedObject
-          .decode(ManagedObjectPath.emptyPath(), definition,
-              configEntry);
+      ServerManagementContext context = ServerManagementContext.getInstance();
+      ServerManagedObject<? extends S> mo = context.decode(getPath(definition),
+          configEntry);
       return mo.getConfiguration();
     } catch (DefinitionDecodingException e) {
       throw ConfigExceptionFactory.getInstance()
@@ -83,4 +97,38 @@
           .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() {
+
+        /**
+         * {@inheritDoc}
+         */
+        @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/opends/tests/unit-tests-testng/src/server/org/opends/server/admin/server/ConstraintTest.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/admin/server/ConstraintTest.java
new file mode 100644
index 0000000..a182534
--- /dev/null
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/admin/server/ConstraintTest.java
@@ -0,0 +1,519 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 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.messages.Message;
+import org.opends.server.TestCaseUtils;
+import org.opends.server.admin.AdminTestCase;
+import org.opends.server.admin.LDAPProfile;
+import org.opends.server.admin.MockLDAPProfile;
+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.client.ldap.JNDIDirContextAdaptor;
+import org.opends.server.admin.std.server.RootCfg;
+import org.opends.server.config.ConfigException;
+import org.opends.server.core.AddOperation;
+import org.opends.server.protocols.internal.InternalClientConnection;
+import org.opends.server.types.ConfigChangeResult;
+import org.opends.server.types.Entry;
+import org.opends.server.types.ResultCode;
+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-virtual-attribute",
+      "cn: test child 1",
+      "ds-cfg-virtual-attribute-enabled: true",
+      "ds-cfg-virtual-attribute-class: org.opends.server.extensions.UserDefinedVirtualAttributeProvider",
+      "ds-cfg-virtual-attribute-type: description",
+      "ds-cfg-virtual-attribute-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-virtual-attribute",
+      "cn: test parent 1",
+      "ds-cfg-virtual-attribute-enabled: true",
+      "ds-cfg-virtual-attribute-class: org.opends.server.extensions.UserDefinedVirtualAttributeProvider",
+      "ds-cfg-virtual-attribute-type: description",
+      "ds-cfg-virtual-attribute-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();
+    LDAPProfile.getInstance().pushWrapper(new MockLDAPProfile());
+
+    // 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 {
+    LDAPProfile.getInstance().popWrapper();
+    TestCfg.cleanup();
+
+    // Remove test entries.
+    deleteSubtree("cn=test parents,cn=config");
+  }
+
+
+
+  /**
+   * 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, false);
+    TestChildCfgDefn.getInstance().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 {
+      TestChildCfgDefn.getInstance().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, true);
+    TestChildCfgDefn.getInstance().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 {
+      TestChildCfgDefn.getInstance().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, false, true);
+    TestChildCfgDefn.getInstance().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 {
+      TestChildCfgDefn.getInstance().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, true, false);
+    TestChildCfgDefn.getInstance().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 {
+      TestChildCfgDefn.getInstance().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(false, true, false);
+    TestChildCfgDefn.getInstance().addConstraint(constraint);
+
+    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-virtual-attribute-base-dn",
+          "ds-cfg-virtual-attribute-base-dn: dc=new value 1,dc=com",
+          "ds-cfg-virtual-attribute-base-dn: dc=new value 2,dc=com",
+          "-",
+          "replace: ds-cfg-virtual-attribute-group-dn",
+          "ds-cfg-virtual-attribute-group-dn: dc=new value 3,dc=com",
+          "ds-cfg-virtual-attribute-group-dn: dc=new value 4,dc=com"
+      };
+
+      int result = TestCaseUtils.applyModifications(changes);
+      Assert.assertEquals(result, ResultCode.SUCCESS.getIntValue());
+    } finally {
+      TestChildCfgDefn.getInstance().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(true, false, true);
+    TestChildCfgDefn.getInstance().addConstraint(constraint);
+
+    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-virtual-attribute-base-dn",
+          "ds-cfg-virtual-attribute-base-dn: dc=new value 1,dc=com",
+          "ds-cfg-virtual-attribute-base-dn: dc=new value 2,dc=com",
+          "-",
+          "replace: ds-cfg-virtual-attribute-group-dn",
+          "ds-cfg-virtual-attribute-group-dn: dc=new value 3,dc=com",
+          "ds-cfg-virtual-attribute-group-dn: dc=new value 4,dc=com"
+      };
+
+      int result = TestCaseUtils.applyModifications(changes);
+      Assert
+          .assertEquals(result, ResultCode.UNWILLING_TO_PERFORM.getIntValue());
+    } finally {
+      TestChildCfgDefn.getInstance().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.simpleBind("127.0.0.1", TestCaseUtils
+          .getServerLdapPort(), "cn=directory manager", "password");
+    }
+    return adaptor;
+  }
+
+
+
+  // Gets the named parent configuration.
+  private TestParentCfg getParent(String name) throws IllegalArgumentException,
+      ConfigException {
+    ServerManagementContext ctx = ServerManagementContext.getInstance();
+    ServerManagedObject<RootCfg> root = ctx.getRootConfigurationManagedObject();
+    TestParentCfg parent = root.getChild(
+        TestCfg.getTestOneToManyParentRelationDefinition(), name)
+        .getConfiguration();
+    return parent;
+  }
+}
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/admin/server/MockConstraint.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/admin/server/MockConstraint.java
new file mode 100644
index 0000000..e5d9368
--- /dev/null
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/admin/server/MockConstraint.java
@@ -0,0 +1,189 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.admin.server;
+
+
+
+import java.util.Collection;
+import java.util.Collections;
+
+import org.opends.messages.Message;
+import org.opends.server.admin.Constraint;
+import org.opends.server.admin.client.ClientConstraintHandler;
+import org.opends.server.config.ConfigEntry;
+import org.opends.server.config.ConfigException;
+import org.opends.server.core.DirectoryServer;
+import org.opends.server.types.DN;
+import org.testng.Assert;
+
+
+
+/**
+ * A mock constraint which can be configured to refuse various types
+ * of operation.
+ */
+public final class MockConstraint implements Constraint {
+
+  /**
+   * Mock server constraint handler.
+   */
+  private class Handler extends ServerConstraintHandler {
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean isAddAcceptable(ServerManagedObject<?> managedObject,
+        Collection<Message> unacceptableReasons) throws ConfigException {
+      if (!allowAdds) {
+        unacceptableReasons.add(Message.raw("Adds not allowed"));
+      }
+
+      return allowAdds;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean isDeleteAcceptable(ServerManagedObject<?> managedObject,
+        Collection<Message> unacceptableReasons) throws ConfigException {
+      if (!allowDeletes) {
+        unacceptableReasons.add(Message.raw("Deletes not allowed"));
+      }
+
+      return allowDeletes;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean isModifyAcceptable(ServerManagedObject<?> managedObject,
+        Collection<Message> unacceptableReasons) throws ConfigException {
+      if (!allowModifies) {
+        unacceptableReasons.add(Message.raw("Modifies not allowed"));
+      }
+
+      return allowModifies;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void performAddPostCondition(ServerManagedObject<?> managedObject)
+        throws ConfigException {
+      // Make sure that the associated config entry exists.
+      DN targetDN = managedObject.getDN();
+      ConfigEntry configEntry = DirectoryServer.getConfigEntry(targetDN);
+      Assert.assertNotNull(configEntry);
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void performDeletePostCondition(ServerManagedObject<?> managedObject)
+        throws ConfigException {
+      // Make sure that the associated config entry does not exist.
+      DN targetDN = managedObject.getDN();
+      ConfigEntry configEntry = DirectoryServer.getConfigEntry(targetDN);
+      Assert.assertNull(configEntry);
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void performModifyPostCondition(ServerManagedObject<?> managedObject)
+        throws ConfigException {
+      // Make sure that the associated config entry exists.
+      DN targetDN = managedObject.getDN();
+      ConfigEntry configEntry = DirectoryServer.getConfigEntry(targetDN);
+      Assert.assertNotNull(configEntry);
+    }
+
+  }
+
+  // Determines if add operations are allowed.
+  private final boolean allowAdds;
+
+  // Determines if modify operations are allowed.
+  private final boolean allowModifies;
+
+  // Determines if delete operations are allowed.
+  private final boolean allowDeletes;
+
+
+
+  /**
+   * Creates a new mock constraint.
+   *
+   * @param allowAdds
+   *          Determines if add operations are allowed.
+   * @param allowModifies
+   *          Determines if modify operations are allowed.
+   * @param allowDeletes
+   *          Determines if delete operations are allowed.
+   */
+  public MockConstraint(boolean allowAdds, boolean allowModifies,
+      boolean allowDeletes) {
+    this.allowAdds = allowAdds;
+    this.allowModifies = allowModifies;
+    this.allowDeletes = allowDeletes;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public Collection<ClientConstraintHandler> getClientConstraintHandlers() {
+    return Collections.emptySet();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  public Collection<ServerConstraintHandler> getServerConstraintHandlers() {
+    return Collections.<ServerConstraintHandler> singleton(new Handler());
+  }
+
+}

--
Gitblit v1.10.0