From 794cb43ee4bf8f480f220d82b2032713d2764f7b Mon Sep 17 00:00:00 2001
From: matthew_swift <matthew_swift@localhost>
Date: Mon, 16 Jul 2007 17:17:20 +0000
Subject: [PATCH] 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.

---
 opends/src/server/org/opends/server/admin/server/ServerManagedObject.java                         |  155 +++++---
 opends/tests/unit-tests-testng/src/server/org/opends/server/admin/server/DefaultBehaviorTest.java |  858 ++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 952 insertions(+), 61 deletions(-)

diff --git a/opends/src/server/org/opends/server/admin/server/ServerManagedObject.java b/opends/src/server/org/opends/server/admin/server/ServerManagedObject.java
index 49be1e0..b2792fe 100644
--- a/opends/src/server/org/opends/server/admin/server/ServerManagedObject.java
+++ b/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;
       }
diff --git a/opends/tests/unit-tests-testng/src/server/org/opends/server/admin/server/DefaultBehaviorTest.java b/opends/tests/unit-tests-testng/src/server/org/opends/server/admin/server/DefaultBehaviorTest.java
new file mode 100644
index 0000000..5d850e4
--- /dev/null
+++ b/opends/tests/unit-tests-testng/src/server/org/opends/server/admin/server/DefaultBehaviorTest.java
@@ -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;
+  }
+}

--
Gitblit v1.10.0