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

---
 opends/src/server/org/opends/server/tools/dsconfig/DSConfig.java |  426 ++++++++++++++++++++++++++++++++++++++++++++++------
 1 files changed, 372 insertions(+), 54 deletions(-)

diff --git a/opends/src/server/org/opends/server/tools/dsconfig/DSConfig.java b/opends/src/server/org/opends/server/tools/dsconfig/DSConfig.java
index 6b452e6..bb63ea4 100644
--- a/opends/src/server/org/opends/server/tools/dsconfig/DSConfig.java
+++ b/opends/src/server/org/opends/server/tools/dsconfig/DSConfig.java
@@ -25,12 +25,12 @@
  *      Portions Copyright 2007 Sun Microsystems, Inc.
  */
 package org.opends.server.tools.dsconfig;
-import org.opends.messages.Message;
 
 
 
-import static org.opends.server.loggers.debug.DebugLogger.*;
+import static org.opends.messages.DSConfigMessages.*;
 import static org.opends.messages.ToolMessages.*;
+import static org.opends.server.loggers.debug.DebugLogger.*;
 import static org.opends.server.tools.ToolConstants.*;
 import static org.opends.server.util.StaticUtils.*;
 
@@ -39,18 +39,21 @@
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Set;
 import java.util.SortedSet;
 import java.util.TreeMap;
 import java.util.TreeSet;
 
+import org.opends.messages.Message;
 import org.opends.server.admin.AbstractManagedObjectDefinition;
 import org.opends.server.admin.AttributeTypePropertyDefinition;
 import org.opends.server.admin.ClassLoaderProvider;
 import org.opends.server.admin.ClassPropertyDefinition;
+import org.opends.server.admin.InstantiableRelationDefinition;
 import org.opends.server.admin.PropertyException;
+import org.opends.server.admin.RelationDefinition;
 import org.opends.server.admin.Tag;
 import org.opends.server.admin.client.ManagedObjectDecodingException;
-import org.opends.server.admin.client.ManagementContext;
 import org.opends.server.admin.client.cli.SecureConnectionCliParser;
 import org.opends.server.loggers.debug.DebugTracer;
 import org.opends.server.tools.ClientException;
@@ -62,6 +65,13 @@
 import org.opends.server.util.args.BooleanArgument;
 import org.opends.server.util.args.SubCommand;
 import org.opends.server.util.args.SubCommandArgumentParser;
+import org.opends.server.util.cli.CLIException;
+import org.opends.server.util.cli.ConsoleApplication;
+import org.opends.server.util.cli.Menu;
+import org.opends.server.util.cli.MenuBuilder;
+import org.opends.server.util.cli.MenuCallback;
+import org.opends.server.util.cli.MenuResult;
+import org.opends.server.util.cli.OutputStreamConsoleApplication;
 import org.opends.server.util.table.TableBuilder;
 import org.opends.server.util.table.TextTablePrinter;
 
