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

matthew_swift
16.17.2007 8a6b96d00b2556d652b5d903be4786d496a0a5fd
Add a new test suite for testing server-side admin framework: this suite tests that default values are decoded properly and that components are correctly notified when changes to property values occur. Also included in this change is a fix for a bug found during testing where inherited default values were not correctly decoded during add/change notification.
1 files added
1 files modified
1013 ■■■■■ changed files
opendj-sdk/opends/src/server/org/opends/server/admin/server/ServerManagedObject.java 155 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/admin/server/DefaultBehaviorTest.java 858 ●●●●● patch | view | raw | blame | history
opendj-sdk/opends/src/server/org/opends/server/admin/server/ServerManagedObject.java
@@ -109,7 +109,7 @@
   *          The type of the property.
   */
  private static class DefaultValueFinder<T> implements
      DefaultBehaviorProviderVisitor<T, Collection<T>, ManagedObjectPath> {
      DefaultBehaviorProviderVisitor<T, Collection<T>, Void> {
    /**
     * Get the default values for the specified property.
@@ -120,40 +120,40 @@
     *          The managed object path of the current managed object.
     * @param pd
     *          The property definition.
     * @param newConfigEntry
     *          Optional new configuration entry which does not yet
     *          exist in the configuration back-end.
     * @return Returns the default values for the specified property.
     * @throws DefaultBehaviorException
     *           If the default values could not be retrieved or
     *           decoded properly.
     */
    public static <T> Collection<T> getDefaultValues(ManagedObjectPath p,
        PropertyDefinition<T> pd) throws DefaultBehaviorException {
      DefaultValueFinder<T> v = new DefaultValueFinder<T>(pd);
      Collection<T> values = pd.getDefaultBehaviorProvider().accept(v, p);
      if (v.exception != null) {
        throw v.exception;
      }
      if (values.size() > 1 && !pd.hasOption(PropertyOption.MULTI_VALUED)) {
        throw new DefaultBehaviorException(pd,
            new PropertyIsSingleValuedException(pd));
      }
      return values;
        PropertyDefinition<T> pd, ConfigEntry newConfigEntry)
        throws DefaultBehaviorException {
      DefaultValueFinder<T> v = new DefaultValueFinder<T>(newConfigEntry);
      return v.find(p, pd);
    }
    // Any exception that occurred whilst retrieving inherited default
    // values.
    private DefaultBehaviorException exception = null;
    // The property definition whose default values are required.
    private final PropertyDefinition<T> pd;
    // The path of the managed object containing the next property.
    private ManagedObjectPath nextPath = null;
    // The next property whose default values were required.
    private PropertyDefinition<T> nextProperty = null;
    // Optional new configuration entry which does not yet exist in
    // the configuration back-end.
    private ConfigEntry newConfigEntry;
    // Private constructor.
    private DefaultValueFinder(PropertyDefinition<T> pd) {
      this.pd = pd;
    private DefaultValueFinder(ConfigEntry newConfigEntry) {
      this.newConfigEntry = newConfigEntry;
    }
@@ -162,14 +162,14 @@
     * {@inheritDoc}
     */
    public Collection<T> visitAbsoluteInherited(
        AbsoluteInheritedDefaultBehaviorProvider<T> d, ManagedObjectPath p) {
        AbsoluteInheritedDefaultBehaviorProvider<T> d, Void p) {
      try {
        return getInheritedProperty(d.getManagedObjectPath(), d
            .getManagedObjectDefinition(), d.getPropertyName());
      } catch (DefaultBehaviorException e) {
        exception = new DefaultBehaviorException(pd, e);
        exception = e;
        return Collections.emptySet();
      }
      return Collections.emptySet();
    }
@@ -177,8 +177,7 @@
    /**
     * {@inheritDoc}
     */
    public Collection<T> visitAlias(AliasDefaultBehaviorProvider<T> d,
        ManagedObjectPath p) {
    public Collection<T> visitAlias(AliasDefaultBehaviorProvider<T> d, Void p) {
      return Collections.emptySet();
    }
@@ -188,15 +187,15 @@
     * {@inheritDoc}
     */
    public Collection<T> visitDefined(DefinedDefaultBehaviorProvider<T> d,
        ManagedObjectPath p) {
        Void p) {
      Collection<String> stringValues = d.getDefaultValues();
      List<T> values = new ArrayList<T>(stringValues.size());
      for (String stringValue : stringValues) {
        try {
          values.add(pd.decodeValue(stringValue));
          values.add(nextProperty.decodeValue(stringValue));
        } catch (IllegalPropertyValueStringException e) {
          exception = new DefaultBehaviorException(pd, e);
          exception = new DefaultBehaviorException(nextProperty, e);
          break;
        }
      }
@@ -210,14 +209,14 @@
     * {@inheritDoc}
     */
    public Collection<T> visitRelativeInherited(
        RelativeInheritedDefaultBehaviorProvider<T> d, ManagedObjectPath p) {
        RelativeInheritedDefaultBehaviorProvider<T> d, Void p) {
      try {
        return getInheritedProperty(d.getManagedObjectPath(p), d
        return getInheritedProperty(d.getManagedObjectPath(nextPath), d
            .getManagedObjectDefinition(), d.getPropertyName());
      } catch (DefaultBehaviorException e) {
        exception = new DefaultBehaviorException(pd, e);
        exception = e;
        return Collections.emptySet();
      }
      return Collections.emptySet();
    }
@@ -226,72 +225,104 @@
     * {@inheritDoc}
     */
    public Collection<T> visitUndefined(UndefinedDefaultBehaviorProvider<T> d,
        ManagedObjectPath p) {
        Void p) {
      return Collections.emptySet();
    }
    // Find the default values for the next path/property.
    private Collection<T> find(ManagedObjectPath p, PropertyDefinition<T> pd)
        throws DefaultBehaviorException {
      nextPath = p;
      nextProperty = pd;
      Collection<T> values = nextProperty.getDefaultBehaviorProvider().accept(
          this, null);
      if (exception != null) {
        throw exception;
      }
      if (values.size() > 1 && !pd.hasOption(PropertyOption.MULTI_VALUED)) {
        throw new DefaultBehaviorException(pd,
            new PropertyIsSingleValuedException(pd));
      }
      return values;
    }
    // Get an inherited property value.
    @SuppressWarnings("unchecked")
    private Collection<T> getInheritedProperty(ManagedObjectPath target,
        AbstractManagedObjectDefinition<?, ?> d, String propertyName)
        throws DefaultBehaviorException {
      try {
        // First check that the requested type of managed object
        // corresponds to the path.
        AbstractManagedObjectDefinition<?, ?> supr = target
            .getManagedObjectDefinition();
        if (!supr.isParentOf(d)) {
          throw new DefinitionDecodingException(Reason.WRONG_TYPE_INFORMATION);
        }
      // First check that the requested type of managed object
      // corresponds to the path.
      AbstractManagedObjectDefinition<?, ?> supr = target
          .getManagedObjectDefinition();
      if (!supr.isParentOf(d)) {
        throw new DefaultBehaviorException(nextProperty,
            new DefinitionDecodingException(Reason.WRONG_TYPE_INFORMATION));
      }
      // Save the current property in case of recursion.
      PropertyDefinition<T> pd1 = nextProperty;
      try {
        // Get the actual managed object definition.
        DN dn = DNBuilder.create(target);
        ConfigEntry configEntry = getManagedObjectConfigEntry(dn);
        ConfigEntry configEntry;
        if (newConfigEntry != null && newConfigEntry.getDN().equals(dn)) {
          configEntry = newConfigEntry;
        } else {
          configEntry = getManagedObjectConfigEntry(dn);
        }
        DefinitionResolver resolver = new MyDefinitionResolver(configEntry);
        ManagedObjectDefinition<?, ?> mod = d
            .resolveManagedObjectDefinition(resolver);
        PropertyDefinition<?> pd2;
        PropertyDefinition<T> pd2;
        try {
          pd2 = mod.getPropertyDefinition(propertyName);
          PropertyDefinition<?> pdTmp = mod.getPropertyDefinition(propertyName);
          pd2 = pd1.getClass().cast(pdTmp);
        } catch (IllegalArgumentException e) {
          throw new PropertyNotFoundException(propertyName);
        } catch (ClassCastException e) {
          // FIXME: would be nice to throw a better exception here.
          throw new PropertyNotFoundException(propertyName);
        }
        List<String> stringValues = getAttribute(mod, pd2, configEntry);
        if (stringValues.isEmpty()) {
          // Recursively retrieve this property's default values.
          Collection<?> tmp = getDefaultValues(target, pd2);
          Collection<T> tmp = find(target, pd2);
          Collection<T> values = new ArrayList<T>(tmp.size());
          for (Object o : tmp) {
            T value;
            try {
              value = pd.castValue(o);
            } catch (ClassCastException e) {
              throw new IllegalPropertyValueException(pd, o);
            }
            pd.validateValue(value);
          for (T value : tmp) {
            pd1.validateValue(value);
            values.add(value);
          }
          return values;
        } else {
          Collection<T> values = new ArrayList<T>(stringValues.size());
          for (String s : stringValues) {
            values.add(pd.decodeValue(s));
            values.add(pd1.decodeValue(s));
          }
          return values;
        }
      } catch (DefinitionDecodingException e) {
        throw new DefaultBehaviorException(pd, e);
        throw new DefaultBehaviorException(pd1, e);
      } catch (PropertyNotFoundException e) {
        throw new DefaultBehaviorException(pd, e);
        throw new DefaultBehaviorException(pd1, e);
      } catch (IllegalPropertyValueException e) {
        throw new DefaultBehaviorException(pd, e);
        throw new DefaultBehaviorException(pd1, e);
      } catch (IllegalPropertyValueStringException e) {
        throw new DefaultBehaviorException(pd, e);
        throw new DefaultBehaviorException(pd1, e);
      } catch (ConfigException e) {
        throw new DefaultBehaviorException(pd, e);
        throw new DefaultBehaviorException(pd1, e);
      }
    }
  }
@@ -379,7 +410,7 @@
    for (PropertyDefinition<?> pd : mod.getAllPropertyDefinitions()) {
      List<String> values = getAttribute(mod, pd, configEntry);
      try {
        decodeProperty(properties, path, pd, values);
        decodeProperty(properties, path, pd, values, configEntry);
      } catch (PropertyException e) {
        exceptions.add(e);
      }
@@ -423,7 +454,8 @@
  private static <T> void decodeProperty(
      Map<PropertyDefinition<?>, SortedSet<?>> properties,
      ManagedObjectPath path, PropertyDefinition<T> pd,
      List<String> stringValues) throws PropertyException {
      List<String> stringValues, ConfigEntry newConfigEntry)
      throws PropertyException {
    PropertyException exception = null;
    SortedSet<T> values = new TreeSet<T>(pd);
@@ -439,7 +471,8 @@
    } else {
      // No values defined so get the defaults.
      try {
        values.addAll(DefaultValueFinder.getDefaultValues(path, pd));
        values.addAll(DefaultValueFinder.getDefaultValues(path, pd,
            newConfigEntry));
      } catch (DefaultBehaviorException e) {
        exception = e;
      }
opendj-sdk/opends/tests/unit-tests-testng/src/server/org/opends/server/admin/server/DefaultBehaviorTest.java
New file
@@ -0,0 +1,858 @@
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at
 * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
 * add the following below this CDDL HEADER, with the fields enclosed
 * by brackets "[]" replaced with your own identifying information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Portions Copyright 2007 Sun Microsystems, Inc.
 */
package org.opends.server.admin.server;
import java.util.List;
import java.util.SortedSet;
import javax.naming.ldap.LdapName;
import org.opends.server.TestCaseUtils;
import org.opends.server.admin.AdminTestCase;
import org.opends.server.admin.LDAPProfile;
import org.opends.server.admin.MockLDAPProfile;
import org.opends.server.admin.TestCfg;
import org.opends.server.admin.TestChildCfg;
import org.opends.server.admin.TestParentCfg;
import org.opends.server.admin.client.ldap.JNDIDirContextAdaptor;
import org.opends.server.admin.std.server.RootCfg;
import org.opends.server.config.ConfigException;
import org.opends.server.core.DirectoryServer;
import org.opends.server.types.ConfigChangeResult;
import org.opends.server.types.DN;
import org.opends.server.types.ResultCode;
import org.testng.Assert;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
/**
 * Test cases for default behavior on the server-side.
 */
public final class DefaultBehaviorTest extends AdminTestCase {
  /**
   * A test child add listener.
   */
  private static class AddListener implements
      ConfigurationAddListener<TestChildCfg> {
    // The child configuration that was added.
    private TestChildCfg child;
    /**
     * Creates a new add listener.
     */
    public AddListener() {
      // No implementation required.
    }
    /**
     * {@inheritDoc}
     */
    public ConfigChangeResult applyConfigurationAdd(TestChildCfg configuration) {
      return new ConfigChangeResult(ResultCode.SUCCESS, false);
    }
    /**
     * Gets the child configuration checking that it has the expected
     * name.
     *
     * @param expectedName
     *          The child's expected name.
     * @return Returns the child configuration.
     */
    public TestChildCfg getChild(String expectedName) {
      Assert.assertNotNull(child);
      Assert.assertEquals(child.dn().getRDN().getAttributeValue(0)
          .getStringValue(), expectedName);
      return child;
    }
    /**
     * {@inheritDoc}
     */
    public boolean isConfigurationAddAcceptable(TestChildCfg configuration,
        List<String> unacceptableReasons) {
      child = configuration;
      return true;
    }
  }
  /**
   * A test child change listener.
   */
  private static class ChangeListener implements
      ConfigurationChangeListener<TestChildCfg> {
    // The child configuration that was changed.
    private TestChildCfg child;
    /**
     * Creates a new change listener.
     */
    public ChangeListener() {
      // No implementation required.
    }
    /**
     * {@inheritDoc}
     */
    public ConfigChangeResult applyConfigurationChange(
        TestChildCfg configuration) {
      return new ConfigChangeResult(ResultCode.SUCCESS, false);
    }
    /**
     * Gets the child configuration checking that it has the expected
     * name.
     *
     * @param expectedName
     *          The child's expected name.
     * @return Returns the child configuration.
     */
    public TestChildCfg getChild(String expectedName) {
      Assert.assertNotNull(child);
      Assert.assertEquals(child.dn().getRDN().getAttributeValue(0)
          .getStringValue(), expectedName);
      return child;
    }
    /**
     * {@inheritDoc}
     */
    public boolean isConfigurationChangeAcceptable(TestChildCfg configuration,
        List<String> unacceptableReasons) {
      child = configuration;
      return true;
    }
  }
  // Test child 1 LDIF.
  private static final String[] TEST_CHILD_1 = new String[] {
      "dn: cn=test child 1,cn=test children,cn=test parent 1,cn=test parents,cn=config",
      "objectclass: top",
      "objectclass: ds-cfg-virtual-attribute",
      "cn: test child 1",
      "ds-cfg-virtual-attribute-enabled: true",
      "ds-cfg-virtual-attribute-class: org.opends.server.extensions.UserDefinedVirtualAttributeProvider",
      "ds-cfg-virtual-attribute-type: description",
      "ds-cfg-virtual-attribute-conflict-behavior: virtual-overrides-real"
  };
  // Test 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-virtual-attribute",
      "cn: test child 2",
      "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-virtual-attribute-base-dn: dc=default value c2v1,dc=com",
      "ds-cfg-virtual-attribute-base-dn: dc=default value c2v2,dc=com"
  };
  // Test child 3 LDIF.
  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-virtual-attribute",
      "cn: test child 3",
      "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-virtual-attribute-base-dn: dc=default value c3v1,dc=com",
      "ds-cfg-virtual-attribute-base-dn: dc=default value c3v2,dc=com",
      "ds-cfg-virtual-attribute-group-dn: dc=default value c3v3,dc=com",
      "ds-cfg-virtual-attribute-group-dn: dc=default value c3v4,dc=com"
  };
  // Test child 4 LDIF.
  private static final String[] TEST_CHILD_4 = new String[] {
      "dn: cn=test child 4,cn=test children,cn=test parent 2,cn=test parents,cn=config",
      "objectclass: top",
      "objectclass: ds-cfg-virtual-attribute",
      "cn: test child 4",
      "ds-cfg-virtual-attribute-enabled: true",
      "ds-cfg-virtual-attribute-class: org.opends.server.extensions.UserDefinedVirtualAttributeProvider",
      "ds-cfg-virtual-attribute-type: description",
      "ds-cfg-virtual-attribute-conflict-behavior: virtual-overrides-real"
  };
  // Test LDIF.
  private static final String[] TEST_LDIF = new String[] {
      // Base entries.
      "dn: cn=test parents,cn=config",
      "objectclass: top",
      "objectclass: ds-cfg-branch",
      "cn: test parents",
      "",
      // Parent 1 - uses default values for
      // optional-multi-valued-dn-property.
      "dn: cn=test parent 1,cn=test parents,cn=config",
      "objectclass: top",
      "objectclass: ds-cfg-virtual-attribute",
      "cn: test parent 1",
      "ds-cfg-virtual-attribute-enabled: true",
      "ds-cfg-virtual-attribute-class: org.opends.server.extensions.UserDefinedVirtualAttributeProvider",
      "ds-cfg-virtual-attribute-type: description",
      "ds-cfg-virtual-attribute-conflict-behavior: virtual-overrides-real",
      "",
      // Parent 2 - overrides default values for
      // optional-multi-valued-dn-property.
      "dn: cn=test parent 2,cn=test parents,cn=config",
      "objectclass: top",
      "objectclass: ds-cfg-virtual-attribute",
      "cn: test parent 2",
      "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-virtual-attribute-base-dn: dc=default value p2v1,dc=com",
      "ds-cfg-virtual-attribute-base-dn: dc=default value p2v2,dc=com",
      "",
      // 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",
      "",
      "dn:cn=test children,cn=test parent 2,cn=test parents,cn=config",
      "objectclass: top",
      "objectclass: ds-cfg-branch",
      "cn: test children",
      ""
  };
  // JNDI LDAP context.
  private JNDIDirContextAdaptor adaptor = null;
  /**
   * Sets up tests
   *
   * @throws Exception
   *           If the server could not be initialized.
   */
  @BeforeClass
  public void setUp() throws Exception {
    // This test suite depends on having the schema available, so
    // we'll start the server.
    TestCaseUtils.startServer();
    LDAPProfile.getInstance().pushWrapper(new MockLDAPProfile());
    // Add test managed objects.
    TestCaseUtils.addEntries(TEST_LDIF);
  }
  /**
   * Tears down test environment.
   *
   * @throws Exception
   *           If the test entries could not be removed.
   */
  @AfterClass
  public void tearDown() throws Exception {
    LDAPProfile.getInstance().popWrapper();
    // Remove test entries.
    deleteSubtree("cn=test parents,cn=config");
  }
  /**
   * Tests that children have correct values when accessed through an
   * add listener.
   *
   * @throws Exception
   *           If the test unexpectedly fails.
   */
  @Test
  public void testAddListenerChildValues1() throws Exception {
    TestParentCfg parent = getParent("test parent 1");
    AddListener listener = new AddListener();
    parent.addTestChildAddListener(listener);
    try {
      // Add the entry.
      TestCaseUtils.addEntry(TEST_CHILD_1);
      try {
        assertChild1(listener.getChild("test child 1"));
      } finally {
        deleteSubtree("cn=test child 1,cn=test children,cn=test parent 1,cn=test parents,cn=config");
      }
    } finally {
      parent.removeTestChildAddListener(listener);
    }
  }
  /**
   * Tests that children have correct values when accessed through an
   * add listener.
   *
   * @throws Exception
   *           If the test unexpectedly fails.
   */
  @Test
  public void testAddListenerChildValues2() throws Exception {
    TestParentCfg parent = getParent("test parent 1");
    AddListener listener = new AddListener();
    parent.addTestChildAddListener(listener);
    try {
      // Add the entry.
      TestCaseUtils.addEntry(TEST_CHILD_2);
      try {
        assertChild2(listener.getChild("test child 2"));
      } finally {
        deleteSubtree("cn=test child 2,cn=test children,cn=test parent 1,cn=test parents,cn=config");
      }
    } finally {
      parent.removeTestChildAddListener(listener);
    }
  }
  /**
   * Tests that children have correct values when accessed through an
   * add listener.
   *
   * @throws Exception
   *           If the test unexpectedly fails.
   */
  @Test
  public void testAddListenerChildValues3() throws Exception {
    TestParentCfg parent = getParent("test parent 1");
    AddListener listener = new AddListener();
    parent.addTestChildAddListener(listener);
    try {
      // Add the entry.
      TestCaseUtils.addEntry(TEST_CHILD_3);
      try {
        assertChild3(listener.getChild("test child 3"));
      } finally {
        deleteSubtree("cn=test child 3,cn=test children,cn=test parent 1,cn=test parents,cn=config");
      }
    } finally {
      parent.removeTestChildAddListener(listener);
    }
  }
  /**
   * Tests that children have correct values when accessed through an
   * add listener.
   *
   * @throws Exception
   *           If the test unexpectedly fails.
   */
  @Test
  public void testAddListenerChildValues4() throws Exception {
    TestParentCfg parent = getParent("test parent 2");
    AddListener listener = new AddListener();
    parent.addTestChildAddListener(listener);
    try {
      // Add the entry.
      TestCaseUtils.addEntry(TEST_CHILD_4);
      try {
        assertChild4(listener.getChild("test child 4"));
      } finally {
        deleteSubtree("cn=test child 4,cn=test children,cn=test parent 2,cn=test parents,cn=config");
      }
    } finally {
      parent.removeTestChildAddListener(listener);
    }
  }
  /**
   * Tests that children have correct values when accessed through a
   * change listener. This test replaces the defaulted properties with
   * real values.
   *
   * @throws Exception
   *           If the test unexpectedly fails.
   */
  @Test
  public void testChangeListenerChildValues1() throws Exception {
    TestParentCfg parent = getParent("test parent 1");
    try {
      // Add the entry.
      TestCaseUtils.addEntry(TEST_CHILD_1);
      TestChildCfg child = parent.getTestChild("test child 1");
      ChangeListener listener = new ChangeListener();
      child.addChangeListener(listener);
      // Now modify it.
      String[] changes = new String[] {
          "dn: cn=test child 1,cn=test children,cn=test parent 1,cn=test parents,cn=config",
          "changetype: modify",
          "replace: ds-cfg-virtual-attribute-base-dn",
          "ds-cfg-virtual-attribute-base-dn: dc=new value 1,dc=com",
          "ds-cfg-virtual-attribute-base-dn: dc=new value 2,dc=com",
          "-",
          "replace: ds-cfg-virtual-attribute-group-dn",
          "ds-cfg-virtual-attribute-group-dn: dc=new value 3,dc=com",
          "ds-cfg-virtual-attribute-group-dn: dc=new value 4,dc=com"
      };
      TestCaseUtils.applyModifications(changes);
      // Make sure that the change listener was notified and the
      // modified child contains the correct values.
      child = listener.getChild("test child 1");
      Assert.assertEquals(child.getMandatoryClassProperty(),
          "org.opends.server.extensions.UserDefinedVirtualAttributeProvider");
      Assert.assertEquals(child.getMandatoryReadOnlyAttributeTypeProperty(),
          DirectoryServer.getAttributeType("description"));
      assertDNSetEquals(child.getOptionalMultiValuedDNProperty1(),
          "dc=new value 1,dc=com", "dc=new value 2,dc=com");
      assertDNSetEquals(child.getOptionalMultiValuedDNProperty2(),
          "dc=new value 3,dc=com", "dc=new value 4,dc=com");
    } finally {
      deleteSubtree("cn=test child 1,cn=test children,cn=test parent 1,cn=test parents,cn=config");
    }
  }
  /**
   * Tests that children have correct values when accessed through a
   * change listener. This test makes sure that default values
   * inherited from within the modified component itself behave as
   * expected.
   *
   * @throws Exception
   *           If the test unexpectedly fails.
   */
  @Test
  public void testChangeListenerChildValues2() throws Exception {
    TestParentCfg parent = getParent("test parent 1");
    try {
      // Add the entry.
      TestCaseUtils.addEntry(TEST_CHILD_1);
      TestChildCfg child = parent.getTestChild("test child 1");
      ChangeListener listener = new ChangeListener();
      child.addChangeListener(listener);
      // Now modify it.
      String[] changes = new String[] {
          "dn: cn=test child 1,cn=test children,cn=test parent 1,cn=test parents,cn=config",
          "changetype: modify",
          "replace: ds-cfg-virtual-attribute-base-dn",
          "ds-cfg-virtual-attribute-base-dn: dc=new value 1,dc=com",
          "ds-cfg-virtual-attribute-base-dn: dc=new value 2,dc=com"
      };
      TestCaseUtils.applyModifications(changes);
      // Make sure that the change listener was notified and the
      // modified child contains the correct values.
      child = listener.getChild("test child 1");
      Assert.assertEquals(child.getMandatoryClassProperty(),
          "org.opends.server.extensions.UserDefinedVirtualAttributeProvider");
      Assert.assertEquals(child.getMandatoryReadOnlyAttributeTypeProperty(),
          DirectoryServer.getAttributeType("description"));
      assertDNSetEquals(child.getOptionalMultiValuedDNProperty1(),
          "dc=new value 1,dc=com", "dc=new value 2,dc=com");
      assertDNSetEquals(child.getOptionalMultiValuedDNProperty2(),
          "dc=new value 1,dc=com", "dc=new value 2,dc=com");
    } finally {
      deleteSubtree("cn=test child 1,cn=test children,cn=test parent 1,cn=test parents,cn=config");
    }
  }
  /**
   * Tests that children have correct values when accessed through a
   * change listener. This test makes sure that default values
   * inherited from outside the modified component behave as expected.
   *
   * @throws Exception
   *           If the test unexpectedly fails.
   */
  @Test
  public void testChangeListenerChildValues3() throws Exception {
    TestParentCfg parent = getParent("test parent 1");
    try {
      // Add the entry.
      TestCaseUtils.addEntry(TEST_CHILD_1);
      TestChildCfg child = parent.getTestChild("test child 1");
      ChangeListener listener = new ChangeListener();
      child.addChangeListener(listener);
      // Now modify it.
      String[] changes = new String[] {
          "dn: cn=test child 1,cn=test children,cn=test parent 1,cn=test parents,cn=config",
          "changetype: modify",
          "replace: ds-cfg-virtual-attribute-group-dn",
          "ds-cfg-virtual-attribute-group-dn: dc=new value 1,dc=com",
          "ds-cfg-virtual-attribute-group-dn: dc=new value 2,dc=com"
      };
      TestCaseUtils.applyModifications(changes);
      // Make sure that the change listener was notified and the
      // modified child contains the correct values.
      child = listener.getChild("test child 1");
      Assert.assertEquals(child.getMandatoryClassProperty(),
          "org.opends.server.extensions.UserDefinedVirtualAttributeProvider");
      Assert.assertEquals(child.getMandatoryReadOnlyAttributeTypeProperty(),
          DirectoryServer.getAttributeType("description"));
      assertDNSetEquals(child.getOptionalMultiValuedDNProperty1(),
          "dc=domain1,dc=com", "dc=domain2,dc=com", "dc=domain3,dc=com");
      assertDNSetEquals(child.getOptionalMultiValuedDNProperty2(),
          "dc=new value 1,dc=com", "dc=new value 2,dc=com");
    } finally {
      deleteSubtree("cn=test child 1,cn=test children,cn=test parent 1,cn=test parents,cn=config");
    }
  }
  /**
   * Tests that children have correct values when accessed through a
   * change listener. This test makes sure that a component is
   * notified when the default values it inherits from another
   * component are modified.
   * <p>
   * FIXME: disabled - waiting for fix to issue 1793.
   *
   * @throws Exception
   *           If the test unexpectedly fails.
   */
  @Test(enabled = false)
  public void testChangeListenerChildValues4() throws Exception {
    TestParentCfg parent = getParent("test parent 1");
    try {
      // Add the entry.
      TestCaseUtils.addEntry(TEST_CHILD_1);
      TestChildCfg child = parent.getTestChild("test child 1");
      ChangeListener listener = new ChangeListener();
      child.addChangeListener(listener);
      // Now modify the parent.
      String[] changes = new String[] {
          "dn: cn=test parent 1,cn=test parents,cn=config",
          "changetype: modify",
          "replace: ds-cfg-virtual-attribute-base-dn",
          "ds-cfg-virtual-attribute-base-dn: dc=new value 1,dc=com",
          "ds-cfg-virtual-attribute-base-dn: dc=new value 2,dc=com"
      };
      TestCaseUtils.applyModifications(changes);
      // Make sure that the change listener was notified and the
      // modified child contains the correct values.
      child = listener.getChild("test child 1");
      Assert.assertEquals(child.getMandatoryClassProperty(),
          "org.opends.server.extensions.UserDefinedVirtualAttributeProvider");
      Assert.assertEquals(child.getMandatoryReadOnlyAttributeTypeProperty(),
          DirectoryServer.getAttributeType("description"));
      assertDNSetEquals(child.getOptionalMultiValuedDNProperty1(),
          "dc=new value 1,dc=com", "dc=new value 2,dc=com");
      assertDNSetEquals(child.getOptionalMultiValuedDNProperty2(),
          "dc=new value 1,dc=com", "dc=new value 2,dc=com");
    } finally {
      deleteSubtree("cn=test child 1,cn=test children,cn=test parent 1,cn=test parents,cn=config");
      // Undo the modifications.
      String[] changes = new String[] {
          "dn: cn=test parent 1,cn=test parents,cn=config",
          "changetype: modify",
          "delete: ds-cfg-virtual-attribute-base-dn"
      };
      TestCaseUtils.applyModifications(changes);
    }
  }
  /**
   * Tests that children have correct values.
   *
   * @throws Exception
   *           If the test unexpectedly fails.
   */
  @Test
  public void testChildValues1() 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 children have correct values.
   *
   * @throws Exception
   *           If the test unexpectedly fails.
   */
  @Test
  public void testChildValues2() 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 children have correct values.
   *
   * @throws Exception
   *           If the test unexpectedly fails.
   */
  @Test
  public void testChildValues3() throws Exception {
    // Add the entry.
    TestCaseUtils.addEntry(TEST_CHILD_3);
    try {
      TestParentCfg parent = getParent("test parent 1");
      assertChild3(parent.getTestChild("test child 3"));
    } finally {
      deleteSubtree("cn=test child 3,cn=test children,cn=test parent 1,cn=test parents,cn=config");
    }
  }
  /**
   * Tests that children have correct values.
   *
   * @throws Exception
   *           If the test unexpectedly fails.
   */
  @Test
  public void testChildValues4() throws Exception {
    // Add the entry.
    TestCaseUtils.addEntry(TEST_CHILD_4);
    try {
      TestParentCfg parent = getParent("test parent 2");
      assertChild4(parent.getTestChild("test child 4"));
    } finally {
      deleteSubtree("cn=test child 4,cn=test children,cn=test parent 2,cn=test parents,cn=config");
    }
  }
  /**
   * Tests that parent 1 has correct values.
   *
   * @throws Exception
   *           If the test unexpectedly fails.
   */
  @Test
  public void testParentValues1() throws Exception {
    TestParentCfg parent = getParent("test parent 1");
    Assert.assertEquals(parent.getMandatoryClassProperty(),
        "org.opends.server.extensions.UserDefinedVirtualAttributeProvider");
    Assert.assertEquals(parent.getMandatoryReadOnlyAttributeTypeProperty(),
        DirectoryServer.getAttributeType("description"));
    assertDNSetEquals(parent.getOptionalMultiValuedDNProperty(),
        "dc=domain1,dc=com", "dc=domain2,dc=com", "dc=domain3,dc=com");
  }
  /**
   * Tests that parent 2 has correct values.
   *
   * @throws Exception
   *           If the test unexpectedly fails.
   */
  @Test
  public void testParentValues2() throws Exception {
    TestParentCfg parent = getParent("test parent 2");
    Assert.assertEquals(parent.getMandatoryClassProperty(),
        "org.opends.server.extensions.UserDefinedVirtualAttributeProvider");
    Assert.assertEquals(parent.getMandatoryReadOnlyAttributeTypeProperty(),
        DirectoryServer.getAttributeType("description"));
    assertDNSetEquals(parent.getOptionalMultiValuedDNProperty(),
        "dc=default value p2v1,dc=com", "dc=default value p2v2,dc=com");
  }
  // 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"));
    assertDNSetEquals(child.getOptionalMultiValuedDNProperty1(),
        "dc=domain1,dc=com", "dc=domain2,dc=com", "dc=domain3,dc=com");
    assertDNSetEquals(child.getOptionalMultiValuedDNProperty2(),
        "dc=domain1,dc=com", "dc=domain2,dc=com", "dc=domain3,dc=com");
  }
  // 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"));
    assertDNSetEquals(child.getOptionalMultiValuedDNProperty1(),
        "dc=default value c2v1,dc=com", "dc=default value c2v2,dc=com");
    assertDNSetEquals(child.getOptionalMultiValuedDNProperty2(),
        "dc=default value c2v1,dc=com", "dc=default value c2v2,dc=com");
  }
  // Assert that the values of child 3 are correct.
  private void assertChild3(TestChildCfg child) {
    Assert.assertEquals(child.getMandatoryClassProperty(),
        "org.opends.server.extensions.UserDefinedVirtualAttributeProvider");
    Assert.assertEquals(child.getMandatoryReadOnlyAttributeTypeProperty(),
        DirectoryServer.getAttributeType("description"));
    assertDNSetEquals(child.getOptionalMultiValuedDNProperty1(),
        "dc=default value c3v1,dc=com", "dc=default value c3v2,dc=com");
    assertDNSetEquals(child.getOptionalMultiValuedDNProperty2(),
        "dc=default value c3v3,dc=com", "dc=default value c3v4,dc=com");
  }
  // 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"));
    assertDNSetEquals(child.getOptionalMultiValuedDNProperty1(),
        "dc=default value p2v1,dc=com", "dc=default value p2v2,dc=com");
    assertDNSetEquals(child.getOptionalMultiValuedDNProperty2(),
        "dc=default value p2v1,dc=com", "dc=default value p2v2,dc=com");
  }
  // Asserts that the actual set of DNs contains the expected values.
  private void assertDNSetEquals(SortedSet<DN> actual, String... expected) {
    String[] actualStrings = new String[actual.size()];
    int i = 0;
    for (DN dn : actual) {
      actualStrings[i] = dn.toString();
      i++;
    }
    Assert.assertEqualsNoOrder(actualStrings, expected);
  }
  // Deletes the named sub-tree.
  private void deleteSubtree(String dn) throws Exception {
    getAdaptor().deleteSubtree(new LdapName(dn));
  }
  // Gets the JNDI connection for the test server instance.
  private synchronized JNDIDirContextAdaptor getAdaptor() throws Exception {
    if (adaptor == null) {
      adaptor = JNDIDirContextAdaptor.simpleBind("127.0.0.1", TestCaseUtils
          .getServerLdapPort(), "cn=directory manager", "password");
    }
    return adaptor;
  }
  // Gets the named parent configuration.
  private TestParentCfg getParent(String name) throws IllegalArgumentException,
      ConfigException {
    ServerManagementContext ctx = ServerManagementContext.getInstance();
    ServerManagedObject<RootCfg> root = ctx.getRootConfigurationManagedObject();
    TestParentCfg parent = root.getChild(TestCfg.RD_TEST_ONE_TO_MANY_PARENT,
        name).getConfiguration();
    return parent;
  }
}