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