/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
 * or http://forgerock.org/license/CDDLv1.0.html.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at legal-notices/CDDLv1_0.txt.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2007-2008 Sun Microsystems, Inc.
 *      Portions Copyright 2013-2015 ForgeRock AS
 */
package org.opends.server.admin.server;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;

import javax.naming.ldap.LdapName;

import org.forgerock.i18n.LocalizableMessage;
import org.opends.server.TestCaseUtils;
import org.opends.server.admin.*;
import org.opends.server.admin.client.OperationRejectedException;
import org.opends.server.admin.client.ldap.JNDIDirContextAdaptor;
import org.opends.server.admin.condition.Conditions;
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.LDAPConnectionHandlerCfgDefn;
import org.opends.server.admin.std.server.ConnectionHandlerCfg;
import org.opends.server.admin.std.server.RootCfg;
import org.forgerock.opendj.config.server.ConfigException;
import org.opends.server.core.DirectoryServer;
import org.forgerock.opendj.config.server.ConfigChangeResult;
import org.testng.Assert;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

/**
 * Test cases for aggregations on the server-side.
 */
@Test(sequential = true)
public final class AggregationServerTest extends AdminTestCase {

  /**
   * Dummy change listener for triggering change constraint
   * call-backs.
   */
  private static final class DummyChangeListener implements
      ConfigurationChangeListener<TestChildCfg> {

    /** {@inheritDoc} */
    @Override
    public ConfigChangeResult applyConfigurationChange(
        TestChildCfg configuration) {
      return new ConfigChangeResult();
    }



    /** {@inheritDoc} */
    @Override
    public boolean isConfigurationChangeAcceptable(TestChildCfg configuration,
        List<LocalizableMessage> unacceptableReasons) {
      return true;
    }
  }



  /**
   * Dummy delete listener for triggering delete constraint
   * call-backs.
   */
  private static final class DummyDeleteListener implements
      ConfigurationDeleteListener<TestChildCfg> {

    /** {@inheritDoc} */
    @Override
    public ConfigChangeResult applyConfigurationDelete(
        TestChildCfg configuration) {
      return new ConfigChangeResult();
    }



    /** {@inheritDoc} */
    @Override
    public boolean isConfigurationDeleteAcceptable(TestChildCfg configuration,
        List<LocalizableMessage> unacceptableReasons) {
      return true;
    }
  }

  private static final String TEST_CHILD_7_DN = "cn=test child 7,cn=test children,cn=test parent 1,cn=test parents,cn=config";

  private static final String TEST_CHILD_6_DN = "cn=test child 6,cn=test children,cn=test parent 1,cn=test parents,cn=config";

  /** The name of the test connection handler. */
  private static final String TEST_CONNECTION_HANDLER_NAME = "Test Connection Handler";

  /** Test child 1 LDIF. */
  private static final String[] TEST_CHILD_1 = new String[] {
      "dn: cn=test child 1,cn=test children,cn=test parent 1,cn=test parents,cn=config",
      "objectclass: top",
      "objectclass: ds-cfg-test-child-dummy",
      "cn: test child 1",
      "ds-cfg-enabled: true",
      "ds-cfg-java-class: org.opends.server.extensions.UserDefinedVirtualAttributeProvider",
      "ds-cfg-attribute-type: description",
      "ds-cfg-conflict-behavior: virtual-overrides-real"
  };

  /** Test child 2 LDIF. */
  private static final String[] TEST_CHILD_2 = new String[] {
      "dn: cn=test child 2,cn=test children,cn=test parent 1,cn=test parents,cn=config",
      "objectclass: top",
      "objectclass: ds-cfg-test-child-dummy",
      "cn: test child 2",
      "ds-cfg-enabled: true",
      "ds-cfg-java-class: org.opends.server.extensions.UserDefinedVirtualAttributeProvider",
      "ds-cfg-attribute-type: description",
      "ds-cfg-conflict-behavior: virtual-overrides-real",
      "ds-cfg-rotation-policy: cn=LDAP Connection Handler, cn=connection handlers, cn=config"
  };