@@ -74,6 +84,172 @@
 public final class DSConfig extends ConsoleApplication {
 
   /**
+   * A menu call-back which runs a sub-command interactively.
+   */
+  public class SubCommandHandlerMenuCallback implements MenuCallback<Integer> {
+
+    // The sub-command handler.
+    private final SubCommandHandler handler;
+
+
+
+    /**
+     * Creates a new sub-command handler call-back.
+     *
+     * @param handler
+     *          The sub-command handler.
+     */
+    public SubCommandHandlerMenuCallback(SubCommandHandler handler) {
+      this.handler = handler;
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public MenuResult<Integer> invoke(ConsoleApplication app)
+        throws CLIException {
+      try {
+        MenuResult<Integer> result = handler.run(app, factory);
+
+        if (result.isQuit()) {
+          return result;
+        } else {
+          // Success or cancel.
+          app.println();
+          app.pressReturnToContinue();
+          return MenuResult.again();
+        }
+      } catch (ArgumentException e) {
+        app.println(e.getMessageObject());
+        return MenuResult.success(1);
+      } catch (ClientException e) {
+        app.println(e.getMessageObject());
+        return MenuResult.success(e.getExitCode());
+      }
+    }
+  }
+
+
+
+  /**
+   * The interactive mode sub-menu implementation.
+   */
+  public class SubMenuCallback implements MenuCallback<Integer> {
+
+    // The menu.
+    private final Menu<Integer> menu;
+
+
+
+    /**
+     * Creates a new sub-menu implementation.
+     *
+     * @param app
+     *          The console application.
+     * @param rd
+     *          The relation definition.
+     * @param ch
+     *          The optional create sub-command.
+     * @param dh
+     *          The optional delete sub-command.
+     * @param lh
+     *          The optional list sub-command.
+     * @param sh
+     *          The option set-prop sub-command.
+     */
+    public SubMenuCallback(ConsoleApplication app, RelationDefinition<?, ?> rd,
+        CreateSubCommandHandler<?, ?> ch, DeleteSubCommandHandler dh,
+        ListSubCommandHandler lh, SetPropSubCommandHandler sh) {
+      Message ufn = rd.getUserFriendlyName();
+
+      Message ufpn = null;
+      if (rd instanceof InstantiableRelationDefinition) {
+        InstantiableRelationDefinition<?, ?> ir =
+          (InstantiableRelationDefinition<?, ?>) rd;
+        ufpn = ir.getUserFriendlyPluralName();
+      }
+
+      MenuBuilder<Integer> builder = new MenuBuilder<Integer>(app);
+
+      builder.setTitle(INFO_DSCFG_HEADING_COMPONENT_MENU_TITLE.get(ufn));
+      builder.setPrompt(INFO_DSCFG_HEADING_COMPONENT_MENU_PROMPT.get());
+
+      if (lh != null) {
+        SubCommandHandlerMenuCallback callback =
+          new SubCommandHandlerMenuCallback(lh);
+        if (ufpn != null) {
+          builder.addNumberedOption(
+              INFO_DSCFG_OPTION_COMPONENT_MENU_LIST_PLURAL.get(ufpn), callback);
+        } else {
+          builder
+              .addNumberedOption(INFO_DSCFG_OPTION_COMPONENT_MENU_LIST_SINGULAR
+                  .get(ufn), callback);
+        }
+      }
+
+      if (ch != null) {
+        SubCommandHandlerMenuCallback callback =
+          new SubCommandHandlerMenuCallback(ch);
+        builder.addNumberedOption(INFO_DSCFG_OPTION_COMPONENT_MENU_CREATE
+            .get(ufn), callback);
+      }
+
+      if (sh != null) {
+        SubCommandHandlerMenuCallback callback =
+          new SubCommandHandlerMenuCallback(sh);
+        if (ufpn != null) {
+          builder
+              .addNumberedOption(INFO_DSCFG_OPTION_COMPONENT_MENU_MODIFY_PLURAL
+                  .get(ufn), callback);
+        } else {
+          builder.addNumberedOption(
+              INFO_DSCFG_OPTION_COMPONENT_MENU_MODIFY_SINGULAR.get(ufn),
+              callback);
+        }
+      }
+
+      if (dh != null) {
+        SubCommandHandlerMenuCallback callback =
+          new SubCommandHandlerMenuCallback(dh);
+        builder.addNumberedOption(INFO_DSCFG_OPTION_COMPONENT_MENU_DELETE
+            .get(ufn), callback);
+      }
+
+      builder.addBackOption(true);
+      builder.addQuitOption();
+
+      this.menu = builder.toMenu();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public final MenuResult<Integer> invoke(ConsoleApplication app)
+        throws CLIException {
+      try {
+        app.println();
+        app.println();
+
+        MenuResult<Integer> result = menu.run();
+
+        if (result.isCancel()) {
+          return MenuResult.again();
+        }
+
+        return result;
+      } catch (CLIException e) {
+        app.println(e.getMessageObject());
+        return MenuResult.success(1);
+      }
+    }
+
+  }
+
+  /**
    * The tracer object for the debug logger.
    */
   private static final DebugTracer TRACER = getTracer();
@@ -126,7 +302,7 @@
         app.initializeClientEnvironment();
       } catch (InitializationException e) {
         // TODO: is this ok as an error message?
-        app.printMessage(e.getMessageObject());
+        app.println(e.getMessageObject());
         return 1;
       }
     }
@@ -147,10 +323,16 @@
   // already been initialized.
   private boolean globalArgumentsInitialized = false;
 
+  // The sub-command handler factory.
+  private SubCommandHandlerFactory handlerFactory = null;
+
   // Mapping of sub-commands to their implementations;
   private final Map<SubCommand, SubCommandHandler> handlers =
     new HashMap<SubCommand, SubCommandHandler>();
 
+  // Indicates whether or not a sub-command was provided.
+  private boolean hasSubCommand = true;
+
   // The argument which should be used to request non interactive
   // behavior.
   private BooleanArgument noPromptArgument;
@@ -168,10 +350,6 @@
   // The argument which should be used to request usage information.
   private BooleanArgument showUsageArgument;
 
-  // Flag indicating whether or not the sub-commands have
-  // already been initialized.
-  private boolean subCommandsInitialized = false;
-
   // The argument which should be used to request verbose output.
   private BooleanArgument verboseArgument;
 
@@ -240,6 +418,16 @@
   /**
    * {@inheritDoc}
    */
+  @Override
+  public boolean isMenuDrivenMode() {
+    return !hasSubCommand;
+  }
+
+
+
+  /**
+   * {@inheritDoc}
+   */
   public boolean isQuiet() {
     return quietArgument.isPresent();
   }
@@ -264,21 +452,11 @@
 
 
 
-  /**
-   * {@inheritDoc}
-   */
-  public ManagementContext getManagementContext() throws ArgumentException,
-      ClientException {
-    return factory.getManagementContext(this);
-  }
-
-
-
   // Displays the provided message followed by a help usage reference.
   private void displayMessageAndUsageReference(Message message) {
-    printMessage(message);
-    printMessage(Message.EMPTY);
-    printMessage(parser.getHelpUsageReference());
+    println(message);
+    println();
+    println(parser.getHelpUsageReference());
   }
 
 
@@ -297,8 +475,8 @@
       quietArgument = new BooleanArgument(
           SecureConnectionCliParser.QUIET_OPTION_LONG,
           SecureConnectionCliParser.QUIET_OPTION_SHORT,
-          SecureConnectionCliParser.QUIET_OPTION_LONG,
-          INFO_DESCRIPTION_QUIET.get());
+          SecureConnectionCliParser.QUIET_OPTION_LONG, INFO_DESCRIPTION_QUIET
+              .get());
 
       scriptFriendlyArgument = new BooleanArgument("script-friendly", 's',
           "script-friendly", INFO_DESCRIPTION_SCRIPT_FRIENDLY.get());
