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