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}