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 * Copyright 2008-2009 Sun Microsystems, Inc. 025 * Portions Copyright 2013-2015 ForgeRock AS. 026 */ 027package org.opends.quicksetup; 028 029import org.forgerock.i18n.LocalizableMessage; 030import com.forgerock.opendj.cli.ArgumentParser; 031 032import static org.opends.messages.QuickSetupMessages.*; 033import static org.opends.server.util.DynamicConstants.PRINTABLE_VERSION_STRING; 034import static com.forgerock.opendj.cli.ArgumentConstants.*; 035 036import org.opends.quicksetup.util.Utils; 037 038import java.io.PrintStream; 039import java.io.File; 040 041import org.forgerock.i18n.slf4j.LocalizedLogger; 042 043/** 044 * Responsible for providing initial evaluation of command line arguments 045 * and determining whether to launch a CLI, GUI, or print a usage statement. 046 */ 047public abstract class Launcher { 048 049 private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); 050 051 /** Arguments with which this launcher was invoked. */ 052 protected String[] args; 053 054 /** 055 * Creates a Launcher. 056 * @param args String[] of argument passes from the command line 057 */ 058 public Launcher(String[] args) { 059 if (args == null) { 060 throw new IllegalArgumentException("args cannot be null"); 061 } 062 this.args = args; 063 064 } 065 066 /** 067 * Gets the arguments with which this launcher was invoked. 068 * @return String[] args from the CLI invocation 069 */ 070 public String[] getArguments() { 071 return this.args; 072 } 073 074 /** 075 * Gets an argument parser appropriate for this CLI launcher. 076 * 077 * @return ArgumentParser for parsing args 078 */ 079 public abstract ArgumentParser getArgumentParser(); 080 081 /** 082 * Indicates whether or not the launcher should print a usage 083 * statement based on the content of the arguments passed into 084 * the constructor. 085 * @return boolean where true indicates usage should be printed 086 */ 087 protected boolean shouldPrintUsage() { 088 if (args != null && args.length > 0) { 089 for (String arg : args) { 090 if (arg.equals("-?") || 091 arg.equalsIgnoreCase("-H") || 092 arg.equalsIgnoreCase("--help")) { 093 return true; 094 } 095 } 096 } 097 return false; 098 } 099 100 /** 101 * Indicates whether or not the launcher should print a usage 102 * statement based on the content of the arguments passed into 103 * the constructor. 104 * @return boolean where true indicates usage should be printed 105 */ 106 protected boolean isQuiet() { 107 if (args != null && args.length > 0) { 108 for (String arg : args) { 109 if (arg.equals("-?") || 110 arg.equalsIgnoreCase("-Q") || 111 arg.equalsIgnoreCase("--quiet")) { 112 return true; 113 } 114 } 115 } 116 return false; 117 } 118 119 /** 120 * Indicates whether or not the launcher should print a version 121 * statement based on the content of the arguments passed into 122 * the constructor. 123 * @return boolean where true indicates version should be printed 124 */ 125 protected boolean shouldPrintVersion() { 126 if (args != null && args.length > 0) 127 { 128 for (String arg : args) 129 { 130 if (arg.equalsIgnoreCase("--version")) 131 { 132 return true; 133 } 134 } 135 } 136 return false; 137 } 138 139 /** 140 * Indicates whether the launcher will launch a command line versus 141 * a graphical application based on the contents of the arguments 142 * passed into the constructor. 143 * 144 * @return boolean where true indicates that a CLI application 145 * should be launched 146 */ 147 protected boolean isCli() { 148 for (String arg : args) { 149 if (arg.equalsIgnoreCase("--"+OPTION_LONG_CLI) || 150 arg.equalsIgnoreCase("-"+OPTION_SHORT_CLI)) { 151 return true; 152 } 153 } 154 return false; 155 } 156 157 /** 158 * Prints a usage message to the terminal. 159 * @param i18nMsg localized user message that will be printed to the terminal. 160 * @param toStdErr whether the message must be printed to the standard error 161 * or the standard output. 162 */ 163 protected void printUsage(String i18nMsg, boolean toStdErr) { 164 if (toStdErr) 165 { 166 System.err.println(i18nMsg); 167 } 168 else 169 { 170 System.out.println(i18nMsg); 171 } 172 } 173 174 /** 175 * Launches the graphical uninstall. The graphical uninstall is launched in a 176 * different thread that the main thread because if we have a problem with the 177 * graphical system (for instance the DISPLAY environment variable is not 178 * correctly set) the native libraries will call exit. However if we launch 179 * this from another thread, the thread will just be killed. 180 * 181 * This code also assumes that if the call to SplashWindow.main worked (and 182 * the splash screen was displayed) we will never get out of it (we will call 183 * a System.exit() when we close the graphical uninstall dialog). 184 * 185 * @param args String[] the arguments used to call the SplashWindow main 186 * method 187 * @return 0 if everything worked fine, or 1 if we could not display properly 188 * the SplashWindow. 189 */ 190 protected int launchGui(final String[] args) 191 { 192// Setup MacOSX native menu bar before AWT is loaded. 193 Utils.setMacOSXMenuBar(getFrameTitle()); 194 final int[] returnValue = 195 { -1 }; 196 Thread t = new Thread(new Runnable() 197 { 198 public void run() 199 { 200 try 201 { 202 SplashScreen.main(args); 203 returnValue[0] = 0; 204 } 205 catch (Throwable t) 206 { 207 if (QuickSetupLog.isInitialized()) 208 { 209 logger.warn(LocalizableMessage.raw("Error launching GUI: "+t)); 210 StringBuilder buf = new StringBuilder(); 211 while (t != null) 212 { 213 for (StackTraceElement aStack : t.getStackTrace()) { 214 buf.append(aStack).append("\n"); 215 } 216 217 t = t.getCause(); 218 if (t != null) 219 { 220 buf.append("Root cause:\n"); 221 } 222 } 223 logger.warn(LocalizableMessage.raw(buf)); 224 } 225 } 226 } 227 }); 228 /* 229 * This is done to avoid displaying the stack that might occur if there are 230 * problems with the display environment. 231 */ 232 PrintStream printStream = System.err; 233 System.setErr(Utils.getEmptyPrintStream()); 234 t.start(); 235 try 236 { 237 t.join(); 238 } 239 catch (InterruptedException ie) 240 { 241 /* An error occurred, so the return value will be -1. We got nothing to 242 do with this exception. */ 243 } 244 System.setErr(printStream); 245 return returnValue[0]; 246 } 247 248 /** 249 * Gets the frame title of the GUI application that will be used 250 * in some operating systems. 251 * @return internationalized String representing the frame title 252 */ 253 protected abstract LocalizableMessage getFrameTitle(); 254 255 /** 256 * Launches the command line based uninstall. 257 * 258 * @param cliApp the CLI application to launch 259 * @return 0 if everything worked fine, and an error code if something wrong 260 * occurred. 261 */ 262 protected int launchCli(CliApplication cliApp) 263 { 264 System.setProperty(Constants.CLI_JAVA_PROPERTY, "true"); 265 QuickSetupCli cli = new QuickSetupCli(cliApp, this); 266 ReturnCode returnValue = cli.run(); 267 if (returnValue.equals(ReturnCode.USER_DATA_ERROR)) 268 { 269 printUsage(true); 270 System.exit(ReturnCode.USER_DATA_ERROR.getReturnCode()); 271 } 272 else if (returnValue.equals(ReturnCode.CANCELED)) 273 { 274 System.exit(ReturnCode.CANCELED.getReturnCode()); 275 } 276 else if (returnValue.equals(ReturnCode.USER_INPUT_ERROR)) 277 { 278 System.exit(ReturnCode.USER_INPUT_ERROR.getReturnCode()); 279 } 280 return returnValue.getReturnCode(); 281 } 282 283 /** 284 * Prints the version statement to standard output terminal. 285 */ 286 protected void printVersion() 287 { 288 System.out.print(PRINTABLE_VERSION_STRING); 289 } 290 291 /** 292 * Prints a usage statement to terminal and exits with an error 293 * code. 294 * @param toStdErr whether the message must be printed to the standard error 295 * or the standard output. 296 */ 297 protected void printUsage(boolean toStdErr) { 298 try 299 { 300 ArgumentParser argParser = getArgumentParser(); 301 if (argParser != null) { 302 String msg = argParser.getUsage(); 303 printUsage(msg, toStdErr); 304 } 305 } 306 catch (Throwable t) 307 { 308 System.out.println("ERROR: "+t); 309 t.printStackTrace(); 310 } 311 } 312 313 /** 314 * Creates a CLI application that will be run if the 315 * launcher needs to launch a CLI application. 316 * @return CliApplication that will be run 317 */ 318 protected abstract CliApplication createCliApplication(); 319 320 /** 321 * Called before the launcher launches the GUI. Here 322 * subclasses can do any application specific things 323 * like set system properties of print status messages 324 * that need to be done before the GUI launches. 325 */ 326 protected abstract void willLaunchGui(); 327 328 /** 329 * Called if launching of the GUI failed. Here 330 * subclasses can so application specific things 331 * like print a message. 332 * @param logFileName the log file containing more information about why 333 * the launch failed. 334 */ 335 protected abstract void guiLaunchFailed(String logFileName); 336 337 /** 338 * The main method which is called by the command lines. 339 */ 340 public void launch() { 341 if (shouldPrintVersion()) { 342 ArgumentParser parser = getArgumentParser(); 343 if (parser == null || !parser.usageOrVersionDisplayed()) { 344 printVersion(); 345 } 346 System.exit(ReturnCode.PRINT_VERSION.getReturnCode()); 347 } 348 else if (shouldPrintUsage()) { 349 ArgumentParser parser = getArgumentParser(); 350 if (parser == null || !parser.usageOrVersionDisplayed()) { 351 printUsage(false); 352 } 353 System.exit(ReturnCode.SUCCESSFUL.getReturnCode()); 354 } else if (isCli()) { 355 CliApplication cliApp = createCliApplication(); 356 int exitCode = launchCli(cliApp); 357 preExit(cliApp); 358 System.exit(exitCode); 359 } else { 360 willLaunchGui(); 361 int exitCode = launchGui(args); 362 if (exitCode != 0) { 363 File logFile = QuickSetupLog.getLogFile(); 364 if (logFile != null) 365 { 366 guiLaunchFailed(logFile.toString()); 367 } 368 else 369 { 370 guiLaunchFailed(null); 371 } 372 CliApplication cliApp = createCliApplication(); 373 exitCode = launchCli(cliApp); 374 preExit(cliApp); 375 System.exit(exitCode); 376 } 377 } 378 } 379 380 private void preExit(CliApplication cliApp) { 381 if (cliApp != null) { 382 UserData ud = cliApp.getUserData(); 383 if (ud != null && !ud.isQuiet()) { 384 385 // Add an extra space systematically 386 System.out.println(); 387 388 File logFile = QuickSetupLog.getLogFile(); 389 if (logFile != null) { 390 System.out.println(INFO_GENERAL_SEE_FOR_DETAILS.get( 391 QuickSetupLog.getLogFile().getPath())); 392 } 393 } 394 } 395 } 396}