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 }