  /** Test child 3 LDIF (invalid reference). */
  private static final String[] TEST_CHILD_3 = new String[] {
      "dn: cn=test child 3,cn=test children,cn=test parent 1,cn=test parents,cn=config",
      "objectclass: top",
      "objectclass: ds-cfg-test-child-dummy",
      "cn: test child 3",
      "ds-cfg-enabled: true",
      "ds-cfg-java-class: org.opends.server.extensions.UserDefinedVirtualAttributeProvider",
      "ds-cfg-attribute-type: description",
      "ds-cfg-conflict-behavior: virtual-overrides-real",
      "ds-cfg-rotation-policy: cn=LDAP Connection Handler, cn=bad rdn, cn=config"
  };

  /** Test child 4 LDIF. */
  private static final String[] TEST_CHILD_4 = new String[] {
      "dn: cn=test child 4,cn=test children,cn=test parent 1,cn=test parents,cn=config",
      "objectclass: top",
      "objectclass: ds-cfg-test-child-dummy",
      "cn: test child 4",
      "ds-cfg-enabled: true",
      "ds-cfg-java-class: org.opends.server.extensions.UserDefinedVirtualAttributeProvider",
      "ds-cfg-attribute-type: description",
      "ds-cfg-conflict-behavior: virtual-overrides-real",
      "ds-cfg-rotation-policy: cn=LDAP Connection Handler, cn=connection handlers, cn=config",
      "ds-cfg-rotation-policy: cn=LDAPS Connection Handler, cn=connection handlers, cn=config"
  };

  /** Test child 5 LDIF. */
  private static final String[] TEST_CHILD_5 = new String[] {
      "dn: cn=test child 5,cn=test children,cn=test parent 1,cn=test parents,cn=config",
      "objectclass: top",
      "objectclass: ds-cfg-test-child-dummy",
      "cn: test child 5",
      "ds-cfg-enabled: true",
      "ds-cfg-java-class: org.opends.server.extensions.UserDefinedVirtualAttributeProvider",
      "ds-cfg-attribute-type: description",
      "ds-cfg-conflict-behavior: virtual-overrides-real",
      "ds-cfg-rotation-policy: cn=BAD Connection Handler 1, cn=connection handlers, cn=config",
      "ds-cfg-rotation-policy: cn=BAD Connection Handler 2, cn=connection handlers, cn=config",
      "ds-cfg-rotation-policy: cn=LDAP Connection Handler, cn=connection handlers, cn=config"
  };

  /** Test child 6 LDIF. */
  private static final String[] TEST_CHILD_6 = new String[] {
      "dn: cn=test child 6,cn=test children,cn=test parent 1,cn=test parents,cn=config",
      "objectclass: top",
      "objectclass: ds-cfg-test-child-dummy",
      "cn: test child 6",
      "ds-cfg-enabled: true",
      "ds-cfg-java-class: org.opends.server.extensions.UserDefinedVirtualAttributeProvider",
      "ds-cfg-attribute-type: description",
      "ds-cfg-conflict-behavior: virtual-overrides-real",
      "ds-cfg-rotation-policy: cn=" + TEST_CONNECTION_HANDLER_NAME
          + ", cn=connection handlers, cn=config"
  };

  /** Test child 7 LDIF. */
  private static final String[] TEST_CHILD_7 = new String[] {
      "dn: cn=test child 7,cn=test children,cn=test parent 1,cn=test parents,cn=config",
      "objectclass: top",
      "objectclass: ds-cfg-test-child-dummy",
      "cn: test child 7",
      "ds-cfg-enabled: false",
      "ds-cfg-java-class: org.opends.server.extensions.UserDefinedVirtualAttributeProvider",
      "ds-cfg-attribute-type: description",
      "ds-cfg-conflict-behavior: virtual-overrides-real",
      "ds-cfg-rotation-policy: cn=" + TEST_CONNECTION_HANDLER_NAME
          + ", cn=connection handlers, cn=config"
  };

