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

jvergara
19.09.2008 13a63611d24de8c5727e62a215b26354c1a22278
Fix for issue 2197 (dsconfig: interactive mode should display the effective dsconfig command)

After discussing with different members the following has been committed:

* The current behavior is kept (no additional output by default).

* Two new options have been added:
--displayEquivalentCommand
Display the equivalent non-interactive argument in the standard output when
this command is run in interactive mode
--equivalentCommandFilePath {path}
The full path to the file where the equivalent non-interactive
commands will be written when this command is run in interactive mode


So the user will have to explicitly ask to display the equivalent command-line (or to dump it to a file). This approach privileges the users that configure the server using interactive dsconfig instead of those whose ultimate goal is to create scripts to configure the server (and to whom this feature is targetted).

In terms of implementation basically what is done is to create a new class called CommandBuilder when we store the equivalent arguments to what the user provides interactively. When the user applies a change (or asks to display a list of objects) the equivalent command-line is displayed by using the contents stored in the CommandBuilder object.
2 files added
17 files modified
1981 ■■■■■ changed files
opends/src/messages/messages/dsconfig.properties 15 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/admin/condition/ContainsCondition.java 21 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tools/ToolConstants.java 12 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tools/dsconfig/CreateSubCommandHandler.java 308 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tools/dsconfig/DSConfig.java 249 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tools/dsconfig/DeleteSubCommandHandler.java 16 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tools/dsconfig/GetPropSubCommandHandler.java 9 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tools/dsconfig/HelpSubCommandHandler.java 11 ●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tools/dsconfig/InternalManagementContextFactory.java 9 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tools/dsconfig/LDAPManagementContextFactory.java 13 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tools/dsconfig/ListSubCommandHandler.java 6 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tools/dsconfig/ManagementContextFactory.java 9 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tools/dsconfig/PropertyEditorModification.java 198 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tools/dsconfig/PropertyValueEditor.java 372 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tools/dsconfig/SetPropSubCommandHandler.java 188 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/tools/dsconfig/SubCommandHandler.java 106 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/util/args/ArgumentParser.java 4 ●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/util/cli/CommandBuilder.java 287 ●●●●● patch | view | raw | blame | history
opends/src/server/org/opends/server/util/cli/LDAPConnectionConsoleInteraction.java 148 ●●●●● patch | view | raw | blame | history
opends/src/messages/messages/dsconfig.properties
@@ -380,7 +380,7 @@
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_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
@@ -449,3 +449,16 @@
 implementation class
INFO_DSCFG_GENERIC_TYPE_SYNOPSIS_149=A Generic %s
INFO_DSCFG_CREATE_TYPE_HELP_HEADING_150=Help: %s
INFO_DSCFG_NON_INTERACTIVE_151=The equivalent non-interactive command-line is:\
 %n%s
INFO_DSCFG_DESCRIPTION_DISPLAY_EQUIVALENT_152=Display the equivalent \
 non-interactive argument in the standard output when this command is run in \
 interactive mode
INFO_DSCFG_DESCRIPTION_EQUIVALENT_COMMAND_FILE_PATH_153=The full path to the \
 file where the equivalent non-interactive commands will be written when this \
 command is run in interactive mode
MILD_ERR_DSCFG_ERROR_WRITING_EQUIVALENT_COMMAND_LINE_154=An error occurred \
 while attempting to write equivalent non-interactive command line to file \
 %s.  Error details:  %s
SEVERE_ERR_DSCFG_CANNOT_WRITE_EQUIVALENT_COMMAND_LINE_FILE_155=Cannot write \
 to file %s.  Verify that you have access rights to that file
