jdk/src/share/classes/sun/jkernel/Bundle.java
changeset 8199 bbe30e093ae9
parent 8095 6823ea7eb8eb
parent 8198 aca2f99e4b52
child 8200 c3b2a9c6194b
equal deleted inserted replaced
8095:6823ea7eb8eb 8199:bbe30e093ae9
     1 /*
       
     2  * Copyright (c) 2008, 2009, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.  Oracle designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Oracle in the LICENSE file that accompanied this code.
       
    10  *
       
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    14  * version 2 for more details (a copy is included in the LICENSE file that
       
    15  * accompanied this code).
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License version
       
    18  * 2 along with this work; if not, write to the Free Software Foundation,
       
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    20  *
       
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    22  * or visit www.oracle.com if you need additional information or have any
       
    23  * questions.
       
    24  */
       
    25 package sun.jkernel;
       
    26 
       
    27 import java.io.*;
       
    28 import java.net.HttpRetryException;
       
    29 import java.util.*;
       
    30 import java.util.concurrent.*;
       
    31 import java.util.jar.*;
       
    32 import java.util.zip.GZIPInputStream;
       
    33 
       
    34 /**
       
    35  * Represents a bundle which may or may not currently be installed.
       
    36  *
       
    37  *@author Ethan Nicholas
       
    38  */
       
    39 public class Bundle {
       
    40     static {
       
    41         if (!DownloadManager.jkernelLibLoaded) {
       
    42             // This code can be invoked directly by the deploy build.
       
    43             System.loadLibrary("jkernel");
       
    44         }
       
    45     }
       
    46     /**
       
    47      * Compress file sourcePath with "extra" algorithm (e.g. 7-Zip LZMA)
       
    48      * if available, put the uncompressed data into file destPath and
       
    49      * return true. If not available return false and do nothing with destPath.
       
    50      *
       
    51      * @param srcPath path to existing uncompressed file
       
    52      * @param destPath path for the compressed file to be created
       
    53      * @returns true if extra algorithm used, false if not
       
    54      * @throws IOException if the extra compression code should be available
       
    55      *     but cannot be located or linked to, the destination file already
       
    56      *     exists or cannot be opened for writing, or the compression fails
       
    57      */
       
    58     public static native boolean extraCompress(String srcPath,
       
    59         String destPath) throws IOException;
       
    60 
       
    61     /**
       
    62      * Decompress file sourcePath with "extra" algorithm (e.g. 7-Zip LZMA)
       
    63      * if available, put the uncompressed data into file destPath and
       
    64      * return true. If not available return false and do nothing with
       
    65      * destPath.
       
    66      * @param srcPath path to existing compressed file
       
    67      * @param destPath path to uncompressed file to be created
       
    68      * @returns true if extra algorithm used, false if not
       
    69      * @throws IOException if the extra uncompression code should be available
       
    70      *     but cannot be located or linked to, the destination file already
       
    71      *     exists or cannot be opened for writing, or the uncompression fails
       
    72      */
       
    73     public static native boolean extraUncompress(String srcPath,
       
    74         String destPath) throws IOException;
       
    75 
       
    76     private static final String BUNDLE_JAR_ENTRY_NAME = "classes.jar";
       
    77 
       
    78     /** The bundle is not present. */
       
    79     protected static final int NOT_DOWNLOADED = 0;
       
    80 
       
    81     /**
       
    82      * The bundle is in the download queue but has not finished downloading.
       
    83      */
       
    84     protected static final int QUEUED = 1;
       
    85 
       
    86     /** The bundle has finished downloading but is not installed. */
       
    87     protected static final int DOWNLOADED = 2;
       
    88 
       
    89     /** The bundle is fully installed and functional. */
       
    90     protected static final int INSTALLED = 3;
       
    91 
       
    92     /** Thread pool used to manage dependency downloads. */
       
    93     private static ExecutorService threadPool;
       
    94 
       
    95     /** Size of thread pool. */
       
    96     static final int THREADS;
       
    97 
       
    98     static {
       
    99         String downloads = System.getProperty(
       
   100                 DownloadManager.KERNEL_SIMULTANEOUS_DOWNLOADS_PROPERTY);
       
   101         if (downloads != null)
       
   102             THREADS = Integer.parseInt(downloads.trim());
       
   103         else
       
   104             THREADS = 1;
       
   105     }
       
   106 
       
   107     /** Mutex used to safely access receipts file. */
       
   108     private static Mutex receiptsMutex;
       
   109 
       
   110     /** Maps bundle names to known bundle instances. */
       
   111     private static Map<String, Bundle> bundles =
       
   112             new HashMap<String, Bundle>();
       
   113 
       
   114     /** Contains the names of currently-installed bundles. */
       
   115     static Set<String> receipts = new HashSet<String>();
       
   116 
       
   117     private static int bytesDownloaded;
       
   118 
       
   119     /** Path where bundle receipts are written. */
       
   120     private static File receiptPath = new File(DownloadManager.getBundlePath(),
       
   121             "receipts");
       
   122 
       
   123     /** The size of the receipts file the last time we saw it. */
       
   124     private static int receiptsSize;
       
   125 
       
   126     /** The bundle name, e.g. "java_awt". */
       
   127     private String name;
       
   128 
       
   129     /** The path to which we are saving the downloaded bundle file. */
       
   130     private File localPath;
       
   131 
       
   132     /**
       
   133      * The path of the extracted JAR file containing the bundle's classes.
       
   134      */
       
   135     private File jarPath;
       
   136 
       
   137     // for vista IE7 protected mode
       
   138     private File lowJarPath;
       
   139     private File lowJavaPath = null;
       
   140 
       
   141     /** The current state (DOWNLOADED, INSTALLED, etc.). */
       
   142     protected int state;
       
   143 
       
   144     /**
       
   145      * True if we should delete the downloaded bundle after installing it.
       
   146      */
       
   147     protected boolean deleteOnInstall = true;
       
   148 
       
   149     private static Mutex getReceiptsMutex() {
       
   150         if (receiptsMutex == null)
       
   151             receiptsMutex = Mutex.create(DownloadManager.MUTEX_PREFIX +
       
   152                     "receipts");
       
   153         return receiptsMutex;
       
   154     }
       
   155 
       
   156 
       
   157     /**
       
   158      * Reads the receipts file in order to seed the list of currently
       
   159      * installed bundles.
       
   160      */
       
   161     static synchronized void loadReceipts() {
       
   162         getReceiptsMutex().acquire();
       
   163         try {
       
   164             if (receiptPath.exists()) {
       
   165                 int size = (int) receiptPath.length();
       
   166                 if (size != receiptsSize) { // ensure that it has actually
       
   167                                             // been modified
       
   168                     DataInputStream in = null;
       
   169                     try {
       
   170                         receipts.clear();
       
   171                         for (String bundleName : DownloadManager.getBundleNames()) {
       
   172                             if ("true".equals(DownloadManager.getBundleProperty(bundleName,
       
   173                                     DownloadManager.INSTALL_PROPERTY)))
       
   174                                 receipts.add(bundleName);
       
   175                         }
       
   176                         if (receiptPath.exists()) {
       
   177                             in = new DataInputStream(new BufferedInputStream(
       
   178                                     new FileInputStream(receiptPath)));
       
   179                             String line;
       
   180                             while ((line = in.readLine()) != null) {
       
   181                                 receipts.add(line.trim());
       
   182                             }
       
   183                         }
       
   184                         receiptsSize = size;
       
   185                     }
       
   186                     catch (IOException e) {
       
   187                         DownloadManager.log(e);
       
   188                         // safe to continue, as the worst that happens is
       
   189                         // we re-download existing bundles
       
   190                     } finally {
       
   191                         if (in != null) {
       
   192                             try {
       
   193                                 in.close();
       
   194                             } catch (IOException ioe) {
       
   195                                 DownloadManager.log(ioe);
       
   196                             }
       
   197                         }
       
   198                     }
       
   199                 }
       
   200             }
       
   201         }
       
   202         finally {
       
   203             getReceiptsMutex().release();
       
   204         }
       
   205     }
       
   206 
       
   207 
       
   208     /** Returns the bundle corresponding to the specified name. */
       
   209     public static synchronized Bundle getBundle(String bundleId)
       
   210             throws IOException {
       
   211         Bundle result =(Bundle) bundles.get(bundleId);
       
   212         if (result == null && (bundleId.equals("merged") ||
       
   213                 Arrays.asList(DownloadManager.getBundleNames()).contains(bundleId))) {
       
   214             result = new Bundle();
       
   215             result.name = bundleId;
       
   216 
       
   217             if (DownloadManager.isWindowsVista()) {
       
   218                 result.localPath =
       
   219                         new File(DownloadManager.getLocalLowTempBundlePath(),
       
   220                                  bundleId + ".zip");
       
   221                 result.lowJavaPath = new File(
       
   222                         DownloadManager.getLocalLowKernelJava() + bundleId);
       
   223             } else {
       
   224                 result.localPath = new File(DownloadManager.getBundlePath(),
       
   225                         bundleId + ".zip");
       
   226             }
       
   227 
       
   228             String jarPath = DownloadManager.getBundleProperty(bundleId,
       
   229                     DownloadManager.JAR_PATH_PROPERTY);
       
   230             if (jarPath != null) {
       
   231                 if (DownloadManager.isWindowsVista()) {
       
   232                     result.lowJarPath = new File(
       
   233                         DownloadManager.getLocalLowKernelJava() + bundleId,
       
   234                         jarPath);
       
   235                 }
       
   236                 result.jarPath = new File(DownloadManager.JAVA_HOME,
       
   237                         jarPath);
       
   238 
       
   239             } else {
       
   240 
       
   241                 if (DownloadManager.isWindowsVista()) {
       
   242                     result.lowJarPath = new File(
       
   243                         DownloadManager.getLocalLowKernelJava() + bundleId +
       
   244                             "\\lib\\bundles",
       
   245                         bundleId + ".jar");
       
   246                 }
       
   247 
       
   248                 result.jarPath = new File(DownloadManager.getBundlePath(),
       
   249                         bundleId + ".jar");
       
   250 
       
   251             }
       
   252 
       
   253             bundles.put(bundleId, result);
       
   254         }
       
   255         return result;
       
   256     }
       
   257 
       
   258 
       
   259     /**
       
   260      * Returns the name of this bundle.  The name is typically defined by
       
   261      * the bundles.xml file.
       
   262      */
       
   263     public String getName() {
       
   264         return name;
       
   265     }
       
   266 
       
   267 
       
   268     /**
       
   269      * Sets the name of this bundle.
       
   270      */
       
   271     public void setName(String name) {
       
   272         this.name = name;
       
   273     }
       
   274 
       
   275 
       
   276     /**
       
   277      * Returns the path to the bundle file on the local filesystem.  The file
       
   278      * will only exist if the bundle has already been downloaded;  otherwise
       
   279      * it will be created when download() is called.
       
   280      */
       
   281     public File getLocalPath() {
       
   282         return localPath;
       
   283     }
       
   284 
       
   285 
       
   286     /**
       
   287      * Sets the location of the bundle file on the local filesystem.  If the
       
   288      * file already exists, the bundle will be considered downloaded;
       
   289      * otherwise the file will be created when download() is called.
       
   290      */
       
   291     public void setLocalPath(File localPath) {
       
   292         this.localPath = localPath;
       
   293     }
       
   294 
       
   295 
       
   296     /**
       
   297      * Returns the path to the extracted JAR file containing this bundle's
       
   298      * classes.  This file should only exist after the bundle has been
       
   299      * installed.
       
   300      */
       
   301     public File getJarPath() {
       
   302         return jarPath;
       
   303     }
       
   304 
       
   305 
       
   306     /**
       
   307      * Sets the path to the extracted JAR file containing this bundle's
       
   308      * classes.  This file will be created as part of installing the bundle.
       
   309      */
       
   310     public void setJarPath(File jarPath) {
       
   311         this.jarPath = jarPath;
       
   312     }
       
   313 
       
   314 
       
   315     /**
       
   316      * Returns the size of the bundle download in bytes.
       
   317      */
       
   318     public int getSize() {
       
   319         return Integer.valueOf(DownloadManager.getBundleProperty(getName(),
       
   320                 DownloadManager.SIZE_PROPERTY));
       
   321     }
       
   322 
       
   323 
       
   324     /**
       
   325      * Returns true if the bundle file (getLocalPath()) should be deleted
       
   326      * when the bundle is successfully installed.  Defaults to true.
       
   327      */
       
   328     public boolean getDeleteOnInstall() {
       
   329         return deleteOnInstall;
       
   330     }
       
   331 
       
   332 
       
   333     /**
       
   334      * Sets whether the bundle file (getLocalPath()) should be deleted
       
   335      * when the bundle is successfully installed.  Defaults to true.
       
   336      */
       
   337     public void setDeleteOnInstall(boolean deleteOnInstall) {
       
   338         this.deleteOnInstall = deleteOnInstall;
       
   339     }
       
   340 
       
   341 
       
   342     /** Sets the current state of this bundle to match reality. */
       
   343     protected void updateState() {
       
   344         synchronized(Bundle.class) {
       
   345             loadReceipts();
       
   346             if (receipts.contains(name) ||
       
   347                     "true".equals(DownloadManager.getBundleProperty(name,
       
   348                     DownloadManager.INSTALL_PROPERTY)))
       
   349                 state = Bundle.INSTALLED;
       
   350             else if (localPath.exists())
       
   351                 state = Bundle.DOWNLOADED;
       
   352         }
       
   353     }
       
   354 
       
   355 
       
   356     private String getURL(boolean showUI) throws IOException {
       
   357         Properties urls = DownloadManager.getBundleURLs(showUI);
       
   358         String result = urls.getProperty(name + ".zip");
       
   359         if (result == null) {
       
   360             result = urls.getProperty(name);
       
   361             if (result == null) {
       
   362                 DownloadManager.log("Unable to determine bundle URL for " + this);
       
   363                 DownloadManager.log("Bundle URLs: " + urls);
       
   364                 DownloadManager.sendErrorPing(DownloadManager.ERROR_NO_SUCH_BUNDLE);
       
   365 
       
   366                 throw new NullPointerException("Unable to determine URL " +
       
   367                         "for bundle: " + this);
       
   368             }
       
   369         }
       
   370         return result;
       
   371     }
       
   372 
       
   373 
       
   374     /**
       
   375      * Downloads the bundle.  This method blocks until the download is
       
   376      * complete.
       
   377      *
       
   378      *@param showProgress true to display a progress dialog
       
   379      */
       
   380     private void download(boolean showProgress) {
       
   381         if (DownloadManager.isJREComplete())
       
   382             return;
       
   383         Mutex mutex = Mutex.create(DownloadManager.MUTEX_PREFIX + name +
       
   384                 ".download");
       
   385         mutex.acquire();
       
   386         try {
       
   387             long start = System.currentTimeMillis();
       
   388 
       
   389             boolean retry;
       
   390 
       
   391             do {
       
   392                 retry = false;
       
   393                 updateState();
       
   394                 if (state == DOWNLOADED || state == INSTALLED) {
       
   395                     return;
       
   396                 }
       
   397                 File tmp = null;
       
   398                 try {
       
   399                     tmp = new File(localPath + ".tmp");
       
   400 
       
   401                     // tmp.deleteOnExit();
       
   402 
       
   403                     if (DownloadManager.getBaseDownloadURL().equals(
       
   404                             DownloadManager.RESOURCE_URL)) {
       
   405                         // RESOURCE_URL is used during build process, to
       
   406                         // avoid actual network traffic.  This is called in
       
   407                         // the SplitJRE DownloadTest to determine which
       
   408                         // classes are needed to support downloads, but we
       
   409                         // bypass the actual HTTP download to simplify the
       
   410                         // build process (it's all native code, so from
       
   411                         // DownloadTest's standpoint it doesn't matter if we
       
   412                         // really call it or not).
       
   413                         String path = "/" + name + ".zip";
       
   414                         InputStream in =
       
   415                                 getClass().getResourceAsStream(path);
       
   416                         if (in == null)
       
   417                             throw new IOException("could not locate " +
       
   418                                     "resource: " + path);
       
   419                         FileOutputStream out = new FileOutputStream(tmp);
       
   420                         DownloadManager.send(in, out);
       
   421                         in.close();
       
   422                         out.close();
       
   423                     }
       
   424                     else {
       
   425                         try {
       
   426                             String bundleURL = getURL(showProgress);
       
   427                             DownloadManager.log("Downloading from: " +
       
   428                                         bundleURL);
       
   429                             DownloadManager.downloadFromURL(bundleURL, tmp,
       
   430                                     name.replace('_', '.'), showProgress);
       
   431                         }
       
   432                         catch (HttpRetryException e) {
       
   433                             // Akamai returned a 403, get new URL
       
   434                             DownloadManager.flushBundleURLs();
       
   435                             String bundleURL = getURL(showProgress);
       
   436                             DownloadManager.log("Retrying at new " +
       
   437                                         "URL: " + bundleURL);
       
   438                             DownloadManager.downloadFromURL(bundleURL, tmp,
       
   439                                     name.replace('_', '.'),
       
   440                                     showProgress);
       
   441                             // we intentionally don't do a 403 retry
       
   442                             // again, to avoid infinite retries
       
   443                         }
       
   444                     }
       
   445                     if (!tmp.exists() || tmp.length() == 0) {
       
   446                         if (showProgress) {
       
   447                             // since showProgress = true, native code should
       
   448                             // have offered to retry.  Since we ended up here,
       
   449                             // we conclude that download failed & user opted to
       
   450                             // cancel.  Set complete to true to stop bugging
       
   451                             // him in the future (if one bundle fails, the
       
   452                             // rest are virtually certain to).
       
   453                             DownloadManager.complete = true;
       
   454                         }
       
   455                         DownloadManager.fatalError(DownloadManager.ERROR_UNSPECIFIED);
       
   456                     }
       
   457 
       
   458                     /**
       
   459                      * Bundle security
       
   460                      *
       
   461                      * Check for corruption/spoofing
       
   462                      */
       
   463 
       
   464 
       
   465                     /* Create a bundle check from the tmp file */
       
   466                     BundleCheck gottenCheck = BundleCheck.getInstance(tmp);
       
   467 
       
   468                     /* Get the check expected for the Bundle */
       
   469                     BundleCheck expectedCheck = BundleCheck.getInstance(name);
       
   470 
       
   471                     // Do they match?
       
   472 
       
   473                     if (expectedCheck.equals(gottenCheck)) {
       
   474 
       
   475                         // Security check OK, uncompress the bundle file
       
   476                         // into the local path
       
   477 
       
   478                         long uncompressedLength = tmp.length();
       
   479                         localPath.delete();
       
   480 
       
   481                         File uncompressedPath = new File(tmp.getPath() +
       
   482                             ".jar0");
       
   483                         if (! extraUncompress(tmp.getPath(),
       
   484                             uncompressedPath.getPath())) {
       
   485                             // Extra uncompression not available, fall
       
   486                             // back to alternative if it is enabled.
       
   487                             if (DownloadManager.debug) {
       
   488                                 DownloadManager.log("Uncompressing with GZIP");
       
   489                             }
       
   490                             GZIPInputStream in = new GZIPInputStream( new
       
   491                                 BufferedInputStream(new FileInputStream(tmp),
       
   492                                 DownloadManager.BUFFER_SIZE));
       
   493                             BufferedOutputStream out = new BufferedOutputStream(
       
   494                                 new FileOutputStream(uncompressedPath),
       
   495                                 DownloadManager.BUFFER_SIZE);
       
   496                             DownloadManager.send(in,out);
       
   497                             in.close();
       
   498                             out.close();
       
   499                             if (! uncompressedPath.renameTo(localPath)) {
       
   500                                 throw new IOException("unable to rename " +
       
   501                                     uncompressedPath + " to " + localPath);
       
   502                             }
       
   503                         } else {
       
   504                             if (DownloadManager.debug) {
       
   505                                 DownloadManager.log("Uncompressing with LZMA");
       
   506                             }
       
   507                             if (! uncompressedPath.renameTo(localPath)) {
       
   508                                 throw new IOException("unable to rename " +
       
   509                                     uncompressedPath + " to " + localPath);
       
   510                             }
       
   511                         }
       
   512                         state = DOWNLOADED;
       
   513                         bytesDownloaded += uncompressedLength;
       
   514                         long time = (System.currentTimeMillis() -
       
   515                                 start);
       
   516                         DownloadManager.log("Downloaded " + name +
       
   517                                 " in " + time + "ms.  Downloaded " +
       
   518                                 bytesDownloaded + " bytes this session.");
       
   519 
       
   520                         // Normal completion
       
   521                     } else {
       
   522 
       
   523                         // Security check not OK: remove the temp file
       
   524                         // and consult the user
       
   525 
       
   526                         tmp.delete();
       
   527 
       
   528                         DownloadManager.log(
       
   529                                 "DownloadManager: Security check failed for " +
       
   530                                 "bundle " + name);
       
   531 
       
   532                         // only show dialog if we are not in silent mode
       
   533                         if (showProgress) {
       
   534                             retry = DownloadManager.askUserToRetryDownloadOrQuit(
       
   535                                     DownloadManager.ERROR_UNSPECIFIED);
       
   536                         }
       
   537 
       
   538                         if (!retry) {
       
   539                             // User wants to give up
       
   540                             throw new RuntimeException(
       
   541                                 "Failed bundle security check and user " +
       
   542                                 "canceled");
       
   543                         }
       
   544                     }
       
   545                 }
       
   546                 catch (IOException e) {
       
   547                     // Look for "out of space" using File.getUsableSpace()
       
   548                     // here when downloadFromURL starts throwing IOException
       
   549                     // (or preferably a distinct exception for this case).
       
   550                     DownloadManager.log(e);
       
   551                 }
       
   552             } while (retry);
       
   553         } finally {
       
   554             mutex.release();
       
   555         }
       
   556     }
       
   557 
       
   558 
       
   559     /**
       
   560      * Calls {@link #queueDownload()} on all of this bundle's dependencies.
       
   561      */
       
   562     void queueDependencies(boolean showProgress) {
       
   563         try {
       
   564             String dependencies =
       
   565                     DownloadManager.getBundleProperty(name,
       
   566                     DownloadManager.DEPENDENCIES_PROPERTY);
       
   567             if (dependencies != null) {
       
   568                 StringTokenizer st = new StringTokenizer(dependencies,
       
   569                         " ,");
       
   570                 while (st.hasMoreTokens()) {
       
   571                     Bundle b = getBundle(st.nextToken());
       
   572                     if (b != null && !b.isInstalled()) {
       
   573                         if (DownloadManager.debug) {
       
   574                             DownloadManager.log("Queueing " + b.name +
       
   575                                     " as a dependency of " + name + "...");
       
   576                         }
       
   577                         b.install(showProgress, true, false);
       
   578                     }
       
   579                 }
       
   580             }
       
   581         } catch (IOException e) {
       
   582             // shouldn't happen
       
   583             DownloadManager.log(e);
       
   584         }
       
   585     }
       
   586 
       
   587 
       
   588     static synchronized ExecutorService getThreadPool() {
       
   589         if (threadPool == null) {
       
   590             threadPool = Executors.newFixedThreadPool(THREADS,
       
   591                             new ThreadFactory () {
       
   592                                 public Thread newThread(Runnable r) {
       
   593                                     Thread result = new Thread(r);
       
   594                                     result.setDaemon(true);
       
   595                                     return result;
       
   596                                 }
       
   597                             }
       
   598                         );
       
   599         }
       
   600         return threadPool;
       
   601     }
       
   602 
       
   603 
       
   604     private void unpackBundle() throws IOException {
       
   605         File useJarPath = null;
       
   606         if (DownloadManager.isWindowsVista()) {
       
   607             useJarPath = lowJarPath;
       
   608             File jarDir = useJarPath.getParentFile();
       
   609             if (jarDir != null) {
       
   610                 jarDir.mkdirs();
       
   611             }
       
   612         } else {
       
   613             useJarPath = jarPath;
       
   614         }
       
   615 
       
   616         DownloadManager.log("Unpacking " + this + " to " + useJarPath);
       
   617 
       
   618         InputStream rawStream = new FileInputStream(localPath);
       
   619         JarInputStream in = new JarInputStream(rawStream) {
       
   620             public void close() throws IOException {
       
   621                 // prevent any sub-processes here from actually closing the
       
   622                 // input stream; we'll use rawsStream.close() when we're
       
   623                 // done with it
       
   624             }
       
   625         };
       
   626 
       
   627         try {
       
   628             File jarTmp = null;
       
   629             JarEntry entry;
       
   630             while ((entry = in.getNextJarEntry()) != null) {
       
   631                 String entryName = entry.getName();
       
   632                 if (entryName.equals("classes.pack")) {
       
   633                     File packTmp = new File(useJarPath + ".pack");
       
   634                     packTmp.getParentFile().mkdirs();
       
   635                     DownloadManager.log("Writing temporary .pack file " + packTmp);
       
   636                     OutputStream tmpOut = new FileOutputStream(packTmp);
       
   637                     try {
       
   638                         DownloadManager.send(in, tmpOut);
       
   639                     } finally {
       
   640                         tmpOut.close();
       
   641                     }
       
   642                     // we unpack to a temporary file and then, towards the end
       
   643                     // of this method, use a (hopefully atomic) rename to put it
       
   644                     // into its final location; this should avoid the problem of
       
   645                     // partially-completed downloads.  Doing the rename last
       
   646                     // allows us to check for the presence of the JAR file to
       
   647                     // see whether the bundle has in fact been downloaded.
       
   648                     jarTmp = new File(useJarPath + ".tmp");
       
   649                     DownloadManager.log("Writing temporary .jar file " + jarTmp);
       
   650                     unpack(packTmp, jarTmp);
       
   651                     packTmp.delete();
       
   652                 } else if (!entryName.startsWith("META-INF")) {
       
   653                     File dest;
       
   654                     if (DownloadManager.isWindowsVista()) {
       
   655                         dest = new File(lowJavaPath,
       
   656                             entryName.replace('/', File.separatorChar));
       
   657                     } else {
       
   658                         dest = new File(DownloadManager.JAVA_HOME,
       
   659                             entryName.replace('/', File.separatorChar));
       
   660                     }
       
   661                     if (entryName.equals(BUNDLE_JAR_ENTRY_NAME))
       
   662                         dest = useJarPath;
       
   663                     File destTmp = new File(dest + ".tmp");
       
   664                     boolean exists = dest.exists();
       
   665                     if (!exists) {
       
   666                         DownloadManager.log(dest + ".mkdirs()");
       
   667                         dest.getParentFile().mkdirs();
       
   668                     }
       
   669                     try {
       
   670                         DownloadManager.log("Using temporary file " + destTmp);
       
   671                         FileOutputStream out =
       
   672                                 new FileOutputStream(destTmp);
       
   673                         try {
       
   674                             byte[] buffer = new byte[2048];
       
   675                             int c;
       
   676                             while ((c = in.read(buffer)) > 0)
       
   677                                 out.write(buffer, 0, c);
       
   678                         } finally {
       
   679                             out.close();
       
   680                         }
       
   681                         if (exists)
       
   682                             dest.delete();
       
   683                         DownloadManager.log("Renaming from " + destTmp + " to " + dest);
       
   684                         if (!destTmp.renameTo(dest)) {
       
   685                             throw new IOException("unable to rename " +
       
   686                                     destTmp + " to " + dest);
       
   687                         }
       
   688 
       
   689                     } catch (IOException e) {
       
   690                         if (!exists)
       
   691                             throw e;
       
   692                         // otherwise the file already existed and the fact
       
   693                         // that we failed to re-write it probably just
       
   694                         // means that it was in use
       
   695                     }
       
   696                 }
       
   697             }
       
   698 
       
   699             // rename the temporary jar into its final location
       
   700             if (jarTmp != null) {
       
   701                 if (useJarPath.exists())
       
   702                     jarTmp.delete();
       
   703                 else if (!jarTmp.renameTo(useJarPath)) {
       
   704                     throw new IOException("unable to rename " + jarTmp +
       
   705                             " to " + useJarPath);
       
   706                 }
       
   707             }
       
   708             if (DownloadManager.isWindowsVista()) {
       
   709                 // move bundle to real location
       
   710                 DownloadManager.log("Using broker to move " + name);
       
   711                 if (!DownloadManager.moveDirWithBroker(
       
   712                         DownloadManager.getKernelJREDir() + name)) {
       
   713                     throw new IOException("unable to create " + name);
       
   714                 }
       
   715                 DownloadManager.log("Broker finished " + name);
       
   716             }
       
   717             DownloadManager.log("Finished unpacking " + this);
       
   718         } finally {
       
   719             rawStream.close();
       
   720         }
       
   721         if (deleteOnInstall) {
       
   722             localPath.delete();
       
   723         }
       
   724 
       
   725     }
       
   726 
       
   727 
       
   728     public static void unpack(File pack, File jar) throws IOException {
       
   729         Process p = Runtime.getRuntime().exec(DownloadManager.JAVA_HOME + File.separator +
       
   730                 "bin" + File.separator + "unpack200 -Hoff \"" + pack + "\" \"" + jar + "\"");
       
   731         try {
       
   732             p.waitFor();
       
   733         }
       
   734         catch (InterruptedException e) {
       
   735         }
       
   736     }
       
   737 
       
   738 
       
   739     /**
       
   740      * Unpacks and installs the bundle.  The bundle's classes are not
       
   741      * immediately added to the boot class path; this happens when the VM
       
   742      * attempts to load a class and calls getBootClassPathEntryForClass().
       
   743      */
       
   744     public void install() throws IOException {
       
   745         install(true, false, true);
       
   746     }
       
   747 
       
   748 
       
   749     /**
       
   750      * Unpacks and installs the bundle, optionally hiding the progress
       
   751      * indicator.  The bundle's classes are not immediately added to the
       
   752      * boot class path; this happens when the VM attempts to load a class
       
   753      * and calls getBootClassPathEntryForClass().
       
   754      *
       
   755      *@param showProgress true to display a progress dialog
       
   756      *@param downloadOnly true to download but not install
       
   757      *@param block true to wait until the operation is complete before returning
       
   758      */
       
   759     public synchronized void install(final boolean showProgress,
       
   760             final boolean downloadOnly, boolean block) throws IOException {
       
   761         if (DownloadManager.isJREComplete())
       
   762             return;
       
   763         if (state == NOT_DOWNLOADED || state == QUEUED) {
       
   764             // we allow an already-queued bundle to be placed into the queue
       
   765             // again, to handle the case where the bundle is queued with
       
   766             // downloadOnly true and then we try to queue it again with
       
   767             // downloadOnly false -- the second queue entry will actually
       
   768             // install it.
       
   769             if (state != QUEUED) {
       
   770                 DownloadManager.addToTotalDownloadSize(getSize());
       
   771                 state = QUEUED;
       
   772             }
       
   773             if (getThreadPool().isShutdown()) {
       
   774                 if (state == NOT_DOWNLOADED || state == QUEUED)
       
   775                     doInstall(showProgress, downloadOnly);
       
   776             }
       
   777             else {
       
   778                 Future task = getThreadPool().submit(new Runnable() {
       
   779                     public void run() {
       
   780                         try {
       
   781                             if (state == NOT_DOWNLOADED || state == QUEUED ||
       
   782                                     (!downloadOnly && state == DOWNLOADED)) {
       
   783                                 doInstall(showProgress, downloadOnly);
       
   784                             }
       
   785                         }
       
   786                         catch (IOException e) {
       
   787                             // ignore
       
   788                         }
       
   789                     }
       
   790                 });
       
   791                 queueDependencies(showProgress);
       
   792                 if (block) {
       
   793                     try {
       
   794                         task.get();
       
   795                     }
       
   796                     catch (Exception e) {
       
   797                         throw new Error(e);
       
   798                     }
       
   799                 }
       
   800             }
       
   801         }
       
   802         else if (state == DOWNLOADED && !downloadOnly)
       
   803             doInstall(showProgress, false);
       
   804     }
       
   805 
       
   806 
       
   807     private void doInstall(boolean showProgress, boolean downloadOnly)
       
   808             throws IOException {
       
   809         Mutex mutex = Mutex.create(DownloadManager.MUTEX_PREFIX + name +
       
   810                 ".install");
       
   811         DownloadManager.bundleInstallStart();
       
   812         try {
       
   813             mutex.acquire();
       
   814             updateState();
       
   815             if (state == NOT_DOWNLOADED || state == QUEUED) {
       
   816                 download(showProgress);
       
   817             }
       
   818 
       
   819             if (state == DOWNLOADED && downloadOnly) {
       
   820                 return;
       
   821             }
       
   822 
       
   823             if (state == INSTALLED) {
       
   824                 return;
       
   825             }
       
   826             if (state != DOWNLOADED) {
       
   827                 DownloadManager.fatalError(DownloadManager.ERROR_UNSPECIFIED);
       
   828             }
       
   829 
       
   830             DownloadManager.log("Calling unpackBundle for " + this);
       
   831             unpackBundle();
       
   832             DownloadManager.log("Writing receipt for " + this);
       
   833             writeReceipt();
       
   834             updateState();
       
   835             DownloadManager.log("Finished installing " + this + ", state=" + state);
       
   836         } finally {
       
   837             if (lowJavaPath != null) {
       
   838                 lowJavaPath.delete();
       
   839             }
       
   840             mutex.release();
       
   841             DownloadManager.bundleInstallComplete();
       
   842         }
       
   843     }
       
   844 
       
   845 
       
   846     synchronized void setState(int state) {
       
   847         this.state = state;
       
   848     }
       
   849 
       
   850 
       
   851     /** Returns <code>true</code> if this bundle has been installed. */
       
   852     public boolean isInstalled() {
       
   853         synchronized (Bundle.class) {
       
   854             updateState();
       
   855             return state == INSTALLED;
       
   856         }
       
   857     }
       
   858 
       
   859 
       
   860     /**
       
   861      * Adds an entry to the receipts file indicating that this bundle has
       
   862      * been successfully downloaded.
       
   863      */
       
   864     private void writeReceipt() {
       
   865         getReceiptsMutex().acquire();
       
   866         File useReceiptPath = null;
       
   867         try {
       
   868 
       
   869             try {
       
   870 
       
   871                 receipts.add(name);
       
   872 
       
   873                 if (DownloadManager.isWindowsVista()) {
       
   874                     // write out receipts to locallow
       
   875                     useReceiptPath = new File(
       
   876                             DownloadManager.getLocalLowTempBundlePath(),
       
   877                             "receipts");
       
   878 
       
   879                     if (receiptPath.exists()) {
       
   880                         // copy original file to locallow location
       
   881                         DownloadManager.copyReceiptFile(receiptPath,
       
   882                                 useReceiptPath);
       
   883                     }
       
   884 
       
   885                     // update receipt in locallow path
       
   886                     // only append if original receipt path exists
       
   887                     FileOutputStream out = new FileOutputStream(useReceiptPath,
       
   888                             receiptPath.exists());
       
   889                     out.write((name + System.getProperty("line.separator")).getBytes("utf-8"));
       
   890                     out.close();
       
   891 
       
   892                     // use broker to move back to real path
       
   893                     if (!DownloadManager.moveFileWithBroker(
       
   894                             DownloadManager.getKernelJREDir()
       
   895                         + "-bundles" + File.separator + "receipts")) {
       
   896                         throw new IOException("failed to write receipts");
       
   897                     }
       
   898                 } else {
       
   899                     useReceiptPath = receiptPath;
       
   900                     FileOutputStream out = new FileOutputStream(useReceiptPath,
       
   901                             true);
       
   902                     out.write((name + System.getProperty("line.separator")).getBytes("utf-8"));
       
   903                     out.close();
       
   904                 }
       
   905 
       
   906 
       
   907             } catch (IOException e) {
       
   908                 DownloadManager.log(e);
       
   909                 // safe to continue, as the worst that happens is we
       
   910                 // re-download existing bundles
       
   911             }
       
   912         }
       
   913         finally {
       
   914             getReceiptsMutex().release();
       
   915         }
       
   916     }
       
   917 
       
   918 
       
   919     public String toString() {
       
   920         return "Bundle[" + name + "]";
       
   921     }
       
   922 }