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