  /** Test LDIF. */
  private static final String[] TEST_LDIF = new String[] {
      // Base entries.
      "dn: cn=test parents,cn=config",
      "objectclass: top",
      "objectclass: ds-cfg-branch",
      "cn: test parents",
      "",
      // Parent 1.
      "dn: cn=test parent 1,cn=test parents,cn=config",
      "objectclass: top",
      "objectclass: ds-cfg-test-parent-dummy",
      "cn: test parent 1",
      "ds-cfg-enabled: true",
      "ds-cfg-java-class: org.opends.server.extensions.UserDefinedVirtualAttributeProvider",
      "ds-cfg-attribute-type: description",
      "ds-cfg-conflict-behavior: virtual-overrides-real",
      "",
      // Child base entries.
      "dn:cn=test children,cn=test parent 1,cn=test parents,cn=config",
      "objectclass: top",
      "objectclass: ds-cfg-branch",
      "cn: test children",
      ""
  };

  /** JNDI LDAP context. */
  private JNDIDirContextAdaptor adaptor;

  /**
   * The saved test child configuration "aggregation-property" property
   * definition.
   */
  private AggregationPropertyDefinition<ConnectionHandlerCfgClient, ConnectionHandlerCfg> aggregationPropertyDefinitionDefault;

  /**
   * An aggregation where the target must be enabled if the source is enabled.
   */
  private AggregationPropertyDefinition<ConnectionHandlerCfgClient, ConnectionHandlerCfg> aggregationPropertyDefinitionTargetAndSourceMustBeEnabled;

  /** An aggregation where the target must be enabled. */
  private AggregationPropertyDefinition<ConnectionHandlerCfgClient, ConnectionHandlerCfg> aggregationPropertyDefinitionTargetMustBeEnabled;



  /**
   * Sets up tests
   *
   * @throws Exception
   *           If the server could not be initialized.
   */
  @BeforeClass
  public void setUp() throws Exception {
    // This test suite depends on having the schema available, so
    // we'll start the server.
    TestCaseUtils.startServer();
    TestCfg.setUp();

    // Add test managed objects.
    TestCaseUtils.addEntries(TEST_LDIF);

    // Save the aggregation property definition so that it can be
    // replaced and restored later.
    aggregationPropertyDefinitionDefault = TestChildCfgDefn.getInstance()
        .getAggregationPropertyPropertyDefinition();

    // Create the two test aggregation properties.
    AggregationPropertyDefinition.Builder<ConnectionHandlerCfgClient, ConnectionHandlerCfg> builder;
    TestChildCfgDefn d = TestChildCfgDefn.getInstance();
    builder = AggregationPropertyDefinition.createBuilder(d,
        "aggregation-property");
    builder.setOption(PropertyOption.MULTI_VALUED);
    builder.setAdministratorAction(new AdministratorAction(
        AdministratorAction.Type.NONE, d, "aggregation-property"));
    builder
        .setDefaultBehaviorProvider(new UndefinedDefaultBehaviorProvider<String>());
    builder.setParentPath("/");
    builder.setRelationDefinition("connection-handler");
    builder.setTargetIsEnabledCondition(Conditions.contains("enabled", "true"));
    aggregationPropertyDefinitionTargetMustBeEnabled = builder.getInstance();
    TestCfg
        .initializePropertyDefinition(aggregationPropertyDefinitionTargetMustBeEnabled);

    builder = AggregationPropertyDefinition.createBuilder(d,
        "aggregation-property");
    builder.setOption(PropertyOption.MULTI_VALUED);
    builder.setAdministratorAction(new AdministratorAction(
        AdministratorAction.Type.NONE, d, "aggregation-property"));
    builder
        .setDefaultBehaviorProvider(new UndefinedDefaultBehaviorProvider<String>());
    builder.setParentPath("/");
    builder.setRelationDefinition("connection-handler");
    builder.setTargetIsEnabledCondition(Conditions.contains("enabled", "true"));
    builder.setTargetNeedsEnablingCondition(Conditions.contains(
        "mandatory-boolean-property", "true"));
    aggregationPropertyDefinitionTargetAndSourceMustBeEnabled = builder
        .getInstance();
    TestCfg
        .initializePropertyDefinition(aggregationPropertyDefinitionTargetAndSourceMustBeEnabled);
  }



