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 2006-2010 Sun Microsystems, Inc.
025 *      Portions Copyright 2011-2015 ForgeRock AS
026 */
027package org.opends.quicksetup.installer.webstart;
028
029import java.io.File;
030import java.io.IOException;
031import java.io.InputStream;
032import java.io.PrintStream;
033import java.util.ArrayList;
034import java.util.HashMap;
035import java.util.List;
036import java.util.Map;
037
038import org.forgerock.i18n.LocalizableMessage;
039import org.forgerock.i18n.slf4j.LocalizedLogger;
040
041import org.opends.quicksetup.ApplicationException;
042import org.opends.quicksetup.LicenseFile;
043import org.opends.quicksetup.ReturnCode;
044import org.opends.quicksetup.ProgressStep;
045import org.opends.quicksetup.Installation;
046import org.opends.quicksetup.installer.Installer;
047import org.opends.quicksetup.installer.InstallProgressStep;
048import org.opends.quicksetup.util.Utils;
049import org.opends.quicksetup.util.ZipExtractor;
050import org.opends.quicksetup.util.ServerController;
051import org.opends.quicksetup.util.FileManager;
052import org.opends.server.util.SetupUtils;
053
054import static org.opends.messages.QuickSetupMessages.*;
055import static com.forgerock.opendj.util.OperatingSystem.isWindows;
056import static com.forgerock.opendj.cli.Utils.getThrowableMsg;
057
058/**
059 * This is an implementation of the Installer class that is used to install
060 * the Directory Server using Web Start.
061 *
062 * It just takes a UserData object and based on that installs OpenDS.
063 *
064 *
065 * This object has as parameter a WebStartDownloader object that is downloading
066 * some jar files.  Until the WebStartDownloader has not finished downloading
067 * the jar files will be on the ProgressStep.DOWNLOADING step because
068 * we require all the jar files to be downloaded in order to install and
069 * configure the Directory Server.
070 *
071 * Based on the Java properties set through the QuickSetup.jnlp file this
072 * class will retrieve the zip file containing the install, unzip it and extract
073 * it in the path specified by the user and that is contained in the
074 * UserData object.
075 *
076 *
077 * When there is an update during the installation it will notify the
078 * ProgressUpdateListener objects that have been added to it.  The notification
079 * will send a ProgressUpdateEvent.
080 *
081 * This class is supposed to be fully independent of the graphical layout.
082 */
083public class WebStartInstaller extends Installer {
084  private final Map<ProgressStep, Integer> hmRatio = new HashMap<>();
085  private final Map<ProgressStep, LocalizableMessage> hmSummary = new HashMap<>();
086
087  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
088
089  /**
090   * WebStartInstaller constructor.
091   */
092  public WebStartInstaller()
093  {
094    initLoader();
095    setCurrentProgressStep(InstallProgressStep.NOT_STARTED);
096  }
097
098  /** Actually performs the install in this thread.  The thread is blocked. */
099  @Override
100  public void run()
101  {
102    initMaps();
103    PrintStream origErr = System.err;
104    PrintStream origOut = System.out;
105    boolean downloadedBits = false;
106    try
107    {
108      System.setErr(getApplicationErrorStream());
109      System.setOut(getApplicationOutputStream());
110
111      setCurrentProgressStep(InstallProgressStep.DOWNLOADING);
112
113      notifyListenersOfLog();
114      notifyListeners(getLineBreak());
115
116      checkAbort();
117
118      InputStream in =
119          getZipInputStream(getRatio(InstallProgressStep.EXTRACTING));
120
121      setCurrentProgressStep(InstallProgressStep.EXTRACTING);
122      if (isVerbose())
123      {
124        notifyListeners(getTaskSeparator());
125      }
126
127      checkAbort();
128
129      createParentDirectoryIfRequired();
130      extractZipFiles(in, getRatio(InstallProgressStep.EXTRACTING),
131          getRatio(InstallProgressStep.CONFIGURING_SERVER));
132      downloadedBits = true;
133
134      try
135      {
136        in.close();
137      }
138      catch (Throwable t)
139      {
140        logger.info(LocalizableMessage.raw("Error closing zip input stream: "+t, t));
141      }
142
143      checkAbort();
144
145      setCurrentProgressStep(InstallProgressStep.CONFIGURING_SERVER);
146      if (isVerbose())
147      {
148        notifyListeners(getTaskSeparator());
149      }
150      configureServer();
151
152      checkAbort();
153
154      // create license accepted file
155      LicenseFile.createFileLicenseApproved(getInstallationPath());
156
157      createData();
158
159      checkAbort();
160
161      if (isWindows() && getUserData().getEnableWindowsService())
162      {
163        if (isVerbose())
164        {
165          notifyListeners(getTaskSeparator());
166        }
167        setCurrentProgressStep(InstallProgressStep.ENABLING_WINDOWS_SERVICE);
168        enableWindowsService();
169        checkAbort();
170      }
171
172      if (mustStart())
173      {
174        if (isVerbose())
175        {
176          notifyListeners(getTaskSeparator());
177        }
178        setCurrentProgressStep(InstallProgressStep.STARTING_SERVER);
179        PointAdder pointAdder = new PointAdder();
180        if (!isVerbose())
181        {
182          notifyListeners(getFormattedProgress(
183              INFO_PROGRESS_STARTING_NON_VERBOSE.get()));
184          pointAdder.start();
185        }
186        try
187        {
188          new ServerController(this).startServer(!isStartVerbose());
189        }
190        finally
191        {
192          if (!isVerbose())
193          {
194            pointAdder.stop();
195          }
196        }
197        if (!isVerbose())
198        {
199          notifyListeners(getFormattedDoneWithLineBreak());
200        }
201        else
202        {
203          notifyListeners(getLineBreak());
204        }
205        checkAbort();
206      }
207
208      if (mustCreateAds())
209      {
210        if (isVerbose())
211        {
212          notifyListeners(getTaskSeparator());
213        }
214        setCurrentProgressStep(InstallProgressStep.CONFIGURING_ADS);
215        updateADS();
216        checkAbort();
217      }
218
219      if (mustConfigureReplication())
220      {
221        if (isVerbose())
222        {
223          notifyListeners(getTaskSeparator());
224        }
225        setCurrentProgressStep(InstallProgressStep.CONFIGURING_REPLICATION);
226        createReplicatedBackendsIfRequired();
227        configureReplication();
228        checkAbort();
229      }
230
231      if (mustInitializeSuffixes())
232      {
233        if (isVerbose())
234        {
235          notifyListeners(getTaskSeparator());
236        }
237        setCurrentProgressStep(
238            InstallProgressStep.INITIALIZE_REPLICATED_SUFFIXES);
239        initializeSuffixes();
240        checkAbort();
241      }
242
243      if (mustStop())
244      {
245        if (isVerbose())
246        {
247          notifyListeners(getTaskSeparator());
248        }
249        setCurrentProgressStep(InstallProgressStep.STOPPING_SERVER);
250        if (!isVerbose())
251        {
252          notifyListeners(getFormattedWithPoints(
253              INFO_PROGRESS_STOPPING_NON_VERBOSE.get()));
254        }
255        new ServerController(this).stopServer(!isVerbose());
256        if (!isVerbose())
257        {
258          notifyListeners(getFormattedDoneWithLineBreak());
259        }
260      }
261
262      checkAbort();
263      updateSummaryWithServerState(hmSummary, false);
264      setCurrentProgressStep(InstallProgressStep.FINISHED_SUCCESSFULLY);
265      notifyListeners(null);
266
267    } catch (ApplicationException ex)
268    {
269      if (ReturnCode.CANCELED.equals(ex.getType())) {
270        uninstall(downloadedBits);
271
272        setCurrentProgressStep(InstallProgressStep.FINISHED_CANCELED);
273        notifyListeners(null);
274      } else {
275        // Stop the server if necessary
276        Installation installation = getInstallation();
277        if (installation.getStatus().isServerRunning()) {
278          try {
279            if (!isVerbose())
280            {
281              notifyListeners(getFormattedWithPoints(
282                  INFO_PROGRESS_STOPPING_NON_VERBOSE.get()));
283            }
284            new ServerController(installation).stopServer(!isVerbose());
285            if (!isVerbose())
286            {
287              notifyListeners(getFormattedDoneWithLineBreak());
288            }
289          } catch (Throwable t) {
290            logger.info(LocalizableMessage.raw("error stopping server", t));
291          }
292        }
293        notifyListeners(getLineBreak());
294        updateSummaryWithServerState(hmSummary, false);
295        setCurrentProgressStep(InstallProgressStep.FINISHED_WITH_ERROR);
296        LocalizableMessage html = getFormattedError(ex, true);
297        notifyListeners(html);
298        logger.error(LocalizableMessage.raw("Error installing.", ex));
299        notifyListeners(getLineBreak());
300        notifyListenersOfLogAfterError();
301      }
302    }
303    catch (Throwable t)
304    {
305      // Stop the server if necessary
306      Installation installation = getInstallation();
307      if (installation.getStatus().isServerRunning()) {
308        try {
309          new ServerController(installation).stopServer(true);
310        } catch (Throwable t2) {
311          logger.info(LocalizableMessage.raw("error stopping server", t2));
312        }
313      }
314      notifyListeners(getLineBreak());
315      updateSummaryWithServerState(hmSummary, false);
316      setCurrentProgressStep(InstallProgressStep.FINISHED_WITH_ERROR);
317      ApplicationException ex = new ApplicationException(
318          ReturnCode.BUG,
319          getThrowableMsg(INFO_BUG_MSG.get(), t), t);
320      LocalizableMessage msg = getFormattedError(ex, true);
321      notifyListeners(msg);
322      logger.error(LocalizableMessage.raw("Error installing.", t));
323      notifyListeners(getLineBreak());
324      notifyListenersOfLogAfterError();
325    }
326    System.setErr(origErr);
327    System.setOut(origOut);
328  }
329
330  /** {@inheritDoc} */
331  @Override
332  public Integer getRatio(ProgressStep status)
333  {
334    return hmRatio.get(status);
335  }
336
337  /** {@inheritDoc} */
338  @Override
339  public LocalizableMessage getSummary(ProgressStep status)
340  {
341    LocalizableMessage summary;
342    if (InstallProgressStep.DOWNLOADING.equals(status)) {
343      summary = loader.getSummary();
344    } else {
345      summary = hmSummary.get(status);
346    }
347    return summary;
348  }
349
350  /**
351   * Initialize the different map used in this class.
352   *
353   */
354  private void initMaps()
355  {
356    initSummaryMap(hmSummary, false);
357
358    /*
359     * hmTime contains the relative time that takes for each task to be
360     * accomplished. For instance if downloading takes twice the time of
361     * extracting, the value for downloading will be the double of the value for
362     * extracting.
363     */
364    Map<ProgressStep, Integer> hmTime = new HashMap<>();
365    hmTime.put(InstallProgressStep.DOWNLOADING, 30);
366    hmTime.put(InstallProgressStep.EXTRACTING, 15);
367    hmTime.put(InstallProgressStep.CONFIGURING_SERVER, 5);
368    hmTime.put(InstallProgressStep.CREATING_BASE_ENTRY, 10);
369    hmTime.put(InstallProgressStep.IMPORTING_LDIF, 20);
370    hmTime.put(InstallProgressStep.IMPORTING_AUTOMATICALLY_GENERATED, 20);
371    hmTime.put(InstallProgressStep.CONFIGURING_REPLICATION, 10);
372    hmTime.put(InstallProgressStep.ENABLING_WINDOWS_SERVICE, 5);
373    hmTime.put(InstallProgressStep.STARTING_SERVER, 10);
374    hmTime.put(InstallProgressStep.STOPPING_SERVER, 5);
375    hmTime.put(InstallProgressStep.CONFIGURING_ADS, 5);
376    hmTime.put(InstallProgressStep.INITIALIZE_REPLICATED_SUFFIXES, 25);
377
378    int totalTime = 0;
379    List<InstallProgressStep> steps = new ArrayList<>();
380    totalTime += hmTime.get(InstallProgressStep.DOWNLOADING);
381    steps.add(InstallProgressStep.DOWNLOADING);
382    totalTime += hmTime.get(InstallProgressStep.EXTRACTING);
383    steps.add(InstallProgressStep.EXTRACTING);
384    totalTime += hmTime.get(InstallProgressStep.CONFIGURING_SERVER);
385    steps.add(InstallProgressStep.CONFIGURING_SERVER);
386
387    if (createNotReplicatedSuffix())
388    {
389      switch (getUserData().getNewSuffixOptions().getType())
390      {
391      case CREATE_BASE_ENTRY:
392        steps.add(InstallProgressStep.CREATING_BASE_ENTRY);
393        totalTime += hmTime.get(InstallProgressStep.CREATING_BASE_ENTRY);
394        break;
395      case IMPORT_FROM_LDIF_FILE:
396        steps.add(InstallProgressStep.IMPORTING_LDIF);
397        totalTime += hmTime.get(InstallProgressStep.IMPORTING_LDIF);
398        break;
399      case IMPORT_AUTOMATICALLY_GENERATED_DATA:
400        steps.add(InstallProgressStep.IMPORTING_AUTOMATICALLY_GENERATED);
401        totalTime +=hmTime.get(
402            InstallProgressStep.IMPORTING_AUTOMATICALLY_GENERATED);
403        break;
404      }
405    }
406
407    if (isWindows() && getUserData().getEnableWindowsService())
408    {
409        totalTime += hmTime.get(InstallProgressStep.ENABLING_WINDOWS_SERVICE);
410        steps.add(InstallProgressStep.ENABLING_WINDOWS_SERVICE);
411    }
412    if (mustStart())
413    {
414      totalTime += hmTime.get(InstallProgressStep.STARTING_SERVER);
415      steps.add(InstallProgressStep.STARTING_SERVER);
416    }
417
418    if (mustCreateAds())
419    {
420      totalTime += hmTime.get(InstallProgressStep.CONFIGURING_ADS);
421      steps.add(InstallProgressStep.CONFIGURING_ADS);
422    }
423
424    if (mustConfigureReplication())
425    {
426      steps.add(InstallProgressStep.CONFIGURING_REPLICATION);
427      totalTime += hmTime.get(InstallProgressStep.CONFIGURING_REPLICATION);
428    }
429
430    if (mustInitializeSuffixes())
431    {
432      totalTime += hmTime.get(
433          InstallProgressStep.INITIALIZE_REPLICATED_SUFFIXES);
434      steps.add(InstallProgressStep.INITIALIZE_REPLICATED_SUFFIXES);
435    }
436
437    if (mustStop())
438    {
439      totalTime += hmTime.get(InstallProgressStep.STOPPING_SERVER);
440      steps.add(InstallProgressStep.STOPPING_SERVER);
441    }
442
443    int cumulatedTime = 0;
444    for (InstallProgressStep s : steps)
445    {
446      Integer statusTime = hmTime.get(s);
447      hmRatio.put(s, (100 * cumulatedTime) / totalTime);
448      if (statusTime != null)
449      {
450        cumulatedTime += statusTime;
451      }
452    }
453
454    hmRatio.put(InstallProgressStep.FINISHED_SUCCESSFULLY, 100);
455    hmRatio.put(InstallProgressStep.FINISHED_CANCELED, 100);
456    hmRatio.put(InstallProgressStep.FINISHED_WITH_ERROR, 100);
457  }
458
459  private InputStream getZipInputStream(Integer maxRatio)
460      throws ApplicationException {
461    notifyListeners(getFormattedWithPoints(INFO_PROGRESS_DOWNLOADING.get()));
462
463    waitForLoader(maxRatio);
464
465    String zipName = getZipFileName();
466    InputStream in =
467      Installer.class.getClassLoader().getResourceAsStream(zipName);
468
469    if (in == null)
470    {
471      throw new ApplicationException(
472          ReturnCode.DOWNLOAD_ERROR,
473              INFO_ERROR_ZIPINPUTSTREAMNULL.get(zipName), null);
474    }
475
476    notifyListeners(getFormattedDoneWithLineBreak());
477    return in;
478  }
479
480  /**
481   * Creates the parent Directory for the server location if it does not exist.
482   * @throws ApplicationException if something goes wrong.
483   */
484  private void createParentDirectoryIfRequired() throws ApplicationException
485  {
486    String serverLocation = getUserData().getServerLocation();
487    if (!Utils.parentDirectoryExists(serverLocation))
488    {
489      File f = new File(serverLocation);
490      String parent = f.getParent();
491      try
492      {
493        if (!Utils.createDirectory(parent))
494        {
495          throw new ApplicationException(
496              ReturnCode.FILE_SYSTEM_ACCESS_ERROR,
497              INFO_ERROR_COULD_NOT_CREATE_PARENT_DIR.get(parent), null);
498        }
499      }
500      catch (IOException ioe)
501      {
502        throw new ApplicationException(
503            ReturnCode.FILE_SYSTEM_ACCESS_ERROR,
504            INFO_ERROR_COULD_NOT_CREATE_PARENT_DIR.get(parent),
505            ioe);
506      }
507    }
508  }
509
510  /**
511   * This method extracts the zip file.
512   * @param is the input stream with the contents of the zip file.
513   * @param minRatio the value of the ratio in the install that corresponds to
514   * the moment where we start extracting the zip files.  Used to update
515   * properly the install progress ratio.
516   * @param maxRatio the value of the ratio in the installation that corresponds
517   * to the moment where we finished extracting the last zip file.  Used to
518   * update properly the install progress ratio.
519   * @throws ApplicationException if an error occurs.
520   */
521  private void extractZipFiles(InputStream is, int minRatio, int maxRatio)
522      throws ApplicationException {
523    ZipExtractor extractor =
524            new ZipExtractor(is, minRatio, maxRatio,
525            Utils.getNumberZipEntries(),
526            getZipFileName(),
527            this);
528    extractor.extract(getUserData().getServerLocation());
529  }
530
531  /**
532   * Returns the name of the zip file name that contains all the installation.
533   * @return the name of the zip file name that contains all the installation.
534   */
535  private String getZipFileName()
536  {
537    // Passed as a java option in the JNLP file
538    return System.getProperty(SetupUtils.ZIP_FILE_NAME);
539  }
540
541  /**
542   * Uninstall what has already been installed.
543   * @param downloadedBits whether the bits were downloaded or not.
544   */
545  private void uninstall(boolean downloadedBits) {
546    if (downloadedBits)
547    {
548      notifyListeners(getTaskSeparator());
549      if (!isVerbose())
550      {
551        notifyListeners(getFormattedWithPoints(INFO_PROGRESS_CANCELING.get()));
552      }
553      else
554      {
555        notifyListeners(
556            getFormattedProgressWithLineBreak(INFO_SUMMARY_CANCELING.get()));
557      }
558      Installation installation = getInstallation();
559      FileManager fm = new FileManager(this);
560
561      // Stop the server if necessary
562      if (installation.getStatus().isServerRunning()) {
563        try {
564          new ServerController(installation).stopServer(true);
565        } catch (ApplicationException e) {
566          logger.info(LocalizableMessage.raw("error stopping server", e));
567        }
568      }
569
570      uninstallServices();
571
572      try {
573        fm.deleteRecursively(installation.getRootDirectory(), null,
574            FileManager.DeletionPolicy.DELETE_ON_EXIT_IF_UNSUCCESSFUL);
575      } catch (ApplicationException e) {
576        logger.info(LocalizableMessage.raw("error deleting files", e));
577      }
578    }
579    else
580    {
581      if (!isVerbose())
582      {
583        notifyListeners(getFormattedWithPoints(INFO_PROGRESS_CANCELING.get()));
584      }
585      else
586      {
587        notifyListeners(
588            getFormattedProgressWithLineBreak(INFO_SUMMARY_CANCELING.get()));
589      }
590      File serverRoot = new File(getUserData().getServerLocation());
591      if (serverRoot.exists())
592      {
593        FileManager fm = new FileManager(this);
594        try {
595          fm.deleteRecursively(serverRoot, null,
596              FileManager.DeletionPolicy.DELETE_ON_EXIT_IF_UNSUCCESSFUL);
597        } catch (ApplicationException e) {
598          logger.info(LocalizableMessage.raw("error deleting files", e));
599        }
600      }
601    }
602    if (!isVerbose())
603    {
604      notifyListeners(getFormattedDoneWithLineBreak());
605    }
606  }
607
608  /** {@inheritDoc} */
609  @Override
610  public String getInstallationPath()
611  {
612    return getUserData().getServerLocation();
613  }
614
615  /** {@inheritDoc} */
616  @Override
617  public String getInstancePath()
618  {
619    return getUserData().getServerLocation();
620  }
621}