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 }