From 2ba8e81b3d42cdacb4bf15d77ca681660595ee46 Mon Sep 17 00:00:00 2001
From: matthew_swift <matthew_swift@localhost>
Date: Tue, 11 Sep 2007 23:37:29 +0000
Subject: [PATCH] Partial fix for issue 1449: improve server-side referential integrity support.

---
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/admin/server/AggregationTest.java |  761 ++++++++++++++++++++++++++++++++++-----
 opendj-sdk/opends/src/messages/messages/admin.properties                                                 |   16 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/admin/TestCfg.java                |   14 
 opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/admin/MockLDAPProfile.java        |    2 
 opendj-sdk/opends/src/server/org/opends/server/admin/AggregationPropertyDefinition.java                  |  307 +++++++++++++++
 5 files changed, 979 insertions(+), 121 deletions(-)

diff --git a/opendj-sdk/opends/src/messages/messages/admin.properties b/opendj-sdk/opends/src/messages/messages/admin.properties
index 5089f2e..3b03a62 100644
--- a/opendj-sdk/opends/src/messages/messages/admin.properties
+++ b/opendj-sdk/opends/src/messages/messages/admin.properties
@@ -260,4 +260,18 @@
 SEVERE_ERR_CONSTRAINT_VIOLATION_EXCEPTION_PLURAL_113=The following \
  constraint violations occurred: %s
 SEVERE_ERR_SERVER_REFINT_DANGLING_REFERENCE_114=The value "%s" in \
- property "%s" in the %s in entry "%s" refers to a non-existent %s at "%s"
\ No newline at end of file
+ property "%s" in the %s in entry "%s" refers to a non-existent %s \
+ in entry "%s"
+SEVERE_ERR_SERVER_REFINT_SOURCE_ENABLED_TARGET_DISABLED_115=The value \
+ "%s" in property "%s" in the enabled %s in entry "%s" refers to a \
+ disabled %s in entry "%s"
+SEVERE_ERR_SERVER_REFINT_TARGET_DISABLED_116=The value "%s" in \
+ property "%s" in the %s in entry "%s" refers to a disabled %s in \
+ entry "%s"
+SEVERE_ERR_SERVER_REFINT_CANNOT_DELETE_117=The %s in entry "%s" \
+ cannot be deleted because it is referenced by the "%s" property \
+ of the %s in entry "%s"
+SEVERE_ERR_SERVER_REFINT_CANNOT_DISABLE_118=The %s in entry "%s" \
+ cannot be disabled because it is referenced by the "%s" property \
+ of the %s in entry "%s"
+
diff --git a/opendj-sdk/opends/src/server/org/opends/server/admin/AggregationPropertyDefinition.java b/opendj-sdk/opends/src/server/org/opends/server/admin/AggregationPropertyDefinition.java
index 77b4046..f06b6c3 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/admin/AggregationPropertyDefinition.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/admin/AggregationPropertyDefinition.java
@@ -34,14 +34,21 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
 import java.util.SortedSet;
 
 import org.opends.messages.Message;
 import org.opends.server.admin.client.ClientConstraintHandler;
+import org.opends.server.admin.server.ConfigurationChangeListener;
+import org.opends.server.admin.server.ConfigurationDeleteListener;
 import org.opends.server.admin.server.ServerConstraintHandler;
 import org.opends.server.admin.server.ServerManagedObject;
 import org.opends.server.admin.server.ServerManagementContext;
 import org.opends.server.config.ConfigException;
+import org.opends.server.types.ConfigChangeResult;
 import org.opends.server.types.DN;
 
 
