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-2010 Sun Microsystems, Inc.
025 *      Portions Copyright 2011-2015 ForgeRock AS
026 */
027package org.opends.guitools.controlpanel;
028
029import static com.forgerock.opendj.cli.Utils.*;
030
031import static org.opends.messages.AdminToolMessages.*;
032import static org.opends.messages.ToolMessages.*;
033
034import java.io.File;
035import java.io.PrintStream;
036
037import javax.swing.SwingUtilities;
038
039import org.forgerock.i18n.LocalizableMessage;
040import org.forgerock.i18n.slf4j.LocalizedLogger;
041import org.opends.guitools.controlpanel.util.ControlPanelLog;
042import org.opends.messages.AdminToolMessages;
043import org.opends.quicksetup.ui.UIFactory;
044import org.opends.quicksetup.util.Utils;
045import org.opends.server.types.InitializationException;
046import org.opends.server.util.BuildVersion;
047import org.opends.server.util.DynamicConstants;
048
049import com.forgerock.opendj.cli.ArgumentException;
050
051/**
052 * The class that is invoked directly by the control-panel command-line.  This
053 * class basically displays a splash screen and then calls the methods in
054 * ControlPanel.  It also is in charge of detecting whether there are issues
055 * with the
056 *
057 */
058public class ControlPanelLauncher
059{
060  private static ControlPanelArgumentParser  argParser;
061
062  /** Prefix for log files. */
063  public static final String LOG_FILE_PREFIX = "opendj-control-panel-";
064
065  /** Suffix for log files. */
066  public static final String LOG_FILE_SUFFIX = ".log";
067
068  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
069
070  /**
071   * Main method invoked by the control-panel script.
072   * @param args the arguments.
073   */
074  public static void main(String[] args)
075  {
076    try {
077      ControlPanelLog.initLogFileHandler(
078          File.createTempFile(LOG_FILE_PREFIX, LOG_FILE_SUFFIX));
079    } catch (Throwable t) {
080      System.err.println("Unable to initialize log");
081      t.printStackTrace();
082    }
083
084    argParser = new ControlPanelArgumentParser(
085        ControlPanelLauncher.class.getName(),
086        INFO_CONTROL_PANEL_LAUNCHER_USAGE_DESCRIPTION.get());
087    //  Validate user provided data
088    try
089    {
090      argParser.initializeArguments();
091      argParser.parseArguments(args);
092    }
093    catch (ArgumentException ae)
094    {
095      argParser.displayMessageAndUsageReference(System.err, ERR_ERROR_PARSING_ARGS.get(ae.getMessage()));
096      System.exit(ErrorReturnCode.ERROR_PARSING_ARGS.getReturnCode());
097    }
098
099    // If we should just display usage or version information,
100    // then print it and exit.
101    if (argParser.usageOrVersionDisplayed())
102    {
103      ControlPanelLog.closeAndDeleteLogFile();
104      System.exit(ErrorReturnCode.SUCCESSFUL_NOP.getReturnCode());
105    }
106
107    // Checks the version - if upgrade required, the tool is unusable
108    if (!argParser.isRemote())
109    {
110      try
111      {
112        BuildVersion.checkVersionMismatch();
113      }
114      catch (InitializationException e)
115      {
116        System.err.println(wrapText(e.getMessage(), MAX_LINE_WIDTH));
117        System.exit(ErrorReturnCode.ERROR_UNEXPECTED.getReturnCode());
118      }
119    }
120
121    if (!argParser.usageOrVersionDisplayed())
122    {
123      int exitCode = launchControlPanel(args);
124      if (exitCode != 0)
125      {
126        String logFileName = null;
127        if (ControlPanelLog.getLogFile() != null)
128        {
129          logFileName = ControlPanelLog.getLogFile().toString();
130        }
131        if (logFileName != null)
132        {
133          System.err.println(wrapText(
134              ERR_CONTROL_PANEL_LAUNCHER_GUI_LAUNCH_FAILED_DETAILS.get(
135                  logFileName),
136                  Utils.getCommandLineMaxLineWidth()));
137        }
138        else
139        {
140          System.err.println(wrapText(
141              ERR_CONTROL_PANEL_LAUNCHER_GUI_LAUNCH_FAILED.get(),
142              Utils.getCommandLineMaxLineWidth()));
143        }
144        System.exit(exitCode);
145      }
146    }
147    ControlPanelLog.closeAndDeleteLogFile();
148  }
149
150  /**
151   * Launches the graphical status panel. It is launched in a
152   * different thread that the main thread because if we have a problem with the
153   * graphical system (for instance the DISPLAY environment variable is not
154   * correctly set) the native libraries will call exit. However if we launch
155   * this from another thread, the thread will just be killed.
156   *
157   * This code also assumes that if the call to ControlPanelSplashScreen.main
158   * worked (and the splash screen was displayed) we will never get out of it
159   * (we will call a System.exit() when we close the graphical status dialog).
160   *
161   * @param  args the arguments used to call the
162   *         ControlPanelSplashScreen main method.
163   * @return 0 if everything worked fine, or 1 if we could not display properly
164   *         the ControlPanelSplashScreen.
165   */
166  private static int launchControlPanel(final String[] args)
167  {
168    final int[] returnValue = { -1 };
169    Thread t = new Thread(new Runnable()
170    {
171      @Override
172      public void run()
173      {
174        try
175        {
176          try
177          {
178            initLookAndFeel();
179          }
180          catch (Throwable t)
181          {
182            logger.warn(LocalizableMessage.raw("Error setting look and feel: "+t, t));
183          }
184
185          ControlPanelSplashScreen.main(args);
186          returnValue[0] = 0;
187        }
188        catch (Throwable t)
189        {
190          if (ControlPanelLog.isInitialized())
191          {
192            logger.warn(LocalizableMessage.raw("Error launching GUI: "+t));
193            StringBuilder buf = new StringBuilder();
194            while (t != null)
195            {
196              for (StackTraceElement aStack : t.getStackTrace()) {
197                buf.append(aStack).append("\n");
198              }
199
200              t = t.getCause();
201              if (t != null)
202              {
203                buf.append("Root cause:\n");
204              }
205            }
206            logger.warn(LocalizableMessage.raw(buf));
207          }
208        }
209      }
210    });
211    /*
212     * This is done to avoid displaying the stack that might occur if there are
213     * problems with the display environment.
214     */
215    PrintStream printStream = System.err;
216    System.setErr(Utils.getEmptyPrintStream());
217    t.start();
218    try
219    {
220      t.join();
221    }
222    catch (InterruptedException ie)
223    {
224      /* An error occurred, so the return value will be -1.  We got nothing to
225      do with this exception. */
226    }
227    System.setErr(printStream);
228    return returnValue[0];
229  }
230
231  private static void initLookAndFeel() throws Throwable
232  {
233//  Setup MacOSX native menu bar before AWT is loaded.
234    LocalizableMessage title = Utils.getCustomizedObject("INFO_CONTROL_PANEL_TITLE",
235        AdminToolMessages.INFO_CONTROL_PANEL_TITLE.get(
236        DynamicConstants.PRODUCT_NAME), LocalizableMessage.class);
237    Utils.setMacOSXMenuBar(title);
238    UIFactory.initializeLookAndFeel();
239  }
240}
241
242
243/**
244 * The enumeration containing the different return codes that the command-line
245 * can have.
246 *
247 */
248enum ErrorReturnCode
249{
250  /**
251   * Successful display of the status.
252   */
253  SUCCESSFUL(0),
254  /**
255   * We did no have an error but the status was not displayed (displayed
256   * version or usage).
257   */
258  SUCCESSFUL_NOP(0),
259  /**
260   * Unexpected error (potential bug).
261   */
262  ERROR_UNEXPECTED(1),
263  /**
264   * Cannot parse arguments.
265   */
266  ERROR_PARSING_ARGS(2),
267  /**
268   * User cancelled (for instance not accepting the certificate proposed) or
269   * could not use the provided connection parameters in interactive mode.
270   */
271  USER_CANCELLED_OR_DATA_ERROR(3),
272  /**
273   * This occurs for instance when the authentication provided by the user is
274   * not valid.
275   */
276  ERROR_READING_CONFIGURATION_WITH_LDAP(4);
277
278  private int returnCode;
279  private ErrorReturnCode(int returnCode)
280  {
281    this.returnCode = returnCode;
282  }
283
284  /**
285   * Returns the corresponding return code value.
286   * @return the corresponding return code value.
287   */
288  public int getReturnCode()
289  {
290    return returnCode;
291  }
292}
293
294/**
295 * The splash screen for the control panel.
296 *
297 */
298class ControlPanelSplashScreen extends org.opends.quicksetup.SplashScreen
299{
300  private static final long serialVersionUID = 4472839063380302713L;
301
302  private static ControlPanel controlPanel;
303
304  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
305
306  /**
307   * The main method for this class.
308   * It can be called from the event thread and outside the event thread.
309   * @param args arguments to be passed to the method ControlPanel.initialize
310   */
311  public static void main(String[] args)
312  {
313    ControlPanelSplashScreen screen = new ControlPanelSplashScreen();
314    screen.display(args);
315  }
316
317
318  /**
319   * This methods constructs the ControlPanel object.
320   * This method assumes that is being called outside the event thread.
321   * @param args arguments to be passed to the method ControlPanel.initialize.
322   */
323  @Override
324  protected void constructApplication(String[] args)
325  {
326    try
327    {
328      controlPanel = new ControlPanel();
329      controlPanel.initialize(args);
330    } catch (Throwable t)
331    {
332      if (ControlPanelLog.isInitialized())
333      {
334        logger.error(LocalizableMessage.raw("Error launching GUI: "+t, t));
335      }
336      InternalError error =
337        new InternalError("Failed to invoke initialize method");
338      error.initCause(t);
339      throw error;
340    }
341  }
342
343  /**
344   * This method displays the StatusPanel dialog.
345   * @see org.opends.guitools.controlpanel.ControlPanel#createAndDisplayGUI()
346   * This method assumes that is being called outside the event thread.
347   */
348  @Override
349  protected void displayApplication()
350  {
351    Runnable runnable = new Runnable()
352    {
353      @Override
354      public void run()
355      {
356        try
357        {
358          logger.info(LocalizableMessage.raw("going to call createAndDisplayGUI."));
359          controlPanel.createAndDisplayGUI();
360          logger.info(LocalizableMessage.raw("called createAndDisplayGUI."));
361        } catch (Throwable t)
362        {
363          logger.error(LocalizableMessage.raw("Error displaying GUI: "+t, t));
364          InternalError error =
365            new InternalError("Failed to invoke display method");
366          error.initCause(t);
367          throw error;
368        }
369      }
370    };
371    if (SwingUtilities.isEventDispatchThread())
372    {
373      runnable.run();
374    }
375    else
376    {
377      try
378      {
379        SwingUtilities.invokeAndWait(runnable);
380      }
381      catch (Throwable t)
382      {
383        logger.error(LocalizableMessage.raw("Error calling SwingUtilities.invokeAndWait: "+t,
384            t));
385        InternalError error =
386          new InternalError(
387              "Failed to invoke SwingUtilities.invokeAndWait method");
388        error.initCause(t);
389        throw error;
390      }
391    }
392  }
393}
394
395