  /**
   * Tears down test environment.
   *
   * @throws Exception
   *           If the test entries could not be removed.
   */
  @AfterClass
  public void tearDown() throws Exception {
    TestCfg.cleanup();

    // Restore the test child aggregation definition.
    TestCfg.addPropertyDefinition(aggregationPropertyDefinitionDefault);

    // Remove test entries.
    deleteSubtree("cn=test parents,cn=config");
  }



  /**
   * Tests that aggregation is rejected when the LDAP DN contains a
   * valid RDN but an invalid parent DN.
   *
   * @throws Exception
   *           If the test unexpectedly fails.
   */
  @Test
  public void testAggregationBadBaseDN() throws Exception {
    // Add the entry.
    TestCaseUtils.addEntry(TEST_CHILD_3);

    try {
      TestParentCfg parent = getParent("test parent 1");
      parent.getTestChild("test child 3");
      Assert
          .fail("Unexpectedly added test child 3 when it had a bad aggregation value");
    } catch (ConfigException e) {
      // Check that we have a decoding exception as the cause and
      // there was only one cause the illegal property value.
      Throwable cause = e.getCause();
      if (cause instanceof ServerManagedObjectDecodingException) {
        ServerManagedObjectDecodingException de = (ServerManagedObjectDecodingException) cause;

        Collection<PropertyException> causes = de.getCauses();
        Assert.assertEquals(causes.size(), 1);

        cause = causes.iterator().next();
        if (cause instanceof PropertyException) {
          PropertyException pe = (PropertyException) cause;
          Assert.assertEquals(pe.getPropertyDefinition(), TestChildCfgDefn
              .getInstance().getAggregationPropertyPropertyDefinition());
        } else {
          // Got an unexpected cause.
          throw e;
        }
      } else {
        // Got an unexpected cause.
        throw e;
      }
    } finally {
      deleteSubtree("cn=test child 3,cn=test children,cn=test parent 1,cn=test parents,cn=config");
    }
  }



  /**
   * Tests that aggregation is rejected by a constraint violation when
   * the DN values are dangling.
   *
   * @throws Exception
   *           If the test unexpectedly fails.
   */
  @Test
  public void testAggregationDanglingReference() throws Exception {
    // Add the entry.
    TestCaseUtils.addEntry(TEST_CHILD_5);

    try {
      TestParentCfg parent = getParent("test parent 1");
      parent.getTestChild("test child 5");
      Assert
          .fail("Unexpectedly added test child 5 when it had a dangling reference");
    } catch (ConfigException e) {
      // Check that we have a constraint violation as the cause.
      Throwable cause = e.getCause();
      if (cause instanceof ConstraintViolationException) {
        ConstraintViolationException cve = (ConstraintViolationException) cause;
        Collection<LocalizableMessage> causes = cve.getMessages();
        Assert.assertEquals(causes.size(), 2);
      } else {
        // Got an unexpected cause.
        throw e;
      }
    } finally {
      deleteSubtree("cn=test child 5,cn=test children,cn=test parent 1,cn=test parents,cn=config");
    }
  }



