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 2012-2015 ForgeRock AS.
026 */
027
028package org.opends.quicksetup.ui;
029
030import org.opends.quicksetup.util.UIKeyStore;
031import org.opends.quicksetup.Application;
032import org.opends.quicksetup.ApplicationException;
033import org.opends.quicksetup.ButtonName;
034import org.opends.quicksetup.UserData;
035import org.opends.quicksetup.UserDataCertificateException;
036import org.opends.quicksetup.UserDataException;
037import org.opends.quicksetup.WizardStep;
038import org.opends.quicksetup.webstart.WebStartDownloader;
039import org.forgerock.i18n.LocalizableMessage;
040import static org.opends.messages.QuickSetupMessages.*;
041
042import javax.swing.*;
043import java.awt.event.WindowEvent;
044import java.security.cert.X509Certificate;
045import java.util.LinkedHashSet;
046import java.util.Set;
047
048import org.forgerock.i18n.slf4j.LocalizedLogger;
049
050/**
051 * This class represents an application with a wizard GUI that can be run in the
052 * context of QuickSetup.  Examples of applications might be 'installer',
053 * and 'uninstaller'.
054 */
055public abstract class GuiApplication extends Application {
056
057  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
058
059  /** The currently displayed wizard step. */
060  private WizardStep displayedStep;
061
062  /** Downloads .jar files for webstart application. */
063  protected WebStartDownloader loader;
064
065  /** The QuickSetupDialog in control. */
066  private QuickSetupDialog qs;
067
068  private String[] args = {};
069
070  /**
071   * Constructs an instance of an application.  Subclasses
072   * of this application must have a default constructor.
073   */
074  public GuiApplication() {
075    this.displayedStep = getFirstWizardStep();
076  }
077
078  /**
079   * Gets the frame title of the GUI application that will be used
080   * in some operating systems.
081   * @return internationalized String representing the frame title
082   */
083  public abstract LocalizableMessage getFrameTitle();
084
085  /**
086   * Returns the initial wizard step.
087   * @return Step representing the first step to show in the wizard
088   */
089  public abstract WizardStep getFirstWizardStep();
090
091  /**
092   * Called by the quicksetup controller when the user advances to
093   * a new step in the wizard.  Applications are expected to manipulate
094   * the QuickSetupDialog to reflect the current step.
095   *
096   * @param step     Step indicating the new current step
097   * @param userData UserData representing the data specified by the user
098   * @param dlg      QuickSetupDialog hosting the wizard
099   */
100  public void setDisplayedWizardStep(WizardStep step,
101                                        UserData userData,
102                                        QuickSetupDialog dlg) {
103    this.displayedStep = step;
104
105    // First call the panels to do the required updates on their layout
106    dlg.setDisplayedStep(step, userData);
107    setWizardDialogState(dlg, userData, step);
108  }
109
110  /**
111   * Called when the user advances to new step in the wizard.  Applications
112   * are expected to manipulate the QuickSetupDialog to reflect the current
113   * step.
114   * @param dlg QuickSetupDialog hosting the wizard
115   * @param userData UserData representing the data specified by the user
116   * @param step Step indicating the new current step
117   */
118  public abstract void setWizardDialogState(QuickSetupDialog dlg,
119                                               UserData userData,
120                                               WizardStep step);
121
122  /**
123   * Returns the tab formatted.
124   * @return the tab formatted.
125   */
126  protected LocalizableMessage getTab()
127  {
128    return formatter.getTab();
129  }
130
131  /**
132   * Called by the controller when the window is closing.  The application
133   * can take application specific actions here.
134   * @param dlg QuickSetupDialog that will be closing
135   * @param evt The event from the Window indicating closing
136   */
137  public abstract void windowClosing(QuickSetupDialog dlg, WindowEvent evt);
138
139  /**
140   * This method is called when we detected that there is something installed
141   * we inform of this to the user and the user wants to proceed with the
142   * installation destroying the contents of the data and the configuration
143   * in the current installation.
144   */
145  public void forceToDisplay() {
146    // This is really only appropriate for Installer.
147    // The default implementation is to do nothing.
148    // The Installer application overrides this with
149    // whatever it needs.
150  }
151
152  /**
153   * Called before the application cancels its operation, giving the
154   * user a chance to confirm the cancellation action.
155   * @param qs QuickSetup that can be used for confirming
156   * @return boolean where true indicates that the user answered
157   * affirmatively to the cancelation confirmation
158   */
159  public boolean confirmCancel(QuickSetup qs) {
160    return qs.displayConfirmation(
161          INFO_CONFIRM_CANCEL_PROMPT.get(),
162          INFO_CONFIRM_CANCEL_TITLE.get());
163  }
164
165  /**
166   * Get the name of the button that will receive initial focus.
167   * @return ButtonName of the button to receive initial focus
168   */
169  public abstract ButtonName getInitialFocusButtonName();
170
171  /**
172   * Creates the main panel for the wizard dialog.
173   * @param dlg QuickSetupDialog used
174   * @return JPanel frame panel
175   */
176  public JPanel createFramePanel(QuickSetupDialog dlg) {
177    return new FramePanel(dlg.getStepsPanel(),
178            dlg.getCurrentStepPanel(),
179            dlg.getButtonsPanel());
180  }
181
182  /**
183   * Returns the set of wizard steps used in this application's wizard.
184   * @return Set of Step objects representing wizard steps
185   */
186  public abstract Set<? extends WizardStep> getWizardSteps();
187
188  /**
189   * Creates a wizard panel given a specific step.
190   * @param step for which a panel representation should be created
191   * @return QuickSetupStepPanel for representing the <code>step</code>
192   */
193  public abstract QuickSetupStepPanel createWizardStepPanel(WizardStep step);
194
195  /**
196   * Gets the next step in the wizard given a current step.
197   * @param step Step the current step
198   * @return Step the next step
199   */
200  public abstract WizardStep getNextWizardStep(WizardStep step);
201
202  /**
203   * Gets the previous step in the wizard given a current step.
204   * @param step Step the current step
205   * @return Step the previous step
206   */
207  public abstract WizardStep getPreviousWizardStep(WizardStep step);
208
209  /**
210   * Gets the finished step in the wizard.
211   * @return Step the finished step
212   */
213  public abstract WizardStep getFinishedStep();
214
215  /**
216   * Gets the currently displayed wizard step.
217   * @return WizardStep being displayed.
218   */
219  public WizardStep getCurrentWizardStep() {
220    return displayedStep;
221  }
222
223  /**
224   * Indicates whether the provided <code>step</code> is a sub step or not.
225   * @param step WizardStep for which the return value indicates whether
226   * or not is a sub step.
227   * @return boolean where true indicates the provided <code>step</code> is a
228   * substep.
229   */
230  public boolean isSubStep(WizardStep step)
231  {
232    return false;
233  }
234
235  /**
236   * Indicates whether the provided <code>step</code> is visible or not
237   * depending on the contents of the UserData object that is provided.
238   * @param step WizardStep for which the return value indicates whether
239   * or not is visible.
240   * @param userData the UserData to be used to determine if the step is
241   * visible or not.
242   * @return boolean where true indicates the provided <code>step</code> is
243   * visible.
244   */
245  public boolean isVisible(WizardStep step, UserData userData)
246  {
247    return true;
248  }
249
250  /**
251   * Indicates whether the provided <code>step</code> is visible or not
252   * depending on the contents of the QuickSetup object that is provided.
253   * @param step WizardStep for which the return value indicates whether
254   * or not is visible.
255   * @param qs the QuickSetup to be used to determine if the step is
256   * visible or not.
257   * @return boolean where true indicates the provided <code>step</code> is
258   * visible.
259   */
260  public boolean isVisible(WizardStep step, QuickSetup qs)
261  {
262    return true;
263  }
264
265  /**
266   * Returns the list of all the steps in an ordered manner.  This is required
267   * because in the case of an application with substeps the user of the other
268   * interfaces is not enough.  This is a default implementation that uses
269   * the getNextWizardStep method to calculate the list that work for
270   * applications with no substeps.
271   * @return a list containing ALL the steps (including substeps) in an ordered
272   * manner.
273   */
274  public LinkedHashSet<WizardStep> getOrderedSteps()
275  {
276    LinkedHashSet<WizardStep> orderedSteps = new LinkedHashSet<>();
277    WizardStep step = getFirstWizardStep();
278    orderedSteps.add(step);
279    while (null != (step = getNextWizardStep(step))) {
280      orderedSteps.add(step);
281    }
282    return orderedSteps;
283  }
284
285  /**
286   * Indicates whether or not the user is allowed to return to a previous
287   * step from <code>step</code>.
288   * @param step WizardStep for which the the return value indicates whether
289   * or not the user can return to a previous step
290   * @return boolean where true indicates the user can return to a previous
291   * step from <code>step</code>
292   */
293  public boolean canGoBack(WizardStep step) {
294    return !getFirstWizardStep().equals(step);
295  }
296
297  /**
298   * Indicates whether or not the user is allowed to move to a new
299   * step from <code>step</code>.
300   * @param step WizardStep for which the the return value indicates whether
301   * or not the user can move to a new step
302   * @return boolean where true indicates the user can move to a new
303   * step from <code>step</code>
304   */
305  public boolean canGoForward(WizardStep step) {
306    return !step.isProgressStep() && getNextWizardStep(step) != null;
307  }
308
309  /**
310   * Indicates whether or not the user is allowed to finish the wizard from
311   * <code>step</code>.
312   * @param step WizardStep for which the the return value indicates whether
313   * or not the user can finish the wizard
314   * @return boolean where true indicates the user can finish the wizard
315   */
316  public abstract boolean canFinish(WizardStep step);
317
318  /**
319   * Called when the user has clicked the 'previous' button.
320   * @param cStep WizardStep at which the user clicked the previous button
321   * @param qs QuickSetup controller
322   */
323  public abstract void previousClicked(WizardStep cStep, QuickSetup qs);
324
325  /**
326   * Called when the user has clicked the 'finish' button.
327   * @param cStep WizardStep at which the user clicked the previous button
328   * @param qs QuickSetup controller
329   * @return boolean that the application uses to indicate the the
330   * application should be launched.  If false, the application is
331   * responsible for updating the user data for the final screen and
332   * launching the application if this is the desired behavior.
333   */
334  public abstract boolean finishClicked(final WizardStep cStep,
335                                     final QuickSetup qs);
336
337  /**
338   * Called when the user has clicked the 'next' button.
339   * @param cStep WizardStep at which the user clicked the next button
340   * @param qs QuickSetup controller
341   */
342  public abstract void nextClicked(WizardStep cStep, QuickSetup qs);
343
344  /**
345   * Called when the user has clicked the 'close' button.
346   * @param cStep WizardStep at which the user clicked the close button
347   * @param qs QuickSetup controller
348   */
349  public void closeClicked(WizardStep cStep, QuickSetup qs) {
350    qs.quit();
351  }
352
353  /**
354   * Called when the user has clicked the 'quit' button.
355   * @param step WizardStep at which the user clicked the quit button
356   * @param qs QuickSetup controller
357   */
358  public void quitClicked(WizardStep step, QuickSetup qs) {
359    qs.quit();
360  }
361
362  /**
363   * Called whenever this application should update its user data from
364   * values found in QuickSetup.
365   * @param cStep current wizard step
366   * @param qs QuickSetup controller
367   * @throws org.opends.quicksetup.UserDataException if there is a problem with
368   *  the data
369   */
370  public abstract void updateUserData(WizardStep cStep, QuickSetup qs)
371          throws UserDataException;
372
373  /**
374   * Gets the key for the close button's tool tip text.
375   * @return String key of the text in the resource bundle
376   */
377  public LocalizableMessage getCloseButtonToolTip() {
378    return INFO_CLOSE_BUTTON_TOOLTIP.get();
379  }
380
381  /**
382   * Gets the key for the quit button's tool tip text.
383   * @return String key of the text in the resource bundle
384   */
385  public LocalizableMessage getQuitButtonToolTip() {
386    return INFO_QUIT_BUTTON_INSTALL_TOOLTIP.get();
387  }
388
389  /**
390   * Gets the key for the finish button's tool tip text.
391   * @return String key of the text in the resource bundle
392   */
393  public LocalizableMessage getFinishButtonToolTip() {
394    return INFO_FINISH_BUTTON_TOOLTIP.get();
395  }
396
397  /**
398   * Gets the key for the finish button's label.
399   * @return String key of the text in the resource bundle
400   */
401  public LocalizableMessage getFinishButtonLabel() {
402    return INFO_FINISH_BUTTON_LABEL.get();
403  }
404
405  /**
406   * Indicates whether the finish button must be placed on the left (close to
407   * "Next" button) or on the right (close to "Quit" button).
408   * @return <CODE>true</CODE> if the finish button must be placed on the left
409   * and <CODE>false</CODE> otherwise.
410   */
411  public boolean finishOnLeft()
412  {
413    return true;
414  }
415
416  /**
417   * Updates the list of certificates accepted by the user in the trust manager
418   * based on the information stored in the UserDataCertificateException we got
419   * when trying to connect in secure mode.
420   * @param ce the UserDataCertificateException that contains the information to
421   * be used.
422   * @param acceptPermanently whether the certificate must be accepted
423   * permanently or not.
424   */
425  protected void acceptCertificateForException(UserDataCertificateException ce,
426      boolean acceptPermanently)
427  {
428    X509Certificate[] chain = ce.getChain();
429    String authType = ce.getAuthType();
430    String host = ce.getHost();
431
432    if (chain != null && authType != null && host != null)
433    {
434      logger.info(LocalizableMessage.raw("Accepting certificate presented by host "+host));
435      getTrustManager().acceptCertificate(chain, authType, host);
436    }
437    else
438    {
439      if (chain == null)
440      {
441        logger.warn(LocalizableMessage.raw(
442            "The chain is null for the UserDataCertificateException"));
443      }
444      if (authType == null)
445      {
446        logger.warn(LocalizableMessage.raw(
447            "The auth type is null for the UserDataCertificateException"));
448      }
449      if (host == null)
450      {
451        logger.warn(LocalizableMessage.raw(
452            "The host is null for the UserDataCertificateException"));
453      }
454    }
455    if (acceptPermanently && chain != null)
456    {
457      try
458      {
459        UIKeyStore.acceptCertificate(chain);
460      }
461      catch (Throwable t)
462      {
463        logger.warn(LocalizableMessage.raw("Error accepting certificate: "+t, t));
464      }
465    }
466  }
467
468  /**
469   * Begins downloading webstart jars in another thread
470   * for WebStart applications only.
471   */
472  protected void initLoader() {
473    loader = new WebStartDownloader();
474    loader.start(false);
475  }
476
477  /**
478   * Waits for the loader to be finished.  Every time we have an update in the
479   * percentage that is downloaded we notify the listeners of this.
480   *
481   * @param maxRatio is the integer value that tells us which is the max ratio
482   * that corresponds to the download.  It is used to calculate how the global
483   * installation ratio changes when the download ratio increases.  For instance
484   * if we suppose that the download takes 25 % of the total installation
485   * process, then maxRatio will be 25.  When the download is complete this
486   * method will send a notification to the ProgressUpdateListeners with a ratio
487   * of 25 %.
488   * @throws org.opends.quicksetup.ApplicationException if something goes wrong
489   *
490   */
491  protected void waitForLoader(Integer maxRatio) throws ApplicationException {
492    int lastPercentage = -1;
493    WebStartDownloader.Status lastStatus =
494      WebStartDownloader.Status.DOWNLOADING;
495    while (!loader.isFinished() && loader.getException() == null)
496    {
497      checkAbort();
498      // Pool until is over
499      int perc = loader.getDownloadPercentage();
500      WebStartDownloader.Status downloadStatus = loader.getStatus();
501      if (perc != lastPercentage || downloadStatus != lastStatus)
502      {
503        lastPercentage = perc;
504        int ratio = (perc * maxRatio) / 100;
505        LocalizableMessage summary;
506        switch (downloadStatus)
507        {
508        case VALIDATING:
509          summary = INFO_VALIDATING_RATIO.get(perc, loader.getCurrentValidatingPercentage());
510          break;
511        case UPGRADING:
512          summary = INFO_UPGRADING_RATIO.get(perc, loader.getCurrentValidatingPercentage());
513          break;
514        default:
515          summary = INFO_DOWNLOADING_RATIO.get(perc);
516        }
517        loader.setSummary(summary);
518        notifyListeners(ratio, summary, null);
519      }
520      checkAbort();
521      try
522      {
523        Thread.sleep(300);
524      } catch (Exception ex)
525      {
526        // do nothing;
527      }
528    }
529    checkAbort();
530
531    if (loader.getException() != null)
532    {
533      throw loader.getException();
534    }
535  }
536
537  /**
538   * Gets the amount of addition pixels added to the height
539   * of the tallest panel in order to size the wizard for
540   * asthetic reasons.
541   * @return int height to add
542   */
543  public int getExtraDialogHeight() {
544    return 0;
545  }
546
547  /**
548   * Sets the QuickSetupDialog driving this application.
549   * @param dialog QuickSetupDialog driving this application
550   */
551  public void setQuickSetupDialog(QuickSetupDialog dialog) {
552    this.qs = dialog;
553  }
554
555  /**
556   * Sets the arguments passed in the command-line to launch the application.
557   * @param args the arguments passed in the command-line to launch the
558   * application.
559   */
560  public void setUserArguments(String[] args)
561  {
562    this.args = args;
563  }
564
565  /**
566   * Returns the arguments passed in the command-line to launch the application.
567   * @return the arguments passed in the command-line to launch the application.
568   */
569  public String[] getUserArguments()
570  {
571    return args;
572  }
573}