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