@@ -310,8 +488,8 @@
           INFO_DESCRIPTION_NO_PROMPT.get());
 
       showUsageArgument = new BooleanArgument("showUsage", OPTION_SHORT_HELP,
-              OPTION_LONG_HELP,
-              INFO_DSCFG_DESCRIPTION_SHOW_GROUP_USAGE_SUMMARY.get());
+          OPTION_LONG_HELP, INFO_DSCFG_DESCRIPTION_SHOW_GROUP_USAGE_SUMMARY
+              .get());
 
       // Register the global arguments.
       parser.addGlobalArgument(showUsageArgument);
@@ -340,7 +518,9 @@
    *           If a sub-command could not be created.
    */
   private void initializeSubCommands() throws ArgumentException {
-    if (subCommandsInitialized == false) {
+    if (handlerFactory == null) {
+      handlerFactory = new SubCommandHandlerFactory(parser);
+
       Comparator<SubCommand> c = new Comparator<SubCommand>() {
 
         public int compare(SubCommand o1, SubCommand o2) {
@@ -348,12 +528,11 @@
         }
       };
 
-      SubCommandBuilder builder = new SubCommandBuilder();
       Map<Tag, SortedSet<SubCommand>> groups =
         new TreeMap<Tag, SortedSet<SubCommand>>();
       SortedSet<SubCommand> allSubCommands = new TreeSet<SubCommand>(c);
-      for (SubCommandHandler handler : builder.getSubCommandHandlers(this,
-          parser)) {
+      for (SubCommandHandler handler : handlerFactory
+          .getAllSubCommandHandlers()) {
         SubCommand sc = handler.getSubCommand();
 
         handlers.put(sc, handler);
@@ -391,8 +570,6 @@
 
       parser.addGlobalArgument(arg);
       parser.setUsageGroupArgument(arg, allSubCommands);
-
-      subCommandsInitialized = true;
     }
   }
 
@@ -415,7 +592,7 @@
       initializeSubCommands();
     } catch (ArgumentException e) {
       Message message = ERR_CANNOT_INITIALIZE_ARGS.get(e.getMessage());
-      printMessage(message);
+      println(message);
       return 1;
     }
 
@@ -434,26 +611,18 @@
       return 0;
     }
 
-    // Make sure that we have a sub-command.
-    if (parser.getSubCommand() == null) {
-      Message message = ERR_ERROR_PARSING_ARGS.get(
-              ERR_DSCFG_ERROR_MISSING_SUBCOMMAND.get());
-      displayMessageAndUsageReference(message);
-      return 1;
-    }
-
+    // Check for conflicting arguments.
     if (quietArgument.isPresent() && verboseArgument.isPresent()) {
-      Message message = ERR_TOOL_CONFLICTING_ARGS.get(
-              quietArgument.getLongIdentifier(),
-          verboseArgument.getLongIdentifier());
+      Message message = ERR_TOOL_CONFLICTING_ARGS.get(quietArgument
+          .getLongIdentifier(), verboseArgument.getLongIdentifier());
       displayMessageAndUsageReference(message);
       return 1;
     }
 
     if (quietArgument.isPresent() && !noPromptArgument.isPresent()) {
       Message message = ERR_DSCFG_ERROR_QUIET_AND_INTERACTIVE_INCOMPATIBLE.get(
-              quietArgument.getLongIdentifier(),
-          noPromptArgument.getLongIdentifier());
+          quietArgument.getLongIdentifier(), noPromptArgument
+              .getLongIdentifier());
       displayMessageAndUsageReference(message);
       return 1;
     }
