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 legal-notices/CDDLv1_0.txt 010 * or http://forgerock.org/license/CDDLv1.0.html. 011 * See the License for the specific language governing permissions 012 * and limitations under the License. 013 * 014 * When distributing Covered Code, include this CDDL HEADER in each 015 * file and include the License file at legal-notices/CDDLv1_0.txt. 016 * If applicable, add the following below this CDDL HEADER, with the 017 * fields enclosed by brackets "[]" replaced with your own identifying 018 * information: 019 * Portions Copyright [yyyy] [name of copyright owner] 020 * 021 * CDDL HEADER END 022 * 023 * 024 * Portions Copyright 2013-2015 ForgeRock AS. 025 */ 026package org.opends.server.tools.upgrade; 027 028import static com.forgerock.opendj.cli.ArgumentConstants.*; 029import static com.forgerock.opendj.cli.Utils.*; 030import static javax.security.auth.callback.TextOutputCallback.*; 031 032import static org.opends.messages.ToolMessages.*; 033import static org.opends.server.tools.upgrade.FormattedNotificationCallback.*; 034import static org.opends.server.tools.upgrade.Upgrade.*; 035 036import java.io.IOException; 037import java.io.InputStream; 038import java.io.OutputStream; 039import java.io.PrintStream; 040import java.util.ArrayList; 041import java.util.List; 042 043import javax.security.auth.callback.Callback; 044import javax.security.auth.callback.CallbackHandler; 045import javax.security.auth.callback.ConfirmationCallback; 046import javax.security.auth.callback.TextOutputCallback; 047import javax.security.auth.callback.UnsupportedCallbackException; 048 049import org.forgerock.i18n.LocalizableMessage; 050import org.forgerock.i18n.slf4j.LocalizedLogger; 051import org.opends.server.core.DirectoryServer.DirectoryServerVersionHandler; 052import org.opends.server.extensions.ConfigFileHandler; 053import org.opends.server.util.StaticUtils; 054 055import com.forgerock.opendj.cli.ArgumentException; 056import com.forgerock.opendj.cli.BooleanArgument; 057import com.forgerock.opendj.cli.ClientException; 058import com.forgerock.opendj.cli.CommonArguments; 059import com.forgerock.opendj.cli.ConsoleApplication; 060import com.forgerock.opendj.cli.StringArgument; 061import com.forgerock.opendj.cli.SubCommandArgumentParser; 062 063/** 064 * This class provides the CLI used for upgrading the OpenDJ product. 065 */ 066public final class UpgradeCli extends ConsoleApplication implements 067 CallbackHandler 068{ 069 /** Upgrade's logger. */ 070 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 071 072 /** The command-line argument parser. */ 073 private final SubCommandArgumentParser parser; 074 075 /** The argument which should be used to specify the config class. */ 076 private StringArgument configClass; 077 /** The argument which should be used to specify the config file. */ 078 private StringArgument configFile; 079 080 /** The argument which should be used to specify non interactive mode. */ 081 private BooleanArgument noPrompt; 082 private BooleanArgument ignoreErrors; 083 private BooleanArgument force; 084 private BooleanArgument quietMode; 085 private BooleanArgument verbose; 086 private BooleanArgument acceptLicense; 087 088 089 /** The argument which should be used to request usage information. */ 090 private BooleanArgument showUsageArgument; 091 092 /** 093 * Flag indicating whether or not the global arguments have 094 * already been initialized. 095 */ 096 private boolean globalArgumentsInitialized; 097 098 private UpgradeCli(InputStream in, OutputStream out, OutputStream err) 099 { 100 super(new PrintStream(out), new PrintStream(err)); 101 this.parser = 102 new SubCommandArgumentParser(getClass().getName(), 103 INFO_UPGRADE_DESCRIPTION_CLI.get(), false); 104 this.parser.setVersionHandler(new DirectoryServerVersionHandler()); 105 this.parser.setShortToolDescription(REF_SHORT_DESC_UPGRADE.get()); 106 this.parser.setDocToolDescriptionSupplement(SUPPLEMENT_DESCRIPTION_UPGRADE_CLI.get()); 107 } 108 109 /** 110 * Provides the command-line arguments to the main application for processing. 111 * 112 * @param args 113 * The set of command-line arguments provided to this program. 114 */ 115 public static void main(String[] args) 116 { 117 final int exitCode = main(args, true, System.out, System.err); 118 if (exitCode != 0) 119 { 120 System.exit(filterExitCode(exitCode)); 121 } 122 } 123 124 /** 125 * Provides the command-line arguments to the main application for processing 126 * and returns the exit code as an integer. 127 * 128 * @param args 129 * The set of command-line arguments provided to this program. 130 * @param initializeServer 131 * Indicates whether to perform basic initialization (which should 132 * not be done if the tool is running in the same JVM as the server). 133 * @param outStream 134 * The output stream for standard output. 135 * @param errStream 136 * The output stream for standard error. 137 * @return Zero to indicate that the program completed successfully, or 138 * non-zero to indicate that an error occurred. 139 */ 140 public static int main(String[] args, boolean initializeServer, 141 OutputStream outStream, OutputStream errStream) 142 { 143 final UpgradeCli app = new UpgradeCli(System.in, outStream, errStream); 144 145 // Run the application. 146 return app.run(args, initializeServer); 147 } 148 149 /** {@inheritDoc} */ 150 @Override 151 public boolean isAdvancedMode() 152 { 153 return false; 154 } 155 156 /** {@inheritDoc} */ 157 @Override 158 public boolean isInteractive() 159 { 160 return !noPrompt.isPresent(); 161 } 162 163 /** {@inheritDoc} */ 164 @Override 165 public boolean isMenuDrivenMode() 166 { 167 return false; 168 } 169 170 /** {@inheritDoc} */ 171 @Override 172 public boolean isQuiet() 173 { 174 return quietMode.isPresent(); 175 } 176 177 /** {@inheritDoc} */ 178 @Override 179 public boolean isScriptFriendly() 180 { 181 return false; 182 } 183 184 /** {@inheritDoc} */ 185 @Override 186 public boolean isVerbose() 187 { 188 return verbose.isPresent(); 189 } 190 191 /** 192 * Force the upgrade. All critical questions will be forced to 'yes'. 193 * 194 * @return {@code true} if the upgrade process is forced. 195 */ 196 private boolean isForceUpgrade() 197 { 198 return force.isPresent(); 199 } 200 201 /** 202 * Force to ignore the errors during the upgrade process. 203 * Continues rather than fails. 204 * 205 * @return {@code true} if the errors are forced to be ignored. 206 */ 207 private boolean isIgnoreErrors() 208 { 209 return ignoreErrors.isPresent(); 210 } 211 212 /** 213 * Automatically accepts the license if it's present. 214 * 215 * @return {@code true} if license is accepted by default. 216 */ 217 private boolean isAcceptLicense() 218 { 219 return acceptLicense.isPresent(); 220 } 221 222 /** Initialize arguments provided by the command line. */ 223 private void initializeGlobalArguments() throws ArgumentException 224 { 225 if (!globalArgumentsInitialized) 226 { 227 configClass = CommonArguments.getConfigClass(ConfigFileHandler.class.getName()); 228 configFile = CommonArguments.getConfigFile(); 229 noPrompt = CommonArguments.getNoPrompt(); 230 verbose = CommonArguments.getVerbose(); 231 quietMode = CommonArguments.getQuiet(); 232 233 ignoreErrors = 234 new BooleanArgument(OPTION_LONG_IGNORE_ERRORS, null, 235 OPTION_LONG_IGNORE_ERRORS, INFO_UPGRADE_OPTION_IGNORE_ERRORS 236 .get()); 237 238 force = new BooleanArgument(OPTION_LONG_FORCE_UPGRADE, null, 239 OPTION_LONG_FORCE_UPGRADE, 240 INFO_UPGRADE_OPTION_FORCE.get(OPTION_LONG_NO_PROMPT)); 241 242 acceptLicense = CommonArguments.getAcceptLicense(); 243 showUsageArgument = CommonArguments.getShowUsage(); 244 245 246 // Register the global arguments. 247 parser.addGlobalArgument(showUsageArgument); 248 parser.setUsageArgument(showUsageArgument, getOutputStream()); 249 parser.addGlobalArgument(configClass); 250 parser.addGlobalArgument(configFile); 251 parser.addGlobalArgument(noPrompt); 252 parser.addGlobalArgument(verbose); 253 parser.addGlobalArgument(quietMode); 254 parser.addGlobalArgument(force); 255 parser.addGlobalArgument(ignoreErrors); 256 parser.addGlobalArgument(acceptLicense); 257 258 globalArgumentsInitialized = true; 259 } 260 } 261 262 private int run(String[] args, boolean initializeServer) 263 { 264 // Initialize the arguments 265 try 266 { 267 initializeGlobalArguments(); 268 } 269 catch (ArgumentException e) 270 { 271 final LocalizableMessage message = ERR_CANNOT_INITIALIZE_ARGS.get(e.getMessage()); 272 getOutputStream().print(message); 273 return EXIT_CODE_ERROR; 274 } 275 276 // Parse the command-line arguments provided to this program. 277 try 278 { 279 parser.parseArguments(args); 280 if (isInteractive() && isQuiet()) 281 { 282 final LocalizableMessage message = 283 ERR_UPGRADE_INCOMPATIBLE_ARGS.get(OPTION_LONG_QUIET, 284 "interactive mode"); 285 getOutputStream().println(message); 286 return EXIT_CODE_ERROR; 287 } 288 if (isInteractive() && isForceUpgrade()) 289 { 290 final LocalizableMessage message = 291 ERR_UPGRADE_INCOMPATIBLE_ARGS.get(OPTION_LONG_FORCE_UPGRADE, 292 "interactive mode"); 293 getOutputStream().println(message); 294 return EXIT_CODE_ERROR; 295 } 296 if (isQuiet() && isVerbose()) 297 { 298 final LocalizableMessage message = 299 ERR_UPGRADE_INCOMPATIBLE_ARGS.get(OPTION_LONG_QUIET, 300 OPTION_LONG_VERBOSE); 301 getOutputStream().println(message); 302 return EXIT_CODE_ERROR; 303 } 304 } 305 catch (ArgumentException ae) 306 { 307 parser.displayMessageAndUsageReference(getErrStream(), ERR_ERROR_PARSING_ARGS.get(ae.getMessage())); 308 return EXIT_CODE_ERROR; 309 } 310 311 // If the usage/version argument was provided, then we don't need 312 // to do anything else. 313 if (parser.usageOrVersionDisplayed()) 314 { 315 return EXIT_CODE_SUCCESS; 316 } 317 318 // Main process 319 try 320 { 321 // Creates the log file. 322 UpgradeLog.initLogFileHandler(); 323 324 // Upgrade's context. 325 UpgradeContext context = new UpgradeContext(this) 326 .setIgnoreErrorsMode(isIgnoreErrors()) 327 .setAcceptLicenseMode(isAcceptLicense()) 328 .setInteractiveMode(isInteractive()) 329 .setForceUpgradeMode(isForceUpgrade()); 330 331 // Starts upgrade. 332 Upgrade.upgrade(context); 333 } 334 catch (ClientException ex) 335 { 336 return ex.getReturnCode(); 337 } 338 catch (Exception ex) 339 { 340 println(Style.ERROR, ERR_UPGRADE_MAIN_UPGRADE_PROCESS.get(ex 341 .getMessage()), 0); 342 343 return EXIT_CODE_ERROR; 344 } 345 return EXIT_CODE_SUCCESS; 346 } 347 348 /** {@inheritDoc} */ 349 @Override 350 public void handle(Callback[] callbacks) throws IOException, 351 UnsupportedCallbackException 352 { 353 for (final Callback c : callbacks) 354 { 355 // Displays progress eg. for a task. 356 if (c instanceof ProgressNotificationCallback) 357 { 358 final ProgressNotificationCallback pnc = 359 (ProgressNotificationCallback) c; 360 printProgressBar(pnc.getMessage(), pnc.getProgress(), 2); 361 } 362 else if (c instanceof FormattedNotificationCallback) 363 { 364 // Displays formatted notifications. 365 final FormattedNotificationCallback fnc = 366 (FormattedNotificationCallback) c; 367 switch (fnc.getMessageSubType()) 368 { 369 case TITLE_CALLBACK: 370 println(Style.TITLE, LocalizableMessage.raw(fnc.getMessage()), 0); 371 logger.info(LocalizableMessage.raw(fnc.getMessage())); 372 break; 373 case SUBTITLE_CALLBACK: 374 println(Style.SUBTITLE, LocalizableMessage.raw(fnc.getMessage()), 375 4); 376 logger.info(LocalizableMessage.raw(fnc.getMessage())); 377 break; 378 case NOTICE_CALLBACK: 379 println(Style.NOTICE, LocalizableMessage.raw(fnc.getMessage()), 1); 380 logger.info(LocalizableMessage.raw(fnc.getMessage())); 381 break; 382 case ERROR_CALLBACK: 383 println(Style.ERROR, LocalizableMessage.raw(fnc.getMessage()), 1); 384 logger.error(LocalizableMessage.raw(fnc.getMessage())); 385 break; 386 case WARNING: 387 println(Style.WARNING, LocalizableMessage.raw(fnc.getMessage()), 2); 388 logger.warn(LocalizableMessage.raw(fnc.getMessage())); 389 break; 390 default: 391 logger.error(LocalizableMessage.raw("Unsupported message type: " 392 + fnc.getMessage())); 393 throw new IOException("Unsupported message type: "); 394 } 395 } 396 else if (c instanceof TextOutputCallback) 397 { 398 // Usual output text. 399 final TextOutputCallback toc = (TextOutputCallback) c; 400 if(toc.getMessageType() == TextOutputCallback.INFORMATION) { 401 logger.info(LocalizableMessage.raw(toc.getMessage())); 402 println(LocalizableMessage.raw(toc.getMessage())); 403 } else { 404 logger.error(LocalizableMessage.raw("Unsupported message type: " 405 + toc.getMessage())); 406 throw new IOException("Unsupported message type: "); 407 } 408 } 409 else if (c instanceof ConfirmationCallback) 410 { 411 final ConfirmationCallback cc = (ConfirmationCallback) c; 412 List<String> choices = new ArrayList<>(); 413 414 final String defaultOption = getDefaultOption(cc.getDefaultOption()); 415 StringBuilder prompt = 416 new StringBuilder(wrapText(cc.getPrompt(), MAX_LINE_WIDTH, 2)); 417 418 // Default answers. 419 final List<String> yesNoDefaultResponses = 420 StaticUtils.arrayToList( 421 INFO_PROMPT_YES_COMPLETE_ANSWER.get().toString(), 422 INFO_PROMPT_YES_FIRST_LETTER_ANSWER.get().toString(), 423 INFO_PROMPT_NO_COMPLETE_ANSWER.get().toString(), 424 INFO_PROMPT_NO_FIRST_LETTER_ANSWER.get().toString()); 425 426 // Generating prompt and possible answers list. 427 prompt.append(" ").append("("); 428 if (cc.getOptionType() == ConfirmationCallback.YES_NO_OPTION) 429 { 430 choices.addAll(yesNoDefaultResponses); 431 prompt.append(INFO_PROMPT_YES_COMPLETE_ANSWER.get()) 432 .append("/") 433 .append(INFO_PROMPT_NO_COMPLETE_ANSWER.get()); 434 } 435 else if (cc.getOptionType() 436 == ConfirmationCallback.YES_NO_CANCEL_OPTION) 437 { 438 choices.addAll(yesNoDefaultResponses); 439 choices.addAll(StaticUtils.arrayToList( 440 INFO_TASKINFO_CMD_CANCEL_CHAR.get().toString())); 441 442 prompt.append(" ") 443 .append("(").append(INFO_PROMPT_YES_COMPLETE_ANSWER.get()) 444 .append("/").append(INFO_PROMPT_NO_COMPLETE_ANSWER.get()) 445 .append("/").append(INFO_TASKINFO_CMD_CANCEL_CHAR.get()); 446 } 447 prompt.append(")"); 448 449 logger.info(LocalizableMessage.raw(cc.getPrompt())); 450 451 // Displays the output and 452 // while it hasn't a valid response, question is repeated. 453 if (isInteractive()) 454 { 455 while (true) 456 { 457 String value = null; 458 try 459 { 460 value = 461 readInput(LocalizableMessage.raw(prompt), defaultOption, 462 Style.SUBTITLE); 463 } 464 catch (ClientException e) 465 { 466 logger.error(LocalizableMessage.raw(e.getMessage())); 467 break; 468 } 469 470 String valueLC = value.toLowerCase(); 471 if ((valueLC.equals(INFO_PROMPT_YES_FIRST_LETTER_ANSWER.get().toString()) 472 || valueLC.equals(INFO_PROMPT_YES_COMPLETE_ANSWER.get().toString())) 473 && choices.contains(value)) 474 { 475 cc.setSelectedIndex(ConfirmationCallback.YES); 476 break; 477 } 478 else if ((valueLC.equals(INFO_PROMPT_NO_FIRST_LETTER_ANSWER.get().toString()) 479 || valueLC.equals(INFO_PROMPT_NO_COMPLETE_ANSWER.get().toString())) 480 && choices.contains(value)) 481 { 482 cc.setSelectedIndex(ConfirmationCallback.NO); 483 break; 484 } 485 else if (valueLC.equals(INFO_TASKINFO_CMD_CANCEL_CHAR.get().toString()) 486 && choices.contains(value)) 487 { 488 cc.setSelectedIndex(ConfirmationCallback.CANCEL); 489 break; 490 } 491 logger.info(LocalizableMessage.raw(value)); 492 } 493 } 494 else // Non interactive mode : 495 { 496 // Force mode. 497 if (isForceUpgrade()) 498 { 499 cc.setSelectedIndex(ConfirmationCallback.YES); 500 } 501 else // Default non interactive mode. 502 { 503 cc.setSelectedIndex(cc.getDefaultOption()); 504 } 505 // Displays the prompt 506 prompt.append(" ").append(getDefaultOption(cc.getSelectedIndex())); 507 println(Style.SUBTITLE, LocalizableMessage.raw(prompt), 0); 508 logger.info(LocalizableMessage.raw(getDefaultOption(cc.getSelectedIndex()))); 509 } 510 } 511 else 512 { 513 logger.error(LocalizableMessage.raw("Unrecognized Callback")); 514 throw new UnsupportedCallbackException(c, "Unrecognized Callback"); 515 } 516 } 517 } 518 519 520 521 private static String getDefaultOption(final int defaultOption) 522 { 523 if (defaultOption == ConfirmationCallback.YES) 524 { 525 return INFO_PROMPT_YES_COMPLETE_ANSWER.get().toString(); 526 } 527 else if (defaultOption == ConfirmationCallback.NO) 528 { 529 return INFO_PROMPT_NO_COMPLETE_ANSWER.get().toString(); 530 } 531 else if (defaultOption == ConfirmationCallback.CANCEL) 532 { 533 return INFO_TASKINFO_CMD_CANCEL_CHAR.get().toString(); 534 } 535 return null; 536 } 537}