opends/src/server/org/opends/server/admin/condition/ContainsCondition.java
@@ -58,10 +58,10 @@
  private static final class Impl<T> implements Condition {
    // The property.
    private final PropertyDefinition<T> pd;
    final PropertyDefinition<T> pd;
    // The required property value.
    private final T value;
    final T value;
@@ -194,4 +194,21 @@
    this.impl = new Impl<T>(pd, value);
  }
  /**
   * Returns the property definition associated with this condition.
   * @return the property definition associated with this condition.
   */
  public PropertyDefinition<?> getPropertyDefinition()
  {
    return impl.pd;
  }
  /**
   * Returns the value that must be set for this condition to be fulfilled.
   * @return the value that must be set for this condition to be fulfilled.
   */
  public Object getValue()
  {
    return impl.value;
  }
}
opends/src/server/org/opends/server/tools/ToolConstants.java
@@ -702,5 +702,17 @@
   * The default separator to be used in tables.
   */
  public static final String LIST_TABLE_SEPARATOR = ":";
  /**
   * Display the equivalent non-interactive command.
   */
  public static final String OPTION_DSCFG_LONG_DISPLAY_EQUIVALENT =
    "displayEquivalentCommand";
  /**
   * The path where we write the equivalent non-interactive command.
   */
  public static final String OPTION_LONG_EQUIVALENT_COMMAND_FILE_PATH =
    "equivalentCommandFilePath";
}
opends/src/server/org/opends/server/tools/dsconfig/CreateSubCommandHandler.java
@@ -80,6 +80,7 @@
import org.opends.server.protocols.ldap.LDAPResultCode;
import org.opends.server.tools.ClientException;
import org.opends.server.tools.ToolConstants;
import org.opends.server.util.args.Argument;
import org.opends.server.util.args.ArgumentException;
import org.opends.server.util.args.StringArgument;
import org.opends.server.util.args.SubCommand;
@@ -356,6 +357,16 @@
  private static final Character OPTION_DSCFG_SHORT_TYPE = 't';
  /**
   * The value for the long option remove (this is used only internally).
   */
  private static final String OPTION_DSCFG_LONG_REMOVE = "remove";
  /**
   * The value for the long option reset (this is used only internally).
   */
  private static final String OPTION_DSCFG_LONG_RESET = "reset";
  /**
   * Creates a new create-xxx sub-command for an instantiable
@@ -410,7 +421,6 @@
  }
  /**
   * Interactively lets a user create a new managed object beneath a
   * parent.
@@ -445,6 +455,46 @@
      ConsoleApplication app, ManagementContext context,
      ManagedObject<?> parent, InstantiableRelationDefinition<C, S> rd)
      throws ClientException, CLIException {
    return createManagedObject(app, context, parent, rd, null);
  }
  /**
   * Interactively lets a user create a new managed object beneath a
   * parent.
   *
   * @param <C>
   *          The type of managed object which can be created.
   * @param <S>
   *          The type of server managed object which can be created.
   * @param app
   *          The console application.
   * @param context
   *          The management context.
   * @param parent
   *          The parent managed object.
   * @param rd
   *          The relation beneath which the child managed object
   *          should be created.
   * @param handler
   *          The subcommand handler whose command builder must be updated.
   * @return Returns a MenuResult.success() containing the name of the
   *         created managed object if it was created successfully, or
   *         MenuResult.quit(), or MenuResult.cancel(), if the managed
   *         object was edited interactively and the user chose to
   *         quit or cancel.
   * @throws ClientException
   *           If an unrecoverable client exception occurred whilst
   *           interacting with the server.
   * @throws CLIException
   *           If an error occurred whilst interacting with the
   *           console.
   */
  private static <C extends ConfigurationClient, S extends Configuration>
      MenuResult<String> createManagedObject(
      ConsoleApplication app, ManagementContext context,
      ManagedObject<?> parent, InstantiableRelationDefinition<C, S> rd,
      SubCommandHandler handler)
      throws ClientException, CLIException {
    AbstractManagedObjectDefinition<C, S> d = rd.getChildDefinition();
    // First determine what type of component the user wants to create.
@@ -470,7 +520,7 @@
      createChildInteractively(app, parent, rd, mod, exceptions);
    // Let the user interactively configure the managed object and commit it.
    MenuResult<Void> result2 = commitManagedObject(app, context, mo);
    MenuResult<Void> result2 = commitManagedObject(app, context, mo, handler);
    if (result2.isCancel()) {
      return MenuResult.cancel();
    } else if (result2.isQuit()) {
@@ -485,7 +535,8 @@
  // Check that any referenced components are enabled if
  // required.
  private static MenuResult<Void> checkReferences(ConsoleApplication app,
      ManagementContext context, ManagedObject<?> mo) throws ClientException,
      ManagementContext context, ManagedObject<?> mo,
      SubCommandHandler handler) throws ClientException,
      CLIException {
    ManagedObjectDefinition<?, ?> d = mo.getManagedObjectDefinition();
    Message ufn = d.getUserFriendlyName();
@@ -549,7 +600,7 @@
                    if (app.confirmAction(INFO_DSCFG_PROMPT_EDIT.get(rufn),
                        true)) {
                      MenuResult<Void> result = SetPropSubCommandHandler
                          .modifyManagedObject(app, context, ref);
                          .modifyManagedObject(app, context, ref, handler);
                      if (result.isQuit()) {
                        return result;
                      } else if (result.isSuccess()) {
@@ -570,7 +621,7 @@
                    if (app.confirmAction(INFO_DSCFG_PROMPT_EDIT.get(rufn),
                        true)) {
                      MenuResult<Void> result = SetPropSubCommandHandler
                          .modifyManagedObject(app, context, ref);
                          .modifyManagedObject(app, context, ref, handler);
                      if (result.isQuit()) {
                        return result;
                      } else if (result.isSuccess()) {
@@ -589,7 +640,7 @@
                if (app.confirmAction(INFO_DSCFG_PROMPT_EDIT_TO_ENABLE.get(
                    rufn, name, ufn), true)) {
                  MenuResult<Void> result = SetPropSubCommandHandler
                      .modifyManagedObject(app, context, ref);
                      .modifyManagedObject(app, context, ref, handler);
                  if (result.isQuit()) {
                    return result;
                  } else if (result.isSuccess()) {
@@ -634,12 +685,14 @@
  // Commit a new managed object's configuration.
  private static MenuResult<Void> commitManagedObject(ConsoleApplication app,
      ManagementContext context, ManagedObject<?> mo) throws ClientException,
      ManagementContext context, ManagedObject<?> mo, SubCommandHandler handler)
      throws ClientException,
      CLIException {
    ManagedObjectDefinition<?, ?> d = mo.getManagedObjectDefinition();
    Message ufn = d.getUserFriendlyName();
    while (true) {
      PropertyValueEditor editor = new PropertyValueEditor(app, context);
      // Interactively set properties if applicable.
      if (app.isInteractive()) {
        SortedSet<PropertyDefinition<?>> properties =
@@ -654,12 +707,11 @@
          properties.add(pd);
        }
        PropertyValueEditor editor = new PropertyValueEditor(app, context);
        MenuResult<Void> result = editor.edit(mo, properties, false);
        // Interactively enable/edit referenced components.
        if (result.isSuccess()) {
          result = checkReferences(app, context, mo);
          result = checkReferences(app, context, mo, handler);
          if (result.isAgain()) {
            // Edit again.
            continue;
@@ -687,6 +739,22 @@
        Message msg = INFO_DSCFG_CONFIRM_CREATE_SUCCESS.get(ufn);
        app.printVerboseMessage(msg);
        for (PropertyEditorModification mod : editor.getModifications())
        {
          try
          {
            Argument arg = createArgument(mod);
            handler.getCommandBuilder().addArgument(arg);
          }
          catch (ArgumentException ae)
          {
            // This is a bug
            throw new RuntimeException(
                "Unexpected error generating the command builder: "+ae, ae);
          }
        }
        handler.setCommandBuilderUseful(true);
        return MenuResult.success();
      } catch (MissingMandatoryPropertiesException e) {
        if (app.isInteractive()) {
@@ -984,6 +1052,8 @@
  // Common constructor.
  private CreateSubCommandHandler(
      SubCommandArgumentParser parser, ManagedObjectPath<?, ?> p,
@@ -1128,6 +1198,14 @@
    // Get the naming argument values.
    List<String> names = getNamingArgValues(app, namingArgs);
    // Reset the command builder
    getCommandBuilder().clearArguments();
    setCommandBuilderUseful(false);
    // Update the command builder.
    updateCommandBuilderWithSubCommand();
    // Encode the provided properties.
    List<String> propertyArgs = propertySetArgument.getValues();
    MyPropertyProvider provider = new MyPropertyProvider(d,
@@ -1178,6 +1256,8 @@
    ManagedObject<? extends C> child;
    List<DefaultBehaviorException> exceptions =
      new LinkedList<DefaultBehaviorException>();
    boolean isNameProvidedInteractively = false;
    String providedNamingArgName = null;
    if (relation instanceof InstantiableRelationDefinition) {
      InstantiableRelationDefinition<C, S> irelation =
        (InstantiableRelationDefinition<C, S>) relation;
@@ -1188,6 +1268,9 @@
          app.println();
          child = createChildInteractively(app, parent, irelation,
              d, exceptions);
          isNameProvidedInteractively = true;
          providedNamingArgName =
            CLIProfile.getInstance().getNamingArgument(irelation);
        } else {
          throw ArgumentExceptionFactory
              .missingMandatoryNonInteractiveArgument(namingArgs.get(names
@@ -1219,12 +1302,132 @@
    // Now the command line changes have been made, create the managed
    // object interacting with the user to fix any problems if
    // required.
    MenuResult<Void> result2 = commitManagedObject(app, context, child);
    MenuResult<Void> result2 = commitManagedObject(app, context, child, this);
    if (result2.isCancel()) {
      return MenuResult.cancel();
    } else if (result2.isQuit()) {
      return MenuResult.quit();
    } else {
      if (typeArgument.hasValue())
      {
        getCommandBuilder().addArgument(typeArgument);
      }
      else
      {
        // Set the type provided by the user
        StringArgument arg = new StringArgument(typeArgument.getName(),
            OPTION_DSCFG_SHORT_TYPE,
            OPTION_DSCFG_LONG_TYPE, false, false, true,
            INFO_TYPE_PLACEHOLDER.get(), typeArgument.getDefaultValue(),
            typeArgument.getPropertyName(),
            typeArgument.getDescription());
        arg.addValue(getTypeName(d));
        getCommandBuilder().addArgument(arg);
      }
      if (propertySetArgument.hasValue())
      {
        /*
          We might have some conflicts in terms of arguments: the user might
           have provided some values that were not good and then these have
           overwritten when asking for them interactively: filter them */
        StringArgument filteredArg = new StringArgument(OPTION_DSCFG_LONG_SET,
            OPTION_DSCFG_SHORT_SET, OPTION_DSCFG_LONG_SET, false, true, true,
            INFO_VALUE_SET_PLACEHOLDER.get(), null, null,
            INFO_DSCFG_DESCRIPTION_PROP_VAL.get());
        for (String value : propertySetArgument.getValues())
        {
          boolean addValue = true;
          int index = value.indexOf(':');
          if (index != -1)
          {
            String propName = value.substring(0, index);
            for (Argument arg : getCommandBuilder().getArguments())
            {
              for (String value2 : arg.getValues())
              {
                String prop2Name;
                if (arg.getName().equals(OPTION_DSCFG_LONG_SET) ||
                    arg.getName().equals(OPTION_DSCFG_LONG_REMOVE))
                {
                  int index2 = value2.indexOf(':');
                  if (index2 != -1)
                  {
                    prop2Name = value2.substring(0, index2);
                  }
                  else
                  {
                    prop2Name = null;
                  }
                }
                else if (arg.getName().equals(OPTION_DSCFG_LONG_RESET))
                {
                  prop2Name = value2;
                }
                else
                {
                  prop2Name = null;
                }
                if (prop2Name != null)
                {
                  if (prop2Name.equalsIgnoreCase(propName))
                  {
                    addValue = false;
                    break;
                  }
                }
              }
              if (!addValue)
              {
                break;
              }
            }
          }
          else
          {
            addValue = false;
          }
          if (addValue)
          {
            filteredArg.addValue(value);
          }
        }
        if (filteredArg.hasValue())
        {
          getCommandBuilder().addArgument(filteredArg);
        }
      }
      /* Filter the arguments that are used internally */
      List<Argument> argsCopy = new LinkedList<Argument>(
          getCommandBuilder().getArguments());
      for (Argument arg : argsCopy)
      {
        if (arg.getName().equals(OPTION_DSCFG_LONG_RESET) ||
            arg.getName().equals(OPTION_DSCFG_LONG_REMOVE))
        {
          getCommandBuilder().removeArgument(arg);
        }
      }
      if (isNameProvidedInteractively)
      {
        StringArgument arg = new StringArgument(providedNamingArgName, null,
            providedNamingArgName, false, true,
            INFO_NAME_PLACEHOLDER.get(),
            INFO_DSCFG_DESCRIPTION_NAME_CREATE.get(d.getUserFriendlyName()));
        arg.addValue(child.getManagedObjectPath().getName());
        getCommandBuilder().addArgument(arg);
      }
      else
      {
        for (StringArgument arg : namingArgs)
        {
          if (arg.isPresent())
          {
            getCommandBuilder().addArgument(arg);
          }
        }
      }
      return MenuResult.success(0);
    }
  }
@@ -1240,4 +1443,89 @@
    // validated.
    mo.setPropertyValues(pd, values);
  }
  /**
   * Creates an argument (the one that the user should provide in the
   * command-line) that is equivalent to the modification proposed by the user
   * in the provided PropertyEditorModification object.
   * @param mod the object describing the modification made.
   * @return the argument representing the modification.
   * @throws ArgumentException if there is a problem creating the argument.
   */
  private static Argument createArgument(PropertyEditorModification mod)
  throws ArgumentException
  {
    StringArgument arg;
    String propName = mod.getPropertyDefinition().getName();
    switch (mod.getType())
    {
    case ADD:
      arg = new StringArgument(OPTION_DSCFG_LONG_SET,
          OPTION_DSCFG_SHORT_SET, OPTION_DSCFG_LONG_SET, false, true, true,
          INFO_VALUE_SET_PLACEHOLDER.get(), null, null,
          INFO_DSCFG_DESCRIPTION_PROP_VAL.get());
      for (Object value : mod.getModificationValues())
      {
        arg.addValue(propName+':'+value);
      }
      break;
    case SET:
      arg = new StringArgument(OPTION_DSCFG_LONG_SET,
          OPTION_DSCFG_SHORT_SET, OPTION_DSCFG_LONG_SET, false, true, true,
          INFO_VALUE_SET_PLACEHOLDER.get(), null, null,
          INFO_DSCFG_DESCRIPTION_PROP_VAL.get());
      for (Object value : mod.getModificationValues())
      {
        arg.addValue(propName+':'+value);
      }
      break;
    case RESET:
      arg = new StringArgument(OPTION_DSCFG_LONG_RESET,
          null, OPTION_DSCFG_LONG_RESET, false, true, true,
          INFO_PROPERTY_PLACEHOLDER.get(), null, null,
          INFO_DSCFG_DESCRIPTION_RESET_PROP.get());
      arg.addValue(propName);
      break;
    case REMOVE:
      arg = new StringArgument(OPTION_DSCFG_LONG_REMOVE,
          null, OPTION_DSCFG_LONG_REMOVE, false, true,
          true, INFO_VALUE_SET_PLACEHOLDER.get(), null, null,
          INFO_DSCFG_DESCRIPTION_REMOVE_PROP_VAL.get());
      for (Object value : mod.getModificationValues())
      {
        arg.addValue(propName+':'+value);
      }
      arg = null;
      break;
    default:
      // Bug
      throw new IllegalStateException("Unknown modification type: "+
          mod.getType());
    }
    return arg;
  }
  /**
   * Returns the type name for a given ManagedObjectDefinition.
   * @param d the ManagedObjectDefinition.
   * @return the type name for the provided ManagedObjectDefinition.
   */
  private String getTypeName(
      ManagedObjectDefinition<? extends C, ? extends S> d)
  {
    String name = d.getName();
    for (String key : types.keySet())
    {
      ManagedObjectDefinition<? extends C, ? extends S> current =
        types.get(key);
      if (current.equals(d))
      {
        name = key;
        break;
      }
    }
    return name;
  }
}
opends/src/server/org/opends/server/tools/dsconfig/DSConfig.java
@@ -35,6 +35,9 @@
import static org.opends.server.tools.dsconfig.ArgumentExceptionFactory.*;
import static org.opends.server.util.StaticUtils.*;
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Comparator;
@@ -46,6 +49,7 @@
import java.util.TreeSet;
import org.opends.messages.Message;
import org.opends.quicksetup.util.Utils;
import org.opends.server.admin.AttributeTypePropertyDefinition;
import org.opends.server.admin.ClassLoaderProvider;
import org.opends.server.admin.ClassPropertyDefinition;
@@ -60,6 +64,7 @@
import org.opends.server.types.DebugLogLevel;
import org.opends.server.types.InitializationException;
import org.opends.server.util.EmbeddedUtils;
import org.opends.server.util.ServerConstants;
import org.opends.server.util.StaticUtils;
import org.opends.server.util.args.ArgumentException;
import org.opends.server.util.args.BooleanArgument;
@@ -68,6 +73,7 @@
import org.opends.server.util.args.SubCommandArgumentParser;
import org.opends.server.util.args.ArgumentGroup;
import org.opends.server.util.cli.CLIException;
import org.opends.server.util.cli.CommandBuilder;
import org.opends.server.util.cli.ConsoleApplication;
import org.opends.server.util.cli.Menu;
import org.opends.server.util.cli.MenuBuilder;
@@ -116,6 +122,11 @@
        if (result.isQuit()) {
          return result;
        } else {
          if (result.isSuccess() && isInteractive() &&
              handler.isCommandBuilderUseful())
          {
            printCommandBuilder(getCommandBuilder(handler));
          }
          // Success or cancel.
          app.println();
          app.pressReturnToContinue();
@@ -364,6 +375,14 @@
  // behavior.
  private BooleanArgument noPromptArgument;
  // The argument that the user must set to display the equivalent
  // non-interactive mode argument
  private BooleanArgument displayEquivalentArgument;
  // The argument that allows the user to dump the equivalent non-interactive
  // command to a file.
  private StringArgument equivalentCommandFileArgument;
  // The command-line argument parser.
  private final SubCommandArgumentParser parser;
@@ -387,6 +406,10 @@
  // properties file.
  private BooleanArgument noPropertiesFileArgument;
  // The boolean that is used to know if data must be appended to the file
  // containing equivalent non-interactive commands.
  private boolean alreadyWroteEquivalentCommand;
  /**
   * Creates a new dsconfig application instance.
   *
@@ -540,6 +563,19 @@
          OPTION_LONG_HELP, INFO_DSCFG_DESCRIPTION_SHOW_GROUP_USAGE_SUMMARY
              .get());
      displayEquivalentArgument = new BooleanArgument(
          OPTION_DSCFG_LONG_DISPLAY_EQUIVALENT,
          null, OPTION_DSCFG_LONG_DISPLAY_EQUIVALENT,
          INFO_DSCFG_DESCRIPTION_DISPLAY_EQUIVALENT.get());
      advancedModeArgument.setPropertyName(
          OPTION_DSCFG_LONG_DISPLAY_EQUIVALENT);
      equivalentCommandFileArgument = new StringArgument(
          OPTION_LONG_EQUIVALENT_COMMAND_FILE_PATH, null,
          OPTION_LONG_EQUIVALENT_COMMAND_FILE_PATH, false, false, true,
          INFO_PATH_PLACEHOLDER.get(), null, null,
          INFO_DSCFG_DESCRIPTION_EQUIVALENT_COMMAND_FILE_PATH.get());
      propertiesFileArgument = new StringArgument("propertiesFilePath",
          null, OPTION_LONG_PROP_FILE_PATH,
          false, false, true, INFO_PROP_FILE_PATH_PLACEHOLDER.get(), null, null,
@@ -561,6 +597,8 @@
      parser.addGlobalArgument(quietArgument);
      parser.addGlobalArgument(scriptFriendlyArgument);
      parser.addGlobalArgument(noPromptArgument);
      parser.addGlobalArgument(displayEquivalentArgument);
      parser.addGlobalArgument(equivalentCommandFileArgument);
      parser.addGlobalArgument(propertiesFileArgument);
      parser.setFilePropertiesArgument(propertiesFileArgument);
      parser.addGlobalArgument(noPropertiesFileArgument);
@@ -711,6 +749,18 @@
      return 1;
    }
    // Check that we can write on the provided path where we write the
    // equivalent non-interactive commands.
    if (equivalentCommandFileArgument.isPresent())
    {
      String file = equivalentCommandFileArgument.getValue();
      if (!Utils.canWrite(file))
      {
        println(ERR_DSCFG_CANNOT_WRITE_EQUIVALENT_COMMAND_LINE_FILE.get(file));
        return 1;
      }
    }
    // Make sure that management context's arguments are valid.
    try {
      factory.validateGlobalArguments();
@@ -724,32 +774,32 @@
      hasSubCommand = false;
      if (isInteractive()) {
          // Top-level interactive mode.
          retCode = runInteractiveMode();
        } else {
          Message message = ERR_ERROR_PARSING_ARGS
              .get(ERR_DSCFG_ERROR_MISSING_SUBCOMMAND.get());
          displayMessageAndUsageReference(message);
          retCode = 1;
        }
        // Top-level interactive mode.
        retCode = runInteractiveMode();
      } else {
        hasSubCommand = true;
        // Retrieve the sub-command implementation and run it.
        SubCommandHandler handler = handlers.get(parser.getSubCommand());
        retCode = runSubCommand(handler);
        Message message = ERR_ERROR_PARSING_ARGS
        .get(ERR_DSCFG_ERROR_MISSING_SUBCOMMAND.get());
        displayMessageAndUsageReference(message);
        retCode = 1;
      }
    } else {
      hasSubCommand = true;
      try {
        // Close the Management context ==> an LDAP UNBIND is sent
        factory.close();
      } catch (Exception e) {
      // Nothing to report in this case
      }
      return retCode;
      // Retrieve the sub-command implementation and run it.
      SubCommandHandler handler = handlers.get(parser.getSubCommand());
      retCode = runSubCommand(handler);
    }
    try {
      // Close the Management context ==> an LDAP UNBIND is sent
      factory.close();
    } catch (Exception e) {
      // Nothing to report in this case
    }
    return retCode;
  }
  // Run the top-level interactive console.
@@ -874,6 +924,13 @@
      MenuResult<Integer> result = handler.run(this, factory);
      if (result.isSuccess()) {
        if (isInteractive())
        {
          println();
          println(INFO_DSCFG_NON_INTERACTIVE.get(
              getCommandBuilder(handler).toString()));
        }
        return result.getValue();
      } else {
        // User must have quit.
@@ -918,4 +975,154 @@
      return 1;
    }
  }
  /**
   * Updates the command builder with the global options: script friendly,
   * verbose, etc. for a given subcommand.  It also adds systematically the
   * no-prompt option.
   * @param handler the subcommand handler.
   */
  private CommandBuilder getCommandBuilder(SubCommandHandler handler)
  {
    String commandName =
      System.getProperty(ServerConstants.PROPERTY_SCRIPT_NAME);
    if (commandName == null)
    {
      commandName = "dsconfig";
    }
    CommandBuilder commandBuilder =
      new CommandBuilder(commandName, handler.getSubCommand().getName());
    if (advancedModeArgument.isPresent())
    {
      commandBuilder.addArgument(advancedModeArgument);
    }
    commandBuilder.append(handler.getCommandBuilder());
    if ((factory != null) && (factory.getContextCommandBuilder() != null))
    {
      commandBuilder.append(factory.getContextCommandBuilder());
    }
    if (verboseArgument.isPresent())
    {
      commandBuilder.addArgument(verboseArgument);
    }
    if (scriptFriendlyArgument.isPresent())
    {
      commandBuilder.addArgument(scriptFriendlyArgument);
    }
    commandBuilder.addArgument(noPromptArgument);
    if (propertiesFileArgument.isPresent())
    {
      commandBuilder.addArgument(propertiesFileArgument);
    }
    if (noPropertiesFileArgument.isPresent())
    {
      commandBuilder.addArgument(noPropertiesFileArgument);
    }
    return commandBuilder;
  }
  /**
   * Creates a command builder with the global options: script friendly,
   * verbose, etc. for a given subcommand name.  It also adds systematically the
   * no-prompt option.
   * @param subcommandName the subcommand name.
   * @return the command builder that has been created with the specified
   * subcommandName.
   */
  CommandBuilder getCommandBuilder(String subcommandName)
  {
    String commandName =
      System.getProperty(ServerConstants.PROPERTY_SCRIPT_NAME);
    if (commandName == null)
    {
      commandName = "dsconfig";
    }
    CommandBuilder commandBuilder =
      new CommandBuilder(commandName, subcommandName);
    if (advancedModeArgument.isPresent())
    {
      commandBuilder.addArgument(advancedModeArgument);
    }
    if ((factory != null) && (factory.getContextCommandBuilder() != null))
    {
      commandBuilder.append(factory.getContextCommandBuilder());
    }
    if (verboseArgument.isPresent())
    {
      commandBuilder.addArgument(verboseArgument);
    }
    if (scriptFriendlyArgument.isPresent())
    {
      commandBuilder.addArgument(scriptFriendlyArgument);
    }
    commandBuilder.addArgument(noPromptArgument);
    if (propertiesFileArgument.isPresent())
    {
      commandBuilder.addArgument(propertiesFileArgument);
    }
    if (noPropertiesFileArgument.isPresent())
    {
      commandBuilder.addArgument(noPropertiesFileArgument);
    }
    return commandBuilder;
  }
  /**
   * Prints the contents of a command builder.  This method has been created
   * since SetPropSubCommandHandler calls it.  All the logic of DSConfig is on
   * this method.  Currently it simply writes the content of the CommandBuilder
   * to the standard output, but if we provide an option to write the content
   * to a file only the implementation of this method must be changed.
   * @param commandBuilder the command builder to be printed.
   */
  void printCommandBuilder(CommandBuilder commandBuilder)
  {
    if (displayEquivalentArgument.isPresent())
    {
      println();
      // We assume that the app we are running is this one.
      println(INFO_DSCFG_NON_INTERACTIVE.get(commandBuilder.toString()));
    }
    if (equivalentCommandFileArgument.isPresent())
    {
      // Write to the file.
      boolean append = alreadyWroteEquivalentCommand;
      String file = equivalentCommandFileArgument.getValue();
      try
      {
        BufferedWriter writer =
          new BufferedWriter(new FileWriter(file, append));
        writer.write(commandBuilder.toString());
        writer.newLine();
        writer.flush();
        writer.close();
      }
      catch (IOException ioe)
      {
        println(ERR_DSCFG_ERROR_WRITING_EQUIVALENT_COMMAND_LINE.get(file,
            ioe.toString()));
      }
      alreadyWroteEquivalentCommand = true;
    }
  }
}
opends/src/server/org/opends/server/tools/dsconfig/DeleteSubCommandHandler.java
@@ -203,6 +203,10 @@
    // Get the naming argument values.
    List<String> names = getNamingArgValues(app, namingArgs);
    // Reset the command builder
    getCommandBuilder().clearArguments();
    setCommandBuilderUseful(false);
    // Delete the child managed object.
    ManagementContext context = factory.getManagementContext(app);
    MenuResult<ManagedObject<?>> result;
@@ -276,6 +280,7 @@
        }
        if (confirmDeletion(app)) {
          setCommandBuilderUseful(true);
          parent.removeChild(irelation, childName);
        } else {
          return MenuResult.cancel();
@@ -285,6 +290,8 @@
          (OptionalRelationDefinition<?, ?>) relation;
        if (confirmDeletion(app)) {
          setCommandBuilderUseful(true);
          parent.removeChild(orelation);
        } else {
          return MenuResult.cancel();
@@ -336,6 +343,15 @@
      throw new ClientException(LDAPResultCode.CLIENT_SIDE_SERVER_DOWN, msg);
    }
    // Add the naming arguments if they were provided.
    for (StringArgument arg : namingArgs)
    {
      if (arg.isPresent())
      {
        getCommandBuilder().addArgument(arg);
      }
    }
    // Output success message.
    Message msg = INFO_DSCFG_CONFIRM_DELETE_SUCCESS.get(ufn);
    app.printVerboseMessage(msg);
opends/src/server/org/opends/server/tools/dsconfig/GetPropSubCommandHandler.java
@@ -222,6 +222,14 @@
    // Get the naming argument values.
    List<String> names = getNamingArgValues(app, namingArgs);
    //  Reset the command builder
    getCommandBuilder().clearArguments();
    setCommandBuilderUseful(false);
    // Update the command builder.
    updateCommandBuilderWithSubCommand();
    // Get the targeted managed object.
    Message ufn = path.getRelationDefinition().getUserFriendlyName();
    ManagementContext context = factory.getManagementContext(app);
@@ -287,6 +295,7 @@
      if (propertyNames.isEmpty() || propertyNames.contains(pd.getName())) {
        displayProperty(app, builder, child, pd, valuePrinter);
        setCommandBuilderUseful(true);
      }
    }
opends/src/server/org/opends/server/tools/dsconfig/HelpSubCommandHandler.java
@@ -741,6 +741,8 @@
      new TreeMap<String, Map<String, AbstractManagedObjectDefinition<?, ?>>>();
    this.tagMap =
      new HashMap<Tag, Map<String, AbstractManagedObjectDefinition<?, ?>>>();
    setCommandBuilderUseful(false);
  }
@@ -816,11 +818,18 @@
  public MenuResult<Integer> run(ConsoleApplication app,
      ManagementContextFactory factory) throws ArgumentException,
      ClientException, CLIException {
    String categoryName = categoryArgument.getValue();
    String typeName = typeArgument.getValue();
    Tag tag = null;
    Set<String> propertyNames = getPropertyNames();
    // Reset the command builder
    getCommandBuilder().clearArguments();
    // Update the command builder.
    updateCommandBuilderWithSubCommand();
    List<AbstractManagedObjectDefinition<?, ?>> dlist =
      new LinkedList<AbstractManagedObjectDefinition<?, ?>>();
    AbstractManagedObjectDefinition<?, ?> tmp = null;
@@ -924,8 +933,6 @@
    return MenuResult.success(0);
  }
  // Output property summary table.
  private void displayNonVerbose(ConsoleApplication app, String categoryName,
      String typeName, Tag tag, Set<String> propertyNames) {
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.CommandBuilder;
import org.opends.server.util.cli.ConsoleApplication;
@@ -96,4 +97,12 @@
    // No implementation required.
  }
  /**
   * {@inheritDoc}
   */
  public CommandBuilder getContextCommandBuilder() {
    // No implementation required.
    return new CommandBuilder(null, null);
  }
}
opends/src/server/org/opends/server/tools/dsconfig/LDAPManagementContextFactory.java
@@ -47,6 +47,7 @@
import org.opends.server.util.args.Argument;
import org.opends.server.util.args.ArgumentException;
import org.opends.server.util.args.SubCommandArgumentParser;
import org.opends.server.util.cli.CommandBuilder;
import org.opends.server.util.cli.LDAPConnectionConsoleInteraction;
import org.opends.server.util.cli.ConsoleApplication;
@@ -69,6 +70,9 @@
  // The management context.
  private ManagementContext context = null;
  // The connection parameters command builder.
  private CommandBuilder contextCommandBuilder;
  /**
   * Creates a new LDAP management context factory.
   */
@@ -89,6 +93,7 @@
        new LDAPConnectionConsoleInteraction(app, secureArgsList);
      ci.run();
      context = getManagementContext(app, ci);
      contextCommandBuilder = ci.getCommandBuilder();
    }
    return context;
  }
@@ -105,6 +110,14 @@
  }
  /**
   * {@inheritDoc}
   */
  public CommandBuilder getContextCommandBuilder()
  {
    return contextCommandBuilder;
  }
  /**
   * Gets the management context which sub-commands should use in
   * order to manage the directory server. Implementations can use the
   * application instance for retrieving passwords interactively.
opends/src/server/org/opends/server/tools/dsconfig/ListSubCommandHandler.java
@@ -193,6 +193,12 @@
    // Get the property names.
    Set<String> propertyNames = getPropertyNames();
    // Reset the command builder
    getCommandBuilder().clearArguments();
    // Update the command builder.
    updateCommandBuilderWithSubCommand();
    if (propertyNames.isEmpty()) {
      // Use a default set of properties.
      propertyNames = CLIProfile.getInstance().getDefaultListPropertyNames(
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.CommandBuilder;
import org.opends.server.util.cli.ConsoleApplication;
@@ -96,4 +97,12 @@
   *           If the global arguments are invalid for some reason.
   */
  void validateGlobalArguments() throws ArgumentException;
  /**
   * Returns the command builder that provides the equivalent arguments in
   * interactive mode to get the management context.
   * @return the command builder that provides the equivalent arguments in
   * interactive mode to get the management context.
   */
  CommandBuilder getContextCommandBuilder();
}
opends/src/server/org/opends/server/tools/dsconfig/PropertyEditorModification.java
New file
@@ -0,0 +1,198 @@
/*
 * 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
 *
 *
 *      Copyright 2008 Sun Microsystems, Inc.
 */
package org.opends.server.tools.dsconfig;
import java.util.SortedSet;
import java.util.TreeSet;
import org.opends.server.admin.PropertyDefinition;
/**
 * This class is a data structure that can be used as an interface between
 * PropertyValueEditor and the different handlers that call it.
 * Since PropertyValueEditor is not aware of the different options used by the
 * handlers it cannot directly construct a CommandBuilder, but
 * PropertyValueEditor knows about the different changes (set, reset, delete)
 * that can be performed against the ManagedObject and these changes can be
 * used to generate a CommandBuilder.
 *
 * @param <T> The type of the underlying property associated with the
 * modification.
 */
final class PropertyEditorModification<T>
{
  /**
   * The enumeration that describes the different types of modifications that
   * we can have.
   */
  enum Type
  {
    /** The user chose to set values. */
    SET,
    /** The user chose to reset values. */
    RESET,
    /** The user chose to add values. */
    ADD,
    /** The user chose to delete values. */
    REMOVE
  };
  private PropertyDefinition<T> propertyDefinition;
  private Type type;
  private SortedSet<T> values;
  private SortedSet<T> originalValues;
  /**
   * The private constructor of the PropertyEditorModification.
   * @param propertyDefinition the property definition associated with the
   * modification.
   * @param type the type of the modification.
   * @param values the values associated with the modifications.
   * @param originalValues the original values of the property we are modifying.
   */
  private PropertyEditorModification(PropertyDefinition<T> propertyDefinition,
      Type type, SortedSet<T> values, SortedSet<T> originalValues)
  {
    this.propertyDefinition = propertyDefinition;
    this.type = type;
    this.values = new TreeSet<T>();
    this.values.addAll(values);
    this.originalValues = new TreeSet<T>();
    this.originalValues.addAll(originalValues);
  }
  /**
   * Creates a reset modification.
   * @param <T> The type of the underlying property.
   * @param propertyDefinition the property that is modified.
   * @param originalValues the original values of the property.
   * @return a reset modification for a given property.
   */
  static <T> PropertyEditorModification<T> createResetModification(
      PropertyDefinition<T> propertyDefinition, SortedSet<T> originalValues)
  {
    return new PropertyEditorModification<T>(propertyDefinition, Type.RESET,
        new TreeSet<T>(), originalValues);
  }
  /**
   * Creates an add modification.
   * @param <T> The type of the underlying property.
   * @param propertyDefinition the property that is modified.
   * @param addedValues the values that are added in this modification.
   * @param originalValues the original values of the property.
   * @return a reset modification for a given property.
   */
  static <T> PropertyEditorModification<T> createAddModification(
      PropertyDefinition<T> propertyDefinition,
      SortedSet<T> addedValues, SortedSet<T> originalValues)
  {
    return new PropertyEditorModification<T>(propertyDefinition, Type.ADD,
        addedValues, originalValues);
  }
  /**
   * Creates a set modification.
   * @param <T> The type of the underlying property.
   * @param propertyDefinition the property that is modified.
   * @param newValues the new values for the property.
   * @param originalValues the original values of the property.
   * @return a reset modification for a given property.
   */
  static <T> PropertyEditorModification<T> createSetModification(
      PropertyDefinition<T> propertyDefinition,
      SortedSet<T> newValues, SortedSet<T> originalValues)
  {
    return new PropertyEditorModification<T>(propertyDefinition, Type.SET,
        newValues, originalValues);
  }
  /**
   * Creates a remove modification.
   * @param <T> The type of the underlying property.
   * @param propertyDefinition the property that is modified.
   * @param removedValues the values that are removed in this modification.
   * @param originalValues the original values of the property.
   * @return a reset modification for a given property.
   */
  static <T> PropertyEditorModification<T> createRemoveModification(
      PropertyDefinition<T> propertyDefinition, SortedSet<T> removedValues,
      SortedSet<T> originalValues)
  {
    return new PropertyEditorModification<T>(propertyDefinition, Type.REMOVE,
        removedValues, originalValues);
  }
  /**
   * Retuns the property definition associated with this modification.
   * @return the property definition associated with this modification.
   */
  PropertyDefinition<T> getPropertyDefinition()
  {
    return propertyDefinition;
  }
  /**
   * Returns the type of the modification.
   * @return the type of the modification.
   */
  Type getType()
  {
    return type;
  }
  /**
   * Returns the specific values associated with the modification.
   * @return the specific values associated with the modification.
   */
  SortedSet<T> getModificationValues()
  {
    return values;
  }
  /**
   * Returns the original values associated with the property.
   * @return the original values associated with the property.
   */
  SortedSet<T> getOriginalValues()
  {
    return originalValues;
  }
  /**
   * {@inheritDoc}
   */
  @Override
  public String toString()
  {
    return "Property name: "+getPropertyDefinition()+
        "\nMod type: "+getType()+
        "\nMod values: "+getModificationValues()+
        "\nOriginal values: "+getOriginalValues();
  }
}
opends/src/server/org/opends/server/tools/dsconfig/PropertyValueEditor.java
@@ -518,7 +518,13 @@
        } else if (result.isCancel()) {
          return MenuResult.cancel();
        } else {
          mo.setPropertyValues(d, result.getValues());
          Collection<String> newValues = result.getValues();
          SortedSet<String> oldValues =
            new TreeSet<String>(mo.getPropertyValues(d));
          mo.setPropertyValues(d, newValues);
          isLastChoiceReset = false;
          registerModification(d, new TreeSet<String>(newValues),
              oldValues);
          return MenuResult.success();
        }
      } catch (CLIException e) {
@@ -561,7 +567,13 @@
        } else if (result.isCancel()) {
          return MenuResult.cancel();
        } else {
          mo.setPropertyValue(d, result.getValue());
          Collection<Boolean> newValues = result.getValues();
          SortedSet<Boolean> oldValues = new TreeSet<Boolean>(
              mo.getPropertyValues(d));
          mo.setPropertyValues(d, newValues);
          isLastChoiceReset = false;
          registerModification(d, new TreeSet<Boolean>(newValues),
              oldValues);
          return MenuResult.success();
        }
      } catch (CLIException e) {
@@ -614,7 +626,11 @@
        } else if (result.isCancel()) {
          return MenuResult.cancel();
        } else {
          mo.setPropertyValues(d, result.getValues());
          Collection<E> newValues = result.getValues();
          SortedSet<E> oldValues = new TreeSet<E>(mo.getPropertyValues(d));
          mo.setPropertyValues(d, newValues);
          isLastChoiceReset = false;
          registerModification(d, new TreeSet<E>(newValues), oldValues);
          return MenuResult.success();
        }
      } catch (CLIException e) {
@@ -636,8 +652,12 @@
      // Set the new property value(s).
      try {
        mo.setPropertyValues(d, readPropertyValues(app, mo
            .getManagedObjectDefinition(), d));
        SortedSet<T> values = readPropertyValues(app,
            mo.getManagedObjectDefinition(), d);
        SortedSet<T> oldValues = new TreeSet<T>(mo.getPropertyValues(d));
        mo.setPropertyValues(d, values);
        isLastChoiceReset = false;
        registerModification(d, values, oldValues);
        return MenuResult.success();
      } catch (CLIException e) {
        this.e = e;
@@ -788,7 +808,10 @@
              if (result.isSuccess()) {
                // Set the new property value(s).
                currentValues.addAll(result.getValues());
                Collection<String> addedValues = result.getValues();
                currentValues.addAll(addedValues);
                isLastChoiceReset = false;
                app.println();
                app.pressReturnToContinue();
                return MenuResult.success(false);
@@ -835,7 +858,9 @@
            if (result.isSuccess()) {
              // Set the new property value(s).
              currentValues.removeAll(result.getValues());
              Collection<String> removedValues = result.getValues();
              currentValues.removeAll(removedValues);
              isLastChoiceReset = false;
              app.println();
              app.pressReturnToContinue();
              return MenuResult.success(false);
@@ -929,8 +954,11 @@
              MenuResult<T> result = menu.run();
              if (result.isSuccess()) {
                // Set the new property value(s).
                currentValues.addAll(result.getValues());
                Collection<T> addedValues = result.getValues();
                currentValues.addAll(addedValues);
                isLastChoiceReset = false;
                app.println();
                app.pressReturnToContinue();
                return MenuResult.success(false);
@@ -976,7 +1004,9 @@
            if (result.isSuccess()) {
              // Set the new property value(s).
              currentValues.removeAll(result.getValues());
              Collection<T> removedValues = result.getValues();
              currentValues.removeAll(removedValues);
              isLastChoiceReset = false;
              app.println();
              app.pressReturnToContinue();
              return MenuResult.success(false);
@@ -1037,8 +1067,12 @@
          public MenuResult<Boolean> invoke(ConsoleApplication app)
              throws CLIException {
            app.println();
            SortedSet<T> previousValues = new TreeSet<T>(currentValues);
            readPropertyValues(app, mo.getManagedObjectDefinition(), d,
                currentValues);
            SortedSet<T> addedValues = new TreeSet<T>(currentValues);
            addedValues.removeAll(previousValues);
            isLastChoiceReset = false;
            return MenuResult.success(false);
          }
@@ -1074,7 +1108,9 @@
            if (result.isSuccess()) {
              // Set the new property value(s).
              currentValues.removeAll(result.getValues());
              Collection<T> removedValues = result.getValues();
              currentValues.removeAll(removedValues);
              isLastChoiceReset = false;
              app.println();
              app.pressReturnToContinue();
              return MenuResult.success(false);
@@ -1305,6 +1341,7 @@
            public MenuResult<Boolean> invoke(ConsoleApplication app)
                throws CLIException {
              isLastChoiceReset = false;
              currentValues.clear();
              app.println();
              app.pressReturnToContinue();
@@ -1325,6 +1362,7 @@
              throws CLIException {
            currentValues.clear();
            currentValues.addAll(defaultValues);
            isLastChoiceReset = true;
            app.println();
            app.pressReturnToContinue();
            return MenuResult.success(false);
@@ -1343,6 +1381,7 @@
              throws CLIException {
            currentValues.clear();
            currentValues.addAll(oldValues);
            isLastChoiceReset = false;
            app.println();
            app.pressReturnToContinue();
            return MenuResult.success(false);
@@ -1370,8 +1409,12 @@
      if (result.isSuccess()) {
        if (result.getValue() == true) {
          // Set the new property value(s).
          mo.setPropertyValues(d, currentValues);
          registerModification(d, currentValues, oldValues);
          app.println();
          app.pressReturnToContinue();
          return MenuResult.success(false);
@@ -1923,7 +1966,19 @@
      if (result.isSuccess()) {
        // Set the new property value(s).
        mo.setPropertyValues(d, result.getValues());
        Collection<T> newValues = result.getValues();
        SortedSet<T> oldValues = new TreeSet<T>(mo.getPropertyValues(d));
        mo.setPropertyValues(d, newValues);
        if (newValues.size() > 0)
        {
          isLastChoiceReset = false;
        }
        else
        {
          // There are no newValues when we do a reset.
          isLastChoiceReset = true;
        }
        registerModification(d, new TreeSet<T>(newValues), oldValues);
        app.println();
        app.pressReturnToContinue();
        return MenuResult.success(false);
@@ -2158,6 +2213,15 @@
  // The management context.
  private final ManagementContext context;
  // The modifications performed: we assume that at most there is one
  // modification per property definition.
  private final List<PropertyEditorModification> mods =
    new ArrayList<PropertyEditorModification>();
  // Whether the last type of choice made by the user in a menu is a
  // reset
  private boolean isLastChoiceReset;
  /**
@@ -2204,6 +2268,7 @@
  public MenuResult<Void> edit(ManagedObject<?> mo,
      Collection<PropertyDefinition<?>> c, boolean isCreate)
      throws CLIException {
    // Get values for this missing mandatory property.
    for (PropertyDefinition<?> pd : c) {
      if (pd.hasOption(PropertyOption.MANDATORY)) {
@@ -2308,4 +2373,289 @@
      }
    }
  }
  /**
   * Register the modification in the list of modifications.
   * @param <T> The type of the underlying property associated with the
   * modification.
   * @param pd the property definition.
   * @param newValues the resulting values of the property once the
   * modification is applied.
   * @param previousValues the values we had before the modification is applied
   * (these are not necessarily the *original* values if we already have other
   * modifications applied to the same property).
   */
  private <T> void registerModification(PropertyDefinition<T> pd,
      SortedSet<T> newValues, SortedSet<T> previousValues)
  {
    if (isLastChoiceReset)
    {
      registerResetModification(pd, previousValues);
    }
    else if (!newValues.equals(previousValues))
    {
      if (newValues.containsAll(previousValues))
      {
        registerAddModification(pd, newValues, previousValues);
      }
      else if (previousValues.containsAll(newValues))
      {
        registerRemoveModification(pd, newValues, previousValues);
      }
      else
      {
        registerSetModification(pd, newValues, previousValues);
      }
    }
  }
  /**
   * Register a reset modification in the list of modifications.
   * @param <T> The type of the underlying property associated with the
   * modification.
   * @param pd the property definition.
   * @param previousValues the values we had before the modification is applied
   * (these are not necessarily the *original* values if we already have other
   * modifications applied to the same property).
   */
  private <T> void registerResetModification(PropertyDefinition<T> pd,
      SortedSet<T> previousValues)
  {
    PropertyEditorModification<?> mod = getModification(pd);
    SortedSet<T> originalValues;
    if (mod != null)
    {
      originalValues = new TreeSet<T>();
      castAndAddValues(originalValues, mod.getOriginalValues(), pd);
      removeModification(mod);
    }
    else
    {
      originalValues = new TreeSet<T>(previousValues);
    }
    addModification(PropertyEditorModification.createResetModification(pd,
        originalValues));
  }
  /**
   * Register a set modification in the list of modifications.
   * @param <T> The type of the underlying property associated with the
   * modification.
   * @param pd the property definition.
   * @param newValues the resulting values of the property once the
   * modification is applied.
   * @param previousValues the values we had before the modification is applied
   * (these are not necessarily the *original* values if we already have other
   * modifications applied to the same property).
   */
  private <T> void registerSetModification(PropertyDefinition<T> pd,
      SortedSet<T> newValues, SortedSet<T> previousValues)
  {
    PropertyEditorModification<?> mod = getModification(pd);
    SortedSet<T> originalValues;
    if (mod != null)
    {
      originalValues = new TreeSet<T>();
      castAndAddValues(originalValues, mod.getOriginalValues(), pd);
      removeModification(mod);
    }
    else
    {
      originalValues = new TreeSet<T>(previousValues);
    }
    addModification(PropertyEditorModification.createSetModification(pd,
        newValues, originalValues));
  }
  /**
   * Register an add modification in the list of modifications.
   * @param <T> The type of the underlying property associated with the
   * modification.
   * @param pd the property definition.
   * @param newValues the resulting values of the property once the
   * modification is applied.
   * @param previousValues the values we had before the modification is applied
   * (these are not necessarily the *original* values if we already have other
   * modifications applied to the same property).
   */
  private <T> void registerAddModification(PropertyDefinition<T> pd,
      SortedSet<T> newValues, SortedSet<T> previousValues)
  {
    PropertyEditorModification<?> mod = getModification(pd);
    PropertyEditorModification<T> newMod;
    SortedSet<T> originalValues;
    if (mod != null)
    {
      originalValues = new TreeSet<T>();
      castAndAddValues(originalValues, mod.getOriginalValues(), pd);
      if (mod.getType() == PropertyEditorModification.Type.ADD)
      {
        SortedSet<T> addedValues = new TreeSet<T>(newValues);
        addedValues.removeAll(originalValues);
        newMod = PropertyEditorModification.createAddModification(pd,
            addedValues, originalValues);
      }
      else
      {
        newMod = PropertyEditorModification.createSetModification(pd,
            new TreeSet<T>(newValues), originalValues);
      }
      removeModification(mod);
    }
    else
    {
      originalValues = new TreeSet<T>(previousValues);
      SortedSet<T> addedValues = new TreeSet<T>(newValues);
      addedValues.removeAll(originalValues);
      newMod = PropertyEditorModification.createAddModification(pd,
          addedValues, originalValues);
    }
    addModification(newMod);
  }
  /**
   * Register a remove modification in the list of modifications.
   * @param <T> The type of the underlying property associated with the
   * modification.
   * @param pd the property definition.
   * @param newValues the resulting values of the property once the
   * modification is applied.
   * @param previousValues the values we had before the modification is applied
   * (these are not necessarily the *original* values if we already have other
   * modifications applied to the same property).
   */
  private <T> void registerRemoveModification(PropertyDefinition<T> pd,
      SortedSet<T> newValues, SortedSet<T> previousValues)
  {
    PropertyEditorModification<?> mod = getModification(pd);
    PropertyEditorModification<T> newMod;
    SortedSet<T> originalValues;
    if (mod != null)
    {
      originalValues = new TreeSet<T>();
      castAndAddValues(originalValues, mod.getOriginalValues(), pd);
      if (newValues.isEmpty())
      {
        newMod = PropertyEditorModification.createRemoveModification(pd,
            originalValues, originalValues);
      }
      else if (mod.getType() == PropertyEditorModification.Type.REMOVE)
      {
        SortedSet<T> removedValues = new TreeSet<T>(originalValues);
        removedValues.removeAll(newValues);
        newMod = PropertyEditorModification.createRemoveModification(pd,
            removedValues, originalValues);
      }
      else
      {
        newMod = PropertyEditorModification.createSetModification(pd,
            new TreeSet<T>(newValues), originalValues);
      }
      removeModification(mod);
    }
    else
    {
      originalValues = new TreeSet<T>(previousValues);
      SortedSet<T> removedValues = new TreeSet<T>(originalValues);
      removedValues.removeAll(newValues);
      newMod = PropertyEditorModification.createRemoveModification(pd,
          removedValues, originalValues);
    }
    addModification(newMod);
  }
  /**
   * Returns the modifications that have been applied during the last call of
   * the method PropertyValueEditor.edit.
   * @return the modifications that have been applied during the last call of
   * the method PropertyValueEditor.edit.
   */
  public Collection<PropertyEditorModification> getModifications()
  {
    return mods;
  }
  /**
   * Clears the list of modifications.
   */
  public void resetModifications()
  {
    mods.clear();
  }
  /**
   * Adds a modification to the list of modifications that have been performed.
   * @param <T> The type of the underlying property associated with the
   * modification.
   * @param mod the modification to be added.
   */
  private <T> void addModification(PropertyEditorModification<T> mod)
  {
    mods.add(mod);
  }
  /**
   * Removes a modification from the list of modifications that have been
   * performed.
   * @param <T> The type of the underlying property associated with the
   * modification.
   * @param mod the modification to be removed.
   */
  private <T> boolean removeModification(PropertyEditorModification<T> mod)
  {
    return mods.remove(mod);
  }
  /**
   * Returns the modification associated with a given property definition:
   * we assume that we have only one modification per property definition (in
   * the worst case we merge the modifications and generate a unique set
   * modification).
   * @param <T> The type of the underlying property associated with the
   * modification.
   * @param pd the property definition.
   * @return the modification associated with the provided property definition
   * and <CODE>null</CODE> if no modification could be found.
   */
  private <T> PropertyEditorModification<?> getModification(
      PropertyDefinition<T> pd)
  {
    PropertyEditorModification<?> mod = null;
    for (PropertyEditorModification<?> m : mods)
    {
      if (pd.equals(m.getPropertyDefinition()))
      {
        mod = m;
        break;
      }
    }
    return mod;
  }
  /**
   * This method is required to avoid compilation warnings.  It basically adds
   * the contents of a collection to another collection by explicitly casting
   * its values.  This is done because the method getModification() returns
   * au undefined type.
   * @param <T>  The type of the destination values.
   * @param destination the collection that we want to update.
   * @param source the collection whose values we want to add (and cast) to the
   * source collection.
   * @param pd the PropertyDefinition we use to do the casting.
   * @throws ClassCastException if an error occurs during the cast of the
   * objects.
   */
  private <T> void castAndAddValues(Collection<T> destination,
      Collection<?> source, PropertyDefinition<T> pd) throws ClassCastException
  {
    for (Object o : source)
    {
      destination.add(pd.castValue(o));
    }
  }
}
opends/src/server/org/opends/server/tools/dsconfig/SetPropSubCommandHandler.java
@@ -67,11 +67,13 @@
import org.opends.server.admin.condition.ContainsCondition;
import org.opends.server.protocols.ldap.LDAPResultCode;
import org.opends.server.tools.ClientException;
import org.opends.server.util.args.Argument;
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.CommandBuilder;
import org.opends.server.util.cli.ConsoleApplication;
import org.opends.server.util.cli.MenuResult;
@@ -146,8 +148,6 @@
   */
  private static final Character OPTION_DSCFG_SHORT_SET = null;
  /**
   * Creates a new set-xxx-prop sub-command for an instantiable
   * relation.
@@ -210,10 +210,9 @@
    return new SetPropSubCommandHandler(parser, path.child(r), r);
  }
  /**
   * Configure the provided managed object.
   * Configure the provided managed object and updates the command builder
   * in the pased SetPropSubCommandHandler object.
   *
   * @param app
   *          The console application.
@@ -221,6 +220,9 @@
   *          The management context.
   * @param mo
   *          The managed object to be configured.
   * @param handler
   *          The SubCommandHandler whose command builder properties must be
   *          updated.
   * @return Returns a MenuResult.success() if the managed object was
   *         configured successfully, or MenuResult.quit(), or
   *         MenuResult.cancel(), if the managed object was edited
@@ -233,12 +235,14 @@
   *           console.
   */
  public static MenuResult<Void> modifyManagedObject(ConsoleApplication app,
      ManagementContext context, ManagedObject<?> mo) throws ClientException,
      ManagementContext context, ManagedObject<?> mo,
      SubCommandHandler handler) throws ClientException,
      CLIException {
    ManagedObjectDefinition<?, ?> d = mo.getManagedObjectDefinition();
    Message ufn = d.getUserFriendlyName();
    while (true) {
      PropertyValueEditor editor = new PropertyValueEditor(app, context);
      // Interactively set properties if applicable.
      if (app.isInteractive()) {
        SortedSet<PropertyDefinition<?>> properties =
@@ -253,12 +257,11 @@
          properties.add(pd);
        }
        PropertyValueEditor editor = new PropertyValueEditor(app, context);
        MenuResult<Void> result = editor.edit(mo, properties, false);
        // Interactively enable/edit referenced components.
        if (result.isSuccess()) {
          result = checkReferences(app, context, mo);
          result = checkReferences(app, context, mo, handler);
          if (result.isAgain()) {
            // Edit again.
            continue;
@@ -286,6 +289,22 @@
          app.println();
          Message msg = INFO_DSCFG_CONFIRM_MODIFY_SUCCESS.get(ufn);
          app.printVerboseMessage(msg);
          for (PropertyEditorModification mod : editor.getModifications())
          {
            try
            {
              handler.getCommandBuilder().addArgument(createArgument(mod));
            }
            catch (ArgumentException ae)
            {
              // This is a bug
              throw new RuntimeException(
                  "Unexpected error generating the command builder: "+ae, ae);
            }
          }
          handler.setCommandBuilderUseful(true);
        }
        return MenuResult.success();
@@ -339,7 +358,8 @@
  // Check that any referenced components are enabled if
  // required.
  private static MenuResult<Void> checkReferences(ConsoleApplication app,
      ManagementContext context, ManagedObject<?> mo) throws ClientException,
      ManagementContext context, ManagedObject<?> mo,
      SubCommandHandler handler) throws ClientException,
      CLIException {
    ManagedObjectDefinition<?, ?> d = mo.getManagedObjectDefinition();
    Message ufn = d.getUserFriendlyName();
@@ -394,6 +414,59 @@
                  cvc.setPropertyValue(ref);
                  try {
                    ref.commit();
                    // Try to create the command builder
                    if ((app instanceof DSConfig) && app.isInteractive())
                    {
                      DSConfig dsConfig = (DSConfig)app;
                      String subCommandName = "set-" +
                      path.getRelationDefinition().getName() + "-prop";
                      CommandBuilder builder =
                        dsConfig.getCommandBuilder(subCommandName);
                      if (path.getRelationDefinition() instanceof
                          InstantiableRelationDefinition<?, ?>)
                      {
                        String argName =
                          CLIProfile.getInstance().getNamingArgument(
                              (InstantiableRelationDefinition<?, ?>)
                              path.getRelationDefinition());
                        try
                        {
                          StringArgument arg = new StringArgument(argName, null,
                              argName, false, true,
                              INFO_NAME_PLACEHOLDER.get(),
                              INFO_DSCFG_DESCRIPTION_NAME.get(
                                  d.getUserFriendlyName()));
                          arg.addValue(name);
                          builder.addArgument(arg);
                        }
                        catch (Throwable t)
                        {
                          // Bug
                          throw new RuntimeException("Unexpected error: "+t, t);
                        }
                      }
                      try
                      {
                        StringArgument arg = new StringArgument(
                            OPTION_DSCFG_LONG_SET, OPTION_DSCFG_SHORT_SET,
                            OPTION_DSCFG_LONG_SET, false, true, true,
                            INFO_VALUE_SET_PLACEHOLDER.get(), null, null,
                            INFO_DSCFG_DESCRIPTION_PROP_VAL.get());
                        arg.addValue(cvc.getPropertyDefinition().getName()+':'+
                            cvc.getValue());
                        builder.addArgument(arg);
                      }
                      catch (Throwable t)
                      {
                        // Bug
                        throw new RuntimeException("Unexpected error: "+t, t);
                      }
                      dsConfig.printCommandBuilder(builder);
                    }
                    isBadReference = false;
                  } catch (MissingMandatoryPropertiesException e) {
                    // Give the user the chance to fix the problems.
@@ -403,7 +476,7 @@
                    if (app.confirmAction(INFO_DSCFG_PROMPT_EDIT.get(rufn),
                        true)) {
                      MenuResult<Void> result = modifyManagedObject(app,
                          context, ref);
                          context, ref, handler);
                      if (result.isQuit()) {
                        return result;
                      } else if (result.isSuccess()) {
@@ -424,7 +497,7 @@
                    if (app.confirmAction(INFO_DSCFG_PROMPT_EDIT.get(rufn),
                        true)) {
                      MenuResult<Void> result = modifyManagedObject(app,
                          context, ref);
                          context, ref, handler);
                      if (result.isQuit()) {
                        return result;
                      } else if (result.isSuccess()) {
@@ -443,7 +516,7 @@
                if (app.confirmAction(INFO_DSCFG_PROMPT_EDIT_TO_ENABLE.get(
                    rufn, name, ufn), true)) {
                  MenuResult<Void> result = SetPropSubCommandHandler
                      .modifyManagedObject(app, context, ref);
                      .modifyManagedObject(app, context, ref, handler);
                  if (result.isQuit()) {
                    return result;
                  } else if (result.isSuccess()) {
@@ -585,7 +658,6 @@
  }
  /**
   * {@inheritDoc}
   */
@@ -597,6 +669,14 @@
    // Get the naming argument values.
    List<String> names = getNamingArgValues(app, namingArgs);
    // Reset the command builder
    getCommandBuilder().clearArguments();
    setCommandBuilderUseful(false);
    // Update the command builder.
    updateCommandBuilderWithSubCommand();
    // Get the targeted managed object.
    Message ufn = path.getRelationDefinition().getUserFriendlyName();
    ManagementContext context = factory.getManagementContext(app);
@@ -790,22 +870,38 @@
      } catch (PropertyException e) {
        throw ArgumentExceptionFactory.adaptPropertyException(e, d);
      }
      setCommandBuilderUseful(true);
    }
    // Now the command line changes have been made, apply the changes
    // interacting with the user to fix any problems if required.
    MenuResult<Void> result2 = modifyManagedObject(app, context, child);
    MenuResult<Void> result2 = modifyManagedObject(app, context, child, this);
    if (result2.isCancel()){
      return MenuResult.cancel();
    } else if (result2.isQuit()) {
      return MenuResult.quit();
    } else {
      if (propertyResetArgument.hasValue())
      {
        getCommandBuilder().addArgument(propertyResetArgument);
      }
      if (propertySetArgument.hasValue())
      {
        getCommandBuilder().addArgument(propertySetArgument);
      }
      if (propertyAddArgument.hasValue())
      {
        getCommandBuilder().addArgument(propertyAddArgument);
      }
      if (propertyRemoveArgument.hasValue())
      {
        getCommandBuilder().addArgument(propertyRemoveArgument);
      }
      return MenuResult.success(0);
    }
  }
  // Apply a single modification to the current change-set.
  @SuppressWarnings("unchecked")
  private <T> void modifyPropertyValues(ManagedObject<?> mo,
@@ -844,4 +940,66 @@
    changes.put(pd, values);
  }
  /**
   * Creates an argument (the one that the user should provide in the
   * command-line) that is equivalent to the modification proposed by the user
   * in the provided PropertyEditorModification object.
   * @param mod the object describing the modification made.
   * @return the argument representing the modification.
   * @throws ArgumentException if there is a problem creating the argument.
   */
  private static Argument createArgument(PropertyEditorModification mod)
  throws ArgumentException
  {
    StringArgument arg;
    String propName = mod.getPropertyDefinition().getName();
    switch (mod.getType())
    {
    case RESET:
      arg = new StringArgument(OPTION_DSCFG_LONG_RESET,
          OPTION_DSCFG_SHORT_RESET, OPTION_DSCFG_LONG_RESET, false, true, true,
          INFO_PROPERTY_PLACEHOLDER.get(), null, null,
          INFO_DSCFG_DESCRIPTION_RESET_PROP.get());
      arg.addValue(propName);
      break;
    case REMOVE:
      arg = new StringArgument(OPTION_DSCFG_LONG_REMOVE,
          OPTION_DSCFG_SHORT_REMOVE, OPTION_DSCFG_LONG_REMOVE, false, true,
          true, INFO_VALUE_SET_PLACEHOLDER.get(), null, null,
          INFO_DSCFG_DESCRIPTION_REMOVE_PROP_VAL.get());
      for (Object value : mod.getModificationValues())
      {
        arg.addValue(propName+':'+value);
      }
      break;
    case ADD:
      arg = new StringArgument(OPTION_DSCFG_LONG_ADD,
          OPTION_DSCFG_SHORT_ADD, OPTION_DSCFG_LONG_ADD, false, true, true,
          INFO_VALUE_SET_PLACEHOLDER.get(), null, null,
          INFO_DSCFG_DESCRIPTION_ADD_PROP_VAL.get());
      for (Object value : mod.getModificationValues())
      {
        arg.addValue(propName+':'+value);
      }
      break;
    case SET:
      arg = new StringArgument(OPTION_DSCFG_LONG_SET,
          OPTION_DSCFG_SHORT_SET, OPTION_DSCFG_LONG_SET, false, true, true,
          INFO_VALUE_SET_PLACEHOLDER.get(), null, null,
          INFO_DSCFG_DESCRIPTION_PROP_VAL.get());
      for (Object value : mod.getModificationValues())
      {
        arg.addValue(propName+':'+value);
      }
      break;
    default:
      // Bug
      throw new IllegalStateException("Unknown modification type: "+
          mod.getType());
    }
    return arg;
  }
}
opends/src/server/org/opends/server/tools/dsconfig/SubCommandHandler.java
@@ -70,11 +70,14 @@
import org.opends.server.admin.client.ManagedObjectDecodingException;
import org.opends.server.admin.client.ManagementContext;
import org.opends.server.tools.ClientException;
import org.opends.server.util.ServerConstants;
import org.opends.server.util.args.Argument;
import org.opends.server.util.args.ArgumentException;
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.CommandBuilder;
import org.opends.server.util.cli.ConsoleApplication;
import org.opends.server.util.cli.Menu;
import org.opends.server.util.cli.MenuBuilder;
@@ -125,8 +128,6 @@
    // The current result.
    private MenuResult<ManagedObject<?>> result;
    /**
     * {@inheritDoc}
     */
@@ -383,7 +384,7 @@
   * A path serializer which is used to register a sub-command's
   * naming arguments.
   */
  private static class NamingArgumentBuilder implements
  protected static class NamingArgumentBuilder implements
      ManagedObjectPathSerializer {
    /**
@@ -580,9 +581,17 @@
  // The argument which should be used to request specific time units.
  private StringArgument unitTimeArgument;
  // The command builder associated with this handler.
  private CommandBuilder commandBuilder;
  /**
   * The boolean that says whether is useful to display the command builder's
   * contents after calling the run method or not.
   */
  private boolean isCommandBuilderUseful = true;
  /**
   * Create a new sub-command handler.
   */
  protected SubCommandHandler() {
@@ -631,6 +640,44 @@
  public abstract SubCommand getSubCommand();
  /**
   * Gets the command builder associated with this handler.  The method should
   * be called after calling <CODE>run()</CODE> method.
   *
   * @return Returns the sub-command associated with this handler.
   */
  public final CommandBuilder getCommandBuilder()
  {
    if (commandBuilder == null)
    {
      commandBuilder = new CommandBuilder(
            System.getProperty(ServerConstants.PROPERTY_SCRIPT_NAME),
            getSubCommand().getName());
    }
    return commandBuilder;
  }
  /**
   * This method tells whether displaying the command builder contents makes
   * sense or not.  For instance in the case of the help subcommand handler
   * displaying information makes no much sense.
   *
   * @return <CODE>true</CODE> if displaying the command builder is useful and
   * <CODE>false</CODE> otherwise.
   */
  public final boolean isCommandBuilderUseful()
  {
    return isCommandBuilderUseful;
  }
  /**
   * Sets wheter the command builder is useful or not.
   * @param isCommandBuilderUseful whether the command builder is useful or not.
   */
  protected final void setCommandBuilderUseful(boolean isCommandBuilderUseful)
  {
    this.isCommandBuilderUseful = isCommandBuilderUseful;
  }
  /**
   * Gets the tags associated with this sub-command handler.
@@ -969,7 +1016,7 @@
   *           there are no children.
   */
  protected final <C extends ConfigurationClient, S extends Configuration>
      MenuResult<String> readChildName(
  MenuResult<String> readChildName(
      ConsoleApplication app, ManagedObject<?> parent,
      InstantiableRelationDefinition<C, S> r,
      AbstractManagedObjectDefinition<? extends C, ? extends S> d)
@@ -1019,7 +1066,7 @@
    case 0: {
      // No options available - abort.
      Message msg =
          ERR_DSCFG_ERROR_FINDER_NO_CHILDREN.get(d.getUserFriendlyPluralName());
        ERR_DSCFG_ERROR_FINDER_NO_CHILDREN.get(d.getUserFriendlyPluralName());
      app.println(msg);
      return MenuResult.cancel();
    }
@@ -1027,8 +1074,22 @@
      // Only one option available so confirm that the user wishes to
      // access it.
      Message msg = INFO_DSCFG_FINDER_PROMPT_SINGLE.get(
              d.getUserFriendlyName(), children.first());
          d.getUserFriendlyName(), children.first());
      if (app.confirmAction(msg, true)) {
        try
        {
          String argName = CLIProfile.getInstance().getNamingArgument(r);
          StringArgument arg = new StringArgument(argName, null, argName, false,
              true, INFO_NAME_PLACEHOLDER.get(),
              INFO_DSCFG_DESCRIPTION_NAME_CREATE.get(d.getUserFriendlyName()));
          arg.addValue(children.first());
          getCommandBuilder().addArgument(arg);
        }
        catch (Throwable t)
        {
          // Bug
          new RuntimeException("Unexpected exception: "+t, t);
        }
        return MenuResult.success(children.first());
      } else {
        return MenuResult.cancel();
@@ -1052,7 +1113,22 @@
      builder.addQuitOption();
      Menu<String> menu = builder.toMenu();
      return menu.run();
      MenuResult<String> result = menu.run();
      try
      {
        String argName = CLIProfile.getInstance().getNamingArgument(r);
        StringArgument arg = new StringArgument(argName, null, argName, false,
            true, INFO_NAME_PLACEHOLDER.get(),
            INFO_DSCFG_DESCRIPTION_NAME_CREATE.get(d.getUserFriendlyName()));
        arg.addValue(result.getValue());
        getCommandBuilder().addArgument(arg);
      }
      catch (Throwable t)
      {
        // Bug
        throw new RuntimeException("Unexpected exception: "+t, t);
      }
      return result;
    }
    }
  }
@@ -1135,6 +1211,20 @@
    subCommand.addArgument(unitTimeArgument);
  }
  /**
   * Updates the command builder with the arguments defined in the sub command.
   * This implies basically putting the arguments provided by the user in the
   * command builder.
   */
  protected final void updateCommandBuilderWithSubCommand()
  {
    for (Argument arg : getSubCommand().getArguments())
    {
      if (arg.isPresent())
      {
        getCommandBuilder().addArgument(arg);
      }
    }
  }
}
opends/src/server/org/opends/server/util/args/ArgumentParser.java
@@ -1745,7 +1745,9 @@
              OPTION_LONG_NO_PROP_FILE.equals(longId) ||
              OPTION_LONG_SCRIPT_FRIENDLY.equals(longId) ||
              OPTION_LONG_DONT_WRAP.equals(longId) ||
              OPTION_LONG_ENCODING.equals(longId);
              OPTION_LONG_ENCODING.equals(longId) ||
              OPTION_DSCFG_LONG_DISPLAY_EQUIVALENT.equals(longId) ||
              OPTION_LONG_EQUIVALENT_COMMAND_FILE_PATH.equals(longId);
    }
    return io;
  }
opends/src/server/org/opends/server/util/cli/CommandBuilder.java
New file
@@ -0,0 +1,287 @@
/*
 * 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
 *
 *
 *      Copyright 2008 Sun Microsystems, Inc.
 */
package org.opends.server.util.cli;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import org.opends.server.util.SetupUtils;
import org.opends.server.util.args.Argument;
import org.opends.server.util.args.BooleanArgument;
import org.opends.server.util.args.FileBasedArgument;
/**
 * Class used to be able to generate the non interactive mode.
 *
 */
public class CommandBuilder
{
  // The command name.
  private String commandName;
  // The subcommand name.
  private String subcommandName;
  private ArrayList<Argument> args = new ArrayList<Argument>();
  private HashSet<Argument> obfuscatedArgs = new HashSet<Argument>();
  // The value used to display arguments that must be obfuscated (such as
  // passwords).  This does not require localization (since the output of
  // command builder by its nature is not localized).
  private final static String OBFUSCATED_VALUE = "******";
  /**
   * The constructor for the CommandBuilder.
   * @param commandName the command name.
   */
  public CommandBuilder(String commandName)
  {
    this(commandName, null);
  }
  /**
   * The constructor for the CommandBuilder.
   * @param commandName the command name.
   * @param subcommandName the subcommand name.
   */
  public CommandBuilder(String commandName, String subcommandName)
  {
    this.commandName = commandName;
    this.subcommandName = subcommandName;
  }
  /**
   * Adds an argument to the list of the command builder.
   * @param argument the argument to be added.
   */
  public void addArgument(Argument argument)
  {
    // We use an ArrayList to be able to provide the possibility of updating
    // the position of the attributes.
    if (!args.contains(argument))
    {
      args.add(argument);
    }
  }
  /**
   * Adds an argument whose values must be obfuscated (passwords for instance).
   * @param argument the argument to be added.
   */
  public void addObfuscatedArgument(Argument argument)
  {
    addArgument(argument);
    obfuscatedArgs.add(argument);
  }
  /**
   * Removes the provided argument from this CommandBuilder.
   * @param argument the argument to be removed.
   * @return <CODE>true</CODE> if the attribute was present and removed and
   * <CODE>false</CODE> otherwise.
   */
  public boolean removeArgument(Argument argument)
  {
    obfuscatedArgs.remove(argument);
    return args.remove(argument);
  }
  /**
   * Appends the arguments of another command builder to this command builder.
   * @param builder the CommandBuilder to append.
   */
  public void append(CommandBuilder builder)
  {
    for (Argument arg : builder.args)
    {
      if (builder.isObfuscated(arg))
      {
        addObfuscatedArgument(arg);
      }
      else
      {
        addArgument(arg);
      }
    }
  }
  /**
   * Returns the String representation of this command builder (i.e. what we
   * want to show to the user).
   * @return the String representation of this command builder (i.e. what we
   * want to show to the user).
   */
  public String toString()
  {
    return toString(false);
  }
  /**
   * Returns the String representation of this command builder (i.e. what we
   * want to show to the user).
   * @param showObfuscated displays in clear the obfuscated values.
   * @return the String representation of this command builder (i.e. what we
   * want to show to the user).
   */
  private String toString(boolean showObfuscated)
  {
    StringBuilder builder = new StringBuilder();
    builder.append(commandName);
    if (subcommandName != null)
    {
      builder.append(" "+subcommandName);
    }
    for (Argument arg : args)
    {
      String argName;
      if (arg.getLongIdentifier() != null)
      {
        argName = "--"+arg.getLongIdentifier();
      }
      else
      {
        argName = "-"+arg.getShortIdentifier();
      }
      String separator;
      if (SetupUtils.isWindows())
      {
        separator = " ";
      }
      else
      {
        separator = " \\\n          ";
      }
      if (arg instanceof BooleanArgument)
      {
        builder.append(separator+argName);
      }
      else if (arg instanceof FileBasedArgument)
      {
        for (String value :
          ((FileBasedArgument)arg).getNameToValueMap().keySet())
        {
          builder.append(separator+argName+" ");
          if (isObfuscated(arg) && !showObfuscated)
          {
            value = OBFUSCATED_VALUE;
          }
          else
          {
            value = escapeValue(value);
          }
          builder.append(value);
        }
      }
      else
      {
        for (String value : arg.getValues())
        {
          builder.append(separator+argName+" ");
          if (isObfuscated(arg) && !showObfuscated)
          {
            value = OBFUSCATED_VALUE;
          }
          else
          {
            value = escapeValue(value);
          }
          builder.append(value);
        }
      }
    }
    return builder.toString();
  }
  /**
   * Clears the arguments.
   */
  public void clearArguments()
  {
    args.clear();
    obfuscatedArgs.clear();
  }
  /**
   * Returns the list of arguments.
   * @return the list of arguments.
   */
  public List<Argument> getArguments()
  {
    return args;
  }
  /**
   * Tells whether the provided argument's values must be obfuscated or not.
   * @param argument the argument to handle.
   * @return <CODE>true</CODE> if the attribute's values must be obfuscated and
   * <CODE>false</CODE> otherwise.
   */
  private boolean isObfuscated(Argument argument)
  {
    return obfuscatedArgs.contains(argument);
  }
  // Chars that require special treatment when passing them to command-line.
  private final char[] charsToEscape = {' ', '\t', '\n', '|', ';', '<', '>',
      '(', ')', '$', '`', '\\', '"', '\''};
  /**
   * This method simply takes a value and tries to transform it (with escape or
   * '"') characters so that it can be used in a command line.
   * @param value the String to be treated.
   * @return the transformed value.
   */
  private String escapeValue(String value)
  {
    StringBuilder b = new StringBuilder();
    if (SetupUtils.isUnix())
    {
      for (int i=0 ; i<value.length(); i++)
      {
        char c = value.charAt(i);
        boolean charToEscapeFound = false;
        for (int j=0; j<charsToEscape.length && !charToEscapeFound; j++)
        {
          charToEscapeFound = c == charsToEscape[j];
        }
        if (charToEscapeFound)
        {
          b.append('\\');
        }
        b.append(c);
      }
    }
    else
    {
      b.append('"').append(value).append('"');
    }
    return b.toString();
  }
}
opends/src/server/org/opends/server/util/cli/LDAPConnectionConsoleInteraction.java
@@ -115,6 +115,12 @@
  private Message heading = INFO_LDAP_CONN_HEADING_CONNECTION_PARAMETERS.get();
  // A copy of the secureArgList for convenience.
  private SecureConnectionCliArgs copySecureArgsList = null;
  // The command builder that we can return with the connection information.
  private CommandBuilder commandBuilder;
  /**
   * Enumeration description protocols for interactive CLI choices.
   */
@@ -274,6 +280,18 @@
                                          SecureConnectionCliArgs secureArgs) {
    this.app = app;
    this.secureArgsList = secureArgs;
    this.commandBuilder = new CommandBuilder(null);
    copySecureArgsList = new SecureConnectionCliArgs();
    try
    {
      copySecureArgsList.createGlobalArguments();
    }
    catch (Throwable t)
    {
      // This is  a bug: we should always be able to create the global arguments
      // no need to localize this one.
      throw new RuntimeException("Unexpected error: "+t, t);
    }
  }
  /**
@@ -301,6 +319,9 @@
  public void run(boolean canUseSSL, boolean canUseStartTLS)
          throws ArgumentException
  {
    // Reset everything
    commandBuilder.clearArguments();
    copySecureArgsList.createGlobalArguments();
    boolean secureConnection = (canUseSSL || canUseStartTLS) &&
      (
          secureArgsList.useSSLArg.isPresent()
@@ -372,6 +393,10 @@
      }
    }
    copySecureArgsList.hostNameArg.clearValues();
    copySecureArgsList.hostNameArg.addValue(hostName);
    commandBuilder.addArgument(copySecureArgsList.hostNameArg);
    useSSL = secureArgsList.useSSL();
    useStartTLS = secureArgsList.useStartTLS();
    boolean connectionTypeIsSet =
@@ -455,6 +480,15 @@
      }
    }
    if (useSSL)
    {
      commandBuilder.addArgument(copySecureArgsList.useSSLArg);
    }
    else if (useStartTLS)
    {
      commandBuilder.addArgument(copySecureArgsList.useStartTLSArg);
    }
    if ((useSSL || useStartTLS) && (trustManager == null))
    {
      initializeTrustManager();
@@ -528,6 +562,10 @@
      }
    }
    copySecureArgsList.portArg.clearValues();
    copySecureArgsList.portArg.addValue(String.valueOf(portNumber));
    commandBuilder.addArgument(copySecureArgsList.portArg);
    // Get the LDAP bind credentials.
    bindDN = secureArgsList.bindDnArg.getValue();
    adminUID = secureArgsList.adminUidArg.getValue();
@@ -629,6 +667,18 @@
              .unableToReadConnectionParameters(e);
        }
      }
      if (useAdmin)
      {
        copySecureArgsList.adminUidArg.clearValues();
        copySecureArgsList.adminUidArg.addValue(getAdministratorUID());
        commandBuilder.addArgument(copySecureArgsList.adminUidArg);
      }
      else
      {
        copySecureArgsList.bindDnArg.clearValues();
        copySecureArgsList.bindDnArg.addValue(getBindDN());
        commandBuilder.addArgument(copySecureArgsList.bindDnArg);
      }
    }
    else
    {
@@ -655,6 +705,10 @@
            throw ArgumentExceptionFactory.missingBindPassword(bindDN);
          }
        }
        copySecureArgsList.bindPasswordFileArg.clearValues();
        copySecureArgsList.bindPasswordFileArg.getNameToValueMap().putAll(
            secureArgsList.bindPasswordFileArg.getNameToValueMap());
        commandBuilder.addArgument(secureArgsList.bindPasswordFileArg);
      }
      else if (bindPassword == null || bindPassword.equals("-"))
      {
@@ -695,6 +749,10 @@
              .unableToReadConnectionParameters(e);
        }
      }
      copySecureArgsList.bindPasswordArg.clearValues();
      copySecureArgsList.bindPasswordArg.addValue(bindPassword);
      commandBuilder.addObfuscatedArgument(
          copySecureArgsList.bindPasswordArg);
    }
  }
@@ -707,10 +765,17 @@
  private ApplicationTrustManager getTrustManagerInternal()
  throws ArgumentException
  {
    // Remove these arguments since this method might be called several times.
    commandBuilder.removeArgument(copySecureArgsList.trustAllArg);
    commandBuilder.removeArgument(copySecureArgsList.trustStorePathArg);
    commandBuilder.removeArgument(copySecureArgsList.trustStorePasswordArg);
    commandBuilder.removeArgument(copySecureArgsList.trustStorePasswordFileArg);
    // If we have the trustALL flag, don't do anything
    // just return null
    if (secureArgsList.trustAllArg.isPresent())
    {
      commandBuilder.addArgument(copySecureArgsList.trustAllArg);
      return null;
    }
@@ -755,6 +820,7 @@
        {
          if (result.getValue().equals(TrustMethod.TRUSTALL.getChoice()))
          {
            commandBuilder.addArgument(copySecureArgsList.trustAllArg);
            // If we have the trustALL flag, don't do anything
            // just return null
            return null;
@@ -771,6 +837,10 @@
            // The certificate will be displayed to the user
            askForTrustStore = false;
            trustStoreInMemory = true;
            // There is no direct equivalent for this option, so propose the
            // trust all option as command-line argument.
            commandBuilder.addArgument(copySecureArgsList.trustAllArg);
          }
          else
          {
@@ -791,9 +861,10 @@
      }
    }
    // If we not trust all server certificates, we have to get info
    // If we do not trust all server certificates, we have to get info
    // about truststore. First get the truststore path.
    truststorePath = secureArgsList.trustStorePathArg.getValue();
    if (app.isInteractive() && !secureArgsList.trustStorePathArg.isPresent()
        && askForTrustStore)
    {
@@ -841,6 +912,13 @@
      }
    }
    if (truststorePath != null)
    {
      copySecureArgsList.trustStorePathArg.clearValues();
      copySecureArgsList.trustStorePathArg.addValue(truststorePath);
      commandBuilder.addArgument(copySecureArgsList.trustStorePathArg);
    }
    // Then the truststore password.
    //  As the most common case is to have no password for truststore,
    // we don't ask it in the interactive mode.
@@ -877,6 +955,7 @@
        }
      }
    }
    // We've got all the information to get the truststore manager
    try
    {
@@ -898,6 +977,23 @@
      {
        truststore.load(null, null);
      }
      if (secureArgsList.trustStorePasswordFileArg.isPresent())
      {
        copySecureArgsList.trustStorePasswordFileArg.clearValues();
        copySecureArgsList.trustStorePasswordFileArg.getNameToValueMap().putAll(
            secureArgsList.trustStorePasswordFileArg.getNameToValueMap());
        commandBuilder.addArgument(
            copySecureArgsList.trustStorePasswordFileArg);
      }
      else
      {
        copySecureArgsList.trustStorePasswordArg.clearValues();
        copySecureArgsList.trustStorePasswordArg.addValue(truststorePassword);
        commandBuilder.addObfuscatedArgument(
            copySecureArgsList.trustStorePasswordArg);
      }
      return new ApplicationTrustManager(truststore);
    }
    catch (Exception e)
@@ -915,6 +1011,12 @@
  private KeyManager getKeyManagerInternal()
  throws ArgumentException
  {
//  Remove these arguments since this method might be called several times.
    commandBuilder.removeArgument(copySecureArgsList.certNicknameArg);
    commandBuilder.removeArgument(copySecureArgsList.keyStorePathArg);
    commandBuilder.removeArgument(copySecureArgsList.keyStorePasswordArg);
    commandBuilder.removeArgument(copySecureArgsList.keyStorePasswordFileArg);
    // Do we need client side authentication ?
    // If one of the client side authentication args is set, we assume
    // that we
@@ -980,6 +1082,14 @@
      }
    }
    if (keystorePath != null)
    {
      copySecureArgsList.keyStorePathArg.clearValues();
      copySecureArgsList.keyStorePathArg.addValue(keystorePath);
      commandBuilder.addArgument(copySecureArgsList.keyStorePathArg);
    }
    // Then the keystore password.
    keystorePassword = secureArgsList.keyStorePasswordArg.getValue();
@@ -1094,6 +1204,29 @@
    ApplicationKeyManager akm = new ApplicationKeyManager(keystore,
        keystorePassword.toCharArray());
    if (secureArgsList.keyStorePasswordFileArg.isPresent())
    {
      copySecureArgsList.keyStorePasswordFileArg.clearValues();
      copySecureArgsList.keyStorePasswordFileArg.getNameToValueMap().putAll(
          secureArgsList.keyStorePasswordFileArg.getNameToValueMap());
      commandBuilder.addArgument(
          copySecureArgsList.keyStorePasswordFileArg);
    }
    else
    {
      copySecureArgsList.keyStorePasswordArg.clearValues();
      copySecureArgsList.keyStorePasswordArg.addValue(keystorePassword);
      commandBuilder.addObfuscatedArgument(
          copySecureArgsList.keyStorePasswordArg);
    }
    if (certifNickname != null)
    {
      copySecureArgsList.certNicknameArg.clearValues();
      copySecureArgsList.certNicknameArg.addValue(certifNickname);
    }
    if (certifNickname != null)
    {
      return new SelectableCertificateKeyManager(akm, certifNickname);
@@ -1499,7 +1632,7 @@
  }
 /**
  * Populates an a set of LDAP options with state from this interaction.
  * Populates a set of LDAP options with state from this interaction.
  *
  * @param  options existing set of options; may be null in which case this
  *         method will create a new set of <code>LDAPConnectionOptions</code>
@@ -1662,6 +1795,17 @@
 }
 /**
  * Returns the command builder with the equivalent arguments on the
  * non-interactive mode.
  * @return the command builder with the equivalent arguments on the
  * non-interactive mode.
  */
 public CommandBuilder getCommandBuilder()
 {
   return commandBuilder;
 }
 /**
  * Displays the heading if it was not displayed before.
  *
  */