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

matthew_swift
12.37.2007 2ba8e81b3d42cdacb4bf15d77ca681660595ee46
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"
 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"
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);
  }
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"
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
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));