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