From 57a045a15fcb098b295f8acee77239bd12c76fb1 Mon Sep 17 00:00:00 2001
From: matthew_swift <matthew_swift@localhost>
Date: Wed, 05 Sep 2007 22:57:40 +0000
Subject: [PATCH] Improvements to the server-side constraint violation APIs. Now there are just two server-side constraint enforcement call-backs: isUsable and isDeleteAllowed. The first is invoked whenever a managed object is decoded (except in the case where it's about to be deleted). The second is invoked whenever a managed object is about to be deleted. With this change we will now detect constraint violations during server initialization, not just when config change/add/delete events occur.

---
 opendj-sdk/opends/src/server/org/opends/server/admin/server/ConfigAddListenerAdaptor.java               |   31 --
 opendj-sdk/opends/src/server/org/opends/server/admin/server/ConfigExceptionFactory.java                 |   26 ++
 opendj-sdk/opends/src/server/org/opends/server/admin/server/ConfigChangeListenerAdaptor.java            |   42 --
 opendj-sdk/opends/src/server/org/opends/server/admin/server/ConfigDeleteListenerAdaptor.java            |   12 
 opendj-sdk/opends/src/server/org/opends/server/admin/server/ServerManagedObject.java                    |   58 +++++
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/admin/server/ConstraintTest.java |   90 +++++++-
 opendj-sdk/opends/src/server/org/opends/server/admin/server/AbstractConfigListenerAdaptor.java          |    4 
 opendj-sdk/opends/src/messages/messages/admin.properties                                                |    4 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/admin/server/MockConstraint.java |   65 ++---
 opendj-sdk/opends/src/server/org/opends/server/admin/server/ConstraintViolationException.java           |  179 ++++++++++++++++
 opendj-sdk/opends/src/server/org/opends/server/admin/server/ServerConstraintHandler.java                |   94 +++----
 opendj-sdk/opends/src/server/org/opends/server/admin/server/ServerManagementContext.java                |   11 
 12 files changed, 438 insertions(+), 178 deletions(-)

diff --git a/opendj-sdk/opends/src/messages/messages/admin.properties b/opendj-sdk/opends/src/messages/messages/admin.properties
index c9645c0..af43615 100644
--- a/opendj-sdk/opends/src/messages/messages/admin.properties
+++ b/opendj-sdk/opends/src/messages/messages/admin.properties
@@ -255,3 +255,7 @@
  recognized
 SEVERE_ERR_COMMUNICATION_EXCEPTION_DEFAULT_CAUSE_111=A communication problem \
  occurred while contacting the server: %s
+SEVERE_ERR_CONSTRAINT_VIOLATION_EXCEPTION_SINGLE_112=The following \
+ constraint violation occurred: %s
+SEVERE_ERR_CONSTRAINT_VIOLATION_EXCEPTION_PLURAL_113=The following \
+ constraint violations occurred: %s
diff --git a/opendj-sdk/opends/src/server/org/opends/server/admin/server/AbstractConfigListenerAdaptor.java b/opendj-sdk/opends/src/server/org/opends/server/admin/server/AbstractConfigListenerAdaptor.java
index c70daa6..7938f6a 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/admin/server/AbstractConfigListenerAdaptor.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/admin/server/AbstractConfigListenerAdaptor.java
@@ -28,7 +28,7 @@
 
 
 
-import java.util.List;
+import java.util.Collection;
 
 import org.opends.messages.Message;
 import org.opends.messages.MessageBuilder;
@@ -57,7 +57,7 @@
    * @param unacceptableReason
    *          The single message to which messages should be appended.
    */
-  protected final void generateUnacceptableReason(List<Message> reasons,
+  protected final void generateUnacceptableReason(Collection<Message> reasons,
       MessageBuilder unacceptableReason) {
     boolean isFirst = true;
     for (Message reason : reasons) {
diff --git a/opendj-sdk/opends/src/server/org/opends/server/admin/server/ConfigAddListenerAdaptor.java b/opendj-sdk/opends/src/server/org/opends/server/admin/server/ConfigAddListenerAdaptor.java
index 33017e5..ec306cb 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/admin/server/ConfigAddListenerAdaptor.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/admin/server/ConfigAddListenerAdaptor.java
@@ -28,7 +28,6 @@
 
 
 
-import static org.opends.messages.AdminMessages.*;
 import static org.opends.server.loggers.debug.DebugLogger.*;
 
 import java.util.LinkedList;
@@ -168,7 +167,7 @@
         for (ServerConstraintHandler handler : constraint
             .getServerConstraintHandlers()) {
           try {
-            handler.performAddPostCondition(cachedManagedObject);
+            handler.performPostAdd(cachedManagedObject);
           } catch (ConfigException e) {
             if (debugEnabled()) {
               TRACER.debugCaught(DebugLogLevel.ERROR, e);
@@ -216,35 +215,17 @@
     }
 
     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);
+    try {
+      cachedManagedObject.ensureIsUsable();
+    } catch (ConstraintViolationException e) {
+      generateUnacceptableReason(e.getMessages(), unacceptableReason);
       return false;
     }
 
     // Let the add listener decide.
+    List<Message> reasons = new LinkedList<Message>();
     if (listener.isConfigurationAddAcceptable(cachedConfiguration, reasons)) {
       return true;
     } else {
diff --git a/opendj-sdk/opends/src/server/org/opends/server/admin/server/ConfigChangeListenerAdaptor.java b/opendj-sdk/opends/src/server/org/opends/server/admin/server/ConfigChangeListenerAdaptor.java
index 0cc4e9f..c4e4848 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/admin/server/ConfigChangeListenerAdaptor.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/admin/server/ConfigChangeListenerAdaptor.java
@@ -28,9 +28,6 @@
 
 
 
-import org.opends.messages.Message;
-
-import static org.opends.messages.AdminMessages.*;
 import static org.opends.server.loggers.debug.DebugLogger.*;
 
 import java.util.Collection;
@@ -39,6 +36,9 @@
 import java.util.List;
 import java.util.Set;
 
+import org.opends.messages.AdminMessages;
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
 import org.opends.server.admin.AbsoluteInheritedDefaultBehaviorProvider;
 import org.opends.server.admin.AbstractManagedObjectDefinition;
 import org.opends.server.admin.AliasDefaultBehaviorProvider;
@@ -57,11 +57,8 @@
 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.loggers.ErrorLogger;
-import org.opends.messages.AdminMessages;
-
-import org.opends.messages.MessageBuilder;
+import org.opends.server.loggers.debug.DebugTracer;
 import org.opends.server.types.ConfigChangeResult;
 import org.opends.server.types.DN;
 import org.opends.server.types.DebugLogLevel;
@@ -322,7 +319,7 @@
         for (ServerConstraintHandler handler : constraint
             .getServerConstraintHandlers()) {
           try {
-            handler.performModifyPostCondition(cachedManagedObject);
+            handler.performPostModify(cachedManagedObject);
           } catch (ConfigException e) {
             if (debugEnabled()) {
               TRACER.debugCaught(DebugLogLevel.ERROR, e);
@@ -377,35 +374,16 @@
       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);
+    try {
+      cachedManagedObject.ensureIsUsable();
+    } catch (ConstraintViolationException e) {
+      generateUnacceptableReason(e.getMessages(), unacceptableReason);
       return false;
     }
 
     // Let the change listener decide.
+    List<Message> reasons = new LinkedList<Message>();
     if (listener.isConfigurationChangeAcceptable(cachedManagedObject
         .getConfiguration(), reasons)) {
       return true;
diff --git a/opendj-sdk/opends/src/server/org/opends/server/admin/server/ConfigDeleteListenerAdaptor.java b/opendj-sdk/opends/src/server/org/opends/server/admin/server/ConfigDeleteListenerAdaptor.java
index 443a278..1a3ac54 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/admin/server/ConfigDeleteListenerAdaptor.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/admin/server/ConfigDeleteListenerAdaptor.java
@@ -169,7 +169,7 @@
         for (ServerConstraintHandler handler : constraint
             .getServerConstraintHandlers()) {
           try {
-            handler.performDeletePostCondition(cachedManagedObject);
+            handler.performPostDelete(cachedManagedObject);
           } catch (ConfigException e) {
             if (debugEnabled()) {
               TRACER.debugCaught(DebugLogLevel.ERROR, e);
@@ -220,27 +220,27 @@
     List<Message> reasons = new LinkedList<Message>();
 
     // Enforce any constraints.
-    boolean isAcceptable = true;
+    boolean isDeleteAllowed = true;
     ManagedObjectDefinition<?, ?> d = cachedManagedObject
         .getManagedObjectDefinition();
     for (Constraint constraint : d.getAllConstraints()) {
       for (ServerConstraintHandler handler : constraint
           .getServerConstraintHandlers()) {
         try {
-          if (!handler.isDeleteAcceptable(cachedManagedObject, reasons)) {
-            isAcceptable = false;
+          if (!handler.isDeleteAllowed(cachedManagedObject, reasons)) {
+            isDeleteAllowed = false;
           }
         } catch (ConfigException e) {
           Message message = ERR_SERVER_CONSTRAINT_EXCEPTION.get(e
               .getMessageObject());
           reasons.add(message);
-          isAcceptable = false;
+          isDeleteAllowed = false;
         }
       }
     }
 
     // Give up immediately if a constraint violation occurs.
-    if (!isAcceptable) {
+    if (!isDeleteAllowed) {
       generateUnacceptableReason(reasons, unacceptableReason);
       return false;
     }
diff --git a/opendj-sdk/opends/src/server/org/opends/server/admin/server/ConfigExceptionFactory.java b/opendj-sdk/opends/src/server/org/opends/server/admin/server/ConfigExceptionFactory.java
index 4faf5d7..b319a74 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/admin/server/ConfigExceptionFactory.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/admin/server/ConfigExceptionFactory.java
@@ -96,7 +96,6 @@
 
   public ConfigException createDecodingExceptionAdaptor(
       ServerManagedObjectDecodingException e) {
-
     DN dn = e.getPartialManagedObject().getDN();
     Message message =
             AdminMessages.ERR_ADMIN_MANAGED_OBJECT_DECODING_PROBLEM.get(
@@ -108,13 +107,32 @@
 
 
   /**
-   * Create an exception that describes a problem that occurred when attempting
-   * to load and instantiate a class.
+   * Create a configuration exception from a constraints violation
+   * decoding exception.
+   *
+   * @param e
+   *          The constraints violation decoding exception.
+   * @return Returns the configuration exception.
+   */
+  public ConfigException createDecodingExceptionAdaptor(
+      ConstraintViolationException e) {
+    DN dn = e.getManagedObject().getDN();
+    Message message = AdminMessages.ERR_ADMIN_MANAGED_OBJECT_DECODING_PROBLEM
+        .get(String.valueOf(dn), stackTraceToSingleLineString(e));
+    return new ConfigException(message, e);
+  }
+
+
+
+  /**
+   * Create an exception that describes a problem that occurred when
+   * attempting to load and instantiate a class.
    *
    * @param dn
    *          The dn of the configuration entry was being processed.
    * @param className
-   *          The name of the class that could not be loaded or instantiated.
+   *          The name of the class that could not be loaded or
+   *          instantiated.
    * @param e
    *          The exception that occurred.
    * @return Returns the configuration exception.
diff --git a/opendj-sdk/opends/src/server/org/opends/server/admin/server/ConstraintViolationException.java b/opendj-sdk/opends/src/server/org/opends/server/admin/server/ConstraintViolationException.java
new file mode 100644
index 0000000..32d98c5
--- /dev/null
+++ b/opendj-sdk/opends/src/server/org/opends/server/admin/server/ConstraintViolationException.java
@@ -0,0 +1,179 @@
+/*
+ * 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 static org.opends.messages.AdminMessages.*;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.server.admin.DecodingException;
+import org.opends.server.util.Validator;
+
+
+
+/**
+ * This exception is thrown when the server refuses to use or delete a
+ * managed object due to one or more constraints that cannot be
+ * satisfied.
+ */
+public class ConstraintViolationException extends DecodingException {
+
+  /**
+   * Serialization ID.
+   */
+  private static final long serialVersionUID = -4902443848460011875L;
+
+  // The server managed object.
+  private final ServerManagedObject<?> managedObject;
+
+
+
+  // Gets the default message.
+  private static Message getDefaultMessage(Collection<Message> messages) {
+    Validator.ensureNotNull(messages);
+    Validator.ensureTrue(!messages.isEmpty());
+
+    if (messages.size() == 1) {
+      return ERR_CONSTRAINT_VIOLATION_EXCEPTION_SINGLE.get(messages.iterator()
+          .next());
+    } else {
+      return ERR_CONSTRAINT_VIOLATION_EXCEPTION_PLURAL
+          .get(getSingleMessage(messages));
+    }
+  }
+
+
+
+  // Merge the messages into a single message.
+  private static Message getSingleMessage(Collection<Message> messages) {
+    if (messages.size() == 1) {
+      return messages.iterator().next();
+    } else {
+      MessageBuilder builder = new MessageBuilder();
+
+      boolean isFirst = true;
+      for (Message m : messages) {
+        if (!isFirst) {
+          builder.append(";  ");
+        }
+        builder.append(m);
+        isFirst = false;
+      }
+
+      return builder.toMessage();
+    }
+  }
+
+  // The messages describing the constraint violations that occurred.
+  private final Collection<Message> messages;
+
+
+
+  /**
+   * Creates a new constraint violation exception with the provided
+   * messages.
+   *
+   * @param managedObject
+   *          The server managed object which caused the constraint
+   *          violations.
+   * @param messages
+   *          The messages describing the constraint violations that
+   *          occurred (must be non-<code>null</code> and
+   *          non-empty).
+   */
+  public ConstraintViolationException(ServerManagedObject<?> managedObject,
+      Collection<Message> messages) {
+    super(getDefaultMessage(messages));
+
+    this.managedObject = managedObject;
+    this.messages = new ArrayList<Message>(messages);
+  }
+
+
+
+  /**
+   * Creates a new constraint violation exception with the provided
+   * message.
+   *
+   * @param managedObject
+   *          The server managed object which caused the constraint
+   *          violations.
+   * @param message
+   *          The message describing the constraint violation that
+   *          occurred.
+   */
+  public ConstraintViolationException(ServerManagedObject<?> managedObject,
+      Message message) {
+    this(managedObject, Collections.singleton(message));
+  }
+
+
+
+  /**
+   * Gets an unmodifiable collection view of the messages describing
+   * the constraint violations that occurred.
+   *
+   * @return Returns an unmodifiable collection view of the messages
+   *         describing the constraint violations that occurred.
+   */
+  public Collection<Message> getMessages() {
+    return Collections.unmodifiableCollection(messages);
+  }
+
+
+
+  /**
+   * Creates a single message listing all the messages combined into a
+   * single list separated by semi-colons.
+   *
+   * @return Returns a single message listing all the messages
+   *         combined into a single list separated by semi-colons.
+   */
+  public Message getMessagesAsSingleMessage() {
+    return getSingleMessage(messages);
+  }
+
+
+
+  /**
+   * Gets the server managed object which caused the constraint
+   * violations.
+   *
+   * @return Returns the server managed object which caused the
+   *         constraint violations.
+   */
+  public ServerManagedObject<?> getManagedObject() {
+    return managedObject;
+  }
+}
diff --git a/opendj-sdk/opends/src/server/org/opends/server/admin/server/ServerConstraintHandler.java b/opendj-sdk/opends/src/server/org/opends/server/admin/server/ServerConstraintHandler.java
index 652d8f8..430fc17 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/admin/server/ServerConstraintHandler.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/admin/server/ServerConstraintHandler.java
@@ -67,42 +67,14 @@
 
 
   /**
-   * Determines whether or not the newly created managed object which
-   * is about to be added to the server configuration satisfies this
-   * constraint.
+   * Determines whether or not the existing managed object can be
+   * deleted from the server's configuration. For example, an
+   * implementation might enforce referential integrity by preventing
+   * referenced managed objects from being deleted.
    * <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.
+   * managed object cannot be deleted.
    * <p>
    * The default implementation is to return <code>true</code>.
    *
@@ -112,12 +84,13 @@
    *          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.
+   *         satisfied, or <code>false</code> if it is not and the
+   *         managed object cannot be deleted.
    * @throws ConfigException
    *           If an configuration exception prevented this constraint
    *           from being evaluated.
    */
-  public boolean isDeleteAcceptable(ServerManagedObject<?> managedObject,
+  public boolean isDeleteAllowed(ServerManagedObject<?> managedObject,
       Collection<Message> unacceptableReasons) throws ConfigException {
     return true;
   }
@@ -125,28 +98,32 @@
 
 
   /**
-   * Determines whether or not the changes to an existing managed
-   * object which are about to be committed to the server
-   * configuration satisfies this constraint.
+   * Determines whether or not the provided managed object can be used
+   * by the server. This method is invoked each time a managed object
+   * is decoded by the administration framework: when an attempt is
+   * made to add a new configuration, modify an existing
+   * configuration, or during server initialization. If the constraint
+   * is not satisfied the managed object will be rejected.
    * <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.
+   * managed object is not usable.
    * <p>
    * The default implementation is to return <code>true</code>.
    *
    * @param managedObject
-   *          The modified managed object.
+   *          The new 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.
+   * @return Returns <code>true</code> if this constraint is
+   *         satisfied, or <code>false</code> if it is not and the
+   *         managed object cannot be used.
    * @throws ConfigException
    *           If an configuration exception prevented this constraint
    *           from being evaluated.
    */
-  public boolean isModifyAcceptable(ServerManagedObject<?> managedObject,
+  public boolean isUsable(ServerManagedObject<?> managedObject,
       Collection<Message> unacceptableReasons) throws ConfigException {
     return true;
   }
@@ -154,17 +131,22 @@
 
 
   /**
-   * Perform any add post-condition processing.
+   * Performs any post-add processing required by this constraint.
+   * This method is invoked after a new managed object has been
+   * accepted for use by the administration framework. This might
+   * occur during initialization or when a managed object is added at
+   * run-time.
    * <p>
    * The default implementation is to do nothing.
    *
    * @param managedObject
-   *          The managed object which was added.
+   *          The managed object which has just been added to the
+   *          server's configuration.
    * @throws ConfigException
-   *           If the post-condition processing fails due to a
-   *           configuration exception.
+   *           If the post-add processing fails due to a configuration
+   *           exception.
    */
-  public void performAddPostCondition(ServerManagedObject<?> managedObject)
+  public void performPostAdd(ServerManagedObject<?> managedObject)
       throws ConfigException {
     // Do nothing.
   }
@@ -172,17 +154,19 @@
 
 
   /**
-   * Perform any delete post-condition processing.
+   * Performs any post-delete processing required by this constraint.
+   * This method is invoked after a managed object has been accepted
+   * for deletion from the server's configuration.
    * <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
+   *           If the post-delete processing fails due to a
    *           configuration exception.
    */
-  public void performDeletePostCondition(ServerManagedObject<?> managedObject)
+  public void performPostDelete(ServerManagedObject<?> managedObject)
       throws ConfigException {
     // Do nothing.
   }
@@ -190,17 +174,19 @@
 
 
   /**
-   * Perform any modify post-condition processing.
+   * Performs any post-modify processing required by this constraint.
+   * This method is invoked after changes to an existing managed
+   * object have been accepted.
    * <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
+   *           If the post-modify processing fails due to a
    *           configuration exception.
    */
-  public void performModifyPostCondition(ServerManagedObject<?> managedObject)
+  public void performPostModify(ServerManagedObject<?> managedObject)
       throws ConfigException {
     // Do nothing.
   }
diff --git a/opendj-sdk/opends/src/server/org/opends/server/admin/server/ServerManagedObject.java b/opendj-sdk/opends/src/server/org/opends/server/admin/server/ServerManagedObject.java
index 0c527d8..c18c976 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/admin/server/ServerManagedObject.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/admin/server/ServerManagedObject.java
@@ -29,9 +29,12 @@
 
 
 
+import static org.opends.messages.AdminMessages.*;
 import static org.opends.server.loggers.debug.DebugLogger.*;
 import static org.opends.server.util.StaticUtils.*;
 
+import java.util.LinkedList;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.SortedSet;
@@ -40,6 +43,7 @@
 import org.opends.messages.AdminMessages;
 import org.opends.messages.Message;
 import org.opends.server.admin.Configuration;
+import org.opends.server.admin.Constraint;
 import org.opends.server.admin.InstantiableRelationDefinition;
 import org.opends.server.admin.ManagedObjectDefinition;
 import org.opends.server.admin.ManagedObjectPath;
@@ -536,6 +540,25 @@
     ConfigChangeListener adaptor = new ConfigChangeListenerAdaptor<S>(path,
         listener);
     configEntry.registerChangeListener(adaptor);
+
+    // Change listener registration usually signifies that a managed
+    // object has been accepted and added to the server configuration
+    // either during initialization post-add.
+
+    // FIXME: we should prevent multiple invocations in the case where
+    // multiple change listeners are registered for the same object.
+    for (Constraint constraint : definition.getAllConstraints()) {
+      for (ServerConstraintHandler handler : constraint
+          .getServerConstraintHandlers()) {
+        try {
+          handler.performPostAdd(this);
+        } catch (ConfigException e) {
+          if (debugEnabled()) {
+            TRACER.debugCaught(DebugLogLevel.ERROR, e);
+          }
+        }
+      }
+    }
   }
 
 
@@ -601,6 +624,41 @@
 
 
   /**
+   * Determines whether or not this managed object can be used by the
+   * server.
+   *
+   * @throws ConstraintViolationException
+   *           If one or more constraints determined that this managed
+   *           object cannot be used by the server.
+   */
+  void ensureIsUsable() throws ConstraintViolationException {
+    // Enforce any constraints.
+    boolean isUsable = true;
+    List<Message> reasons = new LinkedList<Message>();
+    for (Constraint constraint : definition.getAllConstraints()) {
+      for (ServerConstraintHandler handler : constraint
+          .getServerConstraintHandlers()) {
+        try {
+          if (!handler.isUsable(this, reasons)) {
+            isUsable = false;
+          }
+        } catch (ConfigException e) {
+          Message message = ERR_SERVER_CONSTRAINT_EXCEPTION.get(e
+              .getMessageObject());
+          reasons.add(message);
+          isUsable = false;
+        }
+      }
+    }
+
+    if (!isUsable) {
+      throw new ConstraintViolationException(this, reasons);
+    }
+  }
+
+
+
+  /**
    * Update the config entry associated with this server managed
    * object. This is only intended to be used by change listener call
    * backs in order to update the managed object with the correct
diff --git a/opendj-sdk/opends/src/server/org/opends/server/admin/server/ServerManagementContext.java b/opendj-sdk/opends/src/server/org/opends/server/admin/server/ServerManagementContext.java
index a8d3391..d663430 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/admin/server/ServerManagementContext.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/admin/server/ServerManagementContext.java
@@ -456,13 +456,22 @@
     DN targetDN = DNBuilder.create(path);
     ConfigEntry configEntry = getManagedObjectConfigEntry(targetDN);
     try {
-      return decode(path, configEntry);
+      ServerManagedObject<? extends S> managedObject;
+      managedObject = decode(path, configEntry);
+
+      // Enforce any constraints.
+      managedObject.ensureIsUsable();
+
+      return managedObject;
     } catch (DefinitionDecodingException e) {
       throw ConfigExceptionFactory.getInstance()
           .createDecodingExceptionAdaptor(targetDN, e);
     } catch (ServerManagedObjectDecodingException e) {
       throw ConfigExceptionFactory.getInstance()
           .createDecodingExceptionAdaptor(e);
+    } catch (ConstraintViolationException e) {
+      throw ConfigExceptionFactory.getInstance()
+          .createDecodingExceptionAdaptor(e);
     }
   }
 
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/admin/server/ConstraintTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/admin/server/ConstraintTest.java
index a234ff8..0f2e835 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/admin/server/ConstraintTest.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/admin/server/ConstraintTest.java
@@ -38,6 +38,7 @@
 import org.opends.server.admin.AdminTestCase;
 import org.opends.server.admin.TestCfg;
 import org.opends.server.admin.TestChildCfg;
+import org.opends.server.admin.TestChildCfgDefn;
 import org.opends.server.admin.TestParentCfg;
 import org.opends.server.admin.client.ldap.JNDIDirContextAdaptor;
 import org.opends.server.admin.std.server.RootCfg;
@@ -60,8 +61,7 @@
 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";
+  private static final String TEST_CHILD_1_DN = "cn=test child 1,cn=test children,cn=test parent 1,cn=test parents,cn=config";
 
 
 
@@ -226,6 +226,75 @@
 
 
   /**
+   * Tests that retrieval can succeed.
+   *
+   * @throws Exception
+   *           If the test unexpectedly fails.
+   */
+  @Test
+  public void testGetManagedObjectSuccess() throws Exception {
+    MockConstraint constraint = new MockConstraint(true, false);
+
+    try {
+      TestCaseUtils.addEntry(TEST_CHILD_1);
+      TestCfg.addConstraint(constraint);
+
+      TestParentCfg parent = getParent("test parent 1");
+      parent.getTestChild("test child 1");
+    } finally {
+      TestCfg.removeConstraint(constraint);
+
+      try {
+        deleteSubtree(TEST_CHILD_1_DN);
+      } catch (Exception e) {
+        // Do nothing.
+      }
+    }
+  }
+
+
+
+  /**
+   * Tests that retrieval can fail.
+   *
+   * @throws Exception
+   *           If the test unexpectedly fails.
+   */
+  @Test
+  public void testGetManagedObjectFail() throws Exception {
+    MockConstraint constraint = new MockConstraint(false, true);
+
+    try {
+      TestCaseUtils.addEntry(TEST_CHILD_1);
+      TestCfg.addConstraint(constraint);
+
+      TestParentCfg parent = getParent("test parent 1");
+      parent.getTestChild("test child 1");
+    } catch (ConfigException e) {
+      Throwable cause = e.getCause();
+      if (cause instanceof ConstraintViolationException) {
+        ConstraintViolationException cve = (ConstraintViolationException) cause;
+        Assert.assertEquals(cve.getMessages().size(), 1);
+        Assert.assertSame(cve.getManagedObject().getManagedObjectDefinition(),
+            TestChildCfgDefn.getInstance());
+      } else {
+        // Wrong cause.
+        throw e;
+      }
+    } finally {
+      TestCfg.removeConstraint(constraint);
+
+      try {
+        deleteSubtree(TEST_CHILD_1_DN);
+      } catch (Exception e) {
+        // Do nothing.
+      }
+    }
+  }
+
+
+
+  /**
    * Tests that an add constraint can succeed.
    *
    * @throws Exception
@@ -237,7 +306,7 @@
     AddListener listener = new AddListener();
     parent.addTestChildAddListener(listener);
 
-    MockConstraint constraint = new MockConstraint(true, false, false);
+    MockConstraint constraint = new MockConstraint(true, false);
     TestCfg.addConstraint(constraint);
 
     try {
@@ -271,7 +340,7 @@
     AddListener listener = new AddListener();
     parent.addTestChildAddListener(listener);
 
-    MockConstraint constraint = new MockConstraint(false, true, true);
+    MockConstraint constraint = new MockConstraint(false, true);
     TestCfg.addConstraint(constraint);
 
     try {
@@ -305,7 +374,7 @@
     DeleteListener listener = new DeleteListener();
     parent.addTestChildDeleteListener(listener);
 
-    MockConstraint constraint = new MockConstraint(false, false, true);
+    MockConstraint constraint = new MockConstraint(false, true);
     TestCfg.addConstraint(constraint);
 
     try {
@@ -341,7 +410,7 @@
     DeleteListener listener = new DeleteListener();
     parent.addTestChildDeleteListener(listener);
 
-    MockConstraint constraint = new MockConstraint(true, true, false);
+    MockConstraint constraint = new MockConstraint(true, false);
     TestCfg.addConstraint(constraint);
 
     try {
@@ -381,14 +450,14 @@
   public void testChangeConstraintSuccess() throws Exception {
     TestParentCfg parent = getParent("test parent 1");
 
-    MockConstraint constraint = new MockConstraint(false, true, false);
-    TestCfg.addConstraint(constraint);
+    MockConstraint constraint = new MockConstraint(true, false);
 
     try {
       // Add the entry.
       TestCaseUtils.addEntry(TEST_CHILD_1);
       TestChildCfg child = parent.getTestChild("test child 1");
 
+      TestCfg.addConstraint(constraint);
       ChangeListener listener = new ChangeListener();
       child.addChangeListener(listener);
 
@@ -428,15 +497,14 @@
   @Test
   public void testChangeConstraintFail() throws Exception {
     TestParentCfg parent = getParent("test parent 1");
-
-    MockConstraint constraint = new MockConstraint(true, false, true);
-    TestCfg.addConstraint(constraint);
+    MockConstraint constraint = new MockConstraint(false, true);
 
     try {
       // Add the entry.
       TestCaseUtils.addEntry(TEST_CHILD_1);
       TestChildCfg child = parent.getTestChild("test child 1");
 
+      TestCfg.addConstraint(constraint);
       ChangeListener listener = new ChangeListener();
       child.addChangeListener(listener);
 
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/admin/server/MockConstraint.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/admin/server/MockConstraint.java
index e5d9368..77c594d 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/admin/server/MockConstraint.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/admin/server/MockConstraint.java
@@ -57,13 +57,14 @@
      * {@inheritDoc}
      */
     @Override
-    public boolean isAddAcceptable(ServerManagedObject<?> managedObject,
+    public boolean isDeleteAllowed(ServerManagedObject<?> managedObject,
         Collection<Message> unacceptableReasons) throws ConfigException {
-      if (!allowAdds) {
-        unacceptableReasons.add(Message.raw("Adds not allowed"));
+      if (!isDeleteAllowed) {
+        unacceptableReasons
+            .add(Message.raw("Configuration cannot be deleted."));
       }
 
-      return allowAdds;
+      return isDeleteAllowed;
     }
 
 
@@ -72,13 +73,13 @@
      * {@inheritDoc}
      */
     @Override
-    public boolean isDeleteAcceptable(ServerManagedObject<?> managedObject,
+    public boolean isUsable(ServerManagedObject<?> managedObject,
         Collection<Message> unacceptableReasons) throws ConfigException {
-      if (!allowDeletes) {
-        unacceptableReasons.add(Message.raw("Deletes not allowed"));
+      if (!isUsable) {
+        unacceptableReasons.add(Message.raw("Configuration is not usable."));
       }
 
-      return allowDeletes;
+      return isUsable;
     }
 
 
@@ -87,22 +88,7 @@
      * {@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)
+    public void performPostAdd(ServerManagedObject<?> managedObject)
         throws ConfigException {
       // Make sure that the associated config entry exists.
       DN targetDN = managedObject.getDN();
@@ -116,7 +102,7 @@
      * {@inheritDoc}
      */
     @Override
-    public void performDeletePostCondition(ServerManagedObject<?> managedObject)
+    public void performPostDelete(ServerManagedObject<?> managedObject)
         throws ConfigException {
       // Make sure that the associated config entry does not exist.
       DN targetDN = managedObject.getDN();
@@ -130,7 +116,7 @@
      * {@inheritDoc}
      */
     @Override
-    public void performModifyPostCondition(ServerManagedObject<?> managedObject)
+    public void performPostModify(ServerManagedObject<?> managedObject)
         throws ConfigException {
       // Make sure that the associated config entry exists.
       DN targetDN = managedObject.getDN();
@@ -140,32 +126,25 @@
 
   }
 
-  // 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;
+  private final boolean isDeleteAllowed;
+
+  // Determines if configurations can be decoded.
+  private final boolean isUsable;
 
 
 
   /**
    * Creates a new mock constraint.
    *
-   * @param allowAdds
-   *          Determines if add operations are allowed.
-   * @param allowModifies
-   *          Determines if modify operations are allowed.
-   * @param allowDeletes
+   * @param isUsable
+   *          Determines if configurations can be decoded.
+   * @param isDeleteAllowed
    *          Determines if delete operations are allowed.
    */
-  public MockConstraint(boolean allowAdds, boolean allowModifies,
-      boolean allowDeletes) {
-    this.allowAdds = allowAdds;
-    this.allowModifies = allowModifies;
-    this.allowDeletes = allowDeletes;
+  public MockConstraint(boolean isUsable, boolean isDeleteAllowed) {
+    this.isUsable = isUsable;
+    this.isDeleteAllowed = isDeleteAllowed;
   }
 
 

--
Gitblit v1.10.0