From a3b0441c12b207c0fdfce0566dba2db5ecd3816c Mon Sep 17 00:00:00 2001
From: Violette Roche-Montane <violette.roche-montane@forgerock.com>
Date: Fri, 25 Apr 2014 14:41:38 +0000
Subject: [PATCH] Checkpoint for OPENDJ-1343 Migrate dsconfig - Moved dsconfig package to opendj-config. -> Several checkstyle errors still exist and will be fixed after this commit in order to let the changes on main files more readable. -> BuildVersion.java will be renamed, moved later.

---
 opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/DSConfig.java                     | 1183 ++++++
 opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/SubCommandHandler.java            | 1343 +++++++
 opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/DeleteSubCommandHandler.java      |  383 ++
 opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/PropertyValueEditor.java          | 2319 ++++++++++++
 opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/GetPropSubCommandHandler.java     |  393 ++
 opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/SubCommandHandlerFactory.java     |  334 +
 opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/LDAPManagementContextFactory.java |  167 
 opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/HelpSubCommandHandler.java        | 1052 +++++
 opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/PropertyEditorModification.java   |  196 +
 opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/ArgumentExceptionFactory.java     |  563 ++
 opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/BuildVersion.java                 |  264 +
 opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/CLIProfile.java                   |  129 
 opendj-config/src/main/java/org/forgerock/opendj/config/ConfigurationFramework.java                |   20 
 opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/CreateSubCommandHandler.java      | 1354 +++++++
 opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/ListSubCommandHandler.java        |  493 ++
 opendj-config/pom.xml                                                                              |    5 
 opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/SetPropSubCommandHandler.java     |  910 ++++
 opendj-config/src/main/resources/com/forgerock/opendj/ldap/config.properties                       |    9 
 opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/PropertyValuePrinter.java         |  193 +
 19 files changed, 11,309 insertions(+), 1 deletions(-)

diff --git a/opendj-config/pom.xml b/opendj-config/pom.xml
index 3346502..797a9af 100644
--- a/opendj-config/pom.xml
+++ b/opendj-config/pom.xml
@@ -58,6 +58,11 @@
       <artifactId>opendj-core</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.forgerock.opendj</groupId>
+      <artifactId>opendj-cli</artifactId>
+      <version>${project.version}</version>
+    </dependency>
   </dependencies>
   <properties>
     <opendj.osgi.import>
diff --git a/opendj-config/src/main/java/org/forgerock/opendj/config/ConfigurationFramework.java b/opendj-config/src/main/java/org/forgerock/opendj/config/ConfigurationFramework.java
index 2136c21..596e331 100644
--- a/opendj-config/src/main/java/org/forgerock/opendj/config/ConfigurationFramework.java
+++ b/opendj-config/src/main/java/org/forgerock/opendj/config/ConfigurationFramework.java
@@ -321,7 +321,7 @@
             throw new IllegalStateException("configuration framework already initialized.");
         }
         this.installPath = installPath == null ? System.getProperty("user.dir") : installPath;
-        this.instancePath = instancePath == null ? installPath : instancePath;
+        this.instancePath = instancePath == null ? this.installPath : instancePath;
         this.parent = parent;
         initialize0();
         return this;
@@ -801,4 +801,22 @@
         }
     }
 
+    /**
+     * Returns the installation path.
+     *
+     * @return The installation path of this instance.
+     */
+    public String getInstallPath() {
+        return installPath;
+    }
+
+    /**
+     * Returns the instance path.
+     *
+     * @return The instance path.
+     */
+    public String getInstancePath() {
+        return instancePath;
+    }
+
 }
diff --git a/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/ArgumentExceptionFactory.java b/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/ArgumentExceptionFactory.java
new file mode 100644
index 0000000..2626cd1
--- /dev/null
+++ b/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/ArgumentExceptionFactory.java
@@ -0,0 +1,563 @@
+/*
+ * 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-2010 Sun Microsystems, Inc.
+ *      Portions Copyright 2014 ForgeRock AS
+ */
+package org.forgerock.opendj.config.dsconfig;
+
+import static com.forgerock.opendj.dsconfig.DsconfigMessages.*;
+
+import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.opendj.config.AbstractManagedObjectDefinition;
+import org.forgerock.opendj.config.ManagedObjectDefinition;
+import org.forgerock.opendj.config.PropertyDefinition;
+import org.forgerock.opendj.config.PropertyDefinitionUsageBuilder;
+import org.forgerock.opendj.config.PropertyException;
+import org.forgerock.opendj.config.RelationDefinition;
+import org.forgerock.opendj.config.client.IllegalManagedObjectNameException;
+import org.forgerock.opendj.config.client.ManagedObjectDecodingException;
+import org.forgerock.opendj.config.client.MissingMandatoryPropertiesException;
+import org.forgerock.opendj.config.client.OperationRejectedException;
+
+import com.forgerock.opendj.cli.Argument;
+import com.forgerock.opendj.cli.ArgumentException;
+import com.forgerock.opendj.cli.ClientException;
+import com.forgerock.opendj.cli.ConsoleApplication;
+import com.forgerock.opendj.cli.ReturnCode;
+import com.forgerock.opendj.cli.TableBuilder;
+import com.forgerock.opendj.cli.TextTablePrinter;
+
+/**
+ * A utility class for converting various admin exception types into argument exceptions.
+ */
+public final class ArgumentExceptionFactory {
+
+    /**
+     * Creates a ClientException exception from an illegal managed object name exception.
+     *
+     * @param e
+     *            The illegal managed object name exception.
+     * @param d
+     *            The managed object definition.
+     * @return Returns a ClientException exception.
+     */
+    public static ClientException adaptIllegalManagedObjectNameException(IllegalManagedObjectNameException e,
+            AbstractManagedObjectDefinition<?, ?> d) {
+        String illegalName = e.getIllegalName();
+        PropertyDefinition<?> pd = e.getNamingPropertyDefinition();
+
+        if (illegalName.length() == 0) {
+            LocalizableMessage message = ERR_DSCFG_ERROR_ILLEGAL_NAME_EMPTY.get(d.getUserFriendlyPluralName());
+            return new ClientException(ReturnCode.ERROR_USER_DATA, message);
+        } else if (illegalName.trim().length() == 0) {
+            LocalizableMessage message = ERR_DSCFG_ERROR_ILLEGAL_NAME_BLANK.get(d.getUserFriendlyPluralName());
+            return new ClientException(ReturnCode.ERROR_USER_DATA, message);
+        } else if (pd != null) {
+            try {
+                pd.decodeValue(illegalName);
+            } catch (PropertyException e1) {
+                PropertyDefinitionUsageBuilder b = new PropertyDefinitionUsageBuilder(true);
+                LocalizableMessage syntax = b.getUsage(pd);
+
+                LocalizableMessage message = ERR_DSCFG_ERROR_ILLEGAL_NAME_SYNTAX.get(illegalName,
+                        d.getUserFriendlyName(), syntax);
+                return new ClientException(ReturnCode.ERROR_USER_DATA, message);
+            }
+        }
+
+        LocalizableMessage message = ERR_DSCFG_ERROR_ILLEGAL_NAME_UNKNOWN.get(illegalName, d.getUserFriendlyName());
+        return new ClientException(ReturnCode.ERROR_USER_DATA, message);
+    }
+
+    /**
+     * Creates an argument exception from a property exception.
+     *
+     * @param e
+     *            The property exception.
+     * @param d
+     *            The managed object definition.
+     * @return Returns an argument exception.
+     */
+    public static ArgumentException adaptPropertyException(PropertyException e,
+            AbstractManagedObjectDefinition<?, ?> d) {
+        return new ArgumentException(e.getMessageObject());
+    }
+
+    /**
+     * Displays a table listing reasons why a managed object could not be decoded successfully.
+     *
+     * @param app
+     *            The console application.
+     * @param e
+     *            The managed object decoding exception.
+     */
+    public static void displayManagedObjectDecodingException(ConsoleApplication app, ManagedObjectDecodingException e) {
+        AbstractManagedObjectDefinition<?, ?> d = e.getPartialManagedObject().getManagedObjectDefinition();
+        LocalizableMessage ufn = d.getUserFriendlyName();
+        LocalizableMessage msg;
+        if (e.getCauses().size() == 1) {
+            msg = ERR_GET_HEADING_MODE_SINGLE.get(ufn);
+        } else {
+            msg = ERR_GET_HEADING_MODE_PLURAL.get(ufn);
+        }
+
+        app.println(msg);
+        app.println();
+        TableBuilder builder = new TableBuilder();
+        for (PropertyException pe : e.getCauses()) {
+            ArgumentException ae = adaptPropertyException(pe, d);
+            builder.startRow();
+            builder.appendCell("*");
+            builder.appendCell(ae.getMessage());
+        }
+
+        TextTablePrinter printer = new TextTablePrinter(app.getErrorStream());
+        printer.setDisplayHeadings(false);
+        printer.setColumnWidth(1, 0);
+        printer.setIndentWidth(4);
+        builder.print(printer);
+    }
+
+    /**
+     * Displays a table listing missing mandatory properties.
+     *
+     * @param app
+     *            The console application.
+     * @param e
+     *            The missing mandatory property exception.
+     */
+    public static void displayMissingMandatoryPropertyException(ConsoleApplication app,
+            MissingMandatoryPropertiesException e) {
+        LocalizableMessage ufn = e.getUserFriendlyName();
+        LocalizableMessage msg;
+        if (e.isCreate()) {
+            if (e.getCauses().size() == 1) {
+                msg = ERR_CREATE_HEADING_MMPE_SINGLE.get(ufn);
+            } else {
+                msg = ERR_CREATE_HEADING_MMPE_PLURAL.get(ufn);
+            }
+        } else {
+            if (e.getCauses().size() == 1) {
+                msg = ERR_MODIFY_HEADING_MMPE_SINGLE.get(ufn);
+            } else {
+                msg = ERR_MODIFY_HEADING_MMPE_PLURAL.get(ufn);
+            }
+        }
+
+        app.println(msg);
+        app.println();
+        TableBuilder builder = new TableBuilder();
+        builder.addSortKey(0);
+        builder.appendHeading(INFO_DSCFG_HEADING_PROPERTY_NAME.get());
+        builder.appendHeading(INFO_DSCFG_HEADING_PROPERTY_SYNTAX.get());
+
+        PropertyDefinitionUsageBuilder b = new PropertyDefinitionUsageBuilder(true);
+        for (PropertyException pe : e.getCauses()) {
+            PropertyDefinition<?> pd = pe.getPropertyDefinition();
+            builder.startRow();
+            builder.appendCell(pd.getName());
+            builder.appendCell(b.getUsage(pd));
+        }
+
+        TextTablePrinter printer = new TextTablePrinter(app.getErrorStream());
+        printer.setDisplayHeadings(true);
+        printer.setColumnWidth(1, 0);
+        printer.setIndentWidth(4);
+        builder.print(printer);
+    }
+
+    /**
+     * Displays a table listing the reasons why an operation was rejected.
+     *
+     * @param app
+     *            The console application.
+     * @param e
+     *            The operation rejected exception.
+     */
+    public static void displayOperationRejectedException(ConsoleApplication app, OperationRejectedException e) {
+        LocalizableMessage ufn = e.getUserFriendlyName();
+        LocalizableMessage msg;
+        switch (e.getOperationType()) {
+        case CREATE:
+            if (e.getMessages().size() == 1) {
+                msg = ERR_DSCFG_ERROR_CREATE_ORE_SINGLE.get(ufn);
+            } else {
+                msg = ERR_DSCFG_ERROR_CREATE_ORE_PLURAL.get(ufn);
+            }
+            break;
+        case DELETE:
+            if (e.getMessages().size() == 1) {
+                msg = ERR_DSCFG_ERROR_DELETE_ORE_SINGLE.get(ufn);
+            } else {
+                msg = ERR_DSCFG_ERROR_DELETE_ORE_PLURAL.get(ufn);
+            }
+            break;
+        default:
+            if (e.getMessages().size() == 1) {
+                msg = ERR_DSCFG_ERROR_MODIFY_ORE_SINGLE.get(ufn);
+            } else {
+                msg = ERR_DSCFG_ERROR_MODIFY_ORE_PLURAL.get(ufn);
+            }
+            break;
+        }
+
+        app.println(msg);
+        app.println();
+        TableBuilder builder = new TableBuilder();
+        for (LocalizableMessage reason : e.getMessages()) {
+            builder.startRow();
+            builder.appendCell("*");
+            builder.appendCell(reason);
+        }
+        TextTablePrinter printer = new TextTablePrinter(app.getErrorStream());
+        printer.setDisplayHeadings(false);
+        printer.setColumnWidth(1, 0);
+        printer.setIndentWidth(4);
+        builder.print(printer);
+    }
+
+    /**
+     * Creates an argument exception which should be used when a property modification argument is incompatible with a
+     * previous modification argument.
+     *
+     * @param arg
+     *            The incompatible argument.
+     * @return Returns an argument exception.
+     */
+    public static ArgumentException incompatiblePropertyModification(String arg) {
+        LocalizableMessage msg = ERR_DSCFG_ERROR_INCOMPATIBLE_PROPERTY_MOD.get(arg);
+        return new ArgumentException(msg);
+    }
+
+    /**
+     * Creates an argument exception which should be used when the client has not specified a bind password.
+     *
+     * @param bindDN
+     *            The name of the user requiring a password.
+     * @return Returns an argument exception.
+     */
+    public static ArgumentException missingBindPassword(String bindDN) {
+        LocalizableMessage msg = ERR_DSCFG_ERROR_NO_PASSWORD.get(bindDN);
+        return new ArgumentException(msg);
+    }
+
+    /**
+     * Creates an argument exception which should be used when the client has not specified a bind password.
+     *
+     * @param bindDN
+     *            The name of the user requiring a password.
+     * @return Returns an argument exception.
+     */
+    public static ArgumentException missingBindPassword(char[] bindDN) {
+        LocalizableMessage msg = ERR_DSCFG_ERROR_NO_PASSWORD.get(bindDN);
+        return new ArgumentException(msg);
+    }
+
+    /**
+     * Creates an argument exception which should be used when an argument, which is mandatory when the application is
+     * non-interactive, has not been specified.
+     *
+     * @param arg
+     *            The missing argument.
+     * @return Returns an argument exception.
+     */
+    public static ArgumentException missingMandatoryNonInteractiveArgument(Argument arg) {
+        LocalizableMessage msg = ERR_DSCFG_ERROR_MISSING_NON_INTERACTIVE_ARG.get(arg.getLongIdentifier());
+        return new ArgumentException(msg);
+    }
+
+    /**
+     * Creates an argument exception which should be used when a property value argument is invalid because it does not
+     * a property name.
+     *
+     * @param arg
+     *            The argument having the missing property name.
+     * @return Returns an argument exception.
+     */
+    public static ArgumentException missingNameInPropertyArgument(String arg) {
+        LocalizableMessage msg = ERR_DSCFG_ERROR_NO_NAME_IN_PROPERTY_VALUE.get(arg);
+        return new ArgumentException(msg);
+    }
+
+    /**
+     * Creates an argument exception which should be used when a property modification argument is invalid because it
+     * does not a property name.
+     *
+     * @param arg
+     *            The argument having the missing property name.
+     * @return Returns an argument exception.
+     */
+    public static ArgumentException missingNameInPropertyModification(String arg) {
+        LocalizableMessage msg = ERR_DSCFG_ERROR_NO_NAME_IN_PROPERTY_MOD.get(arg);
+        return new ArgumentException(msg);
+    }
+
+    /**
+     * Creates an argument exception which should be used when a property value argument is invalid because it does not
+     * contain a separator between the property name and its value.
+     *
+     * @param arg
+     *            The argument having a missing separator.
+     * @return Returns an argument exception.
+     */
+    public static ArgumentException missingSeparatorInPropertyArgument(String arg) {
+        LocalizableMessage msg = ERR_DSCFG_ERROR_NO_SEPARATOR_IN_PROPERTY_VALUE.get(arg);
+        return new ArgumentException(msg);
+    }
+
+    /**
+     * Creates an argument exception which should be used when a property modification argument is invalid because it
+     * does not contain a separator between the property name and its value.
+     *
+     * @param arg
+     *            The argument having a missing separator.
+     * @return Returns an argument exception.
+     */
+    public static ArgumentException missingSeparatorInPropertyModification(String arg) {
+        LocalizableMessage msg = ERR_DSCFG_ERROR_NO_SEPARATOR_IN_PROPERTY_MOD.get(arg);
+        return new ArgumentException(msg);
+    }
+
+    /**
+     * Creates an argument exception which should be used when a property value argument is invalid because it does not
+     * a property value.
+     *
+     * @param arg
+     *            The argument having the missing property value.
+     * @return Returns an argument exception.
+     */
+    public static ArgumentException missingValueInPropertyArgument(String arg) {
+        LocalizableMessage msg = ERR_DSCFG_ERROR_NO_VALUE_IN_PROPERTY_VALUE.get(arg);
+        return new ArgumentException(msg);
+    }
+
+    /**
+     * Creates an argument exception which should be used when a property modification argument is invalid because it
+     * does not a property value.
+     *
+     * @param arg
+     *            The argument having the missing property value.
+     * @return Returns an argument exception.
+     */
+    public static ArgumentException missingValueInPropertyModification(String arg) {
+        LocalizableMessage msg = ERR_DSCFG_ERROR_NO_NAME_IN_PROPERTY_MOD.get(arg);
+        return new ArgumentException(msg);
+    }
+
+    /**
+     * Creates an argument exception which should be used when the connection parameters could not be read from the
+     * standard input.
+     *
+     * @param cause
+     *            The reason why the connection parameters could not be read.
+     * @return Returns an argument exception.
+     */
+    public static ArgumentException unableToReadConnectionParameters(Exception cause) {
+        LocalizableMessage message = ERR_DSCFG_ERROR_CANNOT_READ_CONNECTION_PARAMETERS.get(cause.getMessage());
+        return new ArgumentException(message, cause);
+    }
+
+    /**
+     * Creates an argument exception which should be used when the bind password could not be read from the standard
+     * input because the application is non-interactive.
+     *
+     * @return Returns an argument exception.
+     */
+    public static ArgumentException unableToReadBindPasswordInteractively() {
+        LocalizableMessage message = ERR_DSCFG_ERROR_BIND_PASSWORD_NONINTERACTIVE.get();
+        return new ArgumentException(message);
+    }
+
+    /**
+     * Creates an argument exception which should be used when an attempt is made to reset a mandatory property that
+     * does not have any default values.
+     *
+     * @param d
+     *            The managed object definition.
+     * @param name
+     *            The name of the mandatory property.
+     * @param setOption
+     *            The name of the option which should be used to set the property's values.
+     * @return Returns an argument exception.
+     */
+    public static ArgumentException unableToResetMandatoryProperty(AbstractManagedObjectDefinition<?, ?> d,
+            String name, String setOption) {
+        LocalizableMessage message = ERR_DSCFG_ERROR_UNABLE_TO_RESET_MANDATORY_PROPERTY.get(
+                d.getUserFriendlyPluralName(), name, setOption);
+        return new ArgumentException(message);
+    }
+
+    /**
+     * Creates an argument exception which should be used when an attempt is made to reset a property with a value.
+     *
+     * @param name
+     *            The name of the mandatory property.
+     * @param resetOption
+     *            The name of the option which should be used to reset the property's values.
+     * @return Returns an argument exception.
+     */
+    public static ArgumentException unableToResetPropertyWithValue(String name, String resetOption) {
+        LocalizableMessage message = ERR_DSCFG_ERROR_UNABLE_TO_RESET_PROPERTY_WITH_VALUE.get(resetOption, name,
+                resetOption);
+        return new ArgumentException(message);
+    }
+
+    /**
+     * Creates an argument exception which should be used when an attempt is made to set the naming property for a
+     * managed object during creation.
+     *
+     * @param d
+     *            The managed object definition.
+     * @param pd
+     *            The naming property definition.
+     * @return Returns an argument exception.
+     */
+    public static ArgumentException unableToSetNamingProperty(AbstractManagedObjectDefinition<?, ?> d,
+            PropertyDefinition<?> pd) {
+        LocalizableMessage message = ERR_DSCFG_ERROR_UNABLE_TO_SET_NAMING_PROPERTY.get(pd.getName(),
+                d.getUserFriendlyName());
+        return new ArgumentException(message);
+    }
+
+    /**
+     * Creates an argument exception which should be used when a component category argument is not recognized.
+     *
+     * @param categoryName
+     *            The unrecognized component category.
+     * @return Returns an argument exception.
+     */
+    public static ArgumentException unknownCategory(String categoryName) {
+        LocalizableMessage msg = ERR_DSCFG_ERROR_CATEGORY_UNRECOGNIZED.get(categoryName);
+        return new ArgumentException(msg);
+    }
+
+    /**
+     * Creates an argument exception which should be used when a property name is not recognized.
+     *
+     * @param d
+     *            The managed object definition.
+     * @param name
+     *            The unrecognized property name.
+     * @return Returns an argument exception.
+     */
+    public static ArgumentException unknownProperty(AbstractManagedObjectDefinition<?, ?> d, String name) {
+        LocalizableMessage message = ERR_DSCFG_ERROR_PROPERTY_UNRECOGNIZED.get(name, d.getUserFriendlyPluralName());
+        return new ArgumentException(message);
+    }
+
+    /**
+     * Creates an argument exception which should be used when a property name is not recognized.
+     *
+     * @param name
+     *            The unrecognized property name.
+     * @return Returns an argument exception.
+     */
+    public static ArgumentException unknownProperty(String name) {
+        LocalizableMessage message = ERR_DSCFG_ERROR_PROPERTY_UNRECOGNIZED_NO_DEFN.get(name);
+        return new ArgumentException(message);
+    }
+
+    /**
+     * Creates an argument exception which should be used when a sub-type argument in a create-xxx sub-command is not
+     * recognized.
+     *
+     * @param r
+     *            The relation definition.
+     * @param typeName
+     *            The unrecognized property sub-type.
+     * @param typeUsage
+     *            A usage string describing the allowed sub-types.
+     * @return Returns an argument exception.
+     */
+    public static ArgumentException unknownSubType(RelationDefinition<?, ?> r, String typeName, String typeUsage) {
+        LocalizableMessage msg = ERR_DSCFG_ERROR_SUB_TYPE_UNRECOGNIZED
+                .get(typeName, r.getUserFriendlyName(), typeUsage);
+        return new ArgumentException(msg);
+    }
+
+    /**
+     * Creates an argument exception which should be used when a managed object type argument is not associated with a
+     * category.
+     *
+     * @param categoryName
+     *            The component category.
+     * @param typeName
+     *            The unrecognized component type.
+     * @return Returns an argument exception.
+     */
+    public static ArgumentException unknownTypeForCategory(String typeName, String categoryName) {
+        LocalizableMessage msg = ERR_DSCFG_ERROR_CATEGORY_TYPE_UNRECOGNIZED.get(typeName, categoryName);
+        return new ArgumentException(msg);
+    }
+
+    /**
+     * Creates an argument exception which should be used when a multi-valued property does not contain a given value.
+     *
+     * @param value
+     *            The property value.
+     * @param propertyName
+     *            The property name.
+     * @return Returns an argument exception.
+     */
+    public static ArgumentException unknownValueForMultiValuedProperty(String value, String propertyName) {
+        LocalizableMessage msg = ERR_DSCFG_ERROR_VALUE_DOES_NOT_EXIST.get(value, propertyName);
+        return new ArgumentException(msg);
+    }
+
+    /**
+     * Creates an argument exception which should be used when a child component does not exist.
+     *
+     * @param componentName
+     *            The component name.
+     * @return Returns an argument exception.
+     */
+    public static ArgumentException unknownValueForChildComponent(String componentName) {
+        LocalizableMessage msg = ERR_DSCFG_ERROR_FINDER_NO_CHILDREN.get(componentName);
+        return new ArgumentException(msg);
+    }
+
+    /**
+     * Creates a CLI exception which should be used when a managed object is retrieved but does not have the correct
+     * type appropriate for the associated sub-command.
+     *
+     * @param r
+     *            The relation definition.
+     * @param d
+     *            The definition of the managed object that was retrieved.
+     * @param subcommandName
+     *            the sub-command name.
+     * @return Returns a Client exception.
+     */
+    public static ClientException wrongManagedObjectType(RelationDefinition<?, ?> r, ManagedObjectDefinition<?, ?> d,
+            String subcommandName) {
+        LocalizableMessage msg = ERR_DSCFG_ERROR_TYPE_UNRECOGNIZED_FOR_SUBCOMMAND.get(d.getUserFriendlyName(),
+                subcommandName);
+        return new ClientException(ReturnCode.ERROR_USER_DATA, msg);
+    }
+
+    // Prevent instantiation.
+    private ArgumentExceptionFactory() {
+        // No implementation required.
+    }
+}
diff --git a/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/BuildVersion.java b/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/BuildVersion.java
new file mode 100644
index 0000000..cfdc798
--- /dev/null
+++ b/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/BuildVersion.java
@@ -0,0 +1,264 @@
+/*
+ * 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.
+ *      Portions copyright 2013-2014 ForgeRock AS.
+ */
+package org.forgerock.opendj.config.dsconfig;
+
+import static com.forgerock.opendj.ldap.ConfigMessages.ERR_BUILDVERSION_MISMATCH;
+import static com.forgerock.opendj.ldap.ConfigMessages.ERR_BUILDVERSION_MALFORMED;
+import static com.forgerock.opendj.ldap.ConfigMessages.ERR_BUILDVERSION_NOT_FOUND;
+import static com.forgerock.opendj.ldap.ConfigMessages.ERR_CONFIGVERSION_NOT_FOUND;
+import static org.forgerock.util.Utils.closeSilently;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.Arrays;
+
+import org.forgerock.opendj.config.ConfigurationFramework;
+import org.forgerock.opendj.config.server.ConfigException;
+import org.forgerock.opendj.ldap.Connection;
+import org.forgerock.opendj.ldap.ErrorResultException;
+import org.forgerock.opendj.ldap.responses.SearchResultEntry;
+
+/**
+ * Represents a particular version of OpenDJ useful for making comparisons between versions. FIXME TODO Move this file
+ * in ? package.
+ */
+public class BuildVersion implements Comparable<BuildVersion> {
+
+    private final int major;
+    private final int minor;
+    private final int point;
+    private final long rev;
+
+    /**
+     * Creates a new build version using the provided version information.
+     *
+     * @param major
+     *            Major release version number.
+     * @param minor
+     *            Minor release version number.
+     * @param point
+     *            Point release version number.
+     * @param rev
+     *            VCS revision number.
+     */
+    public BuildVersion(final int major, final int minor, final int point, final long rev) {
+        this.major = major;
+        this.minor = minor;
+        this.point = point;
+        this.rev = rev;
+    }
+
+    /**
+     * Returns the build version as specified in the entry "cn=Version,cn=monitor".
+     *
+     * @param connection
+     *            The connection to use to read the entry.
+     * @return The build version as specified in the current installation configuration.
+     * @throws ConfigException
+     *             Sends an exception if it is impossible to retrieve the version configuration entry.
+     */
+    public static BuildVersion binaryVersion(final Connection connection) throws ConfigException {
+        try {
+            final SearchResultEntry entry = connection.readEntry("cn=Version,cn=monitor", "majorVersion",
+                    "minorVersion", "pointVersion", "revisionNumber");
+            final int eMajor = Integer.valueOf(entry.getAttribute("majorVersion").firstValueAsString());
+            final int eMinor = Integer.valueOf(entry.getAttribute("minorVersion").firstValueAsString());
+            final int ePoint = Integer.valueOf(entry.getAttribute("pointVersion").firstValueAsString());
+            final long eRev = Long.valueOf(entry.getAttribute("revisionNumber").firstValueAsString());
+            return new BuildVersion(eMajor, eMinor, ePoint, eRev);
+        } catch (ErrorResultException e) {
+            throw new ConfigException(ERR_CONFIGVERSION_NOT_FOUND.get());
+        }
+    }
+
+    /**
+     * Checks if the binary version is the same than the instance version. If not, a configuration exception is thrown.
+     *
+     * @param connection
+     *            The connection to use to read the configuration entry.
+     * @throws ConfigException
+     *             Sends an exception if the version mismatch.
+     */
+    public static void checkVersionMismatch(final Connection connection) throws ConfigException {
+        final BuildVersion binaryVersion = BuildVersion.binaryVersion(connection);
+        final BuildVersion instanceVersion = BuildVersion.instanceVersion();
+        if (!binaryVersion.toString().equals(instanceVersion.toString())) {
+            throw new ConfigException(ERR_BUILDVERSION_MISMATCH.get(binaryVersion, instanceVersion));
+        }
+    }
+
+    /**
+     * Reads the instance version from config/buildinfo.
+     *
+     * @return The instance version from config/buildinfo.
+     * @throws ConfigException
+     *             If an error occurred while reading or parsing the version.
+     */
+    public static BuildVersion instanceVersion() throws ConfigException {
+        final String buildInfo = ConfigurationFramework.getInstance().getInstancePath() + File.separator + "config"
+                + File.separator + "buildinfo";
+        BufferedReader reader = null;
+        try {
+            reader = new BufferedReader(new FileReader(buildInfo));
+            final String s = reader.readLine();
+            if (s != null) {
+                return valueOf(s);
+            } else {
+                throw new ConfigException(ERR_BUILDVERSION_MALFORMED.get(buildInfo));
+            }
+        } catch (IOException e) {
+            throw new ConfigException(ERR_BUILDVERSION_NOT_FOUND.get(buildInfo));
+        } catch (final IllegalArgumentException e) {
+            throw new ConfigException(ERR_BUILDVERSION_MALFORMED.get(buildInfo));
+        } finally {
+            closeSilently(reader);
+        }
+    }
+
+    /**
+     * Parses the string argument as a build version. The string must be of the form:
+     *
+     * <pre>
+     * major.minor.point.rev
+     * </pre>
+     *
+     * @param s
+     *            The string to be parsed as a build version.
+     * @return The parsed build version.
+     * @throws IllegalArgumentException
+     *             If the string does not contain a parsable build version.
+     */
+    public static BuildVersion valueOf(final String s) {
+        final String[] fields = s.split("\\.");
+        if (fields.length != 4) {
+            throw new IllegalArgumentException("Invalid version string " + s);
+        }
+        final int major = Integer.parseInt(fields[0]);
+        final int minor = Integer.parseInt(fields[1]);
+        final int point = Integer.parseInt(fields[2]);
+        final long rev = Long.parseLong(fields[3]);
+        return new BuildVersion(major, minor, point, rev);
+    }
+
+    /**
+     * Returns the major release version number.
+     *
+     * @return The major release version number.
+     */
+    public int getMajorVersion() {
+        return major;
+    }
+
+    /**
+     * Returns the minor release version number.
+     *
+     * @return The minor release version number.
+     */
+    public int getMinorVersion() {
+        return minor;
+    }
+
+    /**
+     * Returns the point release version number.
+     *
+     * @return The point release version number.
+     */
+    public int getPointVersion() {
+        return point;
+    }
+
+    /** {@inheritDoc} */
+    public boolean equals(final Object obj) {
+        if (this == obj) {
+            return true;
+        } else if (obj instanceof BuildVersion) {
+            final BuildVersion other = (BuildVersion) obj;
+            return major == other.major && minor == other.minor && point == other.point && rev == other.rev;
+        } else {
+            return false;
+        }
+    }
+
+    /** {@inheritDoc} */
+    public int compareTo(final BuildVersion version) {
+        if (major == version.major) {
+            if (minor == version.minor) {
+                if (point == version.point) {
+                    if (rev == version.rev) {
+                        return 0;
+                    } else if (rev < version.rev) {
+                        return -1;
+                    }
+                } else if (point < version.point) {
+                    return -1;
+                }
+            } else if (minor < version.minor) {
+                return -1;
+            }
+        } else if (major < version.major) {
+            return -1;
+        }
+        return 1;
+    }
+
+    /**
+     * Returns the VCS revision number.
+     *
+     * @return The VCS revision number.
+     */
+    public long getRevisionNumber() {
+        return rev;
+    }
+
+    /** {@inheritDoc} */
+    public int hashCode() {
+        return Arrays.hashCode(new int[] { major, minor, point, (int) (rev >>> 32), (int) (rev & 0xFFFFL) });
+    }
+
+    /**
+     * Returns the string representation of the version. E.g:
+     *
+     * <pre>
+     * version : 2.8.0.1022
+     * </pre>
+     *
+     * @return The string representation of the version.
+     */
+    public String toString() {
+        final StringBuilder builder = new StringBuilder();
+        builder.append(major);
+        builder.append('.');
+        builder.append(minor);
+        builder.append('.');
+        builder.append(point);
+        builder.append('.');
+        builder.append(rev);
+        return builder.toString();
+    }
+}
diff --git a/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/CLIProfile.java b/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/CLIProfile.java
new file mode 100644
index 0000000..48b247c
--- /dev/null
+++ b/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/CLIProfile.java
@@ -0,0 +1,129 @@
+/*
+ * 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 2009 Sun Microsystems, Inc.
+ *      Portions Copyright 2014 ForgeRock AS
+ */
+
+package org.forgerock.opendj.config.dsconfig;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Set;
+
+import org.forgerock.opendj.config.AbstractManagedObjectDefinition;
+import org.forgerock.opendj.config.ManagedObjectDefinitionResource;
+import org.forgerock.opendj.config.RelationDefinition;
+import org.forgerock.opendj.config.SetRelationDefinition;
+
+/**
+ * This class is used to access CLI profile annotations.
+ */
+final class CLIProfile {
+
+    /** The singleton instance. */
+    private static final CLIProfile INSTANCE = new CLIProfile();
+
+    /**
+     * Get the CLI profile instance.
+     *
+     * @return Returns the CLI profile instance.
+     */
+    public static CLIProfile getInstance() {
+        return INSTANCE;
+    }
+
+    /** The CLI profile property table. */
+    private final ManagedObjectDefinitionResource resource;
+
+    /** Private constructor. */
+    private CLIProfile() {
+        this.resource = ManagedObjectDefinitionResource.createForProfile("cli");
+    }
+
+    /**
+     * Gets the default set of properties which should be displayed in a list-xxx operation.
+     *
+     * @param r
+     *            The relation definition.
+     * @return Returns the default set of properties which should be displayed in a list-xxx operation.
+     */
+    public Set<String> getDefaultListPropertyNames(RelationDefinition<?, ?> r) {
+        final String s = resource.getString(r.getParentDefinition(), "relation." + r.getName() + ".list-properties");
+        if (s.trim().length() == 0) {
+            return Collections.emptySet();
+        }
+        return new LinkedHashSet<String>(Arrays.asList(s.split(",")));
+    }
+
+    /**
+     * Gets the naming argument which should be used for a relation definition.
+     *
+     * @param r
+     *            The relation definition.
+     * @return Returns the naming argument which should be used for a relation definition.
+     */
+    public String getNamingArgument(RelationDefinition<?, ?> r) {
+        String s = resource.getString(r.getParentDefinition(), "relation." + r.getName() + ".naming-argument-override")
+                .trim();
+
+        if (s.length() == 0) {
+            // Use the last word in the managed object name as the argument
+            // prefix.
+            StringBuilder builder = new StringBuilder();
+            s = r.getChildDefinition().getName();
+            int i = s.lastIndexOf('-');
+            if (i < 0 || i == (s.length() - 1)) {
+                builder.append(s);
+            } else {
+                builder.append(s.substring(i + 1));
+            }
+
+            if (r instanceof SetRelationDefinition) {
+                // Set relations are named using their type, so be consistent
+                // with their associated create-xxx sub-command.
+                builder.append("-type");
+            } else {
+                // Other relations (instantiable) are named by the user.
+                builder.append("-name");
+            }
+
+            s = builder.toString();
+        }
+
+        return s;
+    }
+
+    /**
+     * Determines if instances of the specified managed object definition are to be used for customization.
+     *
+     * @param d
+     *            The managed object definition.
+     * @return Returns <code>true</code> if instances of the specified managed object definition are to be used for
+     *         customization.
+     */
+    public boolean isForCustomization(AbstractManagedObjectDefinition<?, ?> d) {
+        return Boolean.parseBoolean(resource.getString(d, "is-for-customization"));
+    }
+}
diff --git a/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/CreateSubCommandHandler.java b/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/CreateSubCommandHandler.java
new file mode 100644
index 0000000..dcad010
--- /dev/null
+++ b/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/CreateSubCommandHandler.java
@@ -0,0 +1,1354 @@
+/*
+ * 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-2010 Sun Microsystems, Inc.
+ *      Portions Copyright 2013-2014 ForgeRock AS
+ */
+package org.forgerock.opendj.config.dsconfig;
+
+import static com.forgerock.opendj.cli.CliMessages.*;
+import static com.forgerock.opendj.cli.ArgumentConstants.LIST_TABLE_SEPARATOR;
+import static com.forgerock.opendj.dsconfig.DsconfigMessages.*;
+import static org.forgerock.opendj.config.dsconfig.ArgumentExceptionFactory.displayMissingMandatoryPropertyException;
+import static org.forgerock.opendj.config.dsconfig.ArgumentExceptionFactory.displayOperationRejectedException;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.opendj.config.AbstractManagedObjectDefinition;
+import org.forgerock.opendj.config.AggregationPropertyDefinition;
+import org.forgerock.opendj.config.Configuration;
+import org.forgerock.opendj.config.ConfigurationClient;
+import org.forgerock.opendj.config.DefinitionDecodingException;
+import org.forgerock.opendj.config.InstantiableRelationDefinition;
+import org.forgerock.opendj.config.ManagedObjectAlreadyExistsException;
+import org.forgerock.opendj.config.ManagedObjectDefinition;
+import org.forgerock.opendj.config.ManagedObjectNotFoundException;
+import org.forgerock.opendj.config.ManagedObjectOption;
+import org.forgerock.opendj.config.ManagedObjectPath;
+import org.forgerock.opendj.config.OptionalRelationDefinition;
+import org.forgerock.opendj.config.PropertyDefinition;
+import org.forgerock.opendj.config.PropertyDefinitionUsageBuilder;
+import org.forgerock.opendj.config.PropertyException;
+import org.forgerock.opendj.config.PropertyOption;
+import org.forgerock.opendj.config.PropertyProvider;
+import org.forgerock.opendj.config.RelationDefinition;
+import org.forgerock.opendj.config.SetRelationDefinition;
+import org.forgerock.opendj.config.client.ConcurrentModificationException;
+import org.forgerock.opendj.config.client.IllegalManagedObjectNameException;
+import org.forgerock.opendj.config.client.ManagedObject;
+import org.forgerock.opendj.config.client.ManagedObjectDecodingException;
+import org.forgerock.opendj.config.client.ManagementContext;
+import org.forgerock.opendj.config.client.MissingMandatoryPropertiesException;
+import org.forgerock.opendj.config.client.OperationRejectedException;
+import org.forgerock.opendj.config.conditions.Condition;
+import org.forgerock.opendj.config.conditions.ContainsCondition;
+import org.forgerock.opendj.ldap.AuthorizationException;
+import org.forgerock.opendj.ldap.ErrorResultException;
+
+import com.forgerock.opendj.cli.Argument;
+import com.forgerock.opendj.cli.ArgumentException;
+import com.forgerock.opendj.cli.ClientException;
+import com.forgerock.opendj.cli.ConsoleApplication;
+import com.forgerock.opendj.cli.HelpCallback;
+import com.forgerock.opendj.cli.MenuBuilder;
+import com.forgerock.opendj.cli.MenuResult;
+import com.forgerock.opendj.cli.ReturnCode;
+import com.forgerock.opendj.cli.StringArgument;
+import com.forgerock.opendj.cli.SubCommand;
+import com.forgerock.opendj.cli.SubCommandArgumentParser;
+import com.forgerock.opendj.cli.TableBuilder;
+import com.forgerock.opendj.cli.TextTablePrinter;
+import com.forgerock.opendj.cli.ValidationCallback;
+
+/**
+ * A sub-command handler which is used to create new managed objects.
+ * <p>
+ * This sub-command implements the various create-xxx sub-commands.
+ *
+ * @param <C>
+ *            The type of managed object which can be created.
+ * @param <S>
+ *            The type of server managed object which can be created.
+ */
+final class CreateSubCommandHandler<C extends ConfigurationClient, S extends Configuration> extends SubCommandHandler {
+
+    /**
+     * A property provider which uses the command-line arguments to provide initial property values.
+     */
+    private static class MyPropertyProvider implements PropertyProvider {
+
+        /** Decoded set of properties. */
+        private final Map<PropertyDefinition<?>, Collection<?>> properties
+            = new HashMap<PropertyDefinition<?>, Collection<?>>();
+
+        /**
+         * Create a new property provider using the provided set of property value arguments.
+         *
+         * @param d
+         *            The managed object definition.
+         * @param namingPropertyDefinition
+         *            The naming property definition if there is one.
+         * @param args
+         *            The property value arguments.
+         * @throws ArgumentException
+         *             If the property value arguments could not be parsed.
+         */
+        public MyPropertyProvider(ManagedObjectDefinition<?, ?> d, PropertyDefinition<?> namingPropertyDefinition,
+                List<String> args) throws ArgumentException {
+            for (String s : args) {
+                // Parse the property "property:value".
+                int sep = s.indexOf(':');
+
+                if (sep < 0) {
+                    throw ArgumentExceptionFactory.missingSeparatorInPropertyArgument(s);
+                }
+
+                if (sep == 0) {
+                    throw ArgumentExceptionFactory.missingNameInPropertyArgument(s);
+                }
+
+                String propertyName = s.substring(0, sep);
+                String value = s.substring(sep + 1, s.length());
+                if (value.length() == 0) {
+                    throw ArgumentExceptionFactory.missingValueInPropertyArgument(s);
+                }
+
+                // Check the property definition.
+                PropertyDefinition<?> pd;
+                try {
+                    pd = d.getPropertyDefinition(propertyName);
+                } catch (IllegalArgumentException e) {
+                    throw ArgumentExceptionFactory.unknownProperty(d, propertyName);
+                }
+
+                // Make sure that the user is not attempting to set the naming
+                // property.
+                if (pd.equals(namingPropertyDefinition)) {
+                    throw ArgumentExceptionFactory.unableToSetNamingProperty(d, pd);
+                }
+
+                // Add the value.
+                addPropertyValue(d, pd, value);
+            }
+        }
+
+        /**
+         * Get the set of parsed property definitions that have values specified.
+         *
+         * @return Returns the set of parsed property definitions that have values specified.
+         */
+        public Set<PropertyDefinition<?>> getProperties() {
+            return properties.keySet();
+        }
+
+        /** {@inheritDoc} */
+        @SuppressWarnings("unchecked")
+        public <T> Collection<T> getPropertyValues(PropertyDefinition<T> d) {
+            Collection<T> values = (Collection<T>) properties.get(d);
+            if (values == null) {
+                return Collections.emptySet();
+            }
+            return values;
+        }
+
+        /** Add a single property value. */
+        @SuppressWarnings("unchecked")
+        private <T> void addPropertyValue(ManagedObjectDefinition<?, ?> d, PropertyDefinition<T> pd, String s)
+                throws ArgumentException {
+            T value;
+            try {
+                value = pd.decodeValue(s);
+            } catch (PropertyException e) {
+                throw ArgumentExceptionFactory.adaptPropertyException(e, d);
+            }
+
+            Collection<T> values = (Collection<T>) properties.get(pd);
+            if (values == null) {
+                values = new LinkedList<T>();
+            }
+            values.add(value);
+
+            if (values.size() > 1 && !pd.hasOption(PropertyOption.MULTI_VALUED)) {
+                PropertyException e = PropertyException.propertyIsSingleValuedException(pd);
+                throw ArgumentExceptionFactory.adaptPropertyException(e, d);
+            }
+
+            properties.put(pd, values);
+        }
+    }
+
+    /**
+     * A help call-back which displays help about available component types.
+     */
+    private static final class TypeHelpCallback<C extends ConfigurationClient, S extends Configuration> implements
+            HelpCallback {
+
+        /** The abstract definition for which to provide help on its sub-types. */
+        private final AbstractManagedObjectDefinition<C, S> d;
+
+        /** Create a new type help call-back. */
+        private TypeHelpCallback(AbstractManagedObjectDefinition<C, S> d) {
+            this.d = d;
+        }
+
+        /** {@inheritDoc} */
+        public void display(ConsoleApplication app) {
+            app.println(INFO_DSCFG_CREATE_TYPE_HELP_HEADING.get(d.getUserFriendlyPluralName()));
+
+            app.println();
+            app.println(d.getSynopsis());
+
+            if (d.getDescription() != null) {
+                app.println();
+                app.println(d.getDescription());
+            }
+
+            app.println();
+            app.println();
+
+            // Create a table containing a description of each component
+            // type.
+            TableBuilder builder = new TableBuilder();
+
+            builder.appendHeading(INFO_DSCFG_DESCRIPTION_CREATE_HELP_HEADING_TYPE.get());
+
+            builder.appendHeading(INFO_DSCFG_DESCRIPTION_CREATE_HELP_HEADING_DESCR.get());
+
+            boolean isFirst = true;
+            for (ManagedObjectDefinition<?, ?> mod : getSubTypes(d).values()) {
+                // Only display advanced types and custom types in advanced mode.
+                if (!app.isAdvancedMode()) {
+                    if (mod.hasOption(ManagedObjectOption.ADVANCED)) {
+                        continue;
+                    }
+
+                    if (CLIProfile.getInstance().isForCustomization(mod)) {
+                        continue;
+                    }
+                }
+
+                LocalizableMessage ufn = mod.getUserFriendlyName();
+                LocalizableMessage synopsis = mod.getSynopsis();
+                LocalizableMessage description = mod.getDescription();
+                if (CLIProfile.getInstance().isForCustomization(mod)) {
+                    ufn = INFO_DSCFG_CUSTOM_TYPE_OPTION.get(ufn);
+                    synopsis = INFO_DSCFG_CUSTOM_TYPE_SYNOPSIS.get(ufn);
+                    description = null;
+                } else if (mod == d) {
+                    ufn = INFO_DSCFG_GENERIC_TYPE_OPTION.get(ufn);
+                    synopsis = INFO_DSCFG_GENERIC_TYPE_SYNOPSIS.get(ufn);
+                    description = null;
+                }
+
+                if (!isFirst) {
+                    builder.startRow();
+                    builder.startRow();
+                } else {
+                    isFirst = false;
+                }
+
+                builder.startRow();
+                builder.appendCell(ufn);
+                builder.appendCell(synopsis);
+                if (description != null) {
+                    builder.startRow();
+                    builder.startRow();
+                    builder.appendCell();
+                    builder.appendCell(description);
+                }
+            }
+
+            TextTablePrinter printer = new TextTablePrinter(app.getErrorStream());
+            printer.setColumnWidth(1, 0);
+            printer.setColumnSeparator(LIST_TABLE_SEPARATOR);
+            builder.print(printer);
+            app.println();
+            app.pressReturnToContinue();
+        }
+    }
+
+    /**
+     * The value for the long option set.
+     */
+    private static final String OPTION_DSCFG_LONG_SET = "set";
+
+    /**
+     * The value for the long option type.
+     */
+    private static final String OPTION_DSCFG_LONG_TYPE = "type";
+
+    /**
+     * The value for the short option property.
+     */
+    private static final Character OPTION_DSCFG_SHORT_SET = null;
+
+    /**
+     * The value for the short option type.
+     */
+    private static final Character OPTION_DSCFG_SHORT_TYPE = 't';
+
+    /**
+     * The value for the long option remove (this is used only internally).
+     */
+    private static final String OPTION_DSCFG_LONG_REMOVE = "remove";
+
+    /**
+     * The value for the long option reset (this is used only internally).
+     */
+    private static final String OPTION_DSCFG_LONG_RESET = "reset";
+
+    /**
+     * Creates a new create-xxx sub-command for an instantiable relation.
+     *
+     * @param <C>
+     *            The type of managed object which can be created.
+     * @param <S>
+     *            The type of server managed object which can be created.
+     * @param parser
+     *            The sub-command argument parser.
+     * @param p
+     *            The parent managed object path.
+     * @param r
+     *            The instantiable relation.
+     * @return Returns the new create-xxx sub-command.
+     * @throws ArgumentException
+     *             If the sub-command could not be created successfully.
+     */
+    public static <C extends ConfigurationClient, S extends Configuration> CreateSubCommandHandler<C, S> create(
+            SubCommandArgumentParser parser, ManagedObjectPath<?, ?> p, InstantiableRelationDefinition<C, S> r)
+            throws ArgumentException {
+        return new CreateSubCommandHandler<C, S>(parser, p, r, r.getNamingPropertyDefinition(), p.child(r, "DUMMY"));
+    }
+
+    /**
+     * Creates a new create-xxx sub-command for a sets relation.
+     *
+     * @param <C>
+     *            The type of managed object which can be created.
+     * @param <S>
+     *            The type of server managed object which can be created.
+     * @param parser
+     *            The sub-command argument parser.
+     * @param p
+     *            The parent managed object path.
+     * @param r
+     *            The set relation.
+     * @return Returns the new create-xxx sub-command.
+     * @throws ArgumentException
+     *             If the sub-command could not be created successfully.
+     */
+    public static <C extends ConfigurationClient, S extends Configuration> CreateSubCommandHandler<C, S> create(
+            SubCommandArgumentParser parser, ManagedObjectPath<?, ?> p, SetRelationDefinition<C, S> r)
+            throws ArgumentException {
+        return new CreateSubCommandHandler<C, S>(parser, p, r, null, p.child(r));
+    }
+
+    /**
+     * Creates a new create-xxx sub-command for an optional relation.
+     *
+     * @param <C>
+     *            The type of managed object which can be created.
+     * @param <S>
+     *            The type of server managed object which can be created.
+     * @param parser
+     *            The sub-command argument parser.
+     * @param p
+     *            The parent managed object path.
+     * @param r
+     *            The optional relation.
+     * @return Returns the new create-xxx sub-command.
+     * @throws ArgumentException
+     *             If the sub-command could not be created successfully.
+     */
+    public static <C extends ConfigurationClient, S extends Configuration> CreateSubCommandHandler<C, S> create(
+            SubCommandArgumentParser parser, ManagedObjectPath<?, ?> p, OptionalRelationDefinition<C, S> r)
+            throws ArgumentException {
+        return new CreateSubCommandHandler<C, S>(parser, p, r, null, p.child(r));
+    }
+
+    /**
+     * Interactively lets a user create a new managed object beneath a parent.
+     *
+     * @param <C>
+     *            The type of managed object which can be created.
+     * @param <S>
+     *            The type of server managed object which can be created.
+     * @param app
+     *            The console application.
+     * @param context
+     *            The management context.
+     * @param parent
+     *            The parent managed object.
+     * @param rd
+     *            The relation beneath which the child managed object should be created.
+     * @return Returns a MenuResult.success() containing the name of the created managed object if it was created
+     *         successfully, or MenuResult.quit(), or MenuResult.cancel(), if the managed object was edited
+     *         interactively and the user chose to quit or cancel.
+     * @throws ClientException
+     *             If an unrecoverable client exception occurred whilst interacting with the server.
+     */
+    public static <C extends ConfigurationClient, S extends Configuration> MenuResult<String> createManagedObject(
+            ConsoleApplication app, ManagementContext context, ManagedObject<?> parent,
+            InstantiableRelationDefinition<C, S> rd) throws ClientException {
+        return createManagedObject(app, context, parent, rd, null);
+    }
+
+    /**
+     * Interactively lets a user create a new managed object beneath a parent.
+     *
+     * @param <C>
+     *            The type of managed object which can be created.
+     * @param <S>
+     *            The type of server managed object which can be created.
+     * @param app
+     *            The console application.
+     * @param context
+     *            The management context.
+     * @param parent
+     *            The parent managed object.
+     * @param rd
+     *            The relation beneath which the child managed object should be created.
+     * @param handler
+     *            The subcommand handler whose command builder must be updated.
+     * @return Returns a MenuResult.success() containing the name of the created managed object if it was created
+     *         successfully, or MenuResult.quit(), or MenuResult.cancel(), if the managed object was edited
+     *         interactively and the user chose to quit or cancel.
+     * @throws ClientException
+     *             If an unrecoverable client exception occurred whilst interacting with the server.
+     * @throws ClientException
+     *             If an error occurred whilst interacting with the console.
+     */
+    private static <C extends ConfigurationClient, S extends Configuration> MenuResult<String> createManagedObject(
+            ConsoleApplication app, ManagementContext context, ManagedObject<?> parent,
+            InstantiableRelationDefinition<C, S> rd, SubCommandHandler handler) throws ClientException {
+        AbstractManagedObjectDefinition<C, S> d = rd.getChildDefinition();
+
+        // First determine what type of component the user wants to create.
+        MenuResult<ManagedObjectDefinition<? extends C, ? extends S>> result;
+        result = getTypeInteractively(app, d, Collections.<String> emptySet());
+
+        ManagedObjectDefinition<? extends C, ? extends S> mod;
+        if (result.isSuccess()) {
+            mod = result.getValue();
+        } else if (result.isCancel()) {
+            return MenuResult.cancel();
+        } else {
+            return MenuResult.quit();
+        }
+
+        // Now create the component.
+        // FIXME: handle default value exceptions?
+        List<PropertyException> exceptions = new LinkedList<PropertyException>();
+        app.println();
+        app.println();
+        ManagedObject<? extends C> mo = createChildInteractively(app, parent, rd, mod, exceptions);
+
+        // Let the user interactively configure the managed object and commit it.
+        MenuResult<Void> result2 = commitManagedObject(app, context, mo, handler);
+        if (result2.isCancel()) {
+            return MenuResult.cancel();
+        } else if (result2.isQuit()) {
+            return MenuResult.quit();
+        } else {
+            return MenuResult.success(mo.getManagedObjectPath().getName());
+        }
+    }
+
+    /**
+     * Check that any referenced components are enabled if required.
+     */
+    private static MenuResult<Void> checkReferences(ConsoleApplication app, ManagementContext context,
+            ManagedObject<?> mo, SubCommandHandler handler) throws ClientException {
+        ManagedObjectDefinition<?, ?> d = mo.getManagedObjectDefinition();
+        LocalizableMessage ufn = d.getUserFriendlyName();
+
+        try {
+            for (PropertyDefinition<?> pd : d.getAllPropertyDefinitions()) {
+                if (pd instanceof AggregationPropertyDefinition<?, ?>) {
+                    AggregationPropertyDefinition<?, ?> apd = (AggregationPropertyDefinition<?, ?>) pd;
+
+                    // Skip this aggregation if the referenced managed objects
+                    // do not need to be enabled.
+                    if (!apd.getTargetNeedsEnablingCondition().evaluate(context, mo)) {
+                        continue;
+                    }
+
+                    // The referenced component(s) must be enabled.
+                    for (String name : mo.getPropertyValues(apd)) {
+                        ManagedObjectPath<?, ?> path = apd.getChildPath(name);
+                        LocalizableMessage rufn = path.getManagedObjectDefinition().getUserFriendlyName();
+                        ManagedObject<?> ref;
+                        try {
+                            ref = context.getManagedObject(path);
+                        } catch (DefinitionDecodingException e) {
+                            LocalizableMessage msg = ERR_DSCFG_ERROR_GET_CHILD_DDE.get(rufn, rufn, rufn);
+                            throw new ClientException(ReturnCode.OTHER, msg);
+                        } catch (ManagedObjectDecodingException e) {
+                            // FIXME: should not abort here. Instead, display the
+                            // errors (if verbose) and apply the changes to the
+                            // partial managed object.
+                            LocalizableMessage msg = ERR_DSCFG_ERROR_GET_CHILD_MODE.get(rufn);
+                            throw new ClientException(ReturnCode.OTHER, msg, e);
+                        } catch (ManagedObjectNotFoundException e) {
+                            LocalizableMessage msg = ERR_DSCFG_ERROR_GET_CHILD_MONFE.get(rufn);
+                            throw new ClientException(ReturnCode.NO_SUCH_OBJECT, msg);
+                        }
+
+                        Condition condition = apd.getTargetIsEnabledCondition();
+                        while (!condition.evaluate(context, ref)) {
+                            boolean isBadReference = true;
+
+                            if (condition instanceof ContainsCondition) {
+                                // Attempt to automatically enable the managed object.
+                                ContainsCondition cvc = (ContainsCondition) condition;
+                                app.println();
+                                if (app.confirmAction(
+                                        INFO_EDITOR_PROMPT_ENABLED_REFERENCED_COMPONENT.get(rufn, name, ufn), true)) {
+                                    cvc.setPropertyValue(ref);
+                                    try {
+                                        ref.commit();
+                                        isBadReference = false;
+                                    } catch (MissingMandatoryPropertiesException e) {
+                                        // Give the user the chance to fix the problems.
+                                        app.println();
+                                        displayMissingMandatoryPropertyException(app, e);
+                                        app.println();
+                                        if (app.confirmAction(INFO_DSCFG_PROMPT_EDIT.get(rufn), true)) {
+                                            MenuResult<Void> result = SetPropSubCommandHandler.modifyManagedObject(app,
+                                                    context, ref, handler);
+                                            if (result.isQuit()) {
+                                                return result;
+                                            } else if (result.isSuccess()) {
+                                                // The referenced component was modified
+                                                // successfully, but may still be disabled.
+                                                isBadReference = false;
+                                            }
+                                        }
+                                    } catch (ConcurrentModificationException e) {
+                                        LocalizableMessage msg = ERR_DSCFG_ERROR_CREATE_CME.get(ufn);
+                                        throw new ClientException(ReturnCode.CONSTRAINT_VIOLATION, msg);
+                                    } catch (OperationRejectedException e) {
+                                        // Give the user the chance to fix the problems.
+                                        app.println();
+                                        displayOperationRejectedException(app, e);
+                                        app.println();
+                                        if (app.confirmAction(INFO_DSCFG_PROMPT_EDIT.get(rufn), true)) {
+                                            MenuResult<Void> result = SetPropSubCommandHandler.modifyManagedObject(app,
+                                                    context, ref, handler);
+                                            if (result.isQuit()) {
+                                                return result;
+                                            } else if (result.isSuccess()) {
+                                                // The referenced component was modified
+                                                // successfully, but may still be disabled.
+                                                isBadReference = false;
+                                            }
+                                        }
+                                    } catch (ManagedObjectAlreadyExistsException e) {
+                                        // Should never happen.
+                                        throw new IllegalStateException(e);
+                                    }
+                                }
+                            } else {
+                                app.println();
+                                if (app.confirmAction(INFO_DSCFG_PROMPT_EDIT_TO_ENABLE.get(rufn, name, ufn), true)) {
+                                    MenuResult<Void> result = SetPropSubCommandHandler.modifyManagedObject(app,
+                                            context, ref, handler);
+                                    if (result.isQuit()) {
+                                        return result;
+                                    } else if (result.isSuccess()) {
+                                        // The referenced component was modified
+                                        // successfully, but may still be disabled.
+                                        isBadReference = false;
+                                    }
+                                }
+                            }
+
+                            // If the referenced component is still disabled because
+                            // the user refused to modify it, then give the used the
+                            // option of editing the referencing component.
+                            if (isBadReference) {
+                                app.println();
+                                app.println(ERR_SET_REFERENCED_COMPONENT_DISABLED.get(ufn, rufn));
+                                app.println();
+                                if (app.confirmAction(INFO_DSCFG_PROMPT_EDIT_AGAIN.get(ufn), true)) {
+                                    return MenuResult.again();
+                                }
+                                return MenuResult.cancel();
+                            }
+                        }
+                    }
+                }
+            }
+        } catch (AuthorizationException e) {
+            LocalizableMessage msg = ERR_DSCFG_ERROR_CREATE_AUTHZ.get(ufn);
+            throw new ClientException(ReturnCode.INSUFFICIENT_ACCESS_RIGHTS, msg);
+        } catch (ErrorResultException e) {
+            LocalizableMessage msg = ERR_DSCFG_ERROR_CREATE_CE.get(ufn, e.getMessage());
+            throw new ClientException(ReturnCode.OTHER, msg);
+        }
+
+        return MenuResult.success();
+    }
+
+    /** Commit a new managed object's configuration. */
+    private static MenuResult<Void> commitManagedObject(ConsoleApplication app, ManagementContext context,
+            ManagedObject<?> mo, SubCommandHandler handler) throws ClientException {
+        ManagedObjectDefinition<?, ?> d = mo.getManagedObjectDefinition();
+        LocalizableMessage ufn = d.getUserFriendlyName();
+
+        PropertyValueEditor editor = new PropertyValueEditor(app, context);
+        while (true) {
+            // Interactively set properties if applicable.
+            if (app.isInteractive()) {
+                SortedSet<PropertyDefinition<?>> properties = new TreeSet<PropertyDefinition<?>>();
+                for (PropertyDefinition<?> pd : d.getAllPropertyDefinitions()) {
+                    if (pd.hasOption(PropertyOption.HIDDEN)) {
+                        continue;
+                    }
+                    if (!app.isAdvancedMode() && pd.hasOption(PropertyOption.ADVANCED)) {
+                        continue;
+                    }
+                    properties.add(pd);
+                }
+
+                MenuResult<Void> result = editor.edit(mo, properties, true);
+
+                // Interactively enable/edit referenced components.
+                if (result.isSuccess()) {
+                    result = checkReferences(app, context, mo, handler);
+                    if (result.isAgain()) {
+                        // Edit again.
+                        continue;
+                    }
+                }
+
+                if (result.isQuit()) {
+                    if (!app.isMenuDrivenMode()) {
+                        // User chose to cancel any changes.
+                        app.println();
+                        app.println(INFO_DSCFG_CONFIRM_CREATE_FAIL.get(ufn));
+                    }
+                    return MenuResult.quit();
+                } else if (result.isCancel()) {
+                    return MenuResult.cancel();
+                }
+            }
+
+            try {
+                // Create the managed object.
+                mo.commit();
+
+                // Output success message.
+                if (app.isInteractive() || app.isVerbose()) {
+                    app.println();
+                    app.println(INFO_DSCFG_CONFIRM_CREATE_SUCCESS.get(ufn));
+                }
+
+                if (handler != null) {
+                    for (PropertyEditorModification<?> mod : editor.getModifications()) {
+                        try {
+                            Argument arg = createArgument(mod);
+                            handler.getCommandBuilder().addArgument(arg);
+                        } catch (ArgumentException ae) {
+                            // This is a bug
+                            throw new RuntimeException("Unexpected error generating the command builder: " + ae, ae);
+                        }
+                    }
+                    handler.setCommandBuilderUseful(true);
+                }
+                return MenuResult.success();
+            } catch (MissingMandatoryPropertiesException e) {
+                if (app.isInteractive()) {
+                    // If interactive, give the user the chance to fix the
+                    // problems.
+                    app.println();
+                    displayMissingMandatoryPropertyException(app, e);
+                    app.println();
+                    if (!app.confirmAction(INFO_DSCFG_PROMPT_EDIT_AGAIN.get(ufn), true)) {
+                        return MenuResult.cancel();
+                    }
+                } else {
+                    throw new ClientException(ReturnCode.CONSTRAINT_VIOLATION, e.getMessageObject(), e);
+                }
+            } catch (AuthorizationException e) {
+                LocalizableMessage msg = ERR_DSCFG_ERROR_CREATE_AUTHZ.get(ufn);
+                throw new ClientException(ReturnCode.INSUFFICIENT_ACCESS_RIGHTS, msg);
+            } catch (ConcurrentModificationException e) {
+                LocalizableMessage msg = ERR_DSCFG_ERROR_CREATE_CME.get(ufn);
+                throw new ClientException(ReturnCode.CONSTRAINT_VIOLATION, msg);
+            } catch (OperationRejectedException e) {
+                if (app.isInteractive()) {
+                    // If interactive, give the user the chance to fix the
+                    // problems.
+                    app.println();
+                    displayOperationRejectedException(app, e);
+                    app.println();
+                    if (!app.confirmAction(INFO_DSCFG_PROMPT_EDIT_AGAIN.get(ufn), true)) {
+                        return MenuResult.cancel();
+                    }
+                } else {
+                    throw new ClientException(ReturnCode.CONSTRAINT_VIOLATION, e.getMessageObject(), e);
+                }
+            } catch (ErrorResultException e) {
+                final LocalizableMessage msg = ERR_DSCFG_ERROR_CREATE_CE.get(ufn, e.getMessage());
+                if (app.isInteractive()) {
+                    app.println();
+                    app.printVerboseMessage(msg);
+                    return MenuResult.cancel();
+                } else {
+                    throw new ClientException(ReturnCode.CLIENT_SIDE_SERVER_DOWN, msg);
+                }
+            } catch (ManagedObjectAlreadyExistsException e) {
+                final LocalizableMessage msg = ERR_DSCFG_ERROR_CREATE_MOAEE.get(ufn);
+                if (app.isInteractive()) {
+                    app.println();
+                    app.printVerboseMessage(msg);
+                    return MenuResult.cancel();
+                } else {
+                    throw new ClientException(ReturnCode.ENTRY_ALREADY_EXISTS, msg);
+                }
+            }
+        }
+    }
+
+    /** Interactively create the child by prompting for the name. */
+    private static <C extends ConfigurationClient, S
+        extends Configuration> ManagedObject<? extends C> createChildInteractively(
+            ConsoleApplication app, final ManagedObject<?> parent,
+            final InstantiableRelationDefinition<C, S> irelation,
+            final ManagedObjectDefinition<? extends C, ? extends S> d, final List<PropertyException> exceptions)
+            throws ClientException {
+
+        ValidationCallback<ManagedObject<? extends C>> validator
+            = new ValidationCallback<ManagedObject<? extends C>>() {
+
+                public ManagedObject<? extends C> validate(ConsoleApplication app, String input)
+                        throws ClientException {
+                    ManagedObject<? extends C> child;
+
+                    // First attempt to create the child, this will guarantee that
+                    // the name is acceptable.
+                    try {
+                        child = parent.createChild(irelation, d, input, exceptions);
+                    } catch (IllegalManagedObjectNameException e) {
+                        ClientException ae = ArgumentExceptionFactory.adaptIllegalManagedObjectNameException(e, d);
+                        app.println();
+                        app.println(ae.getMessageObject());
+                        app.println();
+                        return null;
+                    }
+
+                    // Make sure that there are not any other children with the
+                    // same name.
+                    try {
+                        // Attempt to retrieve a child using this name.
+                        parent.getChild(irelation, input);
+                    } catch (AuthorizationException e) {
+                        LocalizableMessage msg = ERR_DSCFG_ERROR_CREATE_AUTHZ.get(irelation.getUserFriendlyName());
+                        throw new ClientException(ReturnCode.ERROR_USER_DATA, msg);
+                    } catch (ConcurrentModificationException e) {
+                        LocalizableMessage msg = ERR_DSCFG_ERROR_CREATE_CME.get(irelation.getUserFriendlyName());
+                        throw new ClientException(ReturnCode.CONSTRAINT_VIOLATION, msg);
+                    } catch (ErrorResultException e) {
+                        LocalizableMessage msg = ERR_DSCFG_ERROR_CREATE_CE.get(irelation.getUserFriendlyName(),
+                                e.getMessage());
+                        throw new ClientException(ReturnCode.APPLICATION_ERROR, msg);
+                    } catch (DefinitionDecodingException e) {
+                        // Do nothing.
+                    } catch (ManagedObjectDecodingException e) {
+                        // Do nothing.
+                    } catch (ManagedObjectNotFoundException e) {
+                        // The child does not already exist so this name is ok.
+                        return child;
+                    }
+
+                    // A child with the specified name must already exist.
+                    LocalizableMessage msg = ERR_DSCFG_ERROR_CREATE_NAME_ALREADY_EXISTS.get(
+                            irelation.getUserFriendlyName(), input);
+                    app.println();
+                    app.println(msg);
+                    app.println();
+                    return null;
+                }
+            };
+
+        // Display additional help if the name is a naming property.
+        LocalizableMessage ufn = d.getUserFriendlyName();
+        PropertyDefinition<?> pd = irelation.getNamingPropertyDefinition();
+        if (pd != null) {
+            app.println(INFO_DSCFG_CREATE_NAME_PROMPT_NAMING.get(ufn, pd.getName()));
+
+            app.println();
+            app.errPrintln(pd.getSynopsis(), 4);
+
+            if (pd.getDescription() != null) {
+                app.println();
+                app.errPrintln(pd.getDescription(), 4);
+            }
+
+            PropertyDefinitionUsageBuilder b = new PropertyDefinitionUsageBuilder(true);
+            TableBuilder builder = new TableBuilder();
+            builder.startRow();
+            builder.appendCell(INFO_EDITOR_HEADING_SYNTAX.get());
+            builder.appendCell(b.getUsage(pd));
+
+            TextTablePrinter printer = new TextTablePrinter(app.getErrorStream());
+            printer.setDisplayHeadings(false);
+            printer.setIndentWidth(4);
+            printer.setColumnWidth(1, 0);
+
+            app.println();
+            builder.print(printer);
+            app.println();
+
+            return app.readValidatedInput(INFO_DSCFG_CREATE_NAME_PROMPT_NAMING_CONT.get(ufn), validator);
+        } else {
+            return app.readValidatedInput(INFO_DSCFG_CREATE_NAME_PROMPT.get(ufn), validator);
+        }
+    }
+
+    /** Interactively ask the user which type of component they want to create. */
+    private static <C extends ConfigurationClient, S extends Configuration> MenuResult
+        <ManagedObjectDefinition<? extends C, ? extends S>> getTypeInteractively(
+            ConsoleApplication app, AbstractManagedObjectDefinition<C, S> d, Set<String> prohibitedTypes)
+            throws ClientException {
+        // First get the list of available of sub-types.
+        List<ManagedObjectDefinition<? extends C, ? extends S>> filteredTypes
+            = new LinkedList<ManagedObjectDefinition<? extends C, ? extends S>>(getSubTypes(d).values());
+        boolean isOnlyOneType = filteredTypes.size() == 1;
+
+        Iterator<ManagedObjectDefinition<? extends C, ? extends S>> i;
+        for (i = filteredTypes.iterator(); i.hasNext();) {
+            ManagedObjectDefinition<? extends C, ? extends S> cd = i.next();
+
+            if (prohibitedTypes.contains(cd.getName())) {
+                // Remove filtered types.
+                i.remove();
+            } else if (!app.isAdvancedMode()) {
+                // Only display advanced types and custom types in advanced mode.
+                if (cd.hasOption(ManagedObjectOption.ADVANCED)) {
+                    i.remove();
+                } else if (CLIProfile.getInstance().isForCustomization(cd)) {
+                    i.remove();
+                }
+            }
+        }
+
+        // If there is only one choice then return immediately.
+        if (filteredTypes.size() == 0) {
+            LocalizableMessage msg = ERR_DSCFG_ERROR_NO_AVAILABLE_TYPES.get(d.getUserFriendlyName());
+            app.println(msg);
+            return MenuResult.<ManagedObjectDefinition<? extends C, ? extends S>> cancel();
+        } else if (filteredTypes.size() == 1) {
+            ManagedObjectDefinition<? extends C, ? extends S> type = filteredTypes.iterator().next();
+            if (!isOnlyOneType) {
+                // Only one option available so confirm that the user wishes to
+                // use it.
+                LocalizableMessage msg = INFO_DSCFG_TYPE_PROMPT_SINGLE.get(d.getUserFriendlyName(),
+                        type.getUserFriendlyName());
+                if (!app.confirmAction(msg, true)) {
+                    return MenuResult.cancel();
+                }
+            }
+            return MenuResult.<ManagedObjectDefinition<? extends C, ? extends S>> success(type);
+        } else {
+            MenuBuilder<ManagedObjectDefinition<? extends C, ? extends S>> builder
+                = new MenuBuilder<ManagedObjectDefinition<? extends C, ? extends S>>(app);
+            LocalizableMessage msg = INFO_DSCFG_CREATE_TYPE_PROMPT.get(d.getUserFriendlyName());
+            builder.setMultipleColumnThreshold(MULTI_COLUMN_THRESHOLD);
+            builder.setPrompt(msg);
+
+            for (ManagedObjectDefinition<? extends C, ? extends S> mod : filteredTypes) {
+
+                LocalizableMessage option = mod.getUserFriendlyName();
+                if (CLIProfile.getInstance().isForCustomization(mod)) {
+                    option = INFO_DSCFG_CUSTOM_TYPE_OPTION.get(option);
+                } else if (mod == d) {
+                    option = INFO_DSCFG_GENERIC_TYPE_OPTION.get(option);
+                }
+                builder.addNumberedOption(option,
+                        MenuResult.<ManagedObjectDefinition<? extends C, ? extends S>> success(mod));
+            }
+            builder.addHelpOption(new TypeHelpCallback<C, S>(d));
+            if (app.isMenuDrivenMode()) {
+                builder.addCancelOption(true);
+            }
+            builder.addQuitOption();
+            return builder.toMenu().run();
+        }
+    }
+
+    /** The sub-commands naming arguments. */
+    private final List<StringArgument> namingArgs;
+
+    /** The optional naming property definition. */
+    private final PropertyDefinition<?> namingPropertyDefinition;
+
+    /** The path of the parent managed object. */
+    private final ManagedObjectPath<?, ?> path;
+
+    /**
+     * The argument which should be used to specify zero or more property values.
+     */
+    private final StringArgument propertySetArgument;
+
+    /** The relation which should be used for creating children. */
+    private final RelationDefinition<C, S> relation;
+
+    /** The sub-command associated with this handler. */
+    private final SubCommand subCommand;
+
+    /**
+     * The argument which should be used to specify the type of managed object to be created.
+     */
+    private final StringArgument typeArgument;
+
+    /**
+     * The set of instantiable managed object definitions and their associated type option value.
+     */
+    private final SortedMap<String, ManagedObjectDefinition<? extends C, ? extends S>> types;
+
+    /** The syntax of the type argument. */
+    private final String typeUsage;
+
+    /** Common constructor. */
+    private CreateSubCommandHandler(SubCommandArgumentParser parser, ManagedObjectPath<?, ?> p,
+            RelationDefinition<C, S> r, PropertyDefinition<?> pd, ManagedObjectPath<?, ?> c) throws ArgumentException {
+        this.path = p;
+        this.relation = r;
+        this.namingPropertyDefinition = pd;
+
+        // Create the sub-command.
+        String name = "create-" + r.getName();
+        LocalizableMessage description = INFO_DSCFG_DESCRIPTION_SUBCMD_CREATE.get(r.getChildDefinition()
+                .getUserFriendlyPluralName());
+        this.subCommand = new SubCommand(parser, name, false, 0, 0, null, description);
+
+        // Create the -t argument which is used to specify the type of
+        // managed object to be created.
+        this.types = getSubTypes(r.getChildDefinition());
+
+        // Create the naming arguments.
+        this.namingArgs = createNamingArgs(subCommand, c, true);
+
+        // Build the -t option usage.
+        this.typeUsage = getSubTypesUsage(r.getChildDefinition());
+
+        // Create the --property argument which is used to specify
+        // property values.
+        this.propertySetArgument = new StringArgument(OPTION_DSCFG_LONG_SET, OPTION_DSCFG_SHORT_SET,
+                OPTION_DSCFG_LONG_SET, false, true, true, INFO_VALUE_SET_PLACEHOLDER.get(), null, null,
+                INFO_DSCFG_DESCRIPTION_PROP_VAL.get());
+        this.subCommand.addArgument(this.propertySetArgument);
+
+        if (!types.containsKey(DSConfig.GENERIC_TYPE)) {
+            // The option is mandatory when non-interactive.
+            this.typeArgument = new StringArgument("type", OPTION_DSCFG_SHORT_TYPE, OPTION_DSCFG_LONG_TYPE, false,
+                    false, true, INFO_TYPE_PLACEHOLDER.get(), null, null, INFO_DSCFG_DESCRIPTION_TYPE.get(r
+                            .getChildDefinition().getUserFriendlyName(), typeUsage));
+        } else {
+            // The option has a sensible default "generic".
+            this.typeArgument = new StringArgument("type", OPTION_DSCFG_SHORT_TYPE, OPTION_DSCFG_LONG_TYPE, false,
+                    false, true, INFO_TYPE_PLACEHOLDER.get(), DSConfig.GENERIC_TYPE, null,
+                    INFO_DSCFG_DESCRIPTION_TYPE_DEFAULT.get(r.getChildDefinition().getUserFriendlyName(),
+                            DSConfig.GENERIC_TYPE, typeUsage));
+
+            // Hide the option if it defaults to generic and generic is the
+            // only possible value.
+            if (types.size() == 1) {
+                this.typeArgument.setHidden(true);
+            }
+        }
+        this.subCommand.addArgument(this.typeArgument);
+
+        // Register the tags associated with the child managed objects.
+        addTags(relation.getChildDefinition().getAllTags());
+    }
+
+    /**
+     * Gets the relation definition associated with the type of component that this sub-command handles.
+     *
+     * @return Returns the relation definition associated with the type of component that this sub-command handles.
+     */
+    public RelationDefinition<?, ?> getRelationDefinition() {
+        return relation;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public SubCommand getSubCommand() {
+        return subCommand;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public MenuResult<Integer> run(ConsoleApplication app, LDAPManagementContextFactory factory)
+            throws ArgumentException, ClientException {
+        LocalizableMessage ufn = relation.getUserFriendlyName();
+
+        // Get the naming argument values.
+        List<String> names = getNamingArgValues(app, namingArgs);
+        // Reset the command builder
+        getCommandBuilder().clearArguments();
+
+        setCommandBuilderUseful(false);
+
+        // Update the command builder.
+        updateCommandBuilderWithSubCommand();
+
+        // Add the child managed object.
+        ManagementContext context = factory.getManagementContext(app);
+        MenuResult<ManagedObject<?>> result;
+        try {
+            result = getManagedObject(app, context, path, names);
+        } catch (AuthorizationException e) {
+            LocalizableMessage msg = ERR_DSCFG_ERROR_CREATE_AUTHZ.get(ufn);
+            throw new ClientException(ReturnCode.INSUFFICIENT_ACCESS_RIGHTS, msg);
+        } catch (DefinitionDecodingException e) {
+            LocalizableMessage pufn = path.getManagedObjectDefinition().getUserFriendlyName();
+            LocalizableMessage msg = ERR_DSCFG_ERROR_GET_PARENT_DDE.get(pufn, pufn, pufn);
+            throw new ClientException(ReturnCode.OTHER, msg);
+        } catch (ManagedObjectDecodingException e) {
+            LocalizableMessage pufn = path.getManagedObjectDefinition().getUserFriendlyName();
+            LocalizableMessage msg = ERR_DSCFG_ERROR_GET_PARENT_MODE.get(pufn);
+            throw new ClientException(ReturnCode.OTHER, msg, e);
+        } catch (ConcurrentModificationException e) {
+            LocalizableMessage msg = ERR_DSCFG_ERROR_CREATE_CME.get(ufn);
+            throw new ClientException(ReturnCode.CONSTRAINT_VIOLATION, msg);
+        } catch (ManagedObjectNotFoundException e) {
+            LocalizableMessage pufn = path.getManagedObjectDefinition().getUserFriendlyName();
+            LocalizableMessage msg = ERR_DSCFG_ERROR_GET_PARENT_MONFE.get(pufn);
+            if (app.isInteractive()) {
+                app.println();
+                app.printVerboseMessage(msg);
+                return MenuResult.cancel();
+            } else {
+                throw new ClientException(ReturnCode.NO_SUCH_OBJECT, msg);
+            }
+        }
+
+        if (result.isQuit()) {
+            if (!app.isMenuDrivenMode()) {
+                // User chose to cancel creation.
+                app.println();
+                app.println(INFO_DSCFG_CONFIRM_CREATE_FAIL.get(ufn));
+            }
+            return MenuResult.quit();
+        } else if (result.isCancel()) {
+            // Must be menu driven, so no need for error message.
+            return MenuResult.cancel();
+        }
+
+        ManagedObject<?> parent = result.getValue();
+
+        // Determine the type of managed object to be created. If we are creating
+        // a managed object beneath a set relation then prevent creation of
+        // duplicates.
+        Set<String> prohibitedTypes;
+        if (relation instanceof SetRelationDefinition) {
+            SetRelationDefinition<C, S> sr = (SetRelationDefinition<C, S>) relation;
+            prohibitedTypes = new HashSet<String>();
+            try {
+                for (String child : parent.listChildren(sr)) {
+                    prohibitedTypes.add(child);
+                }
+            } catch (AuthorizationException e) {
+                LocalizableMessage msg = ERR_DSCFG_ERROR_CREATE_AUTHZ.get(ufn);
+                throw new ClientException(ReturnCode.INSUFFICIENT_ACCESS_RIGHTS, msg);
+            } catch (ConcurrentModificationException e) {
+                LocalizableMessage msg = ERR_DSCFG_ERROR_CREATE_CME.get(ufn);
+                throw new ClientException(ReturnCode.CONSTRAINT_VIOLATION, msg);
+            } catch (ErrorResultException e) {
+                LocalizableMessage msg = ERR_DSCFG_ERROR_CREATE_CE.get(ufn, e.getMessage());
+                throw new ClientException(ReturnCode.CLIENT_SIDE_SERVER_DOWN, msg);
+            }
+        } else {
+            // No prohibited types.
+            prohibitedTypes = Collections.emptySet();
+        }
+
+        ManagedObjectDefinition<? extends C, ? extends S> d;
+        if (!typeArgument.isPresent()) {
+            if (app.isInteractive()) {
+                // Let the user choose.
+                MenuResult<ManagedObjectDefinition<? extends C, ? extends S>> dresult;
+                app.println();
+                app.println();
+                dresult = getTypeInteractively(app, relation.getChildDefinition(), prohibitedTypes);
+
+                if (dresult.isSuccess()) {
+                    d = dresult.getValue();
+                } else if (dresult.isCancel()) {
+                    return MenuResult.cancel();
+                } else {
+                    // Must be quit.
+                    if (!app.isMenuDrivenMode()) {
+                        app.println();
+                        app.println(INFO_DSCFG_CONFIRM_CREATE_FAIL.get(ufn));
+                    }
+                    return MenuResult.quit();
+                }
+            } else if (typeArgument.getDefaultValue() != null) {
+                d = types.get(typeArgument.getDefaultValue());
+            } else {
+                throw ArgumentExceptionFactory.missingMandatoryNonInteractiveArgument(typeArgument);
+            }
+        } else {
+            d = types.get(typeArgument.getValue());
+            if (d == null) {
+                throw ArgumentExceptionFactory.unknownSubType(relation, typeArgument.getValue(), typeUsage);
+            }
+        }
+
+        // Encode the provided properties.
+        List<String> propertyArgs = propertySetArgument.getValues();
+        MyPropertyProvider provider = new MyPropertyProvider(d, namingPropertyDefinition, propertyArgs);
+
+        ManagedObject<? extends C> child;
+        List<PropertyException> exceptions = new LinkedList<PropertyException>();
+        boolean isNameProvidedInteractively = false;
+        String providedNamingArgName = null;
+        if (relation instanceof InstantiableRelationDefinition) {
+            InstantiableRelationDefinition<C, S> irelation = (InstantiableRelationDefinition<C, S>) relation;
+            String name = names.get(names.size() - 1);
+            if (name == null) {
+                if (app.isInteractive()) {
+                    app.println();
+                    app.println();
+                    child = createChildInteractively(app, parent, irelation, d, exceptions);
+                    isNameProvidedInteractively = true;
+                    providedNamingArgName = CLIProfile.getInstance().getNamingArgument(irelation);
+                } else {
+                    throw ArgumentExceptionFactory
+                            .missingMandatoryNonInteractiveArgument(namingArgs.get(names.size() - 1));
+                }
+            } else {
+                try {
+                    child = parent.createChild(irelation, d, name, exceptions);
+                } catch (IllegalManagedObjectNameException e) {
+                    throw ArgumentExceptionFactory.adaptIllegalManagedObjectNameException(e, d);
+                }
+            }
+        } else if (relation instanceof SetRelationDefinition) {
+            SetRelationDefinition<C, S> srelation = (SetRelationDefinition<C, S>) relation;
+            child = parent.createChild(srelation, d, exceptions);
+        } else {
+            OptionalRelationDefinition<C, S> orelation = (OptionalRelationDefinition<C, S>) relation;
+            child = parent.createChild(orelation, d, exceptions);
+        }
+
+        // FIXME: display any default behavior exceptions in verbose
+        // mode.
+
+        // Set any properties specified on the command line.
+        for (PropertyDefinition<?> pd : provider.getProperties()) {
+            setProperty(child, provider, pd);
+        }
+
+        // Now the command line changes have been made, create the managed
+        // object interacting with the user to fix any problems if
+        // required.
+        MenuResult<Void> result2 = commitManagedObject(app, context, child, this);
+        if (result2.isCancel()) {
+            return MenuResult.cancel();
+        } else if (result2.isQuit()) {
+            return MenuResult.quit();
+        } else {
+            if (typeArgument.hasValue()) {
+                getCommandBuilder().addArgument(typeArgument);
+            } else {
+                // Set the type provided by the user
+                StringArgument arg = new StringArgument(typeArgument.getName(), OPTION_DSCFG_SHORT_TYPE,
+                        OPTION_DSCFG_LONG_TYPE, false, false, true, INFO_TYPE_PLACEHOLDER.get(),
+                        typeArgument.getDefaultValue(), typeArgument.getPropertyName(), typeArgument.getDescription());
+                arg.addValue(getTypeName(d));
+                getCommandBuilder().addArgument(arg);
+            }
+            if (propertySetArgument.hasValue()) {
+                /*
+                 * We might have some conflicts in terms of arguments: the user might have provided some values that
+                 * were not good and then these have overwritten when asking for them interactively: filter them
+                 */
+                StringArgument filteredArg = new StringArgument(OPTION_DSCFG_LONG_SET, OPTION_DSCFG_SHORT_SET,
+                        OPTION_DSCFG_LONG_SET, false, true, true, INFO_VALUE_SET_PLACEHOLDER.get(), null, null,
+                        INFO_DSCFG_DESCRIPTION_PROP_VAL.get());
+                for (String value : propertySetArgument.getValues()) {
+                    boolean addValue = true;
+                    int index = value.indexOf(':');
+                    if (index != -1) {
+                        String propName = value.substring(0, index);
+                        for (Argument arg : getCommandBuilder().getArguments()) {
+                            for (String value2 : arg.getValues()) {
+                                String prop2Name;
+                                if (OPTION_DSCFG_LONG_SET.equals(arg.getName())
+                                        || OPTION_DSCFG_LONG_REMOVE.equals(arg.getName())) {
+                                    int index2 = value2.indexOf(':');
+                                    if (index2 != -1) {
+                                        prop2Name = value2.substring(0, index2);
+                                    } else {
+                                        prop2Name = null;
+                                    }
+                                } else if (OPTION_DSCFG_LONG_RESET.equals(arg.getName())) {
+                                    prop2Name = value2;
+                                } else {
+                                    prop2Name = null;
+                                }
+                                if (prop2Name != null && prop2Name.equalsIgnoreCase(propName)) {
+                                    addValue = false;
+                                    break;
+                                }
+                            }
+                            if (!addValue) {
+                                break;
+                            }
+                        }
+                    } else {
+                        addValue = false;
+                    }
+                    if (addValue) {
+                        filteredArg.addValue(value);
+                    }
+                }
+                if (filteredArg.hasValue()) {
+                    getCommandBuilder().addArgument(filteredArg);
+                }
+            }
+
+            /* Filter the arguments that are used internally */
+            List<Argument> argsCopy = new LinkedList<Argument>(getCommandBuilder().getArguments());
+            for (Argument arg : argsCopy) {
+                if (arg != null
+                        && (OPTION_DSCFG_LONG_RESET.equals(arg.getName()) || OPTION_DSCFG_LONG_REMOVE.equals(arg
+                                .getName()))) {
+                    getCommandBuilder().removeArgument(arg);
+                }
+            }
+
+            if (isNameProvidedInteractively) {
+                StringArgument arg = new StringArgument(providedNamingArgName, null, providedNamingArgName, false,
+                        true, INFO_NAME_PLACEHOLDER.get(), INFO_DSCFG_DESCRIPTION_NAME_CREATE.get(d
+                                .getUserFriendlyName()));
+                arg.addValue(child.getManagedObjectPath().getName());
+                getCommandBuilder().addArgument(arg);
+            } else {
+                for (StringArgument arg : namingArgs) {
+                    if (arg.isPresent()) {
+                        getCommandBuilder().addArgument(arg);
+                    }
+                }
+            }
+            return MenuResult.success(0);
+        }
+    }
+
+    /** Set a property's initial values. */
+    private <T> void setProperty(ManagedObject<?> mo, MyPropertyProvider provider, PropertyDefinition<T> pd) {
+        Collection<T> values = provider.getPropertyValues(pd);
+
+        // This cannot fail because the property values have already been
+        // validated.
+        mo.setPropertyValues(pd, values);
+    }
+
+    /**
+     * Creates an argument (the one that the user should provide in the command-line) that is equivalent to the
+     * modification proposed by the user in the provided PropertyEditorModification object.
+     *
+     * @param mod
+     *            the object describing the modification made.
+     * @return the argument representing the modification.
+     * @throws ArgumentException
+     *             if there is a problem creating the argument.
+     */
+    private static <T> Argument createArgument(PropertyEditorModification<T> mod) throws ArgumentException {
+        StringArgument arg;
+
+        PropertyDefinition<T> propertyDefinition = mod.getPropertyDefinition();
+        String propName = propertyDefinition.getName();
+
+        switch (mod.getType()) {
+        case ADD:
+            arg = new StringArgument(OPTION_DSCFG_LONG_SET, OPTION_DSCFG_SHORT_SET, OPTION_DSCFG_LONG_SET, false, true,
+                    true, INFO_VALUE_SET_PLACEHOLDER.get(), null, null, INFO_DSCFG_DESCRIPTION_PROP_VAL.get());
+            for (T value : mod.getModificationValues()) {
+                arg.addValue(propName + ':' + getArgumentValue(propertyDefinition, value));
+            }
+            break;
+        case SET:
+            arg = new StringArgument(OPTION_DSCFG_LONG_SET, OPTION_DSCFG_SHORT_SET, OPTION_DSCFG_LONG_SET, false, true,
+                    true, INFO_VALUE_SET_PLACEHOLDER.get(), null, null, INFO_DSCFG_DESCRIPTION_PROP_VAL.get());
+            for (T value : mod.getModificationValues()) {
+                arg.addValue(propName + ':' + getArgumentValue(propertyDefinition, value));
+            }
+            break;
+        case RESET:
+            arg = new StringArgument(OPTION_DSCFG_LONG_RESET, null, OPTION_DSCFG_LONG_RESET, false, true, true,
+                    INFO_PROPERTY_PLACEHOLDER.get(), null, null, INFO_DSCFG_DESCRIPTION_RESET_PROP.get());
+            arg.addValue(propName);
+            break;
+        case REMOVE:
+            arg = new StringArgument(OPTION_DSCFG_LONG_REMOVE, null, OPTION_DSCFG_LONG_REMOVE, false, true, true,
+                    INFO_VALUE_SET_PLACEHOLDER.get(), null, null, INFO_DSCFG_DESCRIPTION_REMOVE_PROP_VAL.get());
+            for (T value : mod.getModificationValues()) {
+                arg.addValue(propName + ':' + getArgumentValue(propertyDefinition, value));
+            }
+            arg = null;
+            break;
+        default:
+            // Bug
+            throw new IllegalStateException("Unknown modification type: " + mod.getType());
+        }
+        return arg;
+    }
+
+    /**
+     * Returns the type name for a given ManagedObjectDefinition.
+     *
+     * @param d
+     *            the ManagedObjectDefinition.
+     * @return the type name for the provided ManagedObjectDefinition.
+     */
+    private String getTypeName(ManagedObjectDefinition<? extends C, ? extends S> d) {
+        String name = d.getName();
+        for (String key : types.keySet()) {
+            ManagedObjectDefinition<? extends C, ? extends S> current = types.get(key);
+            if (current.equals(d)) {
+                name = key;
+                break;
+            }
+        }
+        return name;
+    }
+}
diff --git a/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/DSConfig.java b/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/DSConfig.java
new file mode 100644
index 0000000..c2af15e
--- /dev/null
+++ b/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/DSConfig.java
@@ -0,0 +1,1183 @@
+/*
+ * 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-2010 Sun Microsystems, Inc.
+ *      Portions Copyright 2012-2014 ForgeRock AS
+ */
+package org.forgerock.opendj.config.dsconfig;
+
+import static com.forgerock.opendj.cli.ArgumentConstants.*;
+import static com.forgerock.opendj.cli.CliMessages.*;
+import static com.forgerock.opendj.dsconfig.DsconfigMessages.*;
+import static com.forgerock.opendj.cli.Utils.SHELL_COMMENT_SEPARATOR;
+import static com.forgerock.opendj.cli.Utils.canWrite;
+import static com.forgerock.opendj.cli.Utils.filterExitCode;
+import static com.forgerock.opendj.cli.Utils.formatDateTimeStringForEquivalentCommand;
+import static com.forgerock.opendj.cli.Utils.getCurrentOperationDateMessage;
+import static com.forgerock.opendj.util.StaticUtils.stackTraceToSingleLineString;
+import static org.forgerock.opendj.config.dsconfig.ArgumentExceptionFactory.displayManagedObjectDecodingException;
+import static org.forgerock.opendj.config.dsconfig.ArgumentExceptionFactory.displayMissingMandatoryPropertyException;
+import static org.forgerock.opendj.config.dsconfig.ArgumentExceptionFactory.displayOperationRejectedException;
+import static org.forgerock.util.Utils.closeSilently;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.i18n.slf4j.LocalizedLogger;
+import org.forgerock.opendj.config.ConfigurationFramework;
+import org.forgerock.opendj.config.InstantiableRelationDefinition;
+import org.forgerock.opendj.config.RelationDefinition;
+import org.forgerock.opendj.config.SetRelationDefinition;
+import org.forgerock.opendj.config.Tag;
+import org.forgerock.opendj.config.client.ManagedObjectDecodingException;
+import org.forgerock.opendj.config.client.MissingMandatoryPropertiesException;
+import org.forgerock.opendj.config.client.OperationRejectedException;
+import org.forgerock.opendj.config.server.ConfigException;
+import com.forgerock.opendj.cli.ArgumentException;
+import com.forgerock.opendj.cli.ArgumentGroup;
+import com.forgerock.opendj.cli.BooleanArgument;
+import com.forgerock.opendj.cli.CliConstants;
+import com.forgerock.opendj.cli.ClientException;
+import com.forgerock.opendj.cli.CommandBuilder;
+import com.forgerock.opendj.cli.CommonArguments;
+import com.forgerock.opendj.cli.ConnectionFactoryProvider;
+import com.forgerock.opendj.cli.ConsoleApplication;
+import com.forgerock.opendj.cli.Menu;
+import com.forgerock.opendj.cli.MenuBuilder;
+import com.forgerock.opendj.cli.MenuCallback;
+import com.forgerock.opendj.cli.MenuResult;
+import com.forgerock.opendj.cli.ReturnCode;
+import com.forgerock.opendj.cli.StringArgument;
+import com.forgerock.opendj.cli.SubCommand;
+import com.forgerock.opendj.cli.SubCommandArgumentParser;
+
+
+
+/**
+ * This class provides a command-line tool which enables
+ * administrators to configure the Directory Server.
+ */
+public final class DSConfig extends ConsoleApplication {
+
+  /** The logger */
+  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
+    
+  /** The name of this tool. */
+  final static String DSCONFIGTOOLNAME = "dsconfig";
+
+  /**
+   * The name of a command-line script used to launch an administrative tool.
+   */
+  final static String PROPERTY_SCRIPT_NAME = "org.opends.server.scriptName";
+
+  /**
+   * A menu call-back which runs a sub-command interactively.
+   */
+  private class SubCommandHandlerMenuCallback implements MenuCallback<Integer> {
+
+    /** The sub-command handler. */
+    private final SubCommandHandler handler;
+
+    /**
+     * Creates a new sub-command handler call-back.
+     *
+     * @param handler
+     *          The sub-command handler.
+     */
+    public SubCommandHandlerMenuCallback(SubCommandHandler handler) {
+      this.handler = handler;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public MenuResult<Integer> invoke(ConsoleApplication app)
+    throws ClientException {
+      try {
+        MenuResult<Integer> result = handler.run(app, factory);
+
+        if (result.isQuit()) {
+          return result;
+        } else {
+          if (result.isSuccess() && isInteractive() &&
+              handler.isCommandBuilderUseful())
+          {
+            printCommandBuilder(getCommandBuilder(handler));
+          }
+          // Success or cancel.
+          app.println();
+          app.pressReturnToContinue();
+          return MenuResult.again();
+        }
+      } catch (ArgumentException e) {
+        app.println(e.getMessageObject());
+        return MenuResult.success(1);
+      } catch (ClientException e) {
+        app.println(e.getMessageObject());
+        return MenuResult.success(e.getReturnCode());
+      }
+    }
+  }
+
+  /** The interactive mode sub-menu implementation. */
+  private class SubMenuCallback implements MenuCallback<Integer> {
+
+    /** The menu. */
+    private final Menu<Integer> menu;
+
+    /**
+     * Creates a new sub-menu implementation.
+     *
+     * @param app
+     *          The console application.
+     * @param rd
+     *          The relation definition.
+     * @param ch
+     *          The optional create sub-command.
+     * @param dh
+     *          The optional delete sub-command.
+     * @param lh
+     *          The optional list sub-command.
+     * @param sh
+     *          The option set-prop sub-command.
+     */
+    public SubMenuCallback(ConsoleApplication app, RelationDefinition<?, ?> rd,
+        CreateSubCommandHandler<?, ?> ch, DeleteSubCommandHandler dh,
+        ListSubCommandHandler lh, SetPropSubCommandHandler sh) {
+      LocalizableMessage userFriendlyName = rd.getUserFriendlyName();
+
+      LocalizableMessage userFriendlyPluralName = null;
+      if (rd instanceof InstantiableRelationDefinition<?,?>) {
+        InstantiableRelationDefinition<?, ?> ir =
+          (InstantiableRelationDefinition<?, ?>) rd;
+        userFriendlyPluralName = ir.getUserFriendlyPluralName();
+      } else if (rd instanceof SetRelationDefinition<?,?>) {
+        SetRelationDefinition<?, ?> sr =
+          (SetRelationDefinition<?, ?>) rd;
+        userFriendlyPluralName = sr.getUserFriendlyPluralName();
+      }
+
+      final MenuBuilder<Integer> builder = new MenuBuilder<Integer>(app);
+
+      builder.setTitle(INFO_DSCFG_HEADING_COMPONENT_MENU_TITLE
+          .get(userFriendlyName));
+      builder.setPrompt(INFO_DSCFG_HEADING_COMPONENT_MENU_PROMPT.get());
+
+      if (lh != null) {
+        final SubCommandHandlerMenuCallback callback =
+          new SubCommandHandlerMenuCallback(lh);
+        if (userFriendlyPluralName != null) {
+          builder.addNumberedOption(
+              INFO_DSCFG_OPTION_COMPONENT_MENU_LIST_PLURAL
+                  .get(userFriendlyPluralName), callback);
+        } else {
+          builder
+          .addNumberedOption(INFO_DSCFG_OPTION_COMPONENT_MENU_LIST_SINGULAR
+              .get(userFriendlyName), callback);
+        }
+      }
+
+      if (ch != null) {
+        final SubCommandHandlerMenuCallback callback =
+            new SubCommandHandlerMenuCallback(ch);
+        builder.addNumberedOption(INFO_DSCFG_OPTION_COMPONENT_MENU_CREATE
+            .get(userFriendlyName), callback);
+      }
+
+      if (sh != null) {
+        final SubCommandHandlerMenuCallback callback =
+            new SubCommandHandlerMenuCallback(sh);
+        if (userFriendlyPluralName != null) {
+          builder.addNumberedOption(
+              INFO_DSCFG_OPTION_COMPONENT_MENU_MODIFY_PLURAL
+                  .get(userFriendlyName), callback);
+        } else {
+          builder.addNumberedOption(
+              INFO_DSCFG_OPTION_COMPONENT_MENU_MODIFY_SINGULAR
+                  .get(userFriendlyName), callback);
+        }
+      }
+
+      if (dh != null) {
+        final SubCommandHandlerMenuCallback callback =
+            new SubCommandHandlerMenuCallback(dh);
+        builder.addNumberedOption(INFO_DSCFG_OPTION_COMPONENT_MENU_DELETE
+            .get(userFriendlyName), callback);
+      }
+
+      builder.addBackOption(true);
+      builder.addQuitOption();
+
+      this.menu = builder.toMenu();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final MenuResult<Integer> invoke(ConsoleApplication app)
+    throws ClientException {
+      try {
+        app.println();
+        app.println();
+
+        MenuResult<Integer> result = menu.run();
+
+        if (result.isCancel()) {
+          return MenuResult.again();
+        }
+        return result;
+      } catch (ClientException e) {
+        app.println(e.getMessageObject());
+        return MenuResult.success(1);
+      }
+    }
+  }
+
+  /**
+   * The type name which will be used for the most generic managed
+   * object types when they are instantiable and intended for
+   * customization only.
+   */
+  public static final String CUSTOM_TYPE = "custom";
+
+  /**
+   * The type name which will be used for the most generic managed
+   * object types when they are instantiable and not intended for
+   * customization.
+   */
+  public static final String GENERIC_TYPE = "generic";
+
+  private long sessionStartTime;
+  private boolean sessionStartTimePrinted = false;
+  private int sessionEquivalentOperationNumber = 0;
+
+  /**
+   * Provides the command-line arguments to the main application for
+   * processing.
+   *
+   * @param args
+   *          The set of command-line arguments provided to this
+   *          program.
+   */
+  public static void main(String[] args) {
+    int exitCode = main(args, System.out, System.err);
+    if (exitCode != ReturnCode.SUCCESS.get()) {
+      System.exit(filterExitCode(exitCode));
+    }
+  }
+
+  /**
+   * Provides the command-line arguments to the main application for
+   * processing and returns the exit code as an integer.
+   *
+   * @param args
+   *          The set of command-line arguments provided to this
+   *          program.
+   * @param outStream
+   *          The output stream for standard output.
+   * @param errStream
+   *          The output stream for standard error.
+   * @return Zero to indicate that the program completed successfully,
+   *         or non-zero to indicate that an error occurred.
+   */
+  public static int main(String[] args, OutputStream outStream,
+      OutputStream errStream)
+  {    
+    final DSConfig app = new DSConfig(System.in, outStream, errStream);
+    app.sessionStartTime = System.currentTimeMillis();
+    /*
+     * FIXME: obtain path info from system properties.
+     */
+    if (!ConfigurationFramework.getInstance().isInitialized())
+    {
+      try
+      {
+        ConfigurationFramework.getInstance().initialize();
+      }
+      catch (ConfigException e)
+      {
+        app.println(e.getMessageObject());
+        return ReturnCode.ERROR_INITIALIZING_SERVER.get();
+      }
+    }
+
+    // Run the application.
+    return app.run(args);
+  }
+
+  /** The argument which should be used to request advanced mode. */
+  private BooleanArgument advancedModeArgument;
+
+  /**
+   * The factory which the application should use to retrieve its management
+   * context.
+   */
+  private LDAPManagementContextFactory factory = null;
+
+  /**
+   * Flag indicating whether or not the global arguments have already been
+   * initialized.
+   */
+  private boolean globalArgumentsInitialized = false;
+
+  /** The sub-command handler factory. */
+  private SubCommandHandlerFactory handlerFactory = null;
+
+  /** Mapping of sub-commands to their implementations. */
+  private final Map<SubCommand, SubCommandHandler> handlers =
+    new HashMap<SubCommand, SubCommandHandler>();
+
+  /** Indicates whether or not a sub-command was provided. */
+  private boolean hasSubCommand = true;
+
+  /** The argument which should be used to read dsconfig commands from a file. */
+  private StringArgument batchFileArgument;
+
+  /**
+   * The argument which should be used to request non interactive behavior.
+   */
+  private BooleanArgument noPromptArgument;
+
+  /**
+   * The argument that the user must set to display the equivalent
+   * non-interactive mode argument.
+   */
+  private BooleanArgument displayEquivalentArgument;
+
+  /**
+   * The argument that allows the user to dump the equivalent non-interactive
+   * command to a file.
+   */
+  private StringArgument equivalentCommandFileArgument;
+
+  /** The command-line argument parser. */
+  private final SubCommandArgumentParser parser;
+
+  /** The argument which should be used to request quiet output. */
+  private BooleanArgument quietArgument;
+
+  /** The argument which should be used to request script-friendly output. */
+  private BooleanArgument scriptFriendlyArgument;
+
+  /** The argument which should be used to request usage information. */
+  private BooleanArgument showUsageArgument;
+
+  /** The argument which should be used to request verbose output. */
+  private BooleanArgument verboseArgument;
+
+  /** The argument which should be used to indicate the properties file. */
+  private StringArgument propertiesFileArgument;
+
+  /**
+   * The argument which should be used to indicate that we will not look for
+   * properties file.
+   */
+  private BooleanArgument noPropertiesFileArgument;
+
+  /**
+   * Creates a new DSConfig application instance.
+   *
+   * @param in
+   *          The application input stream.
+   * @param out
+   *          The application output stream.
+   * @param err
+   *          The application error stream.
+   */
+  private DSConfig(InputStream in, OutputStream out, OutputStream err) {
+    super(new PrintStream(out), new PrintStream(err));
+
+    this.parser = new SubCommandArgumentParser(getClass().getName(),
+        INFO_DSCFG_TOOL_DESCRIPTION.get(), false);
+  }
+
+
+
+  /** {@inheritDoc} */
+  @Override
+  public boolean isAdvancedMode() {
+    return advancedModeArgument.isPresent();
+  }
+
+
+
+  /** {@inheritDoc} */
+  @Override
+  public boolean isInteractive() {
+    return !noPromptArgument.isPresent();
+  }
+
+
+
+  /** {@inheritDoc} */
+  @Override
+  public boolean isMenuDrivenMode() {
+    return !hasSubCommand;
+  }
+
+
+
+  /** {@inheritDoc} */
+  @Override
+  public boolean isQuiet() {
+    return quietArgument.isPresent();
+  }
+
+
+
+  /** {@inheritDoc} */
+  @Override
+  public boolean isScriptFriendly() {
+    return scriptFriendlyArgument.isPresent();
+  }
+
+
+
+  /** {@inheritDoc} */
+  @Override
+  public boolean isVerbose() {
+    return verboseArgument.isPresent();
+  }
+
+
+
+  /** Displays the provided message followed by a help usage reference. */
+  private void displayMessageAndUsageReference(LocalizableMessage message) {
+    println(message);
+    println();
+    println(parser.getHelpUsageReference());
+  }
+
+
+
+  /**
+   * Registers the global arguments with the argument parser.
+   *
+   * @throws ArgumentException
+   *           If a global argument could not be registered.
+   */
+  private void initializeGlobalArguments(String[] args)
+  throws ArgumentException {
+    if (!globalArgumentsInitialized) {
+
+      verboseArgument = CommonArguments.getVerbose();
+      quietArgument = CommonArguments.getQuiet();
+      scriptFriendlyArgument = CommonArguments.getScriptFriendly();
+      noPromptArgument = CommonArguments.getNoPrompt();
+      advancedModeArgument = CommonArguments.getAdvancedMode();
+      showUsageArgument = CommonArguments.getShowUsage();
+
+      batchFileArgument = new StringArgument(OPTION_LONG_BATCH_FILE_PATH,
+          OPTION_SHORT_BATCH_FILE_PATH, OPTION_LONG_BATCH_FILE_PATH,
+          false, false, true, INFO_BATCH_FILE_PATH_PLACEHOLDER.get(),
+          null, null,
+          INFO_DESCRIPTION_BATCH_FILE_PATH.get());
+
+      displayEquivalentArgument = new BooleanArgument(
+          OPTION_LONG_DISPLAY_EQUIVALENT,
+          null, OPTION_LONG_DISPLAY_EQUIVALENT,
+          INFO_DSCFG_DESCRIPTION_DISPLAY_EQUIVALENT.get());
+
+      equivalentCommandFileArgument = new StringArgument(
+          OPTION_LONG_EQUIVALENT_COMMAND_FILE_PATH, null,
+          OPTION_LONG_EQUIVALENT_COMMAND_FILE_PATH, false, false, true,
+          INFO_PATH_PLACEHOLDER.get(), null, null,
+          INFO_DSCFG_DESCRIPTION_EQUIVALENT_COMMAND_FILE_PATH.get());
+
+      propertiesFileArgument = new StringArgument("propertiesFilePath",
+          null, OPTION_LONG_PROP_FILE_PATH,
+          false, false, true, INFO_PROP_FILE_PATH_PLACEHOLDER.get(), null, null,
+          INFO_DESCRIPTION_PROP_FILE_PATH.get());
+
+      noPropertiesFileArgument = new BooleanArgument(
+          "noPropertiesFileArgument", null, OPTION_LONG_NO_PROP_FILE,
+          INFO_DESCRIPTION_NO_PROP_FILE.get());
+
+      // Register the global arguments.
+
+      ArgumentGroup toolOptionsGroup = new ArgumentGroup(
+          INFO_DSCFG_DESCRIPTION_OPTIONS_ARGS.get(), 2);
+      parser.addGlobalArgument(advancedModeArgument, toolOptionsGroup);
+
+      parser.addGlobalArgument(showUsageArgument);
+      parser.setUsageArgument(showUsageArgument, getOutputStream());
+      parser.addGlobalArgument(verboseArgument);
+      parser.addGlobalArgument(quietArgument);
+      parser.addGlobalArgument(scriptFriendlyArgument);
+      parser.addGlobalArgument(noPromptArgument);
+      parser.addGlobalArgument(batchFileArgument);
+      parser.addGlobalArgument(displayEquivalentArgument);
+      parser.addGlobalArgument(equivalentCommandFileArgument);
+      parser.addGlobalArgument(propertiesFileArgument);
+      parser.setFilePropertiesArgument(propertiesFileArgument);
+      parser.addGlobalArgument(noPropertiesFileArgument);
+      parser.setNoPropertiesFileArgument(noPropertiesFileArgument);
+
+      globalArgumentsInitialized = true;
+    }
+  }
+
+
+
+  /**
+   * Registers the sub-commands with the argument parser. This method
+   * uses the administration framework introspection APIs to determine
+   * the overall structure of the command-line.
+   *
+   * @throws ArgumentException
+   *           If a sub-command could not be created.
+   */
+  private void initializeSubCommands() throws ArgumentException {
+    if (handlerFactory == null) {
+      handlerFactory = new SubCommandHandlerFactory(parser);
+
+      final Comparator<SubCommand> c = new Comparator<SubCommand>() {
+
+        @Override
+        public int compare(SubCommand o1, SubCommand o2) {
+          return o1.getName().compareTo(o2.getName());
+        }
+      };
+
+      Map<Tag, SortedSet<SubCommand>> groups =
+        new TreeMap<Tag, SortedSet<SubCommand>>();
+      SortedSet<SubCommand> allSubCommands = new TreeSet<SubCommand>(c);
+      for (SubCommandHandler handler : handlerFactory
+          .getAllSubCommandHandlers()) {
+        SubCommand sc = handler.getSubCommand();
+
+        handlers.put(sc, handler);
+        allSubCommands.add(sc);
+
+        // Add the sub-command to its groups.
+        for (Tag tag : handler.getTags()) {
+          SortedSet<SubCommand> group = groups.get(tag);
+          if (group == null) {
+            group = new TreeSet<SubCommand>(c);
+            groups.put(tag, group);
+          }
+          group.add(sc);
+        }
+      }
+
+      // Register the usage arguments.
+      for (Map.Entry<Tag, SortedSet<SubCommand>> group : groups.entrySet()) {
+        Tag tag = group.getKey();
+        SortedSet<SubCommand> subCommands = group.getValue();
+
+        String option = OPTION_LONG_HELP + "-" + tag.getName();
+        String synopsis = tag.getSynopsis().toString().toLowerCase();
+        BooleanArgument arg = new BooleanArgument(option, null, option,
+            INFO_DSCFG_DESCRIPTION_SHOW_GROUP_USAGE.get(synopsis));
+
+        parser.addGlobalArgument(arg);
+        parser.setUsageGroupArgument(arg, subCommands);
+      }
+
+      // Register the --help-all argument.
+      String option = OPTION_LONG_HELP + "-all";
+      BooleanArgument arg = new BooleanArgument(option, null, option,
+          INFO_DSCFG_DESCRIPTION_SHOW_GROUP_USAGE_ALL.get());
+
+      parser.addGlobalArgument(arg);
+      parser.setUsageGroupArgument(arg, allSubCommands);
+    }
+  }
+
+
+
+  /**
+   * Parses the provided command-line arguments and makes the
+   * appropriate changes to the Directory Server configuration.
+   *
+   * @param args
+   *          The command-line arguments provided to this program.
+   * @return The exit code from the configuration processing. A
+   *         nonzero value indicates that there was some kind of
+   *         problem during the configuration processing.
+   */
+  private int run(String[] args) {
+
+    // Register global arguments and sub-commands.
+    try {
+      initializeGlobalArguments(args);
+      initializeSubCommands();
+    } catch (ArgumentException e) {
+      println(ERR_CANNOT_INITIALIZE_ARGS.get(e.getMessage()));
+      return ReturnCode.ERROR_USER_DATA.get();
+    }
+
+    ConnectionFactoryProvider cfp = null;
+    try
+    {
+      cfp =
+          new ConnectionFactoryProvider(parser, this,
+              CliConstants.DEFAULT_ROOT_USER_DN,
+              CliConstants.DEFAULT_ADMINISTRATION_CONNECTOR_PORT, true, null);
+      cfp.setIsAnAdminConnection();
+
+      // Parse the command-line arguments provided to this program.
+      parser.parseArguments(args);
+      checkForConflictingArguments();
+    }
+    catch (ArgumentException ae)
+    {
+      displayMessageAndUsageReference(ERR_ERROR_PARSING_ARGS.get(ae
+          .getMessage()));
+      return ReturnCode.CONFLICTING_ARGS.get();
+    }
+
+    // If the usage/version argument was provided, then we don't need
+    // to do anything else.
+    if (parser.usageOrVersionDisplayed()) {
+      return ReturnCode.SUCCESS.get();
+    }
+
+    // Check that we can write on the provided path where we write the
+    // equivalent non-interactive commands.
+    if (equivalentCommandFileArgument.isPresent())
+    {
+      final String file = equivalentCommandFileArgument.getValue();
+      if (!canWrite(file))
+      {
+        println(ERR_DSCFG_CANNOT_WRITE_EQUIVALENT_COMMAND_LINE_FILE.get(file));
+        return ReturnCode.ERROR_UNEXPECTED.get();
+      }
+      else
+      {
+        if (new File(file).isDirectory())
+        {
+          println(ERR_DSCFG_EQUIVALENT_COMMAND_LINE_FILE_DIRECTORY.get(file));
+          return ReturnCode.ERROR_UNEXPECTED.get();
+        }
+      }
+    }
+    // Creates the management context factory which is based on the connection
+    // provider factory and an authenticated connection factory.
+    try
+    {
+      factory = new LDAPManagementContextFactory(cfp);
+    }
+    catch (ArgumentException e)
+    {
+      displayMessageAndUsageReference(ERR_ERROR_PARSING_ARGS
+          .get(e.getMessage()));
+      return ReturnCode.CONFLICTING_ARGS.get();
+    }
+
+    // Checks the version - if upgrade required, the tool is unusable
+    /*try
+    {
+      BuildVersion.checkVersionMismatch();
+    }
+    catch (ConfigException e)
+    {
+      println(e.getMessageObject());
+      return ReturnCode.ERROR_USER_DATA.get();
+    }*/
+
+    // Handle batch file if any
+    if (batchFileArgument.isPresent()) {
+      handleBatchFile(args);
+      // don't need to do anything else
+      return ReturnCode.SUCCESS.get();
+    }
+
+    int retCode = 0;
+    if (parser.getSubCommand() == null) {
+      hasSubCommand = false;
+
+      if (isInteractive()) {
+        // Top-level interactive mode.
+        retCode = runInteractiveMode();
+      } else {
+        LocalizableMessage message = ERR_ERROR_PARSING_ARGS
+        .get(ERR_DSCFG_ERROR_MISSING_SUBCOMMAND.get());
+        displayMessageAndUsageReference(message);
+        retCode = ReturnCode.ERROR_USER_DATA.get();
+      }
+    } else {
+      hasSubCommand = true;
+
+      // Retrieve the sub-command implementation and run it.
+      SubCommandHandler handler = handlers.get(parser.getSubCommand());
+      retCode = runSubCommand(handler);
+    }
+
+    factory.close();
+
+    return retCode;
+  }
+
+  private void checkForConflictingArguments() throws ArgumentException
+  {
+    if (quietArgument.isPresent() && verboseArgument.isPresent()) {
+      final LocalizableMessage message = ERR_TOOL_CONFLICTING_ARGS.get(quietArgument
+          .getLongIdentifier(), verboseArgument.getLongIdentifier());
+      throw new ArgumentException(message);
+    }
+
+    if (batchFileArgument.isPresent() && !noPromptArgument.isPresent()) {
+      final LocalizableMessage message =
+          ERR_DSCFG_ERROR_QUIET_AND_INTERACTIVE_INCOMPATIBLE.get(
+              batchFileArgument.getLongIdentifier(), noPromptArgument
+                  .getLongIdentifier());
+      throw new ArgumentException(message);
+    }
+
+    if (quietArgument.isPresent() && !noPromptArgument.isPresent()) {
+      final LocalizableMessage message = ERR_DSCFG_ERROR_QUIET_AND_INTERACTIVE_INCOMPATIBLE.get(
+          quietArgument.getLongIdentifier(), noPromptArgument
+          .getLongIdentifier());
+      throw new ArgumentException(message);
+    }
+
+    if (scriptFriendlyArgument.isPresent() && verboseArgument.isPresent()) {
+      final LocalizableMessage message = ERR_TOOL_CONFLICTING_ARGS.get(scriptFriendlyArgument
+          .getLongIdentifier(), verboseArgument.getLongIdentifier());
+      throw new ArgumentException(message);
+    }
+
+    if (noPropertiesFileArgument.isPresent()
+        && propertiesFileArgument.isPresent())
+    {
+      final LocalizableMessage message = ERR_TOOL_CONFLICTING_ARGS.get(
+          noPropertiesFileArgument.getLongIdentifier(),
+          propertiesFileArgument.getLongIdentifier());
+      throw new ArgumentException(message);
+    }
+  }
+
+
+
+  /** Run the top-level interactive console. */
+  private int runInteractiveMode() {
+
+    ConsoleApplication app = this;
+
+    // Build menu structure.
+    final Comparator<RelationDefinition<?, ?>> c =
+      new Comparator<RelationDefinition<?, ?>>() {
+
+      @Override
+      public int compare(RelationDefinition<?, ?> rd1,
+          RelationDefinition<?, ?> rd2) {
+        final String s1 = rd1.getUserFriendlyName().toString();
+        final String s2 = rd2.getUserFriendlyName().toString();
+
+        return s1.compareToIgnoreCase(s2);
+      }
+    };
+
+    final Set<RelationDefinition<?, ?>> relations =
+        new TreeSet<RelationDefinition<?, ?>>(c);
+    final Map<RelationDefinition<?, ?>, CreateSubCommandHandler<?, ?>> createHandlers =
+        new HashMap<RelationDefinition<?, ?>, CreateSubCommandHandler<?, ?>>();
+    final Map<RelationDefinition<?, ?>, DeleteSubCommandHandler> deleteHandlers =
+        new HashMap<RelationDefinition<?, ?>, DeleteSubCommandHandler>();
+    final Map<RelationDefinition<?, ?>, ListSubCommandHandler> listHandlers =
+        new HashMap<RelationDefinition<?, ?>, ListSubCommandHandler>();
+    final Map<RelationDefinition<?, ?>, GetPropSubCommandHandler> getPropHandlers =
+        new HashMap<RelationDefinition<?, ?>, GetPropSubCommandHandler>();
+    final Map<RelationDefinition<?, ?>, SetPropSubCommandHandler> setPropHandlers =
+        new HashMap<RelationDefinition<?, ?>, SetPropSubCommandHandler>();
+
+
+    for (final CreateSubCommandHandler<?, ?> ch : handlerFactory
+        .getCreateSubCommandHandlers()) {
+      relations.add(ch.getRelationDefinition());
+      createHandlers.put(ch.getRelationDefinition(), ch);
+    }
+
+    for (final DeleteSubCommandHandler dh : handlerFactory
+        .getDeleteSubCommandHandlers()) {
+      relations.add(dh.getRelationDefinition());
+      deleteHandlers.put(dh.getRelationDefinition(), dh);
+    }
+
+    for (final ListSubCommandHandler lh :
+      handlerFactory.getListSubCommandHandlers()) {
+      relations.add(lh.getRelationDefinition());
+      listHandlers.put(lh.getRelationDefinition(), lh);
+    }
+
+    for (final GetPropSubCommandHandler gh : handlerFactory
+        .getGetPropSubCommandHandlers()) {
+      relations.add(gh.getRelationDefinition());
+      getPropHandlers.put(gh.getRelationDefinition(), gh);
+    }
+
+    for (final SetPropSubCommandHandler sh : handlerFactory
+        .getSetPropSubCommandHandlers()) {
+      relations.add(sh.getRelationDefinition());
+      setPropHandlers.put(sh.getRelationDefinition(), sh);
+    }
+
+    // Main menu.
+    final MenuBuilder<Integer> builder = new MenuBuilder<Integer>(app);
+
+    builder.setTitle(INFO_DSCFG_HEADING_MAIN_MENU_TITLE.get());
+    builder.setPrompt(INFO_DSCFG_HEADING_MAIN_MENU_PROMPT.get());
+    builder.setMultipleColumnThreshold(0);
+
+    for (final RelationDefinition<?, ?> rd : relations) {
+      final MenuCallback<Integer> callback = new SubMenuCallback(app, rd,
+          createHandlers.get(rd), deleteHandlers.get(rd), listHandlers.get(rd),
+          setPropHandlers.get(rd));
+      builder.addNumberedOption(rd.getUserFriendlyName(), callback);
+    }
+
+    builder.addQuitOption();
+
+    final Menu<Integer> menu = builder.toMenu();
+
+    try {
+      // Force retrieval of management context.
+      factory.getManagementContext(app);
+    } catch (ArgumentException e) {
+      app.println(e.getMessageObject());
+      return ReturnCode.ERROR_UNEXPECTED.get();
+    } catch (ClientException e) {
+      app.println(e.getMessageObject());
+      return ReturnCode.ERROR_UNEXPECTED.get();
+    }
+
+    try {
+      app.println();
+      app.println();
+
+      final MenuResult<Integer> result = menu.run();
+
+      if (result.isQuit()) {
+        return ReturnCode.SUCCESS.get();
+      } else {
+        return result.getValue();
+      }
+    } catch (ClientException e) {
+      app.println(e.getMessageObject());
+      return ReturnCode.ERROR_UNEXPECTED.get();
+    }
+  }
+
+
+
+  /** Run the provided sub-command handler. */
+  private int runSubCommand(SubCommandHandler handler) {
+    try {
+      final MenuResult<Integer> result = handler.run(this, factory);
+
+      if (result.isSuccess()) {
+        if (isInteractive() &&
+            handler.isCommandBuilderUseful())
+        {
+          printCommandBuilder(getCommandBuilder(handler));
+        }
+        return result.getValue();
+      } else {
+        // User must have quit.
+        return ReturnCode.ERROR_UNEXPECTED.get();
+      }
+    } catch (ArgumentException e) {
+      println(e.getMessageObject());
+      return ReturnCode.ERROR_UNEXPECTED.get();
+    } catch (ClientException e) {
+      Throwable cause = e.getCause();
+      println();
+      if (cause instanceof ManagedObjectDecodingException)
+      {
+        displayManagedObjectDecodingException(this,
+            (ManagedObjectDecodingException) cause);
+      }
+      else if (cause instanceof MissingMandatoryPropertiesException)
+      {
+        displayMissingMandatoryPropertyException(this,
+            (MissingMandatoryPropertiesException) cause);
+      }
+      else if (cause instanceof OperationRejectedException)
+      {
+        displayOperationRejectedException(this,
+            (OperationRejectedException) cause);
+      }
+      else
+      {
+        // Just display the default message.
+        println(e.getMessageObject());
+      }
+      println();
+
+      return ReturnCode.ERROR_UNEXPECTED.get();
+    } catch (Exception e) {
+      println(LocalizableMessage.raw(stackTraceToSingleLineString(e, true)));
+      return ReturnCode.ERROR_UNEXPECTED.get();
+    }
+  }
+
+  /**
+   * Updates the command builder with the global options: script friendly,
+   * verbose, etc. for a given sub command. It also adds systematically the
+   * no-prompt option.
+   *
+   * @param <T>
+   *          SubCommand type.
+   * @param subCommand
+   *          The sub command handler or common.
+   * @return <T> The builded command.
+   */
+  <T> CommandBuilder getCommandBuilder(final T subCommand)
+  {
+    String commandName = System.getProperty(PROPERTY_SCRIPT_NAME);
+    if (commandName == null)
+    {
+      commandName = DSCONFIGTOOLNAME;
+    }
+    CommandBuilder commandBuilder = null;
+    if (subCommand instanceof SubCommandHandler)
+    {
+      commandBuilder =
+          new CommandBuilder(commandName, ((SubCommandHandler) subCommand)
+              .getSubCommand().getName());
+    }
+    else
+    {
+      commandBuilder = new CommandBuilder(commandName, (String) subCommand);
+    }
+    if (factory != null && factory.getContextCommandBuilder() != null)
+    {
+      commandBuilder.append(factory.getContextCommandBuilder());
+    }
+
+    if (verboseArgument.isPresent())
+    {
+      commandBuilder.addArgument(verboseArgument);
+    }
+
+    if (scriptFriendlyArgument.isPresent())
+    {
+      commandBuilder.addArgument(scriptFriendlyArgument);
+    }
+
+    commandBuilder.addArgument(noPromptArgument);
+
+    if (propertiesFileArgument.isPresent())
+    {
+      commandBuilder.addArgument(propertiesFileArgument);
+    }
+
+    if (noPropertiesFileArgument.isPresent())
+    {
+      commandBuilder.addArgument(noPropertiesFileArgument);
+    }
+
+    return commandBuilder;
+  }
+
+  /**
+   * Prints the contents of a command builder.  This method has been created
+   * since SetPropSubCommandHandler calls it.  All the logic of DSConfig is on
+   * this method.  It writes the content of the CommandBuilder to the standard
+   * output, or to a file depending on the options provided by the user.
+   * @param commandBuilder the command builder to be printed.
+   */
+  void printCommandBuilder(CommandBuilder commandBuilder)
+  {
+    if (displayEquivalentArgument.isPresent())
+    {
+      println();
+      // We assume that the app we are running is this one.
+      println(INFO_DSCFG_NON_INTERACTIVE.get(commandBuilder));
+    }
+    if (equivalentCommandFileArgument.isPresent())
+    {
+      String file = equivalentCommandFileArgument.getValue();
+      BufferedWriter writer = null;
+      try
+      {
+        writer = new BufferedWriter(new FileWriter(file, true));
+
+        if (!sessionStartTimePrinted)
+        {
+          writer.write(SHELL_COMMENT_SEPARATOR+getSessionStartTimeMessage());
+          writer.newLine();
+          sessionStartTimePrinted = true;
+        }
+
+        sessionEquivalentOperationNumber++;
+        writer.newLine();
+        writer.write(SHELL_COMMENT_SEPARATOR+
+            INFO_DSCFG_EQUIVALENT_COMMAND_LINE_SESSION_OPERATION_NUMBER.get(
+                sessionEquivalentOperationNumber));
+        writer.newLine();
+
+        writer.write(SHELL_COMMENT_SEPARATOR+getCurrentOperationDateMessage());
+        writer.newLine();
+
+        writer.write(commandBuilder.toString());
+        writer.newLine();
+        writer.newLine();
+
+        writer.flush();
+      }
+      catch (IOException ioe)
+      {
+        println(ERR_DSCFG_ERROR_WRITING_EQUIVALENT_COMMAND_LINE.get(file, ioe));
+      }
+      finally
+      {
+        closeSilently(writer);
+      }
+    }
+  }
+
+  /**
+   * Returns the message to be displayed in the file with the equivalent
+   * command-line with information about when the session started.
+   * @return  the message to be displayed in the file with the equivalent
+   * command-line with information about when the session started.
+   */
+  private String getSessionStartTimeMessage()
+  {
+    String scriptName = System.getProperty(PROPERTY_SCRIPT_NAME);
+    if (scriptName == null || scriptName.length() == 0)
+    {
+      scriptName = DSCONFIGTOOLNAME;
+    }
+    final String date = formatDateTimeStringForEquivalentCommand(
+        new Date(sessionStartTime));
+    return INFO_DSCFG_SESSION_START_TIME_MESSAGE.get(scriptName, date).
+    toString();
+  }
+
+  private void handleBatchFile(String[] args) {
+
+    BufferedReader bReader = null;
+    try {
+      // Build a list of initial arguments,
+      // removing the batch file option + its value
+      final List<String> initialArgs = new ArrayList<String>();
+      Collections.addAll(initialArgs, args);
+      int batchFileArgIndex = -1;
+      for (final String elem : initialArgs) {
+        if (elem.startsWith("-" + OPTION_SHORT_BATCH_FILE_PATH)
+                || elem.contains(OPTION_LONG_BATCH_FILE_PATH)) {
+          batchFileArgIndex = initialArgs.indexOf(elem);
+          break;
+        }
+      }
+      if (batchFileArgIndex != -1) {
+        // Remove both the batch file arg and its value
+        initialArgs.remove(batchFileArgIndex);
+        initialArgs.remove(batchFileArgIndex);
+      }
+      final String batchFilePath = batchFileArgument.getValue().trim();
+      bReader =
+              new BufferedReader(new FileReader(batchFilePath));
+      String line;
+      String command = "";
+      // Split the CLI string into arguments array
+      while ((line = bReader.readLine()) != null) {
+         if ("".equals(line) || line.startsWith("#")) {
+          // Empty line or comment
+          continue;
+        }
+       // command split in several line support
+        if (line.endsWith("\\")) {
+          // command is split into several lines
+          command += line.substring(0, line.length() - 1);
+          continue;
+        } else {
+          command += line;
+        }
+        command = command.trim();
+        // string between quotes support
+        command = replaceSpacesInQuotes(command);
+        String displayCommand = new String(command);
+
+        // "\ " support
+        command = command.replace("\\ ", "##");
+        displayCommand = displayCommand.replace("\\ ", " ");
+
+        String[] fileArguments = command.split("\\s+");
+        // reset command
+        command = "";
+        for (int ii = 0; ii < fileArguments.length; ii++) {
+          fileArguments[ii] = fileArguments[ii].replace("##", " ");
+        }
+
+        errPrintln(LocalizableMessage.raw(displayCommand));
+
+        // Append initial arguments to the file line
+        final List<String> allArguments = new ArrayList<String>();
+        Collections.addAll(allArguments, fileArguments);
+        allArguments.addAll(initialArgs);
+        final String[] allArgsArray = allArguments.toArray(new String[]{});
+
+        int exitCode = main(allArgsArray, getOutputStream(), getErrorStream());
+        if (exitCode != ReturnCode.SUCCESS.get())
+        {
+          System.exit(filterExitCode(exitCode));
+        }
+        errPrintln();
+      }
+    } catch (IOException ex) {
+      println(ERR_DSCFG_ERROR_READING_BATCH_FILE.get(ex));
+    } finally {
+      closeSilently(bReader);
+    }
+  }
+
+  /** Replace spaces in quotes by "\ ". */
+  private String replaceSpacesInQuotes(final String line) {
+    String newLine = "";
+    boolean inQuotes = false;
+    for (int ii = 0; ii < line.length(); ii++) {
+      char ch = line.charAt(ii);
+      if (ch == '\"' || ch == '\'') {
+        inQuotes = !inQuotes;
+        continue;
+      }
+      if (inQuotes && ch == ' ') {
+        newLine += "\\ ";
+      } else {
+        newLine += ch;
+      }
+    }
+    return newLine;
+  }
+}
diff --git a/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/DeleteSubCommandHandler.java b/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/DeleteSubCommandHandler.java
new file mode 100644
index 0000000..53bf839
--- /dev/null
+++ b/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/DeleteSubCommandHandler.java
@@ -0,0 +1,383 @@
+/*
+ * 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 2009 Sun Microsystems, Inc.
+ *      Portions Copyright 2014 ForgeRock AS
+ */
+package org.forgerock.opendj.config.dsconfig;
+
+import static com.forgerock.opendj.dsconfig.DsconfigMessages.*;
+
+import java.util.List;
+import java.util.SortedMap;
+
+import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.opendj.config.DefinitionDecodingException;
+import org.forgerock.opendj.config.InstantiableRelationDefinition;
+import org.forgerock.opendj.config.ManagedObjectDefinition;
+import org.forgerock.opendj.config.ManagedObjectNotFoundException;
+import org.forgerock.opendj.config.ManagedObjectPath;
+import org.forgerock.opendj.config.OptionalRelationDefinition;
+import org.forgerock.opendj.config.RelationDefinition;
+import org.forgerock.opendj.config.SetRelationDefinition;
+import org.forgerock.opendj.config.client.ConcurrentModificationException;
+import org.forgerock.opendj.config.client.ManagedObject;
+import org.forgerock.opendj.config.client.ManagedObjectDecodingException;
+import org.forgerock.opendj.config.client.ManagementContext;
+import org.forgerock.opendj.config.client.OperationRejectedException;
+import org.forgerock.opendj.ldap.AuthorizationException;
+import org.forgerock.opendj.ldap.ErrorResultException;
+
+import com.forgerock.opendj.cli.ArgumentException;
+import com.forgerock.opendj.cli.BooleanArgument;
+import com.forgerock.opendj.cli.ClientException;
+import com.forgerock.opendj.cli.ConsoleApplication;
+import com.forgerock.opendj.cli.MenuResult;
+import com.forgerock.opendj.cli.ReturnCode;
+import com.forgerock.opendj.cli.StringArgument;
+import com.forgerock.opendj.cli.SubCommand;
+import com.forgerock.opendj.cli.SubCommandArgumentParser;
+import com.forgerock.opendj.cli.TableBuilder;
+import com.forgerock.opendj.cli.TextTablePrinter;
+
+/**
+ * A sub-command handler which is used to delete existing managed objects.
+ * <p>
+ * This sub-command implements the various delete-xxx sub-commands.
+ */
+final class DeleteSubCommandHandler extends SubCommandHandler {
+
+    /**
+     * The value for the long option force.
+     */
+    private static final String OPTION_DSCFG_LONG_FORCE = "force";
+
+    /**
+     * The value for the short option force.
+     */
+    private static final char OPTION_DSCFG_SHORT_FORCE = 'f';
+
+    /**
+     * Creates a new delete-xxx sub-command for an instantiable relation.
+     *
+     * @param parser
+     *            The sub-command argument parser.
+     * @param p
+     *            The parent managed object path.
+     * @param r
+     *            The instantiable relation.
+     * @return Returns the new delete-xxx sub-command.
+     * @throws ArgumentException
+     *             If the sub-command could not be created successfully.
+     */
+    public static DeleteSubCommandHandler create(SubCommandArgumentParser parser, ManagedObjectPath<?, ?> p,
+            InstantiableRelationDefinition<?, ?> r) throws ArgumentException {
+        return new DeleteSubCommandHandler(parser, p, r, p.child(r, "DUMMY"));
+    }
+
+    /**
+     * Creates a new delete-xxx sub-command for an optional relation.
+     *
+     * @param parser
+     *            The sub-command argument parser.
+     * @param p
+     *            The parent managed object path.
+     * @param r
+     *            The optional relation.
+     * @return Returns the new delete-xxx sub-command.
+     * @throws ArgumentException
+     *             If the sub-command could not be created successfully.
+     */
+    public static DeleteSubCommandHandler create(SubCommandArgumentParser parser, ManagedObjectPath<?, ?> p,
+            OptionalRelationDefinition<?, ?> r) throws ArgumentException {
+        return new DeleteSubCommandHandler(parser, p, r, p.child(r));
+    }
+
+    /**
+     * Creates a new delete-xxx sub-command for a set relation.
+     *
+     * @param parser
+     *            The sub-command argument parser.
+     * @param p
+     *            The parent managed object path.
+     * @param r
+     *            The set relation.
+     * @return Returns the new delete-xxx sub-command.
+     * @throws ArgumentException
+     *             If the sub-command could not be created successfully.
+     */
+    public static DeleteSubCommandHandler create(SubCommandArgumentParser parser, ManagedObjectPath<?, ?> p,
+            SetRelationDefinition<?, ?> r) throws ArgumentException {
+        return new DeleteSubCommandHandler(parser, p, r, p.child(r));
+    }
+
+    /** The argument which should be used to force deletion. */
+    private final BooleanArgument forceArgument;
+
+    /** The sub-commands naming arguments. */
+    private final List<StringArgument> namingArgs;
+
+    /** The path of the managed object. */
+    private final ManagedObjectPath<?, ?> path;
+
+    /**
+     * The relation which references the managed object to be deleted.
+     */
+    private final RelationDefinition<?, ?> relation;
+
+    /** The sub-command associated with this handler. */
+    private final SubCommand subCommand;
+
+    /** Private constructor. */
+    private DeleteSubCommandHandler(SubCommandArgumentParser parser, ManagedObjectPath<?, ?> p,
+            RelationDefinition<?, ?> r, ManagedObjectPath<?, ?> c) throws ArgumentException {
+        this.path = p;
+        this.relation = r;
+
+        // Create the sub-command.
+        String name = "delete-" + r.getName();
+        LocalizableMessage ufpn = r.getChildDefinition().getUserFriendlyPluralName();
+        LocalizableMessage description = INFO_DSCFG_DESCRIPTION_SUBCMD_DELETE.get(ufpn);
+        this.subCommand = new SubCommand(parser, name, false, 0, 0, null, description);
+
+        // Create the naming arguments.
+        this.namingArgs = createNamingArgs(subCommand, c, false);
+
+        // Create the --force argument which is used to force deletion.
+        this.forceArgument = new BooleanArgument(OPTION_DSCFG_LONG_FORCE, OPTION_DSCFG_SHORT_FORCE,
+                OPTION_DSCFG_LONG_FORCE, INFO_DSCFG_DESCRIPTION_FORCE.get(ufpn));
+        subCommand.addArgument(forceArgument);
+
+        // Register the tags associated with the child managed objects.
+        addTags(relation.getChildDefinition().getAllTags());
+    }
+
+    /**
+     * Gets the relation definition associated with the type of component that this sub-command handles.
+     *
+     * @return Returns the relation definition associated with the type of component that this sub-command handles.
+     */
+    public RelationDefinition<?, ?> getRelationDefinition() {
+        return relation;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public SubCommand getSubCommand() {
+        return subCommand;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public MenuResult<Integer> run(ConsoleApplication app, LDAPManagementContextFactory factory)
+            throws ArgumentException, ClientException {
+        // Get the naming argument values.
+        List<String> names = getNamingArgValues(app, namingArgs);
+
+        // Reset the command builder
+        getCommandBuilder().clearArguments();
+        setCommandBuilderUseful(false);
+
+        // Delete the child managed object.
+        ManagementContext context = factory.getManagementContext(app);
+        MenuResult<ManagedObject<?>> result;
+        LocalizableMessage ufn = relation.getUserFriendlyName();
+        try {
+            result = getManagedObject(app, context, path, names);
+        } catch (AuthorizationException e) {
+            LocalizableMessage msg = ERR_DSCFG_ERROR_DELETE_AUTHZ.get(ufn);
+            throw new ClientException(ReturnCode.INSUFFICIENT_ACCESS_RIGHTS, msg);
+        } catch (DefinitionDecodingException e) {
+            LocalizableMessage pufn = path.getManagedObjectDefinition().getUserFriendlyName();
+            LocalizableMessage msg = ERR_DSCFG_ERROR_GET_PARENT_DDE.get(pufn, pufn, pufn);
+            throw new ClientException(ReturnCode.OTHER, msg);
+        } catch (ManagedObjectDecodingException e) {
+            LocalizableMessage pufn = path.getManagedObjectDefinition().getUserFriendlyName();
+            LocalizableMessage msg = ERR_DSCFG_ERROR_GET_PARENT_MODE.get(pufn);
+            throw new ClientException(ReturnCode.OTHER, msg, e);
+        } catch (ConcurrentModificationException e) {
+            LocalizableMessage msg = ERR_DSCFG_ERROR_DELETE_CME.get(ufn);
+            throw new ClientException(ReturnCode.CONSTRAINT_VIOLATION, msg);
+        } catch (ManagedObjectNotFoundException e) {
+            // Ignore the error if the deletion is being forced.
+            if (!forceArgument.isPresent()) {
+                LocalizableMessage pufn = path.getManagedObjectDefinition().getUserFriendlyName();
+                LocalizableMessage msg = ERR_DSCFG_ERROR_GET_PARENT_MONFE.get(pufn);
+                if (app.isInteractive()) {
+                    app.println();
+                    app.printVerboseMessage(msg);
+                    return MenuResult.cancel();
+                } else {
+                    throw new ClientException(ReturnCode.NO_SUCH_OBJECT, msg);
+                }
+            } else {
+                return MenuResult.success(0);
+            }
+        }
+
+        if (result.isQuit()) {
+            if (!app.isMenuDrivenMode()) {
+                // User chose to cancel deletion.
+                app.println();
+                app.println(INFO_DSCFG_CONFIRM_DELETE_FAIL.get(ufn));
+            }
+            return MenuResult.quit();
+        } else if (result.isCancel()) {
+            // Must be menu driven, so no need for error message.
+            return MenuResult.cancel();
+        }
+
+        ManagedObject<?> parent = result.getValue();
+        try {
+            if (relation instanceof InstantiableRelationDefinition || relation instanceof SetRelationDefinition) {
+                String childName = names.get(names.size() - 1);
+
+                if (childName == null) {
+                    MenuResult<String> sresult = readChildName(app, parent, relation, null);
+
+                    if (sresult.isQuit()) {
+                        if (!app.isMenuDrivenMode()) {
+                            // User chose to cancel deletion.
+                            app.println();
+                            app.println(INFO_DSCFG_CONFIRM_DELETE_FAIL.get(ufn));
+                        }
+                        return MenuResult.quit();
+                    } else if (sresult.isCancel()) {
+                        // Must be menu driven, so no need for error message.
+                        return MenuResult.cancel();
+                    } else {
+                        childName = sresult.getValue();
+                    }
+                } else if (relation instanceof SetRelationDefinition) {
+                    // The provided type short name needs mapping to the full name.
+                    String name = childName.trim();
+                    SortedMap<?, ?> types = getSubTypes(relation.getChildDefinition());
+                    ManagedObjectDefinition<?, ?> cd = (ManagedObjectDefinition<?, ?>) types.get(name);
+                    if (cd == null) {
+                        // The name must be invalid.
+                        String typeUsage = getSubTypesUsage(relation.getChildDefinition());
+                        LocalizableMessage msg = ERR_DSCFG_ERROR_SUB_TYPE_UNRECOGNIZED.get(name,
+                                relation.getUserFriendlyName(), typeUsage);
+                        throw new ArgumentException(msg);
+                    } else {
+                        childName = cd.getName();
+                    }
+                }
+
+                if (confirmDeletion(app)) {
+                    setCommandBuilderUseful(true);
+                    if (relation instanceof InstantiableRelationDefinition) {
+                        parent.removeChild((InstantiableRelationDefinition<?, ?>) relation, childName);
+                    } else {
+                        parent.removeChild((SetRelationDefinition<?, ?>) relation, childName);
+                    }
+                } else {
+                    return MenuResult.cancel();
+                }
+            } else if (relation instanceof OptionalRelationDefinition) {
+                OptionalRelationDefinition<?, ?> orelation = (OptionalRelationDefinition<?, ?>) relation;
+
+                if (confirmDeletion(app)) {
+                    setCommandBuilderUseful(true);
+
+                    parent.removeChild(orelation);
+                } else {
+                    return MenuResult.cancel();
+                }
+            }
+        } catch (AuthorizationException e) {
+            LocalizableMessage msg = ERR_DSCFG_ERROR_DELETE_AUTHZ.get(ufn);
+            throw new ClientException(ReturnCode.INSUFFICIENT_ACCESS_RIGHTS, msg);
+        } catch (OperationRejectedException e) {
+            LocalizableMessage msg;
+            if (e.getMessages().size() == 1) {
+                msg = ERR_DSCFG_ERROR_DELETE_ORE_SINGLE.get(ufn);
+            } else {
+                msg = ERR_DSCFG_ERROR_DELETE_ORE_PLURAL.get(ufn);
+            }
+
+            if (app.isInteractive()) {
+                // If interactive, let the user go back to the main menu.
+                app.println();
+                app.println(msg);
+                app.println();
+                TableBuilder builder = new TableBuilder();
+                for (LocalizableMessage reason : e.getMessages()) {
+                    builder.startRow();
+                    builder.appendCell("*");
+                    builder.appendCell(reason);
+                }
+                TextTablePrinter printer = new TextTablePrinter(app.getErrorStream());
+                printer.setDisplayHeadings(false);
+                printer.setColumnWidth(1, 0);
+                printer.setIndentWidth(4);
+                builder.print(printer);
+                return MenuResult.cancel();
+            } else {
+                throw new ClientException(ReturnCode.CONSTRAINT_VIOLATION, msg, e);
+            }
+        } catch (ManagedObjectNotFoundException e) {
+            // Ignore the error if the deletion is being forced.
+            if (!forceArgument.isPresent()) {
+                LocalizableMessage msg = ERR_DSCFG_ERROR_DELETE_MONFE.get(ufn);
+                throw new ClientException(ReturnCode.NO_SUCH_OBJECT, msg);
+            }
+        } catch (ConcurrentModificationException e) {
+            LocalizableMessage msg = ERR_DSCFG_ERROR_DELETE_CME.get(ufn);
+            throw new ClientException(ReturnCode.CONSTRAINT_VIOLATION, msg);
+        } catch (ErrorResultException e) {
+            LocalizableMessage msg = ERR_DSCFG_ERROR_DELETE_CE.get(ufn, e.getMessage());
+            throw new ClientException(ReturnCode.CLIENT_SIDE_SERVER_DOWN, msg);
+        }
+
+        // Add the naming arguments if they were provided.
+        for (StringArgument arg : namingArgs) {
+            if (arg.isPresent()) {
+                getCommandBuilder().addArgument(arg);
+            }
+        }
+
+        // Output success message.
+        app.println();
+        app.println(INFO_DSCFG_CONFIRM_DELETE_SUCCESS.get(ufn));
+
+        return MenuResult.success(0);
+    }
+
+    /** Confirm deletion. */
+    private boolean confirmDeletion(ConsoleApplication app) throws ClientException {
+        if (app.isInteractive()) {
+            LocalizableMessage prompt = INFO_DSCFG_CONFIRM_DELETE.get(relation.getUserFriendlyName());
+            app.println();
+            if (!app.confirmAction(prompt, false)) {
+                // Output failure message.
+                LocalizableMessage msg = INFO_DSCFG_CONFIRM_DELETE_FAIL.get(relation.getUserFriendlyName());
+                app.println(msg);
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+}
diff --git a/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/GetPropSubCommandHandler.java b/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/GetPropSubCommandHandler.java
new file mode 100644
index 0000000..4b9b576
--- /dev/null
+++ b/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/GetPropSubCommandHandler.java
@@ -0,0 +1,393 @@
+/*
+ * 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 2009 Sun Microsystems, Inc.
+ *      Portions Copyright 2014 ForgeRock AS
+ */
+package org.forgerock.opendj.config.dsconfig;
+
+import static com.forgerock.opendj.dsconfig.DsconfigMessages.*;
+import static com.forgerock.opendj.cli.ArgumentConstants.LIST_TABLE_SEPARATOR;
+
+import java.io.PrintStream;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.SortedSet;
+
+import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.opendj.config.AbsoluteInheritedDefaultBehaviorProvider;
+import org.forgerock.opendj.config.AliasDefaultBehaviorProvider;
+import org.forgerock.opendj.config.DefaultBehaviorProviderVisitor;
+import org.forgerock.opendj.config.DefinedDefaultBehaviorProvider;
+import org.forgerock.opendj.config.DefinitionDecodingException;
+import org.forgerock.opendj.config.InstantiableRelationDefinition;
+import org.forgerock.opendj.config.ManagedObjectDefinition;
+import org.forgerock.opendj.config.ManagedObjectNotFoundException;
+import org.forgerock.opendj.config.ManagedObjectPath;
+import org.forgerock.opendj.config.OptionalRelationDefinition;
+import org.forgerock.opendj.config.PropertyDefinition;
+import org.forgerock.opendj.config.PropertyOption;
+import org.forgerock.opendj.config.RelationDefinition;
+import org.forgerock.opendj.config.RelativeInheritedDefaultBehaviorProvider;
+import org.forgerock.opendj.config.SetRelationDefinition;
+import org.forgerock.opendj.config.SingletonRelationDefinition;
+import org.forgerock.opendj.config.UndefinedDefaultBehaviorProvider;
+import org.forgerock.opendj.config.client.ConcurrentModificationException;
+import org.forgerock.opendj.config.client.ManagedObject;
+import org.forgerock.opendj.config.client.ManagedObjectDecodingException;
+import org.forgerock.opendj.config.client.ManagementContext;
+import org.forgerock.opendj.ldap.AuthorizationException;
+
+import com.forgerock.opendj.cli.ArgumentException;
+import com.forgerock.opendj.cli.ReturnCode;
+import com.forgerock.opendj.cli.StringArgument;
+import com.forgerock.opendj.cli.SubCommand;
+import com.forgerock.opendj.cli.SubCommandArgumentParser;
+import com.forgerock.opendj.cli.ClientException;
+import com.forgerock.opendj.cli.ConsoleApplication;
+import com.forgerock.opendj.cli.MenuResult;
+import com.forgerock.opendj.cli.TableBuilder;
+import com.forgerock.opendj.cli.TablePrinter;
+import com.forgerock.opendj.cli.TextTablePrinter;
+
+/**
+ * A sub-command handler which is used to retrieve the properties of a managed object.
+ * <p>
+ * This sub-command implements the various get-xxx-prop sub-commands.
+ */
+final class GetPropSubCommandHandler extends SubCommandHandler {
+
+    /**
+     * Creates a new get-xxx-prop sub-command for an instantiable relation.
+     *
+     * @param parser
+     *            The sub-command argument parser.
+     * @param path
+     *            The parent managed object path.
+     * @param r
+     *            The instantiable relation.
+     * @return Returns the new get-xxx-prop sub-command.
+     * @throws ArgumentException
+     *             If the sub-command could not be created successfully.
+     */
+    public static GetPropSubCommandHandler create(SubCommandArgumentParser parser, ManagedObjectPath<?, ?> path,
+            InstantiableRelationDefinition<?, ?> r) throws ArgumentException {
+        return new GetPropSubCommandHandler(parser, path.child(r, "DUMMY"), r);
+    }
+
+    /**
+     * Creates a new get-xxx-prop sub-command for an optional relation.
+     *
+     * @param parser
+     *            The sub-command argument parser.
+     * @param path
+     *            The parent managed object path.
+     * @param r
+     *            The optional relation.
+     * @return Returns the new get-xxx-prop sub-command.
+     * @throws ArgumentException
+     *             If the sub-command could not be created successfully.
+     */
+    public static GetPropSubCommandHandler create(SubCommandArgumentParser parser, ManagedObjectPath<?, ?> path,
+            OptionalRelationDefinition<?, ?> r) throws ArgumentException {
+        return new GetPropSubCommandHandler(parser, path.child(r), r);
+    }
+
+    /**
+     * Creates a new get-xxx-prop sub-command for a set relation.
+     *
+     * @param parser
+     *            The sub-command argument parser.
+     * @param path
+     *            The parent managed object path.
+     * @param r
+     *            The set relation.
+     * @return Returns the new get-xxx-prop sub-command.
+     * @throws ArgumentException
+     *             If the sub-command could not be created successfully.
+     */
+    public static GetPropSubCommandHandler create(SubCommandArgumentParser parser, ManagedObjectPath<?, ?> path,
+            SetRelationDefinition<?, ?> r) throws ArgumentException {
+        return new GetPropSubCommandHandler(parser, path.child(r), r);
+    }
+
+    /**
+     * Creates a new get-xxx-prop sub-command for a singleton relation.
+     *
+     * @param parser
+     *            The sub-command argument parser.
+     * @param path
+     *            The parent managed object path.
+     * @param r
+     *            The singleton relation.
+     * @return Returns the new get-xxx-prop sub-command.
+     * @throws ArgumentException
+     *             If the sub-command could not be created successfully.
+     */
+    public static GetPropSubCommandHandler create(SubCommandArgumentParser parser, ManagedObjectPath<?, ?> path,
+            SingletonRelationDefinition<?, ?> r) throws ArgumentException {
+        return new GetPropSubCommandHandler(parser, path.child(r), r);
+    }
+
+    // The sub-commands naming arguments.
+    private final List<StringArgument> namingArgs;
+
+    // The path of the managed object.
+    private final ManagedObjectPath<?, ?> path;
+
+    // The sub-command associated with this handler.
+    private final SubCommand subCommand;
+
+    // Private constructor.
+    private GetPropSubCommandHandler(SubCommandArgumentParser parser, ManagedObjectPath<?, ?> path,
+            RelationDefinition<?, ?> r) throws ArgumentException {
+        this.path = path;
+
+        // Create the sub-command.
+        String name = "get-" + r.getName() + "-prop";
+        LocalizableMessage message = INFO_DSCFG_DESCRIPTION_SUBCMD_GETPROP.get(r.getChildDefinition()
+                .getUserFriendlyName());
+        this.subCommand = new SubCommand(parser, name, false, 0, 0, null, message);
+
+        // Create the naming arguments.
+        this.namingArgs = createNamingArgs(subCommand, path, false);
+
+        // Register common arguments.
+        registerPropertyNameArgument(this.subCommand);
+        registerRecordModeArgument(this.subCommand);
+        registerUnitSizeArgument(this.subCommand);
+        registerUnitTimeArgument(this.subCommand);
+
+        // Register the tags associated with the child managed objects.
+        addTags(path.getManagedObjectDefinition().getAllTags());
+    }
+
+    /**
+     * Gets the relation definition associated with the type of component that this sub-command handles.
+     *
+     * @return Returns the relation definition associated with the type of component that this sub-command handles.
+     */
+    public RelationDefinition<?, ?> getRelationDefinition() {
+        return path.getRelationDefinition();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public SubCommand getSubCommand() {
+        return subCommand;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public MenuResult<Integer> run(ConsoleApplication app, LDAPManagementContextFactory factory)
+            throws ArgumentException, ClientException {
+        // Get the property names.
+        Set<String> propertyNames = getPropertyNames();
+        PropertyValuePrinter valuePrinter = new PropertyValuePrinter(getSizeUnit(), getTimeUnit(),
+                app.isScriptFriendly());
+
+        // Get the naming argument values.
+        List<String> names = getNamingArgValues(app, namingArgs);
+
+        // Reset the command builder
+        getCommandBuilder().clearArguments();
+
+        setCommandBuilderUseful(false);
+
+        // Update the command builder.
+        updateCommandBuilderWithSubCommand();
+
+        // Get the targeted managed object.
+        LocalizableMessage ufn = path.getRelationDefinition().getUserFriendlyName();
+        ManagementContext context = factory.getManagementContext(app);
+        MenuResult<ManagedObject<?>> result;
+        try {
+            result = getManagedObject(app, context, path, names);
+        } catch (AuthorizationException e) {
+            LocalizableMessage msg = ERR_DSCFG_ERROR_GET_CHILD_AUTHZ.get(ufn);
+            throw new ClientException(ReturnCode.INSUFFICIENT_ACCESS_RIGHTS, msg);
+        } catch (DefinitionDecodingException e) {
+            LocalizableMessage msg = ERR_DSCFG_ERROR_GET_CHILD_DDE.get(ufn, ufn, ufn);
+            throw new ClientException(ReturnCode.OTHER, msg);
+        } catch (ManagedObjectDecodingException e) {
+            LocalizableMessage msg = ERR_DSCFG_ERROR_GET_CHILD_MODE.get(ufn);
+            throw new ClientException(ReturnCode.OTHER, msg, e);
+        } catch (ConcurrentModificationException e) {
+            LocalizableMessage msg = ERR_DSCFG_ERROR_GET_CHILD_CME.get(ufn);
+            throw new ClientException(ReturnCode.CONSTRAINT_VIOLATION, msg);
+        } catch (ManagedObjectNotFoundException e) {
+            LocalizableMessage msg = ERR_DSCFG_ERROR_GET_CHILD_MONFE.get(ufn);
+            throw new ClientException(ReturnCode.NO_SUCH_OBJECT, msg);
+        }
+
+        if (result.isQuit()) {
+            return MenuResult.quit();
+        } else if (result.isCancel()) {
+            return MenuResult.cancel();
+        }
+
+        // Validate the property names.
+        ManagedObject<?> child = result.getValue();
+        ManagedObjectDefinition<?, ?> d = child.getManagedObjectDefinition();
+        Collection<PropertyDefinition<?>> pdList;
+        if (propertyNames.isEmpty()) {
+            pdList = d.getAllPropertyDefinitions();
+        } else {
+            pdList = new LinkedList<PropertyDefinition<?>>();
+            for (String name : propertyNames) {
+                try {
+                    pdList.add(d.getPropertyDefinition(name));
+                } catch (IllegalArgumentException e) {
+                    throw ArgumentExceptionFactory.unknownProperty(d, name);
+                }
+            }
+        }
+
+        // Now output its properties.
+        TableBuilder builder = new TableBuilder();
+        builder.appendHeading(INFO_DSCFG_HEADING_PROPERTY_NAME.get());
+        builder.appendHeading(INFO_DSCFG_HEADING_PROPERTY_VALUE.get());
+        builder.addSortKey(0);
+        for (PropertyDefinition<?> pd : pdList) {
+            if (pd.hasOption(PropertyOption.HIDDEN)) {
+                continue;
+            }
+
+            if (!app.isAdvancedMode() && pd.hasOption(PropertyOption.ADVANCED)) {
+                continue;
+            }
+
+            if (propertyNames.isEmpty() || propertyNames.contains(pd.getName())) {
+                displayProperty(app, builder, child, pd, valuePrinter);
+                setCommandBuilderUseful(true);
+            }
+        }
+
+        PrintStream out = app.getOutputStream();
+        if (app.isScriptFriendly()) {
+            TablePrinter printer = createScriptFriendlyTablePrinter(out);
+            builder.print(printer);
+        } else {
+            TextTablePrinter printer = new TextTablePrinter(out);
+            printer.setColumnSeparator(LIST_TABLE_SEPARATOR);
+            printer.setColumnWidth(1, 0);
+            builder.print(printer);
+        }
+
+        return MenuResult.success(0);
+    }
+
+    // Display the set of values associated with a property.
+    private <T> void displayProperty(final ConsoleApplication app, TableBuilder builder, ManagedObject<?> mo,
+            PropertyDefinition<T> pd, PropertyValuePrinter valuePrinter) {
+        SortedSet<T> values = mo.getPropertyValues(pd);
+        if (values.isEmpty()) {
+            // There are no values or default values. Display the default
+            // behavior for alias values.
+            DefaultBehaviorProviderVisitor<T, LocalizableMessage, Void> visitor
+                = new DefaultBehaviorProviderVisitor<T, LocalizableMessage, Void>() {
+
+                    public LocalizableMessage visitAbsoluteInherited(AbsoluteInheritedDefaultBehaviorProvider<T> d,
+                            Void p) {
+                        // Should not happen - inherited default values are
+                        // displayed as normal values.
+                        throw new IllegalStateException();
+                    }
+
+                    public LocalizableMessage visitAlias(AliasDefaultBehaviorProvider<T> d, Void p) {
+                        if (app.isVerbose()) {
+                            return d.getSynopsis();
+                        } else {
+                            return null;
+                        }
+                    }
+
+                    public LocalizableMessage visitDefined(DefinedDefaultBehaviorProvider<T> d, Void p) {
+                        // Should not happen - real default values are displayed as
+                        // normal values.
+                        throw new IllegalStateException();
+                    }
+
+                    public LocalizableMessage visitRelativeInherited(RelativeInheritedDefaultBehaviorProvider<T> d,
+                            Void p) {
+                        // Should not happen - inherited default values are
+                        // displayed as normal values.
+                        throw new IllegalStateException();
+                    }
+
+                    public LocalizableMessage visitUndefined(UndefinedDefaultBehaviorProvider<T> d, Void p) {
+                        return null;
+                    }
+                };
+
+            builder.startRow();
+            builder.appendCell(pd.getName());
+
+            LocalizableMessage content = pd.getDefaultBehaviorProvider().accept(visitor, null);
+            if (content == null) {
+                if (app.isScriptFriendly()) {
+                    builder.appendCell();
+                } else {
+                    builder.appendCell("-");
+                }
+            } else {
+                builder.appendCell(content);
+            }
+        } else {
+            if (isRecordMode()) {
+                for (T value : values) {
+                    builder.startRow();
+                    builder.appendCell(pd.getName());
+                    builder.appendCell(valuePrinter.print(pd, value));
+                }
+            } else {
+                builder.startRow();
+                builder.appendCell(pd.getName());
+
+                if (app.isScriptFriendly()) {
+                    for (T value : values) {
+                        builder.appendCell(valuePrinter.print(pd, value));
+                    }
+                } else {
+                    StringBuilder sb = new StringBuilder();
+                    boolean isFirst = true;
+                    for (T value : values) {
+                        if (!isFirst) {
+                            sb.append(", ");
+                        }
+                        sb.append(valuePrinter.print(pd, value));
+                        isFirst = false;
+                    }
+
+                    builder.appendCell(sb.toString());
+                }
+            }
+        }
+    }
+}
diff --git a/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/HelpSubCommandHandler.java b/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/HelpSubCommandHandler.java
new file mode 100644
index 0000000..765e4d6
--- /dev/null
+++ b/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/HelpSubCommandHandler.java
@@ -0,0 +1,1052 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
+ * or http://forgerock.org/license/CDDLv1.0.html.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at legal-notices/CDDLv1_0.txt.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Copyright 2007-2008 Sun Microsystems, Inc.
+ *      Portions Copyright 2011-2014 ForgeRock AS
+ */
+package org.forgerock.opendj.config.dsconfig;
+
+import static com.forgerock.opendj.dsconfig.DsconfigMessages.*;
+import static com.forgerock.opendj.cli.CliMessages.*;
+import static com.forgerock.opendj.cli.Utils.MAX_LINE_WIDTH;
+
+import java.io.PrintStream;
+import java.util.Collection;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.i18n.LocalizableMessageBuilder;
+import org.forgerock.opendj.config.AbsoluteInheritedDefaultBehaviorProvider;
+import org.forgerock.opendj.config.AbstractManagedObjectDefinition;
+import org.forgerock.opendj.config.AdministratorAction;
+import org.forgerock.opendj.config.AggregationPropertyDefinition;
+import org.forgerock.opendj.config.AliasDefaultBehaviorProvider;
+import org.forgerock.opendj.config.DefaultBehaviorProviderVisitor;
+import org.forgerock.opendj.config.DefinedDefaultBehaviorProvider;
+import org.forgerock.opendj.config.EnumPropertyDefinition;
+import org.forgerock.opendj.config.ManagedObjectOption;
+import org.forgerock.opendj.config.PropertyDefinition;
+import org.forgerock.opendj.config.PropertyDefinitionUsageBuilder;
+import org.forgerock.opendj.config.PropertyDefinitionVisitor;
+import org.forgerock.opendj.config.PropertyOption;
+import org.forgerock.opendj.config.RelativeInheritedDefaultBehaviorProvider;
+import org.forgerock.opendj.config.StringPropertyDefinition;
+import org.forgerock.opendj.config.Tag;
+import org.forgerock.opendj.config.UndefinedDefaultBehaviorProvider;
+import org.forgerock.opendj.config.client.ManagedObject;
+
+import com.forgerock.opendj.cli.ArgumentException;
+import com.forgerock.opendj.cli.BooleanArgument;
+import com.forgerock.opendj.cli.StringArgument;
+import com.forgerock.opendj.cli.SubCommand;
+import com.forgerock.opendj.cli.SubCommandArgumentParser;
+import com.forgerock.opendj.cli.ClientException;
+import com.forgerock.opendj.cli.ConsoleApplication;
+import com.forgerock.opendj.cli.MenuResult;
+import com.forgerock.opendj.cli.TableBuilder;
+import com.forgerock.opendj.cli.TablePrinter;
+import com.forgerock.opendj.cli.TextTablePrinter;
+
+/**
+ * A sub-command handler which is used to display help about managed objects and their properties.
+ * <p>
+ * This sub-command implements the help-properties sub-command.
+ */
+final class HelpSubCommandHandler extends SubCommandHandler {
+
+    /**
+     * This class is used to print the default behavior of a property.
+     */
+    private static class DefaultBehaviorPrinter {
+
+        /**
+         * The default behavior printer visitor implementation.
+         *
+         * @param <T>
+         *            The property type.
+         */
+        private static class DefaultVisitor<T> implements
+                DefaultBehaviorProviderVisitor<T, LocalizableMessage, PropertyDefinition<T>> {
+
+            /** {@inheritDoc} */
+            public LocalizableMessage visitAbsoluteInherited(AbsoluteInheritedDefaultBehaviorProvider<T> d,
+                    PropertyDefinition<T> p) {
+                return INFO_DSCFG_HELP_FIELD_INHERITED_ABS.get(d.getPropertyName(), d.getManagedObjectPath()
+                        .getRelationDefinition().getUserFriendlyName());
+            }
+
+            /** {@inheritDoc} */
+            public LocalizableMessage visitAlias(AliasDefaultBehaviorProvider<T> d, PropertyDefinition<T> p) {
+                return d.getSynopsis();
+            }
+
+            /** {@inheritDoc} */
+            public LocalizableMessage visitDefined(DefinedDefaultBehaviorProvider<T> d, PropertyDefinition<T> p) {
+                LocalizableMessageBuilder builder = new LocalizableMessageBuilder();
+                PropertyValuePrinter printer = new PropertyValuePrinter(null, null, false);
+                boolean isFirst = true;
+                for (String s : d.getDefaultValues()) {
+                    if (!isFirst) {
+                        builder.append(", ");
+                    }
+
+                    T value = p.decodeValue(s);
+                    builder.append(printer.print(p, value));
+                }
+
+                return builder.toMessage();
+            }
+
+            /** {@inheritDoc} */
+            public LocalizableMessage visitRelativeInherited(RelativeInheritedDefaultBehaviorProvider<T> d,
+                    PropertyDefinition<T> p) {
+                if (d.getRelativeOffset() == 0) {
+                    return INFO_DSCFG_HELP_FIELD_INHERITED_THIS.get(d.getPropertyName(), d.getManagedObjectDefinition()
+                            .getUserFriendlyName());
+                } else {
+                    return INFO_DSCFG_HELP_FIELD_INHERITED_PARENT.get(d.getPropertyName(), d
+                            .getManagedObjectDefinition().getUserFriendlyName());
+                }
+            }
+
+            /** {@inheritDoc} */
+            public LocalizableMessage visitUndefined(UndefinedDefaultBehaviorProvider<T> d, PropertyDefinition<T> p) {
+                return INFO_DSCFG_HELP_FIELD_UNDEFINED.get();
+            }
+
+        }
+
+        /**
+         * Create a new default behavior printer.
+         */
+        public DefaultBehaviorPrinter() {
+            // No implementation required.
+        }
+
+        /**
+         * Get a user-friendly description of a property's default behavior.
+         *
+         * @param <T>
+         *            The type of the property definition.
+         * @param pd
+         *            The property definition.
+         * @return Returns the user-friendly description of a property's default behavior.
+         */
+        public <T> LocalizableMessage print(PropertyDefinition<T> pd) {
+            DefaultVisitor<T> v = new DefaultVisitor<T>();
+            return pd.getDefaultBehaviorProvider().accept(v, pd);
+        }
+    }
+
+    /**
+     * This class is used to print detailed syntax information about a property.
+     */
+    private static class SyntaxPrinter {
+
+        /**
+         * The syntax printer visitor implementation.
+         */
+        private static final class Visitor extends PropertyDefinitionVisitor<Void, PrintStream> {
+
+            /** Private constructor. */
+            private Visitor() {
+                // No implementation required.
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public <E extends Enum<E>> Void visitEnum(EnumPropertyDefinition<E> d, PrintStream p) {
+                displayUsage(p, INFO_DSCFG_HELP_FIELD_ENUM.get());
+                p.println();
+
+                TableBuilder builder = new TableBuilder();
+                boolean isFirst = true;
+                for (E value : EnumSet.<E> allOf(d.getEnumClass())) {
+                    if (!isFirst) {
+                        builder.startRow();
+                    }
+
+                    builder.startRow();
+                    builder.appendCell();
+                    builder.appendCell();
+                    builder.appendCell(value.toString());
+                    builder.appendCell(HEADING_SEPARATOR);
+                    builder.appendCell(d.getValueSynopsis(value));
+
+                    isFirst = false;
+                }
+
+                TextTablePrinter factory = new TextTablePrinter(p);
+                factory.setDisplayHeadings(false);
+                factory.setColumnWidth(0, HEADING_WIDTH);
+                factory.setColumnWidth(1, HEADING_SEPARATOR.length());
+                factory.setColumnWidth(4, 0);
+                factory.setPadding(0);
+
+                builder.print(factory);
+
+                return null;
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public Void visitString(StringPropertyDefinition d, PrintStream p) {
+                PropertyDefinitionUsageBuilder usageBuilder = new PropertyDefinitionUsageBuilder(false);
+
+                TableBuilder builder = new TableBuilder();
+                builder.startRow();
+                builder.appendCell(INFO_DSCFG_HELP_HEADING_SYNTAX.get());
+                builder.appendCell(HEADING_SEPARATOR);
+                builder.appendCell(usageBuilder.getUsage(d));
+
+                if (d.getPattern() != null) {
+                    builder.startRow();
+                    builder.startRow();
+                    builder.appendCell();
+                    builder.appendCell();
+                    builder.appendCell(d.getPatternSynopsis());
+                }
+
+                TextTablePrinter factory = new TextTablePrinter(p);
+                factory.setDisplayHeadings(false);
+                factory.setColumnWidth(0, HEADING_WIDTH);
+                factory.setColumnWidth(2, 0);
+                factory.setPadding(0);
+
+                builder.print(factory);
+
+                return null;
+            }
+
+            /** {@inheritDoc} */
+            @Override
+            public <T> Void visitUnknown(PropertyDefinition<T> d, PrintStream p) {
+                PropertyDefinitionUsageBuilder usageBuilder = new PropertyDefinitionUsageBuilder(true);
+                displayUsage(p, usageBuilder.getUsage(d));
+
+                return null;
+            }
+
+            /** Common usage. */
+            private void displayUsage(PrintStream p, LocalizableMessage usage) {
+                TableBuilder builder = new TableBuilder();
+                builder.startRow();
+                builder.appendCell(INFO_DSCFG_HELP_HEADING_SYNTAX.get());
+                builder.appendCell(HEADING_SEPARATOR);
+                builder.appendCell(usage);
+
+                TextTablePrinter factory = new TextTablePrinter(p);
+                factory.setDisplayHeadings(false);
+                factory.setColumnWidth(0, HEADING_WIDTH);
+                factory.setColumnWidth(2, 0);
+                factory.setPadding(0);
+
+                builder.print(factory);
+            }
+        }
+
+        /** The private implementation. */
+        private final Visitor pimpl;
+
+        /**
+         * Creates a new syntax printer which can be used to print detailed syntax information about a property.
+         */
+        public SyntaxPrinter() {
+            this.pimpl = new Visitor();
+        }
+
+        /**
+         * Print detailed syntax information about a property definition.
+         *
+         * @param out
+         *            The output stream.
+         * @param pd
+         *            The property definition.
+         */
+        public void print(PrintStream out, PropertyDefinition<?> pd) {
+            pd.accept(pimpl, out);
+        }
+    }
+
+    /** Strings used in property help. */
+    private static final String HEADING_SEPARATOR = " : ";
+
+    /** Width of biggest heading (need to be careful of I18N). */
+    private static final int HEADING_WIDTH;
+
+    /**
+     * The value for the long option category.
+     */
+    private static final String OPTION_DSCFG_LONG_CATEGORY = "category";
+
+    /**
+     * The value for the long option inherited.
+     */
+    private static final String OPTION_DSCFG_LONG_INHERITED = "inherited";
+
+    /**
+     * The value for the long option type.
+     */
+    private static final String OPTION_DSCFG_LONG_TYPE = "type";
+
+    /**
+     * The value for the short option category.
+     */
+    private static final Character OPTION_DSCFG_SHORT_CATEGORY = 'c';
+
+    /**
+     * The value for the short option inherited.
+     */
+    private static final Character OPTION_DSCFG_SHORT_INHERITED = null;
+
+    /**
+     * The value for the short option type.
+     */
+    private static final Character OPTION_DSCFG_SHORT_TYPE = 't';
+
+    static {
+        int tmp = INFO_DSCFG_HELP_HEADING_SYNTAX.get().length();
+        tmp = Math.max(tmp, INFO_DSCFG_HELP_HEADING_DEFAULT.get().length());
+        tmp = Math.max(tmp, INFO_DSCFG_HELP_HEADING_MULTI_VALUED.get().length());
+        tmp = Math.max(tmp, INFO_DSCFG_HELP_HEADING_MANDATORY.get().length());
+        tmp = Math.max(tmp, INFO_DSCFG_HELP_HEADING_READ_ONLY.get().length());
+        HEADING_WIDTH = tmp;
+    }
+
+    /**
+     * Creates a new help-properties sub-command.
+     *
+     * @param parser
+     *            The sub-command argument parser.
+     * @return Returns the new help-properties sub-command.
+     * @throws ArgumentException
+     *             If the sub-command could not be created successfully.
+     */
+    public static HelpSubCommandHandler create(SubCommandArgumentParser parser) throws ArgumentException {
+        return new HelpSubCommandHandler(parser);
+    }
+
+    /**
+     * Displays detailed help about a single component to the specified output stream.
+     *
+     * @param app
+     *            The application console.
+     * @param mo
+     *            The managed object.
+     * @param c
+     *            The collection of properties to be displayed.
+     */
+    public static void displaySingleComponent(ConsoleApplication app, ManagedObject<?> mo,
+            Collection<PropertyDefinition<?>> c) {
+        String ufn = mo.getManagedObjectPath().getName();
+        if (ufn == null) {
+            ufn = mo.getManagedObjectDefinition().getUserFriendlyName().toString();
+        }
+        // Display the title.
+        app.println(INFO_DSCFG_HELP_HEADING_COMPONENT.get(ufn));
+
+        final AbstractManagedObjectDefinition<?, ?> d = mo.getManagedObjectDefinition();
+
+        app.println();
+        app.println(d.getSynopsis());
+        if (d.getDescription() != null) {
+            app.println();
+            app.println(d.getDescription());
+        }
+
+        app.println();
+        app.println();
+        displayPropertyOptionKey(app);
+
+        app.println();
+        app.println();
+
+        // Headings.
+        final TableBuilder builder = new TableBuilder();
+
+        builder.appendHeading(INFO_DSCFG_HEADING_PROPERTY_NAME.get());
+        builder.appendHeading(INFO_DSCFG_HEADING_PROPERTY_OPTIONS.get());
+        builder.appendHeading(INFO_DSCFG_HEADING_PROPERTY_SYNTAX.get());
+
+        // Sort keys.
+        builder.addSortKey(0);
+
+        // Output summary of each property.
+        for (final PropertyDefinition<?> pd : c) {
+            // Display the property.
+            builder.startRow();
+
+            // Display the property name.
+            builder.appendCell(pd.getName());
+
+            // Display the options.
+            builder.appendCell(getPropertyOptionSummary(pd));
+
+            // Display the syntax.
+            final PropertyDefinitionUsageBuilder v = new PropertyDefinitionUsageBuilder(false);
+            builder.appendCell(v.getUsage(pd));
+        }
+
+        builder.print(new TextTablePrinter(app.getErrorStream()));
+    }
+
+    /**
+     * Displays detailed help about a single property to the specified output stream.
+     *
+     * @param app
+     *            The application console.
+     * @param d
+     *            The managed object definition.
+     * @param name
+     *            The name of the property definition.
+     */
+    public static void displayVerboseSingleProperty(ConsoleApplication app, AbstractManagedObjectDefinition<?, ?> d,
+            String name) {
+        PropertyDefinition<?> pd = d.getPropertyDefinition(name);
+
+        // Display the title.
+        app.println(INFO_DSCFG_HELP_HEADING_PROPERTY.get(name));
+
+        // Display the property synopsis and description.
+        app.println();
+        app.errPrintln(pd.getSynopsis(), 4);
+
+        if (pd.getDescription() != null) {
+            app.println();
+            app.errPrintln(pd.getDescription(), 4);
+        }
+
+        if (pd instanceof AggregationPropertyDefinition) {
+            AggregationPropertyDefinition<?, ?> apd = (AggregationPropertyDefinition<?, ?>) pd;
+            if (apd.getSourceConstraintSynopsis() != null) {
+                app.println();
+                app.println(apd.getSourceConstraintSynopsis(), 4);
+            }
+        }
+
+        // Display the syntax.
+        app.println();
+        SyntaxPrinter syntaxPrinter = new SyntaxPrinter();
+        syntaxPrinter.print(app.getErrorStream(), pd);
+
+        // Display remaining information in a table.
+        app.println();
+        TableBuilder builder = new TableBuilder();
+
+        // Display the default behavior.
+        DefaultBehaviorPrinter defaultPrinter = new DefaultBehaviorPrinter();
+
+        builder.startRow();
+        builder.appendCell(INFO_DSCFG_HELP_HEADING_DEFAULT.get());
+        builder.appendCell(HEADING_SEPARATOR);
+        builder.appendCell(defaultPrinter.print(pd));
+
+        // Display options.
+        builder.startRow();
+        builder.appendCell(INFO_DSCFG_HELP_HEADING_ADVANCED.get());
+        builder.appendCell(HEADING_SEPARATOR);
+        if (pd.hasOption(PropertyOption.ADVANCED)) {
+            builder.appendCell(INFO_GENERAL_YES.get());
+        } else {
+            builder.appendCell(INFO_GENERAL_NO.get());
+        }
+
+        builder.startRow();
+        builder.appendCell(INFO_DSCFG_HELP_HEADING_MULTI_VALUED.get());
+        builder.appendCell(HEADING_SEPARATOR);
+        if (pd.hasOption(PropertyOption.MULTI_VALUED)) {
+            builder.appendCell(INFO_GENERAL_YES.get());
+        } else {
+            builder.appendCell(INFO_GENERAL_NO.get());
+        }
+
+        builder.startRow();
+        builder.appendCell(INFO_DSCFG_HELP_HEADING_MANDATORY.get());
+        builder.appendCell(HEADING_SEPARATOR);
+        if (pd.hasOption(PropertyOption.MANDATORY)) {
+            builder.appendCell(INFO_GENERAL_YES.get());
+        } else {
+            builder.appendCell(INFO_GENERAL_NO.get());
+        }
+
+        builder.startRow();
+        builder.appendCell(INFO_DSCFG_HELP_HEADING_READ_ONLY.get());
+        builder.appendCell(HEADING_SEPARATOR);
+        if (pd.hasOption(PropertyOption.MONITORING)) {
+            builder.appendCell(INFO_DSCFG_HELP_FIELD_MONITORING.get());
+        } else if (pd.hasOption(PropertyOption.READ_ONLY)) {
+            builder.appendCell(INFO_DSCFG_HELP_FIELD_READ_ONLY.get(d.getUserFriendlyName()));
+        } else {
+            builder.appendCell(INFO_GENERAL_NO.get());
+        }
+
+        TextTablePrinter factory = new TextTablePrinter(app.getErrorStream());
+        factory.setDisplayHeadings(false);
+        factory.setColumnWidth(0, HEADING_WIDTH);
+        factory.setColumnWidth(2, 0);
+        factory.setPadding(0);
+        builder.print(factory);
+
+        // Administrator action.
+        AdministratorAction action = pd.getAdministratorAction();
+        LocalizableMessage synopsis = action.getSynopsis();
+        if (synopsis == null) {
+            switch (action.getType()) {
+            case COMPONENT_RESTART:
+                synopsis = INFO_DSCFG_HELP_FIELD_COMPONENT_RESTART.get(d.getUserFriendlyName());
+                break;
+            case SERVER_RESTART:
+                synopsis = INFO_DSCFG_HELP_FIELD_SERVER_RESTART.get();
+                break;
+            default:
+                // Do nothing.
+                break;
+            }
+        }
+
+        if (synopsis != null) {
+            app.println();
+            app.println(synopsis);
+        }
+    }
+
+    /** Displays the property option summary key. */
+    private static void displayPropertyOptionKey(ConsoleApplication app) {
+        LocalizableMessageBuilder builder;
+
+        app.println(INFO_DSCFG_HELP_DESCRIPTION_OPTION.get());
+        app.println();
+
+        builder = new LocalizableMessageBuilder();
+        builder.append(" r -- ");
+        builder.append(INFO_DSCFG_HELP_DESCRIPTION_READ.get());
+        app.println(builder.toMessage());
+
+        builder = new LocalizableMessageBuilder();
+        builder.append(" w -- ");
+        builder.append(INFO_DSCFG_HELP_DESCRIPTION_WRITE.get());
+        app.println(builder.toMessage());
+
+        builder = new LocalizableMessageBuilder();
+        builder.append(" m -- ");
+        builder.append(INFO_DSCFG_HELP_DESCRIPTION_MANDATORY.get());
+        app.println(builder.toMessage());
+
+        builder = new LocalizableMessageBuilder();
+        builder.append(" s -- ");
+        builder.append(INFO_DSCFG_HELP_DESCRIPTION_SINGLE_VALUED.get());
+        app.println(builder.toMessage());
+
+        builder = new LocalizableMessageBuilder();
+        builder.append(" a -- ");
+        builder.append(INFO_DSCFG_HELP_DESCRIPTION_ADMIN_ACTION.get());
+        app.println(builder.toMessage());
+    }
+
+    /** Compute the options field. */
+    private static String getPropertyOptionSummary(PropertyDefinition<?> pd) {
+        StringBuilder b = new StringBuilder();
+
+        if (pd.hasOption(PropertyOption.MONITORING) || pd.hasOption(PropertyOption.READ_ONLY)) {
+            b.append("r-");
+        } else {
+            b.append("rw");
+        }
+
+        if (pd.hasOption(PropertyOption.MANDATORY)) {
+            b.append('m');
+        } else {
+            b.append('-');
+        }
+
+        if (pd.hasOption(PropertyOption.MULTI_VALUED)) {
+            b.append('-');
+        } else {
+            b.append('s');
+        }
+
+        AdministratorAction action = pd.getAdministratorAction();
+        if (action.getType() != AdministratorAction.Type.NONE) {
+            b.append('a');
+        } else {
+            b.append('-');
+        }
+        return b.toString();
+    }
+
+    /**
+     * The argument which should be used to specify the category of managed object to be retrieved.
+     */
+    private final StringArgument categoryArgument;
+
+    /**
+     * A table listing all the available types of managed object indexed on their parent type.
+     */
+    private final Map<String, Map<String, AbstractManagedObjectDefinition<?, ?>>> categoryMap;
+
+    /**
+     * The argument which should be used to display inherited properties.
+     */
+    private BooleanArgument inheritedModeArgument;
+
+    /** The sub-command associated with this handler. */
+    private final SubCommand subCommand;
+
+    /**
+     * A table listing all the available types of managed object indexed on their tag(s).
+     */
+    private final Map<Tag, Map<String, AbstractManagedObjectDefinition<?, ?>>> tagMap;
+
+    /**
+     * The argument which should be used to specify the sub-type of managed object to be retrieved.
+     */
+    private final StringArgument typeArgument;
+
+    /** Private constructor. */
+    private HelpSubCommandHandler(SubCommandArgumentParser parser) throws ArgumentException {
+        // Create the sub-command.
+        String name = "list-properties";
+        LocalizableMessage desc = INFO_DSCFG_DESCRIPTION_SUBCMD_HELPPROP.get();
+        this.subCommand = new SubCommand(parser, name, false, 0, 0, null, desc);
+
+        this.categoryArgument = new StringArgument(OPTION_DSCFG_LONG_CATEGORY, OPTION_DSCFG_SHORT_CATEGORY,
+                OPTION_DSCFG_LONG_CATEGORY, false, false, true, INFO_CATEGORY_PLACEHOLDER.get(), null, null,
+                INFO_DSCFG_DESCRIPTION_HELP_CATEGORY.get());
+        this.subCommand.addArgument(this.categoryArgument);
+
+        this.typeArgument = new StringArgument(OPTION_DSCFG_LONG_TYPE, OPTION_DSCFG_SHORT_TYPE, OPTION_DSCFG_LONG_TYPE,
+                false, false, true, INFO_TYPE_PLACEHOLDER.get(), null, null, INFO_DSCFG_DESCRIPTION_HELP_TYPE.get());
+        this.subCommand.addArgument(this.typeArgument);
+
+        this.inheritedModeArgument = new BooleanArgument(OPTION_DSCFG_LONG_INHERITED, OPTION_DSCFG_SHORT_INHERITED,
+                OPTION_DSCFG_LONG_INHERITED, INFO_DSCFG_DESCRIPTION_HELP_INHERITED.get());
+        subCommand.addArgument(inheritedModeArgument);
+
+        // Register common arguments.
+        registerPropertyNameArgument(this.subCommand);
+
+        this.categoryMap = new TreeMap<String, Map<String, AbstractManagedObjectDefinition<?, ?>>>();
+        this.tagMap = new HashMap<Tag, Map<String, AbstractManagedObjectDefinition<?, ?>>>();
+
+        setCommandBuilderUseful(false);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public SubCommand getSubCommand() {
+        return subCommand;
+    }
+
+    /**
+     * Registers a managed object definition with this help properties sub-command.
+     *
+     * @param d
+     *            The managed object definition.
+     */
+    public void registerManagedObjectDefinition(AbstractManagedObjectDefinition<?, ?> d) {
+        // Determine the definition's base name.
+        AbstractManagedObjectDefinition<?, ?> parent = d;
+        while (!parent.getParent().isTop()) {
+            parent = parent.getParent();
+        }
+
+        String baseName = parent.getName();
+        String typeName = null;
+        if (parent == d) {
+            // This was a top-level definition.
+            typeName = DSConfig.GENERIC_TYPE;
+        } else {
+            // For the type name we shorten it, if possible, by stripping
+            // off the trailing part of the name which matches the
+            // base-type.
+            String suffix = "-" + baseName;
+            typeName = d.getName();
+            if (typeName.endsWith(suffix)) {
+                typeName = typeName.substring(0, typeName.length() - suffix.length());
+            }
+        }
+
+        // Get the sub-type mapping, creating it if necessary.
+        Map<String, AbstractManagedObjectDefinition<?, ?>> subTypes = categoryMap.get(baseName);
+        if (subTypes == null) {
+            subTypes = new TreeMap<String, AbstractManagedObjectDefinition<?, ?>>();
+            categoryMap.put(baseName, subTypes);
+        }
+
+        subTypes.put(typeName, d);
+
+        // Get the tag mapping, creating it if necessary.
+        for (Tag tag : d.getAllTags()) {
+            subTypes = tagMap.get(tag);
+            if (subTypes == null) {
+                subTypes = new TreeMap<String, AbstractManagedObjectDefinition<?, ?>>();
+                tagMap.put(tag, subTypes);
+            }
+            subTypes.put(typeName, d);
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public MenuResult<Integer> run(ConsoleApplication app, LDAPManagementContextFactory factory)
+            throws ArgumentException, ClientException {
+
+        String categoryName = categoryArgument.getValue();
+        String typeName = typeArgument.getValue();
+        Tag tag = null;
+        Set<String> propertyNames = getPropertyNames();
+
+        // Reset the command builder
+        getCommandBuilder().clearArguments();
+
+        // Update the command builder.
+        updateCommandBuilderWithSubCommand();
+
+        List<AbstractManagedObjectDefinition<?, ?>> dlist = new LinkedList<AbstractManagedObjectDefinition<?, ?>>();
+        AbstractManagedObjectDefinition<?, ?> tmp = null;
+
+        if (categoryName != null) {
+            // User requested a category of components.
+            Map<String, AbstractManagedObjectDefinition<?, ?>> subTypes = categoryMap.get(categoryName);
+
+            if (subTypes == null) {
+                // Try a tag-base look-up.
+                try {
+                    tag = Tag.valueOf(categoryName);
+                } catch (IllegalArgumentException e) {
+                    throw ArgumentExceptionFactory.unknownCategory(categoryName);
+                }
+
+                subTypes = tagMap.get(tag);
+                if (subTypes == null) {
+                    throw ArgumentExceptionFactory.unknownCategory(categoryName);
+                }
+            } else {
+                // Cache the generic definition for improved errors later on.
+                tmp = subTypes.get(DSConfig.GENERIC_TYPE);
+            }
+
+            if (typeName != null) {
+                AbstractManagedObjectDefinition<?, ?> d = subTypes.get(typeName);
+                if (d == null) {
+                    throw ArgumentExceptionFactory.unknownTypeForCategory(typeName, categoryName);
+                }
+                dlist.add(d);
+
+                // Cache the generic definition for improved errors later on.
+                tmp = d;
+            } else {
+                dlist.addAll(subTypes.values());
+            }
+        } else if (typeName != null) {
+            // User requested just the sub-type which could appear in
+            // multiple categories.
+            boolean isFound = false;
+
+            for (Map<String, AbstractManagedObjectDefinition<?, ?>> subTypes : categoryMap.values()) {
+                AbstractManagedObjectDefinition<?, ?> d = subTypes.get(typeName);
+                if (d != null) {
+                    dlist.add(d);
+                    isFound = true;
+                }
+            }
+
+            if (!isFound) {
+                throw ArgumentExceptionFactory.unknownTypeForCategory(typeName, categoryName);
+            }
+        } else {
+            // User did not specify a category nor a sub-type.
+            for (Map<String, AbstractManagedObjectDefinition<?, ?>> subTypes : categoryMap.values()) {
+                dlist.addAll(subTypes.values());
+            }
+        }
+
+        // Validate property names.
+        if (dlist.size() == 1) {
+            // Cache the generic definition for improved errors later on.
+            tmp = dlist.get(0);
+        }
+
+        for (String propertyName : propertyNames) {
+            boolean isFound = false;
+
+            for (AbstractManagedObjectDefinition<?, ?> d : dlist) {
+                try {
+                    d.getPropertyDefinition(propertyName);
+                    isFound = true;
+                } catch (IllegalArgumentException e) {
+                    // Ignore for now.
+                }
+            }
+
+            if (!isFound) {
+                if (tmp != null) {
+                    throw ArgumentExceptionFactory.unknownProperty(tmp, propertyName);
+                } else {
+                    throw ArgumentExceptionFactory.unknownProperty(propertyName);
+                }
+            }
+        }
+
+        // Output everything to the output stream.
+        if (!app.isVerbose()) {
+            displayNonVerbose(app, categoryName, typeName, tag, propertyNames);
+        } else {
+            displayVerbose(app, categoryName, typeName, tag, propertyNames);
+        }
+
+        return MenuResult.success(0);
+    }
+
+    /** Output property summary table. */
+    private void displayNonVerbose(ConsoleApplication app, String categoryName, String typeName, Tag tag,
+            Set<String> propertyNames) {
+        if (!app.isScriptFriendly()) {
+            displayPropertyOptionKey(app);
+            app.println();
+            app.println();
+        }
+
+        // Headings.
+        TableBuilder builder = new TableBuilder();
+
+        builder.appendHeading(INFO_DSCFG_HEADING_COMPONENT_NAME.get());
+        builder.appendHeading(INFO_DSCFG_HEADING_COMPONENT_TYPE.get());
+        builder.appendHeading(INFO_DSCFG_HEADING_PROPERTY_NAME.get());
+        builder.appendHeading(INFO_DSCFG_HEADING_PROPERTY_OPTIONS.get());
+        builder.appendHeading(INFO_DSCFG_HEADING_PROPERTY_SYNTAX.get());
+
+        // Sort keys.
+        builder.addSortKey(0);
+        builder.addSortKey(1);
+        builder.addSortKey(2);
+
+        // Generate the table content.
+        for (String category : categoryMap.keySet()) {
+            // Skip if this is the wrong category.
+            if (categoryName != null && !categoryName.equals(category)) {
+                continue;
+            }
+
+            // Process the sub-types.
+            Map<String, AbstractManagedObjectDefinition<?, ?>> subTypes = categoryMap.get(category);
+            for (String type : subTypes.keySet()) {
+                // Skip if this is the wrong sub-type.
+                if (typeName != null && !typeName.equals(type)) {
+                    continue;
+                }
+
+                // Display help for each property.
+                AbstractManagedObjectDefinition<?, ?> mod = subTypes.get(type);
+
+                // Skip hidden types.
+                if (mod.hasOption(ManagedObjectOption.HIDDEN)) {
+                    continue;
+                }
+
+                // Skip advanced types if required.
+                if (!app.isAdvancedMode() && mod.hasOption(ManagedObjectOption.ADVANCED)) {
+                    continue;
+                }
+
+                // Skip if this does not have the required tag.
+                if (tag != null && !mod.hasTag(tag)) {
+                    continue;
+                }
+
+                Set<PropertyDefinition<?>> pds = new TreeSet<PropertyDefinition<?>>();
+                if (inheritedModeArgument.isPresent()) {
+                    pds.addAll(mod.getAllPropertyDefinitions());
+                } else {
+                    pds.addAll(mod.getPropertyDefinitions());
+
+                    // The list will still contain overridden properties.
+                    if (mod.getParent() != null) {
+                        pds.removeAll(mod.getParent().getAllPropertyDefinitions());
+                    }
+                }
+
+                for (PropertyDefinition<?> pd : pds) {
+                    if (pd.hasOption(PropertyOption.HIDDEN)) {
+                        continue;
+                    }
+
+                    if (!app.isAdvancedMode() && pd.hasOption(PropertyOption.ADVANCED)) {
+                        continue;
+                    }
+
+                    if (!propertyNames.isEmpty() && !propertyNames.contains(pd.getName())) {
+                        continue;
+                    }
+
+                    // Display the property.
+                    builder.startRow();
+
+                    // Display the component category.
+                    builder.appendCell(category);
+
+                    // Display the component type.
+                    builder.appendCell(type);
+
+                    // Display the property name.
+                    builder.appendCell(pd.getName());
+
+                    // Display the options.
+                    builder.appendCell(getPropertyOptionSummary(pd));
+
+                    // Display the syntax.
+                    PropertyDefinitionUsageBuilder v = new PropertyDefinitionUsageBuilder(false);
+                    builder.appendCell(v.getUsage(pd));
+                }
+            }
+        }
+
+        TablePrinter printer;
+        if (app.isScriptFriendly()) {
+            printer = createScriptFriendlyTablePrinter(app.getOutputStream());
+        } else {
+            printer = new TextTablePrinter(app.getOutputStream());
+        }
+        builder.print(printer);
+    }
+
+    /** Display detailed help on managed objects and their properties. */
+    private void displayVerbose(ConsoleApplication app, String categoryName, String typeName, Tag tag,
+            Set<String> propertyNames) {
+        // Construct line used to separate consecutive sections.
+        LocalizableMessageBuilder mb;
+
+        mb = new LocalizableMessageBuilder();
+        for (int i = 0; i < MAX_LINE_WIDTH; i++) {
+            mb.append('=');
+        }
+        LocalizableMessage c1 = mb.toMessage();
+
+        mb = new LocalizableMessageBuilder();
+        for (int i = 0; i < MAX_LINE_WIDTH; i++) {
+            mb.append('-');
+        }
+        LocalizableMessage c2 = mb.toMessage();
+
+        // Display help for each managed object.
+        boolean isFirstManagedObject = true;
+        for (String category : categoryMap.keySet()) {
+            // Skip if this is the wrong category.
+            if (categoryName != null && !categoryName.equals(category)) {
+                continue;
+            }
+
+            // Process the sub-types.
+            Map<String, AbstractManagedObjectDefinition<?, ?>> subTypes = categoryMap.get(category);
+            for (String type : subTypes.keySet()) {
+                // Skip if this is the wrong sub-type.
+                if (typeName != null && !typeName.equals(type)) {
+                    continue;
+                }
+
+                // Display help for each property.
+                AbstractManagedObjectDefinition<?, ?> mod = subTypes.get(type);
+
+                // Skip hidden types.
+                if (mod.hasOption(ManagedObjectOption.HIDDEN)) {
+                    continue;
+                }
+
+                // Skip advanced types if required.
+                if (!app.isAdvancedMode() && mod.hasOption(ManagedObjectOption.ADVANCED)) {
+                    continue;
+                }
+
+                // Skip if this does not have the required tag.
+                if (tag != null && !mod.hasTag(tag)) {
+                    continue;
+                }
+
+                Set<PropertyDefinition<?>> pds = new TreeSet<PropertyDefinition<?>>();
+                if (inheritedModeArgument.isPresent()) {
+                    pds.addAll(mod.getAllPropertyDefinitions());
+                } else {
+                    pds.addAll(mod.getPropertyDefinitions());
+
+                    // The list will still contain overridden properties.
+                    if (mod.getParent() != null) {
+                        pds.removeAll(mod.getParent().getAllPropertyDefinitions());
+                    }
+                }
+
+                boolean isFirstProperty = true;
+                for (PropertyDefinition<?> pd : pds) {
+                    if (pd.hasOption(PropertyOption.HIDDEN)) {
+                        continue;
+                    }
+
+                    if (!app.isAdvancedMode() && pd.hasOption(PropertyOption.ADVANCED)) {
+                        continue;
+                    }
+
+                    if (!propertyNames.isEmpty() && !propertyNames.contains(pd.getName())) {
+                        continue;
+                    }
+
+                    if (isFirstProperty) {
+                        // User has requested properties relating to this managed
+                        // object definition, so display the summary of the
+                        // managed
+                        // object.
+                        if (!isFirstManagedObject) {
+                            app.println();
+                            app.println(c1);
+                            app.println();
+                        } else {
+                            isFirstManagedObject = false;
+                        }
+
+                        // Display the title.
+                        app.println(INFO_DSCFG_HELP_HEADING_COMPONENT.get(mod.getUserFriendlyName()));
+
+                        app.println();
+                        app.println(mod.getSynopsis());
+                        if (mod.getDescription() != null) {
+                            app.println();
+                            app.println(mod.getDescription());
+                        }
+                    }
+
+                    app.println();
+                    app.println(c2);
+                    app.println();
+
+                    displayVerboseSingleProperty(app, mod, pd.getName());
+                    isFirstProperty = false;
+                }
+            }
+        }
+    }
+}
diff --git a/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/LDAPManagementContextFactory.java b/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/LDAPManagementContextFactory.java
new file mode 100644
index 0000000..75a9318
--- /dev/null
+++ b/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/LDAPManagementContextFactory.java
@@ -0,0 +1,167 @@
+/*
+ * 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-2010 Sun Microsystems, Inc.
+ *      Portions Copyright 2014 ForgeRock AS
+ */
+package org.forgerock.opendj.config.dsconfig;
+
+import static com.forgerock.opendj.dsconfig.DsconfigMessages.*;
+import static com.forgerock.opendj.cli.CliMessages.*;
+import static org.forgerock.util.Utils.closeSilently;
+
+import javax.net.ssl.SSLException;
+
+import org.forgerock.opendj.config.LDAPProfile;
+import org.forgerock.opendj.config.client.ManagementContext;
+import org.forgerock.opendj.config.client.ldap.LDAPManagementContext;
+import org.forgerock.opendj.config.server.ConfigException;
+import org.forgerock.opendj.ldap.Connection;
+import org.forgerock.opendj.ldap.ConnectionFactory;
+import org.forgerock.opendj.ldap.ErrorResultException;
+
+import com.forgerock.opendj.cli.ArgumentException;
+import com.forgerock.opendj.cli.ClientException;
+import com.forgerock.opendj.cli.CommandBuilder;
+import com.forgerock.opendj.cli.ConnectionFactoryProvider;
+import com.forgerock.opendj.cli.ConsoleApplication;
+import com.forgerock.opendj.cli.ReturnCode;
+
+/**
+ * An LDAP management context factory for the DSConfig tool.
+ */
+public final class LDAPManagementContextFactory
+{
+
+  /** The management context. */
+  private ManagementContext context;
+
+  /** The connection parameters command builder. */
+  private CommandBuilder contextCommandBuilder;
+
+  /** The connection factory provider. */
+  private final ConnectionFactoryProvider provider;
+
+  /** The connection factory. */
+  private final ConnectionFactory factory;
+
+  /**
+   * Creates a new LDAP management context factory based on an authenticated
+   * connection factory.
+   *
+   * @param cfp
+   *          The connection factory provider which should be used in this
+   *          context.
+   * @throws ArgumentException
+   *           If an exception occurs when creating the authenticated connection
+   *           factory linked to this context.
+   */
+  public LDAPManagementContextFactory(ConnectionFactoryProvider cfp) throws ArgumentException {
+    this.provider = cfp;
+    factory = cfp.getAuthenticatedConnectionFactory();
+  }
+
+  /**
+   * Closes this management context.
+   */
+  public void close()
+  {
+    closeSilently(context);
+  }
+
+  /**
+   * Returns the command builder that provides the equivalent arguments in
+   * interactive mode to get the management context.
+   *
+   * @return the command builder that provides the equivalent arguments in
+   *         interactive mode to get the management context.
+   */
+  public CommandBuilder getContextCommandBuilder()
+  {
+    return contextCommandBuilder;
+  }
+
+  /**
+   * Gets the management context which sub-commands should use in
+   * order to manage the directory server.
+   *
+   * @param app
+   *          The console application instance.
+   * @return Returns the management context which sub-commands should
+   *         use in order to manage the directory server.
+   * @throws ArgumentException
+   *           If a management context related argument could not be
+   *           parsed successfully.
+   * @throws ClientException
+   *           If the management context could not be created.
+   */
+  public ManagementContext getManagementContext(ConsoleApplication app)
+      throws ArgumentException, ClientException
+  {
+    // Lazily create the LDAP management context.
+    if (context == null)
+    {
+      Connection connection;
+      final String hostName = provider.getHostname();
+      final int port = provider.getPort();
+      try
+      {
+        connection = factory.getConnection();
+        BuildVersion.checkVersionMismatch(connection);
+      }
+      catch (ErrorResultException e)
+      {
+        if (e.getCause() instanceof SSLException)
+        {
+          throw new ClientException(ReturnCode.CLIENT_SIDE_CONNECT_ERROR,
+              ERR_FAILED_TO_CONNECT_NOT_TRUSTED.get(hostName, String
+                  .valueOf(port)));
+        }
+        else
+        {
+          throw new ClientException(ReturnCode.CLIENT_SIDE_CONNECT_ERROR,
+              ERR_DSCFG_ERROR_LDAP_FAILED_TO_CONNECT.get(hostName, String
+                  .valueOf(port)));
+        }
+      }
+      catch (ConfigException e)
+      {
+        throw new ClientException(ReturnCode.ERROR_USER_DATA,e.getMessageObject());
+      }
+      catch (Exception ex)
+      {
+        throw new ClientException(ReturnCode.CLIENT_SIDE_CONNECT_ERROR,
+            ERR_DSCFG_ERROR_LDAP_FAILED_TO_CONNECT.get(hostName, port));
+      }
+      finally
+      {
+        closeSilently(factory);
+      }
+
+      context =
+          LDAPManagementContext.newManagementContext(connection, LDAPProfile
+              .getInstance());
+    }
+    return context;
+  }
+}
diff --git a/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/ListSubCommandHandler.java b/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/ListSubCommandHandler.java
new file mode 100644
index 0000000..31e46d0
--- /dev/null
+++ b/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/ListSubCommandHandler.java
@@ -0,0 +1,493 @@
+/*
+ * 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-2009 Sun Microsystems, Inc.
+ *      Portions Copyright 2012-2014 ForgeRock AS.
+ */
+package org.forgerock.opendj.config.dsconfig;
+
+import static com.forgerock.opendj.dsconfig.DsconfigMessages.*;
+import static com.forgerock.opendj.cli.ArgumentConstants.LIST_TABLE_SEPARATOR;
+
+import java.io.PrintStream;
+import java.util.List;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.SortedSet;
+import java.util.TreeMap;
+
+import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.opendj.config.DefinitionDecodingException;
+import org.forgerock.opendj.config.InstantiableRelationDefinition;
+import org.forgerock.opendj.config.ManagedObjectDefinition;
+import org.forgerock.opendj.config.ManagedObjectNotFoundException;
+import org.forgerock.opendj.config.ManagedObjectOption;
+import org.forgerock.opendj.config.ManagedObjectPath;
+import org.forgerock.opendj.config.OptionalRelationDefinition;
+import org.forgerock.opendj.config.PropertyDefinition;
+import org.forgerock.opendj.config.RelationDefinition;
+import org.forgerock.opendj.config.SetRelationDefinition;
+import org.forgerock.opendj.config.client.ConcurrentModificationException;
+import org.forgerock.opendj.config.client.ManagedObject;
+import org.forgerock.opendj.config.client.ManagedObjectDecodingException;
+import org.forgerock.opendj.config.client.ManagementContext;
+import org.forgerock.opendj.ldap.AuthorizationException;
+import org.forgerock.opendj.ldap.ErrorResultException;
+
+import com.forgerock.opendj.cli.ArgumentException;
+import com.forgerock.opendj.cli.ClientException;
+import com.forgerock.opendj.cli.ConsoleApplication;
+import com.forgerock.opendj.cli.MenuResult;
+import com.forgerock.opendj.cli.ReturnCode;
+import com.forgerock.opendj.cli.StringArgument;
+import com.forgerock.opendj.cli.SubCommand;
+import com.forgerock.opendj.cli.SubCommandArgumentParser;
+import com.forgerock.opendj.cli.TableBuilder;
+import com.forgerock.opendj.cli.TablePrinter;
+import com.forgerock.opendj.cli.TextTablePrinter;
+
+/**
+ * A sub-command handler which is used to list existing managed objects.
+ * <p>
+ * This sub-command implements the various list-xxx sub-commands.
+ */
+final class ListSubCommandHandler extends SubCommandHandler {
+
+    /**
+     * Creates a new list-xxx sub-command for an instantiable relation.
+     *
+     * @param parser
+     *            The sub-command argument parser.
+     * @param p
+     *            The parent managed object path.
+     * @param r
+     *            The instantiable relation.
+     * @return Returns the new list-xxx sub-command.
+     * @throws ArgumentException
+     *             If the sub-command could not be created successfully.
+     */
+    public static ListSubCommandHandler create(SubCommandArgumentParser parser, ManagedObjectPath<?, ?> p,
+            InstantiableRelationDefinition<?, ?> r) throws ArgumentException {
+        return new ListSubCommandHandler(parser, p, r, r.getPluralName(), r.getUserFriendlyPluralName());
+    }
+
+    /**
+     * Creates a new list-xxx sub-command for a set relation.
+     *
+     * @param parser
+     *            The sub-command argument parser.
+     * @param p
+     *            The parent managed object path.
+     * @param r
+     *            The set relation.
+     * @return Returns the new list-xxx sub-command.
+     * @throws ArgumentException
+     *             If the sub-command could not be created successfully.
+     */
+    public static ListSubCommandHandler create(SubCommandArgumentParser parser, ManagedObjectPath<?, ?> p,
+            SetRelationDefinition<?, ?> r) throws ArgumentException {
+        return new ListSubCommandHandler(parser, p, r, r.getPluralName(), r.getUserFriendlyPluralName());
+    }
+
+    /**
+     * Creates a new list-xxx sub-command for an optional relation.
+     *
+     * @param parser
+     *            The sub-command argument parser.
+     * @param p
+     *            The parent managed object path.
+     * @param r
+     *            The optional relation.
+     * @return Returns the new list-xxx sub-command.
+     * @throws ArgumentException
+     *             If the sub-command could not be created successfully.
+     */
+    public static ListSubCommandHandler create(SubCommandArgumentParser parser, ManagedObjectPath<?, ?> p,
+            OptionalRelationDefinition<?, ?> r) throws ArgumentException {
+        return new ListSubCommandHandler(parser, p, r, r.getName(), r.getUserFriendlyName());
+    }
+
+    /** The sub-commands naming arguments. */
+    private final List<StringArgument> namingArgs;
+
+    /** The path of the parent managed object. */
+    private final ManagedObjectPath<?, ?> path;
+
+    /** The relation which should be listed. */
+    private final RelationDefinition<?, ?> relation;
+
+    /** The sub-command associated with this handler. */
+    private final SubCommand subCommand;
+
+    /** Private constructor. */
+    private ListSubCommandHandler(SubCommandArgumentParser parser, ManagedObjectPath<?, ?> p,
+            RelationDefinition<?, ?> r, String rname, LocalizableMessage rufn) throws ArgumentException {
+        path = p;
+        relation = r;
+
+        // Create the sub-command.
+        subCommand = new SubCommand(parser, "list-" + rname, false, 0, 0, null,
+                INFO_DSCFG_DESCRIPTION_SUBCMD_LIST.get(rufn));
+
+        // Create the naming arguments.
+        namingArgs = createNamingArgs(subCommand, path, false);
+
+        // Register arguments.
+        registerPropertyNameArgument(subCommand);
+        registerUnitSizeArgument(subCommand);
+        registerUnitTimeArgument(subCommand);
+
+        // Register the tags associated with the child managed objects.
+        addTags(relation.getChildDefinition().getAllTags());
+    }
+
+    /**
+     * Gets the relation definition associated with the type of component that this sub-command handles.
+     *
+     * @return Returns the relation definition associated with the type of component that this sub-command handles.
+     */
+    public RelationDefinition<?, ?> getRelationDefinition() {
+        return relation;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public SubCommand getSubCommand() {
+        return subCommand;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public MenuResult<Integer> run(ConsoleApplication app, LDAPManagementContextFactory factory)
+            throws ArgumentException, ClientException {
+        // Get the property names.
+        Set<String> propertyNames = getPropertyNames();
+
+        // Reset the command builder
+        getCommandBuilder().clearArguments();
+
+        // Update the command builder.
+        updateCommandBuilderWithSubCommand();
+
+        if (propertyNames.isEmpty()) {
+            // Use a default set of properties.
+            propertyNames = CLIProfile.getInstance().getDefaultListPropertyNames(relation);
+        }
+
+        PropertyValuePrinter valuePrinter = new PropertyValuePrinter(getSizeUnit(), getTimeUnit(),
+                app.isScriptFriendly());
+
+        // Get the naming argument values.
+        List<String> names = getNamingArgValues(app, namingArgs);
+
+        LocalizableMessage ufn;
+        if (relation instanceof InstantiableRelationDefinition) {
+            InstantiableRelationDefinition<?, ?> irelation = (InstantiableRelationDefinition<?, ?>) relation;
+            ufn = irelation.getUserFriendlyPluralName();
+        } else if (relation instanceof SetRelationDefinition) {
+            SetRelationDefinition<?, ?> srelation = (SetRelationDefinition<?, ?>) relation;
+            ufn = srelation.getUserFriendlyPluralName();
+        } else {
+            ufn = relation.getUserFriendlyName();
+        }
+
+        // List the children.
+        ManagementContext context = factory.getManagementContext(app);
+        MenuResult<ManagedObject<?>> result;
+        try {
+            result = getManagedObject(app, context, path, names);
+        } catch (AuthorizationException e) {
+            LocalizableMessage msg = ERR_DSCFG_ERROR_LIST_AUTHZ.get(ufn);
+            throw new ClientException(ReturnCode.INSUFFICIENT_ACCESS_RIGHTS, msg);
+        } catch (DefinitionDecodingException e) {
+            ufn = path.getManagedObjectDefinition().getUserFriendlyName();
+            LocalizableMessage msg = ERR_DSCFG_ERROR_GET_PARENT_DDE.get(ufn, ufn, ufn);
+            throw new ClientException(ReturnCode.OTHER, msg);
+        } catch (ManagedObjectDecodingException e) {
+            ufn = path.getManagedObjectDefinition().getUserFriendlyName();
+            LocalizableMessage msg = ERR_DSCFG_ERROR_GET_PARENT_MODE.get(ufn);
+            throw new ClientException(ReturnCode.OTHER, msg, e);
+        } catch (ConcurrentModificationException e) {
+            LocalizableMessage msg = ERR_DSCFG_ERROR_LIST_CME.get(ufn);
+            throw new ClientException(ReturnCode.CONSTRAINT_VIOLATION, msg);
+        } catch (ManagedObjectNotFoundException e) {
+            ufn = path.getManagedObjectDefinition().getUserFriendlyName();
+            LocalizableMessage msg = ERR_DSCFG_ERROR_GET_PARENT_MONFE.get(ufn);
+            if (app.isInteractive()) {
+                app.println();
+                app.printVerboseMessage(msg);
+                return MenuResult.cancel();
+            } else {
+                throw new ClientException(ReturnCode.NO_SUCH_OBJECT, msg);
+            }
+        }
+
+        if (result.isQuit()) {
+            return MenuResult.quit();
+        } else if (result.isCancel()) {
+            return MenuResult.cancel();
+        }
+
+        ManagedObject<?> parent = result.getValue();
+        SortedMap<String, ManagedObject<?>> children = new TreeMap<String, ManagedObject<?>>();
+        if (relation instanceof InstantiableRelationDefinition) {
+            InstantiableRelationDefinition<?, ?> irelation = (InstantiableRelationDefinition<?, ?>) relation;
+            try {
+                for (String s : parent.listChildren(irelation)) {
+                    try {
+                        children.put(s, parent.getChild(irelation, s));
+                    } catch (ManagedObjectNotFoundException e) {
+                        // Ignore - as it's been removed since we did the list.
+                    }
+                }
+            } catch (DefinitionDecodingException e) {
+                // FIXME: just output this as a warnings (incl. the name) but
+                // continue.
+                LocalizableMessage msg = ERR_DSCFG_ERROR_LIST_DDE.get(ufn, ufn, ufn);
+                throw new ClientException(ReturnCode.OTHER, msg);
+            } catch (ManagedObjectDecodingException e) {
+                // FIXME: just output this as a warnings (incl. the name) but
+                // continue.
+                LocalizableMessage msg = ERR_DSCFG_ERROR_LIST_MODE.get(ufn);
+                throw new ClientException(ReturnCode.OTHER, msg, e);
+            } catch (AuthorizationException e) {
+                LocalizableMessage msg = ERR_DSCFG_ERROR_LIST_AUTHZ.get(ufn);
+                throw new ClientException(ReturnCode.INSUFFICIENT_ACCESS_RIGHTS, msg);
+            } catch (ConcurrentModificationException e) {
+                LocalizableMessage msg = ERR_DSCFG_ERROR_LIST_CME.get(ufn);
+                throw new ClientException(ReturnCode.CONSTRAINT_VIOLATION, msg);
+            } catch (ErrorResultException e) {
+                LocalizableMessage msg = ERR_DSCFG_ERROR_LIST_CE.get(ufn, e.getMessage());
+                throw new ClientException(ReturnCode.CLIENT_SIDE_SERVER_DOWN, msg);
+            }
+        } else if (relation instanceof SetRelationDefinition) {
+            SetRelationDefinition<?, ?> srelation = (SetRelationDefinition<?, ?>) relation;
+            try {
+                for (String s : parent.listChildren(srelation)) {
+                    try {
+                        children.put(s, parent.getChild(srelation, s));
+                    } catch (ManagedObjectNotFoundException e) {
+                        // Ignore - as it's been removed since we did the list.
+                    }
+                }
+            } catch (DefinitionDecodingException e) {
+                // FIXME: just output this as a warnings (incl. the name) but
+                // continue.
+                LocalizableMessage msg = ERR_DSCFG_ERROR_LIST_DDE.get(ufn, ufn, ufn);
+                throw new ClientException(ReturnCode.OTHER, msg);
+            } catch (ManagedObjectDecodingException e) {
+                // FIXME: just output this as a warnings (incl. the name) but
+                // continue.
+                LocalizableMessage msg = ERR_DSCFG_ERROR_LIST_MODE.get(ufn);
+                throw new ClientException(ReturnCode.OTHER, msg, e);
+            } catch (AuthorizationException e) {
+                LocalizableMessage msg = ERR_DSCFG_ERROR_LIST_AUTHZ.get(ufn);
+                throw new ClientException(ReturnCode.INSUFFICIENT_ACCESS_RIGHTS, msg);
+            } catch (ConcurrentModificationException e) {
+                LocalizableMessage msg = ERR_DSCFG_ERROR_LIST_CME.get(ufn);
+                throw new ClientException(ReturnCode.CONSTRAINT_VIOLATION, msg);
+            } catch (ErrorResultException e) {
+                LocalizableMessage msg = ERR_DSCFG_ERROR_LIST_CE.get(ufn, e.getMessage());
+                throw new ClientException(ReturnCode.CLIENT_SIDE_SERVER_DOWN, msg);
+            }
+        } else if (relation instanceof OptionalRelationDefinition) {
+            OptionalRelationDefinition<?, ?> orelation = (OptionalRelationDefinition<?, ?>) relation;
+            try {
+                if (parent.hasChild(orelation)) {
+                    ManagedObject<?> child = parent.getChild(orelation);
+                    children.put(child.getManagedObjectDefinition().getName(), child);
+                } else {
+                    // Indicate that the managed object does not exist.
+                    LocalizableMessage msg = ERR_DSCFG_ERROR_FINDER_NO_CHILDREN.get(ufn);
+                    if (app.isInteractive()) {
+                        app.println();
+                        app.printVerboseMessage(msg);
+                        return MenuResult.cancel();
+                    } else {
+                        throw new ClientException(ReturnCode.NO_SUCH_OBJECT, msg);
+                    }
+                }
+            } catch (AuthorizationException e) {
+                LocalizableMessage msg = ERR_DSCFG_ERROR_LIST_AUTHZ.get(ufn);
+                throw new ClientException(ReturnCode.INSUFFICIENT_ACCESS_RIGHTS, msg);
+            } catch (DefinitionDecodingException e) {
+                LocalizableMessage msg = ERR_DSCFG_ERROR_LIST_DDE.get(ufn, ufn, ufn);
+                throw new ClientException(ReturnCode.OTHER, msg);
+            } catch (ManagedObjectDecodingException e) {
+                LocalizableMessage msg = ERR_DSCFG_ERROR_LIST_MODE.get(ufn);
+                throw new ClientException(ReturnCode.OTHER, msg, e);
+            } catch (ConcurrentModificationException e) {
+                LocalizableMessage msg = ERR_DSCFG_ERROR_LIST_CME.get(ufn);
+                throw new ClientException(ReturnCode.CONSTRAINT_VIOLATION, msg);
+            } catch (ErrorResultException e) {
+                LocalizableMessage msg = ERR_DSCFG_ERROR_LIST_CE.get(ufn, e.getMessage());
+                throw new ClientException(ReturnCode.CLIENT_SIDE_SERVER_DOWN, msg);
+            } catch (ManagedObjectNotFoundException e) {
+                LocalizableMessage msg = ERR_DSCFG_ERROR_LIST_MONFE.get(ufn);
+                throw new ClientException(ReturnCode.NO_SUCH_OBJECT, msg);
+            }
+        }
+
+        // Output the results.
+        if (app.isScriptFriendly()) {
+            // Output just the names of the children.
+            for (String name : children.keySet()) {
+                // Skip advanced and hidden components in non-advanced mode.
+                if (!app.isAdvancedMode()) {
+                    ManagedObject<?> child = children.get(name);
+                    ManagedObjectDefinition<?, ?> d = child.getManagedObjectDefinition();
+
+                    if (d.hasOption(ManagedObjectOption.HIDDEN)) {
+                        continue;
+                    }
+
+                    if (d.hasOption(ManagedObjectOption.ADVANCED)) {
+                        continue;
+                    }
+                }
+
+                app.println(LocalizableMessage.raw(name));
+            }
+        } else {
+            // Create a table of their properties containing the name, type (if
+            // appropriate), and requested properties.
+            SortedMap<String, ?> subTypes = getSubTypes(relation.getChildDefinition());
+            boolean includeTypesColumn = subTypes.size() != 1 || !subTypes.containsKey(DSConfig.GENERIC_TYPE);
+
+            TableBuilder builder = new TableBuilder();
+            builder.appendHeading(relation.getUserFriendlyName());
+            if (includeTypesColumn) {
+                builder.appendHeading(INFO_DSCFG_HEADING_COMPONENT_TYPE.get());
+            }
+            for (String propertyName : propertyNames) {
+                builder.appendHeading(LocalizableMessage.raw(propertyName));
+            }
+            builder.addSortKey(0);
+
+            String baseType = relation.getChildDefinition().getName();
+            String typeSuffix = "-" + baseType;
+            for (String name : children.keySet()) {
+                ManagedObject<?> child = children.get(name);
+                ManagedObjectDefinition<?, ?> d = child.getManagedObjectDefinition();
+
+                // Skip advanced and hidden components in non-advanced mode.
+                if (!app.isAdvancedMode()) {
+                    if (d.hasOption(ManagedObjectOption.HIDDEN)) {
+                        continue;
+                    }
+
+                    if (d.hasOption(ManagedObjectOption.ADVANCED)) {
+                        continue;
+                    }
+                }
+
+                // First output the name.
+                builder.startRow();
+                if (relation instanceof SetRelationDefinition) {
+                    builder.appendCell(d.getUserFriendlyName());
+                } else {
+                    builder.appendCell(name);
+                }
+
+                if (includeTypesColumn) {
+                    // Output the managed object type in the form used in
+                    // create-xxx commands.
+                    String childType = d.getName();
+                    boolean isCustom = CLIProfile.getInstance().isForCustomization(d);
+                    if (baseType.equals(childType)) {
+                        if (isCustom) {
+                            builder.appendCell(DSConfig.CUSTOM_TYPE);
+                        } else {
+                            builder.appendCell(DSConfig.GENERIC_TYPE);
+                        }
+                    } else if (childType.endsWith(typeSuffix)) {
+                        String ctname = childType.substring(0, childType.length() - typeSuffix.length());
+                        if (isCustom) {
+                            ctname = String.format("%s-%s", DSConfig.CUSTOM_TYPE, ctname);
+                        }
+                        builder.appendCell(ctname);
+                    } else {
+                        builder.appendCell(childType);
+                    }
+                }
+
+                // Now any requested properties.
+                for (String propertyName : propertyNames) {
+                    try {
+                        PropertyDefinition<?> pd = d.getPropertyDefinition(propertyName);
+                        displayProperty(app, builder, child, pd, valuePrinter);
+                    } catch (IllegalArgumentException e) {
+                        // Assume this child managed object does not support this
+                        // property.
+                        if (app.isScriptFriendly()) {
+                            builder.appendCell();
+                        } else {
+                            builder.appendCell("-");
+                        }
+                    }
+                }
+            }
+
+            PrintStream out = app.getOutputStream();
+            if (app.isScriptFriendly()) {
+                TablePrinter printer = createScriptFriendlyTablePrinter(out);
+                builder.print(printer);
+            } else {
+                if (app.isInteractive()) {
+                    // Make interactive mode prettier.
+                    app.println();
+                    app.println();
+                }
+                TextTablePrinter printer = new TextTablePrinter(out);
+                printer.setColumnSeparator(LIST_TABLE_SEPARATOR);
+                builder.print(printer);
+            }
+        }
+
+        return MenuResult.success(0);
+    }
+
+    /** Display the set of values associated with a property. */
+    private <T> void displayProperty(ConsoleApplication app, TableBuilder builder, ManagedObject<?> mo,
+            PropertyDefinition<T> pd, PropertyValuePrinter valuePrinter) {
+        SortedSet<T> values = mo.getPropertyValues(pd);
+        if (values.isEmpty()) {
+            if (app.isScriptFriendly()) {
+                builder.appendCell();
+            } else {
+                builder.appendCell("-");
+            }
+        } else {
+            StringBuilder sb = new StringBuilder();
+            boolean isFirst = true;
+            for (T value : values) {
+                if (!isFirst) {
+                    sb.append(", ");
+                }
+                sb.append(valuePrinter.print(pd, value));
+                isFirst = false;
+            }
+
+            builder.appendCell(sb.toString());
+        }
+    }
+}
diff --git a/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/PropertyEditorModification.java b/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/PropertyEditorModification.java
new file mode 100644
index 0000000..e7230e5
--- /dev/null
+++ b/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/PropertyEditorModification.java
@@ -0,0 +1,196 @@
+/*
+ * 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.
+ *      Portions Copyright 2014 ForgeRock AS
+ */
+package org.forgerock.opendj.config.dsconfig;
+
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import org.forgerock.opendj.config.PropertyDefinition;
+
+/**
+ * This class is a data structure that can be used as an interface between PropertyValueEditor and the different
+ * handlers that call it. Since PropertyValueEditor is not aware of the different options used by the handlers it cannot
+ * directly construct a CommandBuilder, but PropertyValueEditor knows about the different changes (set, reset, delete)
+ * that can be performed against the ManagedObject and these changes can be used to generate a CommandBuilder.
+ *
+ * @param <T>
+ *            The type of the underlying property associated with the modification.
+ */
+final class PropertyEditorModification<T> {
+    /**
+     * The enumeration that describes the different types of modifications that we can have.
+     */
+    enum Type {
+        /** The user chose to set values. */
+        SET,
+        /** The user chose to reset values. */
+        RESET,
+        /** The user chose to add values. */
+        ADD,
+        /** The user chose to delete values. */
+        REMOVE
+    };
+
+    private PropertyDefinition<T> propertyDefinition;
+    private Type type;
+    private SortedSet<T> values;
+    private SortedSet<T> originalValues;
+
+    /**
+     * The private constructor of the PropertyEditorModification.
+     *
+     * @param propertyDefinition
+     *            the property definition associated with the modification.
+     * @param type
+     *            the type of the modification.
+     * @param values
+     *            the values associated with the modifications.
+     * @param originalValues
+     *            the original values of the property we are modifying.
+     */
+    private PropertyEditorModification(PropertyDefinition<T> propertyDefinition, Type type, SortedSet<T> values,
+            SortedSet<T> originalValues) {
+        this.propertyDefinition = propertyDefinition;
+        this.type = type;
+        this.values = new TreeSet<T>(values);
+        this.originalValues = new TreeSet<T>(originalValues);
+    }
+
+    /**
+     * Creates a reset modification.
+     *
+     * @param <T>
+     *            The type of the underlying property.
+     * @param propertyDefinition
+     *            the property that is modified.
+     * @param originalValues
+     *            the original values of the property.
+     * @return a reset modification for a given property.
+     */
+    static <T> PropertyEditorModification<T> createResetModification(PropertyDefinition<T> propertyDefinition,
+            SortedSet<T> originalValues) {
+        return new PropertyEditorModification<T>(propertyDefinition, Type.RESET, new TreeSet<T>(propertyDefinition),
+                originalValues);
+    }
+
+    /**
+     * Creates an add modification.
+     *
+     * @param <T>
+     *            The type of the underlying property.
+     * @param propertyDefinition
+     *            the property that is modified.
+     * @param addedValues
+     *            the values that are added in this modification.
+     * @param originalValues
+     *            the original values of the property.
+     * @return a reset modification for a given property.
+     */
+    static <T> PropertyEditorModification<T> createAddModification(PropertyDefinition<T> propertyDefinition,
+            SortedSet<T> addedValues, SortedSet<T> originalValues) {
+        return new PropertyEditorModification<T>(propertyDefinition, Type.ADD, addedValues, originalValues);
+    }
+
+    /**
+     * Creates a set modification.
+     *
+     * @param <T>
+     *            The type of the underlying property.
+     * @param propertyDefinition
+     *            the property that is modified.
+     * @param newValues
+     *            the new values for the property.
+     * @param originalValues
+     *            the original values of the property.
+     * @return a reset modification for a given property.
+     */
+    static <T> PropertyEditorModification<T> createSetModification(PropertyDefinition<T> propertyDefinition,
+            SortedSet<T> newValues, SortedSet<T> originalValues) {
+        return new PropertyEditorModification<T>(propertyDefinition, Type.SET, newValues, originalValues);
+    }
+
+    /**
+     * Creates a remove modification.
+     *
+     * @param <T>
+     *            The type of the underlying property.
+     * @param propertyDefinition
+     *            the property that is modified.
+     * @param removedValues
+     *            the values that are removed in this modification.
+     * @param originalValues
+     *            the original values of the property.
+     * @return a reset modification for a given property.
+     */
+    static <T> PropertyEditorModification<T> createRemoveModification(PropertyDefinition<T> propertyDefinition,
+            SortedSet<T> removedValues, SortedSet<T> originalValues) {
+        return new PropertyEditorModification<T>(propertyDefinition, Type.REMOVE, removedValues, originalValues);
+    }
+
+    /**
+     * Retuns the property definition associated with this modification.
+     *
+     * @return the property definition associated with this modification.
+     */
+    PropertyDefinition<T> getPropertyDefinition() {
+        return propertyDefinition;
+    }
+
+    /**
+     * Returns the type of the modification.
+     *
+     * @return the type of the modification.
+     */
+    Type getType() {
+        return type;
+    }
+
+    /**
+     * Returns the specific values associated with the modification.
+     *
+     * @return the specific values associated with the modification.
+     */
+    SortedSet<T> getModificationValues() {
+        return values;
+    }
+
+    /**
+     * Returns the original values associated with the property.
+     *
+     * @return the original values associated with the property.
+     */
+    SortedSet<T> getOriginalValues() {
+        return originalValues;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public String toString() {
+        return "Property name: " + getPropertyDefinition() + "\nMod type: " + getType() + "\nMod values: "
+                + getModificationValues() + "\nOriginal values: " + getOriginalValues();
+    }
+}
diff --git a/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/PropertyValueEditor.java b/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/PropertyValueEditor.java
new file mode 100644
index 0000000..0742ca2
--- /dev/null
+++ b/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/PropertyValueEditor.java
@@ -0,0 +1,2319 @@
+/*
+ * 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-2010 Sun Microsystems, Inc.
+ *      Portions Copyright 2013-2014 ForgeRock AS
+ */
+package org.forgerock.opendj.config.dsconfig;
+
+import static com.forgerock.opendj.dsconfig.DsconfigMessages.*;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.i18n.LocalizableMessageBuilder;
+import org.forgerock.opendj.config.AbsoluteInheritedDefaultBehaviorProvider;
+import org.forgerock.opendj.config.AbstractManagedObjectDefinition;
+import org.forgerock.opendj.config.AggregationPropertyDefinition;
+import org.forgerock.opendj.config.AliasDefaultBehaviorProvider;
+import org.forgerock.opendj.config.BooleanPropertyDefinition;
+import org.forgerock.opendj.config.Configuration;
+import org.forgerock.opendj.config.ConfigurationClient;
+import org.forgerock.opendj.config.DefaultBehaviorProviderVisitor;
+import org.forgerock.opendj.config.DefinedDefaultBehaviorProvider;
+import org.forgerock.opendj.config.DefinitionDecodingException;
+import org.forgerock.opendj.config.EnumPropertyDefinition;
+import org.forgerock.opendj.config.InstantiableRelationDefinition;
+import org.forgerock.opendj.config.ManagedObjectDefinition;
+import org.forgerock.opendj.config.ManagedObjectNotFoundException;
+import org.forgerock.opendj.config.ManagedObjectPath;
+import org.forgerock.opendj.config.PropertyDefinition;
+import org.forgerock.opendj.config.PropertyDefinitionUsageBuilder;
+import org.forgerock.opendj.config.PropertyDefinitionVisitor;
+import org.forgerock.opendj.config.PropertyException;
+import org.forgerock.opendj.config.PropertyOption;
+import org.forgerock.opendj.config.RelativeInheritedDefaultBehaviorProvider;
+import org.forgerock.opendj.config.UndefinedDefaultBehaviorProvider;
+import org.forgerock.opendj.config.client.ManagedObject;
+import org.forgerock.opendj.config.client.ManagedObjectDecodingException;
+import org.forgerock.opendj.config.client.ManagementContext;
+import org.forgerock.opendj.ldap.AuthorizationException;
+import org.forgerock.opendj.ldap.ErrorResultException;
+import org.forgerock.util.Reject;
+
+import com.forgerock.opendj.cli.ClientException;
+import com.forgerock.opendj.cli.ConsoleApplication;
+import com.forgerock.opendj.cli.HelpCallback;
+import com.forgerock.opendj.cli.Menu;
+import com.forgerock.opendj.cli.MenuBuilder;
+import com.forgerock.opendj.cli.MenuCallback;
+import com.forgerock.opendj.cli.MenuResult;
+import com.forgerock.opendj.cli.ReturnCode;
+import com.forgerock.opendj.cli.TableBuilder;
+import com.forgerock.opendj.cli.TextTablePrinter;
+
+/**
+ * Common methods used for interactively editing properties.
+ */
+final class PropertyValueEditor {
+
+    /**
+     * A menu call-back which can be used to dynamically create new components when configuring aggregation based
+     * properties.
+     */
+    private final class CreateComponentCallback<C extends ConfigurationClient, S extends Configuration> implements
+            MenuCallback<String> {
+
+        /** The aggregation property definition. */
+        private final AggregationPropertyDefinition<C, S> pd;
+
+        /**
+         * Creates a new component create call-back for the provided aggregation property definition.
+         */
+        private CreateComponentCallback(AggregationPropertyDefinition<C, S> pd) {
+            this.pd = pd;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public MenuResult<String> invoke(ConsoleApplication app) throws ClientException {
+            try {
+                // First get the parent managed object.
+                InstantiableRelationDefinition<?, ?> rd = pd.getRelationDefinition();
+                ManagedObjectPath<?, ?> path = pd.getParentPath();
+                LocalizableMessage ufn = rd.getUserFriendlyName();
+
+                ManagedObject<?> parent;
+                try {
+                    parent = context.getManagedObject(path);
+                } catch (AuthorizationException e) {
+                    LocalizableMessage msg = ERR_DSCFG_ERROR_CREATE_AUTHZ.get(ufn);
+                    throw new ClientException(ReturnCode.INSUFFICIENT_ACCESS_RIGHTS, msg);
+                } catch (DefinitionDecodingException e) {
+                    LocalizableMessage pufn = path.getManagedObjectDefinition().getUserFriendlyName();
+                    LocalizableMessage msg = ERR_DSCFG_ERROR_GET_PARENT_DDE.get(pufn, pufn, pufn);
+                    throw new ClientException(ReturnCode.OTHER, msg);
+                } catch (ManagedObjectDecodingException e) {
+                    LocalizableMessage pufn = path.getManagedObjectDefinition().getUserFriendlyName();
+                    LocalizableMessage msg = ERR_DSCFG_ERROR_GET_PARENT_MODE.get(pufn);
+                    throw new ClientException(ReturnCode.OTHER, msg, e);
+                } catch (ErrorResultException e) {
+                    LocalizableMessage msg = ERR_DSCFG_ERROR_CREATE_CE.get(ufn, e.getMessage());
+                    throw new ClientException(ReturnCode.CLIENT_SIDE_SERVER_DOWN, msg);
+                } catch (ManagedObjectNotFoundException e) {
+                    LocalizableMessage pufn = path.getManagedObjectDefinition().getUserFriendlyName();
+                    LocalizableMessage msg = ERR_DSCFG_ERROR_GET_PARENT_MONFE.get(pufn);
+                    if (app.isInteractive()) {
+                        app.println();
+                        app.printVerboseMessage(msg);
+                        return MenuResult.cancel();
+                    } else {
+                        throw new ClientException(ReturnCode.NO_SUCH_OBJECT, msg);
+                    }
+                }
+
+                // Now let the user create the child component.
+                app.println();
+                app.println();
+                return CreateSubCommandHandler.createManagedObject(app, context, parent, rd);
+            } catch (ClientException e) {
+                // FIXME: should really do something better with the exception
+                // handling here. For example, if a authz or communications
+                // exception occurs then the application should exit.
+                app.println();
+                app.println(e.getMessageObject());
+                app.println();
+                app.pressReturnToContinue();
+                return MenuResult.cancel();
+            }
+        }
+    }
+
+    /**
+     * A help call-back which displays a description and summary of a component and its properties.
+     */
+    private static final class ComponentHelpCallback implements HelpCallback {
+
+        /** The managed object being edited. */
+        private final ManagedObject<?> mo;
+
+        /** The properties that can be edited. */
+        private final Collection<PropertyDefinition<?>> properties;
+
+        /** Creates a new component helper for the specified property. */
+        private ComponentHelpCallback(ManagedObject<?> mo, Collection<PropertyDefinition<?>> c) {
+            this.mo = mo;
+            this.properties = c;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void display(ConsoleApplication app) {
+            app.println();
+            HelpSubCommandHandler.displaySingleComponent(app, mo, properties);
+            app.println();
+            app.pressReturnToContinue();
+        }
+    }
+
+    /**
+     * A simple interface for querying and retrieving common default behavior properties.
+     */
+    private static final class DefaultBehaviorQuery<T> {
+
+        /**
+         * The type of default behavior.
+         */
+        private enum Type {
+            /**
+             * Alias default behavior.
+             */
+            ALIAS,
+
+            /**
+             * Defined default behavior.
+             */
+            DEFINED,
+
+            /**
+             * Inherited default behavior.
+             */
+            INHERITED,
+
+            /**
+             * Undefined default behavior.
+             */
+            UNDEFINED;
+        }
+
+        /**
+         * Create a new default behavior query object based on the provied property definition.
+         *
+         * @param <T>
+         *            The type of property definition.
+         * @param pd
+         *            The property definition.
+         * @return The default behavior query object.
+         */
+        public static <T> DefaultBehaviorQuery<T> query(PropertyDefinition<T> pd) {
+            DefaultBehaviorProviderVisitor<T, DefaultBehaviorQuery<T>, PropertyDefinition<T>> visitor
+                = new DefaultBehaviorProviderVisitor<T, DefaultBehaviorQuery<T>, PropertyDefinition<T>>() {
+
+                    /** {@inheritDoc} */
+                    @Override
+                    public DefaultBehaviorQuery<T> visitAbsoluteInherited(AbsoluteInheritedDefaultBehaviorProvider<T> d,
+                            PropertyDefinition<T> p) {
+                        AbstractManagedObjectDefinition<?, ?> mod = d.getManagedObjectDefinition();
+                        String propertyName = d.getPropertyName();
+                        PropertyDefinition<?> pd2 = mod.getPropertyDefinition(propertyName);
+
+                        DefaultBehaviorQuery<?> query = query(pd2);
+                        return new DefaultBehaviorQuery<T>(Type.INHERITED, query.getAliasDescription());
+                    }
+
+                    /** {@inheritDoc} */
+                    @Override
+                    public DefaultBehaviorQuery<T> visitAlias(AliasDefaultBehaviorProvider<T> d,
+                            PropertyDefinition<T> p) {
+                        return new DefaultBehaviorQuery<T>(Type.ALIAS, d.getSynopsis());
+                    }
+
+                    /** {@inheritDoc} */
+                    @Override
+                    public DefaultBehaviorQuery<T> visitDefined(DefinedDefaultBehaviorProvider<T> d,
+                            PropertyDefinition<T> p) {
+                        return new DefaultBehaviorQuery<T>(Type.DEFINED, null);
+                    }
+
+                    /** {@inheritDoc} */
+                    @Override
+                    public DefaultBehaviorQuery<T> visitRelativeInherited(RelativeInheritedDefaultBehaviorProvider<T> d,
+                            PropertyDefinition<T> p) {
+                        AbstractManagedObjectDefinition<?, ?> mod = d.getManagedObjectDefinition();
+                        String propertyName = d.getPropertyName();
+                        PropertyDefinition<?> pd2 = mod.getPropertyDefinition(propertyName);
+
+                        DefaultBehaviorQuery<?> query = query(pd2);
+                        return new DefaultBehaviorQuery<T>(Type.INHERITED, query.getAliasDescription());
+                    }
+
+                    /** {@inheritDoc} */
+                    @Override
+                    public DefaultBehaviorQuery<T> visitUndefined(UndefinedDefaultBehaviorProvider<T> d,
+                            PropertyDefinition<T> p) {
+                        return new DefaultBehaviorQuery<T>(Type.UNDEFINED, null);
+                    }
+                };
+
+            return pd.getDefaultBehaviorProvider().accept(visitor, pd);
+        }
+
+        /**
+         * The description of the behavior if it is an alias default behavior.
+         */
+        private final LocalizableMessage aliasDescription;
+
+        /** The type of behavior. */
+        private final Type type;
+
+        /** Private constructor. */
+        private DefaultBehaviorQuery(Type type, LocalizableMessage aliasDescription) {
+            this.type = type;
+            this.aliasDescription = aliasDescription;
+        }
+
+        /**
+         * Gets the detailed description of this default behavior if it is an alias default behavior or if it inherits
+         * from an alias default behavior.
+         *
+         * @return Returns the detailed description of this default behavior if it is an alias default behavior or if it
+         *         inherits from an alias default behavior, otherwise <code>null</code>.
+         */
+        public LocalizableMessage getAliasDescription() {
+            return aliasDescription;
+        }
+
+        /**
+         * Determines whether or not the default behavior is alias.
+         *
+         * @return Returns <code>true</code> if the default behavior is alias.
+         */
+        public boolean isAlias() {
+            return type == Type.ALIAS;
+        }
+
+        /**
+         * Determines whether or not the default behavior is defined.
+         *
+         * @return Returns <code>true</code> if the default behavior is defined.
+         */
+        public boolean isDefined() {
+            return type == Type.DEFINED;
+        }
+
+        /**
+         * Determines whether or not the default behavior is inherited.
+         *
+         * @return Returns <code>true</code> if the default behavior is inherited.
+         */
+        public boolean isInherited() {
+            return type == Type.INHERITED;
+        }
+
+        /**
+         * Determines whether or not the default behavior is undefined.
+         *
+         * @return Returns <code>true</code> if the default behavior is undefined.
+         */
+        public boolean isUndefined() {
+            return type == Type.UNDEFINED;
+        }
+
+    }
+
+    /**
+     * A property definition visitor which initializes mandatory properties.
+     */
+    private final class MandatoryPropertyInitializer extends PropertyDefinitionVisitor<MenuResult<Void>, Void>
+            implements MenuCallback<Void> {
+
+        /** Any exception that was caught during processing. */
+        private ClientException e = null;
+
+        /** The managed object being edited. */
+        private final ManagedObject<?> mo;
+
+        /** The property to be edited. */
+        private final PropertyDefinition<?> pd;
+
+        /** Creates a new property editor for the specified property. */
+        private MandatoryPropertyInitializer(ManagedObject<?> mo, PropertyDefinition<?> pd) {
+            this.mo = mo;
+            this.pd = pd;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public MenuResult<Void> invoke(ConsoleApplication app) throws ClientException {
+            displayPropertyHeader(app, pd);
+
+            MenuResult<Void> result = pd.accept(this, null);
+
+            if (e != null) {
+                throw e;
+            }
+            return result;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public <C extends ConfigurationClient, S extends Configuration> MenuResult<Void> visitAggregation(
+                AggregationPropertyDefinition<C, S> d, Void p) {
+            MenuBuilder<String> builder = new MenuBuilder<String>(app);
+            builder.setMultipleColumnThreshold(MULTI_COLUMN_THRESHOLD);
+
+            InstantiableRelationDefinition<C, S> rd = d.getRelationDefinition();
+            if (d.hasOption(PropertyOption.MULTI_VALUED)) {
+                builder.setPrompt(INFO_EDITOR_PROMPT_SELECT_COMPONENT_MULTI.get(rd.getUserFriendlyPluralName(),
+                        d.getName()));
+                builder.setAllowMultiSelect(true);
+            } else {
+                builder.setPrompt(
+                        INFO_EDITOR_PROMPT_SELECT_COMPONENT_SINGLE.get(rd.getUserFriendlyName(), d.getName()));
+            }
+
+            // Create a list of possible names.
+            Set<String> values = new TreeSet<String>(d);
+            ManagedObjectPath<?, ?> path = d.getParentPath();
+            try {
+                values.addAll(Arrays.asList(context.listManagedObjects(path, rd)));
+            } catch (AuthorizationException e) {
+                this.e = new ClientException(
+                        ReturnCode.CLIENT_SIDE_PARAM_ERROR, LocalizableMessage.raw(e.getMessage()));
+                return MenuResult.quit();
+            } catch (ManagedObjectNotFoundException e) {
+                this.e = new ClientException(ReturnCode.NO_SUCH_OBJECT, e.getMessageObject());
+                return MenuResult.cancel();
+            } catch (ErrorResultException e) {
+                this.e = new ClientException(ReturnCode.APPLICATION_ERROR, LocalizableMessage.raw(e.getMessage()));
+                return MenuResult.quit();
+            }
+
+            for (String value : values) {
+                LocalizableMessage option = getPropertyValues(d, Collections.singleton(value));
+                builder.addNumberedOption(option, MenuResult.success(value));
+            }
+            MenuCallback<String> callback = new CreateComponentCallback<C, S>(d);
+            builder.addNumberedOption(
+                    INFO_EDITOR_OPTION_CREATE_A_NEW_COMPONENT.get(rd.getUserFriendlyName()), callback);
+
+            builder.addHelpOption(new PropertyHelpCallback(mo.getManagedObjectDefinition(), d));
+            if (app.isMenuDrivenMode()) {
+                builder.addCancelOption(true);
+            }
+            builder.addQuitOption();
+
+            Menu<String> menu = builder.toMenu();
+            try {
+                app.println();
+                MenuResult<String> result = menu.run();
+
+                if (result.isQuit()) {
+                    return MenuResult.quit();
+                } else if (result.isCancel()) {
+                    return MenuResult.cancel();
+                } else {
+                    Collection<String> newValues = result.getValues();
+                    SortedSet<String> oldValues = new TreeSet<String>(mo.getPropertyValues(d));
+                    mo.setPropertyValues(d, newValues);
+                    isLastChoiceReset = false;
+                    registerModification(d, new TreeSet<String>(newValues), oldValues);
+                    return MenuResult.success();
+                }
+            } catch (ClientException e) {
+                this.e = e;
+                return MenuResult.cancel();
+            }
+        }
+
+        /**
+         * /** {@inheritDoc}
+         */
+        @Override
+        public MenuResult<Void> visitBoolean(BooleanPropertyDefinition d, Void p) {
+            MenuBuilder<Boolean> builder = new MenuBuilder<Boolean>(app);
+
+            builder.setPrompt(INFO_EDITOR_PROMPT_SELECT_VALUE_SINGLE.get(d.getName()));
+
+            builder.addNumberedOption(INFO_VALUE_TRUE.get(), MenuResult.success(true));
+            builder.addNumberedOption(INFO_VALUE_FALSE.get(), MenuResult.success(false));
+
+            builder.addHelpOption(new PropertyHelpCallback(mo.getManagedObjectDefinition(), d));
+            if (app.isMenuDrivenMode()) {
+                builder.addCancelOption(true);
+            }
+            builder.addQuitOption();
+
+            Menu<Boolean> menu = builder.toMenu();
+            try {
+                app.println();
+                MenuResult<Boolean> result = menu.run();
+
+                if (result.isQuit()) {
+                    return MenuResult.quit();
+                } else if (result.isCancel()) {
+                    return MenuResult.cancel();
+                } else {
+                    Collection<Boolean> newValues = result.getValues();
+                    SortedSet<Boolean> oldValues = new TreeSet<Boolean>(mo.getPropertyValues(d));
+                    mo.setPropertyValues(d, newValues);
+                    isLastChoiceReset = false;
+                    registerModification(d, new TreeSet<Boolean>(newValues), oldValues);
+                    return MenuResult.success();
+                }
+            } catch (ClientException e) {
+                this.e = e;
+                return MenuResult.cancel();
+            }
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public <E extends Enum<E>> MenuResult<Void> visitEnum(EnumPropertyDefinition<E> d, Void x) {
+            MenuBuilder<E> builder = new MenuBuilder<E>(app);
+            builder.setMultipleColumnThreshold(MULTI_COLUMN_THRESHOLD);
+
+            if (d.hasOption(PropertyOption.MULTI_VALUED)) {
+                builder.setPrompt(INFO_EDITOR_PROMPT_SELECT_VALUE_MULTI.get(d.getName()));
+                builder.setAllowMultiSelect(true);
+            } else {
+                builder.setPrompt(INFO_EDITOR_PROMPT_SELECT_VALUE_SINGLE.get(d.getName()));
+            }
+
+            Set<E> values = new TreeSet<E>(d);
+            values.addAll(EnumSet.allOf(d.getEnumClass()));
+            for (E value : values) {
+                LocalizableMessage option = getPropertyValues(d, Collections.singleton(value));
+                builder.addNumberedOption(option, MenuResult.success(value));
+            }
+
+            builder.addHelpOption(new PropertyHelpCallback(mo.getManagedObjectDefinition(), d));
+            if (app.isMenuDrivenMode()) {
+                builder.addCancelOption(true);
+            }
+            builder.addQuitOption();
+
+            Menu<E> menu = builder.toMenu();
+            try {
+                app.println();
+                MenuResult<E> result = menu.run();
+
+                if (result.isQuit()) {
+                    return MenuResult.quit();
+                } else if (result.isCancel()) {
+                    return MenuResult.cancel();
+                } else {
+                    Collection<E> newValues = result.getValues();
+                    SortedSet<E> oldValues = new TreeSet<E>(mo.getPropertyValues(d));
+                    mo.setPropertyValues(d, newValues);
+                    isLastChoiceReset = false;
+                    registerModification(d, new TreeSet<E>(newValues), oldValues);
+                    return MenuResult.success();
+                }
+            } catch (ClientException e) {
+                this.e = e;
+                return MenuResult.cancel();
+            }
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public <T> MenuResult<Void> visitUnknown(PropertyDefinition<T> d, Void p) {
+            app.println();
+            displayPropertySyntax(app, d);
+
+            // Set the new property value(s).
+            try {
+                SortedSet<T> values = readPropertyValues(app, mo.getManagedObjectDefinition(), d);
+                SortedSet<T> oldValues = new TreeSet<T>(mo.getPropertyValues(d));
+                mo.setPropertyValues(d, values);
+                isLastChoiceReset = false;
+                registerModification(d, values, oldValues);
+                return MenuResult.success();
+            } catch (ClientException e) {
+                this.e = e;
+                return MenuResult.cancel();
+            }
+        }
+
+    }
+
+    /**
+     * A menu call-back for editing a modifiable multi-valued property.
+     */
+    private final class MultiValuedPropertyEditor extends PropertyDefinitionVisitor<MenuResult<Boolean>, Void>
+            implements MenuCallback<Boolean> {
+
+        /** Any exception that was caught during processing. */
+        private ClientException e = null;
+
+        /** The managed object being edited. */
+        private final ManagedObject<?> mo;
+
+        /** The property to be edited. */
+        private final PropertyDefinition<?> pd;
+
+        /** Creates a new property editor for the specified property. */
+        private MultiValuedPropertyEditor(ManagedObject<?> mo, PropertyDefinition<?> pd) {
+            Reject.ifFalse(pd.hasOption(PropertyOption.MULTI_VALUED));
+
+            this.mo = mo;
+            this.pd = pd;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public MenuResult<Boolean> invoke(ConsoleApplication app) throws ClientException {
+            displayPropertyHeader(app, pd);
+
+            MenuResult<Boolean> result = pd.accept(this, null);
+            if (e != null) {
+                throw e;
+            }
+            return result;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public <C extends ConfigurationClient, S extends Configuration> MenuResult<Boolean> visitAggregation(
+                final AggregationPropertyDefinition<C, S> d, Void p) {
+            final SortedSet<String> defaultValues = mo.getPropertyDefaultValues(d);
+            final SortedSet<String> oldValues = mo.getPropertyValues(d);
+            final SortedSet<String> currentValues = mo.getPropertyValues(d);
+            final InstantiableRelationDefinition<C, S> rd = d.getRelationDefinition();
+            final LocalizableMessage ufpn = rd.getUserFriendlyPluralName();
+
+            boolean isFirst = true;
+            while (true) {
+                if (!isFirst) {
+                    app.println();
+                    app.println(INFO_EDITOR_HEADING_CONFIGURE_PROPERTY_CONT.get(d.getName()));
+                } else {
+                    isFirst = false;
+                }
+
+                if (currentValues.size() > 1) {
+                    app.println();
+                    app.println(INFO_EDITOR_HEADING_COMPONENT_SUMMARY.get(d.getName(), ufpn));
+                    app.println();
+                    displayPropertyValues(app, d, currentValues);
+                }
+
+                // Create a list of possible names.
+                final Set<String> values = new TreeSet<String>(d);
+                ManagedObjectPath<?, ?> path = d.getParentPath();
+                try {
+                    values.addAll(Arrays.asList(context.listManagedObjects(path, rd)));
+                } catch (AuthorizationException e) {
+                    this.e = new ClientException(ReturnCode.CLIENT_SIDE_PARAM_ERROR, LocalizableMessage.raw(e
+                            .getMessage()));
+                    return MenuResult.quit();
+                } catch (ManagedObjectNotFoundException e) {
+                    this.e = new ClientException(ReturnCode.NO_SUCH_OBJECT, e.getMessageObject());
+                    return MenuResult.cancel();
+                } catch (ErrorResultException e) {
+                    this.e = new ClientException(ReturnCode.APPLICATION_ERROR, LocalizableMessage.raw(e.getMessage()));
+                    return MenuResult.quit();
+                }
+
+                // Create the add values call-back.
+                MenuCallback<Boolean> addCallback = null;
+                values.removeAll(currentValues);
+                if (!values.isEmpty()) {
+                    addCallback = new MenuCallback<Boolean>() {
+
+                        @Override
+                        public MenuResult<Boolean> invoke(ConsoleApplication app) throws ClientException {
+                            MenuBuilder<String> builder = new MenuBuilder<String>(app);
+
+                            builder.setPrompt(INFO_EDITOR_PROMPT_SELECT_COMPONENTS_ADD.get(ufpn));
+                            builder.setAllowMultiSelect(true);
+                            builder.setMultipleColumnThreshold(MULTI_COLUMN_THRESHOLD);
+
+                            for (String value : values) {
+                                LocalizableMessage svalue = getPropertyValues(d, Collections.singleton(value));
+                                builder.addNumberedOption(svalue, MenuResult.success(value));
+                            }
+                            MenuCallback<String> callback = new CreateComponentCallback<C, S>(d);
+                            builder.addNumberedOption(
+                                    INFO_EDITOR_OPTION_CREATE_A_NEW_COMPONENT.get(rd.getUserFriendlyName()), callback);
+
+                            if (values.size() > 1) {
+                                // No point in having this option if there's only one
+                                // possible value.
+                                builder.addNumberedOption(INFO_EDITOR_OPTION_ADD_ALL_COMPONENTS.get(ufpn),
+                                        MenuResult.success(values));
+                            }
+
+                            builder.addHelpOption(new PropertyHelpCallback(mo.getManagedObjectDefinition(), d));
+
+                            builder.addCancelOption(true);
+                            builder.addQuitOption();
+
+                            app.println();
+                            app.println();
+                            Menu<String> menu = builder.toMenu();
+                            MenuResult<String> result = menu.run();
+
+                            if (result.isSuccess()) {
+                                // Set the new property value(s).
+                                Collection<String> addedValues = result.getValues();
+                                currentValues.addAll(addedValues);
+
+                                isLastChoiceReset = false;
+                                app.println();
+                                app.pressReturnToContinue();
+                                return MenuResult.success(false);
+                            } else if (result.isCancel()) {
+                                app.println();
+                                app.pressReturnToContinue();
+                                return MenuResult.success(false);
+                            } else {
+                                return MenuResult.quit();
+                            }
+                        }
+
+                    };
+                }
+
+                // Create the remove values call-back.
+                MenuCallback<Boolean> removeCallback = new MenuCallback<Boolean>() {
+
+                    @Override
+                    public MenuResult<Boolean> invoke(ConsoleApplication app) throws ClientException {
+                        MenuBuilder<String> builder = new MenuBuilder<String>(app);
+
+                        builder.setPrompt(INFO_EDITOR_PROMPT_SELECT_COMPONENTS_REMOVE.get(ufpn));
+                        builder.setAllowMultiSelect(true);
+                        builder.setMultipleColumnThreshold(MULTI_COLUMN_THRESHOLD);
+
+                        for (String value : currentValues) {
+                            LocalizableMessage svalue = getPropertyValues(d, Collections.singleton(value));
+                            builder.addNumberedOption(svalue, MenuResult.success(value));
+                        }
+
+                        builder.addHelpOption(new PropertyHelpCallback(mo.getManagedObjectDefinition(), d));
+
+                        builder.addCancelOption(true);
+                        builder.addQuitOption();
+
+                        app.println();
+                        app.println();
+                        Menu<String> menu = builder.toMenu();
+                        MenuResult<String> result = menu.run();
+
+                        if (result.isSuccess()) {
+                            // Set the new property value(s).
+                            Collection<String> removedValues = result.getValues();
+                            currentValues.removeAll(removedValues);
+                            isLastChoiceReset = false;
+                            app.println();
+                            app.pressReturnToContinue();
+                            return MenuResult.success(false);
+                        } else if (result.isCancel()) {
+                            app.println();
+                            app.pressReturnToContinue();
+                            return MenuResult.success(false);
+                        } else {
+                            return MenuResult.quit();
+                        }
+                    }
+
+                };
+
+                MenuResult<Boolean> result = runMenu(d, app, defaultValues, oldValues, currentValues, addCallback,
+                        removeCallback);
+                if (!result.isAgain()) {
+                    return result;
+                }
+            }
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public <T extends Enum<T>> MenuResult<Boolean> visitEnum(final EnumPropertyDefinition<T> d, Void p) {
+            final SortedSet<T> defaultValues = mo.getPropertyDefaultValues(d);
+            final SortedSet<T> oldValues = mo.getPropertyValues(d);
+            final SortedSet<T> currentValues = mo.getPropertyValues(d);
+
+            boolean isFirst = true;
+            while (true) {
+                if (!isFirst) {
+                    app.println();
+                    app.println(INFO_EDITOR_HEADING_CONFIGURE_PROPERTY_CONT.get(d.getName()));
+                } else {
+                    isFirst = false;
+                }
+
+                if (currentValues.size() > 1) {
+                    app.println();
+                    app.println(INFO_EDITOR_HEADING_VALUES_SUMMARY.get(d.getName()));
+                    app.println();
+                    displayPropertyValues(app, d, currentValues);
+                }
+
+                // Create the add values call-back.
+                MenuCallback<Boolean> addCallback = null;
+
+                final EnumSet<T> values = EnumSet.allOf(d.getEnumClass());
+                values.removeAll(currentValues);
+
+                if (!values.isEmpty()) {
+                    addCallback = new MenuCallback<Boolean>() {
+
+                        @Override
+                        public MenuResult<Boolean> invoke(ConsoleApplication app) throws ClientException {
+                            MenuBuilder<T> builder = new MenuBuilder<T>(app);
+
+                            builder.setPrompt(INFO_EDITOR_PROMPT_SELECT_VALUES_ADD.get());
+                            builder.setAllowMultiSelect(true);
+                            builder.setMultipleColumnThreshold(MULTI_COLUMN_THRESHOLD);
+
+                            for (T value : values) {
+                                LocalizableMessage svalue = getPropertyValues(d, Collections.singleton(value));
+                                builder.addNumberedOption(svalue, MenuResult.success(value));
+                            }
+
+                            if (values.size() > 1) {
+                                // No point in having this option if there's only one
+                                // possible value.
+                                builder.addNumberedOption(INFO_EDITOR_OPTION_ADD_ALL_VALUES.get(),
+                                        MenuResult.success(values));
+                            }
+
+                            builder.addHelpOption(new PropertyHelpCallback(mo.getManagedObjectDefinition(), d));
+
+                            builder.addCancelOption(true);
+                            builder.addQuitOption();
+
+                            app.println();
+                            app.println();
+                            Menu<T> menu = builder.toMenu();
+                            MenuResult<T> result = menu.run();
+
+                            if (result.isSuccess()) {
+
+                                // Set the new property value(s).
+                                Collection<T> addedValues = result.getValues();
+                                currentValues.addAll(addedValues);
+                                isLastChoiceReset = false;
+                                app.println();
+                                app.pressReturnToContinue();
+                                return MenuResult.success(false);
+                            } else if (result.isCancel()) {
+                                app.println();
+                                app.pressReturnToContinue();
+                                return MenuResult.success(false);
+                            } else {
+                                return MenuResult.quit();
+                            }
+                        }
+
+                    };
+                }
+
+                // Create the remove values call-back.
+                MenuCallback<Boolean> removeCallback = new MenuCallback<Boolean>() {
+
+                    @Override
+                    public MenuResult<Boolean> invoke(ConsoleApplication app) throws ClientException {
+                        MenuBuilder<T> builder = new MenuBuilder<T>(app);
+
+                        builder.setPrompt(INFO_EDITOR_PROMPT_SELECT_VALUES_REMOVE.get());
+                        builder.setAllowMultiSelect(true);
+                        builder.setMultipleColumnThreshold(MULTI_COLUMN_THRESHOLD);
+
+                        for (T value : currentValues) {
+                            LocalizableMessage svalue = getPropertyValues(d, Collections.singleton(value));
+                            builder.addNumberedOption(svalue, MenuResult.success(value));
+                        }
+
+                        builder.addHelpOption(new PropertyHelpCallback(mo.getManagedObjectDefinition(), d));
+
+                        builder.addCancelOption(true);
+                        builder.addQuitOption();
+
+                        app.println();
+                        app.println();
+                        Menu<T> menu = builder.toMenu();
+                        MenuResult<T> result = menu.run();
+
+                        if (result.isSuccess()) {
+                            // Set the new property value(s).
+                            Collection<T> removedValues = result.getValues();
+                            currentValues.removeAll(removedValues);
+                            isLastChoiceReset = false;
+                            app.println();
+                            app.pressReturnToContinue();
+                            return MenuResult.success(false);
+                        } else if (result.isCancel()) {
+                            app.println();
+                            app.pressReturnToContinue();
+                            return MenuResult.success(false);
+                        } else {
+                            return MenuResult.quit();
+                        }
+                    }
+
+                };
+
+                MenuResult<Boolean> result = runMenu(d, app, defaultValues, oldValues, currentValues, addCallback,
+                        removeCallback);
+                if (!result.isAgain()) {
+                    return result;
+                }
+            }
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public <T> MenuResult<Boolean> visitUnknown(final PropertyDefinition<T> d, Void p) {
+            app.println();
+            displayPropertySyntax(app, d);
+
+            final SortedSet<T> defaultValues = mo.getPropertyDefaultValues(d);
+            final SortedSet<T> oldValues = mo.getPropertyValues(d);
+            final SortedSet<T> currentValues = mo.getPropertyValues(d);
+
+            boolean isFirst = true;
+            while (true) {
+                if (!isFirst) {
+                    app.println();
+                    app.println(INFO_EDITOR_HEADING_CONFIGURE_PROPERTY_CONT.get(d.getName()));
+                } else {
+                    isFirst = false;
+                }
+
+                if (currentValues.size() > 1) {
+                    app.println();
+                    app.println(INFO_EDITOR_HEADING_VALUES_SUMMARY.get(d.getName()));
+                    app.println();
+                    displayPropertyValues(app, d, currentValues);
+                }
+
+                // Create the add values call-back.
+                MenuCallback<Boolean> addCallback = new MenuCallback<Boolean>() {
+
+                    @Override
+                    public MenuResult<Boolean> invoke(ConsoleApplication app) throws ClientException {
+                        app.println();
+                        SortedSet<T> previousValues = new TreeSet<T>(currentValues);
+                        readPropertyValues(app, mo.getManagedObjectDefinition(), d, currentValues);
+                        SortedSet<T> addedValues = new TreeSet<T>(currentValues);
+                        addedValues.removeAll(previousValues);
+                        isLastChoiceReset = false;
+                        return MenuResult.success(false);
+                    }
+
+                };
+
+                // Create the remove values call-back.
+                MenuCallback<Boolean> removeCallback = new MenuCallback<Boolean>() {
+
+                    @Override
+                    public MenuResult<Boolean> invoke(ConsoleApplication app) throws ClientException {
+                        MenuBuilder<T> builder = new MenuBuilder<T>(app);
+
+                        builder.setPrompt(INFO_EDITOR_PROMPT_SELECT_VALUES_REMOVE.get());
+                        builder.setAllowMultiSelect(true);
+                        builder.setMultipleColumnThreshold(MULTI_COLUMN_THRESHOLD);
+
+                        for (T value : currentValues) {
+                            LocalizableMessage svalue = getPropertyValues(d, Collections.singleton(value));
+                            builder.addNumberedOption(svalue, MenuResult.success(value));
+                        }
+
+                        builder.addHelpOption(new PropertyHelpCallback(mo.getManagedObjectDefinition(), d));
+
+                        builder.addCancelOption(true);
+                        builder.addQuitOption();
+
+                        app.println();
+                        app.println();
+                        Menu<T> menu = builder.toMenu();
+                        MenuResult<T> result = menu.run();
+
+                        if (result.isSuccess()) {
+                            // Set the new property value(s).
+                            Collection<T> removedValues = result.getValues();
+                            currentValues.removeAll(removedValues);
+                            isLastChoiceReset = false;
+                            app.println();
+                            app.pressReturnToContinue();
+                            return MenuResult.success(false);
+                        } else if (result.isCancel()) {
+                            app.println();
+                            app.pressReturnToContinue();
+                            return MenuResult.success(false);
+                        } else {
+                            return MenuResult.quit();
+                        }
+                    }
+
+                };
+
+                MenuResult<Boolean> result = runMenu(d, app, defaultValues, oldValues, currentValues, addCallback,
+                        removeCallback);
+                if (!result.isAgain()) {
+                    return result;
+                }
+            }
+        }
+
+        /**
+         * Generate an appropriate menu option for a property which asks the user whether or not they want to keep the
+         * property's current settings.
+         */
+        private <T> LocalizableMessage getKeepDefaultValuesMenuOption(PropertyDefinition<T> pd,
+                SortedSet<T> defaultValues, SortedSet<T> oldValues, SortedSet<T> currentValues) {
+            DefaultBehaviorQuery<T> query = DefaultBehaviorQuery.query(pd);
+
+            boolean isModified = !currentValues.equals(oldValues);
+            boolean isDefault = currentValues.equals(defaultValues);
+
+            if (isModified) {
+                switch (currentValues.size()) {
+                case 0:
+                    if (query.isAlias()) {
+                        return INFO_EDITOR_OPTION_USE_DEFAULT_ALIAS.get(query.getAliasDescription());
+                    } else if (query.isInherited()) {
+                        if (query.getAliasDescription() != null) {
+                            return INFO_EDITOR_OPTION_USE_DEFAULT_INHERITED_ALIAS.get(query.getAliasDescription());
+                        } else {
+                            return INFO_EDITOR_OPTION_USE_DEFAULT_INHERITED_ALIAS_UNDEFINED.get();
+                        }
+                    } else {
+                        return INFO_EDITOR_OPTION_LEAVE_UNDEFINED.get();
+                    }
+                case 1:
+                    LocalizableMessage svalue = getPropertyValues(pd, currentValues);
+                    if (isDefault) {
+                        if (query.isInherited()) {
+                            return INFO_EDITOR_OPTION_USE_INHERITED_DEFAULT_VALUE.get(svalue);
+                        } else {
+                            return INFO_EDITOR_OPTION_USE_DEFAULT_VALUE.get(svalue);
+                        }
+                    } else {
+                        return INFO_EDITOR_OPTION_USE_VALUE.get(svalue);
+                    }
+                default:
+                    if (isDefault) {
+                        if (query.isInherited()) {
+                            return INFO_EDITOR_OPTION_USE_INHERITED_DEFAULT_VALUES.get();
+                        } else {
+                            return INFO_EDITOR_OPTION_USE_DEFAULT_VALUES.get();
+                        }
+                    } else {
+                        return INFO_EDITOR_OPTION_USE_VALUES.get();
+                    }
+                }
+            } else {
+                switch (currentValues.size()) {
+                case 0:
+                    if (query.isAlias()) {
+                        return INFO_EDITOR_OPTION_KEEP_DEFAULT_ALIAS.get(query.getAliasDescription());
+                    } else if (query.isInherited()) {
+                        if (query.getAliasDescription() != null) {
+                            return INFO_EDITOR_OPTION_KEEP_DEFAULT_INHERITED_ALIAS.get(query.getAliasDescription());
+                        } else {
+                            return INFO_EDITOR_OPTION_KEEP_DEFAULT_INHERITED_ALIAS_UNDEFINED.get();
+                        }
+                    } else {
+                        return INFO_EDITOR_OPTION_LEAVE_UNDEFINED.get();
+                    }
+                case 1:
+                    LocalizableMessage svalue = getPropertyValues(pd, currentValues);
+                    if (isDefault) {
+                        if (query.isInherited()) {
+                            return INFO_EDITOR_OPTION_KEEP_INHERITED_DEFAULT_VALUE.get(svalue);
+                        } else {
+                            return INFO_EDITOR_OPTION_KEEP_DEFAULT_VALUE.get(svalue);
+                        }
+                    } else {
+                        return INFO_EDITOR_OPTION_KEEP_VALUE.get(svalue);
+                    }
+                default:
+                    if (isDefault) {
+                        if (query.isInherited()) {
+                            return INFO_EDITOR_OPTION_KEEP_INHERITED_DEFAULT_VALUES.get();
+                        } else {
+                            return INFO_EDITOR_OPTION_KEEP_DEFAULT_VALUES.get();
+                        }
+                    } else {
+                        return INFO_EDITOR_OPTION_KEEP_VALUES.get();
+                    }
+                }
+            }
+        }
+
+        /**
+         * Generate an appropriate menu option which should be used in the case where a property can be reset to its
+         * default behavior.
+         */
+        private <T> LocalizableMessage getResetToDefaultValuesMenuOption(PropertyDefinition<T> pd,
+                SortedSet<T> defaultValues, SortedSet<T> currentValues) {
+            DefaultBehaviorQuery<T> query = DefaultBehaviorQuery.query(pd);
+            boolean isMandatory = pd.hasOption(PropertyOption.MANDATORY);
+
+            if (!isMandatory && query.isAlias()) {
+                return INFO_EDITOR_OPTION_RESET_DEFAULT_ALIAS.get(query.getAliasDescription());
+            } else if (query.isDefined()) {
+                // Only show this option if the current value is different
+                // to the default.
+                if (!currentValues.equals(defaultValues)) {
+                    LocalizableMessage svalue = getPropertyValues(pd, defaultValues);
+                    if (defaultValues.size() > 1) {
+                        return INFO_EDITOR_OPTION_RESET_DEFAULT_VALUES.get(svalue);
+                    } else {
+                        return INFO_EDITOR_OPTION_RESET_DEFAULT_VALUE.get(svalue);
+                    }
+                } else {
+                    return null;
+                }
+            } else if (!isMandatory && query.isInherited()) {
+                if (defaultValues.isEmpty()) {
+                    if (query.getAliasDescription() != null) {
+                        return INFO_EDITOR_OPTION_RESET_DEFAULT_INHERITED_ALIAS.get(query.getAliasDescription());
+                    } else {
+                        return INFO_EDITOR_OPTION_RESET_DEFAULT_INHERITED_ALIAS_UNDEFINED.get();
+                    }
+                } else {
+                    LocalizableMessage svalue = getPropertyValues(pd, defaultValues);
+                    if (defaultValues.size() > 1) {
+                        return INFO_EDITOR_OPTION_RESET_INHERITED_DEFAULT_VALUES.get(svalue);
+                    } else {
+                        return INFO_EDITOR_OPTION_RESET_INHERITED_DEFAULT_VALUE.get(svalue);
+                    }
+                }
+            } else if (!isMandatory && query.isUndefined()) {
+                return INFO_EDITOR_OPTION_LEAVE_UNDEFINED.get();
+            } else {
+                return null;
+            }
+        }
+
+        /** Common menu processing. */
+        private <T> MenuResult<Boolean> runMenu(final PropertyDefinition<T> d, ConsoleApplication app,
+                final SortedSet<T> defaultValues, final SortedSet<T> oldValues, final SortedSet<T> currentValues,
+                MenuCallback<Boolean> addCallback, MenuCallback<Boolean> removeCallback) {
+            // Construct a menu of actions.
+            MenuBuilder<Boolean> builder = new MenuBuilder<Boolean>(app);
+            builder.setPrompt(INFO_EDITOR_PROMPT_MODIFY_MENU.get(d.getName()));
+
+            // First option is for leaving the property unchanged or
+            // applying changes, but only if the state of the property is
+            // valid.
+            if (!(d.hasOption(PropertyOption.MANDATORY) && currentValues.isEmpty())) {
+                MenuResult<Boolean> result;
+                if (!oldValues.equals(currentValues)) {
+                    result = MenuResult.success(true);
+                } else {
+                    result = MenuResult.<Boolean> cancel();
+                }
+
+                LocalizableMessage option = getKeepDefaultValuesMenuOption(d, defaultValues, oldValues, currentValues);
+                builder.addNumberedOption(option, result);
+                builder.setDefault(LocalizableMessage.raw("1"), result);
+            }
+
+            // Add an option for adding some values.
+            if (addCallback != null) {
+                int i = builder.addNumberedOption(INFO_EDITOR_OPTION_ADD_ONE_OR_MORE_VALUES.get(), addCallback);
+                if (d.hasOption(PropertyOption.MANDATORY) && currentValues.isEmpty()) {
+                    builder.setDefault(LocalizableMessage.raw("%d", i), addCallback);
+                }
+            }
+
+            // Add options for removing values if applicable.
+            if (!currentValues.isEmpty()) {
+                builder.addNumberedOption(INFO_EDITOR_OPTION_REMOVE_ONE_OR_MORE_VALUES.get(), removeCallback);
+            }
+
+            // Add options for removing all values and for resetting the
+            // property to its default behavior.
+            LocalizableMessage resetOption = null;
+            if (!currentValues.equals(defaultValues)) {
+                resetOption = getResetToDefaultValuesMenuOption(d, defaultValues, currentValues);
+            }
+
+            if (!currentValues.isEmpty() && (resetOption == null || !defaultValues.isEmpty())) {
+                MenuCallback<Boolean> callback = new MenuCallback<Boolean>() {
+                    @Override
+                    public MenuResult<Boolean> invoke(ConsoleApplication app) throws ClientException {
+                        isLastChoiceReset = false;
+                        currentValues.clear();
+                        app.println();
+                        app.pressReturnToContinue();
+                        return MenuResult.success(false);
+                    }
+                };
+                builder.addNumberedOption(INFO_EDITOR_OPTION_REMOVE_ALL_VALUES.get(), callback);
+            }
+
+            if (resetOption != null) {
+                MenuCallback<Boolean> callback = new MenuCallback<Boolean>() {
+
+                    @Override
+                    public MenuResult<Boolean> invoke(ConsoleApplication app) throws ClientException {
+                        currentValues.clear();
+                        currentValues.addAll(defaultValues);
+                        isLastChoiceReset = true;
+                        app.println();
+                        app.pressReturnToContinue();
+                        return MenuResult.success(false);
+                    }
+
+                };
+
+                builder.addNumberedOption(resetOption, callback);
+            }
+
+            // Add an option for undoing any changes.
+            if (!oldValues.equals(currentValues)) {
+                MenuCallback<Boolean> callback = new MenuCallback<Boolean>() {
+
+                    @Override
+                    public MenuResult<Boolean> invoke(ConsoleApplication app) throws ClientException {
+                        currentValues.clear();
+                        currentValues.addAll(oldValues);
+                        isLastChoiceReset = false;
+                        app.println();
+                        app.pressReturnToContinue();
+                        return MenuResult.success(false);
+                    }
+
+                };
+
+                builder.addNumberedOption(INFO_EDITOR_OPTION_REVERT_CHANGES.get(), callback);
+            }
+
+            builder.addHelpOption(new PropertyHelpCallback(mo.getManagedObjectDefinition(), d));
+            builder.addQuitOption();
+
+            Menu<Boolean> menu = builder.toMenu();
+            MenuResult<Boolean> result;
+            try {
+                app.println();
+                result = menu.run();
+            } catch (ClientException e) {
+                this.e = e;
+                return null;
+            }
+
+            if (result.isSuccess()) {
+                if (result.getValue()) {
+
+                    // Set the new property value(s).
+                    mo.setPropertyValues(d, currentValues);
+
+                    registerModification(d, currentValues, oldValues);
+
+                    app.println();
+                    app.pressReturnToContinue();
+                    return MenuResult.success(false);
+                } else {
+                    // Continue until cancel/apply changes.
+                    app.println();
+                    return MenuResult.again();
+                }
+            } else if (result.isCancel()) {
+                app.println();
+                app.pressReturnToContinue();
+                return MenuResult.success(false);
+            } else {
+                return MenuResult.quit();
+            }
+        }
+    }
+
+    /**
+     * A help call-back which displays a description and summary of a single property.
+     */
+    private static final class PropertyHelpCallback implements HelpCallback {
+
+        /** The managed object definition. */
+        private final ManagedObjectDefinition<?, ?> d;
+
+        /** The property to be edited. */
+        private final PropertyDefinition<?> pd;
+
+        /** Creates a new property helper for the specified property. */
+        private PropertyHelpCallback(ManagedObjectDefinition<?, ?> d, PropertyDefinition<?> pd) {
+            this.d = d;
+            this.pd = pd;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public void display(ConsoleApplication app) {
+            app.println();
+            HelpSubCommandHandler.displayVerboseSingleProperty(app, d, pd.getName());
+            app.println();
+            app.pressReturnToContinue();
+        }
+    }
+
+    /**
+     * A menu call-back for viewing a read-only properties.
+     */
+    private final class ReadOnlyPropertyViewer extends PropertyDefinitionVisitor<MenuResult<Boolean>, Void> implements
+            MenuCallback<Boolean> {
+
+        /** Any exception that was caught during processing. */
+        private ClientException e = null;
+
+        /** The managed object being edited. */
+        private final ManagedObject<?> mo;
+
+        /** The property to be edited. */
+        private final PropertyDefinition<?> pd;
+
+        /** Creates a new property editor for the specified property. */
+        private ReadOnlyPropertyViewer(ManagedObject<?> mo, PropertyDefinition<?> pd) {
+            this.mo = mo;
+            this.pd = pd;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public MenuResult<Boolean> invoke(ConsoleApplication app) throws ClientException {
+            MenuResult<Boolean> result = pd.accept(this, null);
+            if (e != null) {
+                throw e;
+            }
+            return result;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public <T> MenuResult<Boolean> visitUnknown(PropertyDefinition<T> pd, Void p) {
+            SortedSet<T> values = mo.getPropertyValues(pd);
+
+            app.println();
+            app.println();
+            switch (values.size()) {
+            case 0:
+                // Only alias, undefined, or inherited alias or undefined
+                // properties should apply here.
+                DefaultBehaviorQuery<T> query = DefaultBehaviorQuery.query(pd);
+                LocalizableMessage aliasDescription = query.getAliasDescription();
+                if (aliasDescription == null) {
+                    app.println(INFO_EDITOR_HEADING_READ_ONLY_ALIAS_UNDEFINED.get(pd.getName()));
+                } else {
+                    app.println(INFO_EDITOR_HEADING_READ_ONLY_ALIAS.get(pd.getName(), aliasDescription));
+                }
+                break;
+            case 1:
+                LocalizableMessage svalue = getPropertyValues(pd, mo);
+                app.println(INFO_EDITOR_HEADING_READ_ONLY_VALUE.get(pd.getName(), svalue));
+                break;
+            default:
+                app.println(INFO_EDITOR_HEADING_READ_ONLY_VALUES.get(pd.getName()));
+                app.println();
+                displayPropertyValues(app, pd, values);
+                break;
+            }
+
+            app.println();
+            boolean result;
+            try {
+                result = app.confirmAction(INFO_EDITOR_PROMPT_READ_ONLY.get(), false);
+            } catch (ClientException e) {
+                this.e = e;
+                return null;
+            }
+
+            if (result) {
+                app.println();
+                HelpSubCommandHandler.displayVerboseSingleProperty(app, mo.getManagedObjectDefinition(), pd.getName());
+                app.println();
+                app.pressReturnToContinue();
+            }
+
+            return MenuResult.again();
+        }
+    }
+
+    /**
+     * A menu call-back for editing a modifiable single-valued property.
+     */
+    private final class SingleValuedPropertyEditor extends PropertyDefinitionVisitor<MenuResult<Boolean>, Void>
+            implements MenuCallback<Boolean> {
+
+        /** Any exception that was caught during processing. */
+        private ClientException e = null;
+
+        /** The managed object being edited. */
+        private final ManagedObject<?> mo;
+
+        /** The property to be edited. */
+        private final PropertyDefinition<?> pd;
+
+        /** Creates a new property editor for the specified property. */
+        private SingleValuedPropertyEditor(ManagedObject<?> mo, PropertyDefinition<?> pd) {
+            Reject.ifFalse(!pd.hasOption(PropertyOption.MULTI_VALUED));
+
+            this.mo = mo;
+            this.pd = pd;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public MenuResult<Boolean> invoke(ConsoleApplication app) throws ClientException {
+            displayPropertyHeader(app, pd);
+
+            MenuResult<Boolean> result = pd.accept(this, null);
+            if (e != null) {
+                throw e;
+            }
+            return result;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public <C extends ConfigurationClient, S extends Configuration> MenuResult<Boolean> visitAggregation(
+                AggregationPropertyDefinition<C, S> d, Void p) {
+            // Construct a menu of actions.
+            MenuBuilder<String> builder = new MenuBuilder<String>(app);
+            builder.setMultipleColumnThreshold(MULTI_COLUMN_THRESHOLD);
+            builder.setPrompt(INFO_EDITOR_PROMPT_MODIFY_MENU.get(d.getName()));
+
+            DefaultBehaviorQuery<String> query = DefaultBehaviorQuery.query(d);
+            SortedSet<String> currentValues = mo.getPropertyValues(d);
+            SortedSet<String> defaultValues = mo.getPropertyDefaultValues(d);
+            String currentValue = currentValues.isEmpty() ? null : currentValues.first();
+            String defaultValue = defaultValues.isEmpty() ? null : defaultValues.first();
+
+            // First option is for leaving the property unchanged.
+            LocalizableMessage option = getKeepDefaultValuesMenuOption(d);
+            builder.addNumberedOption(option, MenuResult.<String> cancel());
+            builder.setDefault(LocalizableMessage.raw("1"), MenuResult.<String> cancel());
+
+            // Create a list of possible names.
+            final Set<String> values = new TreeSet<String>(d);
+            ManagedObjectPath<?, ?> path = d.getParentPath();
+            InstantiableRelationDefinition<C, S> rd = d.getRelationDefinition();
+            try {
+                values.addAll(Arrays.asList(context.listManagedObjects(path, rd)));
+            } catch (AuthorizationException e) {
+                this.e = new ClientException(
+                        ReturnCode.CLIENT_SIDE_PARAM_ERROR, LocalizableMessage.raw(e.getMessage()));
+                return MenuResult.quit();
+            } catch (ManagedObjectNotFoundException e) {
+                this.e = new ClientException(ReturnCode.NO_SUCH_OBJECT, e.getMessageObject());
+                return MenuResult.cancel();
+            } catch (ErrorResultException e) {
+                this.e = new ClientException(ReturnCode.APPLICATION_ERROR, LocalizableMessage.raw(e.getMessage()));
+                return MenuResult.quit();
+            }
+
+            final LocalizableMessage ufn = rd.getUserFriendlyName();
+            for (String value : values) {
+                if (currentValue != null && d.compare(value, currentValue) == 0) {
+                    // This option is unnecessary.
+                    continue;
+                }
+
+                LocalizableMessage svalue = getPropertyValues(d, Collections.singleton(value));
+                if (value.equals(defaultValue) && query.isDefined()) {
+                    option = INFO_EDITOR_OPTION_CHANGE_TO_DEFAULT_COMPONENT.get(ufn, svalue);
+                } else {
+                    option = INFO_EDITOR_OPTION_CHANGE_TO_COMPONENT.get(ufn, svalue);
+                }
+
+                builder.addNumberedOption(option, MenuResult.success(value));
+            }
+            MenuCallback<String> callback = new CreateComponentCallback<C, S>(d);
+            builder.addNumberedOption(INFO_EDITOR_OPTION_CREATE_A_NEW_COMPONENT.get(ufn), callback);
+
+            // Third option is to reset the value back to its default.
+            if (mo.isPropertyPresent(d) && !query.isDefined()) {
+                option = getResetToDefaultValuesMenuOption(d);
+                if (option != null) {
+                    builder.addNumberedOption(option, MenuResult.<String> success());
+                }
+            }
+
+            return runMenu(d, builder);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public MenuResult<Boolean> visitBoolean(BooleanPropertyDefinition d, Void p) {
+            // Construct a menu of actions.
+            MenuBuilder<Boolean> builder = new MenuBuilder<Boolean>(app);
+            builder.setPrompt(INFO_EDITOR_PROMPT_MODIFY_MENU.get(d.getName()));
+
+            DefaultBehaviorQuery<Boolean> query = DefaultBehaviorQuery.query(d);
+            SortedSet<Boolean> currentValues = mo.getPropertyValues(d);
+            SortedSet<Boolean> defaultValues = mo.getPropertyDefaultValues(d);
+
+            Boolean currentValue = currentValues.isEmpty() ? null : currentValues.first();
+            Boolean defaultValue = defaultValues.isEmpty() ? null : defaultValues.first();
+
+            // First option is for leaving the property unchanged.
+            LocalizableMessage option = getKeepDefaultValuesMenuOption(d);
+            builder.addNumberedOption(option, MenuResult.<Boolean> cancel());
+            builder.setDefault(LocalizableMessage.raw("1"), MenuResult.<Boolean> cancel());
+
+            // The second (and possibly third) option is to always change
+            // the property's value.
+            if (currentValue == null || !currentValue) {
+                LocalizableMessage svalue = getPropertyValues(d, Collections.singleton(true));
+
+                if (defaultValue != null && defaultValue) {
+                    option = INFO_EDITOR_OPTION_CHANGE_TO_DEFAULT_VALUE.get(svalue);
+                } else {
+                    option = INFO_EDITOR_OPTION_CHANGE_TO_VALUE.get(svalue);
+                }
+
+                builder.addNumberedOption(option, MenuResult.success(true));
+            }
+
+            if (currentValue == null || currentValue) {
+                LocalizableMessage svalue = getPropertyValues(d, Collections.singleton(false));
+
+                if (defaultValue != null && !defaultValue) {
+                    option = INFO_EDITOR_OPTION_CHANGE_TO_DEFAULT_VALUE.get(svalue);
+                } else {
+                    option = INFO_EDITOR_OPTION_CHANGE_TO_VALUE.get(svalue);
+                }
+
+                builder.addNumberedOption(option, MenuResult.success(false));
+            }
+
+            // Final option is to reset the value back to its default.
+            if (mo.isPropertyPresent(d) && !query.isDefined()) {
+                option = getResetToDefaultValuesMenuOption(d);
+                if (option != null) {
+                    builder.addNumberedOption(option, MenuResult.<Boolean> success());
+                }
+            }
+
+            return runMenu(d, builder);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public <E extends Enum<E>> MenuResult<Boolean> visitEnum(EnumPropertyDefinition<E> d, Void p) {
+            // Construct a menu of actions.
+            MenuBuilder<E> builder = new MenuBuilder<E>(app);
+            builder.setMultipleColumnThreshold(MULTI_COLUMN_THRESHOLD);
+            builder.setPrompt(INFO_EDITOR_PROMPT_MODIFY_MENU.get(d.getName()));
+
+            DefaultBehaviorQuery<E> query = DefaultBehaviorQuery.query(d);
+            SortedSet<E> currentValues = mo.getPropertyValues(d);
+            SortedSet<E> defaultValues = mo.getPropertyDefaultValues(d);
+            E currentValue = currentValues.isEmpty() ? null : currentValues.first();
+            E defaultValue = defaultValues.isEmpty() ? null : defaultValues.first();
+
+            // First option is for leaving the property unchanged.
+            LocalizableMessage option = getKeepDefaultValuesMenuOption(d);
+            builder.addNumberedOption(option, MenuResult.<E> cancel());
+            builder.setDefault(LocalizableMessage.raw("1"), MenuResult.<E> cancel());
+
+            // Create options for changing to other values.
+            Set<E> values = new TreeSet<E>(d);
+            values.addAll(EnumSet.allOf(d.getEnumClass()));
+            for (E value : values) {
+                if (value.equals(currentValue) && query.isDefined()) {
+                    // This option is unnecessary.
+                    continue;
+                }
+
+                LocalizableMessage svalue = getPropertyValues(d, Collections.singleton(value));
+
+                if (value.equals(defaultValue) && query.isDefined()) {
+                    option = INFO_EDITOR_OPTION_CHANGE_TO_DEFAULT_VALUE.get(svalue);
+                } else {
+                    option = INFO_EDITOR_OPTION_CHANGE_TO_VALUE.get(svalue);
+                }
+
+                builder.addNumberedOption(option, MenuResult.success(value));
+            }
+
+            // Third option is to reset the value back to its default.
+            if (mo.isPropertyPresent(d) && !query.isDefined()) {
+                option = getResetToDefaultValuesMenuOption(d);
+                if (option != null) {
+                    builder.addNumberedOption(option, MenuResult.<E> success());
+                }
+            }
+
+            return runMenu(d, builder);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public <T> MenuResult<Boolean> visitUnknown(final PropertyDefinition<T> d, Void p) {
+            app.println();
+            displayPropertySyntax(app, d);
+
+            // Construct a menu of actions.
+            MenuBuilder<T> builder = new MenuBuilder<T>(app);
+            builder.setPrompt(INFO_EDITOR_PROMPT_MODIFY_MENU.get(d.getName()));
+
+            // First option is for leaving the property unchanged.
+            LocalizableMessage option = getKeepDefaultValuesMenuOption(d);
+            builder.addNumberedOption(option, MenuResult.<T> cancel());
+            builder.setDefault(LocalizableMessage.raw("1"), MenuResult.<T> cancel());
+
+            // The second option is to always change the property's value.
+            builder.addNumberedOption(INFO_EDITOR_OPTION_CHANGE_VALUE.get(), new MenuCallback<T>() {
+
+                @Override
+                public MenuResult<T> invoke(ConsoleApplication app) throws ClientException {
+                    app.println();
+                    Set<T> values = readPropertyValues(app, mo.getManagedObjectDefinition(), d);
+                    return MenuResult.success(values);
+                }
+
+            });
+
+            // Third option is to reset the value back to its default.
+            if (mo.isPropertyPresent(d)) {
+                option = getResetToDefaultValuesMenuOption(d);
+                if (option != null) {
+                    builder.addNumberedOption(option, MenuResult.<T> success());
+                }
+            }
+
+            return runMenu(d, builder);
+        }
+
+        /**
+         * Generate an appropriate menu option for a property which asks the user whether or not they want to keep the
+         * property's current settings.
+         */
+        private <T> LocalizableMessage getKeepDefaultValuesMenuOption(PropertyDefinition<T> pd) {
+            DefaultBehaviorQuery<T> query = DefaultBehaviorQuery.query(pd);
+            SortedSet<T> currentValues = mo.getPropertyValues(pd);
+            SortedSet<T> defaultValues = mo.getPropertyDefaultValues(pd);
+
+            if (query.isDefined() && currentValues.equals(defaultValues)) {
+                LocalizableMessage svalue = getPropertyValues(pd, currentValues);
+                return INFO_EDITOR_OPTION_KEEP_DEFAULT_VALUE.get(svalue);
+            } else if (mo.isPropertyPresent(pd)) {
+                LocalizableMessage svalue = getPropertyValues(pd, currentValues);
+                return INFO_EDITOR_OPTION_KEEP_VALUE.get(svalue);
+            } else if (query.isAlias()) {
+                return INFO_EDITOR_OPTION_KEEP_DEFAULT_ALIAS.get(query.getAliasDescription());
+            } else if (query.isInherited()) {
+                if (defaultValues.isEmpty()) {
+                    if (query.getAliasDescription() != null) {
+                        return INFO_EDITOR_OPTION_KEEP_DEFAULT_INHERITED_ALIAS.get(query.getAliasDescription());
+                    } else {
+                        return INFO_EDITOR_OPTION_KEEP_DEFAULT_INHERITED_ALIAS_UNDEFINED.get();
+                    }
+                } else {
+                    LocalizableMessage svalue = getPropertyValues(pd, defaultValues);
+                    return INFO_EDITOR_OPTION_KEEP_INHERITED_DEFAULT_VALUE.get(svalue);
+                }
+            } else {
+                return INFO_EDITOR_OPTION_LEAVE_UNDEFINED.get();
+            }
+        }
+
+        /**
+         * Generate an appropriate menu option which should be used in the case where a property can be reset to its
+         * default behavior.
+         */
+        private <T> LocalizableMessage getResetToDefaultValuesMenuOption(PropertyDefinition<T> pd) {
+            DefaultBehaviorQuery<T> query = DefaultBehaviorQuery.query(pd);
+            SortedSet<T> currentValues = mo.getPropertyValues(pd);
+            SortedSet<T> defaultValues = mo.getPropertyDefaultValues(pd);
+
+            boolean isMandatory = pd.hasOption(PropertyOption.MANDATORY);
+
+            if (!isMandatory && query.isAlias()) {
+                return INFO_EDITOR_OPTION_RESET_DEFAULT_ALIAS.get(query.getAliasDescription());
+            } else if (query.isDefined()) {
+                // Only show this option if the current value is different
+                // to the default.
+                if (!currentValues.equals(defaultValues)) {
+                    LocalizableMessage svalue = getPropertyValues(pd, defaultValues);
+                    return INFO_EDITOR_OPTION_RESET_DEFAULT_VALUE.get(svalue);
+                }
+                return null;
+            } else if (!isMandatory && query.isInherited()) {
+                if (defaultValues.isEmpty()) {
+                    if (query.getAliasDescription() != null) {
+                        return INFO_EDITOR_OPTION_RESET_DEFAULT_INHERITED_ALIAS.get(query.getAliasDescription());
+                    } else {
+                        return INFO_EDITOR_OPTION_RESET_DEFAULT_INHERITED_ALIAS_UNDEFINED.get();
+                    }
+                } else {
+                    LocalizableMessage svalue = getPropertyValues(pd, defaultValues);
+                    return INFO_EDITOR_OPTION_RESET_INHERITED_DEFAULT_VALUE.get(svalue);
+                }
+            } else if (!isMandatory && query.isUndefined()) {
+                return INFO_EDITOR_OPTION_LEAVE_UNDEFINED.get();
+            }
+            return null;
+        }
+
+        /** Common menu processing. */
+        private <T> MenuResult<Boolean> runMenu(final PropertyDefinition<T> d, MenuBuilder<T> builder) {
+            builder.addHelpOption(new PropertyHelpCallback(mo.getManagedObjectDefinition(), d));
+            builder.addQuitOption();
+
+            Menu<T> menu = builder.toMenu();
+            MenuResult<T> result;
+            try {
+                app.println();
+                result = menu.run();
+            } catch (ClientException e) {
+                this.e = e;
+                return null;
+            }
+
+            if (result.isSuccess()) {
+                // Set the new property value(s).
+                Collection<T> newValues = result.getValues();
+                SortedSet<T> oldValues = new TreeSet<T>(mo.getPropertyValues(d));
+                mo.setPropertyValues(d, newValues);
+
+                // If there are no newValues when we do a reset.
+                isLastChoiceReset = newValues.isEmpty();
+                registerModification(d, new TreeSet<T>(newValues), oldValues);
+                app.println();
+                app.pressReturnToContinue();
+                return MenuResult.success(false);
+            } else if (result.isCancel()) {
+                app.println();
+                app.pressReturnToContinue();
+                return MenuResult.success(false);
+            } else {
+                return MenuResult.quit();
+            }
+        }
+    }
+
+    /** Display a title and a description of the property. */
+    private static void displayPropertyHeader(ConsoleApplication app, PropertyDefinition<?> pd) {
+        app.println();
+        app.println();
+        app.println(INFO_EDITOR_HEADING_CONFIGURE_PROPERTY.get(pd.getName()));
+        app.println();
+        app.errPrintln(pd.getSynopsis(), 4);
+        if (pd.getDescription() != null) {
+            app.println();
+            app.errPrintln(pd.getDescription(), 4);
+        }
+    }
+
+    /** Display a property's syntax. */
+    private static <T> void displayPropertySyntax(ConsoleApplication app, PropertyDefinition<T> d) {
+        PropertyDefinitionUsageBuilder b = new PropertyDefinitionUsageBuilder(true);
+
+        TableBuilder builder = new TableBuilder();
+        builder.startRow();
+        builder.appendCell(INFO_EDITOR_HEADING_SYNTAX.get());
+        builder.appendCell(b.getUsage(d));
+
+        TextTablePrinter printer = new TextTablePrinter(app.getErrorStream());
+        printer.setDisplayHeadings(false);
+        printer.setIndentWidth(4);
+        printer.setColumnWidth(1, 0);
+        builder.print(printer);
+    }
+
+    /** Display a table of property values. */
+    private static <T> void displayPropertyValues(ConsoleApplication app, PropertyDefinition<T> pd,
+            Collection<T> values) {
+        TableBuilder builder = new TableBuilder();
+        PropertyValuePrinter valuePrinter = new PropertyValuePrinter(null, null, false);
+
+        int sz = values.size();
+        boolean useMultipleColumns = (sz >= MULTI_COLUMN_THRESHOLD);
+        int rows = sz;
+        if (useMultipleColumns) {
+            // Display in two columns the first column should contain
+            // half the values. If there are an odd number of columns
+            // then the first column should contain an additional value
+            // (e.g. if there are 23 values, the first column should
+            // contain 12 values and the second column 11 values).
+            rows /= 2;
+            rows += sz % 2;
+        }
+
+        List<T> vl = new ArrayList<T>(values);
+        for (int i = 0, j = rows; i < rows; i++, j++) {
+            builder.startRow();
+            builder.appendCell("*)");
+            builder.appendCell(valuePrinter.print(pd, vl.get(i)));
+
+            if (useMultipleColumns && (j < sz)) {
+                builder.appendCell();
+                builder.appendCell("*)");
+                builder.appendCell(valuePrinter.print(pd, vl.get(j)));
+            }
+        }
+
+        TextTablePrinter printer = new TextTablePrinter(app.getErrorStream());
+        printer.setDisplayHeadings(false);
+        printer.setIndentWidth(4);
+        printer.setColumnWidth(1, 0);
+        if (useMultipleColumns) {
+            printer.setColumnWidth(2, 2);
+            printer.setColumnWidth(4, 0);
+        }
+        builder.print(printer);
+    }
+
+    /** Display the set of values associated with a property. */
+    private static <T> LocalizableMessage getPropertyValues(PropertyDefinition<T> pd, Collection<T> values) {
+        if (values.isEmpty()) {
+            // There are no values or default values. Display the default
+            // behavior for alias values.
+            DefaultBehaviorQuery<T> query = DefaultBehaviorQuery.query(pd);
+            LocalizableMessage content = query.getAliasDescription();
+            if (content != null) {
+                return content;
+            }
+            return LocalizableMessage.raw("-");
+        } else {
+            PropertyValuePrinter printer = new PropertyValuePrinter(null, null, false);
+            LocalizableMessageBuilder builder = new LocalizableMessageBuilder();
+
+            boolean isFirst = true;
+            for (T value : values) {
+                if (!isFirst) {
+                    builder.append(", ");
+                }
+                builder.append(printer.print(pd, value));
+                isFirst = false;
+            }
+
+            return builder.toMessage();
+        }
+    }
+
+    /** Display the set of values associated with a property. */
+    private static <T> LocalizableMessage getPropertyValues(PropertyDefinition<T> pd, ManagedObject<?> mo) {
+        SortedSet<T> values = mo.getPropertyValues(pd);
+        return getPropertyValues(pd, values);
+    }
+
+    /** Read new values for a property. */
+    private static <T> SortedSet<T> readPropertyValues(ConsoleApplication app, ManagedObjectDefinition<?, ?> d,
+            PropertyDefinition<T> pd) throws ClientException {
+        SortedSet<T> values = new TreeSet<T>(pd);
+        readPropertyValues(app, d, pd, values);
+        return values;
+    }
+
+    /** Add values to a property. */
+    private static <T> void readPropertyValues(ConsoleApplication app, ManagedObjectDefinition<?, ?> d,
+            PropertyDefinition<T> pd, SortedSet<T> values) throws ClientException {
+        // Make sure there is at least one value if mandatory and empty.
+        if (values.isEmpty()) {
+            while (true) {
+                try {
+                    LocalizableMessage prompt;
+
+                    if (pd.hasOption(PropertyOption.MANDATORY)) {
+                        prompt = INFO_EDITOR_PROMPT_READ_FIRST_VALUE.get(pd.getName());
+                    } else {
+                        prompt = INFO_EDITOR_PROMPT_READ_FIRST_VALUE_OPTIONAL.get(pd.getName());
+                    }
+
+                    app.println();
+                    String s = app.readLineOfInput(prompt);
+                    if (s.trim().length() == 0 && !pd.hasOption(PropertyOption.MANDATORY)) {
+                        return;
+                    }
+
+                    T value = pd.decodeValue(s);
+                    if (values.contains(value)) {
+                        // Prevent addition of duplicates.
+                        app.println();
+                        app.println(ERR_EDITOR_READ_FIRST_DUPLICATE.get(s));
+                    } else {
+                        values.add(value);
+                    }
+
+                    break;
+                } catch (PropertyException e) {
+                    app.println();
+                    app.println(ArgumentExceptionFactory.adaptPropertyException(e, d).getMessageObject());
+                }
+            }
+        }
+
+        if (pd.hasOption(PropertyOption.MULTI_VALUED)) {
+            // Prompt for more values if multi-valued.
+            while (true) {
+                try {
+                    LocalizableMessage prompt = INFO_EDITOR_PROMPT_READ_NEXT_VALUE.get(pd.getName());
+
+                    app.println();
+                    String s = app.readLineOfInput(prompt);
+                    if (s.trim().length() == 0) {
+                        return;
+                    }
+
+                    T value = pd.decodeValue(s);
+                    if (values.contains(value)) {
+                        // Prevent addition of duplicates.
+                        app.println();
+                        app.println(ERR_EDITOR_READ_NEXT_DUPLICATE.get(s));
+                    } else {
+                        values.add(value);
+                    }
+                } catch (PropertyException e) {
+                    app.println();
+                    app.println(ArgumentExceptionFactory.adaptPropertyException(e, d).getMessageObject());
+                    app.println();
+                }
+            }
+        }
+    }
+
+    /**
+     * The threshold above which choice menus should be displayed in multiple columns.
+     */
+    private static final int MULTI_COLUMN_THRESHOLD = 8;
+
+    /** The application console. */
+    private final ConsoleApplication app;
+
+    /** The management context. */
+    private final ManagementContext context;
+
+    /**
+     * The modifications performed: we assume that at most there is one modification per property definition.
+     */
+    private final List<PropertyEditorModification<?>> mods = new ArrayList<PropertyEditorModification<?>>();
+
+    /**
+     * Whether the last type of choice made by the user in a menu is a reset.
+     */
+    private boolean isLastChoiceReset;
+
+    /**
+     * Create a new property value editor which will read from the provided application console.
+     *
+     * @param app
+     *            The application console.
+     * @param context
+     *            The management context.
+     */
+    public PropertyValueEditor(ConsoleApplication app, ManagementContext context) {
+        this.app = app;
+        this.context = context;
+    }
+
+    /**
+     * Interactively edits the properties of a managed object. Only the properties listed in the provided collection
+     * will be accessible to the client. It is up to the caller to ensure that the list of properties does not include
+     * read-only, monitoring, hidden, or advanced properties as appropriate.
+     *
+     * @param mo
+     *            The managed object.
+     * @param c
+     *            The collection of properties which can be edited.
+     * @param isCreate
+     *            Flag indicating whether or not the managed object is being created. If it is then read-only properties
+     *            will be modifiable.
+     * @return Returns {@code MenuResult.success()} if the changes made to the managed object should be applied, or
+     *         {@code MenuResult.cancel()} if the user to chose to cancel any changes, or {@code MenuResult.quit()} if
+     *         the user chose to quit the application.
+     * @throws ClientException
+     *             If the user input could not be retrieved for some reason.
+     */
+    public MenuResult<Void> edit(ManagedObject<?> mo, Collection<PropertyDefinition<?>> c, boolean isCreate)
+            throws ClientException {
+
+        // Get values for this missing mandatory property.
+        for (PropertyDefinition<?> pd : c) {
+            if (pd.hasOption(PropertyOption.MANDATORY) && mo.getPropertyValues(pd).isEmpty()) {
+                MandatoryPropertyInitializer mpi = new MandatoryPropertyInitializer(mo, pd);
+                MenuResult<Void> result = mpi.invoke(app);
+                if (!result.isSuccess()) {
+                    return result;
+                }
+            }
+        }
+
+        while (true) {
+            // Construct the main menu.
+            MenuBuilder<Boolean> builder = new MenuBuilder<Boolean>(app);
+
+            String ufn = mo.getManagedObjectPath().getName();
+            if (ufn == null) {
+                ufn = mo.getManagedObjectDefinition().getUserFriendlyName().toString();
+            }
+            builder.setPrompt(INFO_EDITOR_HEADING_CONFIGURE_COMPONENT.get(ufn));
+
+            LocalizableMessage heading1 = INFO_DSCFG_HEADING_PROPERTY_NAME.get();
+            LocalizableMessage heading2 = INFO_DSCFG_HEADING_PROPERTY_VALUE.get();
+            builder.setColumnHeadings(heading1, heading2);
+            builder.setColumnWidths(null, 0);
+
+            // Create an option for editing/viewing each property.
+            for (PropertyDefinition<?> pd : c) {
+                // Determine whether this property should be modifiable.
+                boolean isReadOnly = false;
+
+                if (pd.hasOption(PropertyOption.MONITORING)) {
+                    isReadOnly = true;
+                }
+
+                if (!isCreate && pd.hasOption(PropertyOption.READ_ONLY)) {
+                    isReadOnly = true;
+                }
+
+                // Create the appropriate property action.
+                MenuCallback<Boolean> callback;
+                if (pd.hasOption(PropertyOption.MULTI_VALUED)) {
+                    if (isReadOnly) {
+                        callback = new ReadOnlyPropertyViewer(mo, pd);
+                    } else {
+                        callback = new MultiValuedPropertyEditor(mo, pd);
+                    }
+                } else {
+                    if (isReadOnly) {
+                        callback = new ReadOnlyPropertyViewer(mo, pd);
+                    } else {
+                        callback = new SingleValuedPropertyEditor(mo, pd);
+                    }
+                }
+
+                // Create the numeric option.
+                LocalizableMessage values = getPropertyValues(pd, mo);
+                builder.addNumberedOption(LocalizableMessage.raw("%s", pd.getName()), callback, values);
+            }
+
+            // Add a help option which displays a summary of the managed
+            // object's definition.
+            HelpCallback helpCallback = new ComponentHelpCallback(mo, c);
+            builder.addHelpOption(helpCallback);
+
+            // Add an option to apply the changes.
+            if (isCreate) {
+                builder.addCharOption(INFO_EDITOR_OPTION_FINISH_KEY.get(),
+                        INFO_EDITOR_OPTION_FINISH_CREATE_COMPONENT.get(ufn), MenuResult.success(true));
+            } else {
+                builder.addCharOption(INFO_EDITOR_OPTION_FINISH_KEY.get(),
+                        INFO_EDITOR_OPTION_FINISH_MODIFY_COMPONENT.get(ufn), MenuResult.success(true));
+            }
+
+            builder.setDefault(INFO_EDITOR_OPTION_FINISH_KEY.get(), MenuResult.success(true));
+
+            // Add options for canceling and quitting.
+            if (app.isMenuDrivenMode()) {
+                builder.addCancelOption(false);
+            }
+            builder.addQuitOption();
+
+            // Run the menu - success indicates that any changes should be
+            // committed.
+            app.println();
+            app.println();
+            Menu<Boolean> menu = builder.toMenu();
+            MenuResult<Boolean> result = menu.run();
+
+            if (result.isSuccess()) {
+                if (result.getValue()) {
+                    return MenuResult.<Void> success();
+                }
+            } else if (result.isCancel()) {
+                return MenuResult.cancel();
+            } else {
+                return MenuResult.quit();
+            }
+        }
+    }
+
+    /**
+     * Register the modification in the list of modifications.
+     *
+     * @param <T>
+     *            The type of the underlying property associated with the modification.
+     * @param pd
+     *            the property definition.
+     * @param newValues
+     *            the resulting values of the property once the modification is applied.
+     * @param previousValues
+     *            the values we had before the modification is applied (these are not necessarily the *original* values
+     *            if we already have other modifications applied to the same property).
+     */
+    private <T> void registerModification(PropertyDefinition<T> pd, SortedSet<T> newValues,
+            SortedSet<T> previousValues) {
+
+        if (isLastChoiceReset) {
+            registerResetModification(pd, previousValues);
+        } else if (!newValues.equals(previousValues)) {
+            if (newValues.containsAll(previousValues)) {
+                if (newValues.size() <= 1) {
+                    registerSetModification(pd, newValues, previousValues);
+                } else {
+                    registerAddModification(pd, newValues, previousValues);
+                }
+            } else if (previousValues.containsAll(newValues)) {
+                registerRemoveModification(pd, newValues, previousValues);
+            } else if (newValues.size() <= 1) {
+                registerSetModification(pd, newValues, previousValues);
+            } else {
+                // Split into two operations: remove and add
+                SortedSet<T> removedValues = new TreeSet<T>();
+                removedValues.addAll(previousValues);
+                removedValues.removeAll(newValues);
+
+                PropertyEditorModification<T> removeMod = PropertyEditorModification.createRemoveModification(pd,
+                        removedValues, previousValues);
+                addModification(removeMod);
+
+                SortedSet<T> retainedValues = new TreeSet<T>();
+                retainedValues.addAll(previousValues);
+                retainedValues.retainAll(newValues);
+
+                SortedSet<T> addedValues = new TreeSet<T>();
+                addedValues.addAll(newValues);
+                addedValues.removeAll(retainedValues);
+
+                PropertyEditorModification<T> addMod = PropertyEditorModification.createAddModification(pd,
+                        addedValues, retainedValues);
+                addModification(addMod);
+            }
+        }
+    }
+
+    /**
+     * Register a reset modification in the list of modifications.
+     *
+     * @param <T>
+     *            The type of the underlying property associated with the modification.
+     * @param pd
+     *            the property definition.
+     * @param previousValues
+     *            the values we had before the modification is applied (these are not necessarily the *original* values
+     *            if we already have other modifications applied to the same property).
+     */
+    private <T> void registerResetModification(PropertyDefinition<T> pd, SortedSet<T> previousValues) {
+        PropertyEditorModification<?> mod = getModification(pd);
+        SortedSet<T> originalValues;
+        if (mod != null) {
+            originalValues = new TreeSet<T>(pd);
+            castAndAddValues(originalValues, mod.getOriginalValues(), pd);
+            removeModification(mod);
+        } else {
+            originalValues = new TreeSet<T>(previousValues);
+        }
+
+        addModification(PropertyEditorModification.createResetModification(pd, originalValues));
+    }
+
+    /**
+     * Register a set modification in the list of modifications.
+     *
+     * @param <T>
+     *            The type of the underlying property associated with the modification.
+     * @param pd
+     *            the property definition.
+     * @param newValues
+     *            the resulting values of the property once the modification is applied.
+     * @param previousValues
+     *            the values we had before the modification is applied (these are not necessarily the *original* values
+     *            if we already have other modifications applied to the same property).
+     */
+    private <T> void registerSetModification(PropertyDefinition<T> pd, SortedSet<T> newValues,
+            SortedSet<T> previousValues) {
+        PropertyEditorModification<?> mod = getModification(pd);
+        SortedSet<T> originalValues;
+        if (mod != null) {
+            originalValues = new TreeSet<T>(pd);
+            castAndAddValues(originalValues, mod.getOriginalValues(), pd);
+            removeModification(mod);
+        } else {
+            originalValues = new TreeSet<T>(previousValues);
+        }
+        addModification(PropertyEditorModification.createSetModification(pd, newValues, originalValues));
+    }
+
+    /**
+     * Register an add modification in the list of modifications.
+     *
+     * @param <T>
+     *            The type of the underlying property associated with the modification.
+     * @param pd
+     *            the property definition.
+     * @param newValues
+     *            the resulting values of the property once the modification is applied.
+     * @param previousValues
+     *            the values we had before the modification is applied (these are not necessarily the *original* values
+     *            if we already have other modifications applied to the same property).
+     */
+    private <T> void registerAddModification(PropertyDefinition<T> pd, SortedSet<T> newValues,
+            SortedSet<T> previousValues) {
+        PropertyEditorModification<?> mod = getModification(pd);
+        PropertyEditorModification<T> newMod;
+        SortedSet<T> originalValues;
+        if (mod != null) {
+            originalValues = new TreeSet<T>(pd);
+            castAndAddValues(originalValues, mod.getOriginalValues(), pd);
+            if (mod.getType() == PropertyEditorModification.Type.ADD) {
+                SortedSet<T> addedValues = new TreeSet<T>(newValues);
+                addedValues.removeAll(originalValues);
+                newMod = PropertyEditorModification.createAddModification(pd, addedValues, originalValues);
+            } else {
+                newMod = PropertyEditorModification
+                        .createSetModification(pd, new TreeSet<T>(newValues), originalValues);
+            }
+            removeModification(mod);
+        } else {
+            originalValues = new TreeSet<T>(previousValues);
+            SortedSet<T> addedValues = new TreeSet<T>(newValues);
+            addedValues.removeAll(originalValues);
+            newMod = PropertyEditorModification.createAddModification(pd, addedValues, originalValues);
+        }
+        addModification(newMod);
+    }
+
+    /**
+     * Register a remove modification in the list of modifications.
+     *
+     * @param <T>
+     *            The type of the underlying property associated with the modification.
+     * @param pd
+     *            the property definition.
+     * @param newValues
+     *            the resulting values of the property once the modification is applied.
+     * @param previousValues
+     *            the values we had before the modification is applied (these are not necessarily the *original* values
+     *            if we already have other modifications applied to the same property).
+     */
+    private <T> void registerRemoveModification(PropertyDefinition<T> pd, SortedSet<T> newValues,
+            SortedSet<T> previousValues) {
+        PropertyEditorModification<?> mod = getModification(pd);
+        PropertyEditorModification<T> newMod;
+        SortedSet<T> originalValues;
+        if (mod != null) {
+            originalValues = new TreeSet<T>(pd);
+            castAndAddValues(originalValues, mod.getOriginalValues(), pd);
+            if (newValues.isEmpty()) {
+                newMod = PropertyEditorModification.createRemoveModification(pd, originalValues, originalValues);
+            } else if (mod.getType() == PropertyEditorModification.Type.REMOVE) {
+                SortedSet<T> removedValues = new TreeSet<T>(originalValues);
+                removedValues.removeAll(newValues);
+                newMod = PropertyEditorModification.createRemoveModification(pd, removedValues, originalValues);
+            } else {
+                newMod = PropertyEditorModification
+                        .createSetModification(pd, new TreeSet<T>(newValues), originalValues);
+            }
+            removeModification(mod);
+        } else {
+            originalValues = new TreeSet<T>(previousValues);
+            SortedSet<T> removedValues = new TreeSet<T>(originalValues);
+            removedValues.removeAll(newValues);
+            newMod = PropertyEditorModification.createRemoveModification(pd, removedValues, originalValues);
+        }
+        addModification(newMod);
+    }
+
+    /**
+     * Returns the modifications that have been applied during the last call of the method PropertyValueEditor.edit.
+     *
+     * @return the modifications that have been applied during the last call of the method PropertyValueEditor.edit.
+     */
+    public Collection<PropertyEditorModification<?>> getModifications() {
+        return mods;
+    }
+
+    /**
+     * Clears the list of modifications.
+     */
+    public void resetModifications() {
+        mods.clear();
+    }
+
+    /**
+     * Adds a modification to the list of modifications that have been performed.
+     *
+     * @param <T>
+     *            The type of the underlying property associated with the modification.
+     * @param mod
+     *            the modification to be added.
+     */
+    private <T> void addModification(PropertyEditorModification<T> mod) {
+        mods.add(mod);
+    }
+
+    /**
+     * Removes a modification from the list of modifications that have been performed.
+     *
+     * @param <T>
+     *            The type of the underlying property associated with the modification.
+     * @param mod
+     *            the modification to be removed.
+     */
+    private <T> boolean removeModification(PropertyEditorModification<T> mod) {
+        return mods.remove(mod);
+    }
+
+    /**
+     * Returns the modification associated with a given property definition: we assume that we have only one
+     * modification per property definition (in the worst case we merge the modifications and generate a unique set
+     * modification).
+     *
+     * @param <T>
+     *            The type of the underlying property associated with the modification.
+     * @param pd
+     *            the property definition.
+     * @return the modification associated with the provided property definition and <CODE>null</CODE> if no
+     *         modification could be found.
+     */
+    private <T> PropertyEditorModification<?> getModification(PropertyDefinition<T> pd) {
+        PropertyEditorModification<?> mod = null;
+
+        for (PropertyEditorModification<?> m : mods) {
+            if (pd.equals(m.getPropertyDefinition())) {
+                mod = m;
+                break;
+            }
+        }
+
+        return mod;
+    }
+
+    /**
+     * This method is required to avoid compilation warnings. It basically adds the contents of a collection to another
+     * collection by explicitly casting its values. This is done because the method getModification() returns au
+     * undefined type.
+     *
+     * @param <T>
+     *            The type of the destination values.
+     * @param destination
+     *            the collection that we want to update.
+     * @param source
+     *            the collection whose values we want to add (and cast) to the source collection.
+     * @param pd
+     *            the PropertyDefinition we use to do the casting.
+     * @throws ClassCastException
+     *             if an error occurs during the cast of the objects.
+     */
+    private <T> void castAndAddValues(Collection<T> destination, Collection<?> source, PropertyDefinition<T> pd) {
+        for (Object o : source) {
+            destination.add(pd.castValue(o));
+        }
+    }
+}
diff --git a/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/PropertyValuePrinter.java b/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/PropertyValuePrinter.java
new file mode 100644
index 0000000..1e8e733
--- /dev/null
+++ b/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/PropertyValuePrinter.java
@@ -0,0 +1,193 @@
+/*
+ * 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.
+ *      Portions Copyright 2014 ForgeRock AS
+ */
+package org.forgerock.opendj.config.dsconfig;
+
+import static com.forgerock.opendj.dsconfig.DsconfigMessages.*;
+
+import java.text.NumberFormat;
+
+import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.i18n.LocalizableMessageBuilder;
+import org.forgerock.opendj.config.BooleanPropertyDefinition;
+import org.forgerock.opendj.config.DurationPropertyDefinition;
+import org.forgerock.opendj.config.DurationUnit;
+import org.forgerock.opendj.config.PropertyDefinition;
+import org.forgerock.opendj.config.PropertyValueVisitor;
+import org.forgerock.opendj.config.SizePropertyDefinition;
+import org.forgerock.opendj.config.SizeUnit;
+
+/**
+ * A class responsible for displaying property values. This class takes care of using locale specific formatting rules.
+ */
+final class PropertyValuePrinter {
+
+    /**
+     * Perform property type specific print formatting.
+     */
+    private static final class MyPropertyValueVisitor extends PropertyValueVisitor<LocalizableMessage, Void> {
+
+        /**
+         * The requested size unit (null if the property's unit should be used).
+         */
+        private final SizeUnit sizeUnit;
+
+        /**
+         * The requested time unit (null if the property's unit should be used).
+         */
+        private final DurationUnit timeUnit;
+
+        /**
+         * Whether or not values should be displayed in a script-friendly manner.
+         */
+        private final boolean isScriptFriendly;
+
+        /** The formatter to use for numeric values. */
+        private final NumberFormat numberFormat;
+
+        /** Private constructor. */
+        private MyPropertyValueVisitor(SizeUnit sizeUnit, DurationUnit timeUnit, boolean isScriptFriendly) {
+            this.sizeUnit = sizeUnit;
+            this.timeUnit = timeUnit;
+            this.isScriptFriendly = isScriptFriendly;
+
+            numberFormat = NumberFormat.getNumberInstance();
+            numberFormat.setGroupingUsed(!this.isScriptFriendly);
+            numberFormat.setMaximumFractionDigits(2);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public LocalizableMessage visitBoolean(BooleanPropertyDefinition pd, Boolean v, Void p) {
+            return v ? INFO_VALUE_TRUE.get() : INFO_VALUE_FALSE.get();
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public LocalizableMessage visitDuration(DurationPropertyDefinition pd, Long v, Void p) {
+            if (pd.getUpperLimit() == null && (v < 0 || v == Long.MAX_VALUE)) {
+                return INFO_VALUE_UNLIMITED.get();
+            }
+
+            LocalizableMessageBuilder builder = new LocalizableMessageBuilder();
+            long ms = pd.getBaseUnit().toMilliSeconds(v);
+
+            if (timeUnit == null && !isScriptFriendly && ms != 0) {
+                // Use human-readable string representation by default.
+                builder.append(DurationUnit.toString(ms));
+            } else {
+                // Use either the specified unit or the property definition's
+                // base unit.
+                DurationUnit unit = timeUnit;
+                if (unit == null) {
+                    unit = pd.getBaseUnit();
+                }
+
+                builder.append(numberFormat.format(unit.fromMilliSeconds(ms)));
+                builder.append(' ');
+                builder.append(unit.getShortName());
+            }
+
+            return builder.toMessage();
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public LocalizableMessage visitSize(SizePropertyDefinition pd, Long v, Void p) {
+            if (pd.isAllowUnlimited() && v < 0) {
+                return INFO_VALUE_UNLIMITED.get();
+            }
+
+            SizeUnit unit = sizeUnit;
+            if (unit == null) {
+                if (isScriptFriendly) {
+                    // Assume users want a more accurate value.
+                    unit = SizeUnit.getBestFitUnitExact(v);
+                } else {
+                    unit = SizeUnit.getBestFitUnit(v);
+                }
+            }
+
+            LocalizableMessageBuilder builder = new LocalizableMessageBuilder();
+            builder.append(numberFormat.format(unit.fromBytes(v)));
+            builder.append(' ');
+            builder.append(unit.getShortName());
+
+            return builder.toMessage();
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public <T> LocalizableMessage visitUnknown(PropertyDefinition<T> pd, T v, Void p) {
+            // For all other property definition types the default encoding
+            // will do.
+            String s = pd.encodeValue(v);
+            if (isScriptFriendly) {
+                return LocalizableMessage.raw("%s", s);
+            } else if (s.trim().length() == 0 || s.contains(",")) {
+                // Quote empty strings or strings containing commas
+                // non-scripting mode.
+                return LocalizableMessage.raw("\"%s\"", s);
+            } else {
+                return LocalizableMessage.raw("%s", s);
+            }
+        }
+
+    }
+
+    /** The property value printer implementation. */
+    private final MyPropertyValueVisitor pimpl;
+
+    /**
+     * Creates a new property value printer.
+     *
+     * @param sizeUnit
+     *            The user requested size unit or <code>null</code> if best-fit.
+     * @param timeUnit
+     *            The user requested time unit or <code>null</code> if best-fit.
+     * @param isScriptFriendly
+     *            If values should be displayed in a script friendly manner.
+     */
+    public PropertyValuePrinter(SizeUnit sizeUnit, DurationUnit timeUnit, boolean isScriptFriendly) {
+        this.pimpl = new MyPropertyValueVisitor(sizeUnit, timeUnit, isScriptFriendly);
+    }
+
+    /**
+     * Print a property value according to the rules of this property value printer.
+     *
+     * @param <T>
+     *            The type of property value.
+     * @param pd
+     *            The property definition.
+     * @param value
+     *            The property value.
+     * @return Returns the string representation of the property value encoded according to the rules of this property
+     *         value printer.
+     */
+    public <T> LocalizableMessage print(PropertyDefinition<T> pd, T value) {
+        return pd.accept(pimpl, value, null);
+    }
+}
diff --git a/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/SetPropSubCommandHandler.java b/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/SetPropSubCommandHandler.java
new file mode 100644
index 0000000..f0aeac9
--- /dev/null
+++ b/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/SetPropSubCommandHandler.java
@@ -0,0 +1,910 @@
+/*
+ * 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-2010 Sun Microsystems, Inc.
+ *      Portions Copyright 2011-2014 ForgeRock AS
+ *      Portions Copyright 2012 profiq, s.r.o.
+ */
+package org.forgerock.opendj.config.dsconfig;
+
+import static com.forgerock.opendj.dsconfig.DsconfigMessages.*;
+import static com.forgerock.opendj.cli.CliMessages.*;
+import static org.forgerock.opendj.config.dsconfig.ArgumentExceptionFactory.displayMissingMandatoryPropertyException;
+import static org.forgerock.opendj.config.dsconfig.ArgumentExceptionFactory.displayOperationRejectedException;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.opendj.config.AggregationPropertyDefinition;
+import org.forgerock.opendj.config.DefinitionDecodingException;
+import org.forgerock.opendj.config.InstantiableRelationDefinition;
+import org.forgerock.opendj.config.ManagedObjectAlreadyExistsException;
+import org.forgerock.opendj.config.ManagedObjectDefinition;
+import org.forgerock.opendj.config.ManagedObjectNotFoundException;
+import org.forgerock.opendj.config.ManagedObjectPath;
+import org.forgerock.opendj.config.OptionalRelationDefinition;
+import org.forgerock.opendj.config.PropertyDefinition;
+import org.forgerock.opendj.config.PropertyException;
+import org.forgerock.opendj.config.PropertyOption;
+import org.forgerock.opendj.config.RelationDefinition;
+import org.forgerock.opendj.config.SetRelationDefinition;
+import org.forgerock.opendj.config.SingletonRelationDefinition;
+import org.forgerock.opendj.config.UndefinedDefaultBehaviorProvider;
+import org.forgerock.opendj.config.client.ConcurrentModificationException;
+import org.forgerock.opendj.config.client.ManagedObject;
+import org.forgerock.opendj.config.client.ManagedObjectDecodingException;
+import org.forgerock.opendj.config.client.ManagementContext;
+import org.forgerock.opendj.config.client.MissingMandatoryPropertiesException;
+import org.forgerock.opendj.config.client.OperationRejectedException;
+import org.forgerock.opendj.config.conditions.Condition;
+import org.forgerock.opendj.config.conditions.ContainsCondition;
+import org.forgerock.opendj.ldap.AuthorizationException;
+import org.forgerock.opendj.ldap.ErrorResultException;
+
+import com.forgerock.opendj.cli.Argument;
+import com.forgerock.opendj.cli.ArgumentException;
+import com.forgerock.opendj.cli.ClientException;
+import com.forgerock.opendj.cli.CommandBuilder;
+import com.forgerock.opendj.cli.ConsoleApplication;
+import com.forgerock.opendj.cli.MenuResult;
+import com.forgerock.opendj.cli.ReturnCode;
+import com.forgerock.opendj.cli.StringArgument;
+import com.forgerock.opendj.cli.SubCommand;
+import com.forgerock.opendj.cli.SubCommandArgumentParser;
+import com.forgerock.opendj.util.Pair;
+
+/**
+ * A sub-command handler which is used to modify the properties of a managed object.
+ * <p>
+ * This sub-command implements the various set-xxx-prop sub-commands.
+ */
+final class SetPropSubCommandHandler extends SubCommandHandler {
+
+    /**
+     * Type of modification being performed.
+     */
+    private static enum ModificationType {
+        /**
+         * Append a single value to the property.
+         */
+        ADD,
+
+        /**
+         * Remove a single value from the property.
+         */
+        REMOVE,
+
+        /**
+         * Append a single value to the property (first invocation removes existing values).
+         */
+        SET;
+    }
+
+    /**
+     * The value for the long option add.
+     */
+    private static final String OPTION_DSCFG_LONG_ADD = "add";
+
+    /**
+     * The value for the long option remove.
+     */
+    private static final String OPTION_DSCFG_LONG_REMOVE = "remove";
+
+    /**
+     * The value for the long option reset.
+     */
+    private static final String OPTION_DSCFG_LONG_RESET = "reset";
+
+    /**
+     * The value for the long option set.
+     */
+    private static final String OPTION_DSCFG_LONG_SET = "set";
+
+    /**
+     * The value for the short option add.
+     */
+    private static final Character OPTION_DSCFG_SHORT_ADD = null;
+
+    /**
+     * The value for the short option remove.
+     */
+    private static final Character OPTION_DSCFG_SHORT_REMOVE = null;
+
+    /**
+     * The value for the short option reset.
+     */
+    private static final Character OPTION_DSCFG_SHORT_RESET = null;
+
+    /**
+     * The value for the short option set.
+     */
+    private static final Character OPTION_DSCFG_SHORT_SET = null;
+
+    /**
+     * Creates a new set-xxx-prop sub-command for an instantiable relation.
+     *
+     * @param parser
+     *            The sub-command argument parser.
+     * @param path
+     *            The parent managed object path.
+     * @param r
+     *            The instantiable relation.
+     * @return Returns the new set-xxx-prop sub-command.
+     * @throws ArgumentException
+     *             If the sub-command could not be created successfully.
+     */
+    public static SetPropSubCommandHandler create(SubCommandArgumentParser parser, ManagedObjectPath<?, ?> path,
+            InstantiableRelationDefinition<?, ?> r) throws ArgumentException {
+        return new SetPropSubCommandHandler(parser, path.child(r, "DUMMY"), r);
+    }
+
+    /**
+     * Creates a new set-xxx-prop sub-command for an optional relation.
+     *
+     * @param parser
+     *            The sub-command argument parser.
+     * @param path
+     *            The parent managed object path.
+     * @param r
+     *            The optional relation.
+     * @return Returns the new set-xxx-prop sub-command.
+     * @throws ArgumentException
+     *             If the sub-command could not be created successfully.
+     */
+    public static SetPropSubCommandHandler create(SubCommandArgumentParser parser, ManagedObjectPath<?, ?> path,
+            OptionalRelationDefinition<?, ?> r) throws ArgumentException {
+        return new SetPropSubCommandHandler(parser, path.child(r), r);
+    }
+
+    /**
+     * Creates a new set-xxx-prop sub-command for a set relation.
+     *
+     * @param parser
+     *            The sub-command argument parser.
+     * @param path
+     *            The parent managed object path.
+     * @param r
+     *            The set relation.
+     * @return Returns the new set-xxx-prop sub-command.
+     * @throws ArgumentException
+     *             If the sub-command could not be created successfully.
+     */
+    public static SetPropSubCommandHandler create(SubCommandArgumentParser parser, ManagedObjectPath<?, ?> path,
+            SetRelationDefinition<?, ?> r) throws ArgumentException {
+        return new SetPropSubCommandHandler(parser, path.child(r), r);
+    }
+
+    /**
+     * Creates a new set-xxx-prop sub-command for a singleton relation.
+     *
+     * @param parser
+     *            The sub-command argument parser.
+     * @param path
+     *            The parent managed object path.
+     * @param r
+     *            The singleton relation.
+     * @return Returns the new set-xxx-prop sub-command.
+     * @throws ArgumentException
+     *             If the sub-command could not be created successfully.
+     */
+    public static SetPropSubCommandHandler create(SubCommandArgumentParser parser, ManagedObjectPath<?, ?> path,
+            SingletonRelationDefinition<?, ?> r) throws ArgumentException {
+        return new SetPropSubCommandHandler(parser, path.child(r), r);
+    }
+
+    /**
+     * Configure the provided managed object and updates the command builder in the pased SetPropSubCommandHandler
+     * object.
+     *
+     * @param app
+     *            The console application.
+     * @param context
+     *            The management context.
+     * @param mo
+     *            The managed object to be configured.
+     * @param handler
+     *            The SubCommandHandler whose command builder properties must be updated.
+     * @return Returns a MenuResult.success() if the managed object was configured successfully, or MenuResult.quit(),
+     *         or MenuResult.cancel(), if the managed object was edited interactively and the user chose to quit or
+     *         cancel.
+     * @throws ClientException
+     *             If an unrecoverable client exception occurred whilst interacting with the server.
+     * @throws ClientException
+     *             If an error occurred whilst interacting with the console.
+     */
+    public static MenuResult<Void> modifyManagedObject(ConsoleApplication app, ManagementContext context,
+            ManagedObject<?> mo, SubCommandHandler handler) throws ClientException {
+        ManagedObjectDefinition<?, ?> d = mo.getManagedObjectDefinition();
+        LocalizableMessage ufn = d.getUserFriendlyName();
+
+        PropertyValueEditor editor = new PropertyValueEditor(app, context);
+        while (true) {
+            // Interactively set properties if applicable.
+            if (app.isInteractive()) {
+                SortedSet<PropertyDefinition<?>> properties = new TreeSet<PropertyDefinition<?>>();
+                for (PropertyDefinition<?> pd : d.getAllPropertyDefinitions()) {
+                    if (pd.hasOption(PropertyOption.HIDDEN)) {
+                        continue;
+                    }
+                    if (!app.isAdvancedMode() && pd.hasOption(PropertyOption.ADVANCED)) {
+                        continue;
+                    }
+                    properties.add(pd);
+                }
+
+                MenuResult<Void> result = editor.edit(mo, properties, false);
+
+                // Interactively enable/edit referenced components.
+                if (result.isSuccess()) {
+                    result = checkReferences(app, context, mo, handler);
+                    if (result.isAgain()) {
+                        // Edit again.
+                        continue;
+                    }
+                }
+
+                if (result.isQuit()) {
+                    if (!app.isMenuDrivenMode()) {
+                        // User chose to cancel any changes.
+                        app.println();
+                        app.println(INFO_DSCFG_CONFIRM_MODIFY_FAIL.get(ufn));
+                    }
+                    return MenuResult.quit();
+                } else if (result.isCancel()) {
+                    return MenuResult.cancel();
+                }
+            }
+
+            try {
+                // Commit the changes if necessary
+                if (mo.isModified()) {
+                    mo.commit();
+
+                    // Output success message.
+                    if (app.isVerbose() || app.isInteractive()) {
+                        app.println();
+                        app.println(INFO_DSCFG_CONFIRM_MODIFY_SUCCESS.get(ufn));
+                    }
+
+                    for (PropertyEditorModification<?> mod : editor.getModifications()) {
+                        try {
+                            handler.getCommandBuilder().addArgument(createArgument(mod));
+                        } catch (ArgumentException ae) {
+                            // This is a bug
+                            throw new RuntimeException("Unexpected error generating the command builder: " + ae, ae);
+                        }
+                    }
+
+                    handler.setCommandBuilderUseful(true);
+                }
+
+                return MenuResult.success();
+            } catch (MissingMandatoryPropertiesException e) {
+                if (app.isInteractive()) {
+                    // If interactive, give the user the chance to fix the
+                    // problems.
+                    app.println();
+                    displayMissingMandatoryPropertyException(app, e);
+                    app.println();
+                    if (!app.confirmAction(INFO_DSCFG_PROMPT_EDIT_AGAIN.get(ufn), true)) {
+                        return MenuResult.cancel();
+                    }
+                } else {
+                    throw new ClientException(ReturnCode.CONSTRAINT_VIOLATION, e.getMessageObject(), e);
+                }
+            } catch (AuthorizationException e) {
+                LocalizableMessage msg = ERR_DSCFG_ERROR_MODIFY_AUTHZ.get(ufn);
+                throw new ClientException(ReturnCode.INSUFFICIENT_ACCESS_RIGHTS, msg);
+            } catch (ConcurrentModificationException e) {
+                LocalizableMessage msg = ERR_DSCFG_ERROR_MODIFY_CME.get(ufn);
+                throw new ClientException(ReturnCode.CONSTRAINT_VIOLATION, msg);
+            } catch (OperationRejectedException e) {
+                if (app.isInteractive()) {
+                    // If interactive, give the user the chance to fix the
+                    // problems.
+                    app.println();
+                    displayOperationRejectedException(app, e);
+                    app.println();
+                    if (!app.confirmAction(INFO_DSCFG_PROMPT_EDIT_AGAIN.get(ufn), true)) {
+                        return MenuResult.cancel();
+                    }
+                } else {
+                    throw new ClientException(ReturnCode.CONSTRAINT_VIOLATION, e.getMessageObject(), e);
+                }
+            } catch (ErrorResultException e) {
+                LocalizableMessage msg = ERR_DSCFG_ERROR_MODIFY_CE.get(ufn, e.getMessage());
+                throw new ClientException(ReturnCode.OTHER, msg);
+            } catch (ManagedObjectAlreadyExistsException e) {
+                // Should never happen.
+                throw new IllegalStateException(e);
+            }
+        }
+    }
+
+    /**
+     * Check that any referenced components are enabled if required.
+     */
+    private static MenuResult<Void> checkReferences(ConsoleApplication app, ManagementContext context,
+            ManagedObject<?> mo, SubCommandHandler handler) throws ClientException {
+        ManagedObjectDefinition<?, ?> d = mo.getManagedObjectDefinition();
+        LocalizableMessage ufn = d.getUserFriendlyName();
+
+        try {
+            for (PropertyDefinition<?> pd : d.getAllPropertyDefinitions()) {
+                if (pd instanceof AggregationPropertyDefinition<?, ?>) {
+                    AggregationPropertyDefinition<?, ?> apd = (AggregationPropertyDefinition<?, ?>) pd;
+
+                    // Skip this aggregation if the referenced managed objects
+                    // do not need to be enabled.
+                    if (!apd.getTargetNeedsEnablingCondition().evaluate(context, mo)) {
+                        continue;
+                    }
+
+                    // The referenced component(s) must be enabled.
+                    for (String name : mo.getPropertyValues(apd)) {
+                        ManagedObjectPath<?, ?> path = apd.getChildPath(name);
+                        LocalizableMessage rufn = path.getManagedObjectDefinition().getUserFriendlyName();
+                        ManagedObject<?> ref;
+                        try {
+                            ref = context.getManagedObject(path);
+                        } catch (DefinitionDecodingException e) {
+                            LocalizableMessage msg = ERR_DSCFG_ERROR_GET_CHILD_DDE.get(rufn, rufn, rufn);
+                            throw new ClientException(ReturnCode.OTHER, msg);
+                        } catch (ManagedObjectDecodingException e) {
+                            // FIXME: should not abort here. Instead, display the
+                            // errors (if verbose) and apply the changes to the
+                            // partial managed object.
+                            LocalizableMessage msg = ERR_DSCFG_ERROR_GET_CHILD_MODE.get(rufn);
+                            throw new ClientException(ReturnCode.OTHER, msg, e);
+                        } catch (ManagedObjectNotFoundException e) {
+                            LocalizableMessage msg = ERR_DSCFG_ERROR_GET_CHILD_MONFE.get(rufn);
+                            throw new ClientException(ReturnCode.NO_SUCH_OBJECT, msg);
+                        }
+
+                        Condition condition = apd.getTargetIsEnabledCondition();
+                        while (!condition.evaluate(context, ref)) {
+                            boolean isBadReference = true;
+
+                            if (condition instanceof ContainsCondition) {
+                                // Attempt to automatically enable the managed object.
+                                ContainsCondition cvc = (ContainsCondition) condition;
+                                app.println();
+                                if (app.confirmAction(
+                                        INFO_EDITOR_PROMPT_ENABLED_REFERENCED_COMPONENT.get(rufn, name, ufn), true)) {
+                                    cvc.setPropertyValue(ref);
+                                    try {
+                                        ref.commit();
+
+                                        // Try to create the command builder
+                                        if (app instanceof DSConfig && app.isInteractive()) {
+                                            DSConfig dsConfig = (DSConfig) app;
+                                            String subCommandName = "set-" + path.getRelationDefinition().getName()
+                                                    + "-prop";
+                                            CommandBuilder builder = dsConfig.getCommandBuilder(subCommandName);
+
+                                            if (path.getRelationDefinition()
+                                                    instanceof InstantiableRelationDefinition<?, ?>) {
+                                                String argName = CLIProfile.getInstance().getNamingArgument(
+                                                        path.getRelationDefinition());
+                                                try {
+                                                    StringArgument arg = new StringArgument(argName, null, argName,
+                                                            false, true, INFO_NAME_PLACEHOLDER.get(),
+                                                            INFO_DSCFG_DESCRIPTION_NAME.get(d.getUserFriendlyName()));
+                                                    arg.addValue(name);
+                                                    builder.addArgument(arg);
+                                                } catch (Throwable t) {
+                                                    // Bug
+                                                    throw new RuntimeException("Unexpected error: " + t, t);
+                                                }
+                                            }
+
+                                            try {
+                                                StringArgument arg = new StringArgument(OPTION_DSCFG_LONG_SET,
+                                                        OPTION_DSCFG_SHORT_SET, OPTION_DSCFG_LONG_SET, false, true,
+                                                        true, INFO_VALUE_SET_PLACEHOLDER.get(), null, null,
+                                                        INFO_DSCFG_DESCRIPTION_PROP_VAL.get());
+                                                PropertyDefinition<?> propertyDefinition = cvc.getPropertyDefinition();
+                                                arg.addValue(propertyDefinition.getName() + ':'
+                                                        + castAndGetArgumentValue(propertyDefinition, cvc.getValue()));
+                                                builder.addArgument(arg);
+                                            } catch (Throwable t) {
+                                                // Bug
+                                                throw new RuntimeException("Unexpected error: " + t, t);
+                                            }
+                                            dsConfig.printCommandBuilder(builder);
+                                        }
+
+                                        isBadReference = false;
+                                    } catch (MissingMandatoryPropertiesException e) {
+                                        // Give the user the chance to fix the problems.
+                                        app.println();
+                                        displayMissingMandatoryPropertyException(app, e);
+                                        app.println();
+                                        if (app.confirmAction(INFO_DSCFG_PROMPT_EDIT.get(rufn), true)) {
+                                            MenuResult<Void> result = modifyManagedObject(app, context, ref, handler);
+                                            if (result.isQuit()) {
+                                                return result;
+                                            } else if (result.isSuccess()) {
+                                                // The referenced component was modified
+                                                // successfully, but may still be disabled.
+                                                isBadReference = false;
+                                            }
+                                        }
+                                    } catch (ConcurrentModificationException e) {
+                                        LocalizableMessage msg = ERR_DSCFG_ERROR_MODIFY_CME.get(ufn);
+                                        throw new ClientException(ReturnCode.CONSTRAINT_VIOLATION, msg);
+                                    } catch (OperationRejectedException e) {
+                                        // Give the user the chance to fix the problems.
+                                        app.println();
+                                        displayOperationRejectedException(app, e);
+                                        app.println();
+                                        if (app.confirmAction(INFO_DSCFG_PROMPT_EDIT.get(rufn), true)) {
+                                            MenuResult<Void> result = modifyManagedObject(app, context, ref, handler);
+                                            if (result.isQuit()) {
+                                                return result;
+                                            } else if (result.isSuccess()) {
+                                                // The referenced component was modified
+                                                // successfully, but may still be disabled.
+                                                isBadReference = false;
+                                            }
+                                        }
+                                    } catch (ManagedObjectAlreadyExistsException e) {
+                                        // Should never happen.
+                                        throw new IllegalStateException(e);
+                                    }
+                                }
+                            } else {
+                                app.println();
+                                if (app.confirmAction(INFO_DSCFG_PROMPT_EDIT_TO_ENABLE.get(rufn, name, ufn), true)) {
+                                    MenuResult<Void> result = SetPropSubCommandHandler.modifyManagedObject(app,
+                                            context, ref, handler);
+                                    if (result.isQuit()) {
+                                        return result;
+                                    } else if (result.isSuccess()) {
+                                        // The referenced component was modified
+                                        // successfully, but may still be disabled.
+                                        isBadReference = false;
+                                    }
+                                }
+                            }
+
+                            // If the referenced component is still disabled because
+                            // the user refused to modify it, then give the used the
+                            // option of editing the referencing component.
+                            if (isBadReference) {
+                                app.println();
+                                app.println(ERR_SET_REFERENCED_COMPONENT_DISABLED.get(ufn, rufn));
+                                app.println();
+                                if (app.confirmAction(INFO_DSCFG_PROMPT_EDIT_AGAIN.get(ufn), true)) {
+                                    return MenuResult.again();
+                                } else {
+                                    return MenuResult.cancel();
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        } catch (AuthorizationException e) {
+            LocalizableMessage msg = ERR_DSCFG_ERROR_MODIFY_AUTHZ.get(ufn);
+            throw new ClientException(ReturnCode.INSUFFICIENT_ACCESS_RIGHTS, msg);
+        } catch (ErrorResultException e) {
+            LocalizableMessage msg = ERR_DSCFG_ERROR_MODIFY_CE.get(ufn, e.getMessage());
+            throw new ClientException(ReturnCode.OTHER, msg);
+        }
+
+        return MenuResult.success();
+    }
+
+    /** The sub-commands naming arguments. */
+    private final List<StringArgument> namingArgs;
+
+    /** The path of the managed object. */
+    private final ManagedObjectPath<?, ?> path;
+
+    /**
+     * The argument which should be used to specify zero or more property value adds.
+     */
+    private final StringArgument propertyAddArgument;
+
+    /**
+     * The argument which should be used to specify zero or more property value removes.
+     */
+    private final StringArgument propertyRemoveArgument;
+
+    /**
+     * The argument which should be used to specify zero or more property value resets.
+     */
+    private final StringArgument propertyResetArgument;
+
+    /**
+     * The argument which should be used to specify zero or more property value assignments.
+     */
+    private final StringArgument propertySetArgument;
+
+    /** The sub-command associated with this handler. */
+    private final SubCommand subCommand;
+
+    /** Private constructor. */
+    private SetPropSubCommandHandler(SubCommandArgumentParser parser, ManagedObjectPath<?, ?> path,
+            RelationDefinition<?, ?> r) throws ArgumentException {
+        this.path = path;
+
+        // Create the sub-command.
+        String name = "set-" + r.getName() + "-prop";
+        LocalizableMessage description = INFO_DSCFG_DESCRIPTION_SUBCMD_SETPROP.get(r.getChildDefinition()
+                .getUserFriendlyName());
+        this.subCommand = new SubCommand(parser, name, false, 0, 0, null, description);
+
+        // Create the naming arguments.
+        this.namingArgs = createNamingArgs(subCommand, path, false);
+
+        // Create the --set argument.
+        this.propertySetArgument = new StringArgument(OPTION_DSCFG_LONG_SET, OPTION_DSCFG_SHORT_SET,
+                OPTION_DSCFG_LONG_SET, false, true, true, INFO_VALUE_SET_PLACEHOLDER.get(), null, null,
+                INFO_DSCFG_DESCRIPTION_PROP_VAL.get());
+        this.subCommand.addArgument(this.propertySetArgument);
+
+        // Create the --reset argument.
+        this.propertyResetArgument = new StringArgument(OPTION_DSCFG_LONG_RESET, OPTION_DSCFG_SHORT_RESET,
+                OPTION_DSCFG_LONG_RESET, false, true, true, INFO_PROPERTY_PLACEHOLDER.get(), null, null,
+                INFO_DSCFG_DESCRIPTION_RESET_PROP.get());
+        this.subCommand.addArgument(this.propertyResetArgument);
+
+        // Create the --add argument.
+        this.propertyAddArgument = new StringArgument(OPTION_DSCFG_LONG_ADD, OPTION_DSCFG_SHORT_ADD,
+                OPTION_DSCFG_LONG_ADD, false, true, true, INFO_VALUE_SET_PLACEHOLDER.get(), null, null,
+                INFO_DSCFG_DESCRIPTION_ADD_PROP_VAL.get());
+        this.subCommand.addArgument(this.propertyAddArgument);
+
+        // Create the --remove argument.
+        this.propertyRemoveArgument = new StringArgument(OPTION_DSCFG_LONG_REMOVE, OPTION_DSCFG_SHORT_REMOVE,
+                OPTION_DSCFG_LONG_REMOVE, false, true, true, INFO_VALUE_SET_PLACEHOLDER.get(), null, null,
+                INFO_DSCFG_DESCRIPTION_REMOVE_PROP_VAL.get());
+        this.subCommand.addArgument(this.propertyRemoveArgument);
+
+        // Register the tags associated with the child managed objects.
+        addTags(path.getManagedObjectDefinition().getAllTags());
+    }
+
+    /**
+     * Gets the relation definition associated with the type of component that this sub-command handles.
+     *
+     * @return Returns the relation definition associated with the type of component that this sub-command handles.
+     */
+    public RelationDefinition<?, ?> getRelationDefinition() {
+        return path.getRelationDefinition();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public SubCommand getSubCommand() {
+        return subCommand;
+    }
+
+    /** {@inheritDoc} */
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    @Override
+    public MenuResult<Integer> run(ConsoleApplication app, LDAPManagementContextFactory factory)
+            throws ArgumentException, ClientException {
+        // Get the naming argument values.
+        List<String> names = getNamingArgValues(app, namingArgs);
+
+        // Reset the command builder
+        getCommandBuilder().clearArguments();
+
+        setCommandBuilderUseful(false);
+
+        // Update the command builder.
+        updateCommandBuilderWithSubCommand();
+
+        // Get the targeted managed object.
+        LocalizableMessage ufn = path.getRelationDefinition().getUserFriendlyName();
+        ManagementContext context = factory.getManagementContext(app);
+        MenuResult<ManagedObject<?>> result;
+        try {
+            result = getManagedObject(app, context, path, names);
+        } catch (AuthorizationException e) {
+            LocalizableMessage msg = ERR_DSCFG_ERROR_MODIFY_AUTHZ.get(ufn);
+            throw new ClientException(ReturnCode.INSUFFICIENT_ACCESS_RIGHTS, msg);
+        } catch (DefinitionDecodingException e) {
+            LocalizableMessage msg = ERR_DSCFG_ERROR_GET_CHILD_DDE.get(ufn, ufn, ufn);
+            throw new ClientException(ReturnCode.OTHER, msg);
+        } catch (ManagedObjectDecodingException e) {
+            // FIXME: should not abort here. Instead, display the errors (if
+            // verbose) and apply the changes to the partial managed object.
+            LocalizableMessage msg = ERR_DSCFG_ERROR_GET_CHILD_MODE.get(ufn);
+            throw new ClientException(ReturnCode.OTHER, msg, e);
+        } catch (ConcurrentModificationException e) {
+            LocalizableMessage msg = ERR_DSCFG_ERROR_MODIFY_CME.get(ufn);
+            throw new ClientException(ReturnCode.CONSTRAINT_VIOLATION, msg);
+        } catch (ManagedObjectNotFoundException e) {
+            String objName = names.get(names.size() - 1);
+            ArgumentException except = null;
+            LocalizableMessage msg;
+            // if object name is 'null', get a user-friendly string to represent this
+            if (objName == null) {
+                msg = ERR_DSCFG_ERROR_FINDER_NO_CHILDREN_NULL.get();
+                except = new ArgumentException(msg);
+            } else {
+                except = ArgumentExceptionFactory.unknownValueForChildComponent("\"" + objName + "\"");
+            }
+            if (app.isInteractive()) {
+                app.println();
+                app.printVerboseMessage(except.getMessageObject());
+                return MenuResult.cancel();
+            } else {
+                throw except;
+            }
+        }
+
+        if (result.isQuit()) {
+            if (!app.isMenuDrivenMode()) {
+                // User chose to quit.
+                app.println();
+                app.println(INFO_DSCFG_CONFIRM_MODIFY_FAIL.get(ufn));
+            }
+            return MenuResult.quit();
+        } else if (result.isCancel()) {
+            return MenuResult.cancel();
+        }
+
+        ManagedObject<?> child = result.getValue();
+        ManagedObjectDefinition<?, ?> d = child.getManagedObjectDefinition();
+        Map<String, ModificationType> lastModTypes = new HashMap<String, ModificationType>();
+        Map<PropertyDefinition, Set> changes = new HashMap<PropertyDefinition, Set>();
+
+        // Reset properties.
+        for (String m : propertyResetArgument.getValues()) {
+
+            // Check one does not try to reset with a value
+            if (m.contains(":")) {
+                throw ArgumentExceptionFactory.unableToResetPropertyWithValue(m, OPTION_DSCFG_LONG_RESET);
+            }
+
+            PropertyDefinition<?> pd = getPropertyDefinition(d, m);
+
+            // Mandatory properties which have no defined defaults cannot be reset.
+            if (pd.hasOption(PropertyOption.MANDATORY)
+                    && pd.getDefaultBehaviorProvider() instanceof UndefinedDefaultBehaviorProvider) {
+                throw ArgumentExceptionFactory.unableToResetMandatoryProperty(d, m, OPTION_DSCFG_LONG_SET);
+            }
+
+            // Save the modification type.
+            lastModTypes.put(m, ModificationType.SET);
+
+            // Apply the modification.
+            modifyPropertyValues(child, pd, changes, ModificationType.SET, null);
+        }
+
+        // Set properties.
+        for (String m : propertySetArgument.getValues()) {
+            Pair<String, String> pair = parseValue(m);
+            String propertyName = pair.getFirst();
+            String value = pair.getSecond();
+
+            PropertyDefinition<?> pd = getPropertyDefinition(d, propertyName);
+
+            // Apply the modification.
+            if (lastModTypes.containsKey(propertyName)) {
+                modifyPropertyValues(child, pd, changes, ModificationType.ADD, value);
+            } else {
+                lastModTypes.put(propertyName, ModificationType.SET);
+                modifyPropertyValues(child, pd, changes, ModificationType.SET, value);
+            }
+        }
+
+        // Remove properties.
+        for (String m : propertyRemoveArgument.getValues()) {
+            Pair<String, String> pair = parseValue(m);
+            String propertyName = pair.getFirst();
+            String value = pair.getSecond();
+
+            PropertyDefinition<?> pd = getPropertyDefinition(d, propertyName);
+
+            // Apply the modification.
+            if (lastModTypes.containsKey(propertyName) && lastModTypes.get(propertyName) == ModificationType.SET) {
+                throw ArgumentExceptionFactory.incompatiblePropertyModification(m);
+            }
+
+            lastModTypes.put(propertyName, ModificationType.REMOVE);
+            modifyPropertyValues(child, pd, changes, ModificationType.REMOVE, value);
+        }
+
+        // Add properties.
+        for (String m : propertyAddArgument.getValues()) {
+            Pair<String, String> pair = parseValue(m);
+            String propertyName = pair.getFirst();
+            String value = pair.getSecond();
+
+            PropertyDefinition<?> pd = getPropertyDefinition(d, propertyName);
+
+            // Apply the modification.
+            if (lastModTypes.containsKey(propertyName) && lastModTypes.get(propertyName) == ModificationType.SET) {
+                throw ArgumentExceptionFactory.incompatiblePropertyModification(m);
+            }
+
+            lastModTypes.put(propertyName, ModificationType.ADD);
+            modifyPropertyValues(child, pd, changes, ModificationType.ADD, value);
+        }
+
+        // Apply the command line changes.
+        for (PropertyDefinition<?> pd : changes.keySet()) {
+            try {
+                child.setPropertyValues(pd, changes.get(pd));
+            } catch (PropertyException e) {
+                throw ArgumentExceptionFactory.adaptPropertyException(e, d);
+            }
+            setCommandBuilderUseful(true);
+        }
+
+        // Now the command line changes have been made, apply the changes
+        // interacting with the user to fix any problems if required.
+        MenuResult<Void> result2 = modifyManagedObject(app, context, child, this);
+        if (result2.isCancel()) {
+            return MenuResult.cancel();
+        } else if (result2.isQuit()) {
+            return MenuResult.quit();
+        } else {
+            if (propertyResetArgument.hasValue()) {
+                getCommandBuilder().addArgument(propertyResetArgument);
+            }
+            if (propertySetArgument.hasValue()) {
+                getCommandBuilder().addArgument(propertySetArgument);
+            }
+            if (propertyAddArgument.hasValue()) {
+                getCommandBuilder().addArgument(propertyAddArgument);
+            }
+            if (propertyRemoveArgument.hasValue()) {
+                getCommandBuilder().addArgument(propertyRemoveArgument);
+            }
+            return MenuResult.success(0);
+        }
+    }
+
+    /** Parse and check the property "property:value". */
+    private Pair<String, String> parseValue(String m) throws ArgumentException {
+        int sep = m.indexOf(':');
+        if (sep < 0) {
+            throw ArgumentExceptionFactory.missingSeparatorInPropertyArgument(m);
+        }
+        if (sep == 0) {
+            throw ArgumentExceptionFactory.missingNameInPropertyArgument(m);
+        }
+
+        String propertyName = m.substring(0, sep);
+        String value = m.substring(sep + 1, m.length());
+        if (value.length() == 0) {
+            throw ArgumentExceptionFactory.missingValueInPropertyArgument(m);
+        }
+        return Pair.of(propertyName, value);
+    }
+
+    /** Get and check the property definition. */
+    private PropertyDefinition<?> getPropertyDefinition(ManagedObjectDefinition<?, ?> def, String propertyName)
+            throws ArgumentException {
+        try {
+            return def.getPropertyDefinition(propertyName);
+        } catch (IllegalArgumentException e) {
+            throw ArgumentExceptionFactory.unknownProperty(def, propertyName);
+        }
+    }
+
+    /** Apply a single modification to the current change-set. */
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    private <T> void modifyPropertyValues(ManagedObject<?> mo, PropertyDefinition<T> pd,
+            Map<PropertyDefinition, Set> changes, ModificationType modType, String s) throws ArgumentException {
+        Set<T> values = changes.get(pd);
+        if (values == null) {
+            values = mo.getPropertyValues(pd);
+        }
+
+        if (s == null || s.length() == 0) {
+            // Reset back to defaults.
+            values.clear();
+        } else {
+            T value;
+            try {
+                value = pd.decodeValue(s);
+            } catch (PropertyException e) {
+                throw ArgumentExceptionFactory.adaptPropertyException(e, mo.getManagedObjectDefinition());
+            }
+
+            switch (modType) {
+            case ADD:
+                values.add(value);
+                break;
+            case REMOVE:
+                if (!values.remove(value)) {
+                    // value was not part of values
+                    throw ArgumentExceptionFactory.unknownValueForMultiValuedProperty(s, pd.getName());
+                }
+                break;
+            case SET:
+                values = new TreeSet<T>(pd);
+                values.add(value);
+                break;
+            }
+        }
+
+        changes.put(pd, values);
+    }
+
+    /**
+     * Creates an argument (the one that the user should provide in the command-line) that is equivalent to the
+     * modification proposed by the user in the provided PropertyEditorModification object.
+     *
+     * @param mod
+     *            the object describing the modification made.
+     * @param <T>
+     *            the type of the property to be retrieved.
+     * @return the argument representing the modification.
+     * @throws ArgumentException
+     *             if there is a problem creating the argument.
+     */
+    private static <T> Argument createArgument(PropertyEditorModification<T> mod) throws ArgumentException {
+        StringArgument arg;
+
+        PropertyDefinition<T> propertyDefinition = mod.getPropertyDefinition();
+        String propName = propertyDefinition.getName();
+
+        switch (mod.getType()) {
+        case RESET:
+            arg = new StringArgument(OPTION_DSCFG_LONG_RESET, OPTION_DSCFG_SHORT_RESET, OPTION_DSCFG_LONG_RESET, false,
+                    true, true, INFO_PROPERTY_PLACEHOLDER.get(), null, null, INFO_DSCFG_DESCRIPTION_RESET_PROP.get());
+            arg.addValue(propName);
+            break;
+        case REMOVE:
+            arg = new StringArgument(OPTION_DSCFG_LONG_REMOVE, OPTION_DSCFG_SHORT_REMOVE, OPTION_DSCFG_LONG_REMOVE,
+                    false, true, true, INFO_VALUE_SET_PLACEHOLDER.get(), null, null,
+                    INFO_DSCFG_DESCRIPTION_REMOVE_PROP_VAL.get());
+            for (T value : mod.getModificationValues()) {
+                arg.addValue(propName + ':' + getArgumentValue(propertyDefinition, value));
+            }
+            break;
+        case ADD:
+            arg = new StringArgument(OPTION_DSCFG_LONG_ADD, OPTION_DSCFG_SHORT_ADD, OPTION_DSCFG_LONG_ADD, false, true,
+                    true, INFO_VALUE_SET_PLACEHOLDER.get(), null, null, INFO_DSCFG_DESCRIPTION_ADD_PROP_VAL.get());
+            for (T value : mod.getModificationValues()) {
+                arg.addValue(propName + ':' + getArgumentValue(propertyDefinition, value));
+            }
+            break;
+        case SET:
+            arg = new StringArgument(OPTION_DSCFG_LONG_SET, OPTION_DSCFG_SHORT_SET, OPTION_DSCFG_LONG_SET, false, true,
+                    true, INFO_VALUE_SET_PLACEHOLDER.get(), null, null, INFO_DSCFG_DESCRIPTION_PROP_VAL.get());
+            for (T value : mod.getModificationValues()) {
+                arg.addValue(propName + ':' + getArgumentValue(propertyDefinition, value));
+            }
+            break;
+        default:
+            // Bug
+            throw new IllegalStateException("Unknown modification type: " + mod.getType());
+        }
+        return arg;
+    }
+}
diff --git a/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/SubCommandHandler.java b/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/SubCommandHandler.java
new file mode 100644
index 0000000..463cc8e
--- /dev/null
+++ b/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/SubCommandHandler.java
@@ -0,0 +1,1343 @@
+/*
+ * 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-2010 Sun Microsystems, Inc.
+ *      Portions Copyright 2011-2014 ForgeRock AS
+ */
+package org.forgerock.opendj.config.dsconfig;
+
+import static com.forgerock.opendj.dsconfig.DsconfigMessages.*;
+import static com.forgerock.opendj.cli.CliMessages.*;
+import static org.forgerock.opendj.config.dsconfig.DSConfig.PROPERTY_SCRIPT_NAME;
+
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.TreeMap;
+
+import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.opendj.config.AbstractManagedObjectDefinition;
+import org.forgerock.opendj.config.Configuration;
+import org.forgerock.opendj.config.ConfigurationClient;
+import org.forgerock.opendj.config.DefinitionDecodingException;
+import org.forgerock.opendj.config.DurationUnit;
+import org.forgerock.opendj.config.InstantiableRelationDefinition;
+import org.forgerock.opendj.config.ManagedObjectDefinition;
+import org.forgerock.opendj.config.ManagedObjectNotFoundException;
+import org.forgerock.opendj.config.ManagedObjectOption;
+import org.forgerock.opendj.config.ManagedObjectPath;
+import org.forgerock.opendj.config.ManagedObjectPathSerializer;
+import org.forgerock.opendj.config.OptionalRelationDefinition;
+import org.forgerock.opendj.config.PropertyDefinition;
+import org.forgerock.opendj.config.PropertyDefinitionUsageBuilder;
+import org.forgerock.opendj.config.RelationDefinition;
+import org.forgerock.opendj.config.SetRelationDefinition;
+import org.forgerock.opendj.config.SingletonRelationDefinition;
+import org.forgerock.opendj.config.SizeUnit;
+import org.forgerock.opendj.config.Tag;
+import org.forgerock.opendj.config.client.ConcurrentModificationException;
+import org.forgerock.opendj.config.client.IllegalManagedObjectNameException;
+import org.forgerock.opendj.config.client.ManagedObject;
+import org.forgerock.opendj.config.client.ManagedObjectDecodingException;
+import org.forgerock.opendj.config.client.ManagementContext;
+import org.forgerock.opendj.ldap.AuthorizationException;
+import org.forgerock.opendj.ldap.ErrorResultException;
+
+import com.forgerock.opendj.cli.Argument;
+import com.forgerock.opendj.cli.ArgumentException;
+import com.forgerock.opendj.cli.BooleanArgument;
+import com.forgerock.opendj.cli.ClientException;
+import com.forgerock.opendj.cli.CommandBuilder;
+import com.forgerock.opendj.cli.ConsoleApplication;
+import com.forgerock.opendj.cli.Menu;
+import com.forgerock.opendj.cli.MenuBuilder;
+import com.forgerock.opendj.cli.MenuResult;
+import com.forgerock.opendj.cli.ReturnCode;
+import com.forgerock.opendj.cli.StringArgument;
+import com.forgerock.opendj.cli.SubCommand;
+import com.forgerock.opendj.cli.TabSeparatedTablePrinter;
+import com.forgerock.opendj.cli.TablePrinter;
+
+/**
+ * An interface for sub-command implementations.
+ */
+abstract class SubCommandHandler implements Comparable<SubCommandHandler> {
+
+    /**
+     * A path serializer which is used to retrieve a managed object based on a path and a list of path arguments.
+     */
+    private class ManagedObjectFinder implements ManagedObjectPathSerializer {
+
+        /** The console application. */
+        private ConsoleApplication app;
+
+        /** The index of the next path argument to be retrieved. */
+        private int argIndex;
+
+        /** The list of managed object path arguments. */
+        private List<String> args;
+
+        private AuthorizationException authze;
+
+        private ErrorResultException ere;
+
+        /**
+         * Any CLI exception that was caught when attempting to find the managed object.
+         */
+        private ClientException clie;
+
+        private ConcurrentModificationException cme;
+
+        /**
+         * Any operation exception that was caught when attempting to find the managed object.
+         */
+        private DefinitionDecodingException dde;
+
+        private ManagedObjectDecodingException mode;
+
+        private ManagedObjectNotFoundException monfe;
+
+        /** The current result. */
+        private MenuResult<ManagedObject<?>> result;
+
+        /** {@inheritDoc} */
+        public <C extends ConfigurationClient, S extends Configuration> void appendManagedObjectPathElement(
+                InstantiableRelationDefinition<? super C, ? super S> r, AbstractManagedObjectDefinition<C, S> d,
+                String name) {
+            if (result.isSuccess()) {
+                // We should ignore the "template" name here and use a path
+                // argument.
+                String childName = args.get(argIndex++);
+
+                try {
+                    // If the name is null then we must be interactive - so let
+                    // the user choose.
+                    if (childName == null) {
+                        try {
+                            MenuResult<String> sresult = readChildName(app, result.getValue(), r, d);
+
+                            if (sresult.isCancel()) {
+                                result = MenuResult.cancel();
+                                return;
+                            } else if (sresult.isQuit()) {
+                                result = MenuResult.quit();
+                                return;
+                            } else {
+                                childName = sresult.getValue();
+                            }
+                        } catch (ClientException e) {
+                            clie = e;
+                            result = MenuResult.quit();
+                            return;
+                        }
+                    } else if (childName.trim().length() == 0) {
+                        IllegalManagedObjectNameException e = new IllegalManagedObjectNameException(childName);
+                        clie = ArgumentExceptionFactory.adaptIllegalManagedObjectNameException(e, d);
+                        result = MenuResult.quit();
+                        return;
+                    }
+
+                    ManagedObject<?> child = result.getValue().getChild(r, childName);
+
+                    // Check that child is a sub-type of the specified
+                    // definition.
+                    if (!child.getManagedObjectDefinition().isChildOf(d)) {
+                        clie = ArgumentExceptionFactory.wrongManagedObjectType(r, child.getManagedObjectDefinition(),
+                                getSubCommand().getName());
+                        result = MenuResult.quit();
+                    } else {
+                        result = MenuResult.<ManagedObject<?>> success(child);
+                    }
+                } catch (DefinitionDecodingException e) {
+                    dde = e;
+                    result = MenuResult.quit();
+                } catch (ManagedObjectDecodingException e) {
+                    mode = e;
+                    result = MenuResult.quit();
+                } catch (AuthorizationException e) {
+                    authze = e;
+                    result = MenuResult.quit();
+                } catch (ManagedObjectNotFoundException e) {
+                    monfe = e;
+                    result = MenuResult.quit();
+                } catch (ConcurrentModificationException e) {
+                    cme = e;
+                    result = MenuResult.quit();
+                } catch (ErrorResultException e) {
+                    ere = e;
+                    result = MenuResult.quit();
+                }
+            }
+        }
+
+        /** {@inheritDoc} */
+        public <C extends ConfigurationClient, S extends Configuration> void appendManagedObjectPathElement(
+                OptionalRelationDefinition<? super C, ? super S> r, AbstractManagedObjectDefinition<C, S> d) {
+            if (result.isSuccess()) {
+                try {
+                    ManagedObject<?> child = result.getValue().getChild(r);
+
+                    // Check that child is a sub-type of the specified
+                    // definition.
+                    if (!child.getManagedObjectDefinition().isChildOf(d)) {
+                        clie = ArgumentExceptionFactory.wrongManagedObjectType(r, child.getManagedObjectDefinition(),
+                                getSubCommand().getName());
+                        result = MenuResult.quit();
+                    } else {
+                        result = MenuResult.<ManagedObject<?>> success(child);
+                    }
+                } catch (DefinitionDecodingException e) {
+                    dde = e;
+                    result = MenuResult.quit();
+                } catch (ManagedObjectDecodingException e) {
+                    mode = e;
+                    result = MenuResult.quit();
+                } catch (AuthorizationException e) {
+                    authze = e;
+                    result = MenuResult.quit();
+                } catch (ManagedObjectNotFoundException e) {
+                    monfe = e;
+                    result = MenuResult.quit();
+                } catch (ConcurrentModificationException e) {
+                    cme = e;
+                    result = MenuResult.quit();
+                } catch (ErrorResultException e) {
+                    ere = e;
+                    result = MenuResult.quit();
+                }
+            }
+        }
+
+        /** {@inheritDoc} */
+        public <C extends ConfigurationClient, S extends Configuration> void appendManagedObjectPathElement(
+                SetRelationDefinition<? super C, ? super S> r, AbstractManagedObjectDefinition<C, S> d) {
+            if (result.isSuccess()) {
+                // We should ignore the "template" name here and use a path
+                // argument.
+                String childName = args.get(argIndex++);
+
+                try {
+                    // If the name is null then we must be interactive - so let
+                    // the user choose.
+                    if (childName == null) {
+                        try {
+                            MenuResult<String> sresult = readChildName(app, result.getValue(), r, d);
+
+                            if (sresult.isCancel()) {
+                                result = MenuResult.cancel();
+                                return;
+                            } else if (sresult.isQuit()) {
+                                result = MenuResult.quit();
+                                return;
+                            } else {
+                                childName = sresult.getValue();
+                            }
+                        } catch (ClientException e) {
+                            clie = e;
+                            result = MenuResult.quit();
+                            return;
+                        }
+                    } else if (childName.trim().length() == 0) {
+                        IllegalManagedObjectNameException e = new IllegalManagedObjectNameException(childName);
+                        clie = ArgumentExceptionFactory.adaptIllegalManagedObjectNameException(e, d);
+                        result = MenuResult.quit();
+                        return;
+                    } else {
+                        String name = childName.trim();
+                        SortedMap<String, ManagedObjectDefinition<? extends C, ? extends S>> types = getSubTypes(d);
+                        ManagedObjectDefinition<?, ?> cd = types.get(name);
+                        if (cd == null) {
+                            // The name must be invalid.
+                            String typeUsage = getSubTypesUsage(d);
+                            LocalizableMessage msg = ERR_DSCFG_ERROR_SUB_TYPE_UNRECOGNIZED.get(name,
+                                    r.getUserFriendlyName(), typeUsage);
+                            clie = new ClientException(ReturnCode.APPLICATION_ERROR, msg);
+                            result = MenuResult.quit();
+                            return;
+                        } else {
+                            childName = cd.getName();
+                        }
+                    }
+
+                    ManagedObject<?> child = result.getValue().getChild(r, childName);
+
+                    // Check that child is a sub-type of the specified
+                    // definition.
+                    if (!child.getManagedObjectDefinition().isChildOf(d)) {
+                        clie = ArgumentExceptionFactory.wrongManagedObjectType(r, child.getManagedObjectDefinition(),
+                                getSubCommand().getName());
+                        result = MenuResult.quit();
+                    } else {
+                        result = MenuResult.<ManagedObject<?>> success(child);
+                    }
+                } catch (DefinitionDecodingException e) {
+                    dde = e;
+                    result = MenuResult.quit();
+                } catch (ManagedObjectDecodingException e) {
+                    mode = e;
+                    result = MenuResult.quit();
+                } catch (AuthorizationException e) {
+                    authze = e;
+                    result = MenuResult.quit();
+                } catch (ManagedObjectNotFoundException e) {
+                    monfe = e;
+                    result = MenuResult.quit();
+                } catch (ConcurrentModificationException e) {
+                    cme = e;
+                    result = MenuResult.quit();
+                } catch (ErrorResultException e) {
+                    ere = e;
+                    result = MenuResult.quit();
+                }
+            }
+        }
+
+        /** {@inheritDoc} */
+        public <C extends ConfigurationClient, S extends Configuration> void appendManagedObjectPathElement(
+                SingletonRelationDefinition<? super C, ? super S> r, AbstractManagedObjectDefinition<C, S> d) {
+            if (result.isSuccess()) {
+                try {
+                    ManagedObject<?> child = result.getValue().getChild(r);
+
+                    // Check that child is a sub-type of the specified
+                    // definition.
+                    if (!child.getManagedObjectDefinition().isChildOf(d)) {
+                        clie = ArgumentExceptionFactory.wrongManagedObjectType(r, child.getManagedObjectDefinition(),
+                                getSubCommand().getName());
+                        result = MenuResult.quit();
+                    } else {
+                        result = MenuResult.<ManagedObject<?>> success(child);
+                    }
+                } catch (DefinitionDecodingException e) {
+                    dde = e;
+                    result = MenuResult.quit();
+                } catch (ManagedObjectDecodingException e) {
+                    mode = e;
+                    result = MenuResult.quit();
+                } catch (AuthorizationException e) {
+                    authze = e;
+                    result = MenuResult.quit();
+                } catch (ManagedObjectNotFoundException e) {
+                    monfe = e;
+                    result = MenuResult.quit();
+                } catch (ConcurrentModificationException e) {
+                    cme = e;
+                    result = MenuResult.quit();
+                } catch (ErrorResultException e) {
+                    ere = e;
+                    result = MenuResult.quit();
+                }
+            }
+        }
+
+        /**
+         * Finds the named managed object.
+         *
+         * @param app
+         *            The console application.
+         * @param context
+         *            The management context.
+         * @param path
+         *            The managed object path.
+         * @param args
+         *            The managed object path arguments.
+         * @return Returns a {@link MenuResult#success()} containing the managed object referenced by the provided
+         *         managed object path, or {@link MenuResult#quit()}, or {@link MenuResult#cancel()}, if the sub-command
+         *         was run interactively and the user chose to quit or cancel.
+         * @throws ClientException
+         *             If one of the naming arguments referenced a managed object of the wrong type.
+         * @throws DefinitionDecodingException
+         *             If the managed object was found but its type could not be determined.
+         * @throws ManagedObjectDecodingException
+         *             If the managed object was found but one or more of its properties could not be decoded.
+         * @throws ManagedObjectNotFoundException
+         *             If the requested managed object could not be found on the server.
+         * @throws ConcurrentModificationException
+         *             If this managed object has been removed from the server by another client.
+         * @throws AuthorizationException
+         *             If the server refuses to retrieve the managed object because the client does not have the correct
+         *             privileges.
+         */
+        public MenuResult<ManagedObject<?>> find(ConsoleApplication app, ManagementContext context,
+                ManagedObjectPath<?, ?> path, List<String> args) throws ClientException, AuthorizationException,
+                ConcurrentModificationException, DefinitionDecodingException, ManagedObjectDecodingException,
+                ManagedObjectNotFoundException {
+            this.result = MenuResult.<ManagedObject<?>> success(context.getRootConfigurationManagedObject());
+            this.app = app;
+            this.args = args;
+            this.argIndex = 0;
+
+            this.clie = null;
+            this.authze = null;
+            this.cme = null;
+            this.dde = null;
+            this.mode = null;
+            this.monfe = null;
+
+            path.serialize(this);
+
+            if (result.isSuccess()) {
+                return result;
+            } else if (clie != null) {
+                throw clie;
+            } else if (authze != null) {
+                throw authze;
+            } else if (cme != null) {
+                throw cme;
+            } else if (dde != null) {
+                throw dde;
+            } else if (mode != null) {
+                throw mode;
+            } else if (monfe != null) {
+                throw monfe;
+            } else {
+                // User requested termination interactively.
+                return result;
+            }
+        }
+    }
+
+    /**
+     * A path serializer which is used to register a sub-command's naming arguments.
+     */
+    protected static final class NamingArgumentBuilder implements ManagedObjectPathSerializer {
+
+        /**
+         * Creates the naming arguments for a given path.
+         *
+         * @param subCommand
+         *            The sub-command.
+         * @param path
+         *            The managed object path.
+         * @param isCreate
+         *            Indicates whether the sub-command is a create-xxx sub-command, in which case the final path
+         *            element will have different usage information.
+         * @return Returns the naming arguments.
+         * @throws ArgumentException
+         *             If one or more naming arguments could not be registered.
+         */
+        public static List<StringArgument> create(SubCommand subCommand, ManagedObjectPath<?, ?> path, boolean isCreate)
+                throws ArgumentException {
+            NamingArgumentBuilder builder = new NamingArgumentBuilder(subCommand, path.size(), isCreate);
+            path.serialize(builder);
+
+            if (builder.e != null) {
+                throw builder.e;
+            }
+
+            return builder.arguments;
+        }
+
+        /** The list of naming arguments. */
+        private final List<StringArgument> arguments = new LinkedList<StringArgument>();
+
+        /**
+         * Any argument exception thrown when creating the naming arguments.
+         */
+        private ArgumentException e = null;
+
+        /**
+         * Indicates whether the sub-command is a create-xxx sub-command, in which case the final path element will have
+         * different usage information.
+         */
+        private final boolean isCreate;
+
+        /** The sub-command. */
+        private final SubCommand subCommand;
+
+        /** The number of path elements to expect. */
+        private int sz;
+
+        /** Private constructor. */
+        private NamingArgumentBuilder(SubCommand subCommand, int sz, boolean isCreate) {
+            this.subCommand = subCommand;
+            this.sz = sz;
+            this.isCreate = isCreate;
+        }
+
+        /** {@inheritDoc} */
+        public <C extends ConfigurationClient, S extends Configuration> void appendManagedObjectPathElement(
+                InstantiableRelationDefinition<? super C, ? super S> r, AbstractManagedObjectDefinition<C, S> d,
+                String name) {
+            sz--;
+
+            String argName = CLIProfile.getInstance().getNamingArgument(r);
+            StringArgument arg;
+
+            try {
+                if (isCreate && sz == 0) {
+                    // The final path element in create-xxx sub-commands should
+                    // have a different usage.
+                    PropertyDefinition<?> pd = r.getNamingPropertyDefinition();
+
+                    if (pd != null) {
+                        // Use syntax and description from naming property.
+                        PropertyDefinitionUsageBuilder b = new PropertyDefinitionUsageBuilder(false);
+                        LocalizableMessage usage = LocalizableMessage.raw("{" + b.getUsage(pd) + "}");
+                        arg = new StringArgument(argName, null, argName, false, true, usage,
+                                INFO_DSCFG_DESCRIPTION_NAME_CREATE_EXT.get(d.getUserFriendlyName(), pd.getName(),
+                                        pd.getSynopsis()));
+                    } else {
+                        arg = new StringArgument(argName, null, argName, false, true, INFO_NAME_PLACEHOLDER.get(),
+                                INFO_DSCFG_DESCRIPTION_NAME_CREATE.get(d.getUserFriendlyName()));
+                    }
+                } else {
+                    // A normal naming argument.
+                    arg = new StringArgument(argName, null, argName, false, true, INFO_NAME_PLACEHOLDER.get(),
+                            INFO_DSCFG_DESCRIPTION_NAME.get(d.getUserFriendlyName()));
+                }
+                subCommand.addArgument(arg);
+                arguments.add(arg);
+            } catch (ArgumentException e) {
+                this.e = e;
+            }
+        }
+
+        /** {@inheritDoc} */
+        public <C extends ConfigurationClient, S extends Configuration> void appendManagedObjectPathElement(
+                OptionalRelationDefinition<? super C, ? super S> r, AbstractManagedObjectDefinition<C, S> d) {
+            sz--;
+        }
+
+        /** {@inheritDoc} */
+        public <C extends ConfigurationClient, S extends Configuration> void appendManagedObjectPathElement(
+                SetRelationDefinition<? super C, ? super S> r, AbstractManagedObjectDefinition<C, S> d) {
+            sz--;
+
+            // The name of the managed object is determined from its type, so
+            // we don't need this argument.
+            if (isCreate && sz == 0) {
+                return;
+            }
+
+            String argName = CLIProfile.getInstance().getNamingArgument(r);
+            StringArgument arg;
+
+            try {
+                arg = new StringArgument(argName, null, argName, false, true, INFO_NAME_PLACEHOLDER.get(),
+                        INFO_DSCFG_DESCRIPTION_NAME.get(d.getUserFriendlyName()));
+                subCommand.addArgument(arg);
+                arguments.add(arg);
+            } catch (ArgumentException e) {
+                this.e = e;
+            }
+        }
+
+        /** {@inheritDoc} */
+        public <C extends ConfigurationClient, S extends Configuration> void appendManagedObjectPathElement(
+                SingletonRelationDefinition<? super C, ? super S> r, AbstractManagedObjectDefinition<C, S> d) {
+            sz--;
+        }
+
+    }
+
+    /**
+     * The threshold above which choice menus should be displayed in multiple columns.
+     */
+    public static final int MULTI_COLUMN_THRESHOLD = 8;
+
+    /**
+     * The value for the long option property.
+     */
+    private static final String OPTION_DSCFG_LONG_PROPERTY = "property";
+
+    /**
+     * The value for the long option record.
+     */
+    private static final String OPTION_DSCFG_LONG_RECORD = "record";
+
+    /**
+     * The value for the long option unit-size.
+     */
+    private static final String OPTION_DSCFG_LONG_UNIT_SIZE = "unit-size";
+
+    /**
+     * The value for the long option unit-time.
+     */
+    private static final String OPTION_DSCFG_LONG_UNIT_TIME = "unit-time";
+
+    /**
+     * The value for the short option property.
+     */
+    private static final Character OPTION_DSCFG_SHORT_PROPERTY = null;
+
+    /**
+     * The value for the short option record.
+     */
+    private static final char OPTION_DSCFG_SHORT_RECORD = 'E';
+
+    /**
+     * The value for the short option unit-size.
+     */
+    private static final char OPTION_DSCFG_SHORT_UNIT_SIZE = 'z';
+
+    /**
+     * The value for the short option unit-time.
+     */
+    private static final char OPTION_DSCFG_SHORT_UNIT_TIME = 'm';
+
+    /**
+     * The argument which should be used to specify zero or more property names.
+     */
+    private StringArgument propertyArgument;
+
+    /** The argument which should be used to request record mode. */
+    private BooleanArgument recordModeArgument;
+
+    /** The tags associated with this sub-command handler. */
+    private final Set<Tag> tags = new HashSet<Tag>();
+
+    /** The argument which should be used to request specific size units. */
+    private StringArgument unitSizeArgument;
+
+    /** The argument which should be used to request specific time units. */
+    private StringArgument unitTimeArgument;
+
+    /** The command builder associated with this handler. */
+    private CommandBuilder commandBuilder;
+
+    /**
+     * The boolean that says whether is useful to display the command builder's contents after calling the run method or
+     * not.
+     */
+    private boolean isCommandBuilderUseful = true;
+
+    /**
+     * Create a new sub-command handler.
+     */
+    protected SubCommandHandler() {
+        // No implementation required.
+    }
+
+    /** {@inheritDoc} */
+    public final int compareTo(SubCommandHandler o) {
+        String s1 = getSubCommand().getName();
+        String s2 = o.getSubCommand().getName();
+
+        return s1.compareTo(s2);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        } else if (obj instanceof SubCommandHandler) {
+            SubCommandHandler other = (SubCommandHandler) obj;
+
+            String s1 = getSubCommand().getName();
+            String s2 = other.getSubCommand().getName();
+            return s1.equals(s2);
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Gets the sub-command associated with this handler.
+     *
+     * @return Returns the sub-command associated with this handler.
+     */
+    public abstract SubCommand getSubCommand();
+
+    /**
+     * Gets the command builder associated with this handler. The method should be called after calling
+     * <CODE>run()</CODE> method.
+     *
+     * @return Returns the sub-command associated with this handler.
+     */
+    public final CommandBuilder getCommandBuilder() {
+        if (commandBuilder == null) {
+            commandBuilder = new CommandBuilder(System.getProperty(PROPERTY_SCRIPT_NAME), getSubCommand().getName());
+        }
+        return commandBuilder;
+    }
+
+    /**
+     * This method tells whether displaying the command builder contents makes sense or not. For instance in the case of
+     * the help subcommand handler displaying information makes no much sense.
+     *
+     * @return <CODE>true</CODE> if displaying the command builder is useful and <CODE>false</CODE> otherwise.
+     */
+    public final boolean isCommandBuilderUseful() {
+        return isCommandBuilderUseful;
+    }
+
+    /**
+     * Sets wheter the command builder is useful or not.
+     *
+     * @param isCommandBuilderUseful
+     *            whether the command builder is useful or not.
+     */
+    protected final void setCommandBuilderUseful(boolean isCommandBuilderUseful) {
+        this.isCommandBuilderUseful = isCommandBuilderUseful;
+    }
+
+    /**
+     * Gets the tags associated with this sub-command handler.
+     *
+     * @return Returns the tags associated with this sub-command handler.
+     */
+    public final Set<Tag> getTags() {
+        return tags;
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public final int hashCode() {
+        return getSubCommand().getName().hashCode();
+    }
+
+    /**
+     * Run this sub-command handler.
+     *
+     * @param app
+     *            The console application.
+     * @param factory
+     *            The LDAP management context factory context factory.
+     * @return Returns a {@link MenuResult#success()} containing zero if the sub-command completed successfully or
+     *         non-zero if it did not, or {@link MenuResult#quit()}, or {@link MenuResult#cancel()}, if the sub-command
+     *         was run interactively and the user chose to quit or cancel.
+     * @throws ArgumentException
+     *             If an argument required by the sub-command could not be parsed successfully.
+     * @throws ClientException
+     *             If the management context could not be created.
+     */
+    public abstract MenuResult<Integer> run(ConsoleApplication app, LDAPManagementContextFactory factory)
+            throws ArgumentException, ClientException;
+
+    /**
+     * Get the string representation of this sub-command handler.
+     * <p>
+     * The string representation is simply the sub-command's name.
+     *
+     * @return Returns the string representation of this sub-command handler.
+     */
+    @Override
+    public final String toString() {
+        return getSubCommand().getName();
+    }
+
+    /**
+     * Adds one or more tags to this sub-command handler.
+     *
+     * @param tags
+     *            The tags to be added to this sub-command handler.
+     */
+    protected final void addTags(Collection<Tag> tags) {
+        this.tags.addAll(tags);
+    }
+
+    /**
+     * Adds one or more tags to this sub-command handler.
+     *
+     * @param tags
+     *            The tags to be added to this sub-command handler.
+     */
+    protected final void addTags(Tag... tags) {
+        addTags(Arrays.asList(tags));
+    }
+
+    /**
+     * Creates the naming arguments for a given path and registers them.
+     *
+     * @param subCommand
+     *            The sub-command.
+     * @param p
+     *            The managed object path.
+     * @param isCreate
+     *            Indicates whether the sub-command is a create-xxx sub-command, in which case the final path element
+     *            will have different usage information.
+     * @return Returns the naming arguments.
+     * @throws ArgumentException
+     *             If one or more naming arguments could not be registered.
+     */
+    protected final List<StringArgument> createNamingArgs(SubCommand subCommand, ManagedObjectPath<?, ?> p,
+            boolean isCreate) throws ArgumentException {
+        return NamingArgumentBuilder.create(subCommand, p, isCreate);
+    }
+
+    /**
+     * Creates a script-friendly table printer. This factory method should be used by sub-command handler
+     * implementations rather than constructing a table printer directly so that we can easily switch table
+     * implementations (perhaps dynamically depending on argument).
+     *
+     * @param stream
+     *            The output stream for the table.
+     * @return Returns a script-friendly table printer.
+     */
+    protected final TablePrinter createScriptFriendlyTablePrinter(PrintStream stream) {
+        return new TabSeparatedTablePrinter(stream);
+    }
+
+    /**
+     * Get the managed object referenced by the provided managed object path.
+     *
+     * @param app
+     *            The console application.
+     * @param context
+     *            The management context.
+     * @param path
+     *            The managed object path.
+     * @param args
+     *            The list of managed object names required by the path.
+     * @return Returns a {@link MenuResult#success()} containing the managed object referenced by the provided managed
+     *         object path, or {@link MenuResult#quit()}, or {@link MenuResult#cancel()}, if the sub-command was run
+     *         interactively and the user chose to quit or cancel.
+     * @throws DefinitionDecodingException
+     *             If the managed object was found but its type could not be determined.
+     * @throws ManagedObjectDecodingException
+     *             If the managed object was found but one or more of its properties could not be decoded.
+     * @throws ManagedObjectNotFoundException
+     *             If the requested managed object could not be found on the server.
+     * @throws ConcurrentModificationException
+     *             If this managed object has been removed from the server by another client.
+     * @throws AuthorizationException
+     *             If the server refuses to retrieve the managed object because the client does not have the correct
+     *             privileges.
+     * @throws ClientException
+     *             If one of the naming arguments referenced a managed object of the wrong type.
+     * @throws ClientException
+     *             If the management context could not be created.
+     */
+    protected final MenuResult<ManagedObject<?>> getManagedObject(ConsoleApplication app, ManagementContext context,
+            ManagedObjectPath<?, ?> path, List<String> args) throws ClientException, AuthorizationException,
+            DefinitionDecodingException, ManagedObjectDecodingException, ConcurrentModificationException,
+            ManagedObjectNotFoundException {
+        ManagedObjectFinder finder = new ManagedObjectFinder();
+        return finder.find(app, context, path, args);
+    }
+
+    /**
+     * Gets the values of the naming arguments.
+     *
+     * @param app
+     *            The console application.
+     * @param namingArgs
+     *            The naming arguments.
+     * @return Returns the values of the naming arguments.
+     * @throws ArgumentException
+     *             If one of the naming arguments is missing and the application is non-interactive.
+     */
+    protected final List<String> getNamingArgValues(ConsoleApplication app, List<StringArgument> namingArgs)
+            throws ArgumentException {
+        ArrayList<String> values = new ArrayList<String>(namingArgs.size());
+        for (StringArgument arg : namingArgs) {
+            String value = arg.getValue();
+
+            if (value == null && !app.isInteractive()) {
+                throw ArgumentExceptionFactory.missingMandatoryNonInteractiveArgument(arg);
+            } else {
+                values.add(value);
+            }
+        }
+        return values;
+    }
+
+    /**
+     * Gets the optional list of property names that the user requested.
+     *
+     * @return Returns the optional list of property names that the user requested.
+     */
+    protected final Set<String> getPropertyNames() {
+        if (propertyArgument != null) {
+            return new LinkedHashSet<String>(propertyArgument.getValues());
+        } else {
+            return Collections.emptySet();
+        }
+    }
+
+    /**
+     * Gets the optional size unit that the user requested.
+     *
+     * @return Returns the size unit that the user requested, or <code>null</code> if no size unit was specified.
+     * @throws ArgumentException
+     *             If the user specified an invalid size unit.
+     */
+    protected final SizeUnit getSizeUnit() throws ArgumentException {
+        if (unitSizeArgument != null) {
+            String value = unitSizeArgument.getValue();
+
+            if (value != null) {
+                try {
+                    return SizeUnit.getUnit(value);
+                } catch (IllegalArgumentException e) {
+                    LocalizableMessage msg = INFO_DSCFG_ERROR_SIZE_UNIT_UNRECOGNIZED.get(value);
+                    throw new ArgumentException(msg);
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Gets the optional time unit that the user requested.
+     *
+     * @return Returns the time unit that the user requested, or <code>null</code> if no time unit was specified.
+     * @throws ArgumentException
+     *             If the user specified an invalid time unit.
+     */
+    protected final DurationUnit getTimeUnit() throws ArgumentException {
+        if (unitTimeArgument != null) {
+            String value = unitTimeArgument.getValue();
+
+            if (value != null) {
+                try {
+                    return DurationUnit.getUnit(value);
+                } catch (IllegalArgumentException e) {
+                    LocalizableMessage msg = INFO_DSCFG_ERROR_TIME_UNIT_UNRECOGNIZED.get(value);
+                    throw new ArgumentException(msg);
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Determines whether the user requested record-mode.
+     *
+     * @return Returns <code>true</code> if the user requested record-mode.
+     */
+    protected final boolean isRecordMode() {
+        if (recordModeArgument != null) {
+            return recordModeArgument.isPresent();
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Interactively prompts the user to select from a choice of child managed objects.
+     * <p>
+     * This method will adapt according to the available choice. For example, if there is only one choice, then a
+     * question will be asked. If there are no children then an <code>ArgumentException</code> will be thrown.
+     *
+     * @param <C>
+     *            The type of child client configuration.
+     * @param <S>
+     *            The type of child server configuration.
+     * @param app
+     *            The console application.
+     * @param parent
+     *            The parent managed object.
+     * @param r
+     *            The relation between the parent and the children, must be a set or instantiable relation.
+     * @param d
+     *            The type of child managed object to choose from.
+     * @return Returns a {@link MenuResult#success()} containing the name of the managed object that the user selected,
+     *         or {@link MenuResult#quit()}, or {@link MenuResult#cancel()}, if the sub-command was run interactive and
+     *         the user chose to quit or cancel.
+     * @throws ConcurrentModificationException
+     *             If the parent managed object has been deleted.
+     * @throws AuthorizationException
+     *             If the children cannot be listed due to an authorization failure.
+     * @throws ClientException
+     *             If the user input can be read from the console or if there are no children.
+     */
+    protected final <C extends ConfigurationClient, S extends Configuration> MenuResult<String> readChildName(
+            ConsoleApplication app, ManagedObject<?> parent, RelationDefinition<C, S> r,
+            AbstractManagedObjectDefinition<? extends C, ? extends S> d) throws AuthorizationException,
+            ConcurrentModificationException, ClientException {
+        if (d == null) {
+            d = r.getChildDefinition();
+        }
+
+        app.println();
+        app.println();
+
+        // Filter out advanced and hidden types if required.
+        String[] childNames = null;
+        try {
+            if (r instanceof InstantiableRelationDefinition) {
+                childNames = parent.listChildren((InstantiableRelationDefinition<C, S>) r, d);
+            } else {
+                childNames = parent.listChildren((SetRelationDefinition<C, S>) r, d);
+            }
+        } catch (ErrorResultException e) {
+            // FIXME check exceptions
+            System.out.println(String.format("An error occured %s", e.getMessage()));
+        }
+
+        SortedMap<String, String> children = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);
+
+        for (String childName : childNames) {
+            ManagedObject<?> child;
+            try {
+                if (r instanceof InstantiableRelationDefinition) {
+                    child = parent.getChild((InstantiableRelationDefinition<C, S>) r, childName);
+                } else {
+                    child = parent.getChild((SetRelationDefinition<C, S>) r, childName);
+                }
+
+                ManagedObjectDefinition<?, ?> cd = child.getManagedObjectDefinition();
+
+                if (cd.hasOption(ManagedObjectOption.HIDDEN)) {
+                    continue;
+                }
+
+                if (!app.isAdvancedMode() && cd.hasOption(ManagedObjectOption.ADVANCED)) {
+                    continue;
+                }
+
+                if (r instanceof InstantiableRelationDefinition) {
+                    children.put(childName, childName);
+                } else {
+                    // For sets the RDN is the type string, the ufn is more friendly.
+                    children.put(cd.getUserFriendlyName().toString(), childName);
+                }
+            } catch (DefinitionDecodingException e) {
+                // Add it anyway: maybe the user is trying to fix the problem.
+                children.put(childName, childName);
+            } catch (ManagedObjectDecodingException e) {
+                // Add it anyway: maybe the user is trying to fix the problem.
+                children.put(childName, childName);
+            } catch (ManagedObjectNotFoundException e) {
+                // Skip it - the managed object has been concurrently removed.
+            } catch (ErrorResultException e) {
+                // Add it anyway: maybe the user is trying to fix the problem.
+                children.put(childName, childName);
+            }
+        }
+
+        switch (children.size()) {
+        case 0: {
+            // No options available - abort.
+            LocalizableMessage msg = ERR_DSCFG_ERROR_FINDER_NO_CHILDREN.get(d.getUserFriendlyPluralName());
+            app.println(msg);
+            return MenuResult.cancel();
+        }
+        case 1: {
+            // Only one option available so confirm that the user wishes to
+            // access it.
+            LocalizableMessage msg = INFO_DSCFG_FINDER_PROMPT_SINGLE.get(d.getUserFriendlyName(), children.firstKey());
+            if (app.confirmAction(msg, true)) {
+                try {
+                    String argName = CLIProfile.getInstance().getNamingArgument(r);
+                    StringArgument arg = new StringArgument(argName, null, argName, false, true,
+                            INFO_NAME_PLACEHOLDER.get(),
+                            INFO_DSCFG_DESCRIPTION_NAME_CREATE.get(d.getUserFriendlyName()));
+                    if (r instanceof InstantiableRelationDefinition) {
+                        arg.addValue(children.get(children.firstKey()));
+                    } else {
+                        // Set relation: need the short type name.
+                        String friendlyName = children.firstKey();
+                        String shortName = children.get(friendlyName);
+                        try {
+                            AbstractManagedObjectDefinition<?, ?> cd = null;
+                            try {
+                                cd = d.getChild(shortName);
+                            } catch (IllegalArgumentException e) {
+                                // Last resource: try with friendly name
+                                cd = d.getChild(friendlyName);
+                            }
+                            arg.addValue(getShortTypeName(r.getChildDefinition(), cd));
+                        } catch (IllegalArgumentException e) {
+                            arg.addValue(shortName);
+                        }
+                    }
+                    getCommandBuilder().addArgument(arg);
+                } catch (Throwable t) {
+                    // Bug
+                    throw new RuntimeException("Unexpected exception: " + t, t);
+                }
+                return MenuResult.success(children.get(children.firstKey()));
+            } else {
+                return MenuResult.cancel();
+            }
+        }
+        default: {
+            // Display a menu.
+            MenuBuilder<String> builder = new MenuBuilder<String>(app);
+            builder.setMultipleColumnThreshold(MULTI_COLUMN_THRESHOLD);
+            builder.setPrompt(INFO_DSCFG_FINDER_PROMPT_MANY.get(d.getUserFriendlyName()));
+
+            for (Map.Entry<String, String> child : children.entrySet()) {
+                LocalizableMessage option = LocalizableMessage.raw("%s", child.getKey());
+                builder.addNumberedOption(option, MenuResult.success(child.getValue()));
+            }
+
+            if (app.isMenuDrivenMode()) {
+                builder.addCancelOption(true);
+            }
+            builder.addQuitOption();
+
+            Menu<String> menu = builder.toMenu();
+            MenuResult<String> result = menu.run();
+            try {
+                if (result.getValue() == null) {
+                    // nothing has been entered ==> cancel
+                    return MenuResult.cancel();
+                }
+                String argName = CLIProfile.getInstance().getNamingArgument(r);
+                StringArgument arg = new StringArgument(argName, null, argName, false, true,
+                        INFO_NAME_PLACEHOLDER.get(), INFO_DSCFG_DESCRIPTION_NAME_CREATE.get(d.getUserFriendlyName()));
+                if (r instanceof InstantiableRelationDefinition) {
+                    arg.addValue(result.getValue());
+                } else {
+                    // Set relation: need the short type name.
+                    String longName = result.getValue();
+                    try {
+                        AbstractManagedObjectDefinition<?, ?> cd = d.getChild(longName);
+                        arg.addValue(getShortTypeName(r.getChildDefinition(), cd));
+                    } catch (IllegalArgumentException e) {
+                        arg.addValue(children.get(result.getValue()));
+                    }
+                }
+                getCommandBuilder().addArgument(arg);
+            } catch (Throwable t) {
+                // Bug
+                throw new RuntimeException("Unexpected exception: " + t, t);
+            }
+            return result;
+        }
+        }
+    }
+
+    /**
+     * Registers the property name argument with the sub-command.
+     *
+     * @param subCommand
+     *            The sub-command.
+     * @throws ArgumentException
+     *             If the property name argument could not be registered.
+     */
+    protected final void registerPropertyNameArgument(SubCommand subCommand) throws ArgumentException {
+        this.propertyArgument = new StringArgument(OPTION_DSCFG_LONG_PROPERTY, OPTION_DSCFG_SHORT_PROPERTY,
+                OPTION_DSCFG_LONG_PROPERTY, false, true, true, INFO_PROPERTY_PLACEHOLDER.get(), null, null,
+                INFO_DSCFG_DESCRIPTION_PROP.get());
+        subCommand.addArgument(propertyArgument);
+    }
+
+    /**
+     * Registers the record mode argument with the sub-command.
+     *
+     * @param subCommand
+     *            The sub-command.
+     * @throws ArgumentException
+     *             If the record mode argument could not be registered.
+     */
+    protected final void registerRecordModeArgument(SubCommand subCommand) throws ArgumentException {
+        this.recordModeArgument = new BooleanArgument(OPTION_DSCFG_LONG_RECORD, OPTION_DSCFG_SHORT_RECORD,
+                OPTION_DSCFG_LONG_RECORD, INFO_DSCFG_DESCRIPTION_RECORD.get());
+        this.recordModeArgument.setPropertyName(OPTION_DSCFG_LONG_RECORD);
+        subCommand.addArgument(recordModeArgument);
+    }
+
+    /**
+     * Registers the unit-size argument with the sub-command.
+     *
+     * @param subCommand
+     *            The sub-command.
+     * @throws ArgumentException
+     *             If the unit-size argument could not be registered.
+     */
+    protected final void registerUnitSizeArgument(SubCommand subCommand) throws ArgumentException {
+        this.unitSizeArgument = new StringArgument(OPTION_DSCFG_LONG_UNIT_SIZE, OPTION_DSCFG_SHORT_UNIT_SIZE,
+                OPTION_DSCFG_LONG_UNIT_SIZE, false, true, INFO_UNIT_PLACEHOLDER.get(),
+                INFO_DSCFG_DESCRIPTION_UNIT_SIZE.get());
+        this.unitSizeArgument.setPropertyName(OPTION_DSCFG_LONG_UNIT_SIZE);
+
+        subCommand.addArgument(unitSizeArgument);
+    }
+
+    /**
+     * Registers the unit-time argument with the sub-command.
+     *
+     * @param subCommand
+     *            The sub-command.
+     * @throws ArgumentException
+     *             If the unit-time argument could not be registered.
+     */
+    protected final void registerUnitTimeArgument(SubCommand subCommand) throws ArgumentException {
+        this.unitTimeArgument = new StringArgument(OPTION_DSCFG_LONG_UNIT_TIME, OPTION_DSCFG_SHORT_UNIT_TIME,
+                OPTION_DSCFG_LONG_UNIT_TIME, false, true, INFO_UNIT_PLACEHOLDER.get(),
+                INFO_DSCFG_DESCRIPTION_UNIT_TIME.get());
+        this.unitTimeArgument.setPropertyName(OPTION_DSCFG_LONG_UNIT_TIME);
+
+        subCommand.addArgument(unitTimeArgument);
+    }
+
+    /**
+     * Updates the command builder with the arguments defined in the sub command. This implies basically putting the
+     * arguments provided by the user in the command builder.
+     */
+    protected final void updateCommandBuilderWithSubCommand() {
+        for (Argument arg : getSubCommand().getArguments()) {
+            if (arg.isPresent()) {
+                getCommandBuilder().addArgument(arg);
+            }
+        }
+    }
+
+    /**
+     * Returns the string value for a given object as it will be displayed in the equivalent command-line. The code will
+     * cast the provided object using the property definition.
+     *
+     * @param propertyDefinition
+     *            the property definition.
+     * @param o
+     *            the value.
+     * @param <T>
+     *            the type of the property to be retrieved.
+     * @return the String value to be displayed in the equivalent command-line.
+     */
+    protected static <T> String castAndGetArgumentValue(PropertyDefinition<T> propertyDefinition, Object o) {
+        return propertyDefinition.encodeValue(propertyDefinition.castValue(o));
+    }
+
+    /**
+     * Returns the string value for a given object as it will be displayed in the equivalent command-line.
+     *
+     * @param propertyDefinition
+     *            the property definition.
+     * @param o
+     *            the value.
+     * @param <T>
+     *            the type of the property to be retrieved.
+     * @return the String value to be displayed in the equivalent command-line.
+     */
+    protected static <T> String getArgumentValue(PropertyDefinition<T> propertyDefinition, T o) {
+        return propertyDefinition.encodeValue(o);
+    }
+
+    /**
+     * Returns a mapping of subordinate managed object type argument values to their corresponding managed object
+     * definitions for the provided managed object definition.
+     *
+     * @param <C>
+     *            The type of client configuration.
+     * @param <S>
+     *            The type of server configuration.
+     * @param d
+     *            The managed object definition.
+     * @return A mapping of managed object type argument values to their corresponding managed object definitions.
+     */
+    protected static <C extends ConfigurationClient, S extends Configuration> SortedMap<String,
+        ManagedObjectDefinition<? extends C, ? extends S>> getSubTypes(
+            AbstractManagedObjectDefinition<C, S> d) {
+        SortedMap<String, ManagedObjectDefinition<? extends C, ? extends S>> map;
+        map = new TreeMap<String, ManagedObjectDefinition<? extends C, ? extends S>>();
+
+        // If the top-level definition is instantiable, we use the value
+        // "generic" or "custom".
+        if (!d.hasOption(ManagedObjectOption.HIDDEN) && d instanceof ManagedObjectDefinition) {
+            ManagedObjectDefinition<? extends C, ? extends S> mod
+                = (ManagedObjectDefinition<? extends C, ? extends S>) d;
+            map.put(getShortTypeName(d, mod), mod);
+        }
+
+        // Process its sub-definitions.
+        for (AbstractManagedObjectDefinition<? extends C, ? extends S> c : d.getAllChildren()) {
+            if (c.hasOption(ManagedObjectOption.HIDDEN)) {
+                continue;
+            }
+
+            if (c instanceof ManagedObjectDefinition) {
+                @SuppressWarnings("unchecked")
+                ManagedObjectDefinition<? extends C, ? extends S> mod
+                    = (ManagedObjectDefinition<? extends C, ? extends S>) c;
+                map.put(getShortTypeName(d, mod), mod);
+            }
+        }
+
+        return map;
+    }
+
+    /**
+     * Returns the type short name for a child managed object definition.
+     *
+     * @param <C>
+     *            The type of client configuration.
+     * @param <S>
+     *            The type of server configuration.
+     * @param d
+     *            The top level parent definition.
+     * @param c
+     *            The child definition.
+     * @return The type short name.
+     */
+    protected static <C extends ConfigurationClient, S extends Configuration> String getShortTypeName(
+            AbstractManagedObjectDefinition<C, S> d, AbstractManagedObjectDefinition<?, ?> c) {
+        if (c == d) {
+            // This is the top-level definition, so use the value "generic" or
+            // "custom".
+            if (CLIProfile.getInstance().isForCustomization(c)) {
+                return DSConfig.CUSTOM_TYPE;
+            } else {
+                return DSConfig.GENERIC_TYPE;
+            }
+        } else {
+            // It's a child definition.
+            String suffix = "-" + d.getName();
+
+            // For the type name we shorten it, if possible, by stripping
+            // off the trailing part of the name which matches the
+            // base-type.
+            String name = c.getName();
+            if (name.endsWith(suffix)) {
+                name = name.substring(0, name.length() - suffix.length());
+            }
+
+            // If this type is intended for customization, prefix it with
+            // "custom".
+            if (CLIProfile.getInstance().isForCustomization(c)) {
+                name = String.format("%s-%s", DSConfig.CUSTOM_TYPE, name);
+            }
+
+            return name;
+        }
+    }
+
+    /**
+     * Returns a usage string representing the list of possible types for the provided managed object definition.
+     *
+     * @param d
+     *            The managed object definition.
+     * @return A usage string representing the list of possible types for the provided managed object definition.
+     */
+    protected static String getSubTypesUsage(AbstractManagedObjectDefinition<?, ?> d) {
+        // Build the -t option usage.
+        SortedMap<String, ?> types = getSubTypes(d);
+        StringBuilder builder = new StringBuilder();
+        boolean isFirst = true;
+        for (String s : types.keySet()) {
+            if (!isFirst) {
+                builder.append(" | ");
+            }
+            builder.append(s);
+            isFirst = false;
+        }
+        return builder.toString();
+    }
+}
diff --git a/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/SubCommandHandlerFactory.java b/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/SubCommandHandlerFactory.java
new file mode 100644
index 0000000..ffda2c0
--- /dev/null
+++ b/opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/SubCommandHandlerFactory.java
@@ -0,0 +1,334 @@
+/*
+ * 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 2009 Sun Microsystems, Inc.
+ *      Portions Copyright 2014 ForgeRock AS
+ */
+package org.forgerock.opendj.config.dsconfig;
+
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import org.forgerock.opendj.config.AbstractManagedObjectDefinition;
+import org.forgerock.opendj.config.Configuration;
+import org.forgerock.opendj.config.ConfigurationClient;
+import org.forgerock.opendj.config.InstantiableRelationDefinition;
+import org.forgerock.opendj.config.ManagedObjectPath;
+import org.forgerock.opendj.config.OptionalRelationDefinition;
+import org.forgerock.opendj.config.RelationDefinition;
+import org.forgerock.opendj.config.RelationDefinitionVisitor;
+import org.forgerock.opendj.config.RelationOption;
+import org.forgerock.opendj.config.SetRelationDefinition;
+import org.forgerock.opendj.config.SingletonRelationDefinition;
+
+import com.forgerock.opendj.cli.ArgumentException;
+import com.forgerock.opendj.cli.SubCommandArgumentParser;
+
+/**
+ * Uses the administration framework introspection API to construct the dsconfig sub-command handlers.
+ */
+final class SubCommandHandlerFactory {
+
+    /**
+     * A relation definition visitor used to recursively determine the set of available sub-commands.
+     */
+    private final class Visitor implements RelationDefinitionVisitor<Void, ManagedObjectPath<?, ?>> {
+
+        /** {@inheritDoc} */
+        public <C extends ConfigurationClient, S extends Configuration> Void visitInstantiable(
+                InstantiableRelationDefinition<C, S> rd, ManagedObjectPath<?, ?> p) {
+            try {
+                // Create the sub-commands.
+                createHandlers.add(CreateSubCommandHandler.create(parser, p, rd));
+                deleteHandlers.add(DeleteSubCommandHandler.create(parser, p, rd));
+                listHandlers.add(ListSubCommandHandler.create(parser, p, rd));
+                getPropHandlers.add(GetPropSubCommandHandler.create(parser, p, rd));
+                setPropHandlers.add(SetPropSubCommandHandler.create(parser, p, rd));
+
+                // Process the referenced managed object definition and its
+                // sub-types.
+                processRelation(p, rd);
+            } catch (ArgumentException e) {
+                exception = e;
+            }
+
+            return null;
+        }
+
+        /** {@inheritDoc} */
+        public <C extends ConfigurationClient, S extends Configuration> Void visitOptional(
+                OptionalRelationDefinition<C, S> rd, ManagedObjectPath<?, ?> p) {
+            try {
+                // Create the sub-commands.
+                createHandlers.add(CreateSubCommandHandler.create(parser, p, rd));
+                deleteHandlers.add(DeleteSubCommandHandler.create(parser, p, rd));
+                listHandlers.add(ListSubCommandHandler.create(parser, p, rd));
+                getPropHandlers.add(GetPropSubCommandHandler.create(parser, p, rd));
+                setPropHandlers.add(SetPropSubCommandHandler.create(parser, p, rd));
+
+                // Process the referenced managed object definition and its
+                // sub-types.
+                processRelation(p, rd);
+            } catch (ArgumentException e) {
+                exception = e;
+            }
+
+            return null;
+        }
+
+        /** {@inheritDoc} */
+        public <C extends ConfigurationClient, S extends Configuration> Void visitSet(SetRelationDefinition<C, S> rd,
+                ManagedObjectPath<?, ?> p) {
+            try {
+                // Create the sub-commands.
+                createHandlers.add(CreateSubCommandHandler.create(parser, p, rd));
+                deleteHandlers.add(DeleteSubCommandHandler.create(parser, p, rd));
+                listHandlers.add(ListSubCommandHandler.create(parser, p, rd));
+                getPropHandlers.add(GetPropSubCommandHandler.create(parser, p, rd));
+                setPropHandlers.add(SetPropSubCommandHandler.create(parser, p, rd));
+
+                // Process the referenced managed object definition and its
+                // sub-types.
+                processRelation(p, rd);
+            } catch (ArgumentException e) {
+                exception = e;
+            }
+
+            return null;
+        }
+
+        /** {@inheritDoc} */
+        public <C extends ConfigurationClient, S extends Configuration> Void visitSingleton(
+                SingletonRelationDefinition<C, S> rd, ManagedObjectPath<?, ?> p) {
+            try {
+                // Create the sub-commands.
+                getPropHandlers.add(GetPropSubCommandHandler.create(parser, p, rd));
+                setPropHandlers.add(SetPropSubCommandHandler.create(parser, p, rd));
+
+                // Process the referenced managed object definition and its
+                // sub-types.
+                processRelation(p, rd);
+            } catch (ArgumentException e) {
+                exception = e;
+            }
+
+            return null;
+        }
+
+    }
+
+    /** The set of all available sub-commands. */
+    private final SortedSet<SubCommandHandler> allHandlers = new TreeSet<SubCommandHandler>();
+
+    /** The set of create-xxx available sub-commands. */
+    private final SortedSet<CreateSubCommandHandler<?, ?>> createHandlers
+        = new TreeSet<CreateSubCommandHandler<?, ?>>();
+
+    /** The set of delete-xxx available sub-commands. */
+    private final SortedSet<DeleteSubCommandHandler> deleteHandlers = new TreeSet<DeleteSubCommandHandler>();
+
+    /** Any exception that occurred whilst creating the sub-commands. */
+    private ArgumentException exception = null;
+
+    /** The set of get-xxx-prop available sub-commands. */
+    private final SortedSet<GetPropSubCommandHandler> getPropHandlers = new TreeSet<GetPropSubCommandHandler>();
+
+    /** The help sub-command handler. */
+    private HelpSubCommandHandler helpHandler = null;
+
+    /** The set of list-xxx available sub-commands. */
+    private final SortedSet<ListSubCommandHandler> listHandlers = new TreeSet<ListSubCommandHandler>();
+
+    /** The sub-command argument parser. */
+    private final SubCommandArgumentParser parser;
+
+    /** The set of set-xxx-prop available sub-commands. */
+    private final SortedSet<SetPropSubCommandHandler> setPropHandlers = new TreeSet<SetPropSubCommandHandler>();
+
+    /** The relation visitor. */
+    private final Visitor visitor = new Visitor();
+
+    /**
+     * Create a new sub-command builder.
+     *
+     * @param parser
+     *            The sub-command argument parser.
+     * @throws ArgumentException
+     *             If a sub-command could not be created successfully.
+     */
+    public SubCommandHandlerFactory(SubCommandArgumentParser parser) throws ArgumentException {
+        this.parser = parser;
+
+        // We always need a help properties sub-command handler.
+        helpHandler = HelpSubCommandHandler.create(parser);
+
+        processPath(ManagedObjectPath.emptyPath());
+
+        allHandlers.add(helpHandler);
+        allHandlers.addAll(createHandlers);
+        allHandlers.addAll(deleteHandlers);
+        allHandlers.addAll(listHandlers);
+        allHandlers.addAll(getPropHandlers);
+        allHandlers.addAll(setPropHandlers);
+
+        if (exception != null) {
+            throw exception;
+        }
+    }
+
+    /**
+     * Gets all the sub-command handlers.
+     *
+     * @return Returns all the sub-command handlers.
+     */
+    public SortedSet<SubCommandHandler> getAllSubCommandHandlers() {
+        return allHandlers;
+    }
+
+    /**
+     * Gets all the create-xxx sub-command handlers.
+     *
+     * @return Returns all the create-xxx sub-command handlers.
+     */
+    public SortedSet<CreateSubCommandHandler<?, ?>> getCreateSubCommandHandlers() {
+        return createHandlers;
+    }
+
+    /**
+     * Gets all the delete-xxx sub-command handlers.
+     *
+     * @return Returns all the delete-xxx sub-command handlers.
+     */
+    public SortedSet<DeleteSubCommandHandler> getDeleteSubCommandHandlers() {
+        return deleteHandlers;
+    }
+
+    /**
+     * Gets all the get-xxx-prop sub-command handlers.
+     *
+     * @return Returns all the get-xxx-prop sub-command handlers.
+     */
+    public SortedSet<GetPropSubCommandHandler> getGetPropSubCommandHandlers() {
+        return getPropHandlers;
+    }
+
+    /**
+     * Gets all the list-xxx sub-command handlers.
+     *
+     * @return Returns all the list-xxx sub-command handlers.
+     */
+    public SortedSet<ListSubCommandHandler> getListSubCommandHandlers() {
+        return listHandlers;
+    }
+
+    /**
+     * Gets all the set-xxx-prop sub-command handlers.
+     *
+     * @return Returns all the set-xxx-prop sub-command handlers.
+     */
+    public SortedSet<SetPropSubCommandHandler> getSetPropSubCommandHandlers() {
+        return setPropHandlers;
+    }
+
+    /**
+     * Process the relations associated with the managed object definition identified by the provided path.
+     */
+    private void processPath(ManagedObjectPath<?, ?> path) {
+        AbstractManagedObjectDefinition<?, ?> d = path.getManagedObjectDefinition();
+
+        // Do not process inherited relation definitions.
+        for (RelationDefinition<?, ?> r : d.getRelationDefinitions()) {
+            if (!r.hasOption(RelationOption.HIDDEN)) {
+                r.accept(visitor, path);
+            }
+        }
+    }
+
+    /** Process an instantiable relation. */
+    private <C extends ConfigurationClient, S extends Configuration> void processRelation(ManagedObjectPath<?, ?> path,
+            InstantiableRelationDefinition<C, S> r) {
+        AbstractManagedObjectDefinition<C, S> d = r.getChildDefinition();
+
+        // Process all relations associated directly with this
+        // definition.
+        helpHandler.registerManagedObjectDefinition(d);
+        processPath(path.child(r, d, "DUMMY"));
+
+        // Now process relations associated with derived definitions.
+        for (AbstractManagedObjectDefinition<? extends C, ? extends S> c : d.getAllChildren()) {
+            helpHandler.registerManagedObjectDefinition(c);
+            processPath(path.child(r, c, "DUMMY"));
+        }
+    }
+
+    /** Process an optional relation. */
+    private <C extends ConfigurationClient, S extends Configuration> void processRelation(ManagedObjectPath<?, ?> path,
+            OptionalRelationDefinition<C, S> r) {
+        AbstractManagedObjectDefinition<C, S> d = r.getChildDefinition();
+
+        // Process all relations associated directly with this
+        // definition.
+        helpHandler.registerManagedObjectDefinition(d);
+        processPath(path.child(r, d));
+
+        // Now process relations associated with derived definitions.
+        for (AbstractManagedObjectDefinition<? extends C, ? extends S> c : d.getAllChildren()) {
+            helpHandler.registerManagedObjectDefinition(c);
+            processPath(path.child(r, c));
+        }
+    }
+
+    /** Process a set relation. */
+    private <C extends ConfigurationClient, S extends Configuration> void processRelation(ManagedObjectPath<?, ?> path,
+            SetRelationDefinition<C, S> r) {
+        AbstractManagedObjectDefinition<C, S> d = r.getChildDefinition();
+
+        // Process all relations associated directly with this
+        // definition.
+        helpHandler.registerManagedObjectDefinition(d);
+        processPath(path.child(r, d));
+
+        // Now process relations associated with derived definitions.
+        for (AbstractManagedObjectDefinition<? extends C, ? extends S> c : d.getAllChildren()) {
+            helpHandler.registerManagedObjectDefinition(c);
+            processPath(path.child(r, c));
+        }
+    }
+
+    /** Process a singleton relation. */
+    private <C extends ConfigurationClient, S extends Configuration> void processRelation(ManagedObjectPath<?, ?> path,
+            SingletonRelationDefinition<C, S> r) {
+        AbstractManagedObjectDefinition<C, S> d = r.getChildDefinition();
+
+        // Process all relations associated directly with this
+        // definition.
+        helpHandler.registerManagedObjectDefinition(d);
+        processPath(path.child(r, d));
+
+        // Now process relations associated with derived definitions.
+        for (AbstractManagedObjectDefinition<? extends C, ? extends S> c : d.getAllChildren()) {
+            helpHandler.registerManagedObjectDefinition(c);
+            processPath(path.child(r, c));
+        }
+    }
+}
diff --git a/opendj-config/src/main/resources/com/forgerock/opendj/ldap/config.properties b/opendj-config/src/main/resources/com/forgerock/opendj/ldap/config.properties
index ba5ec67..952acb3 100644
--- a/opendj-config/src/main/resources/com/forgerock/opendj/ldap/config.properties
+++ b/opendj-config/src/main/resources/com/forgerock/opendj/ldap/config.properties
@@ -932,3 +932,12 @@
 WARN_CONFIG_LOGGING_UNSUPPORTED_FIELDS_IN_LOG_FORMAT_734=The log format \
  for %s contains the folowing unsupported fields: %s. Their output will be \
  replaced with a dash ("-") character
+ERR_BUILDVERSION_MISMATCH_735=The OpenDJ binary version '%s' does not match the installed \
+version '%s'. Please run upgrade before continuing
+ERR_BUILDVERSION_MALFORMED_736=The version of the installed OpenDJ could not be determined \
+because the version file '%s' exists but contains invalid data. \
+Restore it from backup before continuing
+ERR_BUILDVERSION_NOT_FOUND_737=The version of the installed OpenDJ could not be determined \
+because the version file '%s' could not be found. Restore it from backup before continuing
+ERR_CONFIGVERSION_NOT_FOUND_738=The version of the installed OpenDJ could not be determined \
+because tan error occurs during reading the current configuration.

--
Gitblit v1.10.0