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

Violette Roche-Montane
25.41.2014 a3b0441c12b207c0fdfce0566dba2db5ecd3816c
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.


** DsConfig.java
- Added logger.
- Removed checkVersionMismatch and moved it to LDAPManagementContextFactory where the connection is opened.

** BuildVersion.java
- Added function to get the current configuration entry value of "cn=Version,cn=monitor"

** LDAPManagementContextFactory.java
- Added checkVersionMismatch.

**config.properties
- Added messages linked to BuildVersion.

** SDK *check* style modifications on :
- PropertyEditorModification.java
- PropertyValueEditor.java
- HelpSubCommandHandler.java
- SetPropSubCommandHandler.java
- SubCommandHandler.java
- ListSubCommandHandler.java
- CreateSubCommandHandler.java
- PropertyValuePrinter.java
- DeleteSubCommandHandler.java
- SubCommandHandlerFactory.java
- ArgumentExceptionFactory.java
- GetPropSubCommandHandler.java
- CLIProfile.java

** pom file : added dependency on opendj-cli.

16 files added
3 files modified
11310 ■■■■■ changed files
opendj-config/pom.xml 5 ●●●●● patch | view | raw | blame | history
opendj-config/src/main/java/org/forgerock/opendj/config/ConfigurationFramework.java 20 ●●●●● patch | view | raw | blame | history
opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/ArgumentExceptionFactory.java 563 ●●●●● patch | view | raw | blame | history
opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/BuildVersion.java 264 ●●●●● patch | view | raw | blame | history
opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/CLIProfile.java 129 ●●●●● patch | view | raw | blame | history
opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/CreateSubCommandHandler.java 1354 ●●●●● patch | view | raw | blame | history
opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/DSConfig.java 1183 ●●●●● patch | view | raw | blame | history
opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/DeleteSubCommandHandler.java 383 ●●●●● patch | view | raw | blame | history
opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/GetPropSubCommandHandler.java 393 ●●●●● patch | view | raw | blame | history
opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/HelpSubCommandHandler.java 1052 ●●●●● patch | view | raw | blame | history
opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/LDAPManagementContextFactory.java 167 ●●●●● patch | view | raw | blame | history
opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/ListSubCommandHandler.java 493 ●●●●● patch | view | raw | blame | history
opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/PropertyEditorModification.java 196 ●●●●● patch | view | raw | blame | history
opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/PropertyValueEditor.java 2319 ●●●●● patch | view | raw | blame | history
opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/PropertyValuePrinter.java 193 ●●●●● patch | view | raw | blame | history
opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/SetPropSubCommandHandler.java 910 ●●●●● patch | view | raw | blame | history
opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/SubCommandHandler.java 1343 ●●●●● patch | view | raw | blame | history
opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/SubCommandHandlerFactory.java 334 ●●●●● patch | view | raw | blame | history
opendj-config/src/main/resources/com/forgerock/opendj/ldap/config.properties 9 ●●●●● patch | view | raw | blame | history
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>
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;
    }
}
opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/ArgumentExceptionFactory.java
New file
@@ -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.
    }
}
opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/BuildVersion.java
New file
@@ -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();
    }
}
opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/CLIProfile.java
New file
@@ -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"));
    }
}
opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/CreateSubCommandHandler.java
New file
@@ -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;
    }
}
opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/DSConfig.java
New file
@@ -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;
  }
}
opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/DeleteSubCommandHandler.java
New file
@@ -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;
    }
}
opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/GetPropSubCommandHandler.java
New file
@@ -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());
                }
            }
        }
    }
}
opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/HelpSubCommandHandler.java
New file
@@ -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;
                }
            }
        }
    }
}
opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/LDAPManagementContextFactory.java
New file
@@ -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;
  }
}
opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/ListSubCommandHandler.java
New file
@@ -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());
        }
    }
}
opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/PropertyEditorModification.java
New file
@@ -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();
    }
}
opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/PropertyValueEditor.java
New file
@@ -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));
        }
    }
}
opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/PropertyValuePrinter.java
New file
@@ -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);
    }
}
opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/SetPropSubCommandHandler.java
New file
@@ -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;
    }
}
opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/SubCommandHandler.java
New file
@@ -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();
    }
}
opendj-config/src/main/java/org/forgerock/opendj/config/dsconfig/SubCommandHandlerFactory.java
New file
@@ -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));
        }
    }
}
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.