From eaa23f4b7af97c108ecffa40c86c32e723a90594 Mon Sep 17 00:00:00 2001
From: matthew_swift <matthew_swift@localhost>
Date: Wed, 29 Aug 2007 14:40:34 +0000
Subject: [PATCH] Fix issue 1831: dsconfig interactive mode.

---
 opends/src/server/org/opends/server/admin/PropertyDefinitionUsageBuilder.java            |    2 
 opends/src/server/org/opends/server/tools/dsconfig/ArgumentExceptionFactory.java         |   69 
 opends/src/server/org/opends/server/util/cli/MenuBuilder.java                            |  818 ++++++
 opends/src/server/org/opends/server/util/cli/MenuCallback.java                           |   21 
 opends/src/server/org/opends/server/util/cli/HelpCallback.java                           |    4 
 opends/src/server/org/opends/server/util/cli/OutputStreamConsoleApplication.java         |  105 
 opends/src/messages/messages/utility.properties                                          |   28 
 opends/src/messages/src/org/opends/messages/Category.java                                |    6 
 opends/src/server/org/opends/server/util/cli/ValidationCallback.java                     |   10 
 opends/src/server/org/opends/server/admin/PropertyDefinitionVisitor.java                 |    4 
 opends/src/server/org/opends/server/tools/dsconfig/HelpSubCommandHandler.java            |  437 ++-
 opends/src/server/org/opends/server/util/cli/ErrorStreamConsoleApplication.java          |  105 
 opends/src/server/org/opends/server/tools/dsconfig/ListSubCommandHandler.java            |   91 
 opends/src/server/org/opends/server/tools/dsconfig/PropertyValueEditor.java              | 1888 ++++++++++++++
 opends/src/server/org/opends/server/admin/client/cli/DsFrameworkCliParser.java           |    2 
 opends/src/server/org/opends/server/admin/client/cli/DsFrameworkCliGlobalAdmin.java      |    1 
 opends/src/server/org/opends/server/admin/client/ldap/LDAPManagedObject.java             |   11 
 opends/src/server/org/opends/server/util/cli/MenuResult.java                             |  324 ++
 opends/src/server/org/opends/server/tools/dsconfig/SubCommandHandler.java                |  323 +
 opends/src/server/org/opends/server/util/cli/Menu.java                                   |   52 
 opends/src/server/org/opends/server/tools/dsconfig/DSConfig.java                         |  426 ++
 opends/src/server/org/opends/server/tools/dsconfig/PropertyValuePrinter.java             |   88 
 opends/src/server/org/opends/server/tools/dsconfig/LDAPManagementContextFactory.java     |  147 +
 opends/src/server/org/opends/server/tools/dsconfig/GetPropSubCommandHandler.java         |   98 
 opends/src/server/org/opends/server/util/cli/CLIException.java                           |   92 
 opends/src/messages/messages/dsconfig.properties                                         |  409 +++
 opends/src/messages/messages/tools.properties                                            |  301 --
 opends/src/server/org/opends/server/tools/dsconfig/SubCommandHandlerFactory.java         |  357 ++
 opends/src/server/org/opends/server/admin/client/ManagedObject.java                      |   19 
 opends/src/server/org/opends/server/admin/client/cli/DsFrameworkCliMain.java             |    1 
 opends/src/server/org/opends/server/tools/dsconfig/DeleteSubCommandHandler.java          |  218 +
 /dev/null                                                                                |  304 --
 opends/src/server/org/opends/server/tools/dsconfig/ManagementContextFactory.java         |    1 
 opends/src/server/org/opends/server/util/cli/package-info.java                           |   29 
 opends/src/server/org/opends/server/admin/client/cli/DsFrameworkCliServer.java           |    1 
 opends/build.xml                                                                         |    3 
 opends/src/server/org/opends/server/tools/dsconfig/SetPropSubCommandHandler.java         |  150 
 opends/src/server/org/opends/server/tools/DBTest.java                                    |    2 
 opends/src/server/org/opends/server/tools/dsconfig/InternalManagementContextFactory.java |    1 
 opends/src/server/org/opends/server/tools/dsconfig/CreateSubCommandHandler.java          |  312 +
 opends/src/server/org/opends/server/util/cli/ConsoleApplication.java                     |  453 +++
 41 files changed, 6,296 insertions(+), 1,417 deletions(-)

diff --git a/opends/build.xml b/opends/build.xml
index 9a4e320..7b71e47 100644
--- a/opends/build.xml
+++ b/opends/build.xml
@@ -214,6 +214,9 @@
     <genmsg sourceProps="${msg.prop.dir}/core.properties"
             destJava="${msg.javagen.dir}/org/opends/messages/CoreMessages.java">
     </genmsg>
+    <genmsg sourceProps="${msg.prop.dir}/dsconfig.properties"
+            destJava="${msg.javagen.dir}/org/opends/messages/DSConfigMessages.java">
+    </genmsg>
     <genmsg sourceProps="${msg.prop.dir}/extension.properties"
             destJava="${msg.javagen.dir}/org/opends/messages/ExtensionMessages.java">
     </genmsg>
diff --git a/opends/src/messages/messages/dsconfig.properties b/opends/src/messages/messages/dsconfig.properties
new file mode 100644
index 0000000..0cf9cc7
--- /dev/null
+++ b/opends/src/messages/messages/dsconfig.properties
@@ -0,0 +1,409 @@
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License, Version 1.0 only
+# (the "License").  You may not use this file except in compliance
+# with the License.
+#
+# You can obtain a copy of the license at
+# trunk/opends/resource/legal-notices/OpenDS.LICENSE
+# or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at
+# trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+# add the following below this CDDL HEADER, with the fields enclosed
+# by brackets "[]" replaced with your own identifying information:
+#      Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+#      Portions Copyright 2006-2007 Sun Microsystems, Inc.
+
+
+
+#
+# Global directives
+#
+global.category=DSCONFIG
+
+#
+# Format string definitions
+#
+# Keys must be formatted as follows:
+#
+# [SEVERITY]_[DESCRIPTION]_[ORDINAL]
+#
+# where:
+#
+# SEVERITY is one of:
+# [INFO, MILD_WARN, SEVERE_WARN, MILD_ERR, SEVERE_ERR, FATAL_ERR, DEBUG, NOTICE]
+#
+# DESCRIPTION is an upper case string providing a hint as to the context of
+# the message in upper case with the underscore ('_') character serving as
+# word separator
+#
+# ORDINAL is an integer unique among other ordinals in this file
+#
+SEVERE_ERR_DSCFG_ERROR_CANNOT_READ_CONNECTION_PARAMETERS_1000=The connection \
+ parameters could not be read due to the following error: %s
+SEVERE_ERR_DSCFG_ERROR_LDAP_SIMPLE_BIND_FAILED_1001=Unable to authenticate to \
+ the server as %s
+SEVERE_ERR_DSCFG_ERROR_LDAP_FAILED_TO_CONNECT_1002=Unable to connect to the \
+ server at %s on port %s
+SEVERE_ERR_DSCFG_ERROR_LDAP_SIMPLE_BIND_NOT_SUPPORTED_1003=Unable to \
+ authenticate using simple authentication
+INFO_DSCFG_DESCRIPTION_SUBCMD_CREATE_1004=Creates %s
+INFO_DSCFG_DESCRIPTION_SUBCMD_DELETE_1005=Deletes %s
+INFO_DSCFG_DESCRIPTION_SUBCMD_LIST_1006=Lists existing %s
+INFO_DSCFG_DESCRIPTION_SUBCMD_GETPROP_1007=Shows %s properties
+INFO_DSCFG_DESCRIPTION_SUBCMD_SETPROP_1008=Modifies %s properties
+SEVERE_ERR_DSCFG_ERROR_MISSING_SUBCOMMAND_1009=A sub-command must be \
+ specified
+INFO_DSCFG_DESCRIPTION_TYPE_1010=The type of %s which should be created. The \
+ value for TYPE can be one of: %s
+SEVERE_ERR_DSCFG_ERROR_NO_PASSWORD_1011=No password was specified for \
+ administrator "%s"
+SEVERE_ERR_DSCFG_ERROR_PROPERTY_UNRECOGNIZED_1012=The property "%s" is not a \
+ recognized property of %s
+SEVERE_ERR_DSCFG_ERROR_PROPERTY_INVALID_VALUE_1013=The value "%s" is not a \
+ valid value for the %s property "%s" which has the following syntax: %s
+SEVERE_ERR_DSCFG_ERROR_PROPERTY_READ_ONLY_1014=The %s property "%s" is \
+ read-only and cannot be modified
+SEVERE_ERR_DSCFG_ERROR_PROPERTY_MANDATORY_1015=The %s property "%s" is \
+ mandatory and must be specified
+SEVERE_ERR_DSCFG_ERROR_PROPERTY_SINGLE_VALUED_1016=It is not possible to \
+ specify multiple values for the %s property "%s" as it is single-valued
+INFO_DSCFG_DESCRIPTION_SUBCMD_HELPPROP_1017=Describes managed objects and \
+ their properties
+INFO_DSCFG_HEADING_COMPONENT_NAME_1018=Component
+INFO_DSCFG_HEADING_PROPERTY_NAME_1019=Property
+INFO_DSCFG_HEADING_PROPERTY_VALUE_1020=Value(s)
+INFO_DSCFG_HEADING_PROPERTY_SYNTAX_1021=Syntax
+INFO_DSCFG_HEADING_PROPERTY_OPTIONS_1022=Options
+INFO_DSCFG_HEADING_PROPERTY_DEFAULT_1023=Default
+INFO_DSCFG_HEADING_PROPERTY_DESCRIPTION_1024=Description
+INFO_DSCFG_DESCRIPTION_PROPERTY_SYNTAX_HELP_1025=See detailed help
+SEVERE_ERR_DSCFG_ERROR_GET_PARENT_DDE_1026=The parent %s could not be \
+ retrieved because its type could not be determined. This is probably due to \
+ the %s having an invalid LDAP entry. Check that the %s has the correct object \
+ classes
+SEVERE_ERR_DSCFG_ERROR_GET_PARENT_MODE_1027=The parent %s could not be \
+ retrieved because of the reasons listed below:
+SEVERE_ERR_DSCFG_ERROR_GET_PARENT_MONFE_1028=The parent %s does not exist
+SEVERE_ERR_DSCFG_ERROR_GET_PARENT_AUTHZ_1029=The parent %s could not be \
+ retrieved because you do not have the correct authorization
+SEVERE_ERR_DSCFG_ERROR_GET_PARENT_CE_1030=The parent %s could not be \
+ retrieved due to a communications problem: %s
+SEVERE_ERR_DSCFG_ERROR_GET_PARENT_CME_1031=The parent %s could not be \
+ retrieved because another client is currently making conflicting \
+ configuration changes
+SEVERE_ERR_DSCFG_ERROR_CREATE_MMPE_1032=The %s could not be created because \
+ the following mandatory properties must be defined: %s
+SEVERE_ERR_DSCFG_ERROR_CREATE_MOAEE_1033=The %s could not be created because \
+ there is already an existing one with the same name
+SEVERE_ERR_DSCFG_ERROR_CREATE_AUTHZ_1034=The %s could not be created because \
+ you do not have the correct authorization
+SEVERE_ERR_DSCFG_ERROR_CREATE_CE_1035=The %s could not be created due to a \
+ communications problem: %s
+SEVERE_ERR_DSCFG_ERROR_CREATE_CME_1036=The %s could not be created because \
+ another client is currently making conflicting configuration changes
+SEVERE_ERR_DSCFG_ERROR_CREATE_ORE_1037=The server prevented the %s from being \
+ created because of the following reason: %s
+SEVERE_ERR_DSCFG_ERROR_DELETE_MONFE_1038=The %s could not be deleted because \
+ it does not exist
+SEVERE_ERR_DSCFG_ERROR_DELETE_AUTHZ_1039=The %s could not be deleted because \
+ you do not have the correct authorization
+SEVERE_ERR_DSCFG_ERROR_DELETE_ORE_1040=The server prevented the %s from being \
+ deleted because of the following reason: %s
+SEVERE_ERR_DSCFG_ERROR_DELETE_CE_1041=The %s could not be deleted due to a \
+ communications problem: %s
+SEVERE_ERR_DSCFG_ERROR_DELETE_CME_1042=The %s could not be deleted because \
+ another client is currently making conflicting configuration changes
+SEVERE_ERR_DSCFG_ERROR_GET_CHILD_DDE_1043=The %s could not be retrieved \
+ because its type could not be determined. This is probably due to the %s \
+ having an invalid LDAP entry. Check that the %s object classes are correct
+SEVERE_ERR_DSCFG_ERROR_GET_CHILD_MODE_1044=The %s could not be retrieved \
+ because of the reasons listed below:
+SEVERE_ERR_DSCFG_ERROR_GET_CHILD_MONFE_1045=The %s does not exist
+SEVERE_ERR_DSCFG_ERROR_GET_CHILD_AUTHZ_1046=The %s could not be accessed \
+ because you do not have the correct authorization
+SEVERE_ERR_DSCFG_ERROR_GET_CHILD_CE_1047=The %s could not be accessed due to \
+ a communications problem: %s
+SEVERE_ERR_DSCFG_ERROR_GET_CHILD_CME_1048=The %s could not be accessed \
+ because another client is currently making conflicting configuration changes
+SEVERE_ERR_DSCFG_ERROR_MODIFY_MONFE_1049=The %s could not be modified because \
+ it does not exist
+SEVERE_ERR_DSCFG_ERROR_MODIFY_AUTHZ_1050=The %s could not be modified because \
+ you do not have the correct authorization
+SEVERE_ERR_DSCFG_ERROR_MODIFY_CE_1051=The %s could not be modified due to a \
+ communications problem: %s
+SEVERE_ERR_DSCFG_ERROR_MODIFY_CME_1052=The %s could not be modified because \
+ another client is currently making conflicting configuration changes
+SEVERE_ERR_DSCFG_ERROR_MODIFY_ORE_1053=The server prevented the %s from being \
+ modified because of the following reason: %s
+SEVERE_ERR_DSCFG_ERROR_LIST_DDE_1054=The %s could not be retrieved because \
+ its type could not be determined. This is probably due to the %s having an \
+ invalid LDAP entry. Check that the %s object classes are correct
+SEVERE_ERR_DSCFG_ERROR_LIST_MODE_1055=The %s could not be retrieved because \
+ of the reasons listed below:
+SEVERE_ERR_DSCFG_ERROR_LIST_MONFE_1056=The %s does not exist
+SEVERE_ERR_DSCFG_ERROR_LIST_AUTHZ_1057=The %s could not be listed because you \
+ do not have the correct authorization
+SEVERE_ERR_DSCFG_ERROR_LIST_CE_1058=The %s could not be listed due to a \
+ communications problem: %s
+SEVERE_ERR_DSCFG_ERROR_LIST_CME_1059=The %s could not be listed because \
+ another client is currently making conflicting configuration changes
+SEVERE_ERR_DSCFG_ERROR_PROPERTY_UNKNOWN_ERROR_1060=The value(s) of the %s \
+ property "%s" could not be determined due to an unknown error: %s
+SEVERE_ERR_DSCFG_ERROR_PROPERTY_DEFAULT_BEHAVIOR_1061=The default value(s) of \
+ the %s property "%s" could not be determined due to the following reason: %s
+SEVERE_ERR_DSCFG_ERROR_PROPERTY_INHERITED_DEFAULT_BEHAVIOR_1062=The inherited \
+ default value(s) of the %s property "%s" could not be determined
+SEVERE_ERR_DSCFG_ERROR_NO_SEPARATOR_IN_PROPERTY_VALUE_1063=The property \
+ argument "%s" does not contain a name/value separator. The argument should \
+ have the following syntax: property:value
+SEVERE_ERR_DSCFG_ERROR_NO_NAME_IN_PROPERTY_VALUE_1064=The property argument \
+ "%s" does not contain a property name. The argument should have the following \
+ syntax: property:value
+SEVERE_ERR_DSCFG_ERROR_NO_VALUE_IN_PROPERTY_VALUE_1065=The property argument \
+ "%s" does not contain a property value. The argument should have the \
+ following syntax: property:value
+SEVERE_ERR_DSCFG_ERROR_SUB_TYPE_UNRECOGNIZED_1066=The sub-type "%s" is not a \
+ recognized type of %s. It should be one of: %s
+SEVERE_ERR_DSCFG_ERROR_TYPE_UNRECOGNIZED_1067="%s" is not a recognized \
+ component type
+SEVERE_ERR_DSCFG_ERROR_NO_SEPARATOR_IN_PROPERTY_MOD_1068=The property \
+ modification "%s" does not contain a name/value separator. The argument \
+ should have the following syntax: property[+|-]:value
+SEVERE_ERR_DSCFG_ERROR_NO_NAME_IN_PROPERTY_MOD_1069=The property modification \
+ "%s" does not contain a property name. The argument should have the following \
+ syntax: property[+|-]:value
+SEVERE_ERR_DSCFG_ERROR_NO_VALUE_IN_PROPERTY_MOD_1070=The property \
+ modification "%s" does not contain a property value. The argument should have \
+ the following syntax: property[+|-]:value
+SEVERE_ERR_DSCFG_ERROR_INCOMPATIBLE_PROPERTY_MOD_1071=The property \
+ modification "%s" is incompatible with a previous modification to the same \
+ property
+SEVERE_ERR_DSCFG_ERROR_WRONG_MANAGED_OBJECT_TYPE_1072=The %s could not be \
+ retrieved because it was the wrong type of managed object: %s
+INFO_DSCFG_DESCRIPTION_TYPE_DEFAULT_1073=The type of %s which should be \
+ created (Default: %s). The value for TYPE can be one of: %s
+INFO_DSCFG_DESCRIPTION_RECORD_1074=Modifies the display output to show one \
+ property value per line
+INFO_DSCFG_DESCRIPTION_UNIT_TIME_1078=Display time data using the specified \
+ unit. The value for UNIT can be one of ms, s, m, h, d, or w (milliseconds, \
+ seconds, minutes, hours, days, or weeks)
+INFO_DSCFG_DESCRIPTION_UNIT_SIZE_1079=Display size data using the specified \
+ unit. The value for UNIT can be one of b, kb, mb, gb, or tb (bytes, \
+ kilobytes, megabytes, gigabytes, or terabytes)
+INFO_DSCFG_ERROR_TIME_UNIT_UNRECOGNIZED_1080=The time unit "%s" is invalid. \
+ The valid time units are ms, s, m, h, d, or w (milliseconds, seconds, \
+ minutes, hours, days, or weeks)
+INFO_DSCFG_ERROR_SIZE_UNIT_UNRECOGNIZED_1081=The size unit "%s" is invalid. \
+ The valid size units are b, kb, mb, gb, or tb (bytes, kilobytes, megabytes, \
+ gigabytes, or terabytes)
+INFO_DSCFG_HEADING_COMPONENT_TYPE_1082=Type
+INFO_DSCFG_DESCRIPTION_SHOW_GROUP_USAGE_1083=Display subcommands relating to \
+ %s
+INFO_DSCFG_DESCRIPTION_SHOW_GROUP_USAGE_ALL_1084=Display all subcommands
+INFO_DSCFG_DESCRIPTION_SHOW_GROUP_USAGE_SUMMARY_1085=Display summary usage \
+ information
+INFO_DSCFG_DESCRIPTION_NAME_1086=The name of the %s
+INFO_DSCFG_DESCRIPTION_PROP_1087=The name of a property to be displayed
+INFO_DSCFG_DESCRIPTION_PROP_VAL_1088=Assigns a value to a property where PROP \
+ is the name of the property and VAL is the single value to be assigned. \
+ Specify the same property multiple times in order to assign more than one \
+ value to it
+INFO_DSCFG_DESCRIPTION_ADD_PROP_VAL_1089=Adds a single value to a property \
+ where PROP is the name of the property and VAL is the single value to be \
+ added
+INFO_DSCFG_DESCRIPTION_REMOVE_PROP_VAL_1090=Removes a single value from a \
+ property where PROP is the name of the property and VAL is the single value \
+ to be removed
+INFO_DSCFG_DESCRIPTION_RESET_PROP_1091=Resets a property back to its default \
+ values where PROP is the name of the property to be reset
+INFO_DSCFG_DESCRIPTION_HELP_TYPE_1092=The type of components whose properties \
+ should be described. The value for TYPE must be one of the component types \
+ associated with the CATEGORY specified using the "--category" option
+SEVERE_ERR_DSCFG_ERROR_BIND_PASSWORD_NONINTERACTIVE_1093=The LDAP bind \
+ password was not specified and cannot be read interactively
+INFO_DSCFG_DESCRIPTION_FORCE_1196=Ignore non-existent %s
+SEVERE_ERR_DSCFG_ERROR_UNABLE_TO_RESET_MANDATORY_PROPERTY_1200=The %s \
+ property "%s" is mandatory cannot be reset. Use the "%s" option to specify a \
+ new value
+SEVERE_ERR_DSCFG_ERROR_ILLEGAL_NAME_SYNTAX_1204=The name "%s" is not a valid \
+ name for the %s which has the following syntax: %s
+SEVERE_ERR_DSCFG_ERROR_ILLEGAL_NAME_EMPTY_1205=Empty names are not permitted \
+ for %s
+SEVERE_ERR_DSCFG_ERROR_ILLEGAL_NAME_BLANK_1206=Blank names are not permitted \
+ for %s
+SEVERE_ERR_DSCFG_ERROR_ILLEGAL_NAME_UNKNOWN_1207=The name "%s" is not a valid \
+ name for the %s
+INFO_DSCFG_DESCRIPTION_NAME_CREATE_1208=The name of the new %s
+INFO_DSCFG_DESCRIPTION_NAME_CREATE_EXT_1209=The name of the new %s which will \
+ also be used as the value of the "%s" property: %s
+SEVERE_ERR_DSCFG_ERROR_UNABLE_TO_SET_NAMING_PROPERTY_1210=The property "%s" \
+ cannot be set as it is defined implicitly by the name of the %s
+INFO_DSCFG_DESCRIPTION_ADVANCED_GET_1216=Modifies the display output to show \
+ the advanced properties of the %s
+INFO_DSCFG_DESCRIPTION_ADVANCED_SET_1217=Allows the configuration of advanced \
+ properties during interactive mode
+INFO_DSCFG_DESCRIPTION_ADVANCED_HELP_1218=Modifies the display output to show \
+ the advanced properties of components
+SEVERE_ERR_DSCFG_ERROR_MISSING_NON_INTERACTIVE_ARG_1223=The argument "--%s" \
+ must be specified when this application is used non-interactively
+SEVERE_ERR_DSCFG_ERROR_CANNOT_READ_CONSOLE_INPUT_1224=The response could not \
+ be read from the console due to the following error: %s
+INFO_DSCFG_CREATE_TYPE_PROMPT_1225=>>>> Select the type of %s that you want to \
+ create:
+INFO_DSCFG_CREATE_NAME_PROMPT_1226=>>>> Enter a name for the %s that you want to \
+ create:
+SEVERE_ERR_DSCFG_ERROR_CREATE_NAME_ALREADY_EXISTS_1227=There is already \
+ another %s with the name "%s"
+INFO_DSCFG_DESCRIPTION_CREATE_HELP_HEADING_TYPE_1228=Type
+INFO_DSCFG_DESCRIPTION_CREATE_HELP_HEADING_DESCR_1229=Description
+SEVERE_ERR_DSCFG_ERROR_FINDER_NO_CHILDREN_1230=Unable to continue since there \
+ are no %s currently configured on the server
+SEVERE_ERR_DSCFG_ERROR_FINDER_SINGLE_CHILD_REJECTED_1231=Unable to continue \
+ because the only available %s was not selected
+INFO_DSCFG_FINDER_PROMPT_SINGLE_1232=>>>> There is only one %s: "%s". Are you sure \
+ that this is the correct one?
+INFO_DSCFG_FINDER_PROMPT_MANY_1233=>>>> Select the %s from the following list:
+INFO_DSCFG_GENERAL_CHOICE_PROMPT_NOHELP_1237=Enter choice [1 - %d]:
+INFO_DSCFG_GENERAL_CHOICE_PROMPT_HELP_1238=Enter choice [1 - %d,  ? - help]:
+SEVERE_ERR_DSCFG_ERROR_GENERAL_CHOICE_1239=Invalid response. Please enter a \
+ value between 1 and %d
+INFO_DSCFG_HELP_FIELD_ENUM_1254=one of the following values:
+INFO_DSCFG_HELP_FIELD_UNDEFINED_1255=undefined
+INFO_DSCFG_HELP_FIELD_INHERITED_ABS_1256=inherits from the property "%s" in \
+ the %s
+INFO_DSCFG_HELP_FIELD_INHERITED_PARENT_1257=inherits from the property "%s" \
+ in the parent %s
+INFO_DSCFG_HELP_FIELD_INHERITED_THIS_1258=inherits from the property "%s" in \
+ this %s
+INFO_DSCFG_HELP_FIELD_SERVER_RESTART_1259=The server must be restarted in \
+ order for changes to this property to take effect
+INFO_DSCFG_HELP_FIELD_COMPONENT_RESTART_1260=The %s must be restarted in \
+ order for changes to this property to take effect
+INFO_DSCFG_HELP_FIELD_READ_ONLY_1261=read-only - this property can only be \
+ specified when the %s is created
+INFO_DSCFG_HELP_FIELD_MONITORING_1262=monitoring - this property is \
+ automatically generated by the server
+INFO_DSCFG_HELP_HEADING_PROPERTY_1263=Property: %s
+INFO_DSCFG_HELP_HEADING_COMPONENT_1264=Component name: %s
+INFO_DSCFG_HELP_HEADING_DEFAULT_1265=Default behavior
+INFO_DSCFG_HELP_HEADING_MANDATORY_1266=Mandatory
+INFO_DSCFG_HELP_HEADING_ADVANCED_1267=Advanced
+INFO_DSCFG_HELP_HEADING_MULTI_VALUED_1268=Multi-valued
+INFO_DSCFG_HELP_HEADING_READ_ONLY_1269=Read-only
+INFO_DSCFG_HELP_HEADING_SYNTAX_1270=Syntax
+INFO_DSCFG_HELP_DESCRIPTION_OPTION_1271=Option Types:
+INFO_DSCFG_HELP_DESCRIPTION_READ_1272=Property value(s) are readable
+INFO_DSCFG_HELP_DESCRIPTION_WRITE_1273=Property value(s) are writable
+INFO_DSCFG_HELP_DESCRIPTION_MANDATORY_1274=The property is mandatory
+INFO_DSCFG_HELP_DESCRIPTION_SINGLE_VALUED_1275=The property is single-valued
+INFO_DSCFG_HELP_DESCRIPTION_ADMIN_ACTION_1276=Administrative action is \
+ required for changes to take effect
+INFO_DSCFG_CONFIRM_CREATE_1277=Are you sure that you want to create the %s?
+INFO_DSCFG_CONFIRM_DELETE_1278=Are you sure that you want to delete the %s?
+INFO_DSCFG_CONFIRM_MODIFY_1279=Are you sure that you want to modify the %s?
+INFO_DSCFG_CONFIRM_CREATE_SUCCESS_1280=The %s was created successfully
+INFO_DSCFG_CONFIRM_DELETE_SUCCESS_1281=The %s was deleted successfully
+INFO_DSCFG_CONFIRM_MODIFY_SUCCESS_1282=The %s was modified successfully
+INFO_DSCFG_CONFIRM_CREATE_FAIL_1283=The %s was not created
+INFO_DSCFG_CONFIRM_DELETE_FAIL_1284=The %s was not deleted
+INFO_DSCFG_CONFIRM_MODIFY_FAIL_1285=The %s was not modified
+INFO_DSCFG_DESCRIPTION_HELP_CATEGORY_1286=The category of components whose \
+ properties should be described
+SEVERE_ERR_DSCFG_ERROR_CATEGORY_UNRECOGNIZED_1287="%s" is not a recognized \
+ component category
+SEVERE_ERR_DSCFG_ERROR_CATEGORY_TYPE_UNRECOGNIZED_1288="%s" is not a \
+ recognized component type in category "%s"
+SEVERE_ERR_DSCFG_ERROR_PROPERTY_UNRECOGNIZED_NO_DEFN_1289=The property "%s" \
+ is not a recognized property
+INFO_DSCFG_DESCRIPTION_HELP_INHERITED_1290=Modifies the display output to \
+ show the inherited properties of components
+INFO_VALUE_TRUE_1291=true
+INFO_VALUE_FALSE_1292=false
+INFO_VALUE_UNLIMITED_1293=unlimited
+INFO_EDITOR_PROMPT_SELECT_VALUE_SINGLE_1294=Select a value for the "%s" property:
+INFO_EDITOR_PROMPT_SELECT_VALUE_MULTI_1295=Select one or more values for the "%s" property:
+INFO_EDITOR_HEADING_SYNTAX_1296=Syntax: %s
+INFO_EDITOR_HEADING_VALUES_SUMMARY_1297=The "%s" property has the following values:
+INFO_EDITOR_PROMPT_SELECT_VALUES_ADD_1299=Select the values you wish to add:
+INFO_EDITOR_PROMPT_SELECT_VALUES_REMOVE_1302=Select the values you wish to remove:
+INFO_EDITOR_PROMPT_MODIFY_MENU_1303=Do you want to modify the "%s" property?
+INFO_EDITOR_OPTION_VALUES_1298=%d)
+INFO_EDITOR_OPTION_ADD_ALL_VALUES_1300=Add all values
+INFO_EDITOR_OPTION_ADD_ONE_OR_MORE_VALUES_1304=Add one or more values
+INFO_EDITOR_OPTION_REMOVE_ONE_OR_MORE_VALUES_1305=Remove one or more values
+INFO_EDITOR_OPTION_REMOVE_ALL_VALUES_1306=Remove all values
+INFO_EDITOR_OPTION_REVERT_CHANGES_1307=Revert changes
+INFO_EDITOR_OPTION_LEAVE_UNDEFINED_1311=Leave undefined
+INFO_EDITOR_OPTION_USE_DEFAULT_ALIAS_1308=Use the default behavior: %s
+INFO_EDITOR_OPTION_USE_DEFAULT_INHERITED_ALIAS_1309=Use the inherited default behavior: %s
+INFO_EDITOR_OPTION_USE_DEFAULT_INHERITED_ALIAS_UNDEFINED_1310=Use the inherited default behavior: undefined
+INFO_EDITOR_OPTION_USE_VALUE_1312=Use the value: %s
+INFO_EDITOR_OPTION_USE_DEFAULT_VALUE_1313=Use the default value: %s
+INFO_EDITOR_OPTION_USE_INHERITED_DEFAULT_VALUE_1314=Use the inherited default value: %s
+INFO_EDITOR_OPTION_USE_VALUES_1315=Use these values
+INFO_EDITOR_OPTION_USE_DEFAULT_VALUES_1316=Use these default values
+INFO_EDITOR_OPTION_USE_INHERITED_DEFAULT_VALUES_1317=Use these inherited default values
+INFO_EDITOR_OPTION_KEEP_DEFAULT_ALIAS_1318=Keep the default behavior: %s
+INFO_EDITOR_OPTION_KEEP_DEFAULT_INHERITED_ALIAS_1319=Keep the inherited default behavior: %s
+INFO_EDITOR_OPTION_KEEP_DEFAULT_INHERITED_ALIAS_UNDEFINED_1320=Keep the inherited default behavior: undefined
+INFO_EDITOR_OPTION_KEEP_VALUE_1321=Keep the value: %s
+INFO_EDITOR_OPTION_KEEP_DEFAULT_VALUE_1322=Keep the default value: %s
+INFO_EDITOR_OPTION_KEEP_INHERITED_DEFAULT_VALUE_1323=Keep the inherited default value: %s
+INFO_EDITOR_OPTION_KEEP_VALUES_1324=Keep these values
+INFO_EDITOR_OPTION_KEEP_DEFAULT_VALUES_1325=Keep these default values
+INFO_EDITOR_OPTION_KEEP_INHERITED_DEFAULT_VALUES_1326=Keep these inherited default values
+INFO_EDITOR_OPTION_RESET_DEFAULT_ALIAS_1327=Reset to the default behavior: %s
+INFO_EDITOR_OPTION_RESET_DEFAULT_INHERITED_ALIAS_1328=Reset to the inherited default behavior: %s
+INFO_EDITOR_OPTION_RESET_DEFAULT_INHERITED_ALIAS_UNDEFINED_1329=Reset to the inherited default behavior: undefined
+INFO_EDITOR_OPTION_RESET_DEFAULT_VALUE_1331=Reset to the default value: %s
+INFO_EDITOR_OPTION_RESET_INHERITED_DEFAULT_VALUE_1332=Reset to the inherited default value: %s
+INFO_EDITOR_OPTION_RESET_DEFAULT_VALUES_1334=Reset to the default values: %s
+INFO_EDITOR_OPTION_RESET_INHERITED_DEFAULT_VALUES_1335=Reset to the inherited default values: %s
+INFO_EDITOR_HEADING_READ_ONLY_ALIAS_UNDEFINED_1336=The "%s" property is undefined
+INFO_EDITOR_HEADING_READ_ONLY_ALIAS_1337=The "%s" property is undefined: %s
+INFO_EDITOR_HEADING_READ_ONLY_VALUE_1338=The "%s" property has the following value: %s
+INFO_EDITOR_HEADING_READ_ONLY_VALUES_1339=The "%s" property has the following values:
+INFO_EDITOR_PROMPT_READ_ONLY_1340=This property is read-only and cannot be modified. Would you like to view its help documentation?
+INFO_EDITOR_OPTION_CHANGE_TO_DEFAULT_VALUE_1341=Change it to the default value: %s
+INFO_EDITOR_OPTION_CHANGE_TO_VALUE_1342=Change it to the value: %s
+INFO_EDITOR_OPTION_CHANGE_VALUE_1343=Change the value
+INFO_EDITOR_HEADING_CONFIGURE_PROPERTY_1344=>>>> Configuring the "%s" property
+INFO_EDITOR_PROMPT_READ_FIRST_VALUE_1345=Enter a value for the "%s" property:
+INFO_EDITOR_PROMPT_READ_FIRST_VALUE_OPTIONAL_1346=Enter a value for the "%s" property [continue]:
+INFO_EDITOR_PROMPT_READ_NEXT_VALUE_1347=Enter another value for the "%s" property [continue]:
+SEVERE_ERR_EDITOR_READ_FIRST_DUPLICATE_1348=This property already contains the value "%s". Please enter a different value
+SEVERE_ERR_EDITOR_READ_NEXT_DUPLICATE_1349=This property already contains the value "%s". Please enter a different value, or press RETURN to continue
+INFO_EDITOR_HEADING_CONFIGURE_COMPONENT_1350=>>>> Configure the properties of the %s
+INFO_EDITOR_OPTION_FINISH_CREATE_COMPONENT_1351=finish - create the new %s
+INFO_EDITOR_OPTION_FINISH_MODIFY_COMPONENT_1352=finish - apply any changes to the %s
+INFO_EDITOR_OPTION_FINISH_KEY_1353=f
+INFO_EDITOR_HEADING_CONFIGURE_PROPERTY_CONT_1354=>>>> Configuring the "%s" property (Continued)
+INFO_DSCFG_CREATE_NAME_PROMPT_NAMING_1355=>>>> Specify a name for the %s. This name will be used as the value for the "%s" property which has the following description:
+INFO_DSCFG_CREATE_NAME_PROMPT_NAMING_CONT_1356=Enter a name for the %s that you want to create:
+INFO_DSCFG_HEADING_MAIN_MENU_TITLE_1357=>>>> OpenDS configuration console main menu
+INFO_DSCFG_HEADING_MAIN_MENU_PROMPT_1358=What do you want to configure?
+INFO_DSCFG_HEADING_COMPONENT_MENU_TITLE_1359=>>>> %s management menu 
+INFO_DSCFG_HEADING_COMPONENT_MENU_PROMPT_1360=What would you like to do?
+INFO_DSCFG_OPTION_COMPONENT_MENU_CREATE_1361=Create a new %s
+INFO_DSCFG_OPTION_COMPONENT_MENU_MODIFY_SINGULAR_1362=View and edit the %s
+INFO_DSCFG_OPTION_COMPONENT_MENU_MODIFY_PLURAL_1363=View and edit an existing %s
+INFO_DSCFG_OPTION_COMPONENT_MENU_DELETE_1364=Delete an existing %s
+INFO_DSCFG_OPTION_COMPONENT_MENU_LIST_PLURAL_1365=List existing %s
+INFO_DSCFG_OPTION_COMPONENT_MENU_LIST_SINGULAR_1366=Show the %s
+INFO_DSCFG_PROMPT_HOST_NAME_1367=Directory server hostname or IP address [%s]:
+INFO_DSCFG_PROMPT_PORT_NUMBER_1368=Directory server port number [%d]:
+INFO_DSCFG_PROMPT_BIND_DN_1369=Administrator user bind DN [%s]:
+SEVERE_ERR_DSCFG_BAD_HOST_NAME_1370=The hostname "%s" could not be resolved. Please check you have have the correct address
+SEVERE_ERR_DSCFG_BAD_PORT_NUMBER_1371=Invalid port number "%s". Please enter a valid port number between 1 and 65535
+INFO_DSCFG_GENERIC_TYPE_OPTION_1372=Generic %s
+INFO_DSCFG_HEADING_CONNECTION_PARAMETERS_1373=>>>> Specify OpenDS LDAP connection parameters
+
diff --git a/opends/src/messages/messages/tools.properties b/opends/src/messages/messages/tools.properties
index fafad32..958efa6 100644
--- a/opends/src/messages/messages/tools.properties
+++ b/opends/src/messages/messages/tools.properties
@@ -1601,193 +1601,10 @@
  start with "dn:" to indicate a user DN
 INFO_DESCRIPTION_PRODUCT_VERSION_891=Display Directory Server version \
  information
-SEVERE_ERR_DSCFG_ERROR_CANNOT_READ_LDAP_BIND_PASSWORD_1000=The LDAP bind \
- password could not be read due to the following error: %s
-SEVERE_ERR_DSCFG_ERROR_LDAP_SIMPLE_BIND_FAILED_1001=Unable to authenticate to \
- the server as %s
-SEVERE_ERR_DSCFG_ERROR_LDAP_FAILED_TO_CONNECT_1002=Unable to connect to the \
- server at %s on port %s
-SEVERE_ERR_DSCFG_ERROR_LDAP_SIMPLE_BIND_NOT_SUPPORTED_1003=Unable to \
- authenticate using simple authentication
-INFO_DSCFG_DESCRIPTION_SUBCMD_CREATE_1004=Creates %s
-INFO_DSCFG_DESCRIPTION_SUBCMD_DELETE_1005=Deletes %s
-INFO_DSCFG_DESCRIPTION_SUBCMD_LIST_1006=Lists existing %s
-INFO_DSCFG_DESCRIPTION_SUBCMD_GETPROP_1007=Shows %s properties
-INFO_DSCFG_DESCRIPTION_SUBCMD_SETPROP_1008=Modifies %s properties
-SEVERE_ERR_DSCFG_ERROR_MISSING_SUBCOMMAND_1009=A sub-command must be \
- specified
-INFO_DSCFG_DESCRIPTION_TYPE_1010=The type of %s which should be created. The \
- value for TYPE can be one of: %s
-SEVERE_ERR_DSCFG_ERROR_NO_PASSWORD_1011=No password was specified for \
- administrator "%s"
-SEVERE_ERR_DSCFG_ERROR_PROPERTY_UNRECOGNIZED_1012=The property "%s" is not a \
- recognized property of %s
-SEVERE_ERR_DSCFG_ERROR_PROPERTY_INVALID_VALUE_1013=The value "%s" is not a \
- valid value for the %s property "%s" which has the following syntax: %s
-SEVERE_ERR_DSCFG_ERROR_PROPERTY_READ_ONLY_1014=The %s property "%s" is \
- read-only and cannot be modified
-SEVERE_ERR_DSCFG_ERROR_PROPERTY_MANDATORY_1015=The %s property "%s" is \
- mandatory and must be specified
-SEVERE_ERR_DSCFG_ERROR_PROPERTY_SINGLE_VALUED_1016=It is not possible to \
- specify multiple values for the %s property "%s" as it is single-valued
-INFO_DSCFG_DESCRIPTION_SUBCMD_HELPPROP_1017=Describes managed objects and \
- their properties
-INFO_DSCFG_HEADING_COMPONENT_NAME_1018=Component
-INFO_DSCFG_HEADING_PROPERTY_NAME_1019=Property
-INFO_DSCFG_HEADING_PROPERTY_VALUE_1020=Value(s)
-INFO_DSCFG_HEADING_PROPERTY_SYNTAX_1021=Syntax
-INFO_DSCFG_HEADING_PROPERTY_OPTIONS_1022=Options
-INFO_DSCFG_HEADING_PROPERTY_DEFAULT_1023=Default
-INFO_DSCFG_HEADING_PROPERTY_DESCRIPTION_1024=Description
-INFO_DSCFG_DESCRIPTION_PROPERTY_SYNTAX_HELP_1025=See detailed help
-SEVERE_ERR_DSCFG_ERROR_GET_PARENT_DDE_1026=The parent %s could not be \
- retrieved because its type could not be determined. This is probably due to \
- the %s having an invalid LDAP entry. Check that the %s has the correct object \
- classes
-SEVERE_ERR_DSCFG_ERROR_GET_PARENT_MODE_1027=The parent %s could not be \
- retrieved because of the reasons listed below:
-SEVERE_ERR_DSCFG_ERROR_GET_PARENT_MONFE_1028=The parent %s does not exist
-SEVERE_ERR_DSCFG_ERROR_GET_PARENT_AUTHZ_1029=The parent %s could not be \
- retrieved because you do not have the correct authorization
-SEVERE_ERR_DSCFG_ERROR_GET_PARENT_CE_1030=The parent %s could not be \
- retrieved due to a communications problem: %s
-SEVERE_ERR_DSCFG_ERROR_GET_PARENT_CME_1031=The parent %s could not be \
- retrieved because another client is currently making conflicting \
- configuration changes
-SEVERE_ERR_DSCFG_ERROR_CREATE_MMPE_1032=The %s could not be created because \
- the following mandatory properties must be defined: %s
-SEVERE_ERR_DSCFG_ERROR_CREATE_MOAEE_1033=The %s could not be created because \
- there is already an existing one with the same name
-SEVERE_ERR_DSCFG_ERROR_CREATE_AUTHZ_1034=The %s could not be created because \
- you do not have the correct authorization
-SEVERE_ERR_DSCFG_ERROR_CREATE_CE_1035=The %s could not be created due to a \
- communications problem: %s
-SEVERE_ERR_DSCFG_ERROR_CREATE_CME_1036=The %s could not be created because \
- another client is currently making conflicting configuration changes
-SEVERE_ERR_DSCFG_ERROR_CREATE_ORE_1037=The server prevented the %s from being \
- created because of the following reason: %s
-SEVERE_ERR_DSCFG_ERROR_DELETE_MONFE_1038=The %s could not be deleted because \
- it does not exist
-SEVERE_ERR_DSCFG_ERROR_DELETE_AUTHZ_1039=The %s could not be deleted because \
- you do not have the correct authorization
-SEVERE_ERR_DSCFG_ERROR_DELETE_ORE_1040=The server prevented the %s from being \
- deleted because of the following reason: %s
-SEVERE_ERR_DSCFG_ERROR_DELETE_CE_1041=The %s could not be deleted due to a \
- communications problem: %s
-SEVERE_ERR_DSCFG_ERROR_DELETE_CME_1042=The %s could not be deleted because \
- another client is currently making conflicting configuration changes
-SEVERE_ERR_DSCFG_ERROR_GET_CHILD_DDE_1043=The %s could not be retrieved \
- because its type could not be determined. This is probably due to the %s \
- having an invalid LDAP entry. Check that the %s object classes are correct
-SEVERE_ERR_DSCFG_ERROR_GET_CHILD_MODE_1044=The %s could not be retrieved \
- because of the reasons listed below:
-SEVERE_ERR_DSCFG_ERROR_GET_CHILD_MONFE_1045=The %s does not exist
-SEVERE_ERR_DSCFG_ERROR_GET_CHILD_AUTHZ_1046=The %s could not be accessed \
- because you do not have the correct authorization
-SEVERE_ERR_DSCFG_ERROR_GET_CHILD_CE_1047=The %s could not be accessed due to \
- a communications problem: %s
-SEVERE_ERR_DSCFG_ERROR_GET_CHILD_CME_1048=The %s could not be accessed \
- because another client is currently making conflicting configuration changes
-SEVERE_ERR_DSCFG_ERROR_MODIFY_MONFE_1049=The %s could not be modified because \
- it does not exist
-SEVERE_ERR_DSCFG_ERROR_MODIFY_AUTHZ_1050=The %s could not be modified because \
- you do not have the correct authorization
-SEVERE_ERR_DSCFG_ERROR_MODIFY_CE_1051=The %s could not be modified due to a \
- communications problem: %s
-SEVERE_ERR_DSCFG_ERROR_MODIFY_CME_1052=The %s could not be modified because \
- another client is currently making conflicting configuration changes
-SEVERE_ERR_DSCFG_ERROR_MODIFY_ORE_1053=The server prevented the %s from being \
- modified because of the following reason: %s
-SEVERE_ERR_DSCFG_ERROR_LIST_DDE_1054=The %s could not be retrieved because \
- its type could not be determined. This is probably due to the %s having an \
- invalid LDAP entry. Check that the %s object classes are correct
-SEVERE_ERR_DSCFG_ERROR_LIST_MODE_1055=The %s could not be retrieved because \
- of the reasons listed below:
-SEVERE_ERR_DSCFG_ERROR_LIST_MONFE_1056=The %s does not exist
-SEVERE_ERR_DSCFG_ERROR_LIST_AUTHZ_1057=The %s could not be listed because you \
- do not have the correct authorization
-SEVERE_ERR_DSCFG_ERROR_LIST_CE_1058=The %s could not be listed due to a \
- communications problem: %s
-SEVERE_ERR_DSCFG_ERROR_LIST_CME_1059=The %s could not be listed because \
- another client is currently making conflicting configuration changes
-SEVERE_ERR_DSCFG_ERROR_PROPERTY_UNKNOWN_ERROR_1060=The value(s) of the %s \
- property "%s" could not be determined due to an unknown error: %s
-SEVERE_ERR_DSCFG_ERROR_PROPERTY_DEFAULT_BEHAVIOR_1061=The default value(s) of \
- the %s property "%s" could not be determined due to the following reason: %s
-SEVERE_ERR_DSCFG_ERROR_PROPERTY_INHERITED_DEFAULT_BEHAVIOR_1062=The inherited \
- default value(s) of the %s property "%s" could not be determined
-SEVERE_ERR_DSCFG_ERROR_NO_SEPARATOR_IN_PROPERTY_VALUE_1063=The property \
- argument "%s" does not contain a name/value separator. The argument should \
- have the following syntax: property:value
-SEVERE_ERR_DSCFG_ERROR_NO_NAME_IN_PROPERTY_VALUE_1064=The property argument \
- "%s" does not contain a property name. The argument should have the following \
- syntax: property:value
-SEVERE_ERR_DSCFG_ERROR_NO_VALUE_IN_PROPERTY_VALUE_1065=The property argument \
- "%s" does not contain a property value. The argument should have the \
- following syntax: property:value
-SEVERE_ERR_DSCFG_ERROR_SUB_TYPE_UNRECOGNIZED_1066=The sub-type "%s" is not a \
- recognized type of %s. It should be one of: %s
-SEVERE_ERR_DSCFG_ERROR_TYPE_UNRECOGNIZED_1067="%s" is not a recognized \
- component type
-SEVERE_ERR_DSCFG_ERROR_NO_SEPARATOR_IN_PROPERTY_MOD_1068=The property \
- modification "%s" does not contain a name/value separator. The argument \
- should have the following syntax: property[+|-]:value
-SEVERE_ERR_DSCFG_ERROR_NO_NAME_IN_PROPERTY_MOD_1069=The property modification \
- "%s" does not contain a property name. The argument should have the following \
- syntax: property[+|-]:value
-SEVERE_ERR_DSCFG_ERROR_NO_VALUE_IN_PROPERTY_MOD_1070=The property \
- modification "%s" does not contain a property value. The argument should have \
- the following syntax: property[+|-]:value
-SEVERE_ERR_DSCFG_ERROR_INCOMPATIBLE_PROPERTY_MOD_1071=The property \
- modification "%s" is incompatible with a previous modification to the same \
- property
-SEVERE_ERR_DSCFG_ERROR_WRONG_MANAGED_OBJECT_TYPE_1072=The %s could not be \
- retrieved because it was the wrong type of managed object: %s
-INFO_DSCFG_DESCRIPTION_TYPE_DEFAULT_1073=The type of %s which should be \
- created (Default: %s). The value for TYPE can be one of: %s
-INFO_DSCFG_DESCRIPTION_RECORD_1074=Modifies the display output to show one \
- property value per line
 INFO_DESCRIPTION_QUIET_1075=Use quiet mode
 INFO_DESCRIPTION_SCRIPT_FRIENDLY_1076=Use script-friendly mode
 INFO_DESCRIPTION_NO_PROMPT_1077=Use non-interactive mode.  If some data in \
 the command is missing the user will not be prompted and the tool will fail
-INFO_DSCFG_DESCRIPTION_UNIT_TIME_1078=Display time data using the specified \
- unit. The value for UNIT can be one of ms, s, m, h, d, or w (milliseconds, \
- seconds, minutes, hours, days, or weeks)
-INFO_DSCFG_DESCRIPTION_UNIT_SIZE_1079=Display size data using the specified \
- unit. The value for UNIT can be one of b, kb, mb, gb, or tb (bytes, \
- kilobytes, megabytes, gigabytes, or terabytes)
-INFO_DSCFG_ERROR_TIME_UNIT_UNRECOGNIZED_1080=The time unit "%s" is invalid. \
- The valid time units are ms, s, m, h, d, or w (milliseconds, seconds, \
- minutes, hours, days, or weeks)
-INFO_DSCFG_ERROR_SIZE_UNIT_UNRECOGNIZED_1081=The size unit "%s" is invalid. \
- The valid size units are b, kb, mb, gb, or tb (bytes, kilobytes, megabytes, \
- gigabytes, or terabytes)
-INFO_DSCFG_HEADING_COMPONENT_TYPE_1082=Type
-INFO_DSCFG_DESCRIPTION_SHOW_GROUP_USAGE_1083=Display subcommands relating to \
- %s
-INFO_DSCFG_DESCRIPTION_SHOW_GROUP_USAGE_ALL_1084=Display all subcommands
-INFO_DSCFG_DESCRIPTION_SHOW_GROUP_USAGE_SUMMARY_1085=Display summary usage \
- information
-INFO_DSCFG_DESCRIPTION_NAME_1086=The name of the %s
-INFO_DSCFG_DESCRIPTION_PROP_1087=The name of a property to be displayed
-INFO_DSCFG_DESCRIPTION_PROP_VAL_1088=Assigns a value to a property where PROP \
- is the name of the property and VAL is the single value to be assigned. \
- Specify the same property multiple times in order to assign more than one \
- value to it
-INFO_DSCFG_DESCRIPTION_ADD_PROP_VAL_1089=Adds a single value to a property \
- where PROP is the name of the property and VAL is the single value to be \
- added
-INFO_DSCFG_DESCRIPTION_REMOVE_PROP_VAL_1090=Removes a single value from a \
- property where PROP is the name of the property and VAL is the single value \
- to be removed
-INFO_DSCFG_DESCRIPTION_RESET_PROP_1091=Resets a property back to its default \
- values where PROP is the name of the property to be reset
-INFO_DSCFG_DESCRIPTION_HELP_TYPE_1092=The type of components whose properties \
- should be described. The value for TYPE must be one of the component types \
- associated with the CATEGORY specified using the "--category" option
-SEVERE_ERR_DSCFG_ERROR_BIND_PASSWORD_NONINTERACTIVE_1093=The LDAP bind \
- password was not specified and cannot be read interactively
 INFO_PWPSTATE_TOOL_DESCRIPTION_1094=This utility may be used to retrieve and \
  manipulate the values of password policy state variables
 INFO_PWPSTATE_DESCRIPTION_HOST_1095=Directory server hostname or IP address
@@ -1995,7 +1812,6 @@
 INFO_LDIFIMPORT_DESCRIPTION_COUNT_REJECTS_1195=Count the number of entries \
  rejected by the server and return that value as the exit code (values > 255 \
  will be reduced to 255 due to exit code restrictions)
-INFO_DSCFG_DESCRIPTION_FORCE_1196=Ignore non-existent %s
 INFO_LDIFIMPORT_DESCRIPTION_SKIP_FILE_1197=Write skipped entries to the \
  specified file
 SEVERE_ERR_LDIFIMPORT_CANNOT_OPEN_SKIP_FILE_1198=An error occurred while \
@@ -2003,27 +1819,11 @@
 INFO_VERIFYINDEX_DESCRIPTION_COUNT_ERRORS_1199=Count the number of errors \
  found during the verification and return that value as the exit code (values \
  > 255 will be reduced to 255 due to exit code restrictions)
-SEVERE_ERR_DSCFG_ERROR_UNABLE_TO_RESET_MANDATORY_PROPERTY_1200=The %s \
- property "%s" is mandatory cannot be reset. Use the "%s" option to specify a \
- new value
 INFO_PWPSTATE_LABEL_PASSWORD_HISTORY_1201=Password History
 INFO_DESCRIPTION_PWPSTATE_GET_PASSWORD_HISTORY_1202=Display password history \
  state values for the user
 INFO_DESCRIPTION_PWPSTATE_CLEAR_PASSWORD_HISTORY_1203=Clear password history \
  state values for the user.  This should be used only for testing purposes
-SEVERE_ERR_DSCFG_ERROR_ILLEGAL_NAME_SYNTAX_1204=The name "%s" is not a valid \
- name for the %s which has the following syntax: %s
-SEVERE_ERR_DSCFG_ERROR_ILLEGAL_NAME_EMPTY_1205=Empty names are not permitted \
- for %s
-SEVERE_ERR_DSCFG_ERROR_ILLEGAL_NAME_BLANK_1206=Blank names are not permitted \
- for %s
-SEVERE_ERR_DSCFG_ERROR_ILLEGAL_NAME_UNKNOWN_1207=The name "%s" is not a valid \
- name for the %s
-INFO_DSCFG_DESCRIPTION_NAME_CREATE_1208=The name of the new %s
-INFO_DSCFG_DESCRIPTION_NAME_CREATE_EXT_1209=The name of the new %s which will \
- also be used as the value of the "%s" property: %s
-SEVERE_ERR_DSCFG_ERROR_UNABLE_TO_SET_NAMING_PROPERTY_1210=The property "%s" \
- cannot be set as it is defined implicitly by the name of the %s
 SEVERE_ERR_CONFIGDS_PORT_ALREADY_SPECIFIED_1211=ERROR:  You have specified \
  the value %s for different ports
 SEVERE_ERR_CLI_ERROR_PROPERTY_UNRECOGNIZED_1212=The property "%s" is not a \
@@ -2033,12 +1833,6 @@
 SEVERE_ERR_CLI_ERROR_INVALID_PROPERTY_VALUE_1214=The value "%s" specified for \
  the property "%s" is invalid
 INFO_CLI_HEADING_PROPERTY_DEFAULT_VALUE_1215=Default value
-INFO_DSCFG_DESCRIPTION_ADVANCED_GET_1216=Modifies the display output to show \
- the advanced properties of the %s
-INFO_DSCFG_DESCRIPTION_ADVANCED_SET_1217=Allows the configuration of advanced \
- properties during interactive mode
-INFO_DSCFG_DESCRIPTION_ADVANCED_HELP_1218=Modifies the display output to show \
- the advanced properties of components
 INFO_REVERT_DESCRIPTION_DIRECTORY_1219=Directory where reversion files are \
  stored.  This should be one of the child directories of the 'history' \
  directory that is created when the upgrade tool is run
@@ -2047,50 +1841,6 @@
 INFO_REVERT_DESCRIPTION_INTERACTIVE_1221=Prompt for any required information \
  rather than fail
 INFO_REVERT_DESCRIPTION_SILENT_1222=Perform a quiet reversion
-SEVERE_ERR_DSCFG_ERROR_MISSING_NON_INTERACTIVE_ARG_1223=The argument "--%s" \
- must be specified when this application is used non-interactively
-SEVERE_ERR_DSCFG_ERROR_CANNOT_READ_CONSOLE_INPUT_1224=The response could not \
- be read from the console due to the following error: %s
-INFO_DSCFG_CREATE_TYPE_PROMPT_1225=Select the type of %s that you want to \
- create:
-INFO_DSCFG_CREATE_NAME_PROMPT_1226=Enter a name for the %s that you want to \
- create:
-SEVERE_ERR_DSCFG_ERROR_CREATE_NAME_ALREADY_EXISTS_1227=There is already \
- another %s with the name "%s"
-INFO_DSCFG_DESCRIPTION_CREATE_HELP_HEADING_TYPE_1228=Type
-INFO_DSCFG_DESCRIPTION_CREATE_HELP_HEADING_DESCR_1229=Description
-SEVERE_ERR_DSCFG_ERROR_FINDER_NO_CHILDREN_1230=Unable to continue since there \
- are no %s currently configured on the server
-SEVERE_ERR_DSCFG_ERROR_FINDER_SINGLE_CHILD_REJECTED_1231=Unable to continue \
- because the only available %s was not selected
-INFO_DSCFG_FINDER_PROMPT_SINGLE_1232=There is only one %s: "%s". Are you sure \
- that this is the correct one?
-INFO_DSCFG_FINDER_PROMPT_MANY_1233=Select the %s from the following list:
-INFO_DSCFG_GENERAL_CONFIRM_NO_1234=no
-INFO_DSCFG_GENERAL_CONFIRM_YES_1235=yes
-SEVERE_ERR_DSCFG_ERROR_GENERAL_CONFIRM_1236=Invalid response. Please enter \
- "%s" or "%s"
-INFO_DSCFG_GENERAL_CHOICE_PROMPT_NOHELP_1237=Enter choice [1 - %d]:
-INFO_DSCFG_GENERAL_CHOICE_PROMPT_HELP_1238=Enter choice [1 - %d,  ? - help]:
-SEVERE_ERR_DSCFG_ERROR_GENERAL_CHOICE_1239=Invalid response. Please enter a \
- value between 1 and %d
-INFO_DSCFG_VALUE_READER_MENU_RESET_1240=reset the value back to its default
-INFO_DSCFG_VALUE_READER_MENU_SET_1241=modify the value
-INFO_DSCFG_VALUE_READER_MENU_ADD_1242=add a value
-INFO_DSCFG_VALUE_READER_MENU_REMOVE_1243=remove a value
-INFO_DSCFG_VALUE_READER_MENU_CONTINUE_1244=continue
-INFO_DSCFG_VALUE_READER_PROMPT_REMOVE_1245=Select the value to be removed \
- from the "%s" property:
-INFO_DSCFG_VALUE_READER_PROMPT_SELECT_VALUE_1246=Select a value for the "%s" \
- property:
-INFO_DSCFG_VALUE_READER_PROMPT_ENTER_VALUE_1247=Enter a value for the "%s" \
- property:
-INFO_DSCFG_VALUE_READER_MENU_TITLE_1248=Select a property to be edited, or \
- enter "%d" to continue:
-INFO_DSCFG_VALUE_READER_PROMPT_MANDATORY_1249=The property "%s" is mandatory \
- and must have a value specified
-INFO_DSCFG_VALUE_READER_PROMPT_MODIFY_MENU_1250=Do you want to modify the \
- "%s" property?
 INFO_LDIFIMPORT_DESCRIPTION_CLEAR_BACKEND_1251=Remove all entries for all \
  base DNs in the backend before importing
 SEVERE_ERR_LDIFIMPORT_MISSING_BACKEND_ARGUMENT_1252=Neither the %s or the %s \
@@ -2099,56 +1849,6 @@
 SEVERE_ERR_LDIFIMPORT_MISSING_CLEAR_BACKEND_1253=Importing to a backend \
  without the append argument will remove all entries for all base DNs (%s) in \
  the backend. The %s argument must be given to continue with import
-INFO_DSCFG_HELP_FIELD_ENUM_1254=one of the following values:
-INFO_DSCFG_HELP_FIELD_UNDEFINED_1255=undefined
-INFO_DSCFG_HELP_FIELD_INHERITED_ABS_1256=inherits from the property "%s" in \
- the %s
-INFO_DSCFG_HELP_FIELD_INHERITED_PARENT_1257=inherits from the property "%s" \
- in the parent %s
-INFO_DSCFG_HELP_FIELD_INHERITED_THIS_1258=inherits from the property "%s" in \
- this %s
-INFO_DSCFG_HELP_FIELD_SERVER_RESTART_1259=The server must be restarted in \
- order for changes to this property to take effect
-INFO_DSCFG_HELP_FIELD_COMPONENT_RESTART_1260=The %s must be restarted in \
- order for changes to this property to take effect
-INFO_DSCFG_HELP_FIELD_READ_ONLY_1261=read-only - this property can only be \
- specified when the %s is created
-INFO_DSCFG_HELP_FIELD_MONITORING_1262=monitoring - this property is \
- automatically generated by the server
-INFO_DSCFG_HELP_HEADING_PROPERTY_1263=Property: %s
-INFO_DSCFG_HELP_HEADING_COMPONENT_1264=Component name: %s
-INFO_DSCFG_HELP_HEADING_DEFAULT_1265=Default behavior
-INFO_DSCFG_HELP_HEADING_MANDATORY_1266=Mandatory
-INFO_DSCFG_HELP_HEADING_ADVANCED_1267=Advanced
-INFO_DSCFG_HELP_HEADING_MULTI_VALUED_1268=Multi-valued
-INFO_DSCFG_HELP_HEADING_READ_ONLY_1269=Read-only
-INFO_DSCFG_HELP_HEADING_SYNTAX_1270=Syntax
-INFO_DSCFG_HELP_DESCRIPTION_OPTION_1271=Option Types:
-INFO_DSCFG_HELP_DESCRIPTION_READ_1272=Property value(s) are readable
-INFO_DSCFG_HELP_DESCRIPTION_WRITE_1273=Property value(s) are writable
-INFO_DSCFG_HELP_DESCRIPTION_MANDATORY_1274=The property is mandatory
-INFO_DSCFG_HELP_DESCRIPTION_SINGLE_VALUED_1275=The property is single-valued
-INFO_DSCFG_HELP_DESCRIPTION_ADMIN_ACTION_1276=Administrative action is \
- required for changes to take effect
-INFO_DSCFG_CONFIRM_CREATE_1277=Are you sure that you want to create the %s?
-INFO_DSCFG_CONFIRM_DELETE_1278=Are you sure that you want to delete the %s?
-INFO_DSCFG_CONFIRM_MODIFY_1279=Are you sure that you want to modify the %s?
-INFO_DSCFG_CONFIRM_CREATE_SUCCESS_1280=The %s was created successfully
-INFO_DSCFG_CONFIRM_DELETE_SUCCESS_1281=The %s was deleted successfully
-INFO_DSCFG_CONFIRM_MODIFY_SUCCESS_1282=The %s was modified successfully
-INFO_DSCFG_CONFIRM_CREATE_FAIL_1283=The %s was not created
-INFO_DSCFG_CONFIRM_DELETE_FAIL_1284=The %s was not deleted
-INFO_DSCFG_CONFIRM_MODIFY_FAIL_1285=The %s was not modified
-INFO_DSCFG_DESCRIPTION_HELP_CATEGORY_1286=The category of components whose \
- properties should be described
-SEVERE_ERR_DSCFG_ERROR_CATEGORY_UNRECOGNIZED_1287="%s" is not a recognized \
- component category
-SEVERE_ERR_DSCFG_ERROR_CATEGORY_TYPE_UNRECOGNIZED_1288="%s" is not a \
- recognized component type in category "%s"
-SEVERE_ERR_DSCFG_ERROR_PROPERTY_UNRECOGNIZED_NO_DEFN_1289=The property "%s" \
- is not a recognized property
-INFO_DSCFG_DESCRIPTION_HELP_INHERITED_1290=Modifies the display output to \
- show the inherited properties of components
 MILD_ERR_MAKELDIF_TAG_LIST_NO_ARGUMENTS_1291=The list tag on line %d of the \
  template file does not contain any arguments to specify the list values.  At \
  least one list value must be provided
@@ -2298,3 +1998,4 @@
   min/max data size %s as a integer: %s
 SEVERE_ERR_CONFIGDS_CANNOT_ENABLE_ADS_TRUST_STORE_1373=An error occurred while \
  attempting to enable the ADS trust store: %s
+SEVERE_ERR_DBTEST_MISSING_SUBCOMMAND_1374=A sub-command must be specified
diff --git a/opends/src/messages/messages/utility.properties b/opends/src/messages/messages/utility.properties
index 780cd34..10f1636 100644
--- a/opends/src/messages/messages/utility.properties
+++ b/opends/src/messages/messages/utility.properties
@@ -472,4 +472,30 @@
 SEVERE_ERR_BASE64_CANNOT_WRITE_RAW_DATA_199=An error occurred while \
  attempting to write the decoded data:  %s
 SEVERE_ERR_BASE64_UNKNOWN_SUBCOMMAND_200=Unknown subcommand %s
-
+INFO_GENERAL_NO_201=no
+INFO_GENERAL_YES_202=yes
+SEVERE_ERR_CONSOLE_APP_CONFIRM_203=Invalid response. Please enter \
+ "%s" or "%s"
+INFO_MENU_OPTION_HELP_204=help
+INFO_MENU_OPTION_HELP_KEY_205=?
+INFO_MENU_OPTION_CANCEL_206=cancel
+INFO_MENU_OPTION_CANCEL_KEY_207=c
+INFO_MENU_OPTION_QUIT_208=quit
+INFO_MENU_OPTION_QUIT_KEY_209=q
+INFO_MENU_NUMERIC_OPTION_210=%d)
+INFO_MENU_CHAR_OPTION_211=%c)
+SEVERE_ERR_MENU_BAD_CHOICE_MULTI_212=Invalid response. Please enter one or \
+more valid menu options
+SEVERE_ERR_MENU_BAD_CHOICE_SINGLE_213=Invalid response. Please enter a valid \
+menu option
+SEVERE_ERR_MENU_BAD_CHOICE_MULTI_DUPE_214=The option "%s" was specified \
+more than once. Please enter one or more valid menu options
+INFO_MENU_PROMPT_SINGLE_215=Enter choice:
+INFO_MENU_PROMPT_SINGLE_DEFAULT_216=Enter choice [%s]:
+INFO_MENU_PROMPT_MULTI_217=Enter one or more choices separated by commas:
+INFO_MENU_PROMPT_MULTI_DEFAULT_218=Enter one or more choices separated by commas [%s]:
+INFO_MENU_PROMPT_RETURN_TO_CONTINUE_219=Press RETURN to continue
+INFO_MENU_PROMPT_CONFIRM_220=%s (%s / %s) [%s]:
+SEVERE_ERR_CONSOLE_INPUT_ERROR_221=The response could not be read from the console due to the following error: %s
+INFO_MENU_OPTION_BACK_222=back
+INFO_MENU_OPTION_BACK_KEY_223=b
diff --git a/opends/src/messages/src/org/opends/messages/Category.java b/opends/src/messages/src/org/opends/messages/Category.java
index 99685b9..c5046e4 100644
--- a/opends/src/messages/src/org/opends/messages/Category.java
+++ b/opends/src/messages/src/org/opends/messages/Category.java
@@ -140,6 +140,12 @@
   ADMIN_TOOL(0x01100000),
 
   /**
+   * The category used for messages associated with the dsconfig
+   * administration tool.
+   */
+  DSCONFIG(0x01200000),
+
+  /**
    * The category that will be used for messages associated with
    * third-party (including user-defined) modules.
    */
diff --git a/opends/src/server/org/opends/server/admin/PropertyDefinitionUsageBuilder.java b/opends/src/server/org/opends/server/admin/PropertyDefinitionUsageBuilder.java
index f953d1e..466f5a3 100644
--- a/opends/src/server/org/opends/server/admin/PropertyDefinitionUsageBuilder.java
+++ b/opends/src/server/org/opends/server/admin/PropertyDefinitionUsageBuilder.java
@@ -312,7 +312,7 @@
      * {@inheritDoc}
      */
     @Override
-    public Message visitUnknown(PropertyDefinition<?> d, Void p)
+    public <T> Message visitUnknown(PropertyDefinition<T> d, Void p)
         throws UnknownPropertyDefinitionException {
       return Message.raw("?");
     }
diff --git a/opends/src/server/org/opends/server/admin/PropertyDefinitionVisitor.java b/opends/src/server/org/opends/server/admin/PropertyDefinitionVisitor.java
index b445037..63e7c0b 100644
--- a/opends/src/server/org/opends/server/admin/PropertyDefinitionVisitor.java
+++ b/opends/src/server/org/opends/server/admin/PropertyDefinitionVisitor.java
@@ -241,6 +241,8 @@
    * {@link UnknownPropertyDefinitionException}. Sub-classes can
    * override this method with their own default behavior.
    *
+   * @param <T>
+   *          The type of the underlying property.
    * @param d
    *          The property definition to visit.
    * @param p
@@ -250,7 +252,7 @@
    *           Visitor implementations may optionally throw this
    *           exception.
    */
-  public R visitUnknown(PropertyDefinition<?> d, P p)
+  public <T> R visitUnknown(PropertyDefinition<T> d, P p)
       throws UnknownPropertyDefinitionException {
     throw new UnknownPropertyDefinitionException(d, p);
   }
diff --git a/opends/src/server/org/opends/server/admin/client/ManagedObject.java b/opends/src/server/org/opends/server/admin/client/ManagedObject.java
index bd8ed4a..d34b3e0 100644
--- a/opends/src/server/org/opends/server/admin/client/ManagedObject.java
+++ b/opends/src/server/org/opends/server/admin/client/ManagedObject.java
@@ -443,6 +443,25 @@
 
 
   /**
+   * Determines whether or not the specified property is set. If the
+   * property is unset, then any default behavior associated with the
+   * property applies.
+   *
+   * @param pd
+   *          The property definition.
+   * @return Returns <code>true</code> if the property has been set,
+   *         or <code>false</code> if it is unset and any default
+   *         behavior associated with the property applies.
+   * @throws IllegalArgumentException
+   *           If the property definition is not associated with this
+   *           managed object's definition.
+   */
+  boolean isPropertyPresent(PropertyDefinition<?> pd)
+      throws IllegalArgumentException;
+
+
+
+  /**
    * Determines whether or not the optional managed object associated
    * with the specified optional relations exists.
    *
diff --git a/opends/src/server/org/opends/server/admin/client/cli/DsFrameworkCliGlobalAdmin.java b/opends/src/server/org/opends/server/admin/client/cli/DsFrameworkCliGlobalAdmin.java
index 9f62f5f..adcef30 100644
--- a/opends/src/server/org/opends/server/admin/client/cli/DsFrameworkCliGlobalAdmin.java
+++ b/opends/src/server/org/opends/server/admin/client/cli/DsFrameworkCliGlobalAdmin.java
@@ -29,6 +29,7 @@
 import org.opends.messages.MessageBuilder;
 
 import static org.opends.messages.AdminMessages.*;
+import static org.opends.messages.DSConfigMessages.*;
 import static org.opends.messages.ToolMessages.*;
 import static org.opends.server.tools.ToolConstants.*;
 
diff --git a/opends/src/server/org/opends/server/admin/client/cli/DsFrameworkCliMain.java b/opends/src/server/org/opends/server/admin/client/cli/DsFrameworkCliMain.java
index b314e6e..68bb2bb 100644
--- a/opends/src/server/org/opends/server/admin/client/cli/DsFrameworkCliMain.java
+++ b/opends/src/server/org/opends/server/admin/client/cli/DsFrameworkCliMain.java
@@ -39,6 +39,7 @@
 
 import static org.opends.server.admin.client.cli.DsFrameworkCliReturnCode.*;
 import static org.opends.messages.AdminMessages.*;
+import static org.opends.messages.DSConfigMessages.*;
 import static org.opends.messages.ToolMessages.*;
 import org.opends.messages.MessageBuilder;
 import static org.opends.server.util.ServerConstants.*;
diff --git a/opends/src/server/org/opends/server/admin/client/cli/DsFrameworkCliParser.java b/opends/src/server/org/opends/server/admin/client/cli/DsFrameworkCliParser.java
index ed34041..80d1192 100644
--- a/opends/src/server/org/opends/server/admin/client/cli/DsFrameworkCliParser.java
+++ b/opends/src/server/org/opends/server/admin/client/cli/DsFrameworkCliParser.java
@@ -28,7 +28,7 @@
 
 import static org.opends.server.loggers.debug.DebugLogger.*;
 import static org.opends.messages.AdminMessages.*;
-import static org.opends.messages.ToolMessages.*;
+import static org.opends.messages.DSConfigMessages.*;
 import org.opends.messages.Message;
 import static org.opends.server.tools.ToolConstants.*;
 import static org.opends.server.util.ServerConstants.MAX_LINE_WIDTH;
diff --git a/opends/src/server/org/opends/server/admin/client/cli/DsFrameworkCliServer.java b/opends/src/server/org/opends/server/admin/client/cli/DsFrameworkCliServer.java
index eba4c43..3f9ee00 100644
--- a/opends/src/server/org/opends/server/admin/client/cli/DsFrameworkCliServer.java
+++ b/opends/src/server/org/opends/server/admin/client/cli/DsFrameworkCliServer.java
@@ -29,6 +29,7 @@
 import org.opends.messages.MessageBuilder;
 
 import static org.opends.messages.AdminMessages.*;
+import static org.opends.messages.DSConfigMessages.*;
 import static org.opends.messages.ToolMessages.*;
 import static org.opends.server.tools.ToolConstants.*;
 
diff --git a/opends/src/server/org/opends/server/admin/client/ldap/LDAPManagedObject.java b/opends/src/server/org/opends/server/admin/client/ldap/LDAPManagedObject.java
index 39c7419..16120aa 100644
--- a/opends/src/server/org/opends/server/admin/client/ldap/LDAPManagedObject.java
+++ b/opends/src/server/org/opends/server/admin/client/ldap/LDAPManagedObject.java
@@ -675,6 +675,17 @@
   /**
    * {@inheritDoc}
    */
+  public boolean isPropertyPresent(PropertyDefinition<?> pd)
+      throws IllegalArgumentException {
+    Property<?> p = properties.getProperty(pd);
+    return !p.isEmpty();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
   public <C extends ConfigurationClient, S extends Configuration>
   boolean hasChild(OptionalRelationDefinition<C, S> r)
       throws IllegalArgumentException, ConcurrentModificationException,
diff --git a/opends/src/server/org/opends/server/tools/DBTest.java b/opends/src/server/org/opends/server/tools/DBTest.java
index d269c81..a7a0849 100644
--- a/opends/src/server/org/opends/server/tools/DBTest.java
+++ b/opends/src/server/org/opends/server/tools/DBTest.java
@@ -434,7 +434,7 @@
     // Make sure that we have a sub-command.
     if (parser.getSubCommand() == null)
     {
-      Message message = ERR_DSCFG_ERROR_MISSING_SUBCOMMAND.get();
+      Message message = ERR_DBTEST_MISSING_SUBCOMMAND.get();
       displayMessageAndUsageReference(message);
       return 1;
     }
diff --git a/opends/src/server/org/opends/server/tools/dsconfig/ArgumentExceptionFactory.java b/opends/src/server/org/opends/server/tools/dsconfig/ArgumentExceptionFactory.java
index 5a9c9f0..85cc99c 100644
--- a/opends/src/server/org/opends/server/tools/dsconfig/ArgumentExceptionFactory.java
+++ b/opends/src/server/org/opends/server/tools/dsconfig/ArgumentExceptionFactory.java
@@ -25,12 +25,12 @@
  *      Portions Copyright 2007 Sun Microsystems, Inc.
  */
 package org.opends.server.tools.dsconfig;
+
+
+
+import static org.opends.messages.DSConfigMessages.*;
+
 import org.opends.messages.Message;
-
-
-
-import static org.opends.messages.ToolMessages.*;
-
 import org.opends.server.admin.AbstractManagedObjectDefinition;
 import org.opends.server.admin.DefaultBehaviorException;
 import org.opends.server.admin.IllegalPropertyValueException;
@@ -47,6 +47,7 @@
 import org.opends.server.admin.client.MissingMandatoryPropertiesException;
 import org.opends.server.util.args.Argument;
 import org.opends.server.util.args.ArgumentException;
+import org.opends.server.util.cli.CLIException;
 
 
 
@@ -57,16 +58,16 @@
 public final class ArgumentExceptionFactory {
 
   /**
-   * Creates an argument exception from an illegal managed object name
+   * Creates a CLI exception from an illegal managed object name
    * exception.
    *
    * @param e
    *          The illegal managed object name exception.
    * @param d
    *          The managed object definition.
-   * @return Returns an argument exception.
+   * @return Returns a CLI exception.
    */
-  public static ArgumentException adaptIllegalManagedObjectNameException(
+  public static CLIException adaptIllegalManagedObjectNameException(
       IllegalManagedObjectNameException e,
       AbstractManagedObjectDefinition<?, ?> d) {
     String illegalName = e.getIllegalName();
@@ -75,11 +76,11 @@
     if (illegalName.length() == 0) {
       Message message =
           ERR_DSCFG_ERROR_ILLEGAL_NAME_EMPTY.get(d.getUserFriendlyPluralName());
-      return new ArgumentException(message);
+      return new CLIException(message);
     } else if (illegalName.trim().length() == 0) {
       Message message =
           ERR_DSCFG_ERROR_ILLEGAL_NAME_BLANK.get(d.getUserFriendlyPluralName());
-      return new ArgumentException(message);
+      return new CLIException(message);
     } else if (pd != null) {
       try {
         pd.decodeValue(illegalName);
@@ -90,13 +91,13 @@
 
         Message message = ERR_DSCFG_ERROR_ILLEGAL_NAME_SYNTAX.get(
             illegalName, d.getUserFriendlyName(), syntax);
-        return new ArgumentException(message);
+        return new CLIException(message);
       }
     }
 
     Message message = ERR_DSCFG_ERROR_ILLEGAL_NAME_UNKNOWN.get(
         illegalName, d.getUserFriendlyName());
-    return new ArgumentException(message);
+    return new CLIException(message);
   }
 
 
@@ -320,16 +321,18 @@
 
 
   /**
-   * Creates an argument exception which should be used when the bind
-   * password could not be read from the standard input.
+   * 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 bind password could not be read.
+   *          The reason why the connection parameters could not be
+   *          read.
    * @return Returns an argument exception.
    */
-  public static ArgumentException unableToReadBindPassword(Exception cause) {
-    Message message =
-        ERR_DSCFG_ERROR_CANNOT_READ_LDAP_BIND_PASSWORD.get(cause.getMessage());
+  public static ArgumentException unableToReadConnectionParameters(
+      Exception cause) {
+    Message message = ERR_DSCFG_ERROR_CANNOT_READ_CONNECTION_PARAMETERS
+        .get(cause.getMessage());
     return new ArgumentException(message, cause);
   }
 
@@ -350,22 +353,6 @@
 
 
   /**
-   * Creates an argument exception which should be used when
-   * interaction with the console fails due to an IO exception.
-   *
-   * @param cause
-   *          The reason why console input failed.
-   * @return Returns an argument exception.
-   */
-  public static ArgumentException unableToReadConsoleInput(Exception cause) {
-    Message message =
-        ERR_DSCFG_ERROR_CANNOT_READ_CONSOLE_INPUT.get(cause.getMessage());
-    return new ArgumentException(message, cause);
-  }
-
-
-
-  /**
    * 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.
@@ -513,7 +500,7 @@
 
 
   /**
-   * Creates an argument exception which should be used when a managed
+   * 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.
    *
@@ -521,13 +508,13 @@
    *          The relation definition.
    * @param d
    *          The definition of the managed object that was retrieved.
-   * @return Returns an argument exception.
+   * @return Returns a CLI exception.
    */
-  public static ArgumentException wrongManagedObjectType(
-      RelationDefinition<?, ?> r, ManagedObjectDefinition<?, ?> d) {
-    Message msg = ERR_DSCFG_ERROR_TYPE_UNRECOGNIZED.get(
-        d.getUserFriendlyName());
-    return new ArgumentException(msg);
+  public static CLIException wrongManagedObjectType(RelationDefinition<?, ?> r,
+      ManagedObjectDefinition<?, ?> d) {
+    Message msg = ERR_DSCFG_ERROR_TYPE_UNRECOGNIZED
+        .get(d.getUserFriendlyName());
+    return new CLIException(msg);
   }
 
 
diff --git a/opends/src/server/org/opends/server/tools/dsconfig/ConsoleApplication.java b/opends/src/server/org/opends/server/tools/dsconfig/ConsoleApplication.java
deleted file mode 100644
index e197ce2..0000000
--- a/opends/src/server/org/opends/server/tools/dsconfig/ConsoleApplication.java
+++ /dev/null
@@ -1,497 +0,0 @@
-/*
- * CDDL HEADER START
- *
- * The contents of this file are subject to the terms of the
- * Common Development and Distribution License, Version 1.0 only
- * (the "License").  You may not use this file except in compliance
- * with the License.
- *
- * You can obtain a copy of the license at
- * trunk/opends/resource/legal-notices/OpenDS.LICENSE
- * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
- * See the License for the specific language governing permissions
- * and limitations under the License.
- *
- * When distributing Covered Code, include this CDDL HEADER in each
- * file and include the License file at
- * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
- * add the following below this CDDL HEADER, with the fields enclosed
- * by brackets "[]" replaced with your own identifying information:
- *      Portions Copyright [yyyy] [name of copyright owner]
- *
- * CDDL HEADER END
- *
- *
- *      Portions Copyright 2007 Sun Microsystems, Inc.
- */
-package org.opends.server.tools.dsconfig;
-import org.opends.messages.Message;
-import org.opends.messages.MessageBuilder;
-
-
-import static org.opends.messages.ToolMessages.*;
-import static org.opends.server.util.ServerConstants.*;
-import static org.opends.server.util.StaticUtils.*;
-
-import java.io.BufferedReader;
-import java.io.EOFException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.io.PrintStream;
-import java.io.Reader;
-import java.util.List;
-
-import org.opends.server.admin.client.ManagementContext;
-import org.opends.server.tools.ClientException;
-import org.opends.server.types.NullOutputStream;
-import org.opends.server.util.PasswordReader;
-import org.opends.server.util.Validator;
-import org.opends.server.util.args.ArgumentException;
-import org.opends.server.util.table.TableBuilder;
-import org.opends.server.util.table.TextTablePrinter;
-
-
-
-/**
- * This class provides an abstract base class which can be used as the
- * basis of a console-based application.
- */
-public abstract class ConsoleApplication {
-
-  // The error stream which this application should use.
-  private final PrintStream err;
-
-  // The input stream reader which this application should use.
-  private final BufferedReader in;
-
-  // The output stream which this application should use.
-  private final PrintStream out;
-
-
-
-  /**
-   * Creates a new console application instance.
-   *
-   * @param in
-   *          The application input stream.
-   * @param out
-   *          The application output stream.
-   * @param err
-   *          The application error stream.
-   */
-  protected ConsoleApplication(InputStream in, OutputStream out,
-      OutputStream err) {
-    if (in != null) {
-      this.in = new BufferedReader(new InputStreamReader(in));
-    } else {
-      this.in = new BufferedReader(new Reader() {
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public void close() throws IOException {
-          // Do nothing.
-        }
-
-
-
-        /**
-         * {@inheritDoc}
-         */
-        @Override
-        public int read(char[] cbuf, int off, int len) throws IOException {
-          return -1;
-        }
-
-      });
-    }
-
-    if (out != null) {
-      this.out = new PrintStream(out);
-    } else {
-      this.out = NullOutputStream.printStream();
-    }
-
-    if (err != null) {
-      this.err = new PrintStream(err);
-    } else {
-      this.err = NullOutputStream.printStream();
-    }
-  }
-
-
-
-  /**
-   * Interactively confirms whether a user wishes to perform an
-   * action. If the application is non-interactive, then the action is
-   * granted by default.
-   *
-   * @param prompt
-   *          The prompt describing the action.
-   * @return Returns <code>true</code> if the user wishes the action
-   *         to be performed, or <code>false</code> if they refused,
-   *         or if an exception occurred.
-   * @throws ArgumentException
-   *           If the user's response could not be read from the
-   *           console for some reason.
-   */
-  public final boolean confirmAction(Message prompt) throws ArgumentException {
-    if (!isInteractive()) {
-      return true;
-    }
-
-    final Message yes = INFO_DSCFG_GENERAL_CONFIRM_YES.get();
-    final Message no = INFO_DSCFG_GENERAL_CONFIRM_NO.get();
-    final Message errMsg = ERR_DSCFG_ERROR_GENERAL_CONFIRM.get(yes, no);
-    MessageBuilder mb = new MessageBuilder();
-    mb.append(prompt);
-    mb.append(String.format(" (%s / %s): ", yes, no));
-    prompt = mb.toMessage();
-
-    ValidationCallback<Boolean> validator = new ValidationCallback<Boolean>() {
-
-      public Boolean validate(ConsoleApplication app, String input) {
-        String ninput = input.toLowerCase().trim();
-        if (ninput.length() == 0) {
-          // Empty input.
-          app.println();
-          app.printMessage(errMsg);
-        } else if (no.toString().startsWith(ninput)) {
-          return false;
-        } else if (yes.toString().startsWith(ninput)) {
-          return true;
-        } else {
-          // Try again...
-          app.println();
-          app.printMessage(errMsg);
-        }
-
-        return null;
-      }
-    };
-
-    try {
-      return readValidatedInput(prompt, validator);
-    } catch (ClientException e) {
-      // Should never happen.
-      throw new RuntimeException(e);
-    }
-  }
-
-
-
-  /**
-   * Displays a message to the error stream.
-   *
-   * @param msg
-   *          The message.
-   */
-  public final void printMessage(Message msg) {
-    err.println(wrapText(msg.toString(), MAX_LINE_WIDTH));
-  }
-
-
-
-  /**
-   * Displays a blank line to the error stream.
-   */
-  public final void println() {
-    err.println();
-  }
-
-
-
-  /**
-   * Displays a message to the error stream if verbose mode is
-   * enabled.
-   *
-   * @param msg
-   *          The verbose message.
-   */
-  public final void printVerboseMessage(Message msg) {
-    if (isVerbose() || isInteractive()) {
-      err.println(wrapText(msg, MAX_LINE_WIDTH));
-    }
-  }
-
-
-
-  /**
-   * Gets the application error stream.
-   *
-   * @return Returns the application error stream.
-   */
-  public final PrintStream getErrorStream() {
-    return err;
-  }
-
-
-
-  /**
-   * Gets the application input stream.
-   *
-   * @return Returns the application input stream.
-   */
-  public final BufferedReader getInputStream() {
-    return in;
-  }
-
-
-
-  /**
-   * Gets the management context which sub-commands should use in
-   * order to manage the directory server.
-   *
-   * @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 abstract ManagementContext getManagementContext()
-      throws ArgumentException, ClientException;
-
-
-
-  /**
-   * Gets the application output stream.
-   *
-   * @return Returns the application output stream.
-   */
-  public final PrintStream getOutputStream() {
-    return out;
-  }
-
-
-
-  /**
-   * Indicates whether or not the user has requested interactive
-   * behavior.
-   *
-   * @return Returns <code>true</code> if the user has requested
-   *         interactive behavior.
-   */
-  public abstract boolean isInteractive();
-
-
-
-  /**
-   * Indicates whether or not the user has requested quiet output.
-   *
-   * @return Returns <code>true</code> if the user has requested
-   *         quiet output.
-   */
-  public abstract boolean isQuiet();
-
-
-
-  /**
-   * Indicates whether or not the user has requested script-friendly
-   * output.
-   *
-   * @return Returns <code>true</code> if the user has requested
-   *         script-friendly output.
-   */
-  public abstract boolean isScriptFriendly();
-
-
-
-  /**
-   * Indicates whether or not the user has requested verbose output.
-   *
-   * @return Returns <code>true</code> if the user has requested
-   *         verbose output.
-   */
-  public abstract boolean isVerbose();
-
-
-
-  /**
-   * Interactively prompts the user to select from a choice of
-   * options.
-   *
-   * @param <T>
-   *          The type of the values represented by each choice.
-   * @param prompt
-   *          The prompt which should appear before the list of
-   *          choices.
-   * @param descriptions
-   *          The descriptions of each choice.
-   * @param values
-   *          The choices.
-   * @param helpCallback
-   *          An optional help call-back which can be used to display
-   *          additional help.
-   * @return Returns the selected value.
-   * @throws ArgumentException
-   *           If the user input could not be retrieved for some
-   *           reason.
-   */
-  public final <T> T readChoice(
-          final Message prompt, List<Message> descriptions,
-          List<T> values, final HelpCallback helpCallback)
-      throws ArgumentException {
-    Validator.ensureTrue(descriptions.size() == values.size());
-
-    // Output main prompt.
-    println();
-    printMessage(prompt);
-    println();
-
-    // Build the table of choices.
-    final TableBuilder builder = new TableBuilder();
-    final int size = descriptions.size();
-    for (int i = 0; i < size; i++) {
-      builder.startRow();
-      builder.appendCell("[" + (i + 1) + "]");
-      builder.appendCell(descriptions.get(i));
-    }
-
-    // Display the table of choices.
-    final TextTablePrinter printer = new TextTablePrinter(err);
-    printer.setDisplayHeadings(false);
-    printer.setColumnWidth(1, 0);
-    builder.print(printer);
-
-    // Get the user input.
-    Message promptMsg;
-    if (helpCallback != null) {
-      promptMsg = INFO_DSCFG_GENERAL_CHOICE_PROMPT_HELP.get(size);
-    } else {
-      promptMsg = INFO_DSCFG_GENERAL_CHOICE_PROMPT_NOHELP.get(size);
-    }
-
-    ValidationCallback<Integer> validator = new ValidationCallback<Integer>() {
-
-      public Integer validate(ConsoleApplication app, String input) {
-        String ninput = input.trim();
-
-        if (ninput.equals("?") && helpCallback != null) {
-          app.println();
-          helpCallback.display(app);
-          app.println();
-
-          // Output main prompt.
-          printMessage(prompt);
-          println();
-          builder.print(printer);
-
-          return null;
-        } else {
-          try {
-            int i = Integer.parseInt(ninput);
-            if (i < 1 || i > size) {
-              throw new NumberFormatException();
-            }
-            return i;
-          } catch (NumberFormatException e) {
-            app.println();
-            Message errMsg = ERR_DSCFG_ERROR_GENERAL_CHOICE.get(size);
-            app.printMessage(errMsg);
-            return null;
-          }
-        }
-      }
-    };
-
-    // Get the choice.
-    int choice;
-    try {
-      choice = readValidatedInput(promptMsg, validator);
-    } catch (ClientException e) {
-      // Should never happen.
-      throw new RuntimeException(e);
-    }
-    return values.get(choice - 1);
-  }
-
-
-
-  /**
-   * Interactively retrieves a line of input from the console.
-   *
-   * @param prompt
-   *          The prompt.
-   * @return Returns the line of input, or <code>null</code> if the
-   *         end of input has been reached.
-   * @throws ArgumentException
-   *           If the line of input could not be retrieved for some
-   *           reason.
-   */
-  public final String readLineOfInput(Message prompt) throws ArgumentException {
-    err.println();
-    err.print(wrapText(prompt.toString().trim() + " ", MAX_LINE_WIDTH));
-    try {
-      return in.readLine();
-    } catch (IOException e) {
-      throw ArgumentExceptionFactory.unableToReadConsoleInput(e);
-    }
-  }
-
-
-
-  /**
-   * Interactively retrieves a password from the console.
-   *
-   * @param prompt
-   *          The password prompt.
-   * @return Returns the password.
-   * @throws ArgumentException
-   *           If the password could not be retrieved for some reason.
-   */
-  public final String readPassword(Message prompt) throws ArgumentException {
-    err.print(wrapText(prompt + " ", MAX_LINE_WIDTH));
-    char[] pwChars;
-    try {
-      pwChars = PasswordReader.readPassword();
-    } catch (Exception e) {
-      throw ArgumentExceptionFactory.unableToReadConsoleInput(e);
-    }
-    return new String(pwChars);
-  }
-
-
-
-  /**
-   * Interactively prompts for user input and continues until valid
-   * input is provided.
-   *
-   * @param <T>
-   *          The type of decoded user input.
-   * @param prompt
-   *          The interactive prompt which should be displayed on each
-   *          input attempt.
-   * @param validator
-   *          An input validator responsible for validating and
-   *          decoding the user's response.
-   * @return Returns the decoded user's response.
-   * @throws ArgumentException
-   *           If the line of input could not be retrieved for some
-   *           reason.
-   * @throws ClientException
-   *           If an unexpected error occurred which prevented
-   *           validation.
-   */
-  public final <T> T readValidatedInput(Message prompt,
-      ValidationCallback<T> validator) throws ArgumentException,
-      ClientException {
-    while (true) {
-      String response = readLineOfInput(prompt);
-
-      if (response == null) {
-        throw ArgumentExceptionFactory
-            .unableToReadConsoleInput(new EOFException("End of input"));
-      }
-
-      T value = validator.validate(this, response);
-      if (value != null) {
-        return value;
-      }
-    }
-  }
-}
diff --git a/opends/src/server/org/opends/server/tools/dsconfig/CreateSubCommandHandler.java b/opends/src/server/org/opends/server/tools/dsconfig/CreateSubCommandHandler.java
index 0b2034e..21bb850 100644
--- a/opends/src/server/org/opends/server/tools/dsconfig/CreateSubCommandHandler.java
+++ b/opends/src/server/org/opends/server/tools/dsconfig/CreateSubCommandHandler.java
@@ -25,13 +25,11 @@
  *      Portions Copyright 2007 Sun Microsystems, Inc.
  */
 package org.opends.server.tools.dsconfig;
-import org.opends.messages.Message;
 
 
 
-import static org.opends.messages.ToolMessages.*;
+import static org.opends.messages.DSConfigMessages.*;
 
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -44,6 +42,7 @@
 import java.util.TreeMap;
 import java.util.TreeSet;
 
+import org.opends.messages.Message;
 import org.opends.server.admin.AbstractManagedObjectDefinition;
 import org.opends.server.admin.Configuration;
 import org.opends.server.admin.ConfigurationClient;
@@ -57,6 +56,7 @@
 import org.opends.server.admin.ManagedObjectPath;
 import org.opends.server.admin.OptionalRelationDefinition;
 import org.opends.server.admin.PropertyDefinition;
+import org.opends.server.admin.PropertyDefinitionUsageBuilder;
 import org.opends.server.admin.PropertyException;
 import org.opends.server.admin.PropertyIsSingleValuedException;
 import org.opends.server.admin.PropertyOption;
@@ -68,6 +68,7 @@
 import org.opends.server.admin.client.IllegalManagedObjectNameException;
 import org.opends.server.admin.client.ManagedObject;
 import org.opends.server.admin.client.ManagedObjectDecodingException;
+import org.opends.server.admin.client.ManagementContext;
 import org.opends.server.admin.client.MissingMandatoryPropertiesException;
 import org.opends.server.admin.client.OperationRejectedException;
 import org.opends.server.protocols.ldap.LDAPResultCode;
@@ -76,6 +77,13 @@
 import org.opends.server.util.args.StringArgument;
 import org.opends.server.util.args.SubCommand;
 import org.opends.server.util.args.SubCommandArgumentParser;
+import org.opends.server.util.cli.CLIException;
+import org.opends.server.util.cli.ConsoleApplication;
+import org.opends.server.util.cli.HelpCallback;
+import org.opends.server.util.cli.Menu;
+import org.opends.server.util.cli.MenuBuilder;
+import org.opends.server.util.cli.MenuResult;
+import org.opends.server.util.cli.ValidationCallback;
 import org.opends.server.util.table.TableBuilder;
 import org.opends.server.util.table.TextTablePrinter;
 
@@ -94,7 +102,6 @@
 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.
@@ -220,7 +227,8 @@
 
 
   /**
-   * A help call-back which displays help about available component types.
+   * A help call-back which displays help about available component
+   * types.
    */
   private final class TypeHelpCallback implements HelpCallback {
 
@@ -228,14 +236,15 @@
      * {@inheritDoc}
      */
     public void display(ConsoleApplication app) {
-      // Create a table containing a description of each component type.
+      // 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_TYPE
+          .get());
 
-      builder.appendHeading(
-              INFO_DSCFG_DESCRIPTION_CREATE_HELP_HEADING_DESCR.get());
+      builder.appendHeading(INFO_DSCFG_DESCRIPTION_CREATE_HELP_HEADING_DESCR
+          .get());
 
       boolean isFirst = true;
       for (ManagedObjectDefinition<?, ?> d : types.values()) {
@@ -261,6 +270,8 @@
       printer.setColumnWidth(1, 0);
       printer.setColumnSeparator(":");
       builder.print(printer);
+      app.println();
+      app.pressReturnToContinue();
     }
   }
 
@@ -300,8 +311,6 @@
    *          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 parser
    *          The sub-command argument parser.
    * @param p
@@ -314,10 +323,9 @@
    */
   public static <C extends ConfigurationClient, S extends Configuration>
       CreateSubCommandHandler<C, S> create(
-      ConsoleApplication app, SubCommandArgumentParser parser,
-      ManagedObjectPath<?, ?> p, InstantiableRelationDefinition<C, S> r)
-      throws ArgumentException {
-    return new CreateSubCommandHandler<C, S>(app, parser, p, r, r
+      SubCommandArgumentParser parser, ManagedObjectPath<?, ?> p,
+      InstantiableRelationDefinition<C, S> r) throws ArgumentException {
+    return new CreateSubCommandHandler<C, S>(parser, p, r, r
         .getNamingPropertyDefinition(), p.child(r, "DUMMY"));
   }
 
@@ -330,8 +338,6 @@
    *          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 parser
    *          The sub-command argument parser.
    * @param p
@@ -344,11 +350,9 @@
    */
   public static <C extends ConfigurationClient, S extends Configuration>
       CreateSubCommandHandler<C, S> create(
-      ConsoleApplication app, SubCommandArgumentParser parser,
-      ManagedObjectPath<?, ?> p, OptionalRelationDefinition<C, S> r)
-      throws ArgumentException {
-    return new CreateSubCommandHandler<C, S>(app, parser, p, r, null, p
-        .child(r));
+      SubCommandArgumentParser parser, ManagedObjectPath<?, ?> p,
+      OptionalRelationDefinition<C, S> r) throws ArgumentException {
+    return new CreateSubCommandHandler<C, S>(parser, p, r, null, p.child(r));
   }
 
   // The sub-commands naming arguments.
@@ -377,7 +381,7 @@
   // The set of instantiable managed object definitions and their
   // associated type option value.
   private final SortedMap<String,
-    ManagedObjectDefinition<? extends C, ? extends S>> types;
+      ManagedObjectDefinition<? extends C, ? extends S>> types;
 
   // The syntax of the type argument.
   private final String typeUsage;
@@ -385,24 +389,20 @@
 
 
   // Common constructor.
-  private CreateSubCommandHandler(ConsoleApplication app,
-      SubCommandArgumentParser parser,
-      ManagedObjectPath<?, ?> p, RelationDefinition<C, S> r,
-      PropertyDefinition<?> pd, ManagedObjectPath<?, ?> c)
-      throws ArgumentException {
-    super(app);
-
+  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();
-    Message description = INFO_DSCFG_DESCRIPTION_SUBCMD_CREATE.get(
-      r.getChildDefinition().getUserFriendlyPluralName()
-    );
-    this.subCommand = new SubCommand(parser, name, false, 0,
-            0, null, description);
+    Message 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.
@@ -418,9 +418,8 @@
     // 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, "{PROP:VALUE}", null, null,
-        INFO_DSCFG_DESCRIPTION_PROP_VAL.get());
+        OPTION_DSCFG_SHORT_SET, OPTION_DSCFG_LONG_SET, false, true, true,
+        "{PROP:VALUE}", null, null, INFO_DSCFG_DESCRIPTION_PROP_VAL.get());
     this.subCommand.addArgument(this.propertySetArgument);
 
     // Build the -t option usage.
@@ -463,6 +462,19 @@
 
 
   /**
+   * 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
@@ -476,27 +488,57 @@
    * {@inheritDoc}
    */
   @Override
-  public int run() throws ArgumentException, ClientException {
+  public MenuResult<Integer> run(ConsoleApplication app,
+      ManagementContextFactory factory) throws ArgumentException,
+      ClientException, CLIException {
     // Determine the type of managed object to be created.
     String typeName;
 
     if (!typeArgument.isPresent()) {
-      if (getConsoleApplication().isInteractive()) {
+      if (app.isInteractive()) {
         // Let the user choose.
 
         // If there is only one choice then return immediately.
         if (types.size() == 1) {
           typeName = types.keySet().iterator().next();
         } else {
-          List<String> values = new ArrayList<String>(types.keySet());
-          List<Message> descriptions = new ArrayList<Message>(values.size());
-          for (ManagedObjectDefinition<?, ?> d : types.values()) {
-            descriptions.add(d.getUserFriendlyName());
+          MenuBuilder<String> builder = new MenuBuilder<String>(app);
+          Message msg = INFO_DSCFG_CREATE_TYPE_PROMPT.get(relation
+              .getChildDefinition().getUserFriendlyName());
+          builder.setMultipleColumnThreshold(MULTI_COLUMN_THRESHOLD);
+          builder.setPrompt(msg);
+
+          for (String type : types.keySet()) {
+            ManagedObjectDefinition<?, ?> d = types.get(type);
+            Message option = d.getUserFriendlyName();
+            if (type.equals(GENERIC_TYPE)) {
+              option = INFO_DSCFG_GENERIC_TYPE_OPTION.get(option);
+            }
+            builder.addNumberedOption(option, MenuResult.success(type));
           }
-          Message msg = INFO_DSCFG_CREATE_TYPE_PROMPT.get(
-                  relation.getChildDefinition().getUserFriendlyName());
-          typeName = getConsoleApplication().readChoice(msg, descriptions,
-              values, new TypeHelpCallback());
+          builder.addHelpOption(new TypeHelpCallback());
+          if (app.isMenuDrivenMode()) {
+            builder.addCancelOption(true);
+          }
+          builder.addQuitOption();
+
+          Menu<String> menu = builder.toMenu();
+          app.println();
+          app.println();
+          MenuResult<String> result = menu.run();
+          if (result.isSuccess()) {
+            typeName = result.getValue();
+          } else if (result.isCancel()) {
+            return MenuResult.cancel();
+          } else {
+            // Must be quit.
+            if (!app.isMenuDrivenMode()) {
+              msg = INFO_DSCFG_CONFIRM_CREATE_FAIL.get(relation
+                  .getUserFriendlyName());
+              app.printVerboseMessage(msg);
+            }
+            return MenuResult.quit();
+          }
         }
       } else if (typeArgument.getDefaultValue() != null) {
         typeName = typeArgument.getDefaultValue();
@@ -515,7 +557,7 @@
     }
 
     // Get the naming argument values.
-    List<String> names = getNamingArgValues(namingArgs);
+    List<String> names = getNamingArgValues(app, namingArgs);
 
     // Encode the provided properties.
     List<String> propertyArgs = propertySetArgument.getValues();
@@ -523,13 +565,13 @@
         namingPropertyDefinition, propertyArgs);
 
     // Add the child managed object.
-    ManagedObject<?> parent;
+    ManagementContext context = factory.getManagementContext(app);
+    MenuResult<ManagedObject<?>> result;
     try {
-      parent = getManagedObject(path, names);
+      result = getManagedObject(app, context, path, names);
     } catch (AuthorizationException e) {
       Message msg = ERR_DSCFG_ERROR_CREATE_AUTHZ.get(d.getUserFriendlyName());
-      throw new ClientException(LDAPResultCode.INSUFFICIENT_ACCESS_RIGHTS,
-          msg);
+      throw new ClientException(LDAPResultCode.INSUFFICIENT_ACCESS_RIGHTS, msg);
     } catch (DefinitionDecodingException e) {
       Message ufn = path.getManagedObjectDefinition().getUserFriendlyName();
       Message msg = ERR_DSCFG_ERROR_GET_PARENT_DDE.get(ufn, ufn, ufn);
@@ -539,19 +581,32 @@
       Message msg = ERR_DSCFG_ERROR_GET_PARENT_MODE.get(ufn);
       throw new ClientException(LDAPResultCode.OPERATIONS_ERROR, msg);
     } catch (CommunicationException e) {
-      Message msg = ERR_DSCFG_ERROR_CREATE_CE.get(
-          d.getUserFriendlyName(), e.getMessage());
+      Message msg = ERR_DSCFG_ERROR_CREATE_CE.get(d.getUserFriendlyName(), e
+          .getMessage());
       throw new ClientException(LDAPResultCode.CLIENT_SIDE_SERVER_DOWN, msg);
     } catch (ConcurrentModificationException e) {
       Message msg = ERR_DSCFG_ERROR_CREATE_CME.get(d.getUserFriendlyName());
-      throw new ClientException(LDAPResultCode.CONSTRAINT_VIOLATION,
-          msg);
+      throw new ClientException(LDAPResultCode.CONSTRAINT_VIOLATION, msg);
     } catch (ManagedObjectNotFoundException e) {
       Message ufn = path.getManagedObjectDefinition().getUserFriendlyName();
       Message msg = ERR_DSCFG_ERROR_GET_PARENT_MONFE.get(ufn);
       throw new ClientException(LDAPResultCode.NO_SUCH_OBJECT, msg);
     }
 
+    if (result.isQuit()) {
+      if (!app.isMenuDrivenMode()) {
+        // User chose to cancel creation.
+        Message msg = INFO_DSCFG_CONFIRM_CREATE_FAIL.get(d
+            .getUserFriendlyName());
+        app.printVerboseMessage(msg);
+      }
+      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 {
       ManagedObject<? extends C> child;
       List<DefaultBehaviorException> exceptions =
@@ -561,8 +616,9 @@
           (InstantiableRelationDefinition<C, S>) relation;
         String name = names.get(names.size() - 1);
         if (name == null) {
-          if (getConsoleApplication().isInteractive()) {
-            child = createChildInteractively(parent, irelation, d, exceptions);
+          if (app.isInteractive()) {
+            child = createChildInteractively(app, parent, irelation,
+                d, exceptions);
           } else {
             throw ArgumentExceptionFactory
                 .missingMandatoryNonInteractiveArgument(namingArgs.get(names
@@ -570,7 +626,8 @@
           }
         } else {
           try {
-            child = parent.createChild(irelation, d, name, exceptions);
+            child = parent.createChild(irelation, d, name,
+                exceptions);
           } catch (IllegalManagedObjectNameException e) {
             throw ArgumentExceptionFactory
                 .adaptIllegalManagedObjectNameException(e, d);
@@ -591,7 +648,7 @@
       }
 
       // Interactively set properties if applicable.
-      if (getConsoleApplication().isInteractive()) {
+      if (app.isInteractive()) {
         SortedSet<PropertyDefinition<?>> properties =
           new TreeSet<PropertyDefinition<?>>();
 
@@ -600,10 +657,6 @@
             continue;
           }
 
-          if (pd.hasOption(PropertyOption.MONITORING)) {
-            continue;
-          }
-
           if (!isAdvancedMode() && pd.hasOption(PropertyOption.ADVANCED)) {
             continue;
           }
@@ -611,72 +664,65 @@
           properties.add(pd);
         }
 
-        PropertyValueReader reader =
-          new PropertyValueReader(getConsoleApplication());
-        reader.readAll(child, properties);
-      }
-
-      // Confirm commit.
-      Message prompt = INFO_DSCFG_CONFIRM_CREATE.get(d.getUserFriendlyName());
-      if (!getConsoleApplication().confirmAction(prompt)) {
-        // Output failure message.
-        Message msg =
-            INFO_DSCFG_CONFIRM_CREATE_FAIL.get(d.getUserFriendlyName());
-        getConsoleApplication().printVerboseMessage(msg);
-        return 1;
+        PropertyValueEditor editor = new PropertyValueEditor(app);
+        MenuResult<Void> result2 = editor.edit(child, properties, true);
+        if (result2.isQuit()) {
+          if (!app.isMenuDrivenMode()) {
+            Message msg = INFO_DSCFG_CONFIRM_CREATE_FAIL.get(d
+                .getUserFriendlyName());
+            app.printVerboseMessage(msg);
+          }
+          return MenuResult.quit();
+        } else if (result2.isCancel()) {
+          return MenuResult.cancel();
+        }
       }
 
       // Add the managed object.
       child.commit();
 
       // Output success message.
-      Message msg =
-          INFO_DSCFG_CONFIRM_CREATE_SUCCESS.get(d.getUserFriendlyName());
-      getConsoleApplication().printVerboseMessage(msg);
+      Message msg = INFO_DSCFG_CONFIRM_CREATE_SUCCESS.get(d
+          .getUserFriendlyName());
+      app.printVerboseMessage(msg);
     } catch (MissingMandatoryPropertiesException e) {
       throw ArgumentExceptionFactory.adaptMissingMandatoryPropertiesException(
           e, d);
     } catch (AuthorizationException e) {
       Message msg = ERR_DSCFG_ERROR_CREATE_AUTHZ.get(d.getUserFriendlyName());
-      throw new ClientException(LDAPResultCode.INSUFFICIENT_ACCESS_RIGHTS,
-          msg);
+      throw new ClientException(LDAPResultCode.INSUFFICIENT_ACCESS_RIGHTS, msg);
     } catch (ManagedObjectAlreadyExistsException e) {
       Message msg = ERR_DSCFG_ERROR_CREATE_MOAEE.get(d.getUserFriendlyName());
-      throw new ClientException(LDAPResultCode.ENTRY_ALREADY_EXISTS,
-          msg);
+      throw new ClientException(LDAPResultCode.ENTRY_ALREADY_EXISTS, msg);
     } catch (ConcurrentModificationException e) {
       Message msg = ERR_DSCFG_ERROR_CREATE_CME.get(d.getUserFriendlyName());
-      throw new ClientException(LDAPResultCode.CONSTRAINT_VIOLATION,
-          msg);
+      throw new ClientException(LDAPResultCode.CONSTRAINT_VIOLATION, msg);
     } catch (OperationRejectedException e) {
-      Message msg = ERR_DSCFG_ERROR_CREATE_ORE.get(
-          d.getUserFriendlyName(), e.getMessage());
-      throw new ClientException(LDAPResultCode.CONSTRAINT_VIOLATION,
-          msg);
+      Message msg = ERR_DSCFG_ERROR_CREATE_ORE.get(d.getUserFriendlyName(), e
+          .getMessage());
+      throw new ClientException(LDAPResultCode.CONSTRAINT_VIOLATION, msg);
     } catch (CommunicationException e) {
-      Message msg = ERR_DSCFG_ERROR_CREATE_CE.get(
-          d.getUserFriendlyName(), e.getMessage());
+      Message msg = ERR_DSCFG_ERROR_CREATE_CE.get(d.getUserFriendlyName(), e
+          .getMessage());
       throw new ClientException(LDAPResultCode.CLIENT_SIDE_SERVER_DOWN, msg);
     }
 
-    return 0;
+    return MenuResult.success(0);
   }
 
 
 
   // Interactively create the child by prompting for the name.
   private ManagedObject<? extends C> createChildInteractively(
-      final ManagedObject<?> parent,
+      ConsoleApplication app, final ManagedObject<?> parent,
       final InstantiableRelationDefinition<C, S> irelation,
       final ManagedObjectDefinition<? extends C, ? extends S> d,
-      final List<DefaultBehaviorException> exceptions)
-      throws ArgumentException, ClientException {
-    Message msg = INFO_DSCFG_CREATE_NAME_PROMPT.get(d.getUserFriendlyName());
+      final List<DefaultBehaviorException> exceptions) throws CLIException {
     ValidationCallback<ManagedObject<? extends C>> validator =
       new ValidationCallback<ManagedObject<? extends C>>() {
 
       public ManagedObject<? extends C> validate(ConsoleApplication app,
-          String input) throws ClientException {
+          String input) throws CLIException {
         ManagedObject<? extends C> child;
 
         // First attempt to create the child, this will guarantee that
@@ -684,10 +730,11 @@
         try {
           child = parent.createChild(irelation, d, input, exceptions);
         } catch (IllegalManagedObjectNameException e) {
-          ArgumentException ae = ArgumentExceptionFactory
+          CLIException ae = ArgumentExceptionFactory
               .adaptIllegalManagedObjectNameException(e, d);
           app.println();
-          app.printMessage(ae.getMessageObject());
+          app.println(ae.getMessageObject());
+          app.println();
           return null;
         }
 
@@ -697,19 +744,17 @@
           // Attempt to retrieve a child using this name.
           parent.getChild(irelation, input);
         } catch (AuthorizationException e) {
-          Message msg =
-              ERR_DSCFG_ERROR_CREATE_AUTHZ.get(irelation.getUserFriendlyName());
-          throw new ClientException(LDAPResultCode.INSUFFICIENT_ACCESS_RIGHTS,
-              msg);
+          Message msg = ERR_DSCFG_ERROR_CREATE_AUTHZ.get(irelation
+              .getUserFriendlyName());
+          throw new CLIException(msg);
         } catch (ConcurrentModificationException e) {
-          Message msg =
-              ERR_DSCFG_ERROR_CREATE_CME.get(irelation.getUserFriendlyName());
-          throw new ClientException(LDAPResultCode.CONSTRAINT_VIOLATION, msg);
+          Message msg = ERR_DSCFG_ERROR_CREATE_CME.get(irelation
+              .getUserFriendlyName());
+          throw new CLIException(msg);
         } catch (CommunicationException e) {
-          Message msg = ERR_DSCFG_ERROR_CREATE_CE.get(
-              irelation.getUserFriendlyName(), e.getMessage());
-          throw new ClientException(LDAPResultCode.CLIENT_SIDE_SERVER_DOWN,
-              msg);
+          Message msg = ERR_DSCFG_ERROR_CREATE_CE.get(irelation
+              .getUserFriendlyName(), e.getMessage());
+          throw new CLIException(msg);
         } catch (DefinitionDecodingException e) {
           // Do nothing.
         } catch (ManagedObjectDecodingException e) {
@@ -720,16 +765,45 @@
         }
 
         // A child with the specified name must already exist.
-        Message msg = ERR_DSCFG_ERROR_CREATE_NAME_ALREADY_EXISTS.get(
-                relation.getUserFriendlyName(), input);
+        Message msg = ERR_DSCFG_ERROR_CREATE_NAME_ALREADY_EXISTS.get(relation
+            .getUserFriendlyName(), input);
         app.println();
-        app.printMessage(msg);
+        app.println(msg);
+        app.println();
         return null;
       }
 
     };
 
-    return getConsoleApplication().readValidatedInput(msg, validator);
+    app.println();
+    app.println();
+
+    // Display additional help if the name is a naming property.
+    Message 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.println(pd.getSynopsis(), 4);
+
+      if (pd.getDescription() != null) {
+        app.println();
+        app.println(pd.getDescription(), 4);
+      }
+
+      PropertyDefinitionUsageBuilder b = new PropertyDefinitionUsageBuilder(
+          true);
+      app.println();
+      app.println(INFO_EDITOR_HEADING_SYNTAX.get(b.getUsage(pd)), 4);
+      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);
+    }
   }
 
 
@@ -752,8 +826,8 @@
 
     // Process its sub-definitions.
     String suffix = "-" + d.getName();
-    for (AbstractManagedObjectDefinition<? extends C, ? extends S> c :
-        d.getAllChildren()) {
+    for (AbstractManagedObjectDefinition<? extends C, ? extends S> c : d
+        .getAllChildren()) {
       if (c instanceof ManagedObjectDefinition) {
         ManagedObjectDefinition<? extends C, ? extends S> mod =
           (ManagedObjectDefinition<? extends C, ? extends S>) c;
diff --git a/opends/src/server/org/opends/server/tools/dsconfig/DSConfig.java b/opends/src/server/org/opends/server/tools/dsconfig/DSConfig.java
index 6b452e6..bb63ea4 100644
--- a/opends/src/server/org/opends/server/tools/dsconfig/DSConfig.java
+++ b/opends/src/server/org/opends/server/tools/dsconfig/DSConfig.java
@@ -25,12 +25,12 @@
  *      Portions Copyright 2007 Sun Microsystems, Inc.
  */
 package org.opends.server.tools.dsconfig;
-import org.opends.messages.Message;
 
 
 
-import static org.opends.server.loggers.debug.DebugLogger.*;
+import static org.opends.messages.DSConfigMessages.*;
 import static org.opends.messages.ToolMessages.*;
+import static org.opends.server.loggers.debug.DebugLogger.*;
 import static org.opends.server.tools.ToolConstants.*;
 import static org.opends.server.util.StaticUtils.*;
 
@@ -39,18 +39,21 @@
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Set;
 import java.util.SortedSet;
 import java.util.TreeMap;
 import java.util.TreeSet;
 
+import org.opends.messages.Message;
 import org.opends.server.admin.AbstractManagedObjectDefinition;
 import org.opends.server.admin.AttributeTypePropertyDefinition;
 import org.opends.server.admin.ClassLoaderProvider;
 import org.opends.server.admin.ClassPropertyDefinition;
+import org.opends.server.admin.InstantiableRelationDefinition;
 import org.opends.server.admin.PropertyException;
+import org.opends.server.admin.RelationDefinition;
 import org.opends.server.admin.Tag;
 import org.opends.server.admin.client.ManagedObjectDecodingException;
-import org.opends.server.admin.client.ManagementContext;
 import org.opends.server.admin.client.cli.SecureConnectionCliParser;
 import org.opends.server.loggers.debug.DebugTracer;
 import org.opends.server.tools.ClientException;
@@ -62,6 +65,13 @@
 import org.opends.server.util.args.BooleanArgument;
 import org.opends.server.util.args.SubCommand;
 import org.opends.server.util.args.SubCommandArgumentParser;
+import org.opends.server.util.cli.CLIException;
+import org.opends.server.util.cli.ConsoleApplication;
+import org.opends.server.util.cli.Menu;
+import org.opends.server.util.cli.MenuBuilder;
+import org.opends.server.util.cli.MenuCallback;
+import org.opends.server.util.cli.MenuResult;
+import org.opends.server.util.cli.OutputStreamConsoleApplication;
 import org.opends.server.util.table.TableBuilder;
 import org.opends.server.util.table.TextTablePrinter;
 
@@ -74,6 +84,172 @@
 public final class DSConfig extends ConsoleApplication {
 
   /**
+   * A menu call-back which runs a sub-command interactively.
+   */
+  public 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}
+     */
+    public MenuResult<Integer> invoke(ConsoleApplication app)
+        throws CLIException {
+      try {
+        MenuResult<Integer> result = handler.run(app, factory);
+
+        if (result.isQuit()) {
+          return result;
+        } else {
+          // 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.getExitCode());
+      }
+    }
+  }
+
+
+
+  /**
+   * The interactive mode sub-menu implementation.
+   */
+  public 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) {
+      Message ufn = rd.getUserFriendlyName();
+
+      Message ufpn = null;
+      if (rd instanceof InstantiableRelationDefinition) {
+        InstantiableRelationDefinition<?, ?> ir =
+          (InstantiableRelationDefinition<?, ?>) rd;
+        ufpn = ir.getUserFriendlyPluralName();
+      }
+
+      MenuBuilder<Integer> builder = new MenuBuilder<Integer>(app);
+
+      builder.setTitle(INFO_DSCFG_HEADING_COMPONENT_MENU_TITLE.get(ufn));
+      builder.setPrompt(INFO_DSCFG_HEADING_COMPONENT_MENU_PROMPT.get());
+
+      if (lh != null) {
+        SubCommandHandlerMenuCallback callback =
+          new SubCommandHandlerMenuCallback(lh);
+        if (ufpn != null) {
+          builder.addNumberedOption(
+              INFO_DSCFG_OPTION_COMPONENT_MENU_LIST_PLURAL.get(ufpn), callback);
+        } else {
+          builder
+              .addNumberedOption(INFO_DSCFG_OPTION_COMPONENT_MENU_LIST_SINGULAR
+                  .get(ufn), callback);
+        }
+      }
+
+      if (ch != null) {
+        SubCommandHandlerMenuCallback callback =
+          new SubCommandHandlerMenuCallback(ch);
+        builder.addNumberedOption(INFO_DSCFG_OPTION_COMPONENT_MENU_CREATE
+            .get(ufn), callback);
+      }
+
+      if (sh != null) {
+        SubCommandHandlerMenuCallback callback =
+          new SubCommandHandlerMenuCallback(sh);
+        if (ufpn != null) {
+          builder
+              .addNumberedOption(INFO_DSCFG_OPTION_COMPONENT_MENU_MODIFY_PLURAL
+                  .get(ufn), callback);
+        } else {
+          builder.addNumberedOption(
+              INFO_DSCFG_OPTION_COMPONENT_MENU_MODIFY_SINGULAR.get(ufn),
+              callback);
+        }
+      }
+
+      if (dh != null) {
+        SubCommandHandlerMenuCallback callback =
+          new SubCommandHandlerMenuCallback(dh);
+        builder.addNumberedOption(INFO_DSCFG_OPTION_COMPONENT_MENU_DELETE
+            .get(ufn), callback);
+      }
+
+      builder.addBackOption(true);
+      builder.addQuitOption();
+
+      this.menu = builder.toMenu();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public final MenuResult<Integer> invoke(ConsoleApplication app)
+        throws CLIException {
+      try {
+        app.println();
+        app.println();
+
+        MenuResult<Integer> result = menu.run();
+
+        if (result.isCancel()) {
+          return MenuResult.again();
+        }
+
+        return result;
+      } catch (CLIException e) {
+        app.println(e.getMessageObject());
+        return MenuResult.success(1);
+      }
+    }
+
+  }
+
+  /**
    * The tracer object for the debug logger.
    */
   private static final DebugTracer TRACER = getTracer();
@@ -126,7 +302,7 @@
         app.initializeClientEnvironment();
       } catch (InitializationException e) {
         // TODO: is this ok as an error message?
-        app.printMessage(e.getMessageObject());
+        app.println(e.getMessageObject());
         return 1;
       }
     }
@@ -147,10 +323,16 @@
   // 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 request non interactive
   // behavior.
   private BooleanArgument noPromptArgument;
@@ -168,10 +350,6 @@
   // The argument which should be used to request usage information.
   private BooleanArgument showUsageArgument;
 
-  // Flag indicating whether or not the sub-commands have
-  // already been initialized.
-  private boolean subCommandsInitialized = false;
-
   // The argument which should be used to request verbose output.
   private BooleanArgument verboseArgument;
 
@@ -240,6 +418,16 @@
   /**
    * {@inheritDoc}
    */
+  @Override
+  public boolean isMenuDrivenMode() {
+    return !hasSubCommand;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
   public boolean isQuiet() {
     return quietArgument.isPresent();
   }
@@ -264,21 +452,11 @@
 
 
 
-  /**
-   * {@inheritDoc}
-   */
-  public ManagementContext getManagementContext() throws ArgumentException,
-      ClientException {
-    return factory.getManagementContext(this);
-  }
-
-
-
   // Displays the provided message followed by a help usage reference.
   private void displayMessageAndUsageReference(Message message) {
-    printMessage(message);
-    printMessage(Message.EMPTY);
-    printMessage(parser.getHelpUsageReference());
+    println(message);
+    println();
+    println(parser.getHelpUsageReference());
   }
 
 
@@ -297,8 +475,8 @@
       quietArgument = new BooleanArgument(
           SecureConnectionCliParser.QUIET_OPTION_LONG,
           SecureConnectionCliParser.QUIET_OPTION_SHORT,
-          SecureConnectionCliParser.QUIET_OPTION_LONG,
-          INFO_DESCRIPTION_QUIET.get());
+          SecureConnectionCliParser.QUIET_OPTION_LONG, INFO_DESCRIPTION_QUIET
+              .get());
 
       scriptFriendlyArgument = new BooleanArgument("script-friendly", 's',
           "script-friendly", INFO_DESCRIPTION_SCRIPT_FRIENDLY.get());
@@ -310,8 +488,8 @@
           INFO_DESCRIPTION_NO_PROMPT.get());
 
       showUsageArgument = new BooleanArgument("showUsage", OPTION_SHORT_HELP,
-              OPTION_LONG_HELP,
-              INFO_DSCFG_DESCRIPTION_SHOW_GROUP_USAGE_SUMMARY.get());
+          OPTION_LONG_HELP, INFO_DSCFG_DESCRIPTION_SHOW_GROUP_USAGE_SUMMARY
+              .get());
 
       // Register the global arguments.
       parser.addGlobalArgument(showUsageArgument);
@@ -340,7 +518,9 @@
    *           If a sub-command could not be created.
    */
   private void initializeSubCommands() throws ArgumentException {
-    if (subCommandsInitialized == false) {
+    if (handlerFactory == null) {
+      handlerFactory = new SubCommandHandlerFactory(parser);
+
       Comparator<SubCommand> c = new Comparator<SubCommand>() {
 
         public int compare(SubCommand o1, SubCommand o2) {
@@ -348,12 +528,11 @@
         }
       };
 
-      SubCommandBuilder builder = new SubCommandBuilder();
       Map<Tag, SortedSet<SubCommand>> groups =
         new TreeMap<Tag, SortedSet<SubCommand>>();
       SortedSet<SubCommand> allSubCommands = new TreeSet<SubCommand>(c);
-      for (SubCommandHandler handler : builder.getSubCommandHandlers(this,
-          parser)) {
+      for (SubCommandHandler handler : handlerFactory
+          .getAllSubCommandHandlers()) {
         SubCommand sc = handler.getSubCommand();
 
         handlers.put(sc, handler);
@@ -391,8 +570,6 @@
 
       parser.addGlobalArgument(arg);
       parser.setUsageGroupArgument(arg, allSubCommands);
-
-      subCommandsInitialized = true;
     }
   }
 
@@ -415,7 +592,7 @@
       initializeSubCommands();
     } catch (ArgumentException e) {
       Message message = ERR_CANNOT_INITIALIZE_ARGS.get(e.getMessage());
-      printMessage(message);
+      println(message);
       return 1;
     }
 
@@ -434,26 +611,18 @@
       return 0;
     }
 
-    // Make sure that we have a sub-command.
-    if (parser.getSubCommand() == null) {
-      Message message = ERR_ERROR_PARSING_ARGS.get(
-              ERR_DSCFG_ERROR_MISSING_SUBCOMMAND.get());
-      displayMessageAndUsageReference(message);
-      return 1;
-    }
-
+    // Check for conflicting arguments.
     if (quietArgument.isPresent() && verboseArgument.isPresent()) {
-      Message message = ERR_TOOL_CONFLICTING_ARGS.get(
-              quietArgument.getLongIdentifier(),
-          verboseArgument.getLongIdentifier());
+      Message message = ERR_TOOL_CONFLICTING_ARGS.get(quietArgument
+          .getLongIdentifier(), verboseArgument.getLongIdentifier());
       displayMessageAndUsageReference(message);
       return 1;
     }
 
     if (quietArgument.isPresent() && !noPromptArgument.isPresent()) {
       Message message = ERR_DSCFG_ERROR_QUIET_AND_INTERACTIVE_INCOMPATIBLE.get(
-              quietArgument.getLongIdentifier(),
-          noPromptArgument.getLongIdentifier());
+          quietArgument.getLongIdentifier(), noPromptArgument
+              .getLongIdentifier());
       displayMessageAndUsageReference(message);
       return 1;
     }
@@ -469,28 +638,177 @@
     try {
       factory.validateGlobalArguments();
     } catch (ArgumentException e) {
-      printMessage(e.getMessageObject());
+      println(e.getMessageObject());
       return 1;
     }
 
-    // Retrieve the sub-command implementation and run it.
-    SubCommandHandler handler = handlers.get(parser.getSubCommand());
+    if (parser.getSubCommand() == null) {
+      hasSubCommand = false;
+
+      if (isInteractive()) {
+        // Top-level interactive mode.
+        return runInteractiveMode();
+      } else {
+        Message message = ERR_ERROR_PARSING_ARGS
+            .get(ERR_DSCFG_ERROR_MISSING_SUBCOMMAND.get());
+        displayMessageAndUsageReference(message);
+        return 1;
+      }
+    } else {
+      hasSubCommand = true;
+
+      // Retrieve the sub-command implementation and run it.
+      SubCommandHandler handler = handlers.get(parser.getSubCommand());
+      return runSubCommand(handler);
+    }
+  }
+
+
+
+  // Run the top-level interactive console.
+  private int runInteractiveMode() {
+    // In interactive mode, redirect all output to stdout.
+    ConsoleApplication app = new OutputStreamConsoleApplication(this);
+
+    // Build menu structure.
+    Comparator<RelationDefinition<?, ?>> c =
+      new Comparator<RelationDefinition<?, ?>>() {
+
+      public int compare(RelationDefinition<?, ?> rd1,
+          RelationDefinition<?, ?> rd2) {
+        String s1 = rd1.getUserFriendlyName().toString();
+        String s2 = rd2.getUserFriendlyName().toString();
+
+        return s1.compareToIgnoreCase(s2);
+      }
+
+    };
+
+    Set<RelationDefinition<?, ?>> relations;
+    Map<RelationDefinition<?, ?>, CreateSubCommandHandler<?, ?>> createHandlers;
+    Map<RelationDefinition<?, ?>, DeleteSubCommandHandler> deleteHandlers;
+    Map<RelationDefinition<?, ?>, ListSubCommandHandler> listHandlers;
+    Map<RelationDefinition<?, ?>, GetPropSubCommandHandler> getPropHandlers;
+    Map<RelationDefinition<?, ?>, SetPropSubCommandHandler> setPropHandlers;
+
+    relations = new TreeSet<RelationDefinition<?, ?>>(c);
+    createHandlers =
+      new HashMap<RelationDefinition<?, ?>, CreateSubCommandHandler<?, ?>>();
+    deleteHandlers =
+      new HashMap<RelationDefinition<?, ?>, DeleteSubCommandHandler>();
+    listHandlers =
+      new HashMap<RelationDefinition<?, ?>, ListSubCommandHandler>();
+    getPropHandlers =
+      new HashMap<RelationDefinition<?, ?>, GetPropSubCommandHandler>();
+    setPropHandlers =
+      new HashMap<RelationDefinition<?, ?>, SetPropSubCommandHandler>();
+
+    for (CreateSubCommandHandler<?, ?> ch : handlerFactory
+        .getCreateSubCommandHandlers()) {
+      relations.add(ch.getRelationDefinition());
+      createHandlers.put(ch.getRelationDefinition(), ch);
+    }
+
+    for (DeleteSubCommandHandler dh : handlerFactory
+        .getDeleteSubCommandHandlers()) {
+      relations.add(dh.getRelationDefinition());
+      deleteHandlers.put(dh.getRelationDefinition(), dh);
+    }
+
+    for (ListSubCommandHandler lh :
+      handlerFactory.getListSubCommandHandlers()) {
+      relations.add(lh.getRelationDefinition());
+      listHandlers.put(lh.getRelationDefinition(), lh);
+    }
+
+    for (GetPropSubCommandHandler gh : handlerFactory
+        .getGetPropSubCommandHandlers()) {
+      relations.add(gh.getRelationDefinition());
+      getPropHandlers.put(gh.getRelationDefinition(), gh);
+    }
+
+    for (SetPropSubCommandHandler sh : handlerFactory
+        .getSetPropSubCommandHandlers()) {
+      relations.add(sh.getRelationDefinition());
+      setPropHandlers.put(sh.getRelationDefinition(), sh);
+    }
+
+    // Main menu.
+    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 (RelationDefinition<?, ?> rd : relations) {
+      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();
+
+    Menu<Integer> menu = builder.toMenu();
+
     try {
-      return handler.run();
+      // Force retrieval of management context.
+      factory.getManagementContext(app);
     } catch (ArgumentException e) {
-      printMessage(e.getMessageObject());
+      app.println(e.getMessageObject());
+      return 1;
+    } catch (ClientException e) {
+      app.println(e.getMessageObject());
+      return 1;
+    }
+
+    try {
+      app.println();
+      app.println();
+
+      MenuResult<Integer> result = menu.run();
+
+      if (result.isQuit()) {
+        return 0;
+      } else {
+        return result.getValue();
+      }
+    } catch (CLIException e) {
+      app.println(e.getMessageObject());
+      return 1;
+    }
+  }
+
+
+
+  // Run the provided sub-command handler.
+  private int runSubCommand(SubCommandHandler handler) {
+    try {
+      MenuResult<Integer> result = handler.run(this, factory);
+
+      if (result.isSuccess()) {
+        return result.getValue();
+      } else {
+        // User must have quit.
+        return 1;
+      }
+    } catch (ArgumentException e) {
+      println(e.getMessageObject());
+      return 1;
+    } catch (CLIException e) {
+      println(e.getMessageObject());
       return 1;
     } catch (ClientException e) {
       // If the client exception was caused by a decoding exception
       // then we should display the causes.
-      printMessage(e.getMessageObject());
+      println(e.getMessageObject());
 
       Throwable cause = e.getCause();
       if (cause instanceof ManagedObjectDecodingException) {
         ManagedObjectDecodingException de =
           (ManagedObjectDecodingException) cause;
 
-        printMessage(Message.EMPTY);
+        println();
         TableBuilder builder = new TableBuilder();
         for (PropertyException pe : de.getCauses()) {
           AbstractManagedObjectDefinition<?, ?> d = de
@@ -506,7 +824,7 @@
         printer.setDisplayHeadings(false);
         printer.setColumnWidth(1, 0);
         builder.print(printer);
-        printMessage(Message.EMPTY);
+        println();
       }
 
       return 1;
@@ -514,7 +832,7 @@
       if (debugEnabled()) {
         TRACER.debugCaught(DebugLogLevel.ERROR, e);
       }
-      printMessage(Message.raw(StaticUtils.stackTraceToString(e)));
+      println(Message.raw(StaticUtils.stackTraceToString(e)));
       return 1;
     }
   }
diff --git a/opends/src/server/org/opends/server/tools/dsconfig/DeleteSubCommandHandler.java b/opends/src/server/org/opends/server/tools/dsconfig/DeleteSubCommandHandler.java
index cced653..e24bbfd 100644
--- a/opends/src/server/org/opends/server/tools/dsconfig/DeleteSubCommandHandler.java
+++ b/opends/src/server/org/opends/server/tools/dsconfig/DeleteSubCommandHandler.java
@@ -25,14 +25,14 @@
  *      Portions Copyright 2007 Sun Microsystems, Inc.
  */
 package org.opends.server.tools.dsconfig;
-import org.opends.messages.Message;
 
 
 
-import static org.opends.messages.ToolMessages.*;
+import static org.opends.messages.DSConfigMessages.*;
 
 import java.util.List;
 
+import org.opends.messages.Message;
 import org.opends.server.admin.DefinitionDecodingException;
 import org.opends.server.admin.InstantiableRelationDefinition;
 import org.opends.server.admin.ManagedObjectNotFoundException;
@@ -44,6 +44,7 @@
 import org.opends.server.admin.client.ConcurrentModificationException;
 import org.opends.server.admin.client.ManagedObject;
 import org.opends.server.admin.client.ManagedObjectDecodingException;
+import org.opends.server.admin.client.ManagementContext;
 import org.opends.server.admin.client.OperationRejectedException;
 import org.opends.server.protocols.ldap.LDAPResultCode;
 import org.opends.server.tools.ClientException;
@@ -52,6 +53,9 @@
 import org.opends.server.util.args.StringArgument;
 import org.opends.server.util.args.SubCommand;
 import org.opends.server.util.args.SubCommandArgumentParser;
+import org.opends.server.util.cli.CLIException;
+import org.opends.server.util.cli.ConsoleApplication;
+import org.opends.server.util.cli.MenuResult;
 
 
 
@@ -79,8 +83,6 @@
    * Creates a new delete-xxx sub-command for an instantiable
    * relation.
    *
-   * @param app
-   *          The console application.
    * @param parser
    *          The sub-command argument parser.
    * @param p
@@ -91,10 +93,10 @@
    * @throws ArgumentException
    *           If the sub-command could not be created successfully.
    */
-  public static DeleteSubCommandHandler create(ConsoleApplication app,
+  public static DeleteSubCommandHandler create(
       SubCommandArgumentParser parser, ManagedObjectPath<?, ?> p,
       InstantiableRelationDefinition<?, ?> r) throws ArgumentException {
-    return new DeleteSubCommandHandler(app, parser, p, r, p.child(r, "DUMMY"));
+    return new DeleteSubCommandHandler(parser, p, r, p.child(r, "DUMMY"));
   }
 
 
@@ -102,8 +104,6 @@
   /**
    * Creates a new delete-xxx sub-command for an optional relation.
    *
-   * @param app
-   *          The console application.
    * @param parser
    *          The sub-command argument parser.
    * @param p
@@ -114,10 +114,10 @@
    * @throws ArgumentException
    *           If the sub-command could not be created successfully.
    */
-  public static DeleteSubCommandHandler create(ConsoleApplication app,
+  public static DeleteSubCommandHandler create(
       SubCommandArgumentParser parser, ManagedObjectPath<?, ?> p,
       OptionalRelationDefinition<?, ?> r) throws ArgumentException {
-    return new DeleteSubCommandHandler(app, parser, p, r, p.child(r));
+    return new DeleteSubCommandHandler(parser, p, r, p.child(r));
   }
 
   // The argument which should be used to force deletion.
@@ -139,12 +139,10 @@
 
 
   // Private constructor.
-  private DeleteSubCommandHandler(ConsoleApplication app,
+  private DeleteSubCommandHandler(
       SubCommandArgumentParser parser, ManagedObjectPath<?, ?> p,
       RelationDefinition<?, ?> r, ManagedObjectPath<?, ?> c)
       throws ArgumentException {
-    super(app);
-
     this.path = p;
     this.relation = r;
 
@@ -171,6 +169,19 @@
 
 
   /**
+   * 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
@@ -184,19 +195,21 @@
    * {@inheritDoc}
    */
   @Override
-  public int run() throws ArgumentException, ClientException {
+  public MenuResult<Integer> run(ConsoleApplication app,
+      ManagementContextFactory factory) throws ArgumentException,
+      ClientException, CLIException {
     // Get the naming argument values.
-    List<String> names = getNamingArgValues(namingArgs);
+    List<String> names = getNamingArgValues(app, namingArgs);
 
     // Delete the child managed object.
-    ManagedObject<?> parent = null;
+    ManagementContext context = factory.getManagementContext(app);
+    MenuResult<ManagedObject<?>> result;
     try {
-      parent = getManagedObject(path, names);
+      result = getManagedObject(app, context, path, names);
     } catch (AuthorizationException e) {
-      Message msg =
-          ERR_DSCFG_ERROR_DELETE_AUTHZ.get(relation.getUserFriendlyName());
-      throw new ClientException(LDAPResultCode.INSUFFICIENT_ACCESS_RIGHTS,
-          msg);
+      Message msg = ERR_DSCFG_ERROR_DELETE_AUTHZ.get(relation
+          .getUserFriendlyName());
+      throw new ClientException(LDAPResultCode.INSUFFICIENT_ACCESS_RIGHTS, msg);
     } catch (DefinitionDecodingException e) {
       Message ufn = path.getManagedObjectDefinition().getUserFriendlyName();
       Message msg = ERR_DSCFG_ERROR_GET_PARENT_DDE.get(ufn, ufn, ufn);
@@ -206,95 +219,124 @@
       Message msg = ERR_DSCFG_ERROR_GET_PARENT_MODE.get(ufn);
       throw new ClientException(LDAPResultCode.OPERATIONS_ERROR, msg);
     } catch (CommunicationException e) {
-      Message msg = ERR_DSCFG_ERROR_DELETE_CE.get(
-          relation.getUserFriendlyName(), e.getMessage());
+      Message msg = ERR_DSCFG_ERROR_DELETE_CE.get(relation
+          .getUserFriendlyName(), e.getMessage());
       throw new ClientException(LDAPResultCode.CLIENT_SIDE_SERVER_DOWN, msg);
     } catch (ConcurrentModificationException e) {
-      Message msg =
-          ERR_DSCFG_ERROR_DELETE_CME.get(relation.getUserFriendlyName());
-      throw new ClientException(LDAPResultCode.CONSTRAINT_VIOLATION,
-          msg);
+      Message msg = ERR_DSCFG_ERROR_DELETE_CME.get(relation
+          .getUserFriendlyName());
+      throw new ClientException(LDAPResultCode.CONSTRAINT_VIOLATION, msg);
     } catch (ManagedObjectNotFoundException e) {
       // Ignore the error if the deletion is being forced.
       if (!forceArgument.isPresent()) {
         Message ufn = path.getManagedObjectDefinition().getUserFriendlyName();
         Message msg = ERR_DSCFG_ERROR_GET_PARENT_MONFE.get(ufn);
         throw new ClientException(LDAPResultCode.NO_SUCH_OBJECT, msg);
+      } else {
+        return MenuResult.success(0);
       }
     }
 
-    if (parent != null) {
-      try {
-        if (relation instanceof InstantiableRelationDefinition) {
-          InstantiableRelationDefinition<?, ?> irelation =
-            (InstantiableRelationDefinition<?, ?>) relation;
-          String childName = names.get(names.size() - 1);
-          if (childName == null) {
-            childName = readChildName(parent, irelation, null);
-          }
-
-          if (confirmDeletion()) {
-            parent.removeChild(irelation, childName);
-          } else {
-            return 1;
-          }
-        } else if (relation instanceof OptionalRelationDefinition) {
-          OptionalRelationDefinition<?, ?> orelation =
-            (OptionalRelationDefinition<?, ?>) relation;
-
-          if (confirmDeletion()) {
-            parent.removeChild(orelation);
-          } else {
-            return 1;
-          }
-        }
-      } catch (AuthorizationException e) {
-        Message msg =
-            ERR_DSCFG_ERROR_DELETE_AUTHZ.get(relation.getUserFriendlyName());
-        throw new ClientException(LDAPResultCode.INSUFFICIENT_ACCESS_RIGHTS,
-            msg);
-      } catch (OperationRejectedException e) {
-        Message msg = ERR_DSCFG_ERROR_DELETE_ORE.get(
-            relation.getUserFriendlyName(), e.getMessage());
-        throw new ClientException(LDAPResultCode.CONSTRAINT_VIOLATION, msg);
-      } catch (ManagedObjectNotFoundException e) {
-        // Ignore the error if the deletion is being forced.
-        if (!forceArgument.isPresent()) {
-          Message msg =
-              ERR_DSCFG_ERROR_DELETE_MONFE.get(relation.getUserFriendlyName());
-          throw new ClientException(LDAPResultCode.NO_SUCH_OBJECT, msg);
-        }
-      } catch (ConcurrentModificationException e) {
-        Message msg =
-            ERR_DSCFG_ERROR_DELETE_CME.get(relation.getUserFriendlyName());
-        throw new ClientException(LDAPResultCode.CONSTRAINT_VIOLATION, msg);
-      } catch (CommunicationException e) {
-        Message msg = ERR_DSCFG_ERROR_DELETE_CE.get(
-            relation.getUserFriendlyName(), e.getMessage());
-        throw new ClientException(LDAPResultCode.CLIENT_SIDE_SERVER_DOWN,
-            msg);
+    if (result.isQuit()) {
+      if (!app.isMenuDrivenMode()) {
+        // User chose to cancel deletion.
+        Message msg = INFO_DSCFG_CONFIRM_DELETE_FAIL.get(relation
+            .getUserFriendlyName());
+        app.printVerboseMessage(msg);
       }
+      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) {
+        InstantiableRelationDefinition<?, ?> irelation =
+          (InstantiableRelationDefinition<?, ?>) relation;
+        String childName = names.get(names.size() - 1);
+
+        if (childName == null) {
+          MenuResult<String> sresult =
+            readChildName(app, parent, irelation, null);
+
+          if (sresult.isQuit()) {
+            if (!app.isMenuDrivenMode()) {
+              // User chose to cancel deletion.
+              Message msg = INFO_DSCFG_CONFIRM_DELETE_FAIL.get(relation
+                  .getUserFriendlyName());
+              app.printVerboseMessage(msg);
+            }
+            return MenuResult.quit();
+          } else if (sresult.isCancel()) {
+            // Must be menu driven, so no need for error message.
+            return MenuResult.cancel();
+          } else {
+            childName = sresult.getValue();
+          }
+        }
+
+        if (confirmDeletion(app)) {
+          parent.removeChild(irelation, childName);
+        } else {
+          return MenuResult.cancel();
+        }
+      } else if (relation instanceof OptionalRelationDefinition) {
+        OptionalRelationDefinition<?, ?> orelation =
+          (OptionalRelationDefinition<?, ?>) relation;
+
+        if (confirmDeletion(app)) {
+          parent.removeChild(orelation);
+        } else {
+          return MenuResult.cancel();
+        }
+      }
+    } catch (AuthorizationException e) {
+      Message msg = ERR_DSCFG_ERROR_DELETE_AUTHZ.get(relation
+          .getUserFriendlyName());
+      throw new ClientException(LDAPResultCode.INSUFFICIENT_ACCESS_RIGHTS, msg);
+    } catch (OperationRejectedException e) {
+      Message msg = ERR_DSCFG_ERROR_DELETE_ORE.get(relation
+          .getUserFriendlyName(), e.getMessage());
+      throw new ClientException(LDAPResultCode.CONSTRAINT_VIOLATION, msg);
+    } catch (ManagedObjectNotFoundException e) {
+      // Ignore the error if the deletion is being forced.
+      if (!forceArgument.isPresent()) {
+        Message msg = ERR_DSCFG_ERROR_DELETE_MONFE.get(relation
+            .getUserFriendlyName());
+        throw new ClientException(LDAPResultCode.NO_SUCH_OBJECT, msg);
+      }
+    } catch (ConcurrentModificationException e) {
+      Message msg = ERR_DSCFG_ERROR_DELETE_CME.get(relation
+          .getUserFriendlyName());
+      throw new ClientException(LDAPResultCode.CONSTRAINT_VIOLATION, msg);
+    } catch (CommunicationException e) {
+      Message msg = ERR_DSCFG_ERROR_DELETE_CE.get(relation
+          .getUserFriendlyName(), e.getMessage());
+      throw new ClientException(LDAPResultCode.CLIENT_SIDE_SERVER_DOWN, msg);
     }
 
     // Output success message.
-    Message msg =
-        INFO_DSCFG_CONFIRM_DELETE_SUCCESS.get(relation.getUserFriendlyName());
-    getConsoleApplication().printVerboseMessage(msg);
+    Message msg = INFO_DSCFG_CONFIRM_DELETE_SUCCESS.get(relation
+        .getUserFriendlyName());
+    app.printVerboseMessage(msg);
 
-    return 0;
+    return MenuResult.success(0);
   }
 
 
 
   // Confirm deletion.
-  private boolean confirmDeletion() throws ArgumentException {
-    Message prompt =
-        INFO_DSCFG_CONFIRM_DELETE.get(relation.getUserFriendlyName());
-    if (!getConsoleApplication().confirmAction(prompt)) {
+  private boolean confirmDeletion(ConsoleApplication app) throws CLIException {
+    Message prompt = INFO_DSCFG_CONFIRM_DELETE.get(relation
+        .getUserFriendlyName());
+    app.println();
+    if (!app.confirmAction(prompt, false)) {
       // Output failure message.
-      Message msg =
-          INFO_DSCFG_CONFIRM_DELETE_FAIL.get(relation.getUserFriendlyName());
-      getConsoleApplication().printVerboseMessage(msg);
+      Message msg = INFO_DSCFG_CONFIRM_DELETE_FAIL.get(relation
+          .getUserFriendlyName());
+      app.printVerboseMessage(msg);
       return false;
     } else {
       return true;
diff --git a/opends/src/server/org/opends/server/tools/dsconfig/GetPropSubCommandHandler.java b/opends/src/server/org/opends/server/tools/dsconfig/GetPropSubCommandHandler.java
index ad325ce..ac1049b 100644
--- a/opends/src/server/org/opends/server/tools/dsconfig/GetPropSubCommandHandler.java
+++ b/opends/src/server/org/opends/server/tools/dsconfig/GetPropSubCommandHandler.java
@@ -25,11 +25,10 @@
  *      Portions Copyright 2007 Sun Microsystems, Inc.
  */
 package org.opends.server.tools.dsconfig;
-import org.opends.messages.Message;
 
 
 
-import static org.opends.messages.ToolMessages.*;
+import static org.opends.messages.DSConfigMessages.*;
 
 import java.io.PrintStream;
 import java.util.Collection;
@@ -38,6 +37,7 @@
 import java.util.Set;
 import java.util.SortedSet;
 
+import org.opends.messages.Message;
 import org.opends.server.admin.AbsoluteInheritedDefaultBehaviorProvider;
 import org.opends.server.admin.AliasDefaultBehaviorProvider;
 import org.opends.server.admin.DefaultBehaviorProviderVisitor;
@@ -59,12 +59,16 @@
 import org.opends.server.admin.client.ConcurrentModificationException;
 import org.opends.server.admin.client.ManagedObject;
 import org.opends.server.admin.client.ManagedObjectDecodingException;
+import org.opends.server.admin.client.ManagementContext;
 import org.opends.server.protocols.ldap.LDAPResultCode;
 import org.opends.server.tools.ClientException;
 import org.opends.server.util.args.ArgumentException;
 import org.opends.server.util.args.StringArgument;
 import org.opends.server.util.args.SubCommand;
 import org.opends.server.util.args.SubCommandArgumentParser;
+import org.opends.server.util.cli.CLIException;
+import org.opends.server.util.cli.ConsoleApplication;
+import org.opends.server.util.cli.MenuResult;
 import org.opends.server.util.table.TableBuilder;
 import org.opends.server.util.table.TablePrinter;
 import org.opends.server.util.table.TextTablePrinter;
@@ -83,8 +87,6 @@
    * Creates a new get-xxx-prop sub-command for an instantiable
    * relation.
    *
-   * @param app
-   *          The console application.
    * @param parser
    *          The sub-command argument parser.
    * @param path
@@ -95,10 +97,10 @@
    * @throws ArgumentException
    *           If the sub-command could not be created successfully.
    */
-  public static GetPropSubCommandHandler create(ConsoleApplication app,
+  public static GetPropSubCommandHandler create(
       SubCommandArgumentParser parser, ManagedObjectPath<?, ?> path,
       InstantiableRelationDefinition<?, ?> r) throws ArgumentException {
-    return new GetPropSubCommandHandler(app, parser, path.child(r, "DUMMY"), r);
+    return new GetPropSubCommandHandler(parser, path.child(r, "DUMMY"), r);
   }
 
 
@@ -106,8 +108,6 @@
   /**
    * Creates a new get-xxx-prop sub-command for an optional relation.
    *
-   * @param app
-   *          The console application.
    * @param parser
    *          The sub-command argument parser.
    * @param path
@@ -118,10 +118,10 @@
    * @throws ArgumentException
    *           If the sub-command could not be created successfully.
    */
-  public static GetPropSubCommandHandler create(ConsoleApplication app,
+  public static GetPropSubCommandHandler create(
       SubCommandArgumentParser parser, ManagedObjectPath<?, ?> path,
       OptionalRelationDefinition<?, ?> r) throws ArgumentException {
-    return new GetPropSubCommandHandler(app, parser, path.child(r), r);
+    return new GetPropSubCommandHandler(parser, path.child(r), r);
   }
 
 
@@ -129,8 +129,6 @@
   /**
    * Creates a new get-xxx-prop sub-command for a singleton relation.
    *
-   * @param app
-   *          The console application.
    * @param parser
    *          The sub-command argument parser.
    * @param path
@@ -141,10 +139,10 @@
    * @throws ArgumentException
    *           If the sub-command could not be created successfully.
    */
-  public static GetPropSubCommandHandler create(ConsoleApplication app,
+  public static GetPropSubCommandHandler create(
       SubCommandArgumentParser parser, ManagedObjectPath<?, ?> path,
       SingletonRelationDefinition<?, ?> r) throws ArgumentException {
-    return new GetPropSubCommandHandler(app, parser, path.child(r), r);
+    return new GetPropSubCommandHandler(parser, path.child(r), r);
   }
 
   // The sub-commands naming arguments.
@@ -159,17 +157,15 @@
 
 
   // Private constructor.
-  private GetPropSubCommandHandler(ConsoleApplication app,
+  private GetPropSubCommandHandler(
       SubCommandArgumentParser parser, ManagedObjectPath<?, ?> path,
       RelationDefinition<?, ?> r) throws ArgumentException {
-    super(app);
-
     this.path = path;
 
     // Create the sub-command.
     String name = "get-" + r.getName() + "-prop";
-    Message message = INFO_DSCFG_DESCRIPTION_SUBCMD_GETPROP.get(
-            r.getChildDefinition().getUserFriendlyName());
+    Message 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.
@@ -190,6 +186,19 @@
 
 
   /**
+   * 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
@@ -203,47 +212,51 @@
    * {@inheritDoc}
    */
   @Override
-  public int run() throws ArgumentException, ClientException {
+  public MenuResult<Integer> run(ConsoleApplication app,
+      ManagementContextFactory factory) throws ArgumentException,
+      ClientException, CLIException {
     // Get the property names.
     Set<String> propertyNames = getPropertyNames();
     PropertyValuePrinter valuePrinter = new PropertyValuePrinter(getSizeUnit(),
-        getTimeUnit(), getConsoleApplication().isScriptFriendly());
+        getTimeUnit(), app.isScriptFriendly());
 
     // Get the naming argument values.
-    List<String> names = getNamingArgValues(namingArgs);
+    List<String> names = getNamingArgValues(app, namingArgs);
 
     // Get the targeted managed object.
-    ManagedObject<?> child;
+    Message ufn = path.getRelationDefinition().getUserFriendlyName();
+    ManagementContext context = factory.getManagementContext(app);
+    MenuResult<ManagedObject<?>> result;
     try {
-      child = getManagedObject(path, names);
+      result = getManagedObject(app, context, path, names);
     } catch (AuthorizationException e) {
-      Message ufn = path.getManagedObjectDefinition().getUserFriendlyName();
       Message msg = ERR_DSCFG_ERROR_GET_CHILD_AUTHZ.get(ufn);
-      throw new ClientException(LDAPResultCode.INSUFFICIENT_ACCESS_RIGHTS,
-          msg);
+      throw new ClientException(LDAPResultCode.INSUFFICIENT_ACCESS_RIGHTS, msg);
     } catch (DefinitionDecodingException e) {
-      Message ufn = path.getManagedObjectDefinition().getUserFriendlyName();
       Message msg = ERR_DSCFG_ERROR_GET_CHILD_DDE.get(ufn, ufn, ufn);
       throw new ClientException(LDAPResultCode.OPERATIONS_ERROR, msg);
     } catch (ManagedObjectDecodingException e) {
-      Message ufn = path.getManagedObjectDefinition().getUserFriendlyName();
       Message msg = ERR_DSCFG_ERROR_GET_CHILD_MODE.get(ufn);
       throw new ClientException(LDAPResultCode.OPERATIONS_ERROR, msg);
     } catch (CommunicationException e) {
-      Message ufn = path.getManagedObjectDefinition().getUserFriendlyName();
       Message msg = ERR_DSCFG_ERROR_GET_CHILD_CE.get(ufn, e.getMessage());
       throw new ClientException(LDAPResultCode.CLIENT_SIDE_SERVER_DOWN, msg);
     } catch (ConcurrentModificationException e) {
-      Message ufn = path.getManagedObjectDefinition().getUserFriendlyName();
       Message msg = ERR_DSCFG_ERROR_GET_CHILD_CME.get(ufn);
       throw new ClientException(LDAPResultCode.CONSTRAINT_VIOLATION, msg);
     } catch (ManagedObjectNotFoundException e) {
-      Message ufn = path.getManagedObjectDefinition().getUserFriendlyName();
-      Message msg = ERR_DSCFG_ERROR_GET_CHILD_MONFE.get(ufn);
+       Message msg = ERR_DSCFG_ERROR_GET_CHILD_MONFE.get(ufn);
       throw new ClientException(LDAPResultCode.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()) {
@@ -274,12 +287,12 @@
       }
 
       if (propertyNames.isEmpty() || propertyNames.contains(pd.getName())) {
-        displayProperty(builder, child, pd, valuePrinter);
+        displayProperty(app, builder, child, pd, valuePrinter);
       }
     }
 
-    PrintStream out = getConsoleApplication().getOutputStream();
-    if (getConsoleApplication().isScriptFriendly()) {
+    PrintStream out = app.getOutputStream();
+    if (app.isScriptFriendly()) {
       TablePrinter printer = createScriptFriendlyTablePrinter(out);
       builder.print(printer);
     } else {
@@ -289,14 +302,15 @@
       builder.print(printer);
     }
 
-    return 0;
+    return MenuResult.success(0);
   }
 
 
 
   // Display the set of values associated with a property.
-  private <T> void displayProperty(TableBuilder builder, ManagedObject<?> mo,
-      PropertyDefinition<T> pd, PropertyValuePrinter valuePrinter) {
+  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
@@ -314,7 +328,7 @@
 
 
         public Message visitAlias(AliasDefaultBehaviorProvider<T> d, Void p) {
-          if (getConsoleApplication().isVerbose()) {
+          if (app.isVerbose()) {
             return d.getSynopsis();
           } else {
             return null;
@@ -352,7 +366,7 @@
 
       Message content = pd.getDefaultBehaviorProvider().accept(visitor, null);
       if (content == null) {
-        if (getConsoleApplication().isScriptFriendly()) {
+        if (app.isScriptFriendly()) {
           builder.appendCell();
         } else {
           builder.appendCell("-");
@@ -371,7 +385,7 @@
         builder.startRow();
         builder.appendCell(pd.getName());
 
-        if (getConsoleApplication().isScriptFriendly()) {
+        if (app.isScriptFriendly()) {
           for (T value : values) {
             builder.appendCell(valuePrinter.print(pd, value));
           }
diff --git a/opends/src/server/org/opends/server/tools/dsconfig/HelpSubCommandHandler.java b/opends/src/server/org/opends/server/tools/dsconfig/HelpSubCommandHandler.java
index 5ea9b41..1fe6c6e 100644
--- a/opends/src/server/org/opends/server/tools/dsconfig/HelpSubCommandHandler.java
+++ b/opends/src/server/org/opends/server/tools/dsconfig/HelpSubCommandHandler.java
@@ -26,16 +26,14 @@
  */
 package org.opends.server.tools.dsconfig;
 
-import org.opends.messages.MessageBuilder;
-import org.opends.messages.Message;
 
 
-import static org.opends.messages.ToolMessages.*;
+import static org.opends.messages.DSConfigMessages.*;
+import static org.opends.messages.UtilityMessages.*;
 import static org.opends.server.util.ServerConstants.*;
-import static org.opends.server.util.StaticUtils.*;
 
 import java.io.PrintStream;
-import java.util.Arrays;
+import java.util.Collection;
 import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.LinkedList;
@@ -45,6 +43,8 @@
 import java.util.TreeMap;
 import java.util.TreeSet;
 
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
 import org.opends.server.admin.AbsoluteInheritedDefaultBehaviorProvider;
 import org.opends.server.admin.AbstractManagedObjectDefinition;
 import org.opends.server.admin.AdministratorAction;
@@ -67,6 +67,10 @@
 import org.opends.server.util.args.StringArgument;
 import org.opends.server.util.args.SubCommand;
 import org.opends.server.util.args.SubCommandArgumentParser;
+import org.opends.server.util.cli.CLIException;
+import org.opends.server.util.cli.ConsoleApplication;
+import org.opends.server.util.cli.MenuResult;
+import org.opends.server.util.cli.OutputStreamConsoleApplication;
 import org.opends.server.util.table.TableBuilder;
 import org.opends.server.util.table.TablePrinter;
 import org.opends.server.util.table.TextTablePrinter;
@@ -101,9 +105,9 @@
       public Message visitAbsoluteInherited(
           AbsoluteInheritedDefaultBehaviorProvider<T> d,
           PropertyDefinition<T> p) {
-        return INFO_DSCFG_HELP_FIELD_INHERITED_ABS.get(d
-            .getPropertyName(), d.getManagedObjectPath()
-            .getRelationDefinition().getUserFriendlyName());
+        return INFO_DSCFG_HELP_FIELD_INHERITED_ABS.get(d.getPropertyName(), d
+            .getManagedObjectPath().getRelationDefinition()
+            .getUserFriendlyName());
       }
 
 
@@ -148,13 +152,12 @@
           RelativeInheritedDefaultBehaviorProvider<T> d,
           PropertyDefinition<T> p) {
         if (d.getRelativeOffset() == 0) {
-          return INFO_DSCFG_HELP_FIELD_INHERITED_THIS.get(d
-              .getPropertyName(), d.getManagedObjectDefinition()
-              .getUserFriendlyName());
+          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());
+          return INFO_DSCFG_HELP_FIELD_INHERITED_PARENT.get(
+              d.getPropertyName(), d.getManagedObjectDefinition()
+                  .getUserFriendlyName());
         }
       }
 
@@ -298,7 +301,7 @@
        * {@inheritDoc}
        */
       @Override
-      public Void visitUnknown(PropertyDefinition<?> d, PrintStream p)
+      public <T> Void visitUnknown(PropertyDefinition<T> d, PrintStream p)
           throws UnknownPropertyDefinitionException {
         PropertyDefinitionUsageBuilder usageBuilder =
           new PropertyDefinitionUsageBuilder(true);
@@ -367,44 +370,41 @@
   private final static 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 short option inherited.
-   */
-  private static final Character OPTION_DSCFG_SHORT_INHERITED = null;
-
-  /**
    * The value for the long option type.
    */
   private static final String OPTION_DSCFG_LONG_TYPE = "type";
 
   /**
-   * The value for the short option type.
-   */
-  private static final Character OPTION_DSCFG_SHORT_TYPE = 't';
-
-  /**
-   * The value for the long option category.
-   */
-  private static final String OPTION_DSCFG_LONG_CATEGORY = "category";
-
-  /**
    * 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());
+    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;
   }
 
@@ -413,17 +413,79 @@
   /**
    * Creates a new help-properties sub-command.
    *
-   * @param app
-   *          The console application.
    * @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(ConsoleApplication app,
-      SubCommandArgumentParser parser) throws ArgumentException {
-    return new HelpSubCommandHandler(app, parser);
+  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 d
+   *          The managed object definition.
+   * @param c
+   *          The collection of properties to be displayed.
+   */
+  public static void displaySingleComponent(ConsoleApplication app,
+      AbstractManagedObjectDefinition<?, ?> d,
+      Collection<PropertyDefinition<?>> c) {
+    // Display the title.
+    app.println(INFO_DSCFG_HELP_HEADING_COMPONENT.get(d.getUserFriendlyName()));
+
+    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.
+    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 (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.
+      PropertyDefinitionUsageBuilder v = new PropertyDefinitionUsageBuilder(
+          false);
+      builder.appendCell(v.getUsage(pd));
+    }
+
+    TablePrinter printer = new TextTablePrinter(app.getErrorStream());
+    builder.print(printer);
   }
 
 
@@ -432,35 +494,35 @@
    * 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.
-   * @param out
-   *          The output stream.
    */
-  public static void displayVerboseSingleProperty(
-      AbstractManagedObjectDefinition<?, ?> d, String name, PrintStream out) {
+  public static void displayVerboseSingleProperty(ConsoleApplication app,
+      AbstractManagedObjectDefinition<?, ?> d, String name) {
     PropertyDefinition<?> pd = d.getPropertyDefinition(name);
 
     // Display the title.
-    out.println(INFO_DSCFG_HELP_HEADING_PROPERTY.get(name));
+    app.println(INFO_DSCFG_HELP_HEADING_PROPERTY.get(name));
 
     // Display the property synopsis and description.
-    out.println();
-    out.println(wrapText(pd.getSynopsis(), MAX_LINE_WIDTH));
+    app.println();
+    app.println(pd.getSynopsis(), 4);
     if (pd.getDescription() != null) {
-      out.println();
-      out.println(wrapText(pd.getDescription(), MAX_LINE_WIDTH));
+      app.println();
+      app.println(pd.getDescription(), 4);
     }
 
     // Display the syntax.
-    out.println();
+    app.println();
     SyntaxPrinter syntaxPrinter = new SyntaxPrinter();
-    syntaxPrinter.print(out, pd);
+    syntaxPrinter.print(app.getErrorStream(), pd);
 
     // Display remaining information in a table.
-    out.println();
+    app.println();
     TableBuilder builder = new TableBuilder();
 
     // Display the default behavior.
@@ -476,27 +538,27 @@
     builder.appendCell(INFO_DSCFG_HELP_HEADING_ADVANCED.get());
     builder.appendCell(HEADING_SEPARATOR);
     if (pd.hasOption(PropertyOption.ADVANCED)) {
-      builder.appendCell(INFO_DSCFG_GENERAL_CONFIRM_YES.get());
+      builder.appendCell(INFO_GENERAL_YES.get());
     } else {
-      builder.appendCell(INFO_DSCFG_GENERAL_CONFIRM_NO.get());
+      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_DSCFG_GENERAL_CONFIRM_YES.get());
+      builder.appendCell(INFO_GENERAL_YES.get());
     } else {
-      builder.appendCell(INFO_DSCFG_GENERAL_CONFIRM_NO.get());
+      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_DSCFG_GENERAL_CONFIRM_YES.get());
+      builder.appendCell(INFO_GENERAL_YES.get());
     } else {
-      builder.appendCell(INFO_DSCFG_GENERAL_CONFIRM_NO.get());
+      builder.appendCell(INFO_GENERAL_NO.get());
     }
 
     builder.startRow();
@@ -508,10 +570,10 @@
       builder.appendCell(INFO_DSCFG_HELP_FIELD_READ_ONLY.get(d
           .getUserFriendlyName()));
     } else {
-      builder.appendCell(INFO_DSCFG_GENERAL_CONFIRM_NO.get());
+      builder.appendCell(INFO_GENERAL_NO.get());
     }
 
-    TextTablePrinter factory = new TextTablePrinter(out);
+    TextTablePrinter factory = new TextTablePrinter(app.getErrorStream());
     factory.setDisplayHeadings(false);
     factory.setColumnWidth(0, HEADING_WIDTH);
     factory.setColumnWidth(2, 0);
@@ -524,8 +586,8 @@
     if (synopsis == null) {
       switch (action.getType()) {
       case COMPONENT_RESTART:
-        synopsis = INFO_DSCFG_HELP_FIELD_COMPONENT_RESTART.get(
-            d.getUserFriendlyName());
+        synopsis = INFO_DSCFG_HELP_FIELD_COMPONENT_RESTART.get(d
+            .getUserFriendlyName());
         break;
       case SERVER_RESTART:
         synopsis = INFO_DSCFG_HELP_FIELD_SERVER_RESTART.get();
@@ -537,42 +599,110 @@
     }
 
     if (synopsis != null) {
-      out.println();
-      out.println(wrapText(synopsis, MAX_LINE_WIDTH));
+      app.println();
+      app.println(synopsis);
     }
   }
 
-  // The sub-command associated with this handler.
-  private final SubCommand subCommand;
+
+
+  // Displays the property option summary key.
+  private static void displayPropertyOptionKey(ConsoleApplication app) {
+    MessageBuilder builder;
+
+    app.println(INFO_DSCFG_HELP_DESCRIPTION_OPTION.get());
+    app.println();
+
+    builder = new MessageBuilder();
+    builder.append(" r -- ");
+    builder.append(INFO_DSCFG_HELP_DESCRIPTION_READ.get());
+    app.println(builder.toMessage());
+
+    builder = new MessageBuilder();
+    builder.append(" w -- ");
+    builder.append(INFO_DSCFG_HELP_DESCRIPTION_WRITE.get());
+    app.println(builder.toMessage());
+
+    builder = new MessageBuilder();
+    builder.append(" m -- ");
+    builder.append(INFO_DSCFG_HELP_DESCRIPTION_MANDATORY.get());
+    app.println(builder.toMessage());
+
+    builder = new MessageBuilder();
+    builder.append(" s -- ");
+    builder.append(INFO_DSCFG_HELP_DESCRIPTION_SINGLE_VALUED.get());
+    app.println(builder.toMessage());
+
+    builder = new MessageBuilder();
+    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;
 
-  //The argument which should be used to display inherited properties.
+  // 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;
 
-  // A table listing all the available types of managed object indexed
-  // on their parent type.
-  private final Map<String,
-    Map<String, AbstractManagedObjectDefinition<?, ?>>> categoryMap;
-
-  // A table listing all the available types of managed object indexed
-  // on their tag(s).
-  private final Map<Tag,
-    Map<String, AbstractManagedObjectDefinition<?, ?>>> tagMap;
-
 
 
   // Private constructor.
-  private HelpSubCommandHandler(ConsoleApplication app,
-      SubCommandArgumentParser parser) throws ArgumentException {
-    super(app);
-
+  private HelpSubCommandHandler(SubCommandArgumentParser parser)
+      throws ArgumentException {
     // Create the sub-command.
     String name = "list-properties";
     Message desc = INFO_DSCFG_DESCRIPTION_SUBCMD_HELPPROP.get();
@@ -580,8 +710,8 @@
 
     this.categoryArgument = new StringArgument(OPTION_DSCFG_LONG_CATEGORY,
         OPTION_DSCFG_SHORT_CATEGORY, OPTION_DSCFG_LONG_CATEGORY, false, false,
-        true, "{CATEGORY}", null, null,
-        INFO_DSCFG_DESCRIPTION_HELP_CATEGORY.get());
+        true, "{CATEGORY}", null, null, INFO_DSCFG_DESCRIPTION_HELP_CATEGORY
+            .get());
     this.subCommand.addArgument(this.categoryArgument);
 
     this.typeArgument = new StringArgument(OPTION_DSCFG_LONG_TYPE,
@@ -591,8 +721,8 @@
 
     this.inheritedModeArgument = new BooleanArgument(
         OPTION_DSCFG_LONG_INHERITED, OPTION_DSCFG_SHORT_INHERITED,
-        OPTION_DSCFG_LONG_INHERITED,
-        INFO_DSCFG_DESCRIPTION_HELP_INHERITED.get());
+        OPTION_DSCFG_LONG_INHERITED, INFO_DSCFG_DESCRIPTION_HELP_INHERITED
+            .get());
     subCommand.addArgument(inheritedModeArgument);
 
     // Register common arguments.
@@ -600,10 +730,10 @@
         INFO_DSCFG_DESCRIPTION_ADVANCED_HELP.get());
     registerPropertyNameArgument(this.subCommand);
 
-    this.categoryMap = new TreeMap<String,
-      Map<String, AbstractManagedObjectDefinition<?, ?>>>();
-    this.tagMap = new HashMap<Tag,
-      Map<String, AbstractManagedObjectDefinition<?, ?>>>();
+    this.categoryMap =
+      new TreeMap<String, Map<String, AbstractManagedObjectDefinition<?, ?>>>();
+    this.tagMap =
+      new HashMap<Tag, Map<String, AbstractManagedObjectDefinition<?, ?>>>();
   }
 
 
@@ -676,7 +806,9 @@
    * {@inheritDoc}
    */
   @Override
-  public int run() throws ArgumentException, ClientException {
+  public MenuResult<Integer> run(ConsoleApplication app,
+      ManagementContextFactory factory) throws ArgumentException,
+      ClientException, CLIException {
     String categoryName = categoryArgument.getValue();
     String typeName = typeArgument.getValue();
     Tag tag = null;
@@ -774,36 +906,26 @@
       }
     }
 
-    if (!getConsoleApplication().isVerbose()) {
-      displayNonVerbose(categoryName, typeName, tag, propertyNames);
+    // Output everything to the output stream.
+    app = new OutputStreamConsoleApplication(app);
+    if (!app.isVerbose()) {
+      displayNonVerbose(app, categoryName, typeName, tag, propertyNames);
     } else {
-      displayVerbose(categoryName, typeName, tag, propertyNames);
+      displayVerbose(app, categoryName, typeName, tag, propertyNames);
     }
 
-    return 0;
+    return MenuResult.success(0);
   }
 
 
 
   // Output property summary table.
-  private void displayNonVerbose(String categoryName, String typeName,
-      Tag tag, Set<String> propertyNames) {
-    PrintStream out = getConsoleApplication().getOutputStream();
-    if (!getConsoleApplication().isScriptFriendly()) {
-      out.println(INFO_DSCFG_HELP_DESCRIPTION_OPTION.get());
-      out.println();
-      out.print(" r -- ");
-      out.println(INFO_DSCFG_HELP_DESCRIPTION_READ.get());
-      out.print(" w -- ");
-      out.println(INFO_DSCFG_HELP_DESCRIPTION_WRITE.get());
-      out.print(" m -- ");
-      out.println(INFO_DSCFG_HELP_DESCRIPTION_MANDATORY.get());
-      out.print(" s -- ");
-      out.println(INFO_DSCFG_HELP_DESCRIPTION_SINGLE_VALUED.get());
-      out.print(" a -- ");
-      out.println(INFO_DSCFG_HELP_DESCRIPTION_ADMIN_ACTION.get());
-      out.println();
-      out.println();
+  private void displayNonVerbose(ConsoleApplication app, String categoryName,
+      String typeName, Tag tag, Set<String> propertyNames) {
+    if (!app.isScriptFriendly()) {
+      displayPropertyOptionKey(app);
+      app.println();
+      app.println();
     }
 
     // Headings.
@@ -894,10 +1016,10 @@
     }
 
     TablePrinter printer;
-    if (getConsoleApplication().isScriptFriendly()) {
-      printer = createScriptFriendlyTablePrinter(out);
+    if (app.isScriptFriendly()) {
+      printer = createScriptFriendlyTablePrinter(app.getOutputStream());
     } else {
-      printer = new TextTablePrinter(out);
+      printer = new TextTablePrinter(app.getOutputStream());
     }
     builder.print(printer);
   }
@@ -905,15 +1027,22 @@
 
 
   // Display detailed help on managed objects and their properties.
-  private void displayVerbose(String categoryName, String typeName,
-      Tag tag, Set<String> propertyNames) {
-    PrintStream out = getConsoleApplication().getOutputStream();
-
+  private void displayVerbose(ConsoleApplication app, String categoryName,
+      String typeName, Tag tag, Set<String> propertyNames) {
     // Construct line used to separate consecutive sections.
-    char[] c1 = new char[MAX_LINE_WIDTH];
-    Arrays.fill(c1, '=');
-    char[] c2 = new char[MAX_LINE_WIDTH];
-    Arrays.fill(c2, '-');
+    MessageBuilder mb;
+
+    mb = new MessageBuilder();
+    for (int i = 0; i < MAX_LINE_WIDTH; i++) {
+      mb.append('=');
+    }
+    Message c1 = mb.toMessage();
+
+    mb = new MessageBuilder();
+    for (int i = 0; i < MAX_LINE_WIDTH; i++) {
+      mb.append('-');
+    }
+    Message c2 = mb.toMessage();
 
     // Display help for each managed object.
     boolean isFirstManagedObject = true;
@@ -973,67 +1102,33 @@
             // managed
             // object.
             if (!isFirstManagedObject) {
-              out.println();
-              out.println(c1);
-              out.println();
+              app.println();
+              app.println(c1);
+              app.println();
             } else {
               isFirstManagedObject = false;
             }
 
             // Display the title.
-            out.println(wrapText(INFO_DSCFG_HELP_HEADING_COMPONENT.get(
-                    mod.getUserFriendlyName()), MAX_LINE_WIDTH));
+            app.println(INFO_DSCFG_HELP_HEADING_COMPONENT.get(mod
+                .getUserFriendlyName()));
 
-            out.println();
-            out.println(wrapText(mod.getSynopsis(), MAX_LINE_WIDTH));
+            app.println();
+            app.println(mod.getSynopsis());
             if (mod.getDescription() != null) {
-              out.println();
-              out.println(wrapText(mod.getDescription(), MAX_LINE_WIDTH));
+              app.println();
+              app.println(mod.getDescription());
             }
           }
 
-          out.println();
-          out.println(c2);
-          out.println();
+          app.println();
+          app.println(c2);
+          app.println();
 
-          displayVerboseSingleProperty(mod, pd.getName(), out);
+          displayVerboseSingleProperty(app, mod, pd.getName());
           isFirstProperty = false;
         }
       }
     }
   }
-
-
-
-  // Compute the options field.
-  private 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();
-  }
 }
diff --git a/opends/src/server/org/opends/server/tools/dsconfig/InternalManagementContextFactory.java b/opends/src/server/org/opends/server/tools/dsconfig/InternalManagementContextFactory.java
index 27874ed..64b9a82 100644
--- a/opends/src/server/org/opends/server/tools/dsconfig/InternalManagementContextFactory.java
+++ b/opends/src/server/org/opends/server/tools/dsconfig/InternalManagementContextFactory.java
@@ -32,6 +32,7 @@
 import org.opends.server.tools.ClientException;
 import org.opends.server.util.args.ArgumentException;
 import org.opends.server.util.args.SubCommandArgumentParser;
+import org.opends.server.util.cli.ConsoleApplication;
 
 
 
diff --git a/opends/src/server/org/opends/server/tools/dsconfig/LDAPManagementContextFactory.java b/opends/src/server/org/opends/server/tools/dsconfig/LDAPManagementContextFactory.java
index ae00d60..d8ce86f 100644
--- a/opends/src/server/org/opends/server/tools/dsconfig/LDAPManagementContextFactory.java
+++ b/opends/src/server/org/opends/server/tools/dsconfig/LDAPManagementContextFactory.java
@@ -25,13 +25,17 @@
  *      Portions Copyright 2007 Sun Microsystems, Inc.
  */
 package org.opends.server.tools.dsconfig;
-import org.opends.messages.Message;
 
 
 
+import static org.opends.messages.DSConfigMessages.*;
 import static org.opends.messages.ToolMessages.*;
 import static org.opends.server.tools.ToolConstants.*;
 
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+import org.opends.messages.Message;
 import org.opends.server.admin.client.AuthenticationException;
 import org.opends.server.admin.client.AuthenticationNotSupportedException;
 import org.opends.server.admin.client.CommunicationException;
@@ -46,6 +50,9 @@
 import org.opends.server.util.args.IntegerArgument;
 import org.opends.server.util.args.StringArgument;
 import org.opends.server.util.args.SubCommandArgumentParser;
+import org.opends.server.util.cli.CLIException;
+import org.opends.server.util.cli.ConsoleApplication;
+import org.opends.server.util.cli.ValidationCallback;
 
 
 
@@ -98,14 +105,132 @@
       throws ArgumentException, ClientException {
     // Lazily create the LDAP management context.
     if (context == null) {
+      boolean isHeadingDisplayed = false;
+
       // Get the LDAP host.
       String hostName = hostArgument.getValue();
+      final String tmpHostName = hostName;
+      if (app.isInteractive() && !hostArgument.isPresent()) {
+        if (!isHeadingDisplayed) {
+          app.println();
+          app.println();
+          app.println(INFO_DSCFG_HEADING_CONNECTION_PARAMETERS.get());
+          isHeadingDisplayed = true;
+        }
+
+        ValidationCallback<String> callback = new ValidationCallback<String>() {
+
+          public String validate(ConsoleApplication app, String input)
+              throws CLIException {
+            String ninput = input.trim();
+            if (ninput.length() == 0) {
+              return tmpHostName;
+            } else {
+              try {
+                InetAddress.getByName(ninput);
+                return ninput;
+              } catch (UnknownHostException e) {
+                // Try again...
+                app.println();
+                app.println(ERR_DSCFG_BAD_HOST_NAME.get(ninput));
+                app.println();
+                return null;
+              }
+            }
+          }
+
+        };
+
+        try {
+          app.println();
+          hostName = app.readValidatedInput(INFO_DSCFG_PROMPT_HOST_NAME
+              .get(hostName), callback);
+        } catch (CLIException e) {
+          throw ArgumentExceptionFactory.unableToReadConnectionParameters(e);
+        }
+      }
 
       // Get the LDAP port.
       int portNumber = portArgument.getIntValue();
+      final int tmpPortNumber = portNumber;
+      if (app.isInteractive() && !portArgument.isPresent()) {
+        if (!isHeadingDisplayed) {
+          app.println();
+          app.println();
+          app.println(INFO_DSCFG_HEADING_CONNECTION_PARAMETERS.get());
+          isHeadingDisplayed = true;
+        }
+
+        ValidationCallback<Integer> callback =
+          new ValidationCallback<Integer>() {
+
+          public Integer validate(ConsoleApplication app, String input)
+              throws CLIException {
+            String ninput = input.trim();
+            if (ninput.length() == 0) {
+              return tmpPortNumber;
+            } else {
+              try {
+                int i = Integer.parseInt(ninput);
+                if (i < 1 || i > 65535) {
+                  throw new NumberFormatException();
+                }
+                return i;
+              } catch (NumberFormatException e) {
+                // Try again...
+                app.println();
+                app.println(ERR_DSCFG_BAD_PORT_NUMBER.get(ninput));
+                app.println();
+                return null;
+              }
+            }
+          }
+
+        };
+
+        try {
+          app.println();
+          portNumber = app.readValidatedInput(INFO_DSCFG_PROMPT_PORT_NUMBER
+              .get(portNumber), callback);
+        } catch (CLIException e) {
+          throw ArgumentExceptionFactory.unableToReadConnectionParameters(e);
+        }
+      }
 
       // Get the LDAP bind credentials.
       String bindDN = bindDNArgument.getValue();
+      final String tmpBindDN = bindDN;
+      if (app.isInteractive() && !bindDNArgument.isPresent()) {
+        if (!isHeadingDisplayed) {
+          app.println();
+          app.println();
+          app.println(INFO_DSCFG_HEADING_CONNECTION_PARAMETERS.get());
+          isHeadingDisplayed = true;
+        }
+
+        ValidationCallback<String> callback = new ValidationCallback<String>() {
+
+          public String validate(ConsoleApplication app, String input)
+              throws CLIException {
+            String ninput = input.trim();
+            if (ninput.length() == 0) {
+              return tmpBindDN;
+            } else {
+              return ninput;
+            }
+          }
+
+        };
+
+        try {
+          app.println();
+          bindDN = app.readValidatedInput(
+              INFO_DSCFG_PROMPT_BIND_DN.get(bindDN), callback);
+        } catch (CLIException e) {
+          throw ArgumentExceptionFactory.unableToReadConnectionParameters(e);
+        }
+      }
+
       String bindPassword = bindPasswordArgument.getValue();
 
       if (bindPasswordFileArgument.isPresent()) {
@@ -122,11 +247,19 @@
               .unableToReadBindPasswordInteractively();
         }
 
+        if (!isHeadingDisplayed) {
+          app.println();
+          app.println();
+          app.println(INFO_DSCFG_HEADING_CONNECTION_PARAMETERS.get());
+          isHeadingDisplayed = true;
+        }
+
         try {
+          app.println();
           Message prompt = INFO_LDAPAUTH_PASSWORD_PROMPT.get(bindDN);
           bindPassword = app.readPassword(prompt);
         } catch (Exception e) {
-          throw ArgumentExceptionFactory.unableToReadBindPassword(e);
+          throw ArgumentExceptionFactory.unableToReadConnectionParameters(e);
         }
       }
 
@@ -143,9 +276,8 @@
         Message message = ERR_DSCFG_ERROR_LDAP_SIMPLE_BIND_FAILED.get(bindDN);
         throw new ClientException(LDAPResultCode.INVALID_CREDENTIALS, message);
       } catch (CommunicationException e) {
-        Message message =
-            ERR_DSCFG_ERROR_LDAP_FAILED_TO_CONNECT.get(
-                    hostName, String.valueOf(portNumber));
+        Message message = ERR_DSCFG_ERROR_LDAP_FAILED_TO_CONNECT.get(hostName,
+            String.valueOf(portNumber));
         throw new ClientException(LDAPResultCode.CLIENT_SIDE_CONNECT_ERROR,
             message);
       }
@@ -200,9 +332,8 @@
     // arguments.
     if (bindPasswordArgument.isPresent()
         && bindPasswordFileArgument.isPresent()) {
-      Message message = ERR_TOOL_CONFLICTING_ARGS.
-          get(bindPasswordArgument.getLongIdentifier(),
-              bindPasswordFileArgument.getLongIdentifier());
+      Message message = ERR_TOOL_CONFLICTING_ARGS.get(bindPasswordArgument
+          .getLongIdentifier(), bindPasswordFileArgument.getLongIdentifier());
       throw new ArgumentException(message);
     }
   }
diff --git a/opends/src/server/org/opends/server/tools/dsconfig/ListSubCommandHandler.java b/opends/src/server/org/opends/server/tools/dsconfig/ListSubCommandHandler.java
index 9181105..63df154 100644
--- a/opends/src/server/org/opends/server/tools/dsconfig/ListSubCommandHandler.java
+++ b/opends/src/server/org/opends/server/tools/dsconfig/ListSubCommandHandler.java
@@ -25,11 +25,10 @@
  *      Portions Copyright 2007 Sun Microsystems, Inc.
  */
 package org.opends.server.tools.dsconfig;
-import org.opends.messages.Message;
 
 
 
-import static org.opends.messages.ToolMessages.*;
+import static org.opends.messages.DSConfigMessages.*;
 
 import java.io.PrintStream;
 import java.util.List;
@@ -38,6 +37,7 @@
 import java.util.SortedSet;
 import java.util.TreeMap;
 
+import org.opends.messages.Message;
 import org.opends.server.admin.DefinitionDecodingException;
 import org.opends.server.admin.InstantiableRelationDefinition;
 import org.opends.server.admin.ManagedObjectDefinition;
@@ -51,12 +51,16 @@
 import org.opends.server.admin.client.ConcurrentModificationException;
 import org.opends.server.admin.client.ManagedObject;
 import org.opends.server.admin.client.ManagedObjectDecodingException;
+import org.opends.server.admin.client.ManagementContext;
 import org.opends.server.protocols.ldap.LDAPResultCode;
 import org.opends.server.tools.ClientException;
 import org.opends.server.util.args.ArgumentException;
 import org.opends.server.util.args.StringArgument;
 import org.opends.server.util.args.SubCommand;
 import org.opends.server.util.args.SubCommandArgumentParser;
+import org.opends.server.util.cli.CLIException;
+import org.opends.server.util.cli.ConsoleApplication;
+import org.opends.server.util.cli.MenuResult;
 import org.opends.server.util.table.TableBuilder;
 import org.opends.server.util.table.TablePrinter;
 import org.opends.server.util.table.TextTablePrinter;
@@ -74,8 +78,6 @@
   /**
    * Creates a new list-xxx sub-command for an instantiable relation.
    *
-   * @param app
-   *          The console application.
    * @param parser
    *          The sub-command argument parser.
    * @param p
@@ -86,10 +88,10 @@
    * @throws ArgumentException
    *           If the sub-command could not be created successfully.
    */
-  public static ListSubCommandHandler create(ConsoleApplication app,
+  public static ListSubCommandHandler create(
       SubCommandArgumentParser parser, ManagedObjectPath<?, ?> p,
       InstantiableRelationDefinition<?, ?> r) throws ArgumentException {
-    return new ListSubCommandHandler(app, parser, p, r, r.getPluralName(), r
+    return new ListSubCommandHandler(parser, p, r, r.getPluralName(), r
         .getUserFriendlyPluralName());
   }
 
@@ -98,8 +100,6 @@
   /**
    * Creates a new list-xxx sub-command for an optional relation.
    *
-   * @param app
-   *          The console application.
    * @param parser
    *          The sub-command argument parser.
    * @param p
@@ -110,10 +110,10 @@
    * @throws ArgumentException
    *           If the sub-command could not be created successfully.
    */
-  public static ListSubCommandHandler create(ConsoleApplication app,
+  public static ListSubCommandHandler create(
       SubCommandArgumentParser parser, ManagedObjectPath<?, ?> p,
       OptionalRelationDefinition<?, ?> r) throws ArgumentException {
-    return new ListSubCommandHandler(app, parser, p, r, r.getName(), r
+    return new ListSubCommandHandler(parser, p, r, r.getName(), r
         .getUserFriendlyName());
   }
 
@@ -132,12 +132,10 @@
 
 
   // Private constructor.
-  private ListSubCommandHandler(ConsoleApplication app,
+  private ListSubCommandHandler(
       SubCommandArgumentParser parser, ManagedObjectPath<?, ?> p,
       RelationDefinition<?, ?> r, String rname, Message rufn)
       throws ArgumentException {
-    super(app);
-
     this.path = p;
     this.relation = r;
 
@@ -161,6 +159,19 @@
 
 
   /**
+   * 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
@@ -174,8 +185,9 @@
    * {@inheritDoc}
    */
   @Override
-  public int run()
-      throws ArgumentException, ClientException {
+  public MenuResult<Integer> run(ConsoleApplication app,
+      ManagementContextFactory factory) throws ArgumentException,
+      ClientException, CLIException {
     // Get the property names.
     Set<String> propertyNames = getPropertyNames();
 
@@ -186,10 +198,10 @@
     }
 
     PropertyValuePrinter valuePrinter = new PropertyValuePrinter(getSizeUnit(),
-        getTimeUnit(), getConsoleApplication().isScriptFriendly());
+        getTimeUnit(), app.isScriptFriendly());
 
     // Get the naming argument values.
-    List<String> names = getNamingArgValues(namingArgs);
+    List<String> names = getNamingArgValues(app, namingArgs);
 
     Message ufn;
     if (relation instanceof InstantiableRelationDefinition) {
@@ -201,20 +213,19 @@
     }
 
     // List the children.
-    ManagedObject<?> parent;
+    ManagementContext context = factory.getManagementContext(app);
+    MenuResult<ManagedObject<?>> result;
     try {
-      parent = getManagedObject(path, names);
+      result = getManagedObject(app, context, path, names);
     } catch (AuthorizationException e) {
       Message msg = ERR_DSCFG_ERROR_LIST_AUTHZ.get(ufn);
       throw new ClientException(LDAPResultCode.INSUFFICIENT_ACCESS_RIGHTS,
           msg);
     } catch (DefinitionDecodingException e) {
-
       ufn = path.getManagedObjectDefinition().getUserFriendlyName();
       Message msg = ERR_DSCFG_ERROR_GET_PARENT_DDE.get(ufn, ufn, ufn);
       throw new ClientException(LDAPResultCode.OPERATIONS_ERROR, msg);
     } catch (ManagedObjectDecodingException e) {
-
       ufn = path.getManagedObjectDefinition().getUserFriendlyName();
       Message msg = ERR_DSCFG_ERROR_GET_PARENT_MODE.get(ufn);
       throw new ClientException(LDAPResultCode.OPERATIONS_ERROR, msg);
@@ -225,12 +236,18 @@
       Message msg = ERR_DSCFG_ERROR_LIST_CME.get(ufn);
       throw new ClientException(LDAPResultCode.CONSTRAINT_VIOLATION, msg);
     } catch (ManagedObjectNotFoundException e) {
-
       ufn = path.getManagedObjectDefinition().getUserFriendlyName();
       Message msg = ERR_DSCFG_ERROR_GET_PARENT_MONFE.get(ufn);
       throw new ClientException(LDAPResultCode.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) {
@@ -301,11 +318,10 @@
     }
 
     // Output the results.
-    if (getConsoleApplication().isScriptFriendly()) {
+    if (app.isScriptFriendly()) {
       // Output just the names of the children.
-      PrintStream out = getConsoleApplication().getOutputStream();
       for (String name : children.keySet()) {
-        out.println(name);
+        app.println(Message.raw(name));
       }
     } else {
       // Create a table of their properties.
@@ -313,8 +329,6 @@
       builder.appendHeading(relation.getUserFriendlyName());
       builder
           .appendHeading(INFO_DSCFG_HEADING_COMPONENT_TYPE.get());
-      if (!propertyNames.isEmpty()) {
-      }
       for (String propertyName : propertyNames) {
         builder.appendHeading(Message.raw(propertyName));
       }
@@ -346,11 +360,11 @@
         for (String propertyName : propertyNames) {
           try {
             PropertyDefinition<?> pd = d.getPropertyDefinition(propertyName);
-            displayProperty(builder, child, pd, valuePrinter);
+            displayProperty(app, builder, child, pd, valuePrinter);
           } catch (IllegalArgumentException e) {
             // Assume this child managed object does not support this
             // property.
-            if (getConsoleApplication().isScriptFriendly()) {
+            if (app.isScriptFriendly()) {
               builder.appendCell();
             } else {
               builder.appendCell("-");
@@ -359,28 +373,35 @@
         }
       }
 
-      PrintStream out = getConsoleApplication().getOutputStream();
-      if (getConsoleApplication().isScriptFriendly()) {
+      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(":");
         builder.print(printer);
       }
     }
 
-    return 0;
+    return MenuResult.success(0);
   }
 
 
 
   // Display the set of values associated with a property.
-  private <T> void displayProperty(TableBuilder builder, ManagedObject<?> mo,
-      PropertyDefinition<T> pd, PropertyValuePrinter valuePrinter) {
+  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 (getConsoleApplication().isScriptFriendly()) {
+      if (app.isScriptFriendly()) {
         builder.appendCell();
       } else {
         builder.appendCell("-");
diff --git a/opends/src/server/org/opends/server/tools/dsconfig/ManagementContextFactory.java b/opends/src/server/org/opends/server/tools/dsconfig/ManagementContextFactory.java
index 05aa008..c1c2669 100644
--- a/opends/src/server/org/opends/server/tools/dsconfig/ManagementContextFactory.java
+++ b/opends/src/server/org/opends/server/tools/dsconfig/ManagementContextFactory.java
@@ -32,6 +32,7 @@
 import org.opends.server.tools.ClientException;
 import org.opends.server.util.args.ArgumentException;
 import org.opends.server.util.args.SubCommandArgumentParser;
+import org.opends.server.util.cli.ConsoleApplication;
 
 
 
diff --git a/opends/src/server/org/opends/server/tools/dsconfig/PropertyValueEditor.java b/opends/src/server/org/opends/server/tools/dsconfig/PropertyValueEditor.java
new file mode 100644
index 0000000..d1ec4c7
--- /dev/null
+++ b/opends/src/server/org/opends/server/tools/dsconfig/PropertyValueEditor.java
@@ -0,0 +1,1888 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.tools.dsconfig;
+
+
+
+import static org.opends.messages.DSConfigMessages.*;
+
+import java.util.ArrayList;
+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.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
+import org.opends.server.admin.AbsoluteInheritedDefaultBehaviorProvider;
+import org.opends.server.admin.AbstractManagedObjectDefinition;
+import org.opends.server.admin.AliasDefaultBehaviorProvider;
+import org.opends.server.admin.BooleanPropertyDefinition;
+import org.opends.server.admin.DefaultBehaviorProviderVisitor;
+import org.opends.server.admin.DefinedDefaultBehaviorProvider;
+import org.opends.server.admin.EnumPropertyDefinition;
+import org.opends.server.admin.IllegalPropertyValueException;
+import org.opends.server.admin.IllegalPropertyValueStringException;
+import org.opends.server.admin.ManagedObjectDefinition;
+import org.opends.server.admin.PropertyDefinition;
+import org.opends.server.admin.PropertyDefinitionUsageBuilder;
+import org.opends.server.admin.PropertyDefinitionVisitor;
+import org.opends.server.admin.PropertyIsMandatoryException;
+import org.opends.server.admin.PropertyIsReadOnlyException;
+import org.opends.server.admin.PropertyIsSingleValuedException;
+import org.opends.server.admin.PropertyOption;
+import org.opends.server.admin.RelativeInheritedDefaultBehaviorProvider;
+import org.opends.server.admin.UndefinedDefaultBehaviorProvider;
+import org.opends.server.admin.UnknownPropertyDefinitionException;
+import org.opends.server.admin.client.ManagedObject;
+import org.opends.server.util.Validator;
+import org.opends.server.util.cli.CLIException;
+import org.opends.server.util.cli.ConsoleApplication;
+import org.opends.server.util.cli.HelpCallback;
+import org.opends.server.util.cli.Menu;
+import org.opends.server.util.cli.MenuBuilder;
+import org.opends.server.util.cli.MenuCallback;
+import org.opends.server.util.cli.MenuResult;
+import org.opends.server.util.table.TableBuilder;
+import org.opends.server.util.table.TextTablePrinter;
+
+
+
+/**
+ * Common methods used for interactively editing properties.
+ */
+public final class PropertyValueEditor {
+
+  /**
+   * 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}
+     */
+    public void display(ConsoleApplication app) {
+      app.println();
+      HelpSubCommandHandler.displaySingleComponent(app, mo
+          .getManagedObjectDefinition(), 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}
+         */
+        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}
+         */
+        public DefaultBehaviorQuery<T> visitAlias(
+            AliasDefaultBehaviorProvider<T> d, PropertyDefinition<T> p) {
+          return new DefaultBehaviorQuery<T>(Type.ALIAS, d.getSynopsis());
+        }
+
+
+
+        /**
+         * {@inheritDoc}
+         */
+        public DefaultBehaviorQuery<T> visitDefined(
+            DefinedDefaultBehaviorProvider<T> d, PropertyDefinition<T> p) {
+          return new DefaultBehaviorQuery<T>(Type.DEFINED, null);
+        }
+
+
+
+        /**
+         * {@inheritDoc}
+         */
+        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}
+         */
+        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 Message aliasDescription;
+
+    // The type of behavior.
+    private final Type type;
+
+
+
+    // Private constructor.
+    private DefaultBehaviorQuery(Type type, Message 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 Message 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>, ManagedObject<?>> {
+
+    // Any exception that was caught during processing.
+    private CLIException e = null;
+
+
+
+    // Private constructor.
+    private MandatoryPropertyInitializer() {
+      // No implementation required.
+    }
+
+
+
+    /**
+     * Read the initial value(s) for a mandatory property.
+     *
+     * @param mo
+     *          The managed object.
+     * @param pd
+     *          The property definition.
+     * @return Returns <code>true</code> if new values were read
+     *         successfully, or <code>false</code> if no values were
+     *         read and the user chose to quit.
+     * @throws CLIException
+     *           If the user input could not be retrieved for some
+     *           reason.
+     */
+    public MenuResult<Void> read(ManagedObject<?> mo, PropertyDefinition<?> pd)
+        throws CLIException {
+      displayPropertyHeader(app, pd);
+
+      MenuResult<Void> result = pd.accept(this, mo);
+
+      if (e != null) {
+        throw e;
+      } else {
+        return result;
+      }
+    }
+
+
+
+    /**
+     * /** {@inheritDoc}
+     */
+    @Override
+    public MenuResult<Void> visitBoolean(BooleanPropertyDefinition d,
+        ManagedObject<?> 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(p
+          .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 {
+          p.setPropertyValue(d, result.getValue());
+          return MenuResult.success();
+        }
+      } catch (CLIException e) {
+        this.e = e;
+        return MenuResult.cancel();
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public <E extends Enum<E>> MenuResult<Void> visitEnum(
+        EnumPropertyDefinition<E> d, ManagedObject<?> p) {
+      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) {
+        Message option = getPropertyValues(d, Collections.singleton(value));
+        builder.addNumberedOption(option, MenuResult.success(value));
+      }
+
+      builder.addHelpOption(new PropertyHelpCallback(p
+          .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 {
+          p.setPropertyValues(d, result.getValues());
+          return MenuResult.success();
+        }
+      } catch (CLIException e) {
+        this.e = e;
+        return MenuResult.cancel();
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public <T> MenuResult<Void> visitUnknown(PropertyDefinition<T> d,
+        ManagedObject<?> p) throws UnknownPropertyDefinitionException {
+      PropertyDefinitionUsageBuilder b = new PropertyDefinitionUsageBuilder(
+          true);
+      app.println();
+      app.println(INFO_EDITOR_HEADING_SYNTAX.get(b.getUsage(d)), 4);
+
+      // Set the new property value(s).
+      try {
+        p.setPropertyValues(d, readPropertyValues(app, p
+            .getManagedObjectDefinition(), d));
+        return MenuResult.success();
+      } catch (CLIException e) {
+        this.e = e;
+        return MenuResult.cancel();
+      }
+    }
+
+  }
+
+
+
+  /**
+   * A menu call-back for editing a modifiable multi-valued property.
+   */
+  private static final class MultiValuedPropertyEditor extends
+      PropertyDefinitionVisitor<MenuResult<Boolean>, ConsoleApplication>
+      implements MenuCallback<Boolean> {
+
+    // Any exception that was caught during processing.
+    private CLIException 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) {
+      Validator.ensureTrue(pd.hasOption(PropertyOption.MULTI_VALUED));
+
+      this.mo = mo;
+      this.pd = pd;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public MenuResult<Boolean> invoke(ConsoleApplication app)
+        throws CLIException {
+      displayPropertyHeader(app, pd);
+
+      MenuResult<Boolean> result = pd.accept(this, app);
+      if (e != null) {
+        throw e;
+      } else {
+        return result;
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public <T extends Enum<T>> MenuResult<Boolean> visitEnum(
+        final EnumPropertyDefinition<T> d, ConsoleApplication app) {
+      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>() {
+
+            public MenuResult<Boolean> invoke(ConsoleApplication app)
+                throws CLIException {
+              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) {
+                Message 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).
+                currentValues.addAll(result.getValues());
+                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>() {
+
+          public MenuResult<Boolean> invoke(ConsoleApplication app)
+              throws CLIException {
+            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) {
+              Message 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).
+              currentValues.removeAll(result.getValues());
+              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,
+        ConsoleApplication app) {
+      PropertyDefinitionUsageBuilder b = new PropertyDefinitionUsageBuilder(
+          true);
+      app.println();
+      app.println(INFO_EDITOR_HEADING_SYNTAX.get(b.getUsage(d)), 4);
+
+      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>() {
+
+          public MenuResult<Boolean> invoke(ConsoleApplication app)
+              throws CLIException {
+            app.println();
+            readPropertyValues(app, mo.getManagedObjectDefinition(), d,
+                currentValues);
+            return MenuResult.success(false);
+          }
+
+        };
+
+        // Create the remove values call-back.
+        MenuCallback<Boolean> removeCallback = new MenuCallback<Boolean>() {
+
+          public MenuResult<Boolean> invoke(ConsoleApplication app)
+              throws CLIException {
+            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) {
+              Message 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).
+              currentValues.removeAll(result.getValues());
+              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> Message 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:
+          Message 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:
+          Message 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> Message 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)) {
+          Message 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 {
+          Message 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();
+        }
+
+        Message option = getKeepDefaultValuesMenuOption(d, defaultValues,
+            oldValues, currentValues);
+        builder.addNumberedOption(option, result);
+        builder.setDefault(Message.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(Message.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.
+      Message resetOption = null;
+      if (!currentValues.equals(defaultValues)) {
+        resetOption = getResetToDefaultValuesMenuOption(d, defaultValues,
+            currentValues);
+      }
+
+      if (!currentValues.isEmpty()) {
+        if (resetOption == null || !defaultValues.isEmpty()) {
+          MenuCallback<Boolean> callback = new MenuCallback<Boolean>() {
+
+            public MenuResult<Boolean> invoke(ConsoleApplication app)
+                throws CLIException {
+              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>() {
+
+          public MenuResult<Boolean> invoke(ConsoleApplication app)
+              throws CLIException {
+            currentValues.clear();
+            currentValues.addAll(defaultValues);
+            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>() {
+
+          public MenuResult<Boolean> invoke(ConsoleApplication app)
+              throws CLIException {
+            currentValues.clear();
+            currentValues.addAll(oldValues);
+            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 (CLIException e) {
+        this.e = e;
+        return null;
+      }
+
+      if (result.isSuccess()) {
+        if (result.getValue() == true) {
+          // Set the new property value(s).
+          mo.setPropertyValues(d, currentValues);
+          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}
+     */
+    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 static final class ReadOnlyPropertyViewer extends
+      PropertyDefinitionVisitor<MenuResult<Boolean>, ConsoleApplication>
+      implements MenuCallback<Boolean> {
+
+    // Any exception that was caught during processing.
+    private CLIException 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) {
+      Validator.ensureTrue(!pd.hasOption(PropertyOption.MULTI_VALUED));
+
+      this.mo = mo;
+      this.pd = pd;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public MenuResult<Boolean> invoke(ConsoleApplication app)
+        throws CLIException {
+      MenuResult<Boolean> result = pd.accept(this, app);
+      if (e != null) {
+        throw e;
+      } else {
+        return result;
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public <T> MenuResult<Boolean> visitUnknown(PropertyDefinition<T> pd,
+        ConsoleApplication app) {
+      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);
+        Message 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:
+        Message 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 (CLIException 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 static final class SingleValuedPropertyEditor extends
+      PropertyDefinitionVisitor<MenuResult<Boolean>, ConsoleApplication>
+      implements MenuCallback<Boolean> {
+
+    // Any exception that was caught during processing.
+    private CLIException 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) {
+      Validator.ensureTrue(!pd.hasOption(PropertyOption.MULTI_VALUED));
+
+      this.mo = mo;
+      this.pd = pd;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public MenuResult<Boolean> invoke(ConsoleApplication app)
+        throws CLIException {
+      displayPropertyHeader(app, pd);
+
+      MenuResult<Boolean> result = pd.accept(this, app);
+      if (e != null) {
+        throw e;
+      } else {
+        return result;
+      }
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public MenuResult<Boolean> visitBoolean(BooleanPropertyDefinition d,
+        ConsoleApplication app) {
+      // 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.
+      Message option = getKeepDefaultValuesMenuOption(d);
+      builder.addNumberedOption(option, MenuResult.<Boolean> cancel());
+      builder.setDefault(Message.raw("1"), MenuResult.<Boolean> cancel());
+
+      // The second (and possibly third) option is to always change
+      // the property's value.
+      if (currentValue == null || currentValue == false) {
+        Message svalue = getPropertyValues(d, Collections.singleton(true));
+
+        if (defaultValue != null && defaultValue == true) {
+          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 == true) {
+        Message svalue = getPropertyValues(d, Collections.singleton(false));
+
+        if (defaultValue != null && defaultValue == false) {
+          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(app, d, builder);
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public <E extends Enum<E>> MenuResult<Boolean> visitEnum(
+        EnumPropertyDefinition<E> d, ConsoleApplication app) {
+      // 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.
+      Message option = getKeepDefaultValuesMenuOption(d);
+      builder.addNumberedOption(option, MenuResult.<E> cancel());
+      builder.setDefault(Message.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;
+        }
+
+        Message 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(app, d, builder);
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public <T> MenuResult<Boolean> visitUnknown(final PropertyDefinition<T> d,
+        ConsoleApplication app) {
+      PropertyDefinitionUsageBuilder b = new PropertyDefinitionUsageBuilder(
+          true);
+      app.println();
+      app.println(INFO_EDITOR_HEADING_SYNTAX.get(b.getUsage(d)), 4);
+
+      // 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.
+      Message option = getKeepDefaultValuesMenuOption(d);
+      builder.addNumberedOption(option, MenuResult.<T> cancel());
+      builder.setDefault(Message.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>() {
+
+            public MenuResult<T> invoke(ConsoleApplication app)
+                throws CLIException {
+              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(app, 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> Message 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)) {
+        Message svalue = getPropertyValues(pd, currentValues);
+        return INFO_EDITOR_OPTION_KEEP_DEFAULT_VALUE.get(svalue);
+      } else if (mo.isPropertyPresent(pd)) {
+        Message 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 {
+          Message 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> Message 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)) {
+          Message svalue = getPropertyValues(pd, defaultValues);
+          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 {
+          Message 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();
+      } else {
+        return null;
+      }
+    }
+
+
+
+    // Common menu processing.
+    private <T> MenuResult<Boolean> runMenu(ConsoleApplication app,
+        final PropertyDefinition<T> d, MenuBuilder<T> builder)
+        throws IllegalPropertyValueException, PropertyIsSingleValuedException,
+        PropertyIsReadOnlyException, PropertyIsMandatoryException,
+        IllegalArgumentException {
+      builder.addHelpOption(new PropertyHelpCallback(mo
+          .getManagedObjectDefinition(), d));
+      builder.addQuitOption();
+
+      Menu<T> menu = builder.toMenu();
+      MenuResult<T> result;
+      try {
+        app.println();
+        result = menu.run();
+      } catch (CLIException e) {
+        this.e = e;
+        return null;
+      }
+
+      if (result.isSuccess()) {
+        // Set the new property value(s).
+        mo.setPropertyValues(d, result.getValues());
+        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.println(pd.getSynopsis(), 4);
+    if (pd.getDescription() != null) {
+      app.println();
+      app.println(pd.getDescription(), 4);
+    }
+  }
+
+
+
+  // Display a table of property values.
+  private static <T> void displayPropertyValues(ConsoleApplication app,
+      PropertyDefinition<T> pd, Collection<T> values)
+      throws IllegalArgumentException {
+    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(INFO_EDITOR_OPTION_VALUES.get(i + 1));
+      builder.appendCell(valuePrinter.print(pd, vl.get(i)));
+
+      if (useMultipleColumns && (j < sz)) {
+        builder.appendCell();
+        builder.appendCell(INFO_EDITOR_OPTION_VALUES.get(j + 1));
+        builder.appendCell(valuePrinter.print(pd, vl.get(j)));
+      }
+    }
+
+    TextTablePrinter printer = new TextTablePrinter(app.getErrorStream());
+    printer.setDisplayHeadings(false);
+    printer.setColumnWidth(0, 2);
+    printer.setColumnWidth(2, 0);
+    if (useMultipleColumns) {
+      printer.setColumnWidth(3, 2);
+      printer.setColumnWidth(5, 0);
+    }
+    builder.print(printer);
+  }
+
+
+
+  // Display the set of values associated with a property.
+  private static <T> Message 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);
+      Message content = query.getAliasDescription();
+      if (content == null) {
+        return Message.raw("-");
+      } else {
+        return content;
+      }
+    } else {
+      PropertyValuePrinter printer =
+        new PropertyValuePrinter(null, null, false);
+      MessageBuilder builder = new MessageBuilder();
+
+      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> Message 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 CLIException {
+    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 CLIException {
+    // Make sure there is at least one value if mandatory and empty.
+    if (values.isEmpty()) {
+      while (true) {
+        try {
+          Message 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) {
+            if (!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 (IllegalPropertyValueStringException 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 {
+          Message 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 (IllegalPropertyValueStringException 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;
+
+
+
+  /**
+   * Create a new property value editor which will read from the
+   * provided application console.
+   *
+   * @param app
+   *          The application console.
+   */
+  public PropertyValueEditor(ConsoleApplication app) {
+    this.app = app;
+  }
+
+
+
+  /**
+   * 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 {@link MenuResult#success()} if the changes made
+   *         to the managed object should be applied, or
+   *         {@link MenuResult#cancel()} if the user to chose to
+   *         cancel any changes, or {@link MenuResult#quit()} if the
+   *         user chose to quit the application.
+   * @throws CLIException
+   *           If the user input could not be retrieved for some
+   *           reason.
+   */
+  public MenuResult<Void> edit(ManagedObject<?> mo,
+      Collection<PropertyDefinition<?>> c, boolean isCreate)
+      throws CLIException {
+    // Get values for this missing mandatory property.
+    MandatoryPropertyInitializer mpi = new MandatoryPropertyInitializer();
+    for (PropertyDefinition<?> pd : c) {
+      if (pd.hasOption(PropertyOption.MANDATORY)) {
+        if (mo.getPropertyValues(pd).isEmpty()) {
+          MenuResult<Void> result = mpi.read(mo, pd);
+          if (!result.isSuccess()) {
+            return result;
+          }
+        }
+      }
+    }
+
+    while (true) {
+      // Construct the main menu.
+      MenuBuilder<Boolean> builder = new MenuBuilder<Boolean>(app);
+
+      Message ufn = mo.getManagedObjectDefinition().getUserFriendlyName();
+      builder.setPrompt(INFO_EDITOR_HEADING_CONFIGURE_COMPONENT.get(ufn));
+
+      Message heading1 = INFO_DSCFG_HEADING_PROPERTY_NAME.get();
+      Message 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.
+        Message values = getPropertyValues(pd, mo);
+        builder.addNumberedOption(Message.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();
+      }
+    }
+  }
+}
diff --git a/opends/src/server/org/opends/server/tools/dsconfig/PropertyValuePrinter.java b/opends/src/server/org/opends/server/tools/dsconfig/PropertyValuePrinter.java
index 9a080e3..35023d8 100644
--- a/opends/src/server/org/opends/server/tools/dsconfig/PropertyValuePrinter.java
+++ b/opends/src/server/org/opends/server/tools/dsconfig/PropertyValuePrinter.java
@@ -29,7 +29,12 @@
 
 
 import java.text.NumberFormat;
+import org.opends.messages.Message;
+import org.opends.messages.MessageBuilder;
 
+import static org.opends.messages.DSConfigMessages.*;
+
+import org.opends.server.admin.BooleanPropertyDefinition;
 import org.opends.server.admin.DurationPropertyDefinition;
 import org.opends.server.admin.DurationUnit;
 import org.opends.server.admin.PropertyDefinition;
@@ -49,7 +54,7 @@
    * Perform property type specific print formatting.
    */
   private static class MyPropertyValueVisitor extends
-      PropertyValueVisitor<String, Void> {
+      PropertyValueVisitor<Message, Void> {
 
     // The requested size unit (null if the property's unit should be
     // used).
@@ -91,31 +96,13 @@
      * {@inheritDoc}
      */
     @Override
-    public String visitDuration(DurationPropertyDefinition d, Long v, Void p) {
-      if (d.getUpperLimit() == null && (v < 0 || v == Long.MAX_VALUE)) {
-        return "unlimited";
+    public Message visitBoolean(BooleanPropertyDefinition d, Boolean v,
+        Void p) {
+      if (v == false) {
+        return INFO_VALUE_FALSE.get();
+      } else {
+        return INFO_VALUE_TRUE.get();
       }
-
-      long ms = d.getBaseUnit().toMilliSeconds(v);
-
-      // Use human-readable string representation by default.
-      if (timeUnit == null && !isScriptFriendly && ms != 0) {
-        return DurationUnit.toString(ms);
-      }
-
-      // Use either the specified unit or the property definition's
-      // base unit.
-      DurationUnit unit = timeUnit;
-      if (unit == null) {
-        unit = d.getBaseUnit();
-      }
-
-      StringBuilder builder = new StringBuilder();
-      builder.append(numberFormat.format(unit.fromMilliSeconds(ms)));
-      builder.append(' ');
-      builder.append(unit.getShortName());
-
-      return builder.toString();
     }
 
 
@@ -124,9 +111,42 @@
      * {@inheritDoc}
      */
     @Override
-    public String visitSize(SizePropertyDefinition d, Long v, Void p) {
+    public Message visitDuration(DurationPropertyDefinition d, Long v, Void p) {
+      if (d.getUpperLimit() == null && (v < 0 || v == Long.MAX_VALUE)) {
+        return INFO_VALUE_UNLIMITED.get();
+      }
+
+      MessageBuilder builder = new MessageBuilder();
+      long ms = d.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 = d.getBaseUnit();
+        }
+
+        builder.append(numberFormat.format(unit.fromMilliSeconds(ms)));
+        builder.append(' ');
+        builder.append(unit.getShortName());
+      }
+
+      return builder.toMessage();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Message visitSize(SizePropertyDefinition d, Long v, Void p) {
       if (d.isAllowUnlimited() && v < 0) {
-        return "unlimited";
+        return INFO_VALUE_UNLIMITED.get();
       }
 
       SizeUnit unit = sizeUnit;
@@ -139,12 +159,12 @@
         }
       }
 
-      StringBuilder builder = new StringBuilder();
+      MessageBuilder builder = new MessageBuilder();
       builder.append(numberFormat.format(unit.fromBytes(v)));
       builder.append(' ');
       builder.append(unit.getShortName());
 
-      return builder.toString();
+      return builder.toMessage();
     }
 
 
@@ -153,18 +173,18 @@
      * {@inheritDoc}
      */
     @Override
-    public <T> String visitUnknown(PropertyDefinition<T> d, T v, Void p) {
+    public <T> Message visitUnknown(PropertyDefinition<T> d, T v, Void p) {
       // For all other property definition types the default encoding
       // will do.
       String s = d.encodeValue(v);
       if (isScriptFriendly) {
-        return s;
+        return Message.raw("%s", s);
       } else if (s.trim().length() == 0 || s.contains(",")) {
         // Quote empty strings or strings containing commas
         // non-scripting mode.
-        return "\"" + s + "\"";
+        return Message.raw("\"%s\"", s);
       } else {
-        return s;
+        return Message.raw("%s", s);
       }
     }
 
@@ -210,7 +230,7 @@
    *         encoded according to the rules of this property value
    *         printer.
    */
-  public <T> String print(PropertyDefinition<T> pd, T value) {
+  public <T> Message print(PropertyDefinition<T> pd, T value) {
     return pd.accept(pimpl, value, null);
   }
 }
diff --git a/opends/src/server/org/opends/server/tools/dsconfig/PropertyValueReader.java b/opends/src/server/org/opends/server/tools/dsconfig/PropertyValueReader.java
deleted file mode 100644
index 2607fb8..0000000
--- a/opends/src/server/org/opends/server/tools/dsconfig/PropertyValueReader.java
+++ /dev/null
@@ -1,630 +0,0 @@
-/*
- * CDDL HEADER START
- *
- * The contents of this file are subject to the terms of the
- * Common Development and Distribution License, Version 1.0 only
- * (the "License").  You may not use this file except in compliance
- * with the License.
- *
- * You can obtain a copy of the license at
- * trunk/opends/resource/legal-notices/OpenDS.LICENSE
- * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
- * See the License for the specific language governing permissions
- * and limitations under the License.
- *
- * When distributing Covered Code, include this CDDL HEADER in each
- * file and include the License file at
- * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
- * add the following below this CDDL HEADER, with the fields enclosed
- * by brackets "[]" replaced with your own identifying information:
- *      Portions Copyright [yyyy] [name of copyright owner]
- *
- * CDDL HEADER END
- *
- *
- *      Portions Copyright 2007 Sun Microsystems, Inc.
- */
-package org.opends.server.tools.dsconfig;
-import org.opends.messages.Message;
-import org.opends.messages.MessageBuilder;
-
-
-import static org.opends.messages.ToolMessages.*;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.EnumSet;
-import java.util.List;
-import java.util.SortedMap;
-import java.util.SortedSet;
-import java.util.TreeMap;
-
-import org.opends.server.admin.AbsoluteInheritedDefaultBehaviorProvider;
-import org.opends.server.admin.AliasDefaultBehaviorProvider;
-import org.opends.server.admin.BooleanPropertyDefinition;
-import org.opends.server.admin.DefaultBehaviorProviderVisitor;
-import org.opends.server.admin.DefinedDefaultBehaviorProvider;
-import org.opends.server.admin.EnumPropertyDefinition;
-import org.opends.server.admin.IllegalPropertyValueStringException;
-import org.opends.server.admin.PropertyDefinition;
-import org.opends.server.admin.PropertyDefinitionVisitor;
-import org.opends.server.admin.PropertyOption;
-import org.opends.server.admin.RelativeInheritedDefaultBehaviorProvider;
-import org.opends.server.admin.UndefinedDefaultBehaviorProvider;
-import org.opends.server.admin.UnknownPropertyDefinitionException;
-import org.opends.server.admin.client.ManagedObject;
-import org.opends.server.tools.ClientException;
-import org.opends.server.util.args.ArgumentException;
-import org.opends.server.util.table.TableBuilder;
-import org.opends.server.util.table.TextTablePrinter;
-
-
-
-/**
- * A class responsible for interactively retrieving property values
- * from the console.
- */
-final class PropertyValueReader {
-
-  /**
-   * A help call-back which displays help on a property.
-   */
-  private static final class PropertyHelpCallback implements HelpCallback {
-
-    // The managed object.
-    private final ManagedObject<?> mo;
-
-    // The property definition.
-    private final PropertyDefinition<?> pd;
-
-
-
-    /**
-     * Creates a new property help call-back.
-     *
-     * @param mo
-     *          The managed object.
-     * @param pd
-     *          The property definition.
-     */
-    public PropertyHelpCallback(ManagedObject<?> mo, PropertyDefinition<?> pd) {
-      this.mo = mo;
-      this.pd = pd;
-    }
-
-
-
-    /**
-     * {@inheritDoc}
-     */
-    public void display(ConsoleApplication app) {
-      app.println();
-      HelpSubCommandHandler.displayVerboseSingleProperty(mo
-          .getManagedObjectDefinition(), pd.getName(), app.getErrorStream());
-    }
-
-  }
-
-
-
-  /**
-   * A menu call-back used for editing property values.
-   */
-  private interface MenuCallback {
-
-    /**
-     * Invoke the menu call-back.
-     *
-     * @param mo
-     *          The managed object.
-     * @param pd
-     *          The property definition to be modified.
-     * @param <T>
-     *          The type of property to be edited.
-     * @throws ArgumentException
-     *           If the user input could not be retrieved for some
-     *           reason.
-     */
-    <T> void invoke(ManagedObject<?> mo, PropertyDefinition<T> pd)
-        throws ArgumentException;
-  }
-
-
-
-  /**
-   * A menu call-back for adding values to a property.
-   */
-  private final class AddValueMenuCallback implements MenuCallback {
-
-    /**
-     * {@inheritDoc}
-     */
-    public <T> void invoke(ManagedObject<?> mo, PropertyDefinition<T> pd)
-        throws ArgumentException {
-      // TODO: display error if the value already exists.
-
-      // TODO: for enumerations, only display the values which are not
-      // already assigned.
-      T value = read(mo, pd);
-      SortedSet<T> values = mo.getPropertyValues(pd);
-      values.add(value);
-      mo.setPropertyValues(pd, values);
-    }
-
-  }
-
-
-
-  /**
-   * A menu call-back for removing values from a property.
-   */
-  private final class RemoveValueMenuCallback implements MenuCallback {
-
-    /**
-     * {@inheritDoc}
-     */
-    public <T> void invoke(ManagedObject<?> mo, PropertyDefinition<T> pd)
-        throws ArgumentException {
-      PropertyValuePrinter printer =
-        new PropertyValuePrinter(null, null, false);
-      SortedSet<T> values = mo.getPropertyValues(pd);
-
-      List<Message> descriptions = new ArrayList<Message>(values.size());
-      List<T> lvalues = new ArrayList<T>(values.size());
-
-      for (T value : values) {
-        descriptions.add(Message.raw(printer.print(pd, value)));
-        lvalues.add(value);
-      }
-
-      Message promptMsg =
-          INFO_DSCFG_VALUE_READER_PROMPT_REMOVE.get(pd.getName());
-      T value = app.readChoice(promptMsg, descriptions, lvalues, null);
-      values.remove(value);
-      mo.setPropertyValues(pd, values);
-    }
-
-  }
-
-
-
-  /**
-   * A menu call-back for resetting a property back to its defaults.
-   */
-  private final class ResetValueMenuCallback implements MenuCallback {
-
-    /**
-     * {@inheritDoc}
-     */
-    public <T> void invoke(ManagedObject<?> mo, PropertyDefinition<T> pd)
-        throws ArgumentException {
-      mo.setPropertyValue(pd, null);
-    }
-
-  }
-
-
-
-  /**
-   * A menu call-back for setting a single-valued property.
-   */
-  private final class SetValueMenuCallback implements MenuCallback {
-
-    /**
-     * {@inheritDoc}
-     */
-    public <T> void invoke(ManagedObject<?> mo, PropertyDefinition<T> pd)
-        throws ArgumentException {
-      T value = read(mo, pd);
-      mo.setPropertyValue(pd, value);
-    }
-
-  }
-
-
-
-  /**
-   * The reader implementation.
-   */
-  private final class Visitor extends PropertyDefinitionVisitor<String, Void> {
-
-    // Any argument exception that was caught during processing.
-    private ArgumentException ae = null;
-
-
-
-    /**
-     * Read a value from the console for the provided property
-     * definition.
-     *
-     * @param pd
-     *          The property definition.
-     * @return Returns the string value.
-     * @throws ArgumentException
-     *           If the user input could not be retrieved for some
-     *           reason.
-     */
-    public String read(PropertyDefinition<?> pd) throws ArgumentException {
-      String result = pd.accept(this, null);
-
-      if (result != null) {
-        return result;
-      } else if (ae != null) {
-        throw ae;
-      } else {
-        throw new IllegalStateException(
-            "No result and no ArgumentException caught");
-      }
-    }
-
-
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public String visitBoolean(BooleanPropertyDefinition d, Void p) {
-      List<String> values = Arrays.asList(new String[] {
-          "false", "true"
-      });
-      List<Message> descriptions = Arrays.asList(new Message[] {
-        Message.raw("false"), Message.raw("true")
-      });
-      try {
-        Message promptMsg = INFO_DSCFG_VALUE_READER_PROMPT_SELECT_VALUE.get(
-                d.getName());
-        return app.readChoice(promptMsg, descriptions, values, null);
-      } catch (ArgumentException e) {
-        ae = e;
-        return null;
-      }
-    }
-
-
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public <E extends Enum<E>> String visitEnum(EnumPropertyDefinition<E> d,
-        Void p) {
-      SortedMap<String, String> map = new TreeMap<String, String>();
-      for (E value : EnumSet.allOf(d.getEnumClass())) {
-        String s = String.format("%s : %s", value.toString(), d
-            .getValueSynopsis(value));
-        map.put(value.toString(), s);
-      }
-
-      List<String> descStrings = new ArrayList<String>(map.values());
-      List<Message> descriptions = new ArrayList<Message>(descStrings.size());
-      for (String s : descStrings) {
-        descriptions.add(Message.raw(s));
-      }
-
-      List<String> values = new ArrayList<String>(map.keySet());
-      try {
-        Message promptMsg =
-                INFO_DSCFG_VALUE_READER_PROMPT_SELECT_VALUE.get(d.getName());
-        return app.readChoice(promptMsg, descriptions, values, null);
-      } catch (ArgumentException e) {
-        ae = e;
-        return null;
-      }
-    }
-
-
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public String visitUnknown(PropertyDefinition<?> d, Void p)
-        throws UnknownPropertyDefinitionException {
-      try {
-        Message promptMsg = INFO_DSCFG_VALUE_READER_PROMPT_ENTER_VALUE.get(
-                d.getName());
-        return app.readLineOfInput(promptMsg);
-      } catch (ArgumentException e) {
-        ae = e;
-        return null;
-      }
-    }
-
-  }
-
-  // The application console.
-  private final ConsoleApplication app;
-
-
-
-  /**
-   * Create a new property value reader which will read from the
-   * provider application console.
-   *
-   * @param app
-   *          The application console.
-   */
-  public PropertyValueReader(ConsoleApplication app) {
-    this.app = app;
-  }
-
-
-
-  /**
-   * Asks the user to input a single value for the provided property
-   * definition. The value will be validated according to the
-   * constraints of the property definition and its decoded value
-   * returned.
-   *
-   * @param <T>
-   *          The underlying type of the property definition.
-   * @param mo
-   *          The managed object.
-   * @param pd
-   *          The property definition.
-   * @return Returns the validated string value.
-   * @throws ArgumentException
-   *           If the user input could not be retrieved for some
-   *           reason.
-   */
-  public <T> T read(ManagedObject<?> mo, PropertyDefinition<T> pd)
-      throws ArgumentException {
-    while (true) {
-      app.println();
-      String value = pd.accept(new Visitor(), null);
-
-      try {
-        return pd.decodeValue(value);
-      } catch (IllegalPropertyValueStringException e) {
-        app.println();
-        app.printMessage(Message.raw(
-                ArgumentExceptionFactory.adaptPropertyException(e,
-            mo.getManagedObjectDefinition()).getMessage()));
-      }
-    }
-  }
-
-
-
-  /**
-   * Edit 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.
-   * @throws ArgumentException
-   *           If the user input could not be retrieved for some
-   *           reason.
-   */
-  public void readAll(ManagedObject<?> mo, Collection<PropertyDefinition<?>> c)
-      throws ArgumentException {
-    // Get values for this missing mandatory property.
-    for (PropertyDefinition<?> pd : c) {
-      if (pd.hasOption(PropertyOption.MANDATORY)) {
-        if (mo.getPropertyValues(pd).isEmpty()) {
-          editProperty(mo, pd);
-        }
-      }
-    }
-
-    // Now let users modify remaining properties.
-    boolean isFinished = false;
-    PropertyValuePrinter valuePrinter = new PropertyValuePrinter(null, null,
-        false);
-    while (!isFinished) {
-      // Display a menu allowing users to edit individual options.
-      TableBuilder builder = new TableBuilder();
-      builder.appendHeading();
-      builder.appendHeading(INFO_DSCFG_HEADING_PROPERTY_NAME.get());
-      builder.appendHeading(INFO_DSCFG_HEADING_PROPERTY_VALUE.get());
-
-      int i = 0;
-      List<PropertyDefinition<?>> pl = new ArrayList<PropertyDefinition<?>>(c);
-      for (PropertyDefinition<?> pd : pl) {
-        builder.startRow();
-        builder.appendCell("[" + i + "]");
-        builder.appendCell(pd.getName());
-
-        Message values = getPropertyValuesAsString(mo, pd, valuePrinter);
-        builder.appendCell(values);
-        i++;
-      }
-
-      builder.startRow();
-      builder.startRow();
-      builder.appendCell("[" + i + "]");
-      builder.appendCell(INFO_DSCFG_VALUE_READER_MENU_CONTINUE.get());
-
-      // Display the menu.
-      app.println();
-      app.printMessage(INFO_DSCFG_VALUE_READER_MENU_TITLE.get(i));
-      app.println();
-
-      TextTablePrinter printer = new TextTablePrinter(app.getErrorStream());
-      printer.setColumnWidth(2, 0);
-      builder.print(printer);
-
-      // Get the user input.
-      final int size = i;
-      Message promptMsg = INFO_DSCFG_GENERAL_CHOICE_PROMPT_NOHELP.get(i);
-      ValidationCallback<Integer> validator =
-        new ValidationCallback<Integer>() {
-
-        public Integer validate(ConsoleApplication app, String input) {
-          String ninput = input.trim();
-
-          try {
-            int j = Integer.parseInt(ninput);
-            if (j < 1 || j > size) {
-              throw new NumberFormatException();
-            }
-            return j;
-          } catch (NumberFormatException e) {
-            app.println();
-            Message errMsg = ERR_DSCFG_ERROR_GENERAL_CHOICE.get(size);
-            app.printMessage(errMsg);
-            return null;
-          }
-        }
-      };
-
-      // Get the choice.
-      int choice;
-      try {
-        choice = app.readValidatedInput(promptMsg, validator);
-      } catch (ClientException e) {
-        // Should never happen.
-        throw new RuntimeException(e);
-      }
-
-      if (choice == size) {
-        isFinished = true;
-      } else {
-        editProperty(mo, pl.get(choice));
-      }
-    }
-  }
-
-
-
-  // Interactively edit a property.
-  private <T> void editProperty(ManagedObject<?> mo, PropertyDefinition<T> pd)
-      throws ArgumentException {
-    // If the property is mandatory then make sure we prompt for an
-    // initial value.
-    if (pd.hasOption(PropertyOption.MANDATORY)) {
-      if (mo.getPropertyValues(pd).isEmpty()) {
-        app.println();
-        Message promptMsg = INFO_DSCFG_VALUE_READER_PROMPT_MANDATORY.get(
-                pd.getName());
-        app.printMessage(promptMsg);
-        T value = read(mo, pd);
-        mo.setPropertyValue(pd, value);
-      }
-    }
-
-    boolean isFinished = false;
-    while (!isFinished) {
-      // Construct a list of menu options and their call-backs.
-      List<Message> descriptions = new ArrayList<Message>();
-      List<MenuCallback> callbacks = new ArrayList<MenuCallback>();
-
-      if (pd.hasOption(PropertyOption.MULTI_VALUED)) {
-        descriptions.add(INFO_DSCFG_VALUE_READER_MENU_ADD.get());
-        callbacks.add(new AddValueMenuCallback());
-
-        if (!mo.getPropertyValues(pd).isEmpty()) {
-          descriptions.add(INFO_DSCFG_VALUE_READER_MENU_REMOVE.get());
-          callbacks.add(new RemoveValueMenuCallback());
-        }
-      } else {
-        descriptions.add(INFO_DSCFG_VALUE_READER_MENU_SET.get());
-        callbacks.add(new SetValueMenuCallback());
-      }
-
-      if (!pd.hasOption(PropertyOption.MANDATORY)
-          || !(pd.getDefaultBehaviorProvider()
-              instanceof UndefinedDefaultBehaviorProvider)) {
-        descriptions.add(INFO_DSCFG_VALUE_READER_MENU_RESET.get());
-        callbacks.add(new ResetValueMenuCallback());
-      }
-
-      descriptions.add(INFO_DSCFG_VALUE_READER_MENU_CONTINUE.get());
-      callbacks.add(null);
-
-      // FIXME: display current values of the property.
-      Message promptMsg = INFO_DSCFG_VALUE_READER_PROMPT_MODIFY_MENU.get(
-              pd.getName());
-      MenuCallback callback = app.readChoice(promptMsg, descriptions,
-          callbacks, new PropertyHelpCallback(mo, pd));
-
-      if (callback != null) {
-        callback.invoke(mo, pd);
-      } else {
-        isFinished = true;
-      }
-    }
-  }
-
-
-
-  // Display the set of values associated with a property.
-  private <T> Message getPropertyValuesAsString(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, Message, Void> visitor =
-        new DefaultBehaviorProviderVisitor<T, Message, Void>() {
-
-        public Message visitAbsoluteInherited(
-            AbsoluteInheritedDefaultBehaviorProvider<T> d, Void p) {
-          // Should not happen - inherited default values are
-          // displayed as normal values.
-          throw new IllegalStateException();
-        }
-
-
-
-        public Message visitAlias(AliasDefaultBehaviorProvider<T> d, Void p) {
-          if (app.isVerbose()) {
-            return d.getSynopsis();
-          } else {
-            return null;
-          }
-        }
-
-
-
-        public Message visitDefined(
-            DefinedDefaultBehaviorProvider<T> d, Void p) {
-          // Should not happen - real default values are displayed as
-          // normal values.
-          throw new IllegalStateException();
-        }
-
-
-
-        public Message visitRelativeInherited(
-            RelativeInheritedDefaultBehaviorProvider<T> d, Void p) {
-          // Should not happen - inherited default values are
-          // displayed as normal values.
-          throw new IllegalStateException();
-        }
-
-
-
-        public Message visitUndefined(UndefinedDefaultBehaviorProvider<T> d,
-            Void p) {
-          return null;
-        }
-      };
-
-      Message content = pd.getDefaultBehaviorProvider().accept(visitor, null);
-      if (content == null) {
-        return Message.raw("-");
-      } else {
-        return content;
-      }
-    } else {
-      MessageBuilder sb = new MessageBuilder();
-      boolean isFirst = true;
-      for (T value : values) {
-        if (!isFirst) {
-          sb.append(", ");
-        }
-        sb.append(valuePrinter.print(pd, value));
-        isFirst = false;
-      }
-
-      return sb.toMessage();
-    }
-  }
-}
diff --git a/opends/src/server/org/opends/server/tools/dsconfig/SetPropSubCommandHandler.java b/opends/src/server/org/opends/server/tools/dsconfig/SetPropSubCommandHandler.java
index a9d9afc..1b9b5c0 100644
--- a/opends/src/server/org/opends/server/tools/dsconfig/SetPropSubCommandHandler.java
+++ b/opends/src/server/org/opends/server/tools/dsconfig/SetPropSubCommandHandler.java
@@ -25,11 +25,10 @@
  *      Portions Copyright 2007 Sun Microsystems, Inc.
  */
 package org.opends.server.tools.dsconfig;
-import org.opends.messages.Message;
 
 
 
-import static org.opends.messages.ToolMessages.*;
+import static org.opends.messages.DSConfigMessages.*;
 
 import java.util.HashMap;
 import java.util.List;
@@ -38,6 +37,7 @@
 import java.util.SortedSet;
 import java.util.TreeSet;
 
+import org.opends.messages.Message;
 import org.opends.server.admin.DefinitionDecodingException;
 import org.opends.server.admin.IllegalPropertyValueStringException;
 import org.opends.server.admin.InstantiableRelationDefinition;
@@ -57,6 +57,7 @@
 import org.opends.server.admin.client.ConcurrentModificationException;
 import org.opends.server.admin.client.ManagedObject;
 import org.opends.server.admin.client.ManagedObjectDecodingException;
+import org.opends.server.admin.client.ManagementContext;
 import org.opends.server.admin.client.MissingMandatoryPropertiesException;
 import org.opends.server.admin.client.OperationRejectedException;
 import org.opends.server.protocols.ldap.LDAPResultCode;
@@ -65,6 +66,9 @@
 import org.opends.server.util.args.StringArgument;
 import org.opends.server.util.args.SubCommand;
 import org.opends.server.util.args.SubCommandArgumentParser;
+import org.opends.server.util.cli.CLIException;
+import org.opends.server.util.cli.ConsoleApplication;
+import org.opends.server.util.cli.MenuResult;
 
 
 
@@ -143,8 +147,6 @@
    * Creates a new set-xxx-prop sub-command for an instantiable
    * relation.
    *
-   * @param app
-   *          The console application.
    * @param parser
    *          The sub-command argument parser.
    * @param path
@@ -155,10 +157,10 @@
    * @throws ArgumentException
    *           If the sub-command could not be created successfully.
    */
-  public static SetPropSubCommandHandler create(ConsoleApplication app,
+  public static SetPropSubCommandHandler create(
       SubCommandArgumentParser parser, ManagedObjectPath<?, ?> path,
       InstantiableRelationDefinition<?, ?> r) throws ArgumentException {
-    return new SetPropSubCommandHandler(app, parser, path.child(r, "DUMMY"), r);
+    return new SetPropSubCommandHandler(parser, path.child(r, "DUMMY"), r);
   }
 
 
@@ -166,8 +168,6 @@
   /**
    * Creates a new set-xxx-prop sub-command for an optional relation.
    *
-   * @param app
-   *          The console application.
    * @param parser
    *          The sub-command argument parser.
    * @param path
@@ -178,10 +178,10 @@
    * @throws ArgumentException
    *           If the sub-command could not be created successfully.
    */
-  public static SetPropSubCommandHandler create(ConsoleApplication app,
+  public static SetPropSubCommandHandler create(
       SubCommandArgumentParser parser, ManagedObjectPath<?, ?> path,
       OptionalRelationDefinition<?, ?> r) throws ArgumentException {
-    return new SetPropSubCommandHandler(app, parser, path.child(r), r);
+    return new SetPropSubCommandHandler(parser, path.child(r), r);
   }
 
 
@@ -189,8 +189,6 @@
   /**
    * Creates a new set-xxx-prop sub-command for a singleton relation.
    *
-   * @param app
-   *          The console application.
    * @param parser
    *          The sub-command argument parser.
    * @param path
@@ -201,10 +199,10 @@
    * @throws ArgumentException
    *           If the sub-command could not be created successfully.
    */
-  public static SetPropSubCommandHandler create(ConsoleApplication app,
+  public static SetPropSubCommandHandler create(
       SubCommandArgumentParser parser, ManagedObjectPath<?, ?> path,
       SingletonRelationDefinition<?, ?> r) throws ArgumentException {
-    return new SetPropSubCommandHandler(app, parser, path.child(r), r);
+    return new SetPropSubCommandHandler(parser, path.child(r), r);
   }
 
   // The sub-commands naming arguments.
@@ -235,17 +233,15 @@
 
 
   // Private constructor.
-  private SetPropSubCommandHandler(ConsoleApplication app,
+  private SetPropSubCommandHandler(
       SubCommandArgumentParser parser, ManagedObjectPath<?, ?> path,
       RelationDefinition<?, ?> r) throws ArgumentException {
-    super(app);
-
     this.path = path;
 
     // Create the sub-command.
     String name = "set-" + r.getName() + "-prop";
-    Message description = INFO_DSCFG_DESCRIPTION_SUBCMD_SETPROP.get(
-      r.getChildDefinition().getUserFriendlyName());
+    Message description = INFO_DSCFG_DESCRIPTION_SUBCMD_SETPROP.get(r
+        .getChildDefinition().getUserFriendlyName());
     this.subCommand = new SubCommand(parser, name, false, 0, 0, null,
         description);
 
@@ -287,6 +283,19 @@
 
 
   /**
+   * 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
@@ -301,44 +310,52 @@
    */
   @SuppressWarnings("unchecked")
   @Override
-  public int run()
-      throws ArgumentException, ClientException {
+  public MenuResult<Integer> run(ConsoleApplication app,
+      ManagementContextFactory factory) throws ArgumentException,
+      ClientException, CLIException {
     // Get the naming argument values.
-    List<String> names = getNamingArgValues(namingArgs);
+    List<String> names = getNamingArgValues(app, namingArgs);
 
-    ManagedObject<?> child;
+    // Get the targeted managed object.
+    Message ufn = path.getRelationDefinition().getUserFriendlyName();
+    ManagementContext context = factory.getManagementContext(app);
+    MenuResult<ManagedObject<?>> result;
     try {
-      child = getManagedObject(path, names);
+      result = getManagedObject(app, context, path, names);
     } catch (AuthorizationException e) {
-      Message ufn = path.getManagedObjectDefinition().getUserFriendlyName();
       Message msg = ERR_DSCFG_ERROR_MODIFY_AUTHZ.get(ufn);
-      throw new ClientException(LDAPResultCode.INSUFFICIENT_ACCESS_RIGHTS,
-          msg);
+      throw new ClientException(LDAPResultCode.INSUFFICIENT_ACCESS_RIGHTS, msg);
     } catch (DefinitionDecodingException e) {
-      Message ufn = path.getManagedObjectDefinition().getUserFriendlyName();
       Message msg = ERR_DSCFG_ERROR_GET_CHILD_DDE.get(ufn, ufn, ufn);
       throw new ClientException(LDAPResultCode.OPERATIONS_ERROR, msg);
     } catch (ManagedObjectDecodingException e) {
       // FIXME: should not abort here. Instead, display the errors (if
       // verbose) and apply the changes to the partial managed object.
-      Message ufn = path.getManagedObjectDefinition().getUserFriendlyName();
       Message msg = ERR_DSCFG_ERROR_GET_CHILD_MODE.get(ufn);
       throw new ClientException(LDAPResultCode.OPERATIONS_ERROR, msg);
     } catch (CommunicationException e) {
-      Message ufn = path.getManagedObjectDefinition().getUserFriendlyName();
       Message msg = ERR_DSCFG_ERROR_MODIFY_CE.get(ufn, e.getMessage());
       throw new ClientException(LDAPResultCode.OPERATIONS_ERROR, msg);
     } catch (ConcurrentModificationException e) {
-      Message ufn = path.getManagedObjectDefinition().getUserFriendlyName();
       Message msg = ERR_DSCFG_ERROR_MODIFY_CME.get(ufn);
-      throw new ClientException(LDAPResultCode.CONSTRAINT_VIOLATION,
-          msg);
+      throw new ClientException(LDAPResultCode.CONSTRAINT_VIOLATION, msg);
     } catch (ManagedObjectNotFoundException e) {
-      Message ufn = path.getManagedObjectDefinition().getUserFriendlyName();
       Message msg = ERR_DSCFG_ERROR_GET_CHILD_MONFE.get(ufn);
       throw new ClientException(LDAPResultCode.NO_SUCH_OBJECT, msg);
     }
 
+    if (result.isQuit()) {
+      if (!app.isMenuDrivenMode()) {
+        // User chose to quit.
+        Message msg = INFO_DSCFG_CONFIRM_MODIFY_FAIL.get(ufn);
+        app.printVerboseMessage(msg);
+      }
+      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>();
@@ -442,8 +459,8 @@
         }
       } else {
         lastModTypes.put(propertyName, ModificationType.REMOVE);
-        modifyPropertyValues(child, pd, changes,
-            ModificationType.REMOVE, value);
+        modifyPropertyValues(child, pd, changes, ModificationType.REMOVE,
+            value);
       }
     }
 
@@ -495,7 +512,7 @@
     }
 
     // Interactively set properties if applicable.
-    if (getConsoleApplication().isInteractive()) {
+    if (app.isInteractive()) {
       SortedSet<PropertyDefinition<?>> properties =
         new TreeSet<PropertyDefinition<?>>();
 
@@ -504,14 +521,6 @@
           continue;
         }
 
-        if (pd.hasOption(PropertyOption.READ_ONLY)) {
-          continue;
-        }
-
-        if (pd.hasOption(PropertyOption.MONITORING)) {
-          continue;
-        }
-
         if (!isAdvancedMode() && pd.hasOption(PropertyOption.ADVANCED)) {
           continue;
         }
@@ -519,54 +528,47 @@
         properties.add(pd);
       }
 
-      PropertyValueReader reader =
-        new PropertyValueReader(getConsoleApplication());
-      reader.readAll(child, properties);
+      PropertyValueEditor editor = new PropertyValueEditor(app);
+      MenuResult<Void> result2 = editor.edit(child, properties, true);
+      if (result2.isQuit()) {
+        if (!app.isMenuDrivenMode()) {
+          // User chose to cancel any changes.
+          Message msg = INFO_DSCFG_CONFIRM_MODIFY_FAIL.get(ufn);
+          app.printVerboseMessage(msg);
+        }
+        return MenuResult.quit();
+      } else if (result2.isCancel()) {
+        return MenuResult.cancel();
+      }
     }
 
     try {
-      // Confirm commit.
-      Message prompt = INFO_DSCFG_CONFIRM_MODIFY.get(d.getUserFriendlyName());
-      if (!getConsoleApplication().confirmAction(prompt)) {
-        // Output failure message.
-        Message msg =
-            INFO_DSCFG_CONFIRM_MODIFY_FAIL.get(d.getUserFriendlyName());
-        getConsoleApplication().printVerboseMessage(msg);
-        return 1;
-      }
-
       child.commit();
 
       // Output success message.
-      Message msg =
-          INFO_DSCFG_CONFIRM_MODIFY_SUCCESS.get(d.getUserFriendlyName());
-      getConsoleApplication().printVerboseMessage(msg);
+      Message msg = INFO_DSCFG_CONFIRM_MODIFY_SUCCESS.get(ufn);
+      app.printVerboseMessage(msg);
     } catch (MissingMandatoryPropertiesException e) {
       throw ArgumentExceptionFactory.adaptMissingMandatoryPropertiesException(
           e, d);
     } catch (AuthorizationException e) {
-      Message msg = ERR_DSCFG_ERROR_MODIFY_AUTHZ.get(d.getUserFriendlyName());
-      throw new ClientException(LDAPResultCode.INSUFFICIENT_ACCESS_RIGHTS,
-          msg);
+      Message msg = ERR_DSCFG_ERROR_MODIFY_AUTHZ.get(ufn);
+      throw new ClientException(LDAPResultCode.INSUFFICIENT_ACCESS_RIGHTS, msg);
     } catch (ConcurrentModificationException e) {
-      Message msg = ERR_DSCFG_ERROR_MODIFY_CME.get(d.getUserFriendlyName());
-      throw new ClientException(LDAPResultCode.CONSTRAINT_VIOLATION,
-          msg);
+      Message msg = ERR_DSCFG_ERROR_MODIFY_CME.get(ufn);
+      throw new ClientException(LDAPResultCode.CONSTRAINT_VIOLATION, msg);
     } catch (OperationRejectedException e) {
-      Message msg = ERR_DSCFG_ERROR_MODIFY_ORE.get(
-          d.getUserFriendlyName(), e.getMessage());
-      throw new ClientException(LDAPResultCode.CONSTRAINT_VIOLATION,
-          msg);
+      Message msg = ERR_DSCFG_ERROR_MODIFY_ORE.get(ufn, e.getMessage());
+      throw new ClientException(LDAPResultCode.CONSTRAINT_VIOLATION, msg);
     } catch (CommunicationException e) {
-      Message msg = ERR_DSCFG_ERROR_MODIFY_CE.get(
-          d.getUserFriendlyName(), e.getMessage());
+      Message msg = ERR_DSCFG_ERROR_MODIFY_CE.get(ufn, e.getMessage());
       throw new ClientException(LDAPResultCode.OPERATIONS_ERROR, msg);
     } catch (ManagedObjectAlreadyExistsException e) {
       // Should never happen.
       throw new IllegalStateException(e);
     }
 
-    return 0;
+    return MenuResult.success(0);
   }
 
 
diff --git a/opends/src/server/org/opends/server/tools/dsconfig/SubCommandBuilder.java b/opends/src/server/org/opends/server/tools/dsconfig/SubCommandBuilder.java
deleted file mode 100644
index 665d789..0000000
--- a/opends/src/server/org/opends/server/tools/dsconfig/SubCommandBuilder.java
+++ /dev/null
@@ -1,304 +0,0 @@
-/*
- * CDDL HEADER START
- *
- * The contents of this file are subject to the terms of the
- * Common Development and Distribution License, Version 1.0 only
- * (the "License").  You may not use this file except in compliance
- * with the License.
- *
- * You can obtain a copy of the license at
- * trunk/opends/resource/legal-notices/OpenDS.LICENSE
- * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
- * See the License for the specific language governing permissions
- * and limitations under the License.
- *
- * When distributing Covered Code, include this CDDL HEADER in each
- * file and include the License file at
- * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
- * add the following below this CDDL HEADER, with the fields enclosed
- * by brackets "[]" replaced with your own identifying information:
- *      Portions Copyright [yyyy] [name of copyright owner]
- *
- * CDDL HEADER END
- *
- *
- *      Portions Copyright 2007 Sun Microsystems, Inc.
- */
-package org.opends.server.tools.dsconfig;
-
-
-
-import java.util.Collection;
-import java.util.LinkedList;
-import java.util.List;
-
-import org.opends.server.admin.AbstractManagedObjectDefinition;
-import org.opends.server.admin.AggregationRelationDefinition;
-import org.opends.server.admin.Configuration;
-import org.opends.server.admin.ConfigurationClient;
-import org.opends.server.admin.InstantiableRelationDefinition;
-import org.opends.server.admin.ManagedObjectPath;
-import org.opends.server.admin.OptionalRelationDefinition;
-import org.opends.server.admin.RelationDefinition;
-import org.opends.server.admin.RelationDefinitionVisitor;
-import org.opends.server.admin.RelationOption;
-import org.opends.server.admin.SingletonRelationDefinition;
-import org.opends.server.util.args.ArgumentException;
-import org.opends.server.util.args.SubCommandArgumentParser;
-
-
-
-/**
- * A relation definition visitor which is used to determine the
- * run-time sub-commands which are available.
- */
-final class SubCommandBuilder {
-
-  /**
-   * A relation definition visitor used to recursively determine the
-   * set of available sub-commands.
-   */
-  private static final class Visitor implements
-      RelationDefinitionVisitor<Void, ManagedObjectPath<?, ?>> {
-
-    // The application.
-    private final ConsoleApplication app;
-
-    // Any exception that occurred whilst creating the sub-commands.
-    private ArgumentException exception = null;
-
-    // The set of available sub-commands.
-    private List<SubCommandHandler> handlers = null;
-
-    // The help sub-command handler.
-    private HelpSubCommandHandler helpHandler = null;
-
-    // The sub-command argument parser.
-    private final SubCommandArgumentParser parser;
-
-    // Private constructor.
-    private Visitor(ConsoleApplication app, SubCommandArgumentParser parser) {
-      this.app = app;
-      this.parser = parser;
-    }
-
-
-
-    /**
-     * Get the constructed list of sub-commands handlers.
-     *
-     * @return Returns the constructed list of sub-commands handlers.
-     * @throws ArgumentException
-     *           If a sub-command could not be created successfully.
-     */
-    public List<SubCommandHandler> getSubCommandHandlers()
-        throws ArgumentException {
-      if (handlers == null) {
-        handlers = new LinkedList<SubCommandHandler>();
-
-        // We always need a help properties sub-command handler.
-        helpHandler = HelpSubCommandHandler.create(app, parser);
-        handlers.add(helpHandler);
-
-        processPath(ManagedObjectPath.emptyPath());
-      }
-
-      if (exception != null) {
-        throw exception;
-      }
-
-      return handlers;
-    }
-
-
-
-    /**
-     * {@inheritDoc}
-     */
-    public Void visitAggregation(AggregationRelationDefinition<?, ?> r,
-        ManagedObjectPath<?, ?> p) {
-      // Do not create sub-commands for aggregations.
-      return null;
-    }
-
-
-
-    /**
-     * {@inheritDoc}
-     */
-    public Void visitInstantiable(InstantiableRelationDefinition<?, ?> r,
-        ManagedObjectPath<?, ?> p) {
-      try {
-        // Create the sub-commands.
-        handlers.add(CreateSubCommandHandler.create(app, parser, p, r));
-        handlers.add(DeleteSubCommandHandler.create(app, parser, p, r));
-        handlers.add(ListSubCommandHandler.create(app, parser, p, r));
-        handlers.add(GetPropSubCommandHandler.create(app, parser, p, r));
-        handlers.add(SetPropSubCommandHandler.create(app, parser, p, r));
-
-        // Process the referenced managed object definition and its
-        // sub-types.
-        processRelation(p, r);
-      } catch (ArgumentException e) {
-        exception = e;
-      }
-
-      return null;
-    }
-
-
-
-    /**
-     * {@inheritDoc}
-     */
-    public Void visitOptional(OptionalRelationDefinition<?, ?> r,
-        ManagedObjectPath<?, ?> p) {
-      try {
-        // Create the sub-commands.
-        handlers.add(CreateSubCommandHandler.create(app, parser, p, r));
-        handlers.add(DeleteSubCommandHandler.create(app, parser, p, r));
-        handlers.add(ListSubCommandHandler.create(app, parser, p, r));
-        handlers.add(GetPropSubCommandHandler.create(app, parser, p, r));
-        handlers.add(SetPropSubCommandHandler.create(app, parser, p, r));
-
-        // Process the referenced managed object definition and its
-        // sub-types.
-        processRelation(p, r);
-      } catch (ArgumentException e) {
-        exception = e;
-      }
-
-      return null;
-    }
-
-
-
-    /**
-     * {@inheritDoc}
-     */
-    public Void visitSingleton(SingletonRelationDefinition<?, ?> r,
-        ManagedObjectPath<?, ?> p) {
-      try {
-        // Create the sub-commands.
-        handlers.add(GetPropSubCommandHandler.create(app, parser, p, r));
-        handlers.add(SetPropSubCommandHandler.create(app, parser, p, r));
-
-        // Process the referenced managed object definition and its
-        // sub-types.
-        processRelation(p, r);
-      } catch (ArgumentException e) {
-        exception = e;
-      }
-
-      return null;
-    }
-
-
-
-    // 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(this, 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 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));
-      }
-    }
-
-  }
-
-
-
-  /**
-   * Create a new sub-command builder.
-   */
-  public SubCommandBuilder() {
-    // No implementation required.
-  }
-
-
-
-  /**
-   * Get the set of sub-command handlers constructed by this builder.
-   *
-   * @param app
-   *          The console application.
-   * @param parser
-   *          The sub-command argument parser.
-   * @return Returns the set of sub-command handlers constructed by
-   *         this builder.
-   * @throws ArgumentException
-   *           If a sub-command could not be created successfully.
-   */
-  public Collection<SubCommandHandler> getSubCommandHandlers(
-      ConsoleApplication app, SubCommandArgumentParser parser)
-      throws ArgumentException {
-    Visitor v = new Visitor(app, parser);
-    return v.getSubCommandHandlers();
-  }
-
-}
diff --git a/opends/src/server/org/opends/server/tools/dsconfig/SubCommandHandler.java b/opends/src/server/org/opends/server/tools/dsconfig/SubCommandHandler.java
index 71ebdbd..61aff03 100644
--- a/opends/src/server/org/opends/server/tools/dsconfig/SubCommandHandler.java
+++ b/opends/src/server/org/opends/server/tools/dsconfig/SubCommandHandler.java
@@ -25,11 +25,10 @@
  *      Portions Copyright 2007 Sun Microsystems, Inc.
  */
 package org.opends.server.tools.dsconfig;
-import org.opends.messages.Message;
 
 
 
-import static org.opends.messages.ToolMessages.*;
+import static org.opends.messages.DSConfigMessages.*;
 
 import java.io.PrintStream;
 import java.util.ArrayList;
@@ -42,6 +41,7 @@
 import java.util.List;
 import java.util.Set;
 
+import org.opends.messages.Message;
 import org.opends.server.admin.AbstractManagedObjectDefinition;
 import org.opends.server.admin.Configuration;
 import org.opends.server.admin.ConfigurationClient;
@@ -69,6 +69,11 @@
 import org.opends.server.util.args.BooleanArgument;
 import org.opends.server.util.args.StringArgument;
 import org.opends.server.util.args.SubCommand;
+import org.opends.server.util.cli.CLIException;
+import org.opends.server.util.cli.ConsoleApplication;
+import org.opends.server.util.cli.Menu;
+import org.opends.server.util.cli.MenuBuilder;
+import org.opends.server.util.cli.MenuResult;
 import org.opends.server.util.table.TabSeparatedTablePrinter;
 import org.opends.server.util.table.TablePrinter;
 
@@ -77,7 +82,7 @@
 /**
  * An interface for sub-command implementations.
  */
-abstract class SubCommandHandler {
+abstract class SubCommandHandler implements Comparable<SubCommandHandler> {
 
   /**
    * A path serializer which is used to retrieve a managed object
@@ -85,9 +90,8 @@
    */
   private class ManagedObjectFinder implements ManagedObjectPathSerializer {
 
-    // Any argument exception that was caught when attempting to find
-    // the managed object.
-    private ArgumentException ae;
+    // The console application.
+    private ConsoleApplication app;
 
     // The index of the next path argument to be retrieved.
     private int argIndex;
@@ -99,23 +103,23 @@
 
     private CommunicationException ce;
 
+    // Any CLI exception that was caught when attempting to find
+    // the managed object.
+    private CLIException clie;
+
     private ConcurrentModificationException cme;
 
     // Any operation exception that was caught when attempting to find
     // the managed object.
     private DefinitionDecodingException dde;
 
-    // Flag indicating whether or not an exception occurred during
-    // retrieval.
-    private boolean gotException;
-
-    // The last managed object retrieved.
-    private ManagedObject<?> managedObject;
-
     private ManagedObjectDecodingException mode;
 
     private ManagedObjectNotFoundException monfe;
 
+    // The current result.
+    private MenuResult<ManagedObject<?>> result;
+
 
 
     /**
@@ -125,7 +129,7 @@
         void appendManagedObjectPathElement(
         InstantiableRelationDefinition<? super C, ? super S> r,
         AbstractManagedObjectDefinition<C, S> d, String name) {
-      if (!gotException) {
+      if (result.isSuccess()) {
         // We should ignore the "template" name here and use a path
         // argument.
         String childName = args.get(argIndex++);
@@ -135,50 +139,61 @@
           // the user choose.
           if (childName == null) {
             try {
-              childName = readChildName(managedObject, r, d);
-            } catch (ArgumentException e) {
-              ae = e;
-              gotException = true;
+              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 (CLIException e) {
+              clie = e;
+              result = MenuResult.quit();
               return;
             }
           } else if (childName.trim().length() == 0) {
             IllegalManagedObjectNameException e =
               new IllegalManagedObjectNameException(childName);
-            ae = ArgumentExceptionFactory
+            clie = ArgumentExceptionFactory
                 .adaptIllegalManagedObjectNameException(e, d);
-            gotException = true;
+            result = MenuResult.quit();
             return;
           }
 
-          ManagedObject<?> child = managedObject.getChild(r, childName);
+          ManagedObject<?> child = result.getValue().getChild(r, childName);
 
           // Check that child is a sub-type of the specified
           // definition.
           if (!child.getManagedObjectDefinition().isChildOf(d)) {
-            ae = ArgumentExceptionFactory.wrongManagedObjectType(r, child
+            clie = ArgumentExceptionFactory.wrongManagedObjectType(r, child
                 .getManagedObjectDefinition());
-            gotException = true;
+            result = MenuResult.quit();
           } else {
-            managedObject = child;
+            result = MenuResult.<ManagedObject<?>>success(child);
           }
         } catch (DefinitionDecodingException e) {
           dde = e;
-          gotException = true;
+          result = MenuResult.quit();
         } catch (ManagedObjectDecodingException e) {
           mode = e;
-          gotException = true;
+          result = MenuResult.quit();
         } catch (AuthorizationException e) {
           authze = e;
-          gotException = true;
+          result = MenuResult.quit();
         } catch (ManagedObjectNotFoundException e) {
           monfe = e;
-          gotException = true;
+          result = MenuResult.quit();
         } catch (ConcurrentModificationException e) {
           cme = e;
-          gotException = true;
+          result = MenuResult.quit();
         } catch (CommunicationException e) {
           ce = e;
-          gotException = true;
+          result = MenuResult.quit();
         }
       }
     }
@@ -192,37 +207,37 @@
         void appendManagedObjectPathElement(
         OptionalRelationDefinition<? super C, ? super S> r,
         AbstractManagedObjectDefinition<C, S> d) {
-      if (!gotException) {
+      if (result.isSuccess()) {
         try {
-          ManagedObject<?> child = managedObject.getChild(r);
+          ManagedObject<?> child = result.getValue().getChild(r);
 
           // Check that child is a sub-type of the specified
           // definition.
           if (!child.getManagedObjectDefinition().isChildOf(d)) {
-            ae = ArgumentExceptionFactory.wrongManagedObjectType(r, child
+            clie = ArgumentExceptionFactory.wrongManagedObjectType(r, child
                 .getManagedObjectDefinition());
-            gotException = true;
+            result = MenuResult.quit();
           } else {
-            managedObject = child;
+            result = MenuResult.<ManagedObject<?>>success(child);
           }
         } catch (DefinitionDecodingException e) {
           dde = e;
-          gotException = true;
+          result = MenuResult.quit();
         } catch (ManagedObjectDecodingException e) {
           mode = e;
-          gotException = true;
+          result = MenuResult.quit();
         } catch (AuthorizationException e) {
           authze = e;
-          gotException = true;
+          result = MenuResult.quit();
         } catch (ManagedObjectNotFoundException e) {
           monfe = e;
-          gotException = true;
+          result = MenuResult.quit();
         } catch (ConcurrentModificationException e) {
           cme = e;
-          gotException = true;
+          result = MenuResult.quit();
         } catch (CommunicationException e) {
           ce = e;
-          gotException = true;
+          result = MenuResult.quit();
         }
       }
     }
@@ -236,37 +251,37 @@
         void appendManagedObjectPathElement(
         SingletonRelationDefinition<? super C, ? super S> r,
         AbstractManagedObjectDefinition<C, S> d) {
-      if (!gotException) {
+      if (result.isSuccess()) {
         try {
-          ManagedObject<?> child = managedObject.getChild(r);
+          ManagedObject<?> child = result.getValue().getChild(r);
 
           // Check that child is a sub-type of the specified
           // definition.
           if (!child.getManagedObjectDefinition().isChildOf(d)) {
-            ae = ArgumentExceptionFactory.wrongManagedObjectType(r, child
+            clie = ArgumentExceptionFactory.wrongManagedObjectType(r, child
                 .getManagedObjectDefinition());
-            gotException = true;
+            result = MenuResult.quit();
           } else {
-            managedObject = child;
+            result = MenuResult.<ManagedObject<?>>success(child);
           }
         } catch (DefinitionDecodingException e) {
           dde = e;
-          gotException = true;
+          result = MenuResult.quit();
         } catch (ManagedObjectDecodingException e) {
           mode = e;
-          gotException = true;
+          result = MenuResult.quit();
         } catch (AuthorizationException e) {
           authze = e;
-          gotException = true;
+          result = MenuResult.quit();
         } catch (ManagedObjectNotFoundException e) {
           monfe = e;
-          gotException = true;
+          result = MenuResult.quit();
         } catch (ConcurrentModificationException e) {
           cme = e;
-          gotException = true;
+          result = MenuResult.quit();
         } catch (CommunicationException e) {
           ce = e;
-          gotException = true;
+          result = MenuResult.quit();
         }
       }
     }
@@ -276,14 +291,20 @@
     /**
      * 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 the named managed object.
-     * @throws ArgumentException
+     * @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 CLIException
      *           If one of the naming arguments referenced a managed
      *           object of the wrong type.
      * @throws DefinitionDecodingException
@@ -306,18 +327,19 @@
      *           If the client cannot contact the server due to an
      *           underlying communication problem.
      */
-    public ManagedObject<?> find(ManagementContext context,
-        ManagedObjectPath<?, ?> path, List<String> args)
-        throws ArgumentException, CommunicationException,
+    public MenuResult<ManagedObject<?>> find(ConsoleApplication app,
+        ManagementContext context, ManagedObjectPath<?, ?> path,
+        List<String> args) throws CLIException, CommunicationException,
         AuthorizationException, ConcurrentModificationException,
         DefinitionDecodingException, ManagedObjectDecodingException,
         ManagedObjectNotFoundException {
-      this.managedObject = context.getRootConfigurationManagedObject();
+      this.result = MenuResult.<ManagedObject<?>> success(context
+          .getRootConfigurationManagedObject());
+      this.app = app;
       this.args = args;
       this.argIndex = 0;
 
-      this.gotException = false;
-      this.ae = null;
+      this.clie = null;
       this.authze = null;
       this.ce = null;
       this.cme = null;
@@ -327,8 +349,10 @@
 
       path.serialize(this);
 
-      if (ae != null) {
-        throw ae;
+      if (result.isSuccess()) {
+        return result;
+      } else if (clie != null) {
+        throw clie;
       } else if (authze != null) {
         throw authze;
       } else if (ce != null) {
@@ -342,7 +366,8 @@
       } else if (monfe != null) {
         throw monfe;
       } else {
-        return managedObject;
+        // User requested termination interactively.
+        return result;
       }
     }
   }
@@ -500,6 +525,12 @@
   }
 
   /**
+   * 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 advanced.
    */
   private static final String OPTION_DSCFG_LONG_ADVANCED = "advanced";
@@ -552,9 +583,6 @@
   // The argument which should be used to request advanced mode.
   private BooleanArgument advancedModeArgument;
 
-  // The application instance.
-  private final ConsoleApplication app;
-
   // The argument which should be used to specify zero or more
   // property names.
   private StringArgument propertyArgument;
@@ -575,12 +603,41 @@
 
   /**
    * Create a new sub-command handler.
-   *
-   * @param app
-   *          The application instance.
    */
-  protected SubCommandHandler(ConsoleApplication app) {
-    this.app = app;
+  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;
+    }
   }
 
 
@@ -607,17 +664,38 @@
 
 
   /**
+   * {@inheritDoc}
+   */
+  @Override
+  public final int hashCode() {
+    return getSubCommand().getName().hashCode();
+  }
+
+
+
+  /**
    * Run this sub-command handler.
    *
-   * @return Returns zero if the sub-command completed successfully or
-   *         non-zero if it did not.
+   * @param app
+   *          The console application.
+   * @param factory
+   *          The management 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.
+   * @throws CLIException
+   *           If a CLI exception occurred.
    */
-  public abstract int run() throws ArgumentException, ClientException;
+  public abstract MenuResult<Integer> run(ConsoleApplication app,
+      ManagementContextFactory factory) throws ArgumentException,
+      ClientException, CLIException;
 
 
 
@@ -702,26 +780,22 @@
 
 
   /**
-   * Gets the console application instance.
-   *
-   * @return Returns the console application instance.
-   */
-  protected final ConsoleApplication getConsoleApplication() {
-    return app;
-  }
-
-
-
-  /**
    * 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 the managed object referenced by the provided
-   *         managed object 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.
@@ -741,20 +815,21 @@
    * @throws CommunicationException
    *           If the client cannot contact the server due to an
    *           underlying communication problem.
-   * @throws ArgumentException
+   * @throws CLIException
    *           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 ManagedObject<?> getManagedObject(
-      ManagedObjectPath<?, ?> path, List<String> args)
-      throws ArgumentException, AuthorizationException,
-      DefinitionDecodingException, ManagedObjectDecodingException,
-      CommunicationException, ConcurrentModificationException,
-      ManagedObjectNotFoundException, ClientException {
+  protected final MenuResult<ManagedObject<?>> getManagedObject(
+      ConsoleApplication app, ManagementContext context,
+      ManagedObjectPath<?, ?> path, List<String> args) throws CLIException,
+      AuthorizationException, DefinitionDecodingException,
+      ManagedObjectDecodingException, CommunicationException,
+      ConcurrentModificationException, ManagedObjectNotFoundException,
+      ClientException {
     ManagedObjectFinder finder = new ManagedObjectFinder();
-    return finder.find(app.getManagementContext(), path, args);
+    return finder.find(app, context, path, args);
   }
 
 
@@ -762,6 +837,8 @@
   /**
    * 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.
@@ -769,7 +846,7 @@
    *           If one of the naming arguments is missing and the
    *           application is non-interactive.
    */
-  protected final List<String> getNamingArgValues(
+  protected final List<String> getNamingArgValues(ConsoleApplication app,
       List<StringArgument> namingArgs) throws ArgumentException {
     ArrayList<String> values = new ArrayList<String>(namingArgs.size());
     for (StringArgument arg : namingArgs) {
@@ -902,14 +979,19 @@
    *          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.
    * @param d
    *          The type of child managed object to choose from.
-   * @return Returns the name of the managed object that the user
-   *         selected.
+   * @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 CommunicationException
    *           If the server cannot be contacted.
    * @throws ConcurrentModificationException
@@ -917,52 +999,62 @@
    * @throws AuthorizationException
    *           If the children cannot be listed due to an
    *           authorization failure.
-   * @throws ArgumentException
+   * @throws CLIException
    *           If the user input can be read from the console or if
    *           there are no children.
    */
   protected final <C extends ConfigurationClient, S extends Configuration>
-      String readChildName(ManagedObject<?> parent,
+      MenuResult<String> readChildName(
+      ConsoleApplication app, ManagedObject<?> parent,
       InstantiableRelationDefinition<C, S> r,
       AbstractManagedObjectDefinition<? extends C, ? extends S> d)
       throws AuthorizationException, ConcurrentModificationException,
-      CommunicationException, ArgumentException {
+      CommunicationException, CLIException {
     if (d == null) {
       d = r.getChildDefinition();
     }
 
+    app.println();
+    app.println();
     String[] children = parent.listChildren(r, d);
     switch (children.length) {
     case 0: {
       // No options available - abort.
       Message msg =
           ERR_DSCFG_ERROR_FINDER_NO_CHILDREN.get(d.getUserFriendlyPluralName());
-      throw new ArgumentException(msg);
+      throw new CLIException(msg);
     }
     case 1: {
       // Only one option available so confirm that the user wishes to
       // access it.
       Message msg = INFO_DSCFG_FINDER_PROMPT_SINGLE.get(
               d.getUserFriendlyName(), children[0]);
-      if (getConsoleApplication().confirmAction(msg)) {
-        return children[0];
+      if (app.confirmAction(msg, true)) {
+        return MenuResult.success(children[0]);
       } else {
-        msg = ERR_DSCFG_ERROR_FINDER_SINGLE_CHILD_REJECTED.get(
-            d.getUserFriendlyName());
-        throw new ArgumentException(msg);
+        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()));
+
       Arrays.sort(children, String.CASE_INSENSITIVE_ORDER);
-      ArrayList<Message> desc = new ArrayList<Message>();
-      for (String s : Arrays.asList(children)) {
-        desc.add(Message.raw(s));
+      for (String child : children) {
+        Message option = Message.raw("%s", child);
+        builder.addNumberedOption(option, MenuResult.success(child));
       }
-      Message prompt = INFO_DSCFG_FINDER_PROMPT_MANY.get(
-              d.getUserFriendlyName());
-      return getConsoleApplication().readChoice(
-              prompt, desc, Arrays.asList(children), null);
+
+      if (app.isMenuDrivenMode()) {
+        builder.addCancelOption(true);
+      }
+      builder.addQuitOption();
+
+      Menu<String> menu = builder.toMenu();
+      return menu.run();
     }
     }
   }
@@ -1062,4 +1154,7 @@
 
     subCommand.addArgument(unitTimeArgument);
   }
+
+
+
 }
diff --git a/opends/src/server/org/opends/server/tools/dsconfig/SubCommandHandlerFactory.java b/opends/src/server/org/opends/server/tools/dsconfig/SubCommandHandlerFactory.java
new file mode 100644
index 0000000..a525d1a
--- /dev/null
+++ b/opends/src/server/org/opends/server/tools/dsconfig/SubCommandHandlerFactory.java
@@ -0,0 +1,357 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.tools.dsconfig;
+
+
+
+import java.util.SortedSet;
+import java.util.TreeSet;
+
+import org.opends.server.admin.AbstractManagedObjectDefinition;
+import org.opends.server.admin.AggregationRelationDefinition;
+import org.opends.server.admin.Configuration;
+import org.opends.server.admin.ConfigurationClient;
+import org.opends.server.admin.InstantiableRelationDefinition;
+import org.opends.server.admin.ManagedObjectPath;
+import org.opends.server.admin.OptionalRelationDefinition;
+import org.opends.server.admin.RelationDefinition;
+import org.opends.server.admin.RelationDefinitionVisitor;
+import org.opends.server.admin.RelationOption;
+import org.opends.server.admin.SingletonRelationDefinition;
+import org.opends.server.util.args.ArgumentException;
+import org.opends.server.util.args.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 Void visitAggregation(AggregationRelationDefinition<?, ?> r,
+        ManagedObjectPath<?, ?> p) {
+      // Do not create sub-commands for aggregations.
+      return null;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public Void visitInstantiable(InstantiableRelationDefinition<?, ?> r,
+        ManagedObjectPath<?, ?> p) {
+      try {
+        // Create the sub-commands.
+        createHandlers.add(CreateSubCommandHandler.create(parser, p, r));
+        deleteHandlers.add(DeleteSubCommandHandler.create(parser, p, r));
+        listHandlers.add(ListSubCommandHandler.create(parser, p, r));
+        getPropHandlers.add(GetPropSubCommandHandler.create(parser, p, r));
+        setPropHandlers.add(SetPropSubCommandHandler.create(parser, p, r));
+
+        // Process the referenced managed object definition and its
+        // sub-types.
+        processRelation(p, r);
+      } catch (ArgumentException e) {
+        exception = e;
+      }
+
+      return null;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public Void visitOptional(OptionalRelationDefinition<?, ?> r,
+        ManagedObjectPath<?, ?> p) {
+      try {
+        // Create the sub-commands.
+        createHandlers.add(CreateSubCommandHandler.create(parser, p, r));
+        deleteHandlers.add(DeleteSubCommandHandler.create(parser, p, r));
+        listHandlers.add(ListSubCommandHandler.create(parser, p, r));
+        getPropHandlers.add(GetPropSubCommandHandler.create(parser, p, r));
+        setPropHandlers.add(SetPropSubCommandHandler.create(parser, p, r));
+
+        // Process the referenced managed object definition and its
+        // sub-types.
+        processRelation(p, r);
+      } catch (ArgumentException e) {
+        exception = e;
+      }
+
+      return null;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public Void visitSingleton(SingletonRelationDefinition<?, ?> r,
+        ManagedObjectPath<?, ?> p) {
+      try {
+        // Create the sub-commands.
+        getPropHandlers.add(GetPropSubCommandHandler.create(parser, p, r));
+        setPropHandlers.add(SetPropSubCommandHandler.create(parser, p, r));
+
+        // Process the referenced managed object definition and its
+        // sub-types.
+        processRelation(p, r);
+      } catch (ArgumentException e) {
+        exception = e;
+      }
+
+      return null;
+    }
+
+  }
+
+  // The set of all available sub-commands.
+  private SortedSet<SubCommandHandler> allHandlers =
+    new TreeSet<SubCommandHandler>();
+
+  // The set of create-xxx available sub-commands.
+  private SortedSet<CreateSubCommandHandler<?, ?>> createHandlers =
+    new TreeSet<CreateSubCommandHandler<?, ?>>();
+
+  // The set of delete-xxx available sub-commands.
+  private 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 SortedSet<GetPropSubCommandHandler> getPropHandlers =
+    new TreeSet<GetPropSubCommandHandler>();
+
+  // The help sub-command handler.
+  private HelpSubCommandHandler helpHandler = null;
+
+  // The set of list-xxx available sub-commands.
+  private 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 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 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/opends/src/server/org/opends/server/util/cli/CLIException.java b/opends/src/server/org/opends/server/util/cli/CLIException.java
new file mode 100644
index 0000000..8483f50
--- /dev/null
+++ b/opends/src/server/org/opends/server/util/cli/CLIException.java
@@ -0,0 +1,92 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.util.cli;
+
+
+
+import org.opends.messages.Message;
+import org.opends.messages.UtilityMessages;
+import org.opends.server.types.IdentifiedException;
+
+
+
+/**
+ * Thrown to indicate that a problem occurred when interacting with
+ * the client. For example, if input provided by the client was
+ * invalid.
+ */
+public class CLIException extends IdentifiedException {
+
+  /**
+   * Serialization ID.
+   */
+  private static final long serialVersionUID = -8182075627986981748L;
+
+
+
+  /**
+   * Adapts any exception that may have occurred whilst reading input
+   * from the console.
+   *
+   * @param cause
+   *          The exception that occurred whilst reading input from
+   *          the console.
+   * @return Returns a new CLI exception describing a problem that
+   *         occurred whilst reading input from the console.
+   */
+  public static CLIException adaptInputException(Throwable cause) {
+    return new CLIException(UtilityMessages.ERR_CONSOLE_INPUT_ERROR.get(cause
+        .getMessage()), cause);
+  }
+
+
+
+  /**
+   * Creates a new CLI exception with the provided message.
+   *
+   * @param message
+   *          The message explaining the problem that occurred.
+   */
+  public CLIException(Message message) {
+    super(message);
+  }
+
+
+
+  /**
+   * Creates a new CLI exception with the provided message and cause.
+   *
+   * @param message
+   *          The message explaining the problem that occurred.
+   * @param cause
+   *          The cause of this exception.
+   */
+  public CLIException(Message message, Throwable cause) {
+    super(message, cause);
+  }
+
+}
diff --git a/opends/src/server/org/opends/server/util/cli/ConsoleApplication.java b/opends/src/server/org/opends/server/util/cli/ConsoleApplication.java
new file mode 100644
index 0000000..7c32dff
--- /dev/null
+++ b/opends/src/server/org/opends/server/util/cli/ConsoleApplication.java
@@ -0,0 +1,453 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.util.cli;
+
+
+
+import static org.opends.messages.UtilityMessages.*;
+import static org.opends.server.util.ServerConstants.*;
+import static org.opends.server.util.StaticUtils.*;
+
+import java.io.BufferedReader;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.io.Reader;
+
+import org.opends.messages.Message;
+import org.opends.server.types.NullOutputStream;
+import org.opends.server.util.PasswordReader;
+
+
+
+/**
+ * This class provides an abstract base class which can be used as the
+ * basis of a console-based application.
+ */
+public abstract class ConsoleApplication {
+
+  /**
+   * A null reader.
+   */
+  private static final class NullReader extends Reader {
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void close() throws IOException {
+      // Do nothing.
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int read(char[] cbuf, int off, int len) throws IOException {
+      return -1;
+    }
+  }
+
+  // The error stream which this application should use.
+  private final PrintStream err;
+
+  // The input stream reader which this application should use.
+  private final BufferedReader in;
+
+  // The output stream which this application should use.
+  private final PrintStream out;
+
+
+
+  /**
+   * Creates a new console application instance.
+   *
+   * @param in
+   *          The application input stream.
+   * @param out
+   *          The application output stream.
+   * @param err
+   *          The application error stream.
+   */
+  protected ConsoleApplication(BufferedReader in, PrintStream out,
+      PrintStream err) {
+    if (in != null) {
+      this.in = in;
+    } else {
+      this.in = new BufferedReader(new NullReader());
+    }
+
+    if (out != null) {
+      this.out = out;
+    } else {
+      this.out = NullOutputStream.printStream();
+    }
+
+    if (err != null) {
+      this.err = out;
+    } else {
+      this.err = NullOutputStream.printStream();
+    }
+  }
+
+
+
+  /**
+   * Creates a new console application instance.
+   *
+   * @param in
+   *          The application input stream.
+   * @param out
+   *          The application output stream.
+   * @param err
+   *          The application error stream.
+   */
+  protected ConsoleApplication(InputStream in, OutputStream out,
+      OutputStream err) {
+    if (in != null) {
+      this.in = new BufferedReader(new InputStreamReader(in));
+    } else {
+      this.in = new BufferedReader(new NullReader());
+    }
+
+    if (out != null) {
+      this.out = new PrintStream(out);
+    } else {
+      this.out = NullOutputStream.printStream();
+    }
+
+    if (err != null) {
+      this.err = new PrintStream(err);
+    } else {
+      this.err = NullOutputStream.printStream();
+    }
+  }
+
+
+
+  /**
+   * Interactively confirms whether a user wishes to perform an
+   * action. If the application is non-interactive, then the provided
+   * default is returned automatically.
+   *
+   * @param prompt
+   *          The prompt describing the action.
+   * @param defaultValue
+   *          The default value for the confirmation message. This
+   *          will be returned if the application is non-interactive
+   *          or if the user just presses return.
+   * @return Returns <code>true</code> if the user wishes the action
+   *         to be performed, or <code>false</code> if they refused,
+   *         or if an exception occurred.
+   * @throws CLIException
+   *           If the user's response could not be read from the
+   *           console for some reason.
+   */
+  public final boolean confirmAction(Message prompt, final boolean defaultValue)
+      throws CLIException {
+    if (!isInteractive()) {
+      return defaultValue;
+    }
+
+    final Message yes = INFO_GENERAL_YES.get();
+    final Message no = INFO_GENERAL_NO.get();
+    final Message errMsg = ERR_CONSOLE_APP_CONFIRM.get(yes, no);
+    prompt = INFO_MENU_PROMPT_CONFIRM.get(prompt, yes, no, defaultValue ? yes
+        : no);
+
+    ValidationCallback<Boolean> validator = new ValidationCallback<Boolean>() {
+
+      public Boolean validate(ConsoleApplication app, String input) {
+        String ninput = input.toLowerCase().trim();
+        if (ninput.length() == 0) {
+          return defaultValue;
+        } else if (no.toString().startsWith(ninput)) {
+          return false;
+        } else if (yes.toString().startsWith(ninput)) {
+          return true;
+        } else {
+          // Try again...
+          app.println();
+          app.println(errMsg);
+          app.println();
+        }
+
+        return null;
+      }
+    };
+
+    try {
+      return readValidatedInput(prompt, validator);
+    } catch (CLIException e) {
+      // Should never happen.
+      throw new RuntimeException(e);
+    }
+  }
+
+
+
+  /**
+   * Gets the application error stream.
+   *
+   * @return Returns the application error stream.
+   */
+  public final PrintStream getErrorStream() {
+    return err;
+  }
+
+
+
+  /**
+   * Gets the application input stream.
+   *
+   * @return Returns the application input stream.
+   */
+  public final BufferedReader getInputStream() {
+    return in;
+  }
+
+
+
+  /**
+   * Gets the application output stream.
+   *
+   * @return Returns the application output stream.
+   */
+  public final PrintStream getOutputStream() {
+    return out;
+  }
+
+
+
+  /**
+   * Indicates whether or not the user has requested interactive
+   * behavior.
+   *
+   * @return Returns <code>true</code> if the user has requested
+   *         interactive behavior.
+   */
+  public abstract boolean isInteractive();
+
+
+
+  /**
+   * Indicates whether or not this console application is running in
+   * its menu-driven mode. This can be used to dictate whether output
+   * should go to the error stream or not. In addition, it may also
+   * dictate whether or not sub-menus should display a cancel option
+   * as well as a quit option.
+   *
+   * @return Returns <code>true</code> if this console application
+   *         is running in its menu-driven mode.
+   */
+  public abstract boolean isMenuDrivenMode();
+
+
+
+  /**
+   * Indicates whether or not the user has requested quiet output.
+   *
+   * @return Returns <code>true</code> if the user has requested
+   *         quiet output.
+   */
+  public abstract boolean isQuiet();
+
+
+
+  /**
+   * Indicates whether or not the user has requested script-friendly
+   * output.
+   *
+   * @return Returns <code>true</code> if the user has requested
+   *         script-friendly output.
+   */
+  public abstract boolean isScriptFriendly();
+
+
+
+  /**
+   * Indicates whether or not the user has requested verbose output.
+   *
+   * @return Returns <code>true</code> if the user has requested
+   *         verbose output.
+   */
+  public abstract boolean isVerbose();
+
+
+
+  /**
+   * Interactively prompts the user to press return to continue. This
+   * method should be called in situations where a user needs to be
+   * given a chance to read some documentation before continuing
+   * (continuing may cause the documentation to be scrolled out of
+   * view).
+   */
+  public final void pressReturnToContinue() {
+    Message msg = INFO_MENU_PROMPT_RETURN_TO_CONTINUE.get();
+    try {
+      readLineOfInput(msg);
+    } catch (CLIException e) {
+      // Ignore the exception - applications don't care.
+    }
+  }
+
+
+
+  /**
+   * Displays a blank line to the error stream.
+   */
+  public final void println() {
+    err.println();
+  }
+
+
+
+  /**
+   * Displays a message to the error stream.
+   *
+   * @param msg
+   *          The message.
+   */
+  public final void println(Message msg) {
+    err.println(wrapText(msg, MAX_LINE_WIDTH));
+  }
+
+
+
+  /**
+   * Displays a message to the error stream indented by the specified
+   * number of columns.
+   *
+   * @param msg
+   *          The message.
+   * @param indent
+   *          The number of columns to indent.
+   */
+  public final void println(Message msg, int indent) {
+    err.println(wrapText(msg, MAX_LINE_WIDTH, indent));
+  }
+
+
+
+  /**
+   * Displays a message to the error stream if verbose mode is
+   * enabled.
+   *
+   * @param msg
+   *          The verbose message.
+   */
+  public final void printVerboseMessage(Message msg) {
+    if (isVerbose() || isInteractive()) {
+      err.println(wrapText(msg, MAX_LINE_WIDTH));
+    }
+  }
+
+
+
+  /**
+   * Interactively retrieves a line of input from the console.
+   *
+   * @param prompt
+   *          The prompt.
+   * @return Returns the line of input, or <code>null</code> if the
+   *         end of input has been reached.
+   * @throws CLIException
+   *           If the line of input could not be retrieved for some
+   *           reason.
+   */
+  public final String readLineOfInput(Message prompt) throws CLIException {
+    err.print(wrapText(prompt + " ", MAX_LINE_WIDTH));
+    try {
+      String s = in.readLine();
+      if (s == null) {
+        throw CLIException
+            .adaptInputException(new EOFException("End of input"));
+      } else {
+        return s;
+      }
+    } catch (IOException e) {
+      throw CLIException.adaptInputException(e);
+    }
+  }
+
+
+
+  /**
+   * Interactively retrieves a password from the console.
+   *
+   * @param prompt
+   *          The password prompt.
+   * @return Returns the password.
+   * @throws CLIException
+   *           If the password could not be retrieved for some reason.
+   */
+  public final String readPassword(Message prompt) throws CLIException {
+    err.print(wrapText(prompt + " ", MAX_LINE_WIDTH));
+    char[] pwChars;
+    try {
+      pwChars = PasswordReader.readPassword();
+    } catch (Exception e) {
+      throw CLIException.adaptInputException(e);
+    }
+    return new String(pwChars);
+  }
+
+
+
+  /**
+   * Interactively prompts for user input and continues until valid
+   * input is provided.
+   *
+   * @param <T>
+   *          The type of decoded user input.
+   * @param prompt
+   *          The interactive prompt which should be displayed on each
+   *          input attempt.
+   * @param validator
+   *          An input validator responsible for validating and
+   *          decoding the user's response.
+   * @return Returns the decoded user's response.
+   * @throws CLIException
+   *           If an unexpected error occurred which prevented
+   *           validation.
+   */
+  public final <T> T readValidatedInput(Message prompt,
+      ValidationCallback<T> validator) throws CLIException {
+    while (true) {
+      String response = readLineOfInput(prompt);
+      T value = validator.validate(this, response);
+      if (value != null) {
+        return value;
+      }
+    }
+  }
+}
diff --git a/opends/src/server/org/opends/server/util/cli/ErrorStreamConsoleApplication.java b/opends/src/server/org/opends/server/util/cli/ErrorStreamConsoleApplication.java
new file mode 100644
index 0000000..0d16256
--- /dev/null
+++ b/opends/src/server/org/opends/server/util/cli/ErrorStreamConsoleApplication.java
@@ -0,0 +1,105 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.util.cli;
+
+
+
+/**
+ * A console application decorator which redirects all output to the
+ * underlying application's error stream.
+ */
+public class ErrorStreamConsoleApplication extends ConsoleApplication {
+
+  // The underlying console application.
+  private final ConsoleApplication app;
+
+
+
+  /**
+   * Creates a new console application instance which redirects all
+   * output to the underlying application's error stream.
+   *
+   * @param app
+   *          The underlying application console.
+   */
+  public ErrorStreamConsoleApplication(ConsoleApplication app) {
+    super(app.getInputStream(), app.getErrorStream(), app.getErrorStream());
+
+    this.app = app;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public boolean isInteractive() {
+    return app.isInteractive();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public boolean isMenuDrivenMode() {
+    return app.isMenuDrivenMode();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public boolean isQuiet() {
+    return app.isQuiet();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public boolean isScriptFriendly() {
+    return app.isScriptFriendly();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public boolean isVerbose() {
+    return app.isVerbose();
+  }
+
+}
diff --git a/opends/src/server/org/opends/server/tools/dsconfig/HelpCallback.java b/opends/src/server/org/opends/server/util/cli/HelpCallback.java
similarity index 94%
rename from opends/src/server/org/opends/server/tools/dsconfig/HelpCallback.java
rename to opends/src/server/org/opends/server/util/cli/HelpCallback.java
index 8412933..fb5af43 100644
--- a/opends/src/server/org/opends/server/tools/dsconfig/HelpCallback.java
+++ b/opends/src/server/org/opends/server/util/cli/HelpCallback.java
@@ -24,14 +24,14 @@
  *
  *      Portions Copyright 2007 Sun Microsystems, Inc.
  */
-package org.opends.server.tools.dsconfig;
+package org.opends.server.util.cli;
 
 
 
 /**
  * An interface for displaying help interactively.
  */
-interface HelpCallback {
+public interface HelpCallback {
 
   /**
    * Displays help to the provided application console.
diff --git a/opends/src/server/org/opends/server/util/cli/Menu.java b/opends/src/server/org/opends/server/util/cli/Menu.java
new file mode 100644
index 0000000..abeb384
--- /dev/null
+++ b/opends/src/server/org/opends/server/util/cli/Menu.java
@@ -0,0 +1,52 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.util.cli;
+
+
+
+/**
+ * An interactive console-based menu.
+ *
+ * @param <T>
+ *          The type of success result value(s) returned by the
+ *          call-back. Use <code>Void</code> if the call-backs do
+ *          not return any values.
+ */
+public interface Menu<T> {
+
+  /**
+   * Displays the menu and waits for the user to select a valid
+   * option. When the user selects an option, the call-back associated
+   * with the option will be invoked and its result returned.
+   *
+   * @return Returns the result of invoking the chosen menu call-back.
+   * @throws CLIException
+   *           If an I/O exception occurred or if one of the menu
+   *           option call-backs failed for some reason.
+   */
+  MenuResult<T> run() throws CLIException;
+}
diff --git a/opends/src/server/org/opends/server/util/cli/MenuBuilder.java b/opends/src/server/org/opends/server/util/cli/MenuBuilder.java
new file mode 100644
index 0000000..a69d9a0
--- /dev/null
+++ b/opends/src/server/org/opends/server/util/cli/MenuBuilder.java
@@ -0,0 +1,818 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.util.cli;
+
+
+
+import static org.opends.messages.UtilityMessages.*;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.opends.messages.Message;
+import org.opends.server.util.table.TableBuilder;
+import org.opends.server.util.table.TablePrinter;
+import org.opends.server.util.table.TextTablePrinter;
+
+
+
+/**
+ * An interface for incrementally building a command-line menu.
+ *
+ * @param <T>
+ *          The type of value returned by the call-backs. Use
+ *          <code>Void</code> if the call-backs do not return a
+ *          value.
+ */
+public final class MenuBuilder<T> {
+
+  /**
+   * A simple menu option call-back which is a composite of zero or
+   * more underlying call-backs.
+   *
+   * @param <T>
+   *          The type of value returned by the call-back.
+   */
+  private static final class CompositeCallback<T> implements MenuCallback<T> {
+
+    // The list of underlying call-backs.
+    private final Collection<MenuCallback<T>> callbacks;
+
+
+
+    /**
+     * Creates a new composite call-back with the specified set of
+     * call-backs.
+     *
+     * @param callbacks
+     *          The set of call-backs.
+     */
+    public CompositeCallback(Collection<MenuCallback<T>> callbacks) {
+      this.callbacks = callbacks;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public MenuResult<T> invoke(ConsoleApplication app) throws CLIException {
+      List<T> values = new ArrayList<T>();
+      for (MenuCallback<T> callback : callbacks) {
+        MenuResult<T> result = callback.invoke(app);
+
+        if (!result.isSuccess()) {
+          // Throw away all the other results.
+          return result;
+        } else {
+          values.addAll(result.getValues());
+        }
+      }
+      return MenuResult.success(values);
+    }
+  }
+
+
+
+  /**
+   * Underlying menu implementation generated by this menu builder.
+   *
+   * @param <T>
+   *          The type of value returned by the call-backs. Use
+   *          <code>Void</code> if the call-backs do not return a
+   *          value.
+   */
+  private static final class MenuImpl<T> implements Menu<T> {
+
+    // Indicates whether the menu will allow selection of multiple
+    // numeric options.
+    private final boolean allowMultiSelect;
+
+    // The application console.
+    private final ConsoleApplication app;
+
+    // The call-back lookup table.
+    private final Map<String, MenuCallback<T>> callbacks;
+
+    // The char options table builder.
+    private final TableBuilder cbuilder;
+
+    // The call-back for the optional default action.
+    private final MenuCallback<T> defaultCallback;
+
+    // The description of the optional default action.
+    private final Message defaultDescription;
+
+    // The numeric options table builder.
+    private final TableBuilder nbuilder;
+
+    // The table printer.
+    private final TablePrinter printer;
+
+    // The menu prompt.
+    private final Message prompt;
+
+    // The menu title.
+    private final Message title;
+
+
+
+    // Private constructor.
+    private MenuImpl(ConsoleApplication app, Message title, Message prompt,
+        TableBuilder ntable, TableBuilder ctable, TablePrinter printer,
+        Map<String, MenuCallback<T>> callbacks, boolean allowMultiSelect,
+        MenuCallback<T> defaultCallback, Message defaultDescription) {
+      this.app = app;
+      this.title = title;
+      this.prompt = prompt;
+      this.nbuilder = ntable;
+      this.cbuilder = ctable;
+      this.printer = printer;
+      this.callbacks = callbacks;
+      this.allowMultiSelect = allowMultiSelect;
+      this.defaultCallback = defaultCallback;
+      this.defaultDescription = defaultDescription;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public MenuResult<T> run() throws CLIException {
+      // The validation call-back which will be used to determine the
+      // action call-back.
+      ValidationCallback<MenuCallback<T>> validator =
+        new ValidationCallback<MenuCallback<T>>() {
+
+        public MenuCallback<T> validate(ConsoleApplication app, String input) {
+          String ninput = input.trim();
+
+          if (ninput.length() == 0) {
+            if (defaultCallback != null) {
+              return defaultCallback;
+            } else if (allowMultiSelect) {
+              app.println();
+              app.println(ERR_MENU_BAD_CHOICE_MULTI.get());
+              app.println();
+              return null;
+            } else {
+              app.println();
+              app.println(ERR_MENU_BAD_CHOICE_SINGLE.get());
+              app.println();
+              return null;
+            }
+          } else if (allowMultiSelect) {
+            // Use a composite call-back to collect all the results.
+            List<MenuCallback<T>> cl = new ArrayList<MenuCallback<T>>();
+            for (String value : ninput.split(",")) {
+              // Make sure that there are no duplicates.
+              String nvalue = value.trim();
+              Set<String> choices = new HashSet<String>();
+
+              if (choices.contains(nvalue)) {
+                app.println();
+                app.println(ERR_MENU_BAD_CHOICE_MULTI_DUPE.get(value));
+                app.println();
+                return null;
+              } else if (!callbacks.containsKey(nvalue)) {
+                app.println();
+                app.println(ERR_MENU_BAD_CHOICE_MULTI.get());
+                app.println();
+                return null;
+              } else {
+                cl.add(callbacks.get(nvalue));
+                choices.add(nvalue);
+              }
+            }
+
+            return new CompositeCallback<T>(cl);
+          } else if (!callbacks.containsKey(ninput)) {
+            app.println();
+            app.println(ERR_MENU_BAD_CHOICE_SINGLE.get());
+            app.println();
+            return null;
+          } else {
+            return callbacks.get(ninput);
+          }
+        }
+      };
+
+      // Determine the correct choice prompt.
+      Message promptMsg;
+      if (allowMultiSelect) {
+        if (defaultDescription != null) {
+          promptMsg = INFO_MENU_PROMPT_MULTI_DEFAULT.get(defaultDescription);
+        } else {
+          promptMsg = INFO_MENU_PROMPT_MULTI.get();
+        }
+      } else {
+        if (defaultDescription != null) {
+          promptMsg = INFO_MENU_PROMPT_SINGLE_DEFAULT.get(defaultDescription);
+        } else {
+          promptMsg = INFO_MENU_PROMPT_SINGLE.get();
+        }
+      }
+
+      // If the user selects help then we need to loop around and
+      // display the menu again.
+      while (true) {
+        // Display the menu.
+        if (title != null) {
+          app.println(title);
+          app.println();
+        }
+
+        if (prompt != null) {
+          app.println(prompt);
+          app.println();
+        }
+
+        if (nbuilder.getTableHeight() > 0) {
+          nbuilder.print(printer);
+          app.println();
+        }
+
+        if (cbuilder.getTableHeight() > 0) {
+          TextTablePrinter cprinter =
+            new TextTablePrinter(app.getErrorStream());
+          cprinter.setDisplayHeadings(false);
+          int sz = String.valueOf(nbuilder.getTableHeight()).length() + 1;
+          cprinter.setColumnWidth(0, 2);
+          cprinter.setColumnWidth(1, sz);
+          cprinter.setColumnWidth(2, 0);
+          cbuilder.print(cprinter);
+          app.println();
+        }
+
+        // Get the user's choice.
+        MenuCallback<T> choice;
+        try {
+          choice = app.readValidatedInput(promptMsg, validator);
+        } catch (CLIException e) {
+          // Should never happen.
+          throw new RuntimeException(e);
+        }
+
+        // Invoke the user's selected choice.
+        MenuResult<T> result = choice.invoke(app);
+
+        // Determine if the help needs to be displayed, display it and
+        // start again.
+        if (!result.isAgain()) {
+          return result;
+        } else {
+          app.println();
+          app.println();
+        }
+      }
+    }
+  }
+
+
+
+  /**
+   * A simple menu option call-back which does nothing but return the
+   * provided menu result.
+   *
+   * @param <T>
+   *          The type of result returned by the call-back.
+   */
+  private static final class ResultCallback<T> implements MenuCallback<T> {
+
+    // The result to be returned by this call-back.
+    private final MenuResult<T> result;
+
+
+
+    // Private constructor.
+    private ResultCallback(MenuResult<T> result) {
+      this.result = result;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public MenuResult<T> invoke(ConsoleApplication app) throws CLIException {
+      return result;
+    }
+
+  }
+
+  // The multiple column display threshold.
+  private int threshold = -1;
+
+  // Indicates whether the menu will allow selection of multiple
+  // numeric options.
+  private boolean allowMultiSelect = false;
+
+  // The application console.
+  private final ConsoleApplication app;
+
+  // The char option call-backs.
+  private final List<MenuCallback<T>> charCallbacks =
+    new ArrayList<MenuCallback<T>>();
+
+  // The char option keys (must be single-character messages).
+  private final List<Message> charKeys = new ArrayList<Message>();
+
+  // The synopsis of char options.
+  private final List<Message> charSynopsis = new ArrayList<Message>();
+
+  // Optional column headings.
+  private final List<Message> columnHeadings = new ArrayList<Message>();
+
+  // Optional column widths.
+  private final List<Integer> columnWidths = new ArrayList<Integer>();
+
+  // The call-back for the optional default action.
+  private MenuCallback<T> defaultCallback = null;
+
+  // The description of the optional default action.
+  private Message defaultDescription = null;
+
+  // The numeric option call-backs.
+  private final List<MenuCallback<T>> numericCallbacks =
+    new ArrayList<MenuCallback<T>>();
+
+  // The numeric option fields.
+  private final List<List<Message>> numericFields =
+    new ArrayList<List<Message>>();
+
+  // The menu title.
+  private Message title = null;
+
+  // The menu prompt.
+  private Message prompt = null;
+
+
+
+  /**
+   * Creates a new menu.
+   *
+   * @param app
+   *          The application console.
+   */
+  public MenuBuilder(ConsoleApplication app) {
+    this.app = app;
+  }
+
+
+
+  /**
+   * Creates a "back" menu option. When invoked, this option will
+   * return a {@link MenuResult#cancel()} result.
+   *
+   * @param isDefault
+   *          Indicates whether this option should be made the menu
+   *          default.
+   */
+  public void addBackOption(boolean isDefault) {
+    addCharOption(INFO_MENU_OPTION_BACK_KEY.get(), INFO_MENU_OPTION_BACK.get(),
+        MenuResult.<T> cancel());
+
+    if (isDefault) {
+      setDefault(INFO_MENU_OPTION_BACK_KEY.get(), MenuResult.<T> cancel());
+    }
+  }
+
+
+
+  /**
+   * Creates a "cancel" menu option. When invoked, this option will
+   * return a {@link MenuResult#cancel()} result.
+   *
+   * @param isDefault
+   *          Indicates whether this option should be made the menu
+   *          default.
+   */
+  public void addCancelOption(boolean isDefault) {
+    addCharOption(INFO_MENU_OPTION_CANCEL_KEY.get(), INFO_MENU_OPTION_CANCEL
+        .get(), MenuResult.<T> cancel());
+
+    if (isDefault) {
+      setDefault(INFO_MENU_OPTION_CANCEL_KEY.get(), MenuResult.<T> cancel());
+    }
+  }
+
+
+
+  /**
+   * Adds a menu choice to the menu which will have a single letter as
+   * its key.
+   *
+   * @param c
+   *          The single-letter message which will be used as the key
+   *          for this option.
+   * @param description
+   *          The menu option description.
+   * @param callback
+   *          The call-back associated with this option.
+   */
+  public void addCharOption(Message c, Message description,
+      MenuCallback<T> callback) {
+    charKeys.add(c);
+    charSynopsis.add(description);
+    charCallbacks.add(callback);
+  }
+
+
+
+  /**
+   * Adds a menu choice to the menu which will have a single letter as
+   * its key and which returns the provided result.
+   *
+   * @param c
+   *          The single-letter message which will be used as the key
+   *          for this option.
+   * @param description
+   *          The menu option description.
+   * @param result
+   *          The menu result which should be returned by this menu
+   *          choice.
+   */
+  public void addCharOption(Message c, Message description,
+      MenuResult<T> result) {
+    addCharOption(c, description, new ResultCallback<T>(result));
+  }
+
+
+
+  /**
+   * Creates a "help" menu option which will use the provided help
+   * call-back to display help relating to the other menu options.
+   * When the help menu option is selected help will be displayed and
+   * then the user will be shown the menu again and prompted to enter
+   * a choice.
+   *
+   * @param callback
+   *          The help call-back.
+   */
+  public void addHelpOption(final HelpCallback callback) {
+    MenuCallback<T> wrapper = new MenuCallback<T>() {
+
+      public MenuResult<T> invoke(ConsoleApplication app) throws CLIException {
+        app.println();
+        callback.display(app);
+        return MenuResult.again();
+      }
+
+    };
+
+    addCharOption(INFO_MENU_OPTION_HELP_KEY.get(), INFO_MENU_OPTION_HELP.get(),
+        wrapper);
+  }
+
+
+
+  /**
+   * Adds a menu choice to the menu which will have a numeric key.
+   *
+   * @param description
+   *          The menu option description.
+   * @param callback
+   *          The call-back associated with this option.
+   * @param extraFields
+   *          Any additional fields associated with this menu option.
+   * @return Returns the number associated with menu choice.
+   */
+  public int addNumberedOption(Message description, MenuCallback<T> callback,
+      Message... extraFields) {
+    List<Message> fields = new ArrayList<Message>();
+    fields.add(description);
+    if (extraFields != null) {
+      fields.addAll(Arrays.asList(extraFields));
+    }
+
+    numericFields.add(fields);
+    numericCallbacks.add(callback);
+
+    return numericCallbacks.size();
+  }
+
+
+
+  /**
+   * Adds a menu choice to the menu which will have a numeric key and
+   * which returns the provided result.
+   *
+   * @param description
+   *          The menu option description.
+   * @param result
+   *          The menu result which should be returned by this menu
+   *          choice.
+   * @param extraFields
+   *          Any additional fields associated with this menu option.
+   * @return Returns the number associated with menu choice.
+   */
+  public int addNumberedOption(Message description, MenuResult<T> result,
+      Message... extraFields) {
+    return addNumberedOption(description, new ResultCallback<T>(result),
+        extraFields);
+  }
+
+
+
+  /**
+   * Creates a "quit" menu option. When invoked, this option will
+   * return a {@link MenuResult#quit()} result.
+   */
+  public void addQuitOption() {
+    addCharOption(INFO_MENU_OPTION_QUIT_KEY.get(), INFO_MENU_OPTION_QUIT.get(),
+        MenuResult.<T> quit());
+  }
+
+
+
+  /**
+   * Sets the flag which indicates whether or not the menu will permit
+   * multiple numeric options to be selected at once. Users specify
+   * multiple choices by separating them with a comma. The default is
+   * <code>false</code>.
+   *
+   * @param allowMultiSelect
+   *          Indicates whether or not the menu will permit multiple
+   *          numeric options to be selected at once.
+   */
+  public void setAllowMultiSelect(boolean allowMultiSelect) {
+    this.allowMultiSelect = allowMultiSelect;
+  }
+
+
+
+  /**
+   * Sets the optional column headings. The column headings will be
+   * displayed above the menu options.
+   *
+   * @param headings
+   *          The optional column headings.
+   */
+  public void setColumnHeadings(Message... headings) {
+    this.columnHeadings.clear();
+    if (headings != null) {
+      this.columnHeadings.addAll(Arrays.asList(headings));
+    }
+  }
+
+
+
+  /**
+   * Sets the optional column widths. A value of zero indicates that
+   * the column should be expandable, a value of <code>null</code>
+   * indicates that the column should use its default width.
+   *
+   * @param widths
+   *          The optional column widths.
+   */
+  public void setColumnWidths(Integer... widths) {
+    this.columnWidths.clear();
+    if (widths != null) {
+      this.columnWidths.addAll(Arrays.asList(widths));
+    }
+  }
+
+
+
+  /**
+   * Sets the optional default action for this menu. The default
+   * action call-back will be invoked if the user does not specify an
+   * option and just presses enter.
+   *
+   * @param description
+   *          A short description of the default action.
+   * @param callback
+   *          The call-back associated with the default action.
+   */
+  public void setDefault(Message description, MenuCallback<T> callback) {
+    defaultCallback = callback;
+    defaultDescription = description;
+  }
+
+
+
+  /**
+   * Sets the optional default action for this menu. The default
+   * action call-back will be invoked if the user does not specify an
+   * option and just presses enter.
+   *
+   * @param description
+   *          A short description of the default action.
+   * @param result
+   *          The menu result which should be returned by default.
+   */
+  public void setDefault(Message description, MenuResult<T> result) {
+    setDefault(description, new ResultCallback<T>(result));
+  }
+
+
+
+  /**
+   * Sets the number of numeric options required to trigger
+   * multiple-column display. A negative value (the default) indicates
+   * that the numeric options will always be displayed in a single
+   * column. A value of 0 indicates that numeric options will always
+   * be displayed in multiple columns.
+   *
+   * @param threshold
+   *          The number of numeric options required to trigger
+   *          multiple-column display.
+   */
+  public void setMultipleColumnThreshold(int threshold) {
+    this.threshold = threshold;
+  }
+
+
+
+  /**
+   * Sets the optional menu prompt. The prompt will be displayed above
+   * the menu. Menus do not have a prompt by default.
+   *
+   * @param prompt
+   *          The menu prompt, or <code>null</code> if there is not
+   *          prompt.
+   */
+  public void setPrompt(Message prompt) {
+    this.prompt = prompt;
+  }
+
+
+
+  /**
+   * Sets the optional menu title. The title will be displayed above
+   * the menu prompt. Menus do not have a title by default.
+   *
+   * @param title
+   *          The menu title, or <code>null</code> if there is not
+   *          title.
+   */
+  public void setTitle(Message title) {
+    this.title = title;
+  }
+
+
+
+  /**
+   * Creates a menu from this menu builder.
+   *
+   * @return Returns the new menu.
+   */
+  public Menu<T> toMenu() {
+    TableBuilder nbuilder = new TableBuilder();
+    Map<String, MenuCallback<T>> callbacks =
+      new HashMap<String, MenuCallback<T>>();
+
+    // Determine whether multiple columns should be used for numeric
+    // options.
+    boolean useMultipleColumns = false;
+    if (threshold >= 0 && numericCallbacks.size() >= threshold) {
+      useMultipleColumns = true;
+    }
+
+    // Create optional column headers.
+    if (!columnHeadings.isEmpty()) {
+      nbuilder.appendHeading();
+      nbuilder.appendHeading();
+      for (Message heading : columnHeadings) {
+        if (heading != null) {
+          nbuilder.appendHeading(heading);
+        } else {
+          nbuilder.appendHeading();
+        }
+      }
+
+      if (useMultipleColumns) {
+        nbuilder.appendHeading();
+        for (Message heading : columnHeadings) {
+          if (heading != null) {
+            nbuilder.appendHeading(heading);
+          } else {
+            nbuilder.appendHeading();
+          }
+        }
+      }
+    }
+
+    // Add the numeric options first.
+    int sz = numericCallbacks.size();
+    int rows = sz;
+
+    if (useMultipleColumns) {
+      // Display in two columns the first column should contain half
+      // the options. If there are an odd number of columns then the
+      // first column should contain an additional option (e.g. if
+      // there are 23 options, the first column should contain 12
+      // options and the second column 11 options).
+      rows /= 2;
+      rows += sz % 2;
+    }
+
+    for (int i = 0, j = rows; i < rows; i++, j++) {
+      nbuilder.startRow();
+      nbuilder.appendCell();
+      nbuilder.appendCell(INFO_MENU_NUMERIC_OPTION.get(i + 1));
+
+      for (Message field : numericFields.get(i)) {
+        if (field != null) {
+          nbuilder.appendCell(field);
+        } else {
+          nbuilder.appendCell();
+        }
+      }
+
+      callbacks.put(String.valueOf(i + 1), numericCallbacks.get(i));
+
+      // Second column.
+      if (useMultipleColumns && (j < sz)) {
+        nbuilder.appendCell(INFO_MENU_NUMERIC_OPTION.get(j + 1));
+
+        for (Message field : numericFields.get(j)) {
+          if (field != null) {
+            nbuilder.appendCell(field);
+          } else {
+            nbuilder.appendCell();
+          }
+        }
+
+        callbacks.put(String.valueOf(j + 1), numericCallbacks.get(j));
+      }
+    }
+
+    // Add the char options last.
+    TableBuilder cbuilder = new TableBuilder();
+    for (int i = 0; i < charCallbacks.size(); i++) {
+      char c = charKeys.get(i).charAt(0);
+      Message option = INFO_MENU_CHAR_OPTION.get(c);
+
+      cbuilder.startRow();
+      cbuilder.appendCell();
+      cbuilder.appendCell(option);
+      cbuilder.appendCell(charSynopsis.get(i));
+
+      callbacks.put(String.valueOf(c), charCallbacks.get(i));
+    }
+
+    // Configure the table printer.
+    TextTablePrinter printer = new TextTablePrinter(app.getErrorStream());
+
+    if (columnHeadings.isEmpty()) {
+      printer.setDisplayHeadings(false);
+    } else {
+      printer.setDisplayHeadings(true);
+    }
+
+    printer.setColumnWidth(0, 2);
+    if (columnWidths.isEmpty()) {
+      printer.setColumnWidth(2, 0);
+      if (useMultipleColumns) {
+        printer.setColumnWidth(4, 0);
+      }
+    } else {
+      for (int i = 0; i < columnWidths.size(); i++) {
+        Integer j = columnWidths.get(i);
+        if (j != null) {
+          // Skip the option key column.
+          printer.setColumnWidth(i + 2, j);
+
+          if (useMultipleColumns) {
+            printer.setColumnWidth(i + 3 + columnWidths.size(), j);
+          }
+        }
+      }
+    }
+
+    return new MenuImpl<T>(app, title, prompt, nbuilder, cbuilder, printer,
+        callbacks, allowMultiSelect, defaultCallback, defaultDescription);
+  }
+}
diff --git a/opends/src/server/org/opends/server/tools/dsconfig/HelpCallback.java b/opends/src/server/org/opends/server/util/cli/MenuCallback.java
similarity index 60%
copy from opends/src/server/org/opends/server/tools/dsconfig/HelpCallback.java
copy to opends/src/server/org/opends/server/util/cli/MenuCallback.java
index 8412933..d26ed45 100644
--- a/opends/src/server/org/opends/server/tools/dsconfig/HelpCallback.java
+++ b/opends/src/server/org/opends/server/util/cli/MenuCallback.java
@@ -24,20 +24,29 @@
  *
  *      Portions Copyright 2007 Sun Microsystems, Inc.
  */
-package org.opends.server.tools.dsconfig;
+package org.opends.server.util.cli;
 
 
 
 /**
- * An interface for displaying help interactively.
+ * A menu call-back which should be associated with each menu option.
+ * When an option is selected the call-back is invoked.
+ *
+ * @param <T>
+ *          The type of success result value(s) returned by the
+ *          call-back. Use <code>Void</code> if the call-backs do
+ *          not return any values.
  */
-interface HelpCallback {
+public interface MenuCallback<T> {
 
   /**
-   * Displays help to the provided application console.
+   * Invoke the menu call-back.
    *
    * @param app
-   *          The console application.
+   *          The application console.
+   * @return Returns the result of invoking the menu call-back.
+   * @throws CLIException
+   *           If the menu call-back fails for some reason.
    */
-  void display(ConsoleApplication app);
+  MenuResult<T> invoke(ConsoleApplication app) throws CLIException;
 }
diff --git a/opends/src/server/org/opends/server/util/cli/MenuResult.java b/opends/src/server/org/opends/server/util/cli/MenuResult.java
new file mode 100644
index 0000000..a5c7ca5
--- /dev/null
+++ b/opends/src/server/org/opends/server/util/cli/MenuResult.java
@@ -0,0 +1,324 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.util.cli;
+
+
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+
+
+
+/**
+ * The result of running a {@link Menu}. The result indicates to the
+ * application how it should proceed:
+ * <ul>
+ * <li>{@link #again()} - the menu should be displayed again. A good
+ * example of this is when a user chooses to view some help. Normally,
+ * after the help is displayed, the user is allowed to select another
+ * option
+ * <li>{@link #cancel()} - the user chose to cancel any task
+ * currently in progress and go back to the previous main menu if
+ * applicable
+ * <li>{@link #success()} - the user chose to apply any task
+ * currently in progress and go back to the previous menu if
+ * applicable. Any result values applicable to the chosen option can
+ * be retrieved using {@link #getValue()} or {@link #getValues()}
+ * <li>{@link #quit()} - the user chose to quit the application and
+ * cancel all outstanding tasks.
+ * </ul>
+ *
+ * @param <T>
+ *          The type of result value(s) contained in success results.
+ *          Use <code>Void</code> if success results should not
+ *          contain values.
+ */
+public final class MenuResult<T> {
+
+  /**
+   * The type of result returned from the menu.
+   */
+  private static enum Type {
+    /**
+     * The user selected an option which did not return a result, so
+     * the menu should be displayed again.
+     */
+    AGAIN,
+
+    /**
+     * The user did not select an option and instead chose to cancel
+     * the current task.
+     */
+    CANCEL,
+
+    /**
+     * The user did not select an option and instead chose to quit the
+     * entire application.
+     */
+    QUIT,
+
+    /**
+     * The user selected an option which succeeded and returned one or
+     * more result values.
+     */
+    SUCCESS
+  }
+
+
+
+  /**
+   * Creates a new menu result indicating that the menu should be
+   * displayed again. A good example of this is when a user chooses to
+   * view some help. Normally, after the help is displayed, the user
+   * is allowed to select another option.
+   *
+   * @param <T>
+   *          The type of result value(s) contained in success
+   *          results. Use <code>Void</code> if success results
+   *          should not contain values.
+   * @return Returns a new menu result indicating that the menu should
+   *         be displayed again.
+   */
+  public static <T> MenuResult<T> again() {
+    return new MenuResult<T>(Type.AGAIN, Collections.<T> emptyList());
+  }
+
+
+
+  /**
+   * Creates a new menu result indicating that the user chose to
+   * cancel any task currently in progress and go back to the previous
+   * main menu if applicable.
+   *
+   * @param <T>
+   *          The type of result value(s) contained in success
+   *          results. Use <code>Void</code> if success results
+   *          should not contain values.
+   * @return Returns a new menu result indicating that the user chose
+   *         to cancel any task currently in progress and go back to
+   *         the previous main menu if applicable.
+   */
+  public static <T> MenuResult<T> cancel() {
+    return new MenuResult<T>(Type.CANCEL, Collections.<T> emptyList());
+  }
+
+
+
+  /**
+   * Creates a new menu result indicating that the user chose to quit
+   * the application and cancel all outstanding tasks.
+   *
+   * @param <T>
+   *          The type of result value(s) contained in success
+   *          results. Use <code>Void</code> if success results
+   *          should not contain values.
+   * @return Returns a new menu result indicating that the user chose
+   *         to quit the application and cancel all outstanding tasks.
+   */
+  public static <T> MenuResult<T> quit() {
+    return new MenuResult<T>(Type.QUIT, Collections.<T> emptyList());
+  }
+
+
+
+  /**
+   * Creates a new menu result indicating that the user chose to apply
+   * any task currently in progress and go back to the previous menu
+   * if applicable. The menu result will not contain any result
+   * values.
+   *
+   * @param <T>
+   *          The type of result value(s) contained in success
+   *          results. Use <code>Void</code> if success results
+   *          should not contain values.
+   * @return Returns a new menu result indicating that the user chose
+   *         to apply any task currently in progress and go back to
+   *         the previous menu if applicable.The menu result will not
+   *         contain any result values.
+   */
+  public static <T> MenuResult<T> success() {
+    return success(Collections.<T> emptySet());
+  }
+
+
+
+  /**
+   * Creates a new menu result indicating that the user chose to apply
+   * any task currently in progress and go back to the previous menu
+   * if applicable. The menu result will contain the provided values,
+   * which can be retrieved using {@link #getValue()} or
+   * {@link #getValues()}.
+   *
+   * @param <T>
+   *          The type of the result values.
+   * @param values
+   *          The result values.
+   * @return Returns a new menu result indicating that the user chose
+   *         to apply any task currently in progress and go back to
+   *         the previous menu if applicable. The menu result will
+   *         contain the provided values, which can be retrieved using
+   *         {@link #getValue()} or {@link #getValues()}.
+   */
+  public static <T> MenuResult<T> success(Collection<T> values) {
+    return new MenuResult<T>(Type.SUCCESS, new ArrayList<T>(values));
+  }
+
+
+
+  /**
+   * Creates a new menu result indicating that the user chose to apply
+   * any task currently in progress and go back to the previous menu
+   * if applicable. The menu result will contain the provided value,
+   * which can be retrieved using {@link #getValue()} or
+   * {@link #getValues()}.
+   *
+   * @param <T>
+   *          The type of the result value.
+   * @param value
+   *          The result value.
+   * @return Returns a new menu result indicating that the user chose
+   *         to apply any task currently in progress and go back to
+   *         the previous menu if applicable. The menu result will
+   *         contain the provided value, which can be retrieved using
+   *         {@link #getValue()} or {@link #getValues()}.
+   */
+  public static <T> MenuResult<T> success(T value) {
+    return success(Collections.singleton(value));
+  }
+
+  // The type of result returned from the menu.
+  private final Type type;
+
+  // The menu result value(s).
+  private final Collection<T> values;
+
+
+
+  // Private constructor.
+  private MenuResult(Type type, Collection<T> values) {
+    this.type = type;
+    this.values = values;
+  }
+
+
+
+  /**
+   * Gets the menu result value if this is a menu result indicating
+   * success.
+   *
+   * @return Returns the menu result value, or <code>null</code> if
+   *         there was no result value or if this is not a success
+   *         menu result.
+   * @see #isSuccess()
+   */
+  public T getValue() {
+    if (values.isEmpty()) {
+      return null;
+    } else {
+      return values.iterator().next();
+    }
+  }
+
+
+
+  /**
+   * Gets the menu result values if this is a menu result indicating
+   * success.
+   *
+   * @return Returns the menu result values, which may be empty if
+   *         there were no result values or if this is not a success
+   *         menu result.
+   * @see #isSuccess()
+   */
+  public Collection<T> getValues() {
+    return new ArrayList<T>(values);
+  }
+
+
+
+  /**
+   * Determines if this menu result indicates that the menu should be
+   * displayed again. A good example of this is when a user chooses to
+   * view some help. Normally, after the help is displayed, the user
+   * is allowed to select another option.
+   *
+   * @return Returns <code>true</code> if this menu result indicates
+   *         that the menu should be displayed again.
+   */
+  public boolean isAgain() {
+    return type == Type.AGAIN;
+  }
+
+
+
+  /**
+   * Determines if this menu result indicates that the user chose to
+   * cancel any task currently in progress and go back to the previous
+   * main menu if applicable.
+   *
+   * @return Returns <code>true</code> if this menu result indicates
+   *         that the user chose to cancel any task currently in
+   *         progress and go back to the previous main menu if
+   *         applicable.
+   */
+  public boolean isCancel() {
+    return type == Type.CANCEL;
+  }
+
+
+
+  /**
+   * Determines if this menu result indicates that the user chose to
+   * quit the application and cancel all outstanding tasks.
+   *
+   * @return Returns <code>true</code> if this menu result indicates
+   *         that the user chose to quit the application and cancel
+   *         all outstanding tasks.
+   */
+  public boolean isQuit() {
+    return type == Type.QUIT;
+  }
+
+
+
+  /**
+   * Determines if this menu result indicates that the user chose to
+   * apply any task currently in progress and go back to the previous
+   * menu if applicable. Any result values can be retrieved using the
+   * {@link #getValue()} or {@link #getValues()} methods.
+   *
+   * @return Returns <code>true</code> if this menu result indicates
+   *         that the user chose to apply any task currently in
+   *         progress and go back to the previous menu if applicable.
+   * @see #getValue()
+   * @see #getValues()
+   */
+  public boolean isSuccess() {
+    return type == Type.SUCCESS;
+  }
+}
diff --git a/opends/src/server/org/opends/server/util/cli/OutputStreamConsoleApplication.java b/opends/src/server/org/opends/server/util/cli/OutputStreamConsoleApplication.java
new file mode 100644
index 0000000..e0aa77a
--- /dev/null
+++ b/opends/src/server/org/opends/server/util/cli/OutputStreamConsoleApplication.java
@@ -0,0 +1,105 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License, Version 1.0 only
+ * (the "License").  You may not use this file except in compliance
+ * with the License.
+ *
+ * You can obtain a copy of the license at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE
+ * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at
+ * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
+ * add the following below this CDDL HEADER, with the fields enclosed
+ * by brackets "[]" replaced with your own identifying information:
+ *      Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ *
+ *
+ *      Portions Copyright 2007 Sun Microsystems, Inc.
+ */
+package org.opends.server.util.cli;
+
+
+
+/**
+ * A console application decorator which redirects all output to the
+ * underlying application's output stream.
+ */
+public class OutputStreamConsoleApplication extends ConsoleApplication {
+
+  // The underlying console application.
+  private final ConsoleApplication app;
+
+
+
+  /**
+   * Creates a new console application instance which redirects all
+   * output to the underlying application's output stream.
+   *
+   * @param app
+   *          The underlying application console.
+   */
+  public OutputStreamConsoleApplication(ConsoleApplication app) {
+    super(app.getInputStream(), app.getOutputStream(), app.getOutputStream());
+
+    this.app = app;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public boolean isInteractive() {
+    return app.isInteractive();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public boolean isMenuDrivenMode() {
+    return app.isMenuDrivenMode();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public boolean isQuiet() {
+    return app.isQuiet();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public boolean isScriptFriendly() {
+    return app.isScriptFriendly();
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public boolean isVerbose() {
+    return app.isVerbose();
+  }
+
+}
diff --git a/opends/src/server/org/opends/server/tools/dsconfig/ValidationCallback.java b/opends/src/server/org/opends/server/util/cli/ValidationCallback.java
similarity index 88%
rename from opends/src/server/org/opends/server/tools/dsconfig/ValidationCallback.java
rename to opends/src/server/org/opends/server/util/cli/ValidationCallback.java
index a8f7750..cefe601 100644
--- a/opends/src/server/org/opends/server/tools/dsconfig/ValidationCallback.java
+++ b/opends/src/server/org/opends/server/util/cli/ValidationCallback.java
@@ -24,9 +24,7 @@
  *
  *      Portions Copyright 2007 Sun Microsystems, Inc.
  */
-package org.opends.server.tools.dsconfig;
-
-import org.opends.server.tools.ClientException;
+package org.opends.server.util.cli;
 
 
 
@@ -36,7 +34,7 @@
  * @param <T>
  *          The type of the decoded input.
  */
-interface ValidationCallback<T> {
+public interface ValidationCallback<T> {
 
   /**
    * Validates and decodes the user-provided input. Implementations
@@ -51,9 +49,9 @@
    *          The user input to be validated.
    * @return Returns the decoded input if the input is valid, or
    *         <code>null</code> if it is not.
-   * @throws ClientException
+   * @throws CLIException
    *           If an unexpected error occurred which prevented
    *           validation.
    */
-  T validate(ConsoleApplication app, String input) throws ClientException;
+  T validate(ConsoleApplication app, String input) throws CLIException;
 }
diff --git a/opends/src/server/org/opends/server/tools/dsconfig/HelpCallback.java b/opends/src/server/org/opends/server/util/cli/package-info.java
similarity index 60%
copy from opends/src/server/org/opends/server/tools/dsconfig/HelpCallback.java
copy to opends/src/server/org/opends/server/util/cli/package-info.java
index 8412933..0d8f7ac 100644
--- a/opends/src/server/org/opends/server/tools/dsconfig/HelpCallback.java
+++ b/opends/src/server/org/opends/server/util/cli/package-info.java
@@ -22,22 +22,23 @@
  * CDDL HEADER END
  *
  *
- *      Portions Copyright 2007 Sun Microsystems, Inc.
+ *      Portions Copyright 2006-2007 Sun Microsystems, Inc.
  */
-package org.opends.server.tools.dsconfig;
-
-
 
 /**
- * An interface for displaying help interactively.
+ * Provides a high-level framework for implementing command-line
+ * tools.
+ * <p>
+ * The {@link ConsoleApplication} interface can be used as a basis for
+ * console based applications. It includes common utility methods for
+ * interacting with the console.
+ * <p>
+ * The {@link MenuBuilder} and associated classes and interfaces can
+ * be used to implement text based menu driven applications.
  */
-interface HelpCallback {
+@org.opends.server.types.PublicAPI(
+    stability = org.opends.server.types.StabilityLevel.PRIVATE)
+package org.opends.server.util.cli;
 
-  /**
-   * Displays help to the provided application console.
-   *
-   * @param app
-   *          The console application.
-   */
-  void display(ConsoleApplication app);
-}
+
+

--
Gitblit v1.10.0