From c6f5f43745ec42f805b4ff0809dcb8d5cdbc1ba3 Mon Sep 17 00:00:00 2001
From: Nicolas Capponi <nicolas.capponi@forgerock.com>
Date: Tue, 17 Dec 2013 14:07:42 +0000
Subject: [PATCH] Checkpoint commit for OPENDJ-1235 : Migrate configuration framework

---
 opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/client/ldap/MockLDAPConnection.java              |  386 +++++++++++
 opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/client/ldap/AggregationClientTest.java           |  314 ++++++++
 opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/client/ldap/LDAPManagedObject.java               |    7 
 opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/client/ldap/CreateEntryMockLDAPConnection.java   |  112 +++
 opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/client/ldap/DeleteSubtreeMockLDAPConnection.java |   70 ++
 opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/client/ldap/ModifyEntryMockLDAPConnection.java   |  115 +++
 opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/client/ldap/MockConstraint.java                  |  131 +++
 opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/client/ldap/LDAPNameBuilder.java                 |   36 
 opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/client/ldap/LDAPClientTest.java                  |  889 +++++++++++++++++++++++++
 9 files changed, 2,042 insertions(+), 18 deletions(-)

diff --git a/opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/client/ldap/LDAPManagedObject.java b/opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/client/ldap/LDAPManagedObject.java
index a6780f6..8d66bd2 100644
--- a/opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/client/ldap/LDAPManagedObject.java
+++ b/opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/client/ldap/LDAPManagedObject.java
@@ -37,7 +37,6 @@
 import org.forgerock.opendj.ldap.LinkedAttribute;
 import org.forgerock.opendj.ldap.LinkedHashMapEntry;
 import org.forgerock.opendj.ldap.ModificationType;
-import org.forgerock.opendj.ldap.RDN;
 import org.forgerock.opendj.ldap.ResultCode;
 import org.forgerock.opendj.ldap.requests.ModifyRequest;
 import org.forgerock.opendj.ldap.requests.Requests;
@@ -175,8 +174,7 @@
                 addObjectClassesToEntry(objectClasses, entry);
 
                 // Create the branch's naming attribute.
-                RDN rdn = dn.parent(dn.size() - 1).rdn();
-                entry.addAttribute(rdn.getFirstAVA().toAttribute());
+                entry.addAttribute(dn.rdn().getFirstAVA().toAttribute());
 
                 // Create the entry.
                 try {
@@ -204,8 +202,7 @@
         // Create the naming attribute if there is not naming property.
         PropertyDefinition<?> namingPropertyDef = getNamingPropertyDefinition();
         if (namingPropertyDef == null) {
-            RDN rdn = dn.parent(dn.size() - 1).rdn();
-            entry.addAttribute(rdn.getFirstAVA().toAttribute());
+            entry.addAttribute(dn.rdn().getFirstAVA().toAttribute());
         }
 
         // Create the remaining attributes.
diff --git a/opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/client/ldap/LDAPNameBuilder.java b/opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/client/ldap/LDAPNameBuilder.java
index 9f5f372..f8fba78 100644
--- a/opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/client/ldap/LDAPNameBuilder.java
+++ b/opendj-sdk/opendj-admin/src/main/java/org/opends/server/admin/client/ldap/LDAPNameBuilder.java
@@ -26,8 +26,10 @@
 
 package org.opends.server.admin.client.ldap;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.LinkedList;
+import java.util.List;
 
 import org.forgerock.opendj.ldap.DN;
 import org.forgerock.opendj.ldap.RDN;
@@ -104,10 +106,9 @@
         return builder.getInstance();
     }
 
-    // The list of RDNs in big-endian order.
+    /** The list of RDNs in big-endian order. */
     private final LinkedList<RDN> rdns;
 
-    // The LDAP profile.
     private final LDAPProfile profile;
 
     /**
@@ -145,9 +146,24 @@
     public void appendManagedObjectPathElement(RelationDefinition<?, ?> r) {
         // Add the RDN sequence representing the relation.
         DN dn = DN.valueOf(profile.getRelationRDNSequence(r));
+        List<RDN> rdnsOfDn = getRdnsInBigEndianOrder(dn);
+        rdns.addAll(rdnsOfDn);
+    }
+
+    /**
+     * Returns list of RDNs of provided DN in big-endian order.
+     *
+     * @param dn
+     *            The DN to decompose in RDNs.
+     * @return rdns in big endian order
+     */
+    private List<RDN> getRdnsInBigEndianOrder(DN dn) {
+        List<RDN> rdnsOfDn = new ArrayList<RDN>();
         for (RDN rdn : dn) {
-            rdns.add(rdn);
+            rdnsOfDn.add(rdn);
         }
+        Collections.reverse(rdnsOfDn);
+        return rdnsOfDn;
     }
 
     /**
@@ -188,16 +204,10 @@
      * @return Returns the new DN instance.
      */
     public DN getInstance() {
-        if (rdns.isEmpty()) {
-            return DN.rootDN();
+        DN dn = DN.rootDN();
+        for (RDN rdn : rdns) {
+            dn = dn.child(rdn);
         }
-        else {
-            Collections.reverse(rdns);
-            DN dn = DN.valueOf(rdns.removeFirst().toString());
-            for (RDN rdn : rdns) {
-                dn = dn.child(rdn);
-            }
-            return dn;
-        }
+        return dn;
     }
 }
