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}