  /**
   * Tests that aggregation is rejected by a constraint violation when
   * an enabled component references a disabled component and the
   * referenced component must always be enabled.
   *
   * @throws Exception
   *           If the test unexpectedly fails.
   */
  @Test
  public void testAggregationDisabledReference1() throws Exception {
    // Add the entry and the connection handler.
    TestCaseUtils.addEntry(TEST_CHILD_6);
    try {
      createConnectionHandler(false);
    } catch (Exception e) {
      deleteSubtree(TEST_CHILD_6_DN);
      throw e;
    }

    // Register the temporary aggregation definition.
    TestCfg.removeConstraint(aggregationPropertyDefinitionDefault
        .getSourceConstraint());
    TestCfg
        .addPropertyDefinition(aggregationPropertyDefinitionTargetMustBeEnabled);
    TestCfg.addConstraint(aggregationPropertyDefinitionTargetMustBeEnabled
        .getSourceConstraint());

    try {
      TestParentCfg parent = getParent("test parent 1");
      parent.getTestChild("test child 6");
      Assert
          .fail("Unexpectedly added test child 6 when it had a disabled reference");
    } catch (ConfigException e) {
      // Check that we have a constraint violation as the cause.
      Throwable cause = e.getCause();
      if (cause instanceof ConstraintViolationException) {
        ConstraintViolationException cve = (ConstraintViolationException) cause;
        Collection<LocalizableMessage> causes = cve.getMessages();
        Assert.assertEquals(causes.size(), 1);
      } else {
        // Got an unexpected cause.
        throw e;
      }
    } finally {
      // Put back the default aggregation definition.
      TestCfg.removeConstraint(aggregationPropertyDefinitionTargetMustBeEnabled
          .getSourceConstraint());
      TestCfg.addPropertyDefinition(aggregationPropertyDefinitionDefault);
      TestCfg.addConstraint(aggregationPropertyDefinitionDefault
          .getSourceConstraint());

      try {
        deleteSubtree(TEST_CHILD_6_DN);
      } finally {
        deleteConnectionHandler();
      }
    }
  }



  /**
   * Tests that aggregation is rejected by a constraint violation when
   * a disabled component references a disabled component and the
   * referenced component must always be enabled.
   *
   * @throws Exception
   *           If the test unexpectedly fails.
   */
  @Test
  public void testAggregationDisabledReference2() throws Exception {
    // Add the entry.
    TestCaseUtils.addEntry(TEST_CHILD_7);
    try {
      createConnectionHandler(false);
    } catch (Exception e) {
      deleteSubtree(TEST_CHILD_7_DN);
      throw e;
    }

    // Register the temporary aggregation definition.
    TestCfg.removeConstraint(aggregationPropertyDefinitionDefault
        .getSourceConstraint());
    TestCfg
        .addPropertyDefinition(aggregationPropertyDefinitionTargetMustBeEnabled);
    TestCfg.addConstraint(aggregationPropertyDefinitionTargetMustBeEnabled
        .getSourceConstraint());

    try {
      TestParentCfg parent = getParent("test parent 1");
      parent.getTestChild("test child 7");
      Assert
          .fail("Unexpectedly added test child 7 when it had a disabled reference");
    } catch (ConfigException e) {
      // Check that we have a constraint violation as the cause.
      Throwable cause = e.getCause();
      if (cause instanceof ConstraintViolationException) {
        ConstraintViolationException cve = (ConstraintViolationException) cause;
        Collection<LocalizableMessage> causes = cve.getMessages();
        Assert.assertEquals(causes.size(), 1);
      } else {
        // Got an unexpected cause.
        throw e;
      }
    } finally {
      // Put back the default aggregation definition.
      TestCfg.removeConstraint(aggregationPropertyDefinitionTargetMustBeEnabled
          .getSourceConstraint());
      TestCfg.addPropertyDefinition(aggregationPropertyDefinitionDefault);
      TestCfg.addConstraint(aggregationPropertyDefinitionDefault
          .getSourceConstraint());

      try {
        deleteSubtree(TEST_CHILD_7_DN);
      } finally {
        deleteConnectionHandler();
      }
    }
  }