@@ -285,11 +292,124 @@
 
 
   /**
+   * A change listener which prevents the named component from being
+   * disabled.
+   */
+  private class ReferentialIntegrityChangeListener implements
+      ConfigurationChangeListener<S> {
+
+    // The error message which should be returned if an attempt is
+    // made to disable the referenced component.
+    private final Message message;
+
+    // The path of the referenced component.
+    private final ManagedObjectPath<C, S> path;
+
+
+
+    // Creates a new referential integrity delete listener.
+    private ReferentialIntegrityChangeListener(ManagedObjectPath<C, S> path,
+        Message message) {
+      this.path = path;
+      this.message = message;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public ConfigChangeResult applyConfigurationChange(S configuration) {
+      throw new IllegalStateException("Attempting to disable a referenced "
+          + configuration.definition().getUserFriendlyName());
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isConfigurationChangeAcceptable(S configuration,
+        List<Message> unacceptableReasons) {
+      // Always prevent the referenced component from being
+      // disabled.
+      PropertyProvider provider = configuration.properties();
+      Collection<Boolean> values = provider
+          .getPropertyValues(getTargetEnabledPropertyDefinition());
+      if (values.iterator().next() == false) {
+        unacceptableReasons.add(message);
+        return false;
+      }
+      return true;
+    }
+
+
+
+    // Gets the path associated with this listener.
+    private ManagedObjectPath<C, S> getManagedObjectPath() {
+      return path;
+    }
+
+  }
+
+
+
+  /**
+   * A delete listener which prevents the named component from being
+   * deleted.
+   */
+  private class ReferentialIntegrityDeleteListener implements
+      ConfigurationDeleteListener<S> {
+
+    // The DN of the referenced configuration entry.
+    private final DN dn;
+
+    // The error message which should be returned if an attempt is
+    // made to delete the referenced component.
+    private final Message message;
+
+
+
+    // Creates a new referential integrity delete listener.
+    private ReferentialIntegrityDeleteListener(DN dn, Message message) {
+      this.dn = dn;
+      this.message = message;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public ConfigChangeResult applyConfigurationDelete(S configuration) {
+      throw new IllegalStateException("Attempting to delete a referenced "
+          + configuration.definition().getUserFriendlyName());
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean isConfigurationDeleteAcceptable(S configuration,
+        List<Message> unacceptableReasons) {
+      if (configuration.dn().equals(dn)) {
+        // Always prevent deletion of the referenced component.
+        unacceptableReasons.add(message);
+        return false;
+      }
+
+      return true;
+    }
+
+  }
+
+
+
+  /**
    * The server-side constraint handler implementation.
    */
-  private static class ServerHandler
-      <C extends ConfigurationClient, S extends Configuration>
-      extends ServerConstraintHandler {
+  private class ServerHandler extends ServerConstraintHandler {
 
     // The associated property definition.
     private final AggregationPropertyDefinition<C, S> pd;
@@ -311,23 +431,178 @@
         Collection<Message> unacceptableReasons) throws ConfigException {
       SortedSet<String> names = managedObject.getPropertyValues(pd);
       ServerManagementContext context = ServerManagementContext.getInstance();
-      boolean isUsable = true;
+      BooleanPropertyDefinition tpd = pd.getTargetEnabledPropertyDefinition();
+      BooleanPropertyDefinition spd = pd.getSourceEnabledPropertyDefinition();
+      Message thisUFN = managedObject.getManagedObjectDefinition()
+          .getUserFriendlyName();
+      String thisDN = managedObject.getDN().toString();
+      Message thatUFN = pd.getRelationDefinition().getUserFriendlyName();
 
+      boolean isUsable = true;
       for (String name : names) {
         ManagedObjectPath<C, S> path = pd.getChildPath(name);
+        String thatDN = path.toDN().toString();
+
         if (!context.managedObjectExists(path)) {
           Message msg = ERR_SERVER_REFINT_DANGLING_REFERENCE.get(name, pd
-              .getName(), managedObject.getManagedObjectDefinition()
-              .getUserFriendlyName(), managedObject.getDN().toString(), pd
-              .getRelationDefinition().getUserFriendlyName(), path.toDN()
-              .toString());
+              .getName(), thisUFN, thisDN, thatUFN, thatDN);
           unacceptableReasons.add(msg);
           isUsable = false;
+        } else if (tpd != null) {
+          // Check that the referenced component is enabled.
+          ServerManagedObject<? extends S> ref = context.getManagedObject(path);
+
+          if (spd != null) {
+            // Target must be enabled but only if the source is
+            // enabled.
+            if (managedObject.getPropertyValue(spd)
+                && !ref.getPropertyValue(tpd)) {
+              Message msg = ERR_SERVER_REFINT_SOURCE_ENABLED_TARGET_DISABLED
+                  .get(name, pd.getName(), thisUFN, thisDN, thatUFN, thatDN);
+              unacceptableReasons.add(msg);
+              isUsable = false;
+            }
+          } else {
+            // Target must always be enabled.
+            if (!ref.getPropertyValue(tpd)) {
+              Message msg = ERR_SERVER_REFINT_TARGET_DISABLED.get(name, pd
+                  .getName(), thisUFN, thisDN, thatUFN, thatDN);
+              unacceptableReasons.add(msg);
+              isUsable = false;
+            }
+          }
         }
       }
 
       return isUsable;
     }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void performPostAdd(ServerManagedObject<?> managedObject)
+        throws ConfigException {
+      // First make sure existing listeners associated with this
+      // managed object are removed. This is required in order to
+      // prevent multiple change listener registrations from
+      // occurring, for example if this call-back is invoked multiple
+      // times after the same add event.
+      performPostDelete(managedObject);
+
+      // Add change and delete listeners against all referenced
+      // components.
+      BooleanPropertyDefinition tpd = pd.getTargetEnabledPropertyDefinition();
+      BooleanPropertyDefinition spd = pd.getSourceEnabledPropertyDefinition();
+      Message thisUFN = managedObject.getManagedObjectDefinition()
+          .getUserFriendlyName();
+      String thisDN = managedObject.getDN().toString();
+      Message thatUFN = pd.getRelationDefinition().getUserFriendlyName();
+
+      // Referenced managed objects will only need a change listener
+      // if they have can be disabled.
+      boolean needsChangeListeners;
+      if (tpd != null) {
+        if (spd == null) {
+          needsChangeListeners = true;
+        } else {
+          needsChangeListeners = managedObject.getPropertyValue(spd);
+        }
+      } else {
+        needsChangeListeners = false;
+      }
+
+      // Delete listeners need to be registered against the parent
+      // entry of the referenced components.
+      ServerManagementContext context = ServerManagementContext.getInstance();
+      ManagedObjectPath<?, ?> parentPath = pd.getParentPath();
+      ServerManagedObject<?> parent = context.getManagedObject(parentPath);
+
+      // Create entries in the listener tables.
+      List<ReferentialIntegrityDeleteListener> dlist =
+        new LinkedList<ReferentialIntegrityDeleteListener>();
+      deleteListeners.put(managedObject.getDN(), dlist);
+
+      List<ReferentialIntegrityChangeListener> clist =
+        new LinkedList<ReferentialIntegrityChangeListener>();
+      changeListeners.put(managedObject.getDN(), clist);
+
+      for (String name : managedObject.getPropertyValues(pd)) {
+        ManagedObjectPath<C, S> path = pd.getChildPath(name);
+        DN dn = path.toDN();
+        String thatDN = dn.toString();
+
+        // Register the delete listener.
+        Message msg = ERR_SERVER_REFINT_CANNOT_DELETE.get(thatUFN, thatDN, pd
+            .getName(), thisUFN, thisDN);
+        ReferentialIntegrityDeleteListener dl =
+          new ReferentialIntegrityDeleteListener(dn, msg);
+        parent.registerDeleteListener(pd.getRelationDefinition(), dl);
+        dlist.add(dl);
+
+        // Register the change listener if required.
+        if (needsChangeListeners) {
+          ServerManagedObject<? extends S> ref = context.getManagedObject(path);
+          msg = ERR_SERVER_REFINT_CANNOT_DISABLE.get(thatUFN, thatDN, pd
+              .getName(), thisUFN, thisDN);
+          ReferentialIntegrityChangeListener cl =
+            new ReferentialIntegrityChangeListener(path, msg);
+          ref.registerChangeListener(cl);
+          clist.add(cl);
+        }
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void performPostDelete(ServerManagedObject<?> managedObject)
+        throws ConfigException {
+      // Remove any registered delete and change listeners.
+      ServerManagementContext context = ServerManagementContext.getInstance();
+      DN dn = managedObject.getDN();
+
+      // Delete listeners need to be deregistered against the parent
+      // entry of the referenced components.
+      ManagedObjectPath<?, ?> parentPath = pd.getParentPath();
+      ServerManagedObject<?> parent = context.getManagedObject(parentPath);
+      if (deleteListeners.containsKey(dn)) {
+        for (ReferentialIntegrityDeleteListener dl : deleteListeners.get(dn)) {
+          parent.deregisterDeleteListener(pd.getRelationDefinition(), dl);
+        }
+        deleteListeners.remove(dn);
+      }
+
+      // Change listeners need to be deregistered from their
+      // associated referenced component.
+      if (changeListeners.containsKey(dn)) {
+        for (ReferentialIntegrityChangeListener cl : changeListeners.get(dn)) {
+          ManagedObjectPath<C, S> path = cl.getManagedObjectPath();
+          ServerManagedObject<? extends S> ref = context.getManagedObject(path);
+          ref.deregisterChangeListener(cl);
+        }
+        changeListeners.remove(dn);
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void performPostModify(ServerManagedObject<?> managedObject)
+        throws ConfigException {
+      // Remove all the constraints associated with this managed
+      // object and then re-register them.
+      performPostDelete(managedObject);
+      performPostAdd(managedObject);
+    }
   }
 
 
@@ -349,11 +624,23 @@
    * @return Returns the new aggregation property definition builder.
    */
   public static <C extends ConfigurationClient, S extends Configuration>
-  Builder<C, S> createBuilder(
+      Builder<C, S> createBuilder(
       AbstractManagedObjectDefinition<?, ?> d, String propertyName) {
     return new Builder<C, S>(d, propertyName);
   }
 
+  // The active server-side referential integrity change listeners
+  // associated with this property.
+  private final Map<DN, List<ReferentialIntegrityChangeListener>>
+      changeListeners =
+        new HashMap<DN, List<ReferentialIntegrityChangeListener>>();
+
+  // The active server-side referential integrity delete listeners
+  // associated with this property.
+  private final Map<DN, List<ReferentialIntegrityDeleteListener>>
+      deleteListeners =
+        new HashMap<DN, List<ReferentialIntegrityDeleteListener>>();
+
   // The name of the managed object which is the parent of the
   // aggregated managed objects.
   private final ManagedObjectPath<?, ?> parentPath;
@@ -502,7 +789,7 @@
    * {@inheritDoc}
    */
   public Collection<ServerConstraintHandler> getServerConstraintHandlers() {
-    ServerConstraintHandler handler = new ServerHandler<C, S>(this);
+    ServerConstraintHandler handler = new ServerHandler(this);
     return Collections.singleton(handler);
   }
 
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/admin/MockLDAPProfile.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/admin/MockLDAPProfile.java
index 8effbd0..7a05390 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/admin/MockLDAPProfile.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/admin/MockLDAPProfile.java
@@ -81,7 +81,7 @@
         return "ds-cfg-virtual-attribute-base-dn";
       } else if (pd == (PropertyDefinition<?>)td.getOptionalMultiValuedDNProperty2PropertyDefinition()) {
         return "ds-cfg-virtual-attribute-group-dn";
-      } else if (pd == (PropertyDefinition<?>)td.getAggregationPropertyPropertyDefinition()) {
+      } else if (pd.getName().equals("aggregation-property")) {
         return "ds-cfg-backend-base-dn";
       } else {
         throw new RuntimeException("Unexpected test-child property"
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/admin/TestCfg.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/admin/TestCfg.java
index 53e2e37..82c1a1a 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/admin/TestCfg.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/admin/TestCfg.java
@@ -228,6 +228,20 @@
 
 
   /**
+   * Adds a property definition temporarily with test child
+   * definition, replacing any existing property definition with the
+   * same name.
+   *
+   * @param pd
+   *          The property definition.
+   */
+  public static void addPropertyDefinition(PropertyDefinition<?> pd) {
+    TestChildCfgDefn.getInstance().registerPropertyDefinition(pd);
+  }
+
+
+
+  /**
    * Removes a constraint from the test child definition.
    *
    * @param constraint
diff --git a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/admin/server/AggregationTest.java b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/admin/server/AggregationTest.java
index 78048c0..502eeb2 100644
--- a/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/admin/server/AggregationTest.java
+++ b/opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/admin/server/AggregationTest.java
@@ -28,7 +28,9 @@
 
 
 
+import java.net.ServerSocket;
 import java.util.Collection;
+import java.util.List;
 import java.util.SortedSet;
 import java.util.TreeSet;
 
@@ -37,16 +39,31 @@
 import org.opends.messages.Message;
 import org.opends.server.TestCaseUtils;
 import org.opends.server.admin.AdminTestCase;
+import org.opends.server.admin.AdministratorAction;
+import org.opends.server.admin.AggregationPropertyDefinition;
 import org.opends.server.admin.IllegalPropertyValueStringException;
+import org.opends.server.admin.ManagedObjectNotFoundException;
+import org.opends.server.admin.ManagedObjectPath;
 import org.opends.server.admin.PropertyException;
+import org.opends.server.admin.PropertyOption;
 import org.opends.server.admin.TestCfg;
 import org.opends.server.admin.TestChildCfg;
 import org.opends.server.admin.TestChildCfgDefn;
 import org.opends.server.admin.TestParentCfg;
+import org.opends.server.admin.UndefinedDefaultBehaviorProvider;
+import org.opends.server.admin.client.OperationRejectedException;
 import org.opends.server.admin.client.ldap.JNDIDirContextAdaptor;
+import org.opends.server.admin.std.client.ConnectionHandlerCfgClient;
+import org.opends.server.admin.std.client.LDAPConnectionHandlerCfgClient;
+import org.opends.server.admin.std.client.RootCfgClient;
+import org.opends.server.admin.std.meta.ConnectionHandlerCfgDefn;
+import org.opends.server.admin.std.meta.LDAPConnectionHandlerCfgDefn;
+import org.opends.server.admin.std.server.ConnectionHandlerCfg;
 import org.opends.server.admin.std.server.RootCfg;
 import org.opends.server.config.ConfigException;
 import org.opends.server.core.DirectoryServer;
+import org.opends.server.types.ConfigChangeResult;
+import org.opends.server.types.ResultCode;
 import org.testng.Assert;
 import org.testng.annotations.AfterClass;
 import org.testng.annotations.BeforeClass;
@@ -57,9 +74,70 @@
 /**
  * Test cases for aggregations on the server-side.
  */
-@Test(sequential=true)
+@Test(sequential = true)
 public final class AggregationTest extends AdminTestCase {
 
+  /**
+   * Dummy change listener for triggering change constraint
+   * call-backs.
+   */
+  private static final class DummyChangeListener 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;
+    }
+  }
+
+
+
+  /**
+   * Dummy delete listener for triggering delete constraint
+   * call-backs.
+   */
+  private static final class DummyDeleteListener 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;
+    }
+  }
+
+  private static final String TEST_CHILD_7_DN = "cn=test child 7,cn=test children,cn=test parent 1,cn=test parents,cn=config";
+
+  private static final String TEST_CHILD_6_DN = "cn=test child 6,cn=test children,cn=test parent 1,cn=test parents,cn=config";
+
+  // The name of the test connection handler.
+  private static final String TEST_CONNECTION_HANDLER_NAME = "Test Connection Handler";
+
   // Test child 1 LDIF.
   private static final String[] TEST_CHILD_1 = new String[] {
       "dn: cn=test child 1,cn=test children,cn=test parent 1,cn=test parents,cn=config",
@@ -72,17 +150,6 @@
       "ds-cfg-virtual-attribute-conflict-behavior: virtual-overrides-real"
   };
 
-
-
-  // Assert that the values of child 1 are correct.
-  private void assertChild1(TestChildCfg child) {
-    Assert.assertEquals(child.getMandatoryClassProperty(),
-        "org.opends.server.extensions.UserDefinedVirtualAttributeProvider");
-    Assert.assertEquals(child.getMandatoryReadOnlyAttributeTypeProperty(),
-        DirectoryServer.getAttributeType("description"));
-    assertSetEquals(child.getAggregationProperty(), new String[0]);
-  }
-
   // Test child 2 LDIF.
   private static final String[] TEST_CHILD_2 = new String[] {
       "dn: cn=test child 2,cn=test children,cn=test parent 1,cn=test parents,cn=config",
@@ -96,23 +163,6 @@
       "ds-cfg-backend-base-dn: cn=LDAP Connection Handler, cn=connection handlers, cn=config"
   };
 
-
-
-  // Assert that the values of child 2 are correct.
-  private void assertChild2(TestChildCfg child) {
-    Assert.assertEquals(child.getMandatoryClassProperty(),
-        "org.opends.server.extensions.UserDefinedVirtualAttributeProvider");
-    Assert.assertEquals(child.getMandatoryReadOnlyAttributeTypeProperty(),
-        DirectoryServer.getAttributeType("description"));
-
-    // Test normalization.
-    assertSetEquals(child.getAggregationProperty(), "LDAP Connection Handler");
-    assertSetEquals(child.getAggregationProperty(),
-        "  LDAP   Connection  Handler ");
-    assertSetEquals(child.getAggregationProperty(),
-        "  ldap connection HANDLER ");
-  }
-
   // Test child 3 LDIF (invalid reference).
   private static final String[] TEST_CHILD_3 = new String[] {
       "dn: cn=test child 3,cn=test children,cn=test parent 1,cn=test parents,cn=config",
@@ -140,20 +190,6 @@
       "ds-cfg-backend-base-dn: cn=LDAPS Connection Handler, cn=connection handlers, cn=config"
   };
 
-
-
-  // Assert that the values of child 4 are correct.
-  private void assertChild4(TestChildCfg child) {
-    Assert.assertEquals(child.getMandatoryClassProperty(),
-        "org.opends.server.extensions.UserDefinedVirtualAttributeProvider");
-    Assert.assertEquals(child.getMandatoryReadOnlyAttributeTypeProperty(),
-        DirectoryServer.getAttributeType("description"));
-    assertSetEquals(child.getAggregationProperty(), "LDAPS Connection Handler",
-        "LDAP Connection Handler");
-  }
-  
-  
-
   // Test child 5 LDIF.
   private static final String[] TEST_CHILD_5 = new String[] {
       "dn: cn=test child 5,cn=test children,cn=test parent 1,cn=test parents,cn=config",
@@ -169,6 +205,34 @@
       "ds-cfg-backend-base-dn: cn=LDAP Connection Handler, cn=connection handlers, cn=config"
   };
 
+  // Test child 6 LDIF.
+  private static final String[] TEST_CHILD_6 = new String[] {
+      "dn: cn=test child 6,cn=test children,cn=test parent 1,cn=test parents,cn=config",
+      "objectclass: top",
+      "objectclass: ds-cfg-test-child-dummy",
+      "cn: test child 6",
+      "ds-cfg-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",
+      "ds-cfg-backend-base-dn: cn=" + TEST_CONNECTION_HANDLER_NAME
+          + ", cn=connection handlers, cn=config"
+  };
+
+  // Test child 7 LDIF.
+  private static final String[] TEST_CHILD_7 = new String[] {
+      "dn: cn=test child 7,cn=test children,cn=test parent 1,cn=test parents,cn=config",
+      "objectclass: top",
+      "objectclass: ds-cfg-test-child-dummy",
+      "cn: test child 7",
+      "ds-cfg-virtual-attribute-enabled: false",
+      "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",
+      "ds-cfg-backend-base-dn: cn=" + TEST_CONNECTION_HANDLER_NAME
+          + ", cn=connection handlers, cn=config"
+  };
+
   // Test LDIF.
   private static final String[] TEST_LDIF = new String[] {
       // Base entries.
@@ -198,6 +262,17 @@
   // JNDI LDAP context.
   private JNDIDirContextAdaptor adaptor = null;
 
+  // The saved test child configuration "aggregation-property"
+  // property definition.
+  private AggregationPropertyDefinition<ConnectionHandlerCfgClient, ConnectionHandlerCfg> aggregationPropertyDefinitionDefault = null;
+
+  // An aggregation where the target must be enabled if the source is
+  // enabled.
+  private AggregationPropertyDefinition<ConnectionHandlerCfgClient, ConnectionHandlerCfg> aggregationPropertyDefinitionTargetAndSourceMustBeEnabled = null;
+
+  // An aggregation where the target must be enabled.
+  private AggregationPropertyDefinition<ConnectionHandlerCfgClient, ConnectionHandlerCfg> aggregationPropertyDefinitionTargetMustBeEnabled = null;
+
 
 
   /**
@@ -215,6 +290,42 @@
 
     // Add test managed objects.
     TestCaseUtils.addEntries(TEST_LDIF);
+
+    // Save the aggregation property definition so that it can be
+    // replaced and restored later.
+    aggregationPropertyDefinitionDefault = TestChildCfgDefn.getInstance()
+        .getAggregationPropertyPropertyDefinition();
+
+    // Create the two test aggregation properties.
+    AggregationPropertyDefinition.Builder<ConnectionHandlerCfgClient, ConnectionHandlerCfg> builder;
+    TestChildCfgDefn d = TestChildCfgDefn.getInstance();
+    builder = AggregationPropertyDefinition.createBuilder(d,
+        "aggregation-property");
+    builder.setOption(PropertyOption.MULTI_VALUED);
+    builder.setAdministratorAction(new AdministratorAction(
+        AdministratorAction.Type.NONE, d, "aggregation-property"));
+    builder
+        .setDefaultBehaviorProvider(new UndefinedDefaultBehaviorProvider<String>());
+    builder.setParentPath(ManagedObjectPath.valueOf("/"));
+    builder.setRelationDefinition("connection-handler");
+    builder.setManagedObjectDefinition(ConnectionHandlerCfgDefn.getInstance());
+    builder.setTargetEnabledPropertyName("enabled");
+    aggregationPropertyDefinitionTargetMustBeEnabled = builder.getInstance();
+
+    builder = AggregationPropertyDefinition.createBuilder(d,
+        "aggregation-property");
+    builder.setOption(PropertyOption.MULTI_VALUED);
+    builder.setAdministratorAction(new AdministratorAction(
+        AdministratorAction.Type.NONE, d, "aggregation-property"));
+    builder
+        .setDefaultBehaviorProvider(new UndefinedDefaultBehaviorProvider<String>());
+    builder.setParentPath(ManagedObjectPath.valueOf("/"));
+    builder.setRelationDefinition("connection-handler");
+    builder.setManagedObjectDefinition(ConnectionHandlerCfgDefn.getInstance());
+    builder.setTargetEnabledPropertyName("enabled");
+    builder.setSourceEnabledPropertyName("mandatory-boolean-property");
+    aggregationPropertyDefinitionTargetAndSourceMustBeEnabled = builder
+        .getInstance();
   }
 
 
@@ -229,6 +340,9 @@
   public void tearDown() throws Exception {
     TestCfg.cleanup();
 
+    // Restore the test child aggregation definition.
+    TestCfg.addPropertyDefinition(aggregationPropertyDefinitionDefault);
+
     // Remove test entries.
     deleteSubtree("cn=test parents,cn=config");
   }
@@ -236,50 +350,6 @@
 
 
   /**
-   * Tests that aggregation contains no values when it
-   * contains does not contain any DN attribute values.
-   *
-   * @throws Exception
-   *           If the test unexpectedly fails.
-   */
-  @Test
-  public void testAggregationEmpty() throws Exception {
-    // Add the entry.
-    TestCaseUtils.addEntry(TEST_CHILD_1);
-
-    try {
-      TestParentCfg parent = getParent("test parent 1");
-      assertChild1(parent.getTestChild("test child 1"));
-    } finally {
-      deleteSubtree("cn=test child 1,cn=test children,cn=test parent 1,cn=test parents,cn=config");
-    }
-  }
-
-
-
-  /**
-   * Tests that aggregation contains single valid value when it
-   * contains a single valid DN attribute values.
-   *
-   * @throws Exception
-   *           If the test unexpectedly fails.
-   */
-  @Test
-  public void testAggregationSingle() throws Exception {
-    // Add the entry.
-    TestCaseUtils.addEntry(TEST_CHILD_2);
-
-    try {
-      TestParentCfg parent = getParent("test parent 1");
-      assertChild2(parent.getTestChild("test child 2"));
-    } finally {
-      deleteSubtree("cn=test child 2,cn=test children,cn=test parent 1,cn=test parents,cn=config");
-    }
-  }
-
-
-
-  /**
    * Tests that aggregation is rejected when the LDAP DN contains a
    * valid RDN but an invalid parent DN.
    *
@@ -329,28 +399,6 @@
 
 
   /**
-   * Tests that aggregation contains multiple valid values when it
-   * contains a multiple valid DN attribute values.
-   *
-   * @throws Exception
-   *           If the test unexpectedly fails.
-   */
-  @Test
-  public void testAggregationMultipleValues() throws Exception {
-    // Add the entry.
-    TestCaseUtils.addEntry(TEST_CHILD_4);
-
-    try {
-      TestParentCfg parent = getParent("test parent 1");
-      assertChild4(parent.getTestChild("test child 4"));
-    } finally {
-      deleteSubtree("cn=test child 4,cn=test children,cn=test parent 1,cn=test parents,cn=config");
-    }
-  }
-
-
-
-  /**
    * Tests that aggregation is rejected by a constraint violation when
    * the DN values are dangling.
    *
@@ -385,6 +433,476 @@
 
 
 
+  /**
+   * Tests that aggregation is rejected by a constraint violation when
+   * an enabled component references a disabled component and the
+   * referenced component must always be enabled.
+   *
+   * @throws Exception
+   *           If the test unexpectedly fails.
+   */
+  @Test
+  public void testAggregationDisabledReference1() throws Exception {
+    // Add the entry and the connection handler.
+    TestCaseUtils.addEntry(TEST_CHILD_6);
+    try {
+      createConnectionHandler(false);
+    } catch (Exception e) {
+      deleteSubtree(TEST_CHILD_6_DN);
+      throw e;
+    }
+
+    // Register the temporary aggregation definition.
+    TestCfg.removeConstraint(aggregationPropertyDefinitionDefault);
+    TestCfg
+        .addPropertyDefinition(aggregationPropertyDefinitionTargetMustBeEnabled);
+    TestCfg.addConstraint(aggregationPropertyDefinitionTargetMustBeEnabled);
+
+    try {
+      TestParentCfg parent = getParent("test parent 1");
+      parent.getTestChild("test child 6");
+      Assert
+          .fail("Unexpectedly added test child 6 when it had a disabled reference");
+    } catch (ConfigException e) {
+      // Check that we have a constraint violation as the cause.
+      Throwable cause = e.getCause();
+      if (cause instanceof ConstraintViolationException) {
+        ConstraintViolationException cve = (ConstraintViolationException) cause;
+        Collection<Message> causes = cve.getMessages();
+        Assert.assertEquals(causes.size(), 1);
+      } else {
+        // Got an unexpected cause.
+        throw e;
+      }
+    } finally {
+      // Put back the default aggregation definition.
+      TestCfg
+          .removeConstraint(aggregationPropertyDefinitionTargetMustBeEnabled);
+      TestCfg.addPropertyDefinition(aggregationPropertyDefinitionDefault);
+      TestCfg.addConstraint(aggregationPropertyDefinitionDefault);
+
+      try {
+        deleteSubtree(TEST_CHILD_6_DN);
+      } finally {
+        deleteConnectionHandler();
+      }
+    }
+  }
+
+
+
+  /**
+   * Tests that aggregation is rejected by a constraint violation when
+   * a disabled component references a disabled component and the
+   * referenced component must always be enabled.
+   *
+   * @throws Exception
+   *           If the test unexpectedly fails.
+   */
+  @Test
+  public void testAggregationDisabledReference2() throws Exception {
+    // Add the entry.
+    TestCaseUtils.addEntry(TEST_CHILD_7);
+    try {
+      createConnectionHandler(false);
+    } catch (Exception e) {
+      deleteSubtree(TEST_CHILD_7_DN);
+      throw e;
+    }
+
+    // Register the temporary aggregation definition.
+    TestCfg.removeConstraint(aggregationPropertyDefinitionDefault);
+    TestCfg
+        .addPropertyDefinition(aggregationPropertyDefinitionTargetMustBeEnabled);
+    TestCfg.addConstraint(aggregationPropertyDefinitionTargetMustBeEnabled);
+
+    try {
+      TestParentCfg parent = getParent("test parent 1");
+      parent.getTestChild("test child 7");
+      Assert
+          .fail("Unexpectedly added test child 7 when it had a disabled reference");
+    } catch (ConfigException e) {
+      // Check that we have a constraint violation as the cause.
+      Throwable cause = e.getCause();
+      if (cause instanceof ConstraintViolationException) {
+        ConstraintViolationException cve = (ConstraintViolationException) cause;
+        Collection<Message> causes = cve.getMessages();
+        Assert.assertEquals(causes.size(), 1);
+      } else {
+        // Got an unexpected cause.
+        throw e;
+      }
+    } finally {
+      // Put back the default aggregation definition.
+      TestCfg
+          .removeConstraint(aggregationPropertyDefinitionTargetMustBeEnabled);
+      TestCfg.addPropertyDefinition(aggregationPropertyDefinitionDefault);
+      TestCfg.addConstraint(aggregationPropertyDefinitionDefault);
+
+      try {
+        deleteSubtree(TEST_CHILD_7_DN);
+      } finally {
+        deleteConnectionHandler();
+      }
+    }
+  }
+
+
+
+  /**
+   * Tests that aggregation is rejected by a constraint violation when
+   * an enabled component references a disabled component and the
+   * referenced component must always be enabled when the referencing
+   * component is enabled.
+   *
+   * @throws Exception
+   *           If the test unexpectedly fails.
+   */
+  @Test
+  public void testAggregationDisabledReference3() throws Exception {
+    // Add the entry.
+    TestCaseUtils.addEntry(TEST_CHILD_6);
+    try {
+      createConnectionHandler(false);
+    } catch (Exception e) {
+      deleteSubtree(TEST_CHILD_6_DN);
+      throw e;
+    }
+
+    // Register the temporary aggregation definition.
+    TestCfg.removeConstraint(aggregationPropertyDefinitionDefault);
+    TestCfg
+        .addPropertyDefinition(aggregationPropertyDefinitionTargetAndSourceMustBeEnabled);
+    TestCfg
+        .addConstraint(aggregationPropertyDefinitionTargetAndSourceMustBeEnabled);
+
+    try {
+      TestParentCfg parent = getParent("test parent 1");
+      parent.getTestChild("test child 6");
+      Assert
+          .fail("Unexpectedly added test child 6 when it had a disabled reference");
+    } catch (ConfigException e) {
+      // Check that we have a constraint violation as the cause.
+      Throwable cause = e.getCause();
+      if (cause instanceof ConstraintViolationException) {
+        ConstraintViolationException cve = (ConstraintViolationException) cause;
+        Collection<Message> causes = cve.getMessages();
+        Assert.assertEquals(causes.size(), 1);
+      } else {
+        // Got an unexpected cause.
+        throw e;
+      }
+    } finally {
+      // Put back the default aggregation definition.
+      TestCfg
+          .removeConstraint(aggregationPropertyDefinitionTargetAndSourceMustBeEnabled);
+      TestCfg.addPropertyDefinition(aggregationPropertyDefinitionDefault);
+      TestCfg.addConstraint(aggregationPropertyDefinitionDefault);
+
+      try {
+        deleteSubtree(TEST_CHILD_6_DN);
+      } finally {
+        deleteConnectionHandler();
+      }
+    }
+  }
+
+
+
+  /**
+   * Tests that aggregation is allowed when a disabled component
+   * references a disabled component and the referenced component must
+   * always be enabled when the referencing component is enabled.
+   *
+   * @throws Exception
+   *           If the test unexpectedly fails.
+   */
+  @Test
+  public void testAggregationDisabledReference4() throws Exception {
+    // Add the entry.
+    TestCaseUtils.addEntry(TEST_CHILD_7);
+    try {
+      createConnectionHandler(false);
+    } catch (Exception e) {
+      deleteSubtree(TEST_CHILD_7_DN);
+      throw e;
+    }
+
+    // Register the temporary aggregation definition.
+    TestCfg.removeConstraint(aggregationPropertyDefinitionDefault);
+    TestCfg
+        .addPropertyDefinition(aggregationPropertyDefinitionTargetAndSourceMustBeEnabled);
+    TestCfg
+        .addConstraint(aggregationPropertyDefinitionTargetAndSourceMustBeEnabled);
+
+    try {
+      TestParentCfg parent = getParent("test parent 1");
+      parent.getTestChild("test child 7");
+    } finally {
+      // Put back the default aggregation definition.
+      TestCfg
+          .removeConstraint(aggregationPropertyDefinitionTargetAndSourceMustBeEnabled);
+      TestCfg.addPropertyDefinition(aggregationPropertyDefinitionDefault);
+      TestCfg.addConstraint(aggregationPropertyDefinitionDefault);
+
+      try {
+        deleteSubtree(TEST_CHILD_7_DN);
+      } finally {
+        deleteConnectionHandler();
+      }
+    }
+  }
+
+
+
+  /**
+   * Tests that aggregation contains no values when it contains does
+   * not contain any DN attribute values.
+   *
+   * @throws Exception
+   *           If the test unexpectedly fails.
+   */
+  @Test
+  public void testAggregationEmpty() throws Exception {
+    // Add the entry.
+    TestCaseUtils.addEntry(TEST_CHILD_1);
+
+    try {
+      TestParentCfg parent = getParent("test parent 1");
+      assertChild1(parent.getTestChild("test child 1"));
+    } finally {
+      deleteSubtree("cn=test child 1,cn=test children,cn=test parent 1,cn=test parents,cn=config");
+    }
+  }
+
+
+
+  /**
+   * Tests that aggregation contains multiple valid values when it
+   * contains a multiple valid DN attribute values.
+   *
+   * @throws Exception
+   *           If the test unexpectedly fails.
+   */
+  @Test
+  public void testAggregationMultipleValues() throws Exception {
+    // Add the entry.
+    TestCaseUtils.addEntry(TEST_CHILD_4);
+
+    try {
+      TestParentCfg parent = getParent("test parent 1");
+      assertChild4(parent.getTestChild("test child 4"));
+    } finally {
+      deleteSubtree("cn=test child 4,cn=test children,cn=test parent 1,cn=test parents,cn=config");
+    }
+  }
+
+
+
+  /**
+   * Tests that aggregation contains single valid value when it
+   * contains a single valid DN attribute values.
+   *
+   * @throws Exception
+   *           If the test unexpectedly fails.
+   */
+  @Test
+  public void testAggregationSingle() throws Exception {
+    // Add the entry.
+    TestCaseUtils.addEntry(TEST_CHILD_2);
+
+    try {
+      TestParentCfg parent = getParent("test parent 1");
+      assertChild2(parent.getTestChild("test child 2"));
+    } finally {
+      deleteSubtree("cn=test child 2,cn=test children,cn=test parent 1,cn=test parents,cn=config");
+    }
+  }
+
+
+
+  /**
+   * Tests that it is impossible to delete a referenced component when
+   * the referenced component must always exist regardless of whether
+   * the referencing component is enabled or not.
+   *
+   * @throws Exception
+   *           If the test unexpectedly fails.
+   */
+  @Test
+  public void testCannotDeleteReferencedComponent() throws Exception {
+    // Add the entry.
+    TestCaseUtils.addEntry(TEST_CHILD_7);
+    try {
+      createConnectionHandler(true);
+    } catch (Exception e) {
+      deleteSubtree(TEST_CHILD_7_DN);
+      throw e;
+    }
+
+    // Register the temporary aggregation definition.
+    TestCfg.removeConstraint(aggregationPropertyDefinitionDefault);
+    TestCfg
+        .addPropertyDefinition(aggregationPropertyDefinitionTargetMustBeEnabled);
+    TestCfg.addConstraint(aggregationPropertyDefinitionTargetMustBeEnabled);
+
+    ConfigurationDeleteListener<TestChildCfg> dl = new DummyDeleteListener();
+    ConfigurationChangeListener<TestChildCfg> cl = new DummyChangeListener();
+    try {
+      // Retrieve the parent and child managed objects and register
+      // delete and change listeners respectively in order to trigger
+      // the constraint call-backs.
+      TestParentCfg parent = getParent("test parent 1");
+      parent.addTestChildDeleteListener(dl);
+
+      TestChildCfg child = parent.getTestChild("test child 7");
+      child.addChangeListener(cl);
+
+      // Now attempt to delete the referenced connection handler.
+      // This should fail.
+      try {
+        deleteConnectionHandler();
+        Assert.fail("Successfully deleted a referenced component");
+      } catch (OperationRejectedException e) {
+        // This is the expected exception - do nothing.
+      }
+    } finally {
+      try {
+        deleteSubtree(TEST_CHILD_7_DN);
+      } finally {
+        try {
+          deleteConnectionHandler();
+        } catch (ManagedObjectNotFoundException e) {
+          // Ignore as it may have been deleted already.
+        } finally {
+          // Remove the temporary delete listener.
+          TestParentCfg parent = getParent("test parent 1");
+          parent.removeTestChildDeleteListener(dl);
+
+          // Put back the default aggregation definition.
+          TestCfg
+              .removeConstraint(aggregationPropertyDefinitionTargetAndSourceMustBeEnabled);
+          TestCfg.addPropertyDefinition(aggregationPropertyDefinitionDefault);
+          TestCfg.addConstraint(aggregationPropertyDefinitionDefault);
+        }
+      }
+    }
+  }
+
+
+
+  /**
+   * Tests that it is impossible to disable a referenced component
+   * when the referenced component must always be enabled regardless
+   * of whether the referencing component is enabled or not.
+   *
+   * @throws Exception
+   *           If the test unexpectedly fails.
+   */
+  @Test
+  public void testCannotDisableReferencedComponent() throws Exception {
+    // Add the entry.
+    TestCaseUtils.addEntry(TEST_CHILD_7);
+    try {
+      createConnectionHandler(true);
+    } catch (Exception e) {
+      deleteSubtree(TEST_CHILD_7_DN);
+      throw e;
+    }
+
+    // Register the temporary aggregation definition.
+    TestCfg.removeConstraint(aggregationPropertyDefinitionDefault);
+    TestCfg
+        .addPropertyDefinition(aggregationPropertyDefinitionTargetMustBeEnabled);
+    TestCfg.addConstraint(aggregationPropertyDefinitionTargetMustBeEnabled);
+
+    ConfigurationDeleteListener<TestChildCfg> dl = new DummyDeleteListener();
+    ConfigurationChangeListener<TestChildCfg> cl = new DummyChangeListener();
+    try {
+      // Retrieve the parent and child managed objects and register
+      // delete and change listeners respectively in order to trigger
+      // the constraint call-backs.
+      TestParentCfg parent = getParent("test parent 1");
+      parent.addTestChildDeleteListener(dl);
+
+      TestChildCfg child = parent.getTestChild("test child 7");
+      child.addChangeListener(cl);
+
+      // Now attempt to disable the referenced connection handler.
+      // This should fail.
+      try {
+        RootCfgClient root = TestCaseUtils.getRootConfiguration();
+        ConnectionHandlerCfgClient client = root
+            .getConnectionHandler(TEST_CONNECTION_HANDLER_NAME);
+        client.setEnabled(false);
+        client.commit();
+        Assert.fail("Successfully disabled a referenced component");
+      } catch (OperationRejectedException e) {
+        // This is the expected exception - do nothing.
+      }
+    } finally {
+      try {
+        deleteSubtree(TEST_CHILD_7_DN);
+      } finally {
+        try {
+          deleteConnectionHandler();
+        } finally {
+          // Remove the temporary delete listener.
+          TestParentCfg parent = getParent("test parent 1");
+          parent.removeTestChildDeleteListener(dl);
+
+          // Put back the default aggregation definition.
+          TestCfg
+              .removeConstraint(aggregationPropertyDefinitionTargetAndSourceMustBeEnabled);
+          TestCfg.addPropertyDefinition(aggregationPropertyDefinitionDefault);
+          TestCfg.addConstraint(aggregationPropertyDefinitionDefault);
+        }
+      }
+    }
+  }
+
+
+
+  // Assert that the values of child 1 are correct.
+  private void assertChild1(TestChildCfg child) {
+    Assert.assertEquals(child.getMandatoryClassProperty(),
+        "org.opends.server.extensions.UserDefinedVirtualAttributeProvider");
+    Assert.assertEquals(child.getMandatoryReadOnlyAttributeTypeProperty(),
+        DirectoryServer.getAttributeType("description"));
+    assertSetEquals(child.getAggregationProperty(), new String[0]);
+  }
+
+
+
+  // Assert that the values of child 2 are correct.
+  private void assertChild2(TestChildCfg child) {
+    Assert.assertEquals(child.getMandatoryClassProperty(),
+        "org.opends.server.extensions.UserDefinedVirtualAttributeProvider");
+    Assert.assertEquals(child.getMandatoryReadOnlyAttributeTypeProperty(),
+        DirectoryServer.getAttributeType("description"));
+
+    // Test normalization.
+    assertSetEquals(child.getAggregationProperty(), "LDAP Connection Handler");
+    assertSetEquals(child.getAggregationProperty(),
+        "  LDAP   Connection  Handler ");
+    assertSetEquals(child.getAggregationProperty(),
+        "  ldap connection HANDLER ");
+  }
+
+
+
+  // Assert that the values of child 4 are correct.
+  private void assertChild4(TestChildCfg child) {
+    Assert.assertEquals(child.getMandatoryClassProperty(),
+        "org.opends.server.extensions.UserDefinedVirtualAttributeProvider");
+    Assert.assertEquals(child.getMandatoryReadOnlyAttributeTypeProperty(),
+        DirectoryServer.getAttributeType("description"));
+    assertSetEquals(child.getAggregationProperty(), "LDAPS Connection Handler",
+        "LDAP Connection Handler");
+  }
+
+
+
   // Asserts that the actual set of DNs contains the expected values.
   private void assertSetEquals(SortedSet<String> actual, String... expected) {
     SortedSet<String> values = new TreeSet<String>(TestChildCfgDefn
@@ -399,6 +917,31 @@
 
 
 
+  // Creates a test connection handler for testing.
+  private void createConnectionHandler(boolean enabled) throws Exception {
+    ServerSocket freeSocket = TestCaseUtils.bindFreePort();
+    int freePort = freeSocket.getLocalPort();
+    freeSocket.close();
+
+    RootCfgClient root = TestCaseUtils.getRootConfiguration();
+    LDAPConnectionHandlerCfgClient client = root.createConnectionHandler(
+        LDAPConnectionHandlerCfgDefn.getInstance(),
+        TEST_CONNECTION_HANDLER_NAME, null);
+    client.setEnabled(enabled);
+    client.setListenPort(freePort);
+    client.commit();
+  }
+
+
+
+  // Deletes the test connection handler after testing.
+  private void deleteConnectionHandler() throws Exception {
+    RootCfgClient root = TestCaseUtils.getRootConfiguration();
+    root.removeConnectionHandler(TEST_CONNECTION_HANDLER_NAME);
+  }
+
+
+
   // Deletes the named sub-tree.
   private void deleteSubtree(String dn) throws Exception {
     getAdaptor().deleteSubtree(new LdapName(dn));

--
Gitblit v1.10.0