@@ -469,28 +638,177 @@
     try {
       factory.validateGlobalArguments();
     } catch (ArgumentException e) {
-      printMessage(e.getMessageObject());
+      println(e.getMessageObject());
       return 1;
     }
 
-    // Retrieve the sub-command implementation and run it.
-    SubCommandHandler handler = handlers.get(parser.getSubCommand());
+    if (parser.getSubCommand() == null) {
+      hasSubCommand = false;
+
+      if (isInteractive()) {
+        // Top-level interactive mode.
+        return runInteractiveMode();
+      } else {
+        Message message = ERR_ERROR_PARSING_ARGS
+            .get(ERR_DSCFG_ERROR_MISSING_SUBCOMMAND.get());
+        displayMessageAndUsageReference(message);
+        return 1;
+      }
+    } else {
+      hasSubCommand = true;
+
+      // Retrieve the sub-command implementation and run it.
+      SubCommandHandler handler = handlers.get(parser.getSubCommand());
+      return runSubCommand(handler);
+    }
+  }
+
+
+
+  // Run the top-level interactive console.
+  private int runInteractiveMode() {
+    // In interactive mode, redirect all output to stdout.
+    ConsoleApplication app = new OutputStreamConsoleApplication(this);
+
+    // Build menu structure.
+    Comparator<RelationDefinition<?, ?>> c =
+      new Comparator<RelationDefinition<?, ?>>() {
+
+      public int compare(RelationDefinition<?, ?> rd1,
+          RelationDefinition<?, ?> rd2) {
+        String s1 = rd1.getUserFriendlyName().toString();
+        String s2 = rd2.getUserFriendlyName().toString();
+
+        return s1.compareToIgnoreCase(s2);
+      }
+
+    };
+
+    Set<RelationDefinition<?, ?>> relations;
+    Map<RelationDefinition<?, ?>, CreateSubCommandHandler<?, ?>> createHandlers;
+    Map<RelationDefinition<?, ?>, DeleteSubCommandHandler> deleteHandlers;
+    Map<RelationDefinition<?, ?>, ListSubCommandHandler> listHandlers;
+    Map<RelationDefinition<?, ?>, GetPropSubCommandHandler> getPropHandlers;
+    Map<RelationDefinition<?, ?>, SetPropSubCommandHandler> setPropHandlers;
+
+    relations = new TreeSet<RelationDefinition<?, ?>>(c);
+    createHandlers =
+      new HashMap<RelationDefinition<?, ?>, CreateSubCommandHandler<?, ?>>();
+    deleteHandlers =
+      new HashMap<RelationDefinition<?, ?>, DeleteSubCommandHandler>();
+    listHandlers =
+      new HashMap<RelationDefinition<?, ?>, ListSubCommandHandler>();
+    getPropHandlers =
+      new HashMap<RelationDefinition<?, ?>, GetPropSubCommandHandler>();
+    setPropHandlers =
+      new HashMap<RelationDefinition<?, ?>, SetPropSubCommandHandler>();
+
+    for (CreateSubCommandHandler<?, ?> ch : handlerFactory
+        .getCreateSubCommandHandlers()) {
+      relations.add(ch.getRelationDefinition());
+      createHandlers.put(ch.getRelationDefinition(), ch);
+    }
+
+    for (DeleteSubCommandHandler dh : handlerFactory
+        .getDeleteSubCommandHandlers()) {
+      relations.add(dh.getRelationDefinition());
+      deleteHandlers.put(dh.getRelationDefinition(), dh);
+    }
+
+    for (ListSubCommandHandler lh :
+      handlerFactory.getListSubCommandHandlers()) {
+      relations.add(lh.getRelationDefinition());
+      listHandlers.put(lh.getRelationDefinition(), lh);
+    }
+
+    for (GetPropSubCommandHandler gh : handlerFactory
+        .getGetPropSubCommandHandlers()) {
+      relations.add(gh.getRelationDefinition());
+      getPropHandlers.put(gh.getRelationDefinition(), gh);
+    }
+
+    for (SetPropSubCommandHandler sh : handlerFactory
+        .getSetPropSubCommandHandlers()) {
+      relations.add(sh.getRelationDefinition());
+      setPropHandlers.put(sh.getRelationDefinition(), sh);
+    }
+
+    // Main menu.
+    MenuBuilder<Integer> builder = new MenuBuilder<Integer>(app);
+
+    builder.setTitle(INFO_DSCFG_HEADING_MAIN_MENU_TITLE.get());
+    builder.setPrompt(INFO_DSCFG_HEADING_MAIN_MENU_PROMPT.get());
+    builder.setMultipleColumnThreshold(0);
+
+    for (RelationDefinition<?, ?> rd : relations) {
+      MenuCallback<Integer> callback = new SubMenuCallback(app, rd,
+          createHandlers.get(rd), deleteHandlers.get(rd), listHandlers.get(rd),
+          setPropHandlers.get(rd));
+      builder.addNumberedOption(rd.getUserFriendlyName(), callback);
+    }
+
+    builder.addQuitOption();
+
+    Menu<Integer> menu = builder.toMenu();
+
     try {
-      return handler.run();
+      // Force retrieval of management context.
+      factory.getManagementContext(app);
     } catch (ArgumentException e) {
-      printMessage(e.getMessageObject());
+      app.println(e.getMessageObject());
+      return 1;
+    } catch (ClientException e) {
+      app.println(e.getMessageObject());
+      return 1;
+    }
+
+    try {
+      app.println();
+      app.println();
+
+      MenuResult<Integer> result = menu.run();
+
+      if (result.isQuit()) {
+        return 0;
+      } else {
+        return result.getValue();
+      }
+    } catch (CLIException e) {
+      app.println(e.getMessageObject());
+      return 1;
+    }
+  }
+
+
+
+  // Run the provided sub-command handler.
+  private int runSubCommand(SubCommandHandler handler) {
+    try {
+      MenuResult<Integer> result = handler.run(this, factory);
+
+      if (result.isSuccess()) {
+        return result.getValue();
+      } else {
+        // User must have quit.
+        return 1;
+      }
+    } catch (ArgumentException e) {
+      println(e.getMessageObject());
+      return 1;
+    } catch (CLIException e) {
+      println(e.getMessageObject());
       return 1;
     } catch (ClientException e) {
       // If the client exception was caused by a decoding exception
       // then we should display the causes.
-      printMessage(e.getMessageObject());
+      println(e.getMessageObject());
 
       Throwable cause = e.getCause();
       if (cause instanceof ManagedObjectDecodingException) {
         ManagedObjectDecodingException de =
           (ManagedObjectDecodingException) cause;
 
-        printMessage(Message.EMPTY);
+        println();
         TableBuilder builder = new TableBuilder();
         for (PropertyException pe : de.getCauses()) {
           AbstractManagedObjectDefinition<?, ?> d = de
@@ -506,7 +824,7 @@
         printer.setDisplayHeadings(false);
         printer.setColumnWidth(1, 0);
         builder.print(printer);
-        printMessage(Message.EMPTY);
+        println();
       }
 
       return 1;
@@ -514,7 +832,7 @@
       if (debugEnabled()) {
         TRACER.debugCaught(DebugLogLevel.ERROR, e);
       }
-      printMessage(Message.raw(StaticUtils.stackTraceToString(e)));
+      println(Message.raw(StaticUtils.stackTraceToString(e)));
       return 1;
     }
   }

--
Gitblit v1.10.0