  /**
   * Tests that aggregation is rejected by a constraint violation when
   * an enabled component references a disabled component and the
   * referenced component must always be enabled when the referencing
   * component is enabled.
   *
   * @throws Exception
   *           If the test unexpectedly fails.
   */
  @Test
  public void testAggregationDisabledReference3() throws Exception {
    // Add the entry.
    TestCaseUtils.addEntry(TEST_CHILD_6);
    try {
      createConnectionHandler(false);
    } catch (Exception e) {
      deleteSubtree(TEST_CHILD_6_DN);
      throw e;
    }

    // Register the temporary aggregation definition.
    TestCfg.removeConstraint(aggregationPropertyDefinitionDefault
        .getSourceConstraint());
    TestCfg
        .addPropertyDefinition(aggregationPropertyDefinitionTargetAndSourceMustBeEnabled);
    TestCfg
        .addConstraint(aggregationPropertyDefinitionTargetAndSourceMustBeEnabled
            .getSourceConstraint());

    try {
      TestParentCfg parent = getParent("test parent 1");
      parent.getTestChild("test child 6");
      Assert
          .fail("Unexpectedly added test child 6 when it had a disabled reference");
    } catch (ConfigException e) {
      // Check that we have a constraint violation as the cause.
      Throwable cause = e.getCause();
      if (cause instanceof ConstraintViolationException) {
        ConstraintViolationException cve = (ConstraintViolationException) cause;
        Collection<LocalizableMessage> causes = cve.getMessages();
        Assert.assertEquals(causes.size(), 1);
      } else {
        // Got an unexpected cause.
        throw e;
      }
    } finally {
      // Put back the default aggregation definition.
      TestCfg
          .removeConstraint(aggregationPropertyDefinitionTargetAndSourceMustBeEnabled
              .getSourceConstraint());
      TestCfg.addPropertyDefinition(aggregationPropertyDefinitionDefault);
      TestCfg.addConstraint(aggregationPropertyDefinitionDefault
          .getSourceConstraint());

      try {
        deleteSubtree(TEST_CHILD_6_DN);
      } finally {
        deleteConnectionHandler();
      }
    }
  }



  /**
   * Tests that aggregation is allowed when a disabled component
   * references a disabled component and the referenced component must
   * always be enabled when the referencing component is enabled.
   *
   * @throws Exception
   *           If the test unexpectedly fails.
   */
  @Test
  public void testAggregationDisabledReference4() throws Exception {
    // Add the entry.
    TestCaseUtils.addEntry(TEST_CHILD_7);
    try {
      createConnectionHandler(false);
    } catch (Exception e) {
      deleteSubtree(TEST_CHILD_7_DN);
      throw e;
    }

    // Register the temporary aggregation definition.
    TestCfg.removeConstraint(aggregationPropertyDefinitionDefault
        .getSourceConstraint());
    TestCfg
        .addPropertyDefinition(aggregationPropertyDefinitionTargetAndSourceMustBeEnabled);
    TestCfg
        .addConstraint(aggregationPropertyDefinitionTargetAndSourceMustBeEnabled
            .getSourceConstraint());

    try {
      TestParentCfg parent = getParent("test parent 1");
      parent.getTestChild("test child 7");
    } finally {
      // Put back the default aggregation definition.
      TestCfg
          .removeConstraint(aggregationPropertyDefinitionTargetAndSourceMustBeEnabled
              .getSourceConstraint());
      TestCfg.addPropertyDefinition(aggregationPropertyDefinitionDefault);
      TestCfg.addConstraint(aggregationPropertyDefinitionDefault
          .getSourceConstraint());

      try {
        deleteSubtree(TEST_CHILD_7_DN);
      } finally {
        deleteConnectionHandler();
      }
    }
  }



  /**
   * Tests that aggregation contains no values when it contains does
   * not contain any DN attribute values.
   *
   * @throws Exception
   *           If the test unexpectedly fails.
   */
  @Test
  public void testAggregationEmpty() throws Exception {
    // Add the entry.
    TestCaseUtils.addEntry(TEST_CHILD_1);

    try {
      TestParentCfg parent = getParent("test parent 1");
      assertChild1(parent.getTestChild("test child 1"));
    } finally {
      deleteSubtree("cn=test child 1,cn=test children,cn=test parent 1,cn=test parents,cn=config");
    }
  }



