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-2009 Sun Microsystems, Inc.
025 *      Portions Copyright 2013-2015 ForgeRock AS.
026 */
027package org.opends.quicksetup.webstart;
028
029import java.io.IOException;
030import java.net.MalformedURLException;
031import java.net.URL;
032
033import org.forgerock.i18n.LocalizableMessage;
034import org.forgerock.i18n.slf4j.LocalizedLogger;
035
036import javax.jnlp.DownloadService;
037import javax.jnlp.DownloadServiceListener;
038import javax.jnlp.ServiceManager;
039import javax.jnlp.UnavailableServiceException;
040
041import org.opends.quicksetup.ApplicationException;
042import org.opends.quicksetup.Installation;
043import org.opends.quicksetup.ReturnCode;
044import org.opends.quicksetup.util.Utils;
045import org.opends.server.util.SetupUtils;
046
047import static org.opends.messages.QuickSetupMessages.*;
048import static com.forgerock.opendj.util.OperatingSystem.isWindows;
049import static com.forgerock.opendj.cli.Utils.getThrowableMsg;
050
051/**
052 * This class is used to download the files that have been marked as lazy
053 * in the QuickSetup.jnlp file.
054 *
055 * The global idea is to force the user to download just one jar file
056 * (quicksetup.jar) to display the Web Start installer dialog.  Then QuickSetup
057 * will call this class and it will download the jar files.  Until this class is
058 * not finished the WebStartInstaller will be on the
059 * ProgressStep.DOWNLOADING step.
060 */
061public class WebStartDownloader implements DownloadServiceListener {
062  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
063
064  private ApplicationException ex;
065  private boolean isFinished;
066  private int downloadPercentage;
067  private int currentPercMin;
068  private int currentPercMax;
069  private int currentValidatingPercent;
070  private int currentUpgradingPercent;
071  private Status status = Status.DOWNLOADING;
072  private LocalizableMessage summary;
073
074  /**
075   * This enumeration contains the different Status on which
076   * the downloading process of the jars can be.
077   *
078   */
079  public enum Status
080    {
081    /**
082     * Downloading a jar file.
083     */
084    DOWNLOADING,
085    /**
086     * Validating a jar file.
087     */
088    VALIDATING,
089    /**
090     * Upgrading a jar file.
091     */
092    UPGRADING
093    }
094
095  /**
096   * Creates a default instance.
097   */
098  public WebStartDownloader() {
099    this.summary = INFO_DOWNLOADING.get();
100  }
101
102  /**
103   * Starts the downloading of the jar files.  If forceDownload is set to
104   * <CODE>true</CODE> the files will be re-downloaded even if they already
105   * are on cache.
106   *
107   * This method does not block the thread that calls it.
108   *
109   * @param forceDownload used to ignore the case and force download.
110   */
111  public void start(final boolean forceDownload)
112  {
113    isFinished = false;
114    Thread t = new Thread(new Runnable()
115    {
116      @Override
117      public void run()
118      {
119        try
120        {
121          startDownload(forceDownload);
122        } catch (ApplicationException ex)
123        {
124          WebStartDownloader.this.ex = ex;
125        } catch (MalformedURLException mfe)
126        {
127          // This is a bug
128          ex =
129              new ApplicationException(ReturnCode.BUG,
130                      getThrowableMsg(INFO_BUG_MSG.get(),mfe), mfe);
131        } catch (IOException ioe)
132        {
133          StringBuilder buf = new StringBuilder();
134          String[] jars = getJarUrls();
135          for (int i = 0; i < jars.length; i++)
136          {
137            if (i != 0)
138            {
139              buf.append(",");
140            }
141            buf.append(jars[i]);
142          }
143          ex = new ApplicationException(
144              ReturnCode.DOWNLOAD_ERROR,
145              getThrowableMsg(INFO_DOWNLOADING_ERROR.get(buf), ioe), ioe);
146        } catch (Throwable t)
147        {
148          // This is a bug
149          ex = new ApplicationException(ReturnCode.BUG,
150                      getThrowableMsg(INFO_BUG_MSG.get(), t), t);
151        }
152      }
153    });
154    t.start();
155  }
156
157  /**
158   * Gets a summary message of the downloader's current progress.
159   * @return String for showing the user progress
160   */
161  public LocalizableMessage getSummary() {
162    return this.summary;
163  }
164
165  /**
166   * Sets a summary message of the downloader's current progress.
167   * @param summary String for showing the user progress
168   */
169  public void setSummary(LocalizableMessage summary) {
170    this.summary = summary;
171  }
172
173  /**
174   * Returns <CODE>true</CODE> if the install is finished and
175   * <CODE>false</CODE> otherwise.
176   * @return <CODE>true</CODE> if the install is finished and
177   * <CODE>false</CODE> otherwise.
178   */
179  public boolean isFinished()
180  {
181    return isFinished;
182  }
183
184  /**
185   * Returns the Status of the current download process.
186   * @return the current status of the download process.
187   */
188  public Status getStatus()
189  {
190    return status;
191  }
192
193  /**
194   * Returns the current download percentage.
195   * @return the current download percentage.
196   */
197  public int getDownloadPercentage()
198  {
199    return downloadPercentage;
200  }
201
202  /**
203   * Returns the completed percentage for the file being currently validated.
204   * @return the completed percentage for the file being currently validated.
205   */
206  public int getCurrentValidatingPercentage()
207  {
208    return currentValidatingPercent;
209  }
210
211  /**
212   * Returns the completed percentage for the file being currently upgraded.
213   * @return the completed percentage for the file being currently upgraded.
214   */
215  public int getCurrentUpgradingPercentage()
216  {
217    return currentUpgradingPercent;
218  }
219
220  /**
221   * Starts synchronously the downloading on this thread.  The thread calling
222   * this method will be blocked.  If forceDownload is set to
223   * <CODE>true</CODE> the files will be re-downloaded even if they already
224   * are on cache.
225   * @param forceDownload used to ignore the case and force download.
226   * @throws MalformedURLException if there is an error with the URLs that we
227   * get from the property SetupUtils.LAZY_JAR_URLS
228   * @throws IOException if a network problem occurs.
229   * @throws ApplicationException if the download service is not available.
230   */
231  private void startDownload(boolean forceDownload)
232      throws IOException, ApplicationException
233  {
234    DownloadService ds;
235    try
236    {
237      ds =
238          (DownloadService) ServiceManager.lookup(Utils.JNLP_SERVICE_NAME);
239    } catch (UnavailableServiceException e)
240    {
241      logger.error(LocalizableMessage.raw("Could not find service: "+
242          Utils.JNLP_SERVICE_NAME, e));
243      String setupFile;
244      if (isWindows())
245      {
246        setupFile = Installation.WINDOWS_SETUP_FILE_NAME;
247      }
248      else
249      {
250        setupFile = Installation.UNIX_SETUP_FILE_NAME;
251      }
252      throw new ApplicationException(
253        ReturnCode.DOWNLOAD_ERROR,
254        getThrowableMsg(INFO_DOWNLOADING_ERROR_NO_SERVICE_FOUND.get(
255            Utils.JNLP_SERVICE_NAME, setupFile),
256            e), e);
257    }
258
259    String[] urls = getJarUrls();
260    String[] versions = getJarVersions();
261
262    /*
263     * Calculate the percentages that correspond to each file.
264     * TODO ideally this should be done dynamically, but as this is just
265     * to provide progress, updating this information from time to time can
266     * be enough and does not complexify the build process.
267     */
268    int[] percentageMax = new int[urls.length];
269    int[] ratios = new int[urls.length];
270    int totalRatios = 0;
271    for (int i=0; i<percentageMax.length; i++)
272    {
273      int ratio;
274      if (urls[i].endsWith("OpenDS.jar"))
275      {
276        ratio =  23;
277      }
278      else if (urls[i].endsWith("je.jar"))
279      {
280        ratio = 11;
281      }
282      else if (urls[i].endsWith("zipped.jar"))
283      {
284        ratio = 110;
285      }
286      else if (urls[i].endsWith("aspectjrt.jar"))
287      {
288        ratio = 10;
289      }
290      else
291      {
292        ratio = 100 / urls.length;
293      }
294      ratios[i] = ratio;
295      totalRatios += ratio;
296    }
297
298    for (int i=0; i<percentageMax.length; i++)
299    {
300      int r = 0;
301      for (int j=0; j<=i; j++)
302      {
303        r += ratios[j];
304      }
305      percentageMax[i] = (100 * r)/totalRatios;
306    }
307
308
309    for (int i = 0; i < urls.length && getException() == null; i++)
310    {
311      if (i == 0)
312      {
313        currentPercMin = 0;
314      }
315      else {
316        currentPercMin = percentageMax[i-1];
317      }
318      currentPercMax = percentageMax[i];
319
320      // determine if a particular resource is cached
321      String sUrl = urls[i];
322      String version = versions[i];
323
324      URL url = new URL(sUrl);
325      boolean cached = ds.isResourceCached(url, version);
326
327      if (cached && forceDownload)
328      {
329        try
330        {
331          ds.removeResource(url, version);
332        } catch (IOException ioe)
333        {
334        }
335        cached = false;
336      }
337
338      if (!cached)
339      {
340        // if not in the cache load the resource into the cache
341        ds.loadResource(url, version, this);
342      }
343      downloadPercentage = currentPercMax;
344    }
345    isFinished = true;
346  }
347
348  /**
349   * Returns the ApplicationException that has occurred during the download or
350   * <CODE>null</CODE> if no exception occurred.
351   * @return the ApplicationException that has occurred during the download or
352   * <CODE>null</CODE> if no exception occurred.
353   */
354  public ApplicationException getException()
355  {
356    return ex;
357  }
358
359  /** {@inheritDoc} */
360  @Override
361  public void downloadFailed(URL url, String version)
362  {
363    ex =
364        new ApplicationException(
365        ReturnCode.DOWNLOAD_ERROR,
366                INFO_DOWNLOADING_ERROR.get(url), null);
367  }
368
369  /** {@inheritDoc} */
370  @Override
371  public void progress(URL url, String version, long readSoFar, long total,
372      int overallPercent)
373  {
374    if (overallPercent >= 0)
375    {
376      downloadPercentage = getPercentage(overallPercent);
377    }
378    status = Status.DOWNLOADING;
379  }
380
381  /** {@inheritDoc} */
382  @Override
383  public void upgradingArchive(URL url, String version, int patchPercent,
384      int overallPercent)
385  {
386    currentUpgradingPercent = overallPercent;
387    status = Status.UPGRADING;
388  }
389
390  /** {@inheritDoc} */
391  @Override
392  public void validating(URL url, String version, long entry, long total,
393      int overallPercent)
394  {
395    if (total > 0)
396    {
397      currentValidatingPercent = (int)((100 * entry) / total);
398    }
399    else {
400      currentValidatingPercent = 0;
401    }
402
403    status = Status.VALIDATING;
404  }
405
406  /**
407   * Returns the jar files in a String[] from the System properties.
408   * @return the jar files from the System properties.
409   */
410  private String[] getJarUrls()
411  {
412    String jars = System.getProperty(SetupUtils.LAZY_JAR_URLS);
413    return jars.split(" ");
414  }
415
416
417  /**
418   * Returns the downloaded percentage based on how much of the current jar file
419   * has been downloaded.
420   * @param currentJarRatio the download ratio of the jar file that is being
421   * currently downloaded.
422   * @return the downloaded percentage based on how much of the current jar file
423   * has been downloaded.
424   */
425  private int getPercentage(int currentJarRatio)
426  {
427    return currentPercMin
428            + (currentJarRatio * (currentPercMax - currentPercMin) / 100);
429  }
430
431  /**
432   * Returns the java jar versions in a String[].  Currently just returns some
433   * null strings.
434   * @return the java jar versions in a String[].
435   */
436  private String[] getJarVersions()
437  {
438    return new String[getJarUrls().length];
439  }
440
441}