diff --git a/opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/client/ldap/AggregationClientTest.java b/opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/client/ldap/AggregationClientTest.java
new file mode 100644
index 0000000..5fcfd2b
--- /dev/null
+++ b/opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/client/ldap/AggregationClientTest.java
@@ -0,0 +1,314 @@
+/*
+ * 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.
+ */
+package org.opends.server.admin.client.ldap;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import org.forgerock.opendj.admin.client.RootCfgClient;
+import org.opends.server.admin.AdminTestCase;
+import org.opends.server.admin.IllegalPropertyValueStringException;
+import org.opends.server.admin.PropertyException;
+import org.opends.server.admin.TestCfg;
+import org.opends.server.admin.TestChildCfgClient;
+import org.opends.server.admin.TestChildCfgDefn;
+import org.opends.server.admin.TestParentCfgClient;
+import org.opends.server.admin.client.ManagedObject;
+import org.opends.server.admin.client.ManagedObjectDecodingException;
+import org.opends.server.admin.client.ManagementContext;
+import org.opends.server.core.DirectoryServer;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+@SuppressWarnings("javadoc")
+@Test(singleThreaded = true)
+public class AggregationClientTest extends AdminTestCase {
+
+    // Test LDIF.
+    private static final String[] TEST_LDIF = new String[] {
+            // Base entries.
+            "dn: cn=config",
+            "objectclass: top",
+            "objectclass: ds-cfg-branch",
+            "cn: config",
+            "",
+            "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-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",
+            "",
+            // Child base entry.
+            "dn:cn=test children,cn=test parent 1,cn=test parents,cn=config",
+            "objectclass: top",
+            "objectclass: ds-cfg-branch",
+            "cn: multiple children",
+            "",
+            // Child 1 has no references.
+            "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",
+            "",
+            // Child 2 has a single valid reference.
+            "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-rotation-policy: cn=LDAP Connection Handler, cn=connection handlers, cn=config",
+            "",
+            // Child 3 has a multiple valid references.
+            "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-rotation-policy: cn=LDAP Connection Handler, cn=connection handlers, cn=config",
+            "ds-cfg-rotation-policy: cn=LDAPS Connection Handler, cn=connection handlers, cn=config",
+            "",
+            // Child 4 has a single bad reference.
+            "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-rotation-policy: cn=LDAP Connection Handler, cn=bad rdn, cn=config", "",
+            "dn: cn=Connection Handlers,cn=config", "objectClass: top", "objectClass: ds-cfg-branch",
+            "cn: Connection Handlers", "", "dn: cn=LDAP Connection Handler,cn=Connection Handlers,cn=config",
+            "objectClass: top", "objectClass: ds-cfg-connection-handler",
+            "objectClass: ds-cfg-ldap-connection-handler", "cn: LDAP Connection Handler",
+            "ds-cfg-java-class: org.opends.server.protocols.ldap.LDAPConnectionHandler", "ds-cfg-enabled: true",
+            "ds-cfg-listen-address: 0.0.0.0", "ds-cfg-listen-port: 389", "",
+            "dn: cn=LDAPS Connection Handler,cn=Connection Handlers,cn=config", "objectClass: top",
+            "objectClass: ds-cfg-connection-handler", "objectClass: ds-cfg-ldap-connection-handler",
+            "cn: LDAPS Connection Handler",
+            "ds-cfg-java-class: org.opends.server.protocols.ldap.LDAPConnectionHandler", "ds-cfg-enabled: false",
+            "ds-cfg-listen-address: 0.0.0.0", "ds-cfg-listen-port: 636", "ds-cfg-use-ssl: true",
+            "ds-cfg-ssl-client-auth-policy: optional", "ds-cfg-ssl-cert-nickname: server-cert",
+            "ds-cfg-key-manager-provider: cn=JKS,cn=Key Manager Providers,cn=config",
+            "ds-cfg-trust-manager-provider: cn=JKS,cn=Trust Manager Providers,cn=config", "",
+            "dn: cn=JMX Connection Handler,cn=Connection Handlers,cn=config", "objectClass: top",
+            "objectClass: ds-cfg-connection-handler", "objectClass: ds-cfg-jmx-connection-handler",
+            "cn: JMX Connection Handler", "ds-cfg-java-class: org.opends.server.protocols.jmx.JmxConnectionHandler",
+            "ds-cfg-enabled: false", "ds-cfg-listen-port: 1689", "" };
+
+    @BeforeClass
+    public void setUp() throws Exception {
+        disableClassValidationForProperties();
+        TestCfg.setUp();
+    }
+
+    /**
+     * Tears down test environment.
+     */
+    @AfterClass
+    public void tearDown() {
+        TestCfg.cleanup();
+    }
+
+    /**
+     * 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 {
+        MockLDAPConnection c = new MockLDAPConnection();
+        c.importLDIF(TEST_LDIF);
+        ManagementContext ctx = LDAPManagementContext.createFromContext(c);
+        TestParentCfgClient parent = getTestParent(ctx, "test parent 1");
+        TestChildCfgClient child = parent.getTestChild("test child 1");
+        assertSetEquals(child.getAggregationProperty(), new String[0]);
+    }
+
+    /**
+     * 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 {
+        MockLDAPConnection c = new MockLDAPConnection();
+        c.importLDIF(TEST_LDIF);
+        ManagementContext ctx = LDAPManagementContext.createFromContext(c);
+        TestParentCfgClient parent = getTestParent(ctx, "test parent 1");
+        TestChildCfgClient child = parent.getTestChild("test child 2");
+
+        // Test normalization.
+        assertSetEquals(child.getAggregationProperty(), "LDAP Connection Handler");
+        assertSetEquals(child.getAggregationProperty(), "  LDAP   Connection  Handler ");
+        assertSetEquals(child.getAggregationProperty(), "  ldap connection HANDLER ");
+    }
+
+    /**
+     * 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 testAggregationMultiple() throws Exception {
+        MockLDAPConnection c = new MockLDAPConnection();
+        c.importLDIF(TEST_LDIF);
+        ManagementContext ctx = LDAPManagementContext.createFromContext(c);
+        TestParentCfgClient parent = getTestParent(ctx, "test parent 1");
+        TestChildCfgClient child = parent.getTestChild("test child 3");
+        assertSetEquals(child.getAggregationProperty(), "LDAPS Connection Handler", "LDAP Connection Handler");
+    }
+
+    /**
+     * 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 {
+        MockLDAPConnection c = new MockLDAPConnection();
+        c.importLDIF(TEST_LDIF);
+        ManagementContext ctx = LDAPManagementContext.createFromContext(c);
+        TestParentCfgClient parent = getTestParent(ctx, "test parent 1");
+
+        try {
+            parent.getTestChild("test child 4");
+            Assert.fail("Unexpectedly retrieved test child 4" + " when it had a bad aggregation value");
+        } catch (ManagedObjectDecodingException e) {
+            Collection<PropertyException> causes = e.getCauses();
+            Assert.assertEquals(causes.size(), 1);
+
+            Throwable cause = causes.iterator().next();
+            if (cause instanceof IllegalPropertyValueStringException) {
+                IllegalPropertyValueStringException pe = (IllegalPropertyValueStringException) cause;
+                Assert.assertEquals(pe.getPropertyDefinition(), TestChildCfgDefn.getInstance()
+                        .getAggregationPropertyPropertyDefinition());
+                Assert.assertEquals(pe.getIllegalValueString(), "cn=LDAP Connection Handler, cn=bad rdn, cn=config");
+            } else {
+                // Got an unexpected cause.
+                throw e;
+            }
+        }
+    }
+
+    /**
+     * Tests creation of a child managed object with a single reference.
+     *
+     * @throws Exception
+     *             If an unexpected error occurred.
+     */
+    @Test
+    public void testCreateChildManagedObject() throws Exception {
+        CreateEntryMockLDAPConnection c = new CreateEntryMockLDAPConnection(
+                "cn=test child new,cn=test children,cn=test parent 1,cn=test parents,cn=config");
+        c.importLDIF(TEST_LDIF);
+        c.addExpectedAttribute("cn", "test child new");
+        c.addExpectedAttribute("objectClass", "top", "ds-cfg-test-child-dummy");
+        c.addExpectedAttribute("ds-cfg-enabled", "true");
+        c.addExpectedAttribute("ds-cfg-java-class",
+                "org.opends.server.extensions.UserDefinedVirtualAttributeProvider");
+        c.addExpectedAttribute("ds-cfg-attribute-type", "description");
+        c.addExpectedAttribute("ds-cfg-rotation-policy",
+                "cn=LDAP Connection Handler,cn=connection handlers, cn=config");
+
+        ManagementContext ctx = LDAPManagementContext.createFromContext(c);
+        TestParentCfgClient parent = getTestParent(ctx, "test parent 1");
+        TestChildCfgClient child = parent.createTestChild(TestChildCfgDefn.getInstance(), "test child new", null);
+        child.setMandatoryBooleanProperty(true);
+        child.setMandatoryReadOnlyAttributeTypeProperty(DirectoryServer.getAttributeType("description"));
+        child.setAggregationProperty(Collections.singleton("LDAP Connection Handler"));
+        child.commit();
+
+        c.assertEntryIsCreated();
+    }
+
+    /**
+     * Tests modification of a child managed object so that it has a different
+     * reference.
+     *
+     * @throws Exception
+     *             If an unexpected error occurred.
+     */
+    @Test
+    public void testModifyChildManagedObject() throws Exception {
+        ModifyEntryMockLDAPConnection c = new ModifyEntryMockLDAPConnection(
+                "cn=test child 2,cn=test children,cn=test parent 1,cn=test parents,cn=config");
+        c.importLDIF(TEST_LDIF);
+        c.addExpectedModification("ds-cfg-rotation-policy",
+                "cn=LDAPS Connection Handler,cn=connection handlers, cn=config",
+                "cn=JMX Connection Handler,cn=connection handlers, cn=config");
+        ManagementContext ctx = LDAPManagementContext.createFromContext(c);
+        TestParentCfgClient parent = getTestParent(ctx, "test parent 1");
+        TestChildCfgClient child = parent.getTestChild("test child 2");
+        child.setAggregationProperty(Arrays.asList("LDAPS Connection Handler", "JMX Connection Handler"));
+        child.commit();
+        Assert.assertTrue(c.isEntryModified());
+    }
+
+    // Retrieve the named test parent managed object.
+    private TestParentCfgClient getTestParent(ManagementContext context, String name) throws Exception {
+        ManagedObject<RootCfgClient> root = context.getRootConfigurationManagedObject();
+        return root.getChild(TestCfg.getTestOneToManyParentRelationDefinition(), name).getConfiguration();
+    }
+
+    // Asserts that the actual set of DNs contains the expected values.
+    private void assertSetEquals(SortedSet<String> actual, String... expected) {
+        SortedSet<String> values = new TreeSet<String>(TestChildCfgDefn.getInstance()
+                .getAggregationPropertyPropertyDefinition());
+        if (expected != null) {
+            for (String value : expected) {
+                values.add(value);
+            }
+        }
+        Assert.assertEquals((Object) actual, (Object) values);
+    }
+
+}
diff --git a/opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/client/ldap/CreateEntryMockLDAPConnection.java b/opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/client/ldap/CreateEntryMockLDAPConnection.java
new file mode 100644
index 0000000..f26ea14
--- /dev/null
+++ b/opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/client/ldap/CreateEntryMockLDAPConnection.java
@@ -0,0 +1,112 @@
+/*
+ * 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 2008 Sun Microsystems, Inc.
+ */
+package org.opends.server.admin.client.ldap;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.forgerock.opendj.ldap.Attribute;
+import org.forgerock.opendj.ldap.DN;
+import org.forgerock.opendj.ldap.Entry;
+import org.forgerock.opendj.ldap.ErrorResultException;
+import org.testng.Assert;
+
+import com.forgerock.opendj.util.Validator;
+
+/**
+ * A mock LDAP connection which is used to verify that an add operation was
+ * requested and that it has the correct parameters.
+ */
+public final class CreateEntryMockLDAPConnection extends MockLDAPConnection {
+
+    // Detect multiple calls.
+    private boolean alreadyAdded = false;
+
+    // The expected set of attributes (attribute name -> list of
+    // values).
+    private final Map<String, List<String>> attributes = new HashMap<String, List<String>>();
+
+    // The expected DN.
+    private final DN expectedDN;
+
+    /**
+     * Create a new mock ldap connection for detecting add operations.
+     *
+     * @param dn
+     *            The expected DN of the entry to be added.
+     */
+    public CreateEntryMockLDAPConnection(String dn) {
+        this.expectedDN = DN.valueOf(dn);
+    }
+
+    /**
+     * Add an attribute which should be part of the add operation.
+     *
+     * @param expectedName
+     *            The name of the expected attribute.
+     * @param expectedValues
+     *            The attribute's expected values (never empty).
+     */
+    public void addExpectedAttribute(String expectedName, String... expectedValues) {
+        Validator.ensureNotNull(expectedName);
+        Validator.ensureNotNull(expectedValues);
+        Validator.ensureTrue(expectedValues.length > 0, "should have at least one expected value");
+        attributes.put(expectedName, Arrays.asList(expectedValues));
+    }
+
+    /**
+     * Asserts that the entry was created.
+     */
+    public void assertEntryIsCreated() {
+        Assert.assertTrue(alreadyAdded);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void createEntry(Entry entry) throws ErrorResultException {
+        Assert.assertFalse(alreadyAdded);
+        Assert.assertEquals(entry.getName(), expectedDN);
+
+        Map<String, List<String>> expected = new HashMap<String, List<String>>(this.attributes);
+        for (Attribute attribute : entry.getAllAttributes()) {
+            String attrName = attribute.getAttributeDescription().getAttributeType().getNameOrOID();
+            List<String> values = expected.remove(attrName);
+            if (values == null) {
+                Assert.fail("Unexpected attribute " + attrName);
+            }
+            assertAttributeEquals(attribute, values);
+        }
+        if (!expected.isEmpty()) {
+            Assert.fail("Missing expected attributes: " + expected.keySet());
+        }
+
+        alreadyAdded = true;
+    }
+}
diff --git a/opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/client/ldap/DeleteSubtreeMockLDAPConnection.java b/opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/client/ldap/DeleteSubtreeMockLDAPConnection.java
new file mode 100644
index 0000000..aa980df
--- /dev/null
+++ b/opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/client/ldap/DeleteSubtreeMockLDAPConnection.java
@@ -0,0 +1,70 @@
+/*
+ * 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 2008 Sun Microsystems, Inc.
+ */
+package org.opends.server.admin.client.ldap;
+
+import org.forgerock.opendj.ldap.DN;
+import org.forgerock.opendj.ldap.ErrorResultException;
+import org.testng.Assert;
+
+/**
+ * A mock LDAP connection which is used to verify that a delete subtree takes
+ * place.
+ */
+public final class DeleteSubtreeMockLDAPConnection extends MockLDAPConnection {
+
+    // Detect multiple calls.
+    private boolean alreadyDeleted = false;
+
+    // The expected DN.
+    private final DN expectedDN;
+
+    /**
+     * Create a new mock ldap connection for detecting subtree deletes.
+     *
+     * @param dn
+     *            The expected subtree DN.
+     */
+    public DeleteSubtreeMockLDAPConnection(String dn) {
+        this.expectedDN = DN.valueOf(dn);
+    }
+
+    /**
+     * Asserts that the subtree was deleted.
+     */
+    public void assertSubtreeIsDeleted() {
+        Assert.assertTrue(alreadyDeleted);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void deleteSubtree(DN dn) throws ErrorResultException {
+        Assert.assertFalse(alreadyDeleted);
+        Assert.assertEquals(dn, expectedDN);
+        alreadyDeleted = true;
+    }
+}
diff --git a/opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/client/ldap/LDAPClientTest.java b/opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/client/ldap/LDAPClientTest.java
new file mode 100644
index 0000000..f2d93ab
--- /dev/null
+++ b/opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/client/ldap/LDAPClientTest.java
@@ -0,0 +1,889 @@
+/*
+ * 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 2008 Sun Microsystems, Inc.
+ */
+package org.opends.server.admin.client.ldap;
+
+import static org.fest.assertions.Assertions.*;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.SortedSet;
+
+import org.forgerock.opendj.admin.client.RootCfgClient;
+import org.forgerock.opendj.ldap.DN;
+import org.forgerock.opendj.ldap.Entry;
+import org.forgerock.opendj.ldap.ErrorResultException;
+import org.forgerock.opendj.ldap.ResultCode;
+import org.forgerock.opendj.ldap.responses.SearchResultEntry;
+import org.opends.server.admin.AdminTestCase;
+import org.opends.server.admin.Constraint;
+import org.opends.server.admin.ManagedObjectAlreadyExistsException;
+import org.opends.server.admin.ManagedObjectNotFoundException;
+import org.opends.server.admin.TestCfg;
+import org.opends.server.admin.TestChildCfgClient;
+import org.opends.server.admin.TestChildCfgDefn;
+import org.opends.server.admin.TestParentCfgClient;
+import org.opends.server.admin.TestParentCfgDefn;
+import org.opends.server.admin.client.ManagedObject;
+import org.opends.server.admin.client.ManagementContext;
+import org.opends.server.admin.client.OperationRejectedException;
+import org.opends.server.core.DirectoryServer;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+@SuppressWarnings("javadoc")
+@Test(singleThreaded = true)
+public final class LDAPClientTest extends AdminTestCase {
+
+    // Test LDIF.
+    private static final String[] TEST_LDIF = new String[] {
+            // Base entries.
+            "dn: cn=config",
+            "objectclass: top",
+            "objectclass: ds-cfg-branch",
+            "cn: config",
+            "",
+            "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-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",
+            "",
+            // 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-test-parent-dummy",
+            "cn: test parent 2",
+            "ds-cfg-enabled: true",
+            "ds-cfg-java-class: org.opends.server.extensions.UserDefinedVirtualAttributeProvider",
+            "ds-cfg-attribute-type: description",
+            "ds-cfg-base-dn: dc=default value p2v1,dc=com",
+            "ds-cfg-base-dn: dc=default value p2v2,dc=com",
+            "",
+            // Parent 3 - overrides default values for
+            // optional-multi-valued-dn-property.
+            "dn: cn=test parent 3,cn=test parents,cn=config",
+            "objectclass: top",
+            "objectclass: ds-cfg-test-parent-dummy",
+            "cn: test parent 3",
+            "ds-cfg-enabled: true",
+            "ds-cfg-java-class: org.opends.server.extensions.UserDefinedVirtualAttributeProvider",
+            "ds-cfg-attribute-type: description",
+            "ds-cfg-base-dn: dc=default value p3v1,dc=com",
+            "ds-cfg-base-dn: dc=default value p3v2,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: multiple children",
+            "",
+            "dn:cn=test children,cn=test parent 2,cn=test parents,cn=config",
+            "objectclass: top",
+            "objectclass: ds-cfg-branch",
+            "cn: multiple children",
+            "",
+            // Child 1 inherits defaults for both
+            // optional-multi-valued-dn-property1 and
+            // optional-multi-valued-dn-property2.
+            "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",
+            "",
+            // Child 2 inherits defaults for
+            // optional-multi-valued-dn-property2.
+            "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-base-dn: dc=default value c2v1,dc=com",
+            "ds-cfg-base-dn: dc=default value c2v2,dc=com",
+            "",
+            // Child 3 overrides defaults for
+            // optional-multi-valued-dn-property1 and
+            // optional-multi-valued-dn-property2.
+            "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-base-dn: dc=default value c3v1,dc=com",
+            "ds-cfg-base-dn: dc=default value c3v2,dc=com",
+            "ds-cfg-group-dn: dc=default value c3v3,dc=com",
+            "ds-cfg-group-dn: dc=default value c3v4,dc=com",
+            "",
+            // Child 4 inherits overridden defaults for both
+            // optional-multi-valued-dn-property1 and
+            // optional-multi-valued-dn-property2.
+            "dn: cn=test child 1,cn=test children,cn=test parent 2,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", "", };
+
+    /**
+     * Provide valid naming exception to client API exception mappings.
+     *
+     * @return Returns valid naming exception to client API exception mappings.
+     */
+    @DataProvider(name = "createManagedObjectExceptions")
+    public Object[][] createManagedObjectExceptions() {
+        return new Object[][] {
+            // result code corresponding to exception thrown, expected exception, expected code result
+            { ResultCode.PROTOCOL_ERROR, ErrorResultException.class, ResultCode.PROTOCOL_ERROR },
+            { ResultCode.UNAVAILABLE , ErrorResultException.class, ResultCode.UNAVAILABLE },
+            { ResultCode.ENTRY_ALREADY_EXISTS, ManagedObjectAlreadyExistsException.class, null },
+            { ResultCode.INSUFFICIENT_ACCESS_RIGHTS, ErrorResultException.class,
+                    ResultCode.INSUFFICIENT_ACCESS_RIGHTS },
+            { ResultCode.UNWILLING_TO_PERFORM, OperationRejectedException.class, null }
+        };
+    }
+
+    /**
+     * Provide valid naming exception to client API exception mappings.
+     *
+     * @return Returns valid naming exception to client API exception mappings.
+     */
+    @DataProvider(name = "getManagedObjectExceptions")
+    public Object[][] getManagedObjectExceptions() {
+        return new Object[][] {
+            // result code corresponding to exception thrown, expected exception, expected code result
+            { ResultCode.PROTOCOL_ERROR, ErrorResultException.class, ResultCode.PROTOCOL_ERROR },
+            { ResultCode.UNAVAILABLE, ErrorResultException.class, ResultCode.UNAVAILABLE },
+            { ResultCode.NO_SUCH_OBJECT, ManagedObjectNotFoundException.class, null },
+            { ResultCode.INSUFFICIENT_ACCESS_RIGHTS, ErrorResultException.class,
+                ResultCode.INSUFFICIENT_ACCESS_RIGHTS },
+            { ResultCode.UNWILLING_TO_PERFORM, ErrorResultException.class,
+                ResultCode.UNWILLING_TO_PERFORM }
+        };
+    }
+
+    @BeforeClass
+    public void setUp() throws Exception {
+        disableClassValidationForProperties();
+        TestCfg.setUp();
+    }
+
+
+    @AfterClass
+    public void tearDown() {
+        TestCfg.cleanup();
+    }
+
+    /**
+     * Tests creation of a child managed object.
+     *
+     * @throws Exception
+     *             If an unexpected error occurred.
+     */
+    @Test
+    public void testCreateChildManagedObject() throws Exception {
+        CreateEntryMockLDAPConnection c = new CreateEntryMockLDAPConnection(
+                "cn=test child new,cn=test children,cn=test parent 1,cn=test parents,cn=config");
+        c.importLDIF(TEST_LDIF);
+        c.addExpectedAttribute("cn", "test child new");
+        c.addExpectedAttribute("objectClass", "top", "ds-cfg-test-child-dummy");
+        c.addExpectedAttribute("ds-cfg-enabled", "true");
+        c.addExpectedAttribute("ds-cfg-java-class",
+                "org.opends.server.extensions.UserDefinedVirtualAttributeProvider");
+        c.addExpectedAttribute("ds-cfg-attribute-type", "description");
+
+        ManagementContext ctx = LDAPManagementContext.createFromContext(c);
+        TestParentCfgClient parent = getTestParent(ctx, "test parent 1");
+        TestChildCfgClient child = parent.createTestChild(TestChildCfgDefn.getInstance(), "test child new", null);
+        child.setMandatoryBooleanProperty(true);
+        child.setMandatoryReadOnlyAttributeTypeProperty(DirectoryServer.getAttributeType("description"));
+        child.commit();
+
+        c.assertEntryIsCreated();
+    }
+
+    /**
+     * Tests creation of a top-level managed object using fails when an
+     * underlying exception occurs.
+     */
+    @Test(dataProvider = "createManagedObjectExceptions")
+    public void testCreateManagedObjectException(final ResultCode resultCodeOfThrownException,
+            Class<? extends Exception> expectedExceptionClass, ResultCode expectedCode) {
+        MockLDAPConnection conn = new MockLDAPConnection() {
+
+            @Override
+            public void createEntry(Entry entry) throws ErrorResultException {
+                throw ErrorResultException.newErrorResult(resultCodeOfThrownException);
+            }
+
+        };
+        conn.importLDIF(TEST_LDIF);
+        ManagementContext ctx = LDAPManagementContext.createFromContext(conn);
+        try {
+            TestParentCfgClient parent = createTestParent(ctx, "test parent new");
+            parent.setMandatoryBooleanProperty(true);
+            parent.setMandatoryReadOnlyAttributeTypeProperty(DirectoryServer.getAttributeType("description"));
+            parent.commit();
+        } catch (Exception e) {
+            if (expectedExceptionClass.equals(ErrorResultException.class)) {
+                assertThat(e).isInstanceOf(ErrorResultException.class);
+                assertThat(((ErrorResultException) e).getResult().getResultCode()).isEqualTo(expectedCode);
+            }
+            else {
+                assertThat(e).isInstanceOf(expectedExceptionClass);
+            }
+        }
+    }
+
+    /**
+     * Tests creation of a top-level managed object.
+     *
+     * @throws Exception
+     *             If an unexpected error occurred.
+     */
+    @Test
+    public void testCreateTopLevelManagedObject() throws Exception {
+        CreateEntryMockLDAPConnection c = new CreateEntryMockLDAPConnection(
+                "cn=test parent new,cn=test parents,cn=config");
+        c.importLDIF(TEST_LDIF);
+        c.addExpectedAttribute("cn", "test parent new");
+        c.addExpectedAttribute("objectClass", "top", "ds-cfg-test-parent-dummy");
+        c.addExpectedAttribute("ds-cfg-enabled", "true");
+        c.addExpectedAttribute("ds-cfg-java-class",
+                "org.opends.server.extensions.SomeVirtualAttributeProvider");
+        c.addExpectedAttribute("ds-cfg-attribute-type", "description");
+
+        ManagementContext ctx = LDAPManagementContext.createFromContext(c);
+        TestParentCfgClient parent = createTestParent(ctx, "test parent new");
+        parent.setMandatoryBooleanProperty(true);
+        parent.setMandatoryReadOnlyAttributeTypeProperty(DirectoryServer.getAttributeType("description"));
+        parent.commit();
+        c.assertEntryIsCreated();
+    }
+
+    /**
+     * Tests retrieval of a child managed object with non-default values.
+     *
+     * @throws Exception
+     *             If an unexpected error occurred.
+     */
+    @Test
+    public void testGetChildManagedObject() throws Exception {
+        MockLDAPConnection c = new MockLDAPConnection();
+        c.importLDIF(TEST_LDIF);
+        ManagementContext ctx = LDAPManagementContext.createFromContext(c);
+        TestParentCfgClient parent = getTestParent(ctx, "test parent 1");
+        TestChildCfgClient child = parent.getTestChild("test child 3");
+        Assert.assertEquals(child.isMandatoryBooleanProperty(), Boolean.TRUE);
+        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");
+    }
+
+    /**
+     * Tests retrieval of a child managed object with default values.
+     *
+     * @throws Exception
+     *             If an unexpected error occurred.
+     */
+    @Test
+    public void testGetChildManagedObjectDefault() throws Exception {
+        MockLDAPConnection c = new MockLDAPConnection();
+        c.importLDIF(TEST_LDIF);
+        ManagementContext ctx = LDAPManagementContext.createFromContext(c);
+        TestParentCfgClient parent = getTestParent(ctx, "test parent 1");
+        TestChildCfgClient child = parent.getTestChild("test child 1");
+        Assert.assertEquals(child.isMandatoryBooleanProperty(), Boolean.TRUE);
+        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.assertEquals(child.isMandatoryBooleanProperty(), Boolean.TRUE);
+    }
+
+    /**
+     * Tests retrieval of a top-level managed object fails when an underlying
+     * ErrorResultException occurs.
+     *
+     * @param cause
+     *            The ErrorResultException cause of the failure.
+     * @param expected
+     *            The expected client API exception class.
+     */
+    @Test(dataProvider = "getManagedObjectExceptions")
+    public void testGetManagedObjectException(final ResultCode resultCodeOfThrownException,
+            final Class<? extends Exception> expectedExceptionClass, final ResultCode expectedCode) {
+        MockLDAPConnection c = new MockLDAPConnection() {
+
+            @Override
+            public SearchResultEntry readEntry(DN dn, Collection<String> attrIds) throws ErrorResultException {
+                throw ErrorResultException.newErrorResult(resultCodeOfThrownException);
+            }
+
+        };
+        c.importLDIF(TEST_LDIF);
+        ManagementContext ctx = LDAPManagementContext.createFromContext(c);
+        try {
+            getTestParent(ctx, "test parent 2");
+        } catch (Exception e) {
+            if (expectedExceptionClass.equals(ErrorResultException.class)) {
+                assertThat(e).isInstanceOf(ErrorResultException.class);
+                assertThat(((ErrorResultException) e).getResult().getResultCode()).isEqualTo(expectedCode);
+            }
+            else {
+                assertThat(e).isInstanceOf(expectedExceptionClass);
+            }
+        }
+    }
+
+    /**
+     * Tests retrieval of a top-level managed object with non-default values.
+     *
+     * @throws Exception
+     *             If an unexpected error occurred.
+     */
+    @Test
+    public void testGetTopLevelManagedObject() throws Exception {
+        MockLDAPConnection c = new MockLDAPConnection();
+        c.importLDIF(TEST_LDIF);
+        ManagementContext ctx = LDAPManagementContext.createFromContext(c);
+        TestParentCfgClient parent = getTestParent(ctx, "test parent 2");
+        Assert.assertEquals(parent.isMandatoryBooleanProperty(), Boolean.TRUE);
+        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");
+    }
+
+    /**
+     * Tests retrieval of a top-level managed object with default values.
+     *
+     * @throws Exception
+     *             If an unexpected error occurred.
+     */
+    @Test
+    public void testGetTopLevelManagedObjectDefault() throws Exception {
+        MockLDAPConnection c = new MockLDAPConnection();
+        c.importLDIF(TEST_LDIF);
+        ManagementContext ctx = LDAPManagementContext.createFromContext(c);
+        TestParentCfgClient parent = getTestParent(ctx, "test parent 1");
+        Assert.assertEquals(parent.isMandatoryBooleanProperty(), Boolean.TRUE);
+        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 retrieval of relative inherited default values.
+     *
+     * @throws Exception
+     *             If an unexpected error occurred.
+     */
+    @Test
+    public void testInheritedDefaultValues1() throws Exception {
+        CreateEntryMockLDAPConnection c = new CreateEntryMockLDAPConnection(
+                "cn=test child new,cn=test children,cn=test parent 1,cn=test parents,cn=config");
+        c.importLDIF(TEST_LDIF);
+        c.addExpectedAttribute("cn", "test child new");
+        c.addExpectedAttribute("objectClass", "top", "ds-cfg-test-child-dummy");
+        c.addExpectedAttribute("ds-cfg-enabled", "true");
+        c.addExpectedAttribute("ds-cfg-java-class",
+                "org.opends.server.extensions.UserDefinedVirtualAttributeProvider");
+        c.addExpectedAttribute("ds-cfg-attribute-type", "description");
+
+        ManagementContext ctx = LDAPManagementContext.createFromContext(c);
+        TestParentCfgClient parent = getTestParent(ctx, "test parent 1");
+        TestChildCfgClient child = parent.createTestChild(TestChildCfgDefn.getInstance(), "test child new", null);
+
+        // Check pre-commit values.
+        Assert.assertEquals(child.isMandatoryBooleanProperty(), null);
+        Assert.assertEquals(child.getMandatoryClassProperty(),
+                "org.opends.server.extensions.UserDefinedVirtualAttributeProvider");
+        Assert.assertEquals(child.getMandatoryReadOnlyAttributeTypeProperty(), null);
+        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");
+
+        // Check that the default values are not committed.
+        child.setMandatoryBooleanProperty(true);
+        child.setMandatoryReadOnlyAttributeTypeProperty(DirectoryServer.getAttributeType("description"));
+        child.commit();
+
+        c.assertEntryIsCreated();
+    }
+
+    /**
+     * Tests retrieval of relative inherited default values.
+     *
+     * @throws Exception
+     *             If an unexpected error occurred.
+     */
+    @Test
+    public void testInheritedDefaultValues2() throws Exception {
+        CreateEntryMockLDAPConnection c = new CreateEntryMockLDAPConnection(
+                "cn=test child new,cn=test children,cn=test parent 2,cn=test parents,cn=config");
+        c.importLDIF(TEST_LDIF);
+        c.addExpectedAttribute("cn", "test child new");
+        c.addExpectedAttribute("objectClass", "top", "ds-cfg-test-child-dummy");
+        c.addExpectedAttribute("ds-cfg-enabled", "true");
+        c.addExpectedAttribute("ds-cfg-java-class",
+                "org.opends.server.extensions.UserDefinedVirtualAttributeProvider");
+        c.addExpectedAttribute("ds-cfg-attribute-type", "description");
+
+        ManagementContext ctx = LDAPManagementContext.createFromContext(c);
+        TestParentCfgClient parent = getTestParent(ctx, "test parent 2");
+        TestChildCfgClient child = parent.createTestChild(TestChildCfgDefn.getInstance(), "test child new", null);
+
+        // Check pre-commit values.
+        Assert.assertEquals(child.isMandatoryBooleanProperty(), null);
+        Assert.assertEquals(child.getMandatoryClassProperty(),
+                "org.opends.server.extensions.UserDefinedVirtualAttributeProvider");
+        Assert.assertEquals(child.getMandatoryReadOnlyAttributeTypeProperty(), null);
+        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");
+
+        // Check that the default values are not committed.
+        child.setMandatoryBooleanProperty(true);
+        child.setMandatoryReadOnlyAttributeTypeProperty(DirectoryServer.getAttributeType("description"));
+        child.commit();
+
+        c.assertEntryIsCreated();
+    }
+
+    /**
+     * Tests listing of child managed objects.
+     *
+     * @throws Exception
+     *             If an unexpected error occurred.
+     */
+    @Test
+    public void testListChildManagedObjects() throws Exception {
+        MockLDAPConnection c = new MockLDAPConnection();
+        c.importLDIF(TEST_LDIF);
+        ManagementContext ctx = LDAPManagementContext.createFromContext(c);
+        TestParentCfgClient parent = getTestParent(ctx, "test parent 1");
+        String[] actual = parent.listTestChildren();
+        String[] expected = new String[] { "test child 1", "test child 2", "test child 3" };
+        Assert.assertEqualsNoOrder(actual, expected);
+    }
+
+    /**
+     * Tests listing of child managed objects when their are not any.
+     *
+     * @throws Exception
+     *             If an unexpected error occurred.
+     */
+    @Test
+    public void testListChildManagedObjectsEmpty() throws Exception {
+        MockLDAPConnection c = new MockLDAPConnection();
+        c.importLDIF(TEST_LDIF);
+        ManagementContext ctx = LDAPManagementContext.createFromContext(c);
+        TestParentCfgClient parent = getTestParent(ctx, "test parent 3");
+        String[] actual = parent.listTestChildren();
+        String[] expected = new String[] {};
+        Assert.assertEqualsNoOrder(actual, expected);
+    }
+
+    /**
+     * Tests listing of top level managed objects.
+     *
+     * @throws Exception
+     *             If an unexpected error occurred.
+     */
+    @Test
+    public void testListTopLevelManagedObjects() throws Exception {
+        MockLDAPConnection c = new MockLDAPConnection();
+        c.importLDIF(TEST_LDIF);
+        ManagementContext ctx = LDAPManagementContext.createFromContext(c);
+        String[] actual = listTestParents(ctx);
+        String[] expected = new String[] { "test parent 1", "test parent 2", "test parent 3" };
+        Assert.assertEqualsNoOrder(actual, expected);
+    }
+
+    /**
+     * Tests listing of top level managed objects when their are not any.
+     *
+     * @throws Exception
+     *             If an unexpected error occurred.
+     */
+    @Test
+    public void testListTopLevelManagedObjectsEmpty() throws Exception {
+        String[] ldif = {};
+        MockLDAPConnection c = new MockLDAPConnection();
+        c.importLDIF(ldif);
+        ManagementContext ctx = LDAPManagementContext.createFromContext(c);
+        String[] actual = listTestParents(ctx);
+        String[] expected = new String[] {};
+        Assert.assertEqualsNoOrder(actual, expected);
+    }
+
+    /**
+     * Tests modification of a child managed object.
+     *
+     * @throws Exception
+     *             If an unexpected error occurred.
+     */
+    @Test
+    public void testModifyChildManagedObjectResetToDefault() throws Exception {
+        ModifyEntryMockLDAPConnection c = new ModifyEntryMockLDAPConnection(
+                "cn=test child 2,cn=test children,cn=test parent 1,cn=test parents,cn=config");
+        c.importLDIF(TEST_LDIF);
+        c.addExpectedModification("ds-cfg-base-dn");
+        ManagementContext ctx = LDAPManagementContext.createFromContext(c);
+        TestParentCfgClient parent = getTestParent(ctx, "test parent 1");
+        TestChildCfgClient child = parent.getTestChild("test child 2");
+        child.setOptionalMultiValuedDNProperty1(Collections.<DN> emptySet());
+        child.commit();
+        Assert.assertTrue(c.isEntryModified());
+    }
+
+    /**
+     * Tests modification of a top-level managed object.
+     *
+     * @throws Exception
+     *             If an unexpected error occurred.
+     */
+    @Test
+    public void testModifyTopLevelManagedObjectNoChanges() throws Exception {
+        ModifyEntryMockLDAPConnection c = new ModifyEntryMockLDAPConnection(
+                "cn=test parent 1,cn=test parents,cn=config");
+        c.importLDIF(TEST_LDIF);
+        ManagementContext ctx = LDAPManagementContext.createFromContext(c);
+        TestParentCfgClient parent = getTestParent(ctx, "test parent 1");
+        parent.commit();
+        Assert.assertFalse(c.isEntryModified());
+    }
+
+    /**
+     * Tests modification of a top-level managed object.
+     *
+     * @throws Exception
+     *             If an unexpected error occurred.
+     */
+    @Test
+    public void testModifyTopLevelManagedObjectWithChanges() throws Exception {
+        ModifyEntryMockLDAPConnection c = new ModifyEntryMockLDAPConnection(
+                "cn=test parent 1,cn=test parents,cn=config");
+        c.importLDIF(TEST_LDIF);
+        c.addExpectedModification("ds-cfg-enabled", "false");
+        c.addExpectedModification("ds-cfg-base-dn", "dc=mod1,dc=com", "dc=mod2,dc=com");
+        ManagementContext ctx = LDAPManagementContext.createFromContext(c);
+        TestParentCfgClient parent = getTestParent(ctx, "test parent 1");
+        parent.setMandatoryBooleanProperty(false);
+        parent.setOptionalMultiValuedDNProperty(Arrays.asList(DN.valueOf("dc=mod1,dc=com"),
+                DN.valueOf("dc=mod2,dc=com")));
+        parent.commit();
+        Assert.assertTrue(c.isEntryModified());
+    }
+
+    /**
+     * Tests removal of a child managed object.
+     *
+     * @throws Exception
+     *             If an unexpected error occurred.
+     */
+    @Test
+    public void testRemoveChildManagedObject() throws Exception {
+        DeleteSubtreeMockLDAPConnection c = new DeleteSubtreeMockLDAPConnection(
+                "cn=test child 1,cn=test children,cn=test parent 1,cn=test parents,cn=config");
+        c.importLDIF(TEST_LDIF);
+        ManagementContext ctx = LDAPManagementContext.createFromContext(c);
+        TestParentCfgClient parent = getTestParent(ctx, "test parent 1");
+        parent.removeTestChild("test child 1");
+        c.assertSubtreeIsDeleted();
+    }
+
+    /**
+     * Tests removal of a top-level managed object.
+     *
+     * @throws Exception
+     *             If an unexpected error occurred.
+     */
+    @Test
+    public void testRemoveTopLevelManagedObject() throws Exception {
+        DeleteSubtreeMockLDAPConnection c = new DeleteSubtreeMockLDAPConnection(
+                "cn=test parent 1,cn=test parents,cn=config");
+        c.importLDIF(TEST_LDIF);
+        ManagementContext ctx = LDAPManagementContext.createFromContext(c);
+        removeTestParent(ctx, "test parent 1");
+        c.assertSubtreeIsDeleted();
+    }
+
+    /**
+     * Tests creation of a child managed object succeeds when registered add
+     * constraints succeed.
+     *
+     * @throws Exception
+     *             If an unexpected error occurred.
+     */
+    @Test
+    public void testAddConstraintSuccess() throws Exception {
+        Constraint constraint = new MockConstraint(true, false, false);
+        TestCfg.addConstraint(constraint);
+
+        try {
+            CreateEntryMockLDAPConnection c = new CreateEntryMockLDAPConnection(
+                    "cn=test child new,cn=test children,cn=test parent 1,cn=test parents,cn=config");
+            c.importLDIF(TEST_LDIF);
+            c.addExpectedAttribute("cn", "test child new");
+            c.addExpectedAttribute("objectClass", "top", "ds-cfg-test-child-dummy");
+            c.addExpectedAttribute("ds-cfg-enabled", "true");
+            c.addExpectedAttribute("ds-cfg-java-class",
+                    "org.opends.server.extensions.UserDefinedVirtualAttributeProvider");
+            c.addExpectedAttribute("ds-cfg-attribute-type", "description");
+
+            ManagementContext ctx = LDAPManagementContext.createFromContext(c);
+            TestParentCfgClient parent = getTestParent(ctx, "test parent 1");
+            TestChildCfgClient child = parent.createTestChild(TestChildCfgDefn.getInstance(), "test child new", null);
+            child.setMandatoryBooleanProperty(true);
+            child.setMandatoryReadOnlyAttributeTypeProperty(DirectoryServer.getAttributeType("description"));
+            child.commit();
+
+            c.assertEntryIsCreated();
+        } finally {
+            // Clean up.
+            TestCfg.removeConstraint(constraint);
+        }
+    }
+
+    /**
+     * Tests creation of a child managed object fails when registered add
+     * constraints fail.
+     *
+     * @throws Exception
+     *             If an unexpected error occurred.
+     */
+    @Test(expectedExceptions = OperationRejectedException.class)
+    public void testAddConstraintFail() throws Exception {
+        Constraint constraint = new MockConstraint(false, true, true);
+        TestCfg.addConstraint(constraint);
+
+        try {
+            CreateEntryMockLDAPConnection conn = new CreateEntryMockLDAPConnection(
+                    "cn=test child new,cn=test children,cn=test parent 1,cn=test parents,cn=config");
+            conn.importLDIF(TEST_LDIF);
+            conn.addExpectedAttribute("cn", "test child new");
+            conn.addExpectedAttribute("objectClass", "top", "ds-cfg-test-child-dummy");
+            conn.addExpectedAttribute("ds-cfg-enabled", "true");
+            conn.addExpectedAttribute("ds-cfg-java-class",
+                    "org.opends.server.extensions.UserDefinedVirtualAttributeProvider");
+            conn.addExpectedAttribute("ds-cfg-attribute-type", "description");
+
+            ManagementContext ctx = LDAPManagementContext.createFromContext(conn);
+            TestParentCfgClient parent = getTestParent(ctx, "test parent 1");
+            TestChildCfgClient child = parent.createTestChild(TestChildCfgDefn.getInstance(), "test child new", null);
+            child.setMandatoryBooleanProperty(true);
+            child.setMandatoryReadOnlyAttributeTypeProperty(DirectoryServer.getAttributeType("description"));
+            child.commit();
+            Assert.fail("The add constraint failed to prevent creation of the managed object");
+        } finally {
+            // Clean up.
+            TestCfg.removeConstraint(constraint);
+        }
+    }
+
+    /**
+     * Tests removal of a child managed object succeeds when registered remove
+     * constraints succeed.
+     *
+     * @throws Exception
+     *             If an unexpected error occurred.
+     */
+    @Test
+    public void testRemoveConstraintSuccess() throws Exception {
+        Constraint constraint = new MockConstraint(false, false, true);
+        TestCfg.addConstraint(constraint);
+
+        try {
+            DeleteSubtreeMockLDAPConnection c = new DeleteSubtreeMockLDAPConnection(
+                    "cn=test child 1,cn=test children,cn=test parent 1,cn=test parents,cn=config");
+            c.importLDIF(TEST_LDIF);
+            ManagementContext ctx = LDAPManagementContext.createFromContext(c);
+            TestParentCfgClient parent = getTestParent(ctx, "test parent 1");
+            parent.removeTestChild("test child 1");
+            c.assertSubtreeIsDeleted();
+        } finally {
+            // Clean up.
+            TestCfg.removeConstraint(constraint);
+        }
+    }
+
+    /**
+     * Tests removal of a child managed object fails when registered remove
+     * constraints fails.
+     *
+     * @throws Exception
+     *             If an unexpected error occurred.
+     */
+    @Test(expectedExceptions = OperationRejectedException.class)
+    public void testRemoveConstraintFail() throws Exception {
+        Constraint constraint = new MockConstraint(true, true, false);
+        TestCfg.addConstraint(constraint);
+
+        try {
+            DeleteSubtreeMockLDAPConnection c = new DeleteSubtreeMockLDAPConnection(
+                    "cn=test child 1,cn=test children,cn=test parent 1,cn=test parents,cn=config");
+            c.importLDIF(TEST_LDIF);
+            ManagementContext ctx = LDAPManagementContext.createFromContext(c);
+            TestParentCfgClient parent = getTestParent(ctx, "test parent 1");
+            parent.removeTestChild("test child 1");
+            Assert.fail("The remove constraint failed to prevent removal of the managed object");
+        } finally {
+            // Clean up.
+            TestCfg.removeConstraint(constraint);
+        }
+    }
+
+    /**
+     * Tests modification of a child managed object succeeds when registered
+     * remove constraints succeed.
+     *
+     * @throws Exception
+     *             If an unexpected error occurred.
+     */
+    @Test
+    public void testModifyConstraintSuccess() throws Exception {
+        Constraint constraint = new MockConstraint(false, true, false);
+        TestCfg.addConstraint(constraint);
+
+        try {
+            ModifyEntryMockLDAPConnection c = new ModifyEntryMockLDAPConnection(
+                    "cn=test child 2,cn=test children,cn=test parent 1,cn=test parents,cn=config");
+            c.importLDIF(TEST_LDIF);
+            c.addExpectedModification("ds-cfg-base-dn");
+            ManagementContext ctx = LDAPManagementContext.createFromContext(c);
+            TestParentCfgClient parent = getTestParent(ctx, "test parent 1");
+            TestChildCfgClient child = parent.getTestChild("test child 2");
+            child.setOptionalMultiValuedDNProperty1(Collections.<DN> emptySet());
+            child.commit();
+            Assert.assertTrue(c.isEntryModified());
+        } finally {
+            // Clean up.
+            TestCfg.removeConstraint(constraint);
+        }
+    }
+
+    /**
+     * Tests modification of a child managed object fails when registered remove
+     * constraints fails.
+     *
+     * @throws Exception
+     *             If an unexpected error occurred.
+     */
+    @Test(expectedExceptions = OperationRejectedException.class)
+    public void testModifyConstraintFail() throws Exception {
+        Constraint constraint = new MockConstraint(true, false, true);
+        TestCfg.addConstraint(constraint);
+
+        try {
+            ModifyEntryMockLDAPConnection c = new ModifyEntryMockLDAPConnection(
+                    "cn=test child 2,cn=test children,cn=test parent 1,cn=test parents,cn=config");
+            c.importLDIF(TEST_LDIF);
+            c.addExpectedModification("ds-cfg-base-dn");
+            ManagementContext ctx = LDAPManagementContext.createFromContext(c);
+            TestParentCfgClient parent = getTestParent(ctx, "test parent 1");
+            TestChildCfgClient child = parent.getTestChild("test child 2");
+            child.setOptionalMultiValuedDNProperty1(Collections.<DN> emptySet());
+            child.commit();
+            Assert.fail("The modify constraint failed to prevent modification of the managed object");
+        } finally {
+            // Clean up.
+            TestCfg.removeConstraint(constraint);
+        }
+    }
+
+    // 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);
+    }
+
+    // Create the named test parent managed object.
+    private TestParentCfgClient createTestParent(ManagementContext context, String name)
+            throws Exception {
+        ManagedObject<RootCfgClient> root = context.getRootConfigurationManagedObject();
+        return root.createChild(TestCfg.getTestOneToManyParentRelationDefinition(), TestParentCfgDefn.getInstance(),
+                name, null).getConfiguration();
+    }
+
+    // Retrieve the named test parent managed object.
+    private TestParentCfgClient getTestParent(ManagementContext context, String name)
+            throws Exception {
+        ManagedObject<RootCfgClient> root = context.getRootConfigurationManagedObject();
+        return root.getChild(TestCfg.getTestOneToManyParentRelationDefinition(), name).getConfiguration();
+    }
+
+    // List test parent managed objects.
+    private String[] listTestParents(ManagementContext context) throws Exception {
+        ManagedObject<RootCfgClient> root = context.getRootConfigurationManagedObject();
+        return root.listChildren(TestCfg.getTestOneToManyParentRelationDefinition());
+    }
+
+    // Remove the named test parent managed object.
+    private void removeTestParent(ManagementContext context, String name) throws Exception {
+        ManagedObject<RootCfgClient> root = context.getRootConfigurationManagedObject();
+        root.removeChild(TestCfg.getTestOneToManyParentRelationDefinition(), name);
+    }
+}
diff --git a/opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/client/ldap/MockConstraint.java b/opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/client/ldap/MockConstraint.java
new file mode 100644
index 0000000..cf2b024
--- /dev/null
+++ b/opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/client/ldap/MockConstraint.java
@@ -0,0 +1,131 @@
+/*
+ * 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 2008 Sun Microsystems, Inc.
+ */
+package org.opends.server.admin.client.ldap;
+
+import java.util.Collection;
+import java.util.Collections;
+
+import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.opendj.ldap.ErrorResultException;
+import org.opends.server.admin.Constraint;
+import org.opends.server.admin.ManagedObjectPath;
+import org.opends.server.admin.client.ClientConstraintHandler;
+import org.opends.server.admin.client.ManagedObject;
+import org.opends.server.admin.client.ManagementContext;
+import org.opends.server.admin.server.ServerConstraintHandler;
+
+/**
+ * A mock constraint which can be configured to refuse various types of
+ * operation.
+ */
+public final class MockConstraint extends Constraint {
+
+    /**
+     * Mock client constraint handler.
+     */
+    private class Handler extends ClientConstraintHandler {
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public boolean isAddAcceptable(ManagementContext context, ManagedObject<?> managedObject,
+                Collection<LocalizableMessage> unacceptableReasons) throws ErrorResultException {
+            if (!allowAdds) {
+                unacceptableReasons.add(LocalizableMessage.raw("Adds not allowed"));
+            }
+
+            return allowAdds;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public boolean isDeleteAcceptable(ManagementContext context, ManagedObjectPath<?, ?> path,
+                Collection<LocalizableMessage> unacceptableReasons) throws ErrorResultException {
+            if (!allowDeletes) {
+                unacceptableReasons.add(LocalizableMessage.raw("Deletes not allowed"));
+            }
+
+            return allowDeletes;
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        @Override
+        public boolean isModifyAcceptable(ManagementContext context, ManagedObject<?> managedObject,
+                Collection<LocalizableMessage> unacceptableReasons) throws ErrorResultException {
+            if (!allowModifies) {
+                unacceptableReasons.add(LocalizableMessage.raw("Modifies not allowed"));
+            }
+
+            return allowModifies;
+        }
+
+    }
+
+    // Determines if add operations are allowed.
+    private final boolean allowAdds;
+
+    // Determines if modify operations are allowed.
+    private final boolean allowModifies;
+
+    // Determines if delete operations are allowed.
+    private final boolean allowDeletes;
+
+    /**
+     * Creates a new mock constraint.
+     *
+     * @param allowAdds
+     *            Determines if add operations are allowed.
+     * @param allowModifies
+     *            Determines if modify operations are allowed.
+     * @param allowDeletes
+     *            Determines if delete operations are allowed.
+     */
+    public MockConstraint(boolean allowAdds, boolean allowModifies, boolean allowDeletes) {
+        this.allowAdds = allowAdds;
+        this.allowModifies = allowModifies;
+        this.allowDeletes = allowDeletes;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Collection<ClientConstraintHandler> getClientConstraintHandlers() {
+        return Collections.<ClientConstraintHandler> singleton(new Handler());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Collection<ServerConstraintHandler> getServerConstraintHandlers() {
+        return Collections.emptySet();
+    }
+
+}
diff --git a/opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/client/ldap/MockLDAPConnection.java b/opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/client/ldap/MockLDAPConnection.java
new file mode 100644
index 0000000..1de1f4a
--- /dev/null
+++ b/opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/client/ldap/MockLDAPConnection.java
@@ -0,0 +1,386 @@
+/*
+ * 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.
+ */
+package org.opends.server.admin.client.ldap;
+
+import static org.fest.assertions.Assertions.*;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.forgerock.opendj.ldap.Attribute;
+import org.forgerock.opendj.ldap.AttributeDescription;
+import org.forgerock.opendj.ldap.AttributeParser;
+import org.forgerock.opendj.ldap.ByteString;
+import org.forgerock.opendj.ldap.DN;
+import org.forgerock.opendj.ldap.DecodeException;
+import org.forgerock.opendj.ldap.DecodeOptions;
+import org.forgerock.opendj.ldap.Entry;
+import org.forgerock.opendj.ldap.ErrorResultException;
+import org.forgerock.opendj.ldap.LinkedHashMapEntry;
+import org.forgerock.opendj.ldap.RDN;
+import org.forgerock.opendj.ldap.ResultCode;
+import org.forgerock.opendj.ldap.TestCaseUtils;
+import org.forgerock.opendj.ldap.controls.Control;
+import org.forgerock.opendj.ldap.controls.ControlDecoder;
+import org.forgerock.opendj.ldap.requests.ModifyRequest;
+import org.forgerock.opendj.ldap.responses.SearchResultEntry;
+
+/**
+ * A mock LDAP connection which fakes up search results based on some LDIF
+ * content. Implementations should override the modify operations in order to
+ * get provide the correct fake behavior.
+ */
+public class MockLDAPConnection extends LDAPConnection {
+
+    /**
+     * A mock entry.
+     */
+    private static final class MockEntry {
+
+        private final Entry entry;
+
+        private final List<MockEntry> children;
+
+        private final DN dn;
+
+        public MockEntry(DN dn, Entry entry) {
+            this.dn = dn;
+            this.entry = entry;
+            this.children = new LinkedList<MockEntry>();
+        }
+
+        public Entry getEntry() {
+            return entry;
+        }
+
+        public List<MockEntry> getChildren() {
+            return children;
+        }
+
+        public DN getDN() {
+            return dn;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder builder = new StringBuilder();
+            builder.append("dn:");
+            builder.append(dn);
+            builder.append(", attributes:");
+            builder.append(entry.getAllAttributes());
+            return builder.toString();
+        }
+    }
+
+    /** All the entries. */
+    private final Map<DN, MockEntry> entries;
+
+    /** The single root entry. */
+    private final MockEntry rootEntry;
+
+    /**
+     * Create a mock connection.
+     */
+    public MockLDAPConnection() {
+        this.rootEntry = new MockEntry(DN.rootDN(), new LinkedHashMapEntry(DN.rootDN()));
+        this.entries = new HashMap<DN, MockEntry>();
+        this.entries.put(DN.rootDN(), this.rootEntry);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void createEntry(Entry entry) throws ErrorResultException {
+        throw new UnsupportedOperationException("createEntry");
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void deleteSubtree(DN dn) throws ErrorResultException {
+        throw new UnsupportedOperationException("deleteSubtree");
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean entryExists(DN dn) throws ErrorResultException {
+        return getEntry(dn) != null;
+    }
+
+    /**
+     * Imports the provided LDIF into this mock connection.
+     *
+     * @param lines
+     *            The LDIF.
+     */
+    public final void importLDIF(String... lines) {
+        try {
+            for (Entry entry : TestCaseUtils.makeEntries(lines)) {
+                addEntry(entry);
+            }
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Collection<DN> listEntries(DN dn, String filter) throws ErrorResultException {
+        MockEntry entry = getEntry(dn);
+
+        if (entry == null) {
+            throw ErrorResultException.newErrorResult(ResultCode.NO_SUCH_OBJECT, "Could not find entry: " + dn);
+        } else {
+            List<DN> names = new LinkedList<DN>();
+            for (MockEntry child : entry.children) {
+                names.add(DN.valueOf(child.getDN().toString()));
+            }
+            return names;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void modifyEntry(ModifyRequest request) throws ErrorResultException {
+        throw new UnsupportedOperationException("modifyEntry");
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public SearchResultEntry readEntry(DN dn, Collection<String> attrIds) throws ErrorResultException {
+        final MockEntry entry = getEntry(dn);
+        return new SearchResultEntry() {
+
+            public AttributeParser parseAttribute(String attributeDescription) {
+                throw new RuntimeException("not implemented");
+            }
+
+            public AttributeParser parseAttribute(AttributeDescription attributeDescription) {
+                throw new RuntimeException("not implemented");
+            }
+
+            public boolean containsControl(String oid) {
+                return false;
+            }
+
+            public SearchResultEntry setName(String dn) {
+                throw new RuntimeException("not implemented");
+            }
+
+            @Override
+            public SearchResultEntry setName(DN dn) {
+                throw new RuntimeException("not implemented");
+            }
+
+            @Override
+            public SearchResultEntry replaceAttribute(String attributeDescription, Object... values) {
+                throw new RuntimeException("not implemented");
+            }
+
+            @Override
+            public boolean replaceAttribute(Attribute attribute) {
+                throw new RuntimeException("not implemented");
+            }
+
+            @Override
+            public SearchResultEntry removeAttribute(String attributeDescription, Object... values) {
+                throw new RuntimeException("not implemented");
+            }
+
+            @Override
+            public boolean removeAttribute(AttributeDescription attributeDescription) {
+                throw new RuntimeException("not implemented");
+            }
+
+            @Override
+            public boolean removeAttribute(Attribute attribute, Collection<? super ByteString> missingValues) {
+                throw new RuntimeException("not implemented");
+            }
+
+            @Override
+            public DN getName() {
+                return entry.getDN();
+            }
+
+            @Override
+            public List<Control> getControls() {
+                throw new RuntimeException("not implemented");
+            }
+
+            @Override
+            public <C extends Control> C getControl(ControlDecoder<C> decoder, DecodeOptions options) throws DecodeException {
+                throw new RuntimeException("not implemented");
+            }
+
+            @Override
+            public int getAttributeCount() {
+                return entry.getEntry().getAttributeCount();
+            }
+
+            @Override
+            public Attribute getAttribute(String attributeDescription) {
+                return entry.getEntry().getAttribute(attributeDescription);
+            }
+
+            @Override
+            public Attribute getAttribute(AttributeDescription attributeDescription) {
+                return entry.getEntry().getAttribute(attributeDescription);
+            }
+
+            @Override
+            public Iterable<Attribute> getAllAttributes(String attributeDescription) {
+                return entry.getEntry().getAllAttributes(attributeDescription);
+            }
+
+            @Override
+            public Iterable<Attribute> getAllAttributes(AttributeDescription attributeDescription) {
+                return entry.getEntry().getAllAttributes(attributeDescription);
+            }
+
+            @Override
+            public Iterable<Attribute> getAllAttributes() {
+                return entry.getEntry().getAllAttributes();
+            }
+
+            @Override
+            public boolean containsAttribute(String attributeDescription, Object... values) {
+                return entry.getEntry().containsAttribute(attributeDescription, values);
+            }
+
+            @Override
+            public boolean containsAttribute(Attribute attribute, Collection<? super ByteString> missingValues) {
+                throw new RuntimeException("not implemented");
+            }
+
+            @Override
+            public SearchResultEntry clearAttributes() {
+                throw new RuntimeException("not implemented");
+            }
+
+            @Override
+            public SearchResultEntry addControl(Control control) {
+                throw new RuntimeException("not implemented");
+            }
+
+            @Override
+            public SearchResultEntry addAttribute(String attributeDescription, Object... values) {
+                throw new RuntimeException("not implemented");
+            }
+
+            @Override
+            public boolean addAttribute(Attribute attribute, Collection<? super ByteString> duplicateValues) {
+                throw new RuntimeException("not implemented");
+            }
+
+            @Override
+            public boolean addAttribute(Attribute attribute) {
+                throw new RuntimeException("not implemented");
+            }
+        };
+    }
+
+    /**
+     * Asserts whether the provided attribute contains exactly the set of values
+     * contained in the provided collection.
+     *
+     * @param attr
+     *            The attribute.
+     * @param values
+     *            The expected values.
+     * @throws ErrorResultException
+     *             If an unexpected problem occurred.
+     */
+    protected final void assertAttributeEquals(Attribute attr, Collection<String> values) throws ErrorResultException {
+        List<String> actualValues = new LinkedList<String>();
+        for (ByteString actualValue : attr) {
+            actualValues.add(actualValue.toString());
+        }
+
+        assertThat(actualValues).hasSize(values.size());
+        assertThat(actualValues).containsOnly(values.toArray());
+    }
+
+    /**
+     * Create a new mock entry.
+     *
+     * @param entry
+     *            The entry to be added.
+     */
+    private void addEntry(Entry entry) {
+        MockEntry parent = rootEntry;
+        DN entryDN = entry.getName();
+
+        // Create required glue entries.
+        for (int i = 0; i < entryDN.size() - 1; i++) {
+            RDN rdn = entryDN.parent(entryDN.size() - i - 1).rdn();
+            DN dn = parent.getDN().child(rdn);
+
+            if (!entries.containsKey(dn)) {
+                MockEntry glue = new MockEntry(dn, new LinkedHashMapEntry(dn));
+                parent.getChildren().add(glue);
+                entries.put(dn, glue);
+            }
+
+            parent = entries.get(dn);
+        }
+
+        // We now have the parent entry - so construct the new entry.
+        MockEntry child = new MockEntry(entryDN, LinkedHashMapEntry.deepCopyOfEntry(entry));
+        parent.getChildren().add(child);
+        entries.put(entryDN, child);
+    }
+
+    /**
+     * Gets the named entry.
+     *
+     * @param dn
+     *            The name of the entry.
+     * @return Returns the mock entry or <code>null</code> if it does not exist.
+     */
+    private MockEntry getEntry(DN dn) {
+        DN name = DN.valueOf(dn.toString());
+        return entries.get(name);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void unbind() {
+        // nothing to do
+    }
+
+}
diff --git a/opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/client/ldap/ModifyEntryMockLDAPConnection.java b/opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/client/ldap/ModifyEntryMockLDAPConnection.java
new file mode 100644
index 0000000..45e8c81
--- /dev/null
+++ b/opendj-sdk/opendj-admin/src/test/java/org/opends/server/admin/client/ldap/ModifyEntryMockLDAPConnection.java
@@ -0,0 +1,115 @@
+/*
+ * 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 2008 Sun Microsystems, Inc.
+ */
+package org.opends.server.admin.client.ldap;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.forgerock.opendj.ldap.Attribute;
+import org.forgerock.opendj.ldap.DN;
+import org.forgerock.opendj.ldap.ErrorResultException;
+import org.forgerock.opendj.ldap.Modification;
+import org.forgerock.opendj.ldap.requests.ModifyRequest;
+import org.testng.Assert;
+
+import com.forgerock.opendj.util.Validator;
+
+/**
+ * A mock LDAP connection which is used to verify that a modify operation was
+ * requested and that it has the correct parameters.
+ */
+public final class ModifyEntryMockLDAPConnection extends MockLDAPConnection {
+
+    // Detect multiple calls.
+    private boolean alreadyModified = false;
+
+    private final DN expectedDN;
+
+    // The expected set of modifications (attribute name -> list of
+    // values).
+    private final Map<String, List<String>> modifications = new HashMap<String, List<String>>();
+
+    /**
+     * Create a new mock ldap connection for detecting modify operations.
+     *
+     * @param dn
+     *            The expected DN of the entry to be added.
+     */
+    public ModifyEntryMockLDAPConnection(String dn) {
+        this.expectedDN = DN.valueOf(dn);
+    }
+
+    /**
+     * Add a modification which should be part of the modify operation.
+     *
+     * @param expectedName
+     *            The name of the expected attribute.
+     * @param expectedValues
+     *            The attribute's expected new values (possibly empty if
+     *            deleted).
+     */
+    public void addExpectedModification(String expectedName, String... expectedValues) {
+        Validator.ensureNotNull(expectedName);
+        Validator.ensureNotNull(expectedValues);
+        modifications.put(expectedName, Arrays.asList(expectedValues));
+    }
+
+    /**
+     * Determines whether or not the entry was modified.
+     *
+     * @return Returns <code>true</code> if it was modified.
+     */
+    public boolean isEntryModified() {
+        return alreadyModified;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void modifyEntry(ModifyRequest request) throws ErrorResultException {
+        Assert.assertFalse(alreadyModified);
+        Assert.assertEquals(request.getName(), expectedDN);
+
+        Map<String, List<String>> expected = new HashMap<String, List<String>>(modifications);
+        for (Modification modification : request.getModifications()) {
+            Attribute attribute = modification.getAttribute();
+            String attrName = attribute.getAttributeDescription().getAttributeType().getNameOrOID();
+            List<String> values = expected.remove(attrName);
+            if (values == null) {
+                Assert.fail("Unexpected modification to attribute " + attrName);
+            }
+            assertAttributeEquals(attribute, values);
+        }
+        if (!expected.isEmpty()) {
+            Assert.fail("Missing modifications to: " + expected.keySet());
+        }
+
+        alreadyModified = true;
+    }
+}

--
Gitblit v1.10.0