  /**
   * Tests that aggregation contains multiple valid values when it
   * contains a multiple valid DN attribute values.
   *
   * @throws Exception
   *           If the test unexpectedly fails.
   */
  @Test
  public void testAggregationMultipleValues() throws Exception {
    // Add the entry.
    TestCaseUtils.addEntry(TEST_CHILD_4);

    try {
      TestParentCfg parent = getParent("test parent 1");
      assertChild4(parent.getTestChild("test child 4"));
    } finally {
      deleteSubtree("cn=test child 4,cn=test children,cn=test parent 1,cn=test parents,cn=config");
    }
  }



  /**
   * Tests that aggregation contains single valid value when it
   * contains a single valid DN attribute values.
   *
   * @throws Exception
   *           If the test unexpectedly fails.
   */
  @Test
  public void testAggregationSingle() throws Exception {
    // Add the entry.
    TestCaseUtils.addEntry(TEST_CHILD_2);

    try {
      TestParentCfg parent = getParent("test parent 1");
      assertChild2(parent.getTestChild("test child 2"));
    } finally {
      deleteSubtree("cn=test child 2,cn=test children,cn=test parent 1,cn=test parents,cn=config");
    }
  }



  /**
   * Tests that it is impossible to delete a referenced component when
   * the referenced component must always exist regardless of whether
   * the referencing component is enabled or not.
   *
   * @throws Exception
   *           If the test unexpectedly fails.
   */
  @Test
  public void testCannotDeleteReferencedComponent() throws Exception {
    // Add the entry.
    TestCaseUtils.addEntry(TEST_CHILD_7);
    try {
      createConnectionHandler(true);
    } catch (Exception e) {
      deleteSubtree(TEST_CHILD_7_DN);
      throw e;
    }

    // Register the temporary aggregation definition.
    TestCfg.removeConstraint(aggregationPropertyDefinitionDefault
        .getSourceConstraint());
    TestCfg
        .addPropertyDefinition(aggregationPropertyDefinitionTargetMustBeEnabled);
    TestCfg.addConstraint(aggregationPropertyDefinitionTargetMustBeEnabled
        .getSourceConstraint());

    ConfigurationDeleteListener<TestChildCfg> dl = new DummyDeleteListener();
    ConfigurationChangeListener<TestChildCfg> cl = new DummyChangeListener();
    try {
      // Retrieve the parent and child managed objects and register
      // delete and change listeners respectively in order to trigger
      // the constraint call-backs.
      TestParentCfg parent = getParent("test parent 1");
      parent.addTestChildDeleteListener(dl);

      TestChildCfg child = parent.getTestChild("test child 7");
      child.addChangeListener(cl);

      // Now attempt to delete the referenced connection handler.
      // This should fail.
      try {
        deleteConnectionHandler();
        Assert.fail("Successfully deleted a referenced component");
      } catch (OperationRejectedException e) {
        // This is the expected exception - do nothing.
      }
    } finally {
      try {
        deleteSubtree(TEST_CHILD_7_DN);
      } finally {
        try {
          deleteConnectionHandler();
        } catch (ManagedObjectNotFoundException e) {
          // Ignore as it may have been deleted already.
        } finally {
          // Remove the temporary delete listener.
          TestParentCfg parent = getParent("test parent 1");
          parent.removeTestChildDeleteListener(dl);

          // Put back the default aggregation definition.
          TestCfg
              .removeConstraint(aggregationPropertyDefinitionTargetMustBeEnabled
                  .getSourceConstraint());
          TestCfg.addPropertyDefinition(aggregationPropertyDefinitionDefault);
          TestCfg.addConstraint(aggregationPropertyDefinitionDefault
              .getSourceConstraint());
        }
      }
    }
  }



