001    /*
002     * CDDL HEADER START
003     *
004     * The contents of this file are subject to the terms of the
005     * Common Development and Distribution License, Version 1.0 only
006     * (the "License").  You may not use this file except in compliance
007     * with the License.
008     *
009     * You can obtain a copy of the license at
010     * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011     * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012     * See the License for the specific language governing permissions
013     * and limitations under the License.
014     *
015     * When distributing Covered Code, include this CDDL HEADER in each
016     * file and include the License file at
017     * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
018     * add the following below this CDDL HEADER, with the fields enclosed
019     * by brackets "[]" replaced with your own identifying information:
020     *      Portions Copyright [yyyy] [name of copyright owner]
021     *
022     * CDDL HEADER END
023     *
024     *
025     *      Copyright 2007-2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.tools.dsconfig;
028    
029    
030    
031    import static org.opends.messages.DSConfigMessages.*;
032    import static org.opends.messages.ToolMessages.*;
033    import static org.opends.server.loggers.debug.DebugLogger.*;
034    import static org.opends.server.tools.ToolConstants.*;
035    import static org.opends.server.tools.dsconfig.ArgumentExceptionFactory.*;
036    import static org.opends.server.util.StaticUtils.*;
037    
038    import java.io.BufferedWriter;
039    import java.io.FileWriter;
040    import java.io.IOException;
041    import java.io.InputStream;
042    import java.io.OutputStream;
043    import java.util.Comparator;
044    import java.util.HashMap;
045    import java.util.Map;
046    import java.util.Set;
047    import java.util.SortedSet;
048    import java.util.TreeMap;
049    import java.util.TreeSet;
050    
051    import org.opends.messages.Message;
052    import org.opends.quicksetup.util.Utils;
053    import org.opends.server.admin.AttributeTypePropertyDefinition;
054    import org.opends.server.admin.ClassLoaderProvider;
055    import org.opends.server.admin.ClassPropertyDefinition;
056    import org.opends.server.admin.InstantiableRelationDefinition;
057    import org.opends.server.admin.RelationDefinition;
058    import org.opends.server.admin.Tag;
059    import org.opends.server.admin.client.ManagedObjectDecodingException;
060    import org.opends.server.admin.client.MissingMandatoryPropertiesException;
061    import org.opends.server.admin.client.OperationRejectedException;
062    import org.opends.server.loggers.debug.DebugTracer;
063    import org.opends.server.tools.ClientException;
064    import org.opends.server.types.DebugLogLevel;
065    import org.opends.server.types.InitializationException;
066    import org.opends.server.util.EmbeddedUtils;
067    import org.opends.server.util.ServerConstants;
068    import org.opends.server.util.StaticUtils;
069    import org.opends.server.util.args.ArgumentException;
070    import org.opends.server.util.args.BooleanArgument;
071    import org.opends.server.util.args.StringArgument;
072    import org.opends.server.util.args.SubCommand;
073    import org.opends.server.util.args.SubCommandArgumentParser;
074    import org.opends.server.util.args.ArgumentGroup;
075    import org.opends.server.util.cli.CLIException;
076    import org.opends.server.util.cli.CommandBuilder;
077    import org.opends.server.util.cli.ConsoleApplication;
078    import org.opends.server.util.cli.Menu;
079    import org.opends.server.util.cli.MenuBuilder;
080    import org.opends.server.util.cli.MenuCallback;
081    import org.opends.server.util.cli.MenuResult;
082    import org.opends.server.util.cli.OutputStreamConsoleApplication;
083    
084    
085    
086    /**
087     * This class provides a command-line tool which enables
088     * administrators to configure the Directory Server.
089     */
090    public final class DSConfig extends ConsoleApplication {
091    
092      /**
093       * A menu call-back which runs a sub-command interactively.
094       */
095      private class SubCommandHandlerMenuCallback implements MenuCallback<Integer> {
096    
097        // The sub-command handler.
098        private final SubCommandHandler handler;
099    
100    
101    
102        /**
103         * Creates a new sub-command handler call-back.
104         *
105         * @param handler
106         *          The sub-command handler.
107         */
108        public SubCommandHandlerMenuCallback(SubCommandHandler handler) {
109          this.handler = handler;
110        }
111    
112    
113    
114        /**
115         * {@inheritDoc}
116         */
117        public MenuResult<Integer> invoke(ConsoleApplication app)
118            throws CLIException {
119          try {
120            MenuResult<Integer> result = handler.run(app, factory);
121    
122            if (result.isQuit()) {
123              return result;
124            } else {
125              if (result.isSuccess() && isInteractive() &&
126                  handler.isCommandBuilderUseful())
127              {
128                printCommandBuilder(getCommandBuilder(handler));
129              }
130              // Success or cancel.
131              app.println();
132              app.pressReturnToContinue();
133              return MenuResult.again();
134            }
135          } catch (ArgumentException e) {
136            app.println(e.getMessageObject());
137            return MenuResult.success(1);
138          } catch (ClientException e) {
139            app.println(e.getMessageObject());
140            return MenuResult.success(e.getExitCode());
141          }
142        }
143      }
144    
145    
146    
147      /**
148       * The interactive mode sub-menu implementation.
149       */
150      private class SubMenuCallback implements MenuCallback<Integer> {
151    
152        // The menu.
153        private final Menu<Integer> menu;
154    
155    
156    
157        /**
158         * Creates a new sub-menu implementation.
159         *
160         * @param app
161         *          The console application.
162         * @param rd
163         *          The relation definition.
164         * @param ch
165         *          The optional create sub-command.
166         * @param dh
167         *          The optional delete sub-command.
168         * @param lh
169         *          The optional list sub-command.
170         * @param sh
171         *          The option set-prop sub-command.
172         */
173        public SubMenuCallback(ConsoleApplication app, RelationDefinition<?, ?> rd,
174            CreateSubCommandHandler<?, ?> ch, DeleteSubCommandHandler dh,
175            ListSubCommandHandler lh, SetPropSubCommandHandler sh) {
176          Message ufn = rd.getUserFriendlyName();
177    
178          Message ufpn = null;
179          if (rd instanceof InstantiableRelationDefinition) {
180            InstantiableRelationDefinition<?, ?> ir =
181              (InstantiableRelationDefinition<?, ?>) rd;
182            ufpn = ir.getUserFriendlyPluralName();
183          }
184    
185          MenuBuilder<Integer> builder = new MenuBuilder<Integer>(app);
186    
187          builder.setTitle(INFO_DSCFG_HEADING_COMPONENT_MENU_TITLE.get(ufn));
188          builder.setPrompt(INFO_DSCFG_HEADING_COMPONENT_MENU_PROMPT.get());
189    
190          if (lh != null) {
191            SubCommandHandlerMenuCallback callback =
192              new SubCommandHandlerMenuCallback(lh);
193            if (ufpn != null) {
194              builder.addNumberedOption(
195                  INFO_DSCFG_OPTION_COMPONENT_MENU_LIST_PLURAL.get(ufpn), callback);
196            } else {
197              builder
198                  .addNumberedOption(INFO_DSCFG_OPTION_COMPONENT_MENU_LIST_SINGULAR
199                      .get(ufn), callback);
200            }
201          }
202    
203          if (ch != null) {
204            SubCommandHandlerMenuCallback callback =
205              new SubCommandHandlerMenuCallback(ch);
206            builder.addNumberedOption(INFO_DSCFG_OPTION_COMPONENT_MENU_CREATE
207                .get(ufn), callback);
208          }
209    
210          if (sh != null) {
211            SubCommandHandlerMenuCallback callback =
212              new SubCommandHandlerMenuCallback(sh);
213            if (ufpn != null) {
214              builder
215                  .addNumberedOption(INFO_DSCFG_OPTION_COMPONENT_MENU_MODIFY_PLURAL
216                      .get(ufn), callback);
217            } else {
218              builder.addNumberedOption(
219                  INFO_DSCFG_OPTION_COMPONENT_MENU_MODIFY_SINGULAR.get(ufn),
220                  callback);
221            }
222          }
223    
224          if (dh != null) {
225            SubCommandHandlerMenuCallback callback =
226              new SubCommandHandlerMenuCallback(dh);
227            builder.addNumberedOption(INFO_DSCFG_OPTION_COMPONENT_MENU_DELETE
228                .get(ufn), callback);
229          }
230    
231          builder.addBackOption(true);
232          builder.addQuitOption();
233    
234          this.menu = builder.toMenu();
235        }
236    
237    
238    
239        /**
240         * {@inheritDoc}
241         */
242        public final MenuResult<Integer> invoke(ConsoleApplication app)
243            throws CLIException {
244          try {
245            app.println();
246            app.println();
247    
248            MenuResult<Integer> result = menu.run();
249    
250            if (result.isCancel()) {
251              return MenuResult.again();
252            }
253    
254            return result;
255          } catch (CLIException e) {
256            app.println(e.getMessageObject());
257            return MenuResult.success(1);
258          }
259        }
260    
261      }
262    
263      /**
264       * The type name which will be used for the most generic managed
265       * object types when they are instantiable and intended for
266       * customization only.
267       */
268      public static final String CUSTOM_TYPE = "custom";
269    
270      /**
271       * The type name which will be used for the most generic managed
272       * object types when they are instantiable and not intended for
273       * customization.
274       */
275      public static final String GENERIC_TYPE = "generic";
276    
277      /**
278       * The value for the long option advanced.
279       */
280      private static final String OPTION_DSCFG_LONG_ADVANCED = "advanced";
281    
282      /**
283       * The value for the short option advanced.
284       */
285      private static final Character OPTION_DSCFG_SHORT_ADVANCED = null;
286    
287      /**
288       * The tracer object for the debug logger.
289       */
290      private static final DebugTracer TRACER = getTracer();
291    
292    
293    
294      /**
295       * Provides the command-line arguments to the main application for
296       * processing.
297       *
298       * @param args
299       *          The set of command-line arguments provided to this
300       *          program.
301       */
302      public static void main(String[] args) {
303        int exitCode = main(args, true, System.out, System.err);
304        if (exitCode != 0) {
305          System.exit(filterExitCode(exitCode));
306        }
307      }
308    
309    
310    
311      /**
312       * Provides the command-line arguments to the main application for
313       * processing and returns the exit code as an integer.
314       *
315       * @param args
316       *          The set of command-line arguments provided to this
317       *          program.
318       * @param initializeServer
319       *          Indicates whether to perform basic initialization (which
320       *          should not be done if the tool is running in the same
321       *          JVM as the server).
322       * @param outStream
323       *          The output stream for standard output.
324       * @param errStream
325       *          The output stream for standard error.
326       * @return Zero to indicate that the program completed successfully,
327       *         or non-zero to indicate that an error occurred.
328       */
329      public static int main(String[] args, boolean initializeServer,
330          OutputStream outStream, OutputStream errStream) {
331        DSConfig app = new DSConfig(System.in, outStream, errStream,
332            new LDAPManagementContextFactory());
333        // Only initialize the client environment when run as a standalone
334        // application.
335        if (initializeServer) {
336          try {
337            app.initializeClientEnvironment();
338          } catch (InitializationException e) {
339            // TODO: is this ok as an error message?
340            app.println(e.getMessageObject());
341            return 1;
342          }
343        }
344    
345        // Run the application.
346        return app.run(args);
347      }
348    
349      // The argument which should be used to request advanced mode.
350      private BooleanArgument advancedModeArgument;
351    
352      // Flag indicating whether or not the application environment has
353      // already been initialized.
354      private boolean environmentInitialized = false;
355    
356      // The factory which the application should use to retrieve its
357      // management context.
358      private final ManagementContextFactory factory;
359    
360      // Flag indicating whether or not the global arguments have
361      // already been initialized.
362      private boolean globalArgumentsInitialized = false;
363    
364      // The sub-command handler factory.
365      private SubCommandHandlerFactory handlerFactory = null;
366    
367      // Mapping of sub-commands to their implementations;
368      private final Map<SubCommand, SubCommandHandler> handlers =
369        new HashMap<SubCommand, SubCommandHandler>();
370    
371      // Indicates whether or not a sub-command was provided.
372      private boolean hasSubCommand = true;
373    
374      // The argument which should be used to request non interactive
375      // behavior.
376      private BooleanArgument noPromptArgument;
377    
378      // The argument that the user must set to display the equivalent
379      // non-interactive mode argument
380      private BooleanArgument displayEquivalentArgument;
381    
382      // The argument that allows the user to dump the equivalent non-interactive
383      // command to a file.
384      private StringArgument equivalentCommandFileArgument;
385    
386      // The command-line argument parser.
387      private final SubCommandArgumentParser parser;
388    
389      // The argument which should be used to request quiet output.
390      private BooleanArgument quietArgument;
391    
392      // The argument which should be used to request script-friendly
393      // output.
394      private BooleanArgument scriptFriendlyArgument;
395    
396      // The argument which should be used to request usage information.
397      private BooleanArgument showUsageArgument;
398    
399      // The argument which should be used to request verbose output.
400      private BooleanArgument verboseArgument;
401    
402      // The argument which should be used to indicate the properties file.
403      private StringArgument propertiesFileArgument;
404    
405      // The argument which should be used to indicate that we will not look for
406      // properties file.
407      private BooleanArgument noPropertiesFileArgument;
408    
409      // The boolean that is used to know if data must be appended to the file
410      // containing equivalent non-interactive commands.
411      private boolean alreadyWroteEquivalentCommand;
412    
413      /**
414       * Creates a new dsconfig application instance.
415       *
416       * @param in
417       *          The application input stream.
418       * @param out
419       *          The application output stream.
420       * @param err
421       *          The application error stream.
422       * @param factory
423       *          The factory which this application instance should use
424       *          for obtaining management contexts.
425       */
426      private DSConfig(InputStream in, OutputStream out, OutputStream err,
427          ManagementContextFactory factory) {
428        super(in, out, err);
429    
430        this.parser = new SubCommandArgumentParser(this.getClass().getName(),
431            INFO_CONFIGDS_TOOL_DESCRIPTION.get(), false);
432    
433        this.factory = factory;
434      }
435    
436    
437    
438      /**
439       * Initializes core APIs for use when dsconfig will be run as a
440       * standalone application.
441       *
442       * @throws InitializationException
443       *           If the core APIs could not be initialized.
444       */
445      private void initializeClientEnvironment() throws InitializationException {
446        if (environmentInitialized == false) {
447          EmbeddedUtils.initializeForClientUse();
448    
449          // Bootstrap definition classes.
450          ClassLoaderProvider.getInstance().enable();
451    
452          // Switch off class name validation in client.
453          ClassPropertyDefinition.setAllowClassValidation(false);
454    
455          // Switch off attribute type name validation in client.
456          AttributeTypePropertyDefinition.setCheckSchema(false);
457    
458          environmentInitialized = true;
459        }
460      }
461    
462    
463    
464      /**
465       * {@inheritDoc}
466       */
467      public boolean isAdvancedMode() {
468        return advancedModeArgument.isPresent();
469      }
470    
471    
472    
473      /**
474       * {@inheritDoc}
475       */
476      public boolean isInteractive() {
477        return !noPromptArgument.isPresent();
478      }
479    
480    
481    
482      /**
483       * {@inheritDoc}
484       */
485      @Override
486      public boolean isMenuDrivenMode() {
487        return !hasSubCommand;
488      }
489    
490    
491    
492      /**
493       * {@inheritDoc}
494       */
495      public boolean isQuiet() {
496        return quietArgument.isPresent();
497      }
498    
499    
500    
501      /**
502       * {@inheritDoc}
503       */
504      public boolean isScriptFriendly() {
505        return scriptFriendlyArgument.isPresent();
506      }
507    
508    
509    
510      /**
511       * {@inheritDoc}
512       */
513      public boolean isVerbose() {
514        return verboseArgument.isPresent();
515      }
516    
517    
518    
519      // Displays the provided message followed by a help usage reference.
520      private void displayMessageAndUsageReference(Message message) {
521        println(message);
522        println();
523        println(parser.getHelpUsageReference());
524      }
525    
526    
527    
528      /**
529       * Registers the global arguments with the argument parser.
530       *
531       * @throws ArgumentException
532       *           If a global argument could not be registered.
533       */
534      private void initializeGlobalArguments() throws ArgumentException {
535        if (globalArgumentsInitialized == false) {
536          verboseArgument = new BooleanArgument("verbose", 'v', "verbose",
537              INFO_DESCRIPTION_VERBOSE.get());
538    
539          quietArgument = new BooleanArgument(
540              OPTION_LONG_QUIET,
541              OPTION_SHORT_QUIET,
542              OPTION_LONG_QUIET,
543              INFO_DESCRIPTION_QUIET.get());
544          quietArgument.setPropertyName(OPTION_LONG_QUIET);
545    
546          scriptFriendlyArgument = new BooleanArgument("script-friendly",
547              OPTION_SHORT_SCRIPT_FRIENDLY, OPTION_LONG_SCRIPT_FRIENDLY,
548              INFO_DESCRIPTION_SCRIPT_FRIENDLY.get());
549          scriptFriendlyArgument.setPropertyName(OPTION_LONG_SCRIPT_FRIENDLY);
550    
551          noPromptArgument = new BooleanArgument(
552              OPTION_LONG_NO_PROMPT,
553              OPTION_SHORT_NO_PROMPT,
554              OPTION_LONG_NO_PROMPT,
555              INFO_DESCRIPTION_NO_PROMPT.get());
556    
557          advancedModeArgument = new BooleanArgument(OPTION_DSCFG_LONG_ADVANCED,
558              OPTION_DSCFG_SHORT_ADVANCED, OPTION_DSCFG_LONG_ADVANCED,
559              INFO_DSCFG_DESCRIPTION_ADVANCED.get());
560          advancedModeArgument.setPropertyName(OPTION_DSCFG_LONG_ADVANCED);
561    
562          showUsageArgument = new BooleanArgument("showUsage", OPTION_SHORT_HELP,
563              OPTION_LONG_HELP, INFO_DSCFG_DESCRIPTION_SHOW_GROUP_USAGE_SUMMARY
564                  .get());
565    
566          displayEquivalentArgument = new BooleanArgument(
567              OPTION_DSCFG_LONG_DISPLAY_EQUIVALENT,
568              null, OPTION_DSCFG_LONG_DISPLAY_EQUIVALENT,
569              INFO_DSCFG_DESCRIPTION_DISPLAY_EQUIVALENT.get());
570          advancedModeArgument.setPropertyName(
571              OPTION_DSCFG_LONG_DISPLAY_EQUIVALENT);
572    
573          equivalentCommandFileArgument = new StringArgument(
574              OPTION_LONG_EQUIVALENT_COMMAND_FILE_PATH, null,
575              OPTION_LONG_EQUIVALENT_COMMAND_FILE_PATH, false, false, true,
576              INFO_PATH_PLACEHOLDER.get(), null, null,
577              INFO_DSCFG_DESCRIPTION_EQUIVALENT_COMMAND_FILE_PATH.get());
578    
579          propertiesFileArgument = new StringArgument("propertiesFilePath",
580              null, OPTION_LONG_PROP_FILE_PATH,
581              false, false, true, INFO_PROP_FILE_PATH_PLACEHOLDER.get(), null, null,
582              INFO_DESCRIPTION_PROP_FILE_PATH.get());
583    
584          noPropertiesFileArgument = new BooleanArgument(
585              "noPropertiesFileArgument", null, OPTION_LONG_NO_PROP_FILE,
586              INFO_DESCRIPTION_NO_PROP_FILE.get());
587    
588          // Register the global arguments.
589    
590          ArgumentGroup toolOptionsGroup = new ArgumentGroup(
591            INFO_DESCRIPTION_CONFIG_OPTIONS_ARGS.get(), 2);
592          parser.addGlobalArgument(advancedModeArgument, toolOptionsGroup);
593    
594          parser.addGlobalArgument(showUsageArgument);
595          parser.setUsageArgument(showUsageArgument, getOutputStream());
596          parser.addGlobalArgument(verboseArgument);
597          parser.addGlobalArgument(quietArgument);
598          parser.addGlobalArgument(scriptFriendlyArgument);
599          parser.addGlobalArgument(noPromptArgument);
600          parser.addGlobalArgument(displayEquivalentArgument);
601          parser.addGlobalArgument(equivalentCommandFileArgument);
602          parser.addGlobalArgument(propertiesFileArgument);
603          parser.setFilePropertiesArgument(propertiesFileArgument);
604          parser.addGlobalArgument(noPropertiesFileArgument);
605          parser.setNoPropertiesFileArgument(noPropertiesFileArgument);
606    
607          // Register any global arguments required by the management
608          // context factory.
609          factory.registerGlobalArguments(parser);
610    
611          globalArgumentsInitialized = true;
612        }
613      }
614    
615    
616    
617      /**
618       * Registers the sub-commands with the argument parser. This method
619       * uses the administration framework introspection APIs to determine
620       * the overall structure of the command-line.
621       *
622       * @throws ArgumentException
623       *           If a sub-command could not be created.
624       */
625      private void initializeSubCommands() throws ArgumentException {
626        if (handlerFactory == null) {
627          handlerFactory = new SubCommandHandlerFactory(parser);
628    
629          Comparator<SubCommand> c = new Comparator<SubCommand>() {
630    
631            public int compare(SubCommand o1, SubCommand o2) {
632              return o1.getName().compareTo(o2.getName());
633            }
634          };
635    
636          Map<Tag, SortedSet<SubCommand>> groups =
637            new TreeMap<Tag, SortedSet<SubCommand>>();
638          SortedSet<SubCommand> allSubCommands = new TreeSet<SubCommand>(c);
639          for (SubCommandHandler handler : handlerFactory
640              .getAllSubCommandHandlers()) {
641            SubCommand sc = handler.getSubCommand();
642    
643            handlers.put(sc, handler);
644            allSubCommands.add(sc);
645    
646            // Add the sub-command to its groups.
647            for (Tag tag : handler.getTags()) {
648              SortedSet<SubCommand> group = groups.get(tag);
649              if (group == null) {
650                group = new TreeSet<SubCommand>(c);
651                groups.put(tag, group);
652              }
653              group.add(sc);
654            }
655          }
656    
657          // Register the usage arguments.
658          for (Map.Entry<Tag, SortedSet<SubCommand>> group : groups.entrySet()) {
659            Tag tag = group.getKey();
660            SortedSet<SubCommand> subCommands = group.getValue();
661    
662            String option = OPTION_LONG_HELP + "-" + tag.getName();
663            String synopsis = tag.getSynopsis().toString().toLowerCase();
664            BooleanArgument arg = new BooleanArgument(option, null, option,
665                INFO_DSCFG_DESCRIPTION_SHOW_GROUP_USAGE.get(synopsis));
666    
667            parser.addGlobalArgument(arg);
668            parser.setUsageGroupArgument(arg, subCommands);
669          }
670    
671          // Register the --help-all argument.
672          String option = OPTION_LONG_HELP + "-all";
673          BooleanArgument arg = new BooleanArgument(option, null, option,
674              INFO_DSCFG_DESCRIPTION_SHOW_GROUP_USAGE_ALL.get());
675    
676          parser.addGlobalArgument(arg);
677          parser.setUsageGroupArgument(arg, allSubCommands);
678        }
679      }
680    
681    
682    
683      /**
684       * Parses the provided command-line arguments and makes the
685       * appropriate changes to the Directory Server configuration.
686       *
687       * @param args
688       *          The command-line arguments provided to this program.
689       * @return The exit code from the configuration processing. A
690       *         nonzero value indicates that there was some kind of
691       *         problem during the configuration processing.
692       */
693      private int run(String[] args) {
694        // Register global arguments and sub-commands.
695        try {
696          initializeGlobalArguments();
697          initializeSubCommands();
698        } catch (ArgumentException e) {
699          Message message = ERR_CANNOT_INITIALIZE_ARGS.get(e.getMessage());
700          println(message);
701          return 1;
702        }
703    
704        // Parse the command-line arguments provided to this program.
705        try {
706          parser.parseArguments(args);
707        } catch (ArgumentException ae) {
708          Message message = ERR_ERROR_PARSING_ARGS.get(ae.getMessage());
709          displayMessageAndUsageReference(message);
710          return 1;
711        }
712    
713        // If the usage/version argument was provided, then we don't need
714        // to do anything else.
715        if (parser.usageOrVersionDisplayed()) {
716          return 0;
717        }
718    
719        // Check for conflicting arguments.
720        if (quietArgument.isPresent() && verboseArgument.isPresent()) {
721          Message message = ERR_TOOL_CONFLICTING_ARGS.get(quietArgument
722              .getLongIdentifier(), verboseArgument.getLongIdentifier());
723          displayMessageAndUsageReference(message);
724          return 1;
725        }
726    
727        if (quietArgument.isPresent() && !noPromptArgument.isPresent()) {
728          Message message = ERR_DSCFG_ERROR_QUIET_AND_INTERACTIVE_INCOMPATIBLE.get(
729              quietArgument.getLongIdentifier(), noPromptArgument
730                  .getLongIdentifier());
731          displayMessageAndUsageReference(message);
732          return 1;
733        }
734    
735        if (scriptFriendlyArgument.isPresent() && verboseArgument.isPresent()) {
736          Message message = ERR_TOOL_CONFLICTING_ARGS.get(scriptFriendlyArgument
737              .getLongIdentifier(), verboseArgument.getLongIdentifier());
738          displayMessageAndUsageReference(message);
739          return 1;
740        }
741    
742        if (noPropertiesFileArgument.isPresent()
743            && propertiesFileArgument.isPresent())
744        {
745          Message message = ERR_TOOL_CONFLICTING_ARGS.get(
746              noPropertiesFileArgument.getLongIdentifier(),
747              propertiesFileArgument.getLongIdentifier());
748          displayMessageAndUsageReference(message);
749          return 1;
750        }
751    
752        // Check that we can write on the provided path where we write the
753        // equivalent non-interactive commands.
754        if (equivalentCommandFileArgument.isPresent())
755        {
756          String file = equivalentCommandFileArgument.getValue();
757          if (!Utils.canWrite(file))
758          {
759            println(ERR_DSCFG_CANNOT_WRITE_EQUIVALENT_COMMAND_LINE_FILE.get(file));
760            return 1;
761          }
762        }
763    
764        // Make sure that management context's arguments are valid.
765        try {
766          factory.validateGlobalArguments();
767        } catch (ArgumentException e) {
768          println(e.getMessageObject());
769          return 1;
770        }
771    
772        int retCode = 0;
773        if (parser.getSubCommand() == null) {
774          hasSubCommand = false;
775    
776          if (isInteractive()) {
777            // Top-level interactive mode.
778            retCode = runInteractiveMode();
779          } else {
780            Message message = ERR_ERROR_PARSING_ARGS
781            .get(ERR_DSCFG_ERROR_MISSING_SUBCOMMAND.get());
782            displayMessageAndUsageReference(message);
783            retCode = 1;
784          }
785        } else {
786          hasSubCommand = true;
787    
788          // Retrieve the sub-command implementation and run it.
789          SubCommandHandler handler = handlers.get(parser.getSubCommand());
790          retCode = runSubCommand(handler);
791        }
792    
793        try {
794          // Close the Management context ==> an LDAP UNBIND is sent
795          factory.close();
796        } catch (Exception e) {
797          // Nothing to report in this case
798        }
799    
800        return retCode;
801      }
802    
803    
804    
805      // Run the top-level interactive console.
806      private int runInteractiveMode() {
807        // In interactive mode, redirect all output to stdout.
808        ConsoleApplication app = new OutputStreamConsoleApplication(this);
809    
810        // Build menu structure.
811        Comparator<RelationDefinition<?, ?>> c =
812          new Comparator<RelationDefinition<?, ?>>() {
813    
814          public int compare(RelationDefinition<?, ?> rd1,
815              RelationDefinition<?, ?> rd2) {
816            String s1 = rd1.getUserFriendlyName().toString();
817            String s2 = rd2.getUserFriendlyName().toString();
818    
819            return s1.compareToIgnoreCase(s2);
820          }
821    
822        };
823    
824        Set<RelationDefinition<?, ?>> relations;
825        Map<RelationDefinition<?, ?>, CreateSubCommandHandler<?, ?>> createHandlers;
826        Map<RelationDefinition<?, ?>, DeleteSubCommandHandler> deleteHandlers;
827        Map<RelationDefinition<?, ?>, ListSubCommandHandler> listHandlers;
828        Map<RelationDefinition<?, ?>, GetPropSubCommandHandler> getPropHandlers;
829        Map<RelationDefinition<?, ?>, SetPropSubCommandHandler> setPropHandlers;
830    
831        relations = new TreeSet<RelationDefinition<?, ?>>(c);
832        createHandlers =
833          new HashMap<RelationDefinition<?, ?>, CreateSubCommandHandler<?, ?>>();
834        deleteHandlers =
835          new HashMap<RelationDefinition<?, ?>, DeleteSubCommandHandler>();
836        listHandlers =
837          new HashMap<RelationDefinition<?, ?>, ListSubCommandHandler>();
838        getPropHandlers =
839          new HashMap<RelationDefinition<?, ?>, GetPropSubCommandHandler>();
840        setPropHandlers =
841          new HashMap<RelationDefinition<?, ?>, SetPropSubCommandHandler>();
842    
843        for (CreateSubCommandHandler<?, ?> ch : handlerFactory
844            .getCreateSubCommandHandlers()) {
845          relations.add(ch.getRelationDefinition());
846          createHandlers.put(ch.getRelationDefinition(), ch);
847        }
848    
849        for (DeleteSubCommandHandler dh : handlerFactory
850            .getDeleteSubCommandHandlers()) {
851          relations.add(dh.getRelationDefinition());
852          deleteHandlers.put(dh.getRelationDefinition(), dh);
853        }
854    
855        for (ListSubCommandHandler lh :
856          handlerFactory.getListSubCommandHandlers()) {
857          relations.add(lh.getRelationDefinition());
858          listHandlers.put(lh.getRelationDefinition(), lh);
859        }
860    
861        for (GetPropSubCommandHandler gh : handlerFactory
862            .getGetPropSubCommandHandlers()) {
863          relations.add(gh.getRelationDefinition());
864          getPropHandlers.put(gh.getRelationDefinition(), gh);
865        }
866    
867        for (SetPropSubCommandHandler sh : handlerFactory
868            .getSetPropSubCommandHandlers()) {
869          relations.add(sh.getRelationDefinition());
870          setPropHandlers.put(sh.getRelationDefinition(), sh);
871        }
872    
873        // Main menu.
874        MenuBuilder<Integer> builder = new MenuBuilder<Integer>(app);
875    
876        builder.setTitle(INFO_DSCFG_HEADING_MAIN_MENU_TITLE.get());
877        builder.setPrompt(INFO_DSCFG_HEADING_MAIN_MENU_PROMPT.get());
878        builder.setMultipleColumnThreshold(0);
879    
880        for (RelationDefinition<?, ?> rd : relations) {
881          MenuCallback<Integer> callback = new SubMenuCallback(app, rd,
882              createHandlers.get(rd), deleteHandlers.get(rd), listHandlers.get(rd),
883              setPropHandlers.get(rd));
884          builder.addNumberedOption(rd.getUserFriendlyName(), callback);
885        }
886    
887        builder.addQuitOption();
888    
889        Menu<Integer> menu = builder.toMenu();
890    
891        try {
892          // Force retrieval of management context.
893          factory.getManagementContext(app);
894        } catch (ArgumentException e) {
895          app.println(e.getMessageObject());
896          return 1;
897        } catch (ClientException e) {
898          app.println(e.getMessageObject());
899          return 1;
900        }
901    
902        try {
903          app.println();
904          app.println();
905    
906          MenuResult<Integer> result = menu.run();
907    
908          if (result.isQuit()) {
909            return 0;
910          } else {
911            return result.getValue();
912          }
913        } catch (CLIException e) {
914          app.println(e.getMessageObject());
915          return 1;
916        }
917      }
918    
919    
920    
921      // Run the provided sub-command handler.
922      private int runSubCommand(SubCommandHandler handler) {
923        try {
924          MenuResult<Integer> result = handler.run(this, factory);
925    
926          if (result.isSuccess()) {
927            if (isInteractive())
928            {
929              println();
930              println(INFO_DSCFG_NON_INTERACTIVE.get(
931                  getCommandBuilder(handler).toString()));
932            }
933    
934            return result.getValue();
935          } else {
936            // User must have quit.
937            return 1;
938          }
939        } catch (ArgumentException e) {
940          println(e.getMessageObject());
941          return 1;
942        } catch (CLIException e) {
943          println(e.getMessageObject());
944          return 1;
945        } catch (ClientException e) {
946          Throwable cause = e.getCause();
947          if (cause instanceof ManagedObjectDecodingException) {
948            ManagedObjectDecodingException de =
949              (ManagedObjectDecodingException) cause;
950            println();
951            displayManagedObjectDecodingException(this, de);
952            println();
953          } else if (cause instanceof MissingMandatoryPropertiesException) {
954            MissingMandatoryPropertiesException mmpe =
955              (MissingMandatoryPropertiesException) cause;
956            println();
957            displayMissingMandatoryPropertyException(this, mmpe);
958            println();
959          } else if (cause instanceof OperationRejectedException) {
960            OperationRejectedException ore = (OperationRejectedException) cause;
961            println();
962            displayOperationRejectedException(this, ore);
963            println();
964          } else {
965            // Just display the default message.
966            println(e.getMessageObject());
967          }
968    
969          return 1;
970        } catch (Exception e) {
971          if (debugEnabled()) {
972            TRACER.debugCaught(DebugLogLevel.ERROR, e);
973          }
974          println(Message.raw(StaticUtils.stackTraceToString(e)));
975          return 1;
976        }
977      }
978    
979      /**
980       * Updates the command builder with the global options: script friendly,
981       * verbose, etc. for a given subcommand.  It also adds systematically the
982       * no-prompt option.
983       * @param handler the subcommand handler.
984       */
985      private CommandBuilder getCommandBuilder(SubCommandHandler handler)
986      {
987        String commandName =
988          System.getProperty(ServerConstants.PROPERTY_SCRIPT_NAME);
989        if (commandName == null)
990        {
991          commandName = "dsconfig";
992        }
993    
994        CommandBuilder commandBuilder =
995          new CommandBuilder(commandName, handler.getSubCommand().getName());
996    
997        if (advancedModeArgument.isPresent())
998        {
999          commandBuilder.addArgument(advancedModeArgument);
1000        }
1001    
1002        commandBuilder.append(handler.getCommandBuilder());
1003    
1004        if ((factory != null) && (factory.getContextCommandBuilder() != null))
1005        {
1006          commandBuilder.append(factory.getContextCommandBuilder());
1007        }
1008    
1009        if (verboseArgument.isPresent())
1010        {
1011          commandBuilder.addArgument(verboseArgument);
1012        }
1013    
1014        if (scriptFriendlyArgument.isPresent())
1015        {
1016          commandBuilder.addArgument(scriptFriendlyArgument);
1017        }
1018    
1019        commandBuilder.addArgument(noPromptArgument);
1020    
1021        if (propertiesFileArgument.isPresent())
1022        {
1023          commandBuilder.addArgument(propertiesFileArgument);
1024        }
1025    
1026        if (noPropertiesFileArgument.isPresent())
1027        {
1028          commandBuilder.addArgument(noPropertiesFileArgument);
1029        }
1030    
1031        return commandBuilder;
1032      }
1033    
1034      /**
1035       * Creates a command builder with the global options: script friendly,
1036       * verbose, etc. for a given subcommand name.  It also adds systematically the
1037       * no-prompt option.
1038       * @param subcommandName the subcommand name.
1039       * @return the command builder that has been created with the specified
1040       * subcommandName.
1041       */
1042      CommandBuilder getCommandBuilder(String subcommandName)
1043      {
1044        String commandName =
1045          System.getProperty(ServerConstants.PROPERTY_SCRIPT_NAME);
1046        if (commandName == null)
1047        {
1048          commandName = "dsconfig";
1049        }
1050    
1051        CommandBuilder commandBuilder =
1052          new CommandBuilder(commandName, subcommandName);
1053    
1054        if (advancedModeArgument.isPresent())
1055        {
1056          commandBuilder.addArgument(advancedModeArgument);
1057        }
1058    
1059        if ((factory != null) && (factory.getContextCommandBuilder() != null))
1060        {
1061          commandBuilder.append(factory.getContextCommandBuilder());
1062        }
1063    
1064        if (verboseArgument.isPresent())
1065        {
1066          commandBuilder.addArgument(verboseArgument);
1067        }
1068    
1069        if (scriptFriendlyArgument.isPresent())
1070        {
1071          commandBuilder.addArgument(scriptFriendlyArgument);
1072        }
1073    
1074        commandBuilder.addArgument(noPromptArgument);
1075    
1076        if (propertiesFileArgument.isPresent())
1077        {
1078          commandBuilder.addArgument(propertiesFileArgument);
1079        }
1080    
1081        if (noPropertiesFileArgument.isPresent())
1082        {
1083          commandBuilder.addArgument(noPropertiesFileArgument);
1084        }
1085    
1086        return commandBuilder;
1087      }
1088    
1089      /**
1090       * Prints the contents of a command builder.  This method has been created
1091       * since SetPropSubCommandHandler calls it.  All the logic of DSConfig is on
1092       * this method.  Currently it simply writes the content of the CommandBuilder
1093       * to the standard output, but if we provide an option to write the content
1094       * to a file only the implementation of this method must be changed.
1095       * @param commandBuilder the command builder to be printed.
1096       */
1097      void printCommandBuilder(CommandBuilder commandBuilder)
1098      {
1099        if (displayEquivalentArgument.isPresent())
1100        {
1101          println();
1102          // We assume that the app we are running is this one.
1103          println(INFO_DSCFG_NON_INTERACTIVE.get(commandBuilder.toString()));
1104        }
1105        if (equivalentCommandFileArgument.isPresent())
1106        {
1107          // Write to the file.
1108          boolean append = alreadyWroteEquivalentCommand;
1109          String file = equivalentCommandFileArgument.getValue();
1110          try
1111          {
1112            BufferedWriter writer =
1113              new BufferedWriter(new FileWriter(file, append));
1114            writer.write(commandBuilder.toString());
1115            writer.newLine();
1116    
1117            writer.flush();
1118            writer.close();
1119          }
1120          catch (IOException ioe)
1121          {
1122            println(ERR_DSCFG_ERROR_WRITING_EQUIVALENT_COMMAND_LINE.get(file,
1123                ioe.toString()));
1124          }
1125          alreadyWroteEquivalentCommand = true;
1126        }
1127      }
1128    }