  /**
   * Tests that it is impossible to disable a referenced component
   * when the referenced component must always be enabled regardless
   * of whether the referencing component is enabled or not.
   *
   * @throws Exception
   *           If the test unexpectedly fails.
   */
  @Test
  public void testCannotDisableReferencedComponent() throws Exception {
    // Add the entry.
    TestCaseUtils.addEntry(TEST_CHILD_7);
    try {
      createConnectionHandler(true);
    } catch (Exception e) {
      deleteSubtree(TEST_CHILD_7_DN);
      throw e;
    }

    // Register the temporary aggregation definition.
    TestCfg.removeConstraint(aggregationPropertyDefinitionDefault
        .getSourceConstraint());
    TestCfg
        .addPropertyDefinition(aggregationPropertyDefinitionTargetMustBeEnabled);
    TestCfg.addConstraint(aggregationPropertyDefinitionTargetMustBeEnabled
        .getSourceConstraint());

    ConfigurationDeleteListener<TestChildCfg> dl = new DummyDeleteListener();
    ConfigurationChangeListener<TestChildCfg> cl = new DummyChangeListener();
    try {
      // Retrieve the parent and child managed objects and register
      // delete and change listeners respectively in order to trigger
      // the constraint call-backs.
      TestParentCfg parent = getParent("test parent 1");
      parent.addTestChildDeleteListener(dl);

      TestChildCfg child = parent.getTestChild("test child 7");
      child.addChangeListener(cl);

      // Now attempt to disable the referenced connection handler.
      // This should fail.
      try {
        RootCfgClient root = TestCaseUtils.getRootConfiguration();
        ConnectionHandlerCfgClient client = root
            .getConnectionHandler(TEST_CONNECTION_HANDLER_NAME);
        client.setEnabled(false);
        client.commit();
        Assert.fail("Successfully disabled a referenced component");
      } catch (OperationRejectedException e) {
        // This is the expected exception - do nothing.
      }
    } finally {
      try {
        deleteSubtree(TEST_CHILD_7_DN);
      } finally {
        try {
          deleteConnectionHandler();
        } finally {
          // Remove the temporary delete listener.
          TestParentCfg parent = getParent("test parent 1");
          parent.removeTestChildDeleteListener(dl);

          // Put back the default aggregation definition.
          TestCfg
              .removeConstraint(aggregationPropertyDefinitionTargetMustBeEnabled
                  .getSourceConstraint());
          TestCfg.addPropertyDefinition(aggregationPropertyDefinitionDefault);
          TestCfg.addConstraint(aggregationPropertyDefinitionDefault
              .getSourceConstraint());
        }
      }
    }
  }



  /** Assert that the values of child 1 are correct. */
  private void assertChild1(TestChildCfg child) {
    Assert.assertEquals(child.getMandatoryClassProperty(),
        "org.opends.server.extensions.UserDefinedVirtualAttributeProvider");
    Assert.assertEquals(child.getMandatoryReadOnlyAttributeTypeProperty(),
        DirectoryServer.getAttributeTypeOrNull("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.getAttributeTypeOrNull("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.getAttributeTypeOrNull("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<>(TestChildCfgDefn
        .getInstance().getAggregationPropertyPropertyDefinition());
    if (expected != null) {
      Collections.addAll(values, expected);
    }
    Assert.assertEquals((Object) actual, (Object) values);
  }



  /** Creates a test connection handler for testing. */
  private void createConnectionHandler(boolean enabled) throws Exception {
    int freePort = TestCaseUtils.findFreePort();

    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));
  }



  /** Gets the JNDI connection for the test server instance. */
  private synchronized JNDIDirContextAdaptor getAdaptor() throws Exception {
    if (adaptor == null) {
      adaptor = JNDIDirContextAdaptor.simpleSSLBind("127.0.0.1", TestCaseUtils
          .getServerAdminPort(), "cn=directory manager", "password");
    }
    return adaptor;
  }



  /** Gets the named parent configuration. */
  private TestParentCfg getParent(String name) throws IllegalArgumentException,
      ConfigException {
    ServerManagementContext ctx = ServerManagementContext.getInstance();
    ServerManagedObject<RootCfg> root = ctx.getRootConfigurationManagedObject();
    return root.getChild(TestCfg.getTestOneToManyParentRelationDefinition(),
        name).getConfiguration();
  }
}
