/hg/icedtea-web: Refactor initialize/download runnable out of Re...

jkang at icedtea.classpath.org jkang at icedtea.classpath.org
Wed Jan 28 15:14:17 UTC 2015


changeset 16760ac4a689 in /hg/icedtea-web
details: http://icedtea.classpath.org/hg/icedtea-web?cmd=changeset;node=16760ac4a689
author: Jie Kang <jkang at redhat.com>
date: Wed Jan 28 10:12:28 2015 -0500

	Refactor initialize/download runnable out of ResourceTracker and add tests

	2015-01-28  Jie Kang  <jkang at redhat.com>

	    Refactor initialize/download runnable out of ResourceTracker and add tests
	    * netx/net/sourceforge/jnlp/cache/ResourceTracker.java: moved Downloader
	    runnable into ResourceDownloader along with initialize and download
	    functions and their sub-functions. Removed prefetchTracker system and
	    queue. Moved downloadOptions into Resource.java.
	    * netx/net/sourceforge/jnlp/cache/Resource.java: added downloadOptions
	    field and getter/setter
	    * netx/net/sourceforge/jnlp/cache/ResourceDownloader.java:
	    (getUrlResponseCode), (getUrlResponseCodeWithRedirectonResult)
	    (initializeResource), (findBestUrl), (downloadResource),
	    (getDownloadConnection), (downloadPackGzFile), (downloadGZipFile),
	    (downloadFile), (storeEntryFields), (writeDownloadToFile)(uncompressGzip),
	    (uncompressPackGz): new Runnable class for initializing and downloading
	    resources. Code from ResourceTracker.java
	    * tests/netx/unit/net/sourceforge/jnlp/cache/ResourceTrackerTest.java:
	    tests for downloading/initializing functions and their subfunctions moved
	    to ResourceDownloaderTest.java
	    * tests/netx/unit/net/sourceforge/jnlp/cache/ResourceDownloaderTest.java:
	    relevant tests from ResourceTrackerTest.java moved here.
	    (testDownloadResource), (testDownloadPackGzResource)
	    (testDownloadVersionedResource), (testDownloadVersionedPackGzResource)
	    (testDownloadLocalResourceFails), (testDownloadNotExistingResourceFails):
	    New tests added


diffstat:

 ChangeLog                                                              |   26 +
 netx/net/sourceforge/jnlp/cache/Resource.java                          |   16 +-
 netx/net/sourceforge/jnlp/cache/ResourceDownloader.java                |  445 ++++++
 netx/net/sourceforge/jnlp/cache/ResourceTracker.java                   |  644 +---------
 tests/netx/unit/net/sourceforge/jnlp/cache/ResourceDownloaderTest.java |  498 +++++++
 tests/netx/unit/net/sourceforge/jnlp/cache/ResourceTrackerTest.java    |  210 +---
 6 files changed, 991 insertions(+), 848 deletions(-)

diffs (truncated from 2037 to 500 lines):

diff -r 918fb141b815 -r 16760ac4a689 ChangeLog
--- a/ChangeLog	Fri Jan 23 15:35:46 2015 +0100
+++ b/ChangeLog	Wed Jan 28 10:12:28 2015 -0500
@@ -1,3 +1,29 @@
+2015-01-28  Jie Kang  <jkang at redhat.com>
+
+	Refactor initialize/download runnable out of ResourceTracker and add tests
+	* netx/net/sourceforge/jnlp/cache/ResourceTracker.java: moved Downloader
+	runnable into ResourceDownloader along with initialize and download
+	functions and their sub-functions. Removed prefetchTracker system and
+	queue. Moved downloadOptions into Resource.java. 
+	* netx/net/sourceforge/jnlp/cache/Resource.java: added downloadOptions
+	field and getter/setter
+	* netx/net/sourceforge/jnlp/cache/ResourceDownloader.java:
+	(getUrlResponseCode), (getUrlResponseCodeWithRedirectonResult)
+	(initializeResource), (findBestUrl), (downloadResource),
+	(getDownloadConnection), (downloadPackGzFile), (downloadGZipFile),
+	(downloadFile), (storeEntryFields), (writeDownloadToFile)(uncompressGzip), 
+	(uncompressPackGz): new Runnable class for initializing and downloading
+	resources. Code	from ResourceTracker.java
+	* tests/netx/unit/net/sourceforge/jnlp/cache/ResourceTrackerTest.java:
+	tests for downloading/initializing functions and their subfunctions moved
+	to ResourceDownloaderTest.java
+	* tests/netx/unit/net/sourceforge/jnlp/cache/ResourceDownloaderTest.java:
+	relevant tests from ResourceTrackerTest.java moved here.
+	(testDownloadResource), (testDownloadPackGzResource)
+	(testDownloadVersionedResource), (testDownloadVersionedPackGzResource)
+	(testDownloadLocalResourceFails), (testDownloadNotExistingResourceFails):
+	New tests added
+
 2014-01-23  Jiri Vanek  <jvanek at redhat.com>
 
 	Returned accidentally removed creation of shortcuts for jnlp applications.
diff -r 918fb141b815 -r 16760ac4a689 netx/net/sourceforge/jnlp/cache/Resource.java
--- a/netx/net/sourceforge/jnlp/cache/Resource.java	Fri Jan 23 15:35:46 2015 +0100
+++ b/netx/net/sourceforge/jnlp/cache/Resource.java	Wed Jan 28 10:12:28 2015 -0500
@@ -23,6 +23,7 @@
 import java.util.List;
 import java.util.Set;
 
+import net.sourceforge.jnlp.DownloadOptions;
 import net.sourceforge.jnlp.Version;
 import net.sourceforge.jnlp.util.UrlUtils;
 import net.sourceforge.jnlp.util.WeakList;
@@ -47,7 +48,6 @@
  * @version $Revision: 1.9 $
  */
 public class Resource {
-
     // todo: fix resources to handle different versions
 
     // todo: IIRC, any resource is checked for being up-to-date
@@ -97,6 +97,9 @@
     /** Update policy for this resource */
     private final UpdatePolicy updatePolicy;
 
+    /** Download options for this resource */
+    private DownloadOptions downloadOptions;
+
     /**
      * Create a resource.
      */
@@ -414,6 +417,14 @@
         }
     }
 
+    public void setDownloadOptions(DownloadOptions downloadOptions) {
+        this.downloadOptions = downloadOptions;
+    }
+
+    public DownloadOptions getDownloadOptions() {
+        return this.downloadOptions;
+    }
+
     @Override
     public int hashCode() {
         // FIXME: should probably have a better hashcode than this, but considering
@@ -440,7 +451,4 @@
     public String toString() {
         return "location=" + location.toString() + " state=" + getStatusString();
     }
-    
-    
-
 }
diff -r 918fb141b815 -r 16760ac4a689 netx/net/sourceforge/jnlp/cache/ResourceDownloader.java
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/netx/net/sourceforge/jnlp/cache/ResourceDownloader.java	Wed Jan 28 10:12:28 2015 -0500
@@ -0,0 +1,445 @@
+package net.sourceforge.jnlp.cache;
+
+import static net.sourceforge.jnlp.cache.Resource.Status.CONNECTED;
+import static net.sourceforge.jnlp.cache.Resource.Status.CONNECTING;
+import static net.sourceforge.jnlp.cache.Resource.Status.DOWNLOADED;
+import static net.sourceforge.jnlp.cache.Resource.Status.DOWNLOADING;
+import static net.sourceforge.jnlp.cache.Resource.Status.ERROR;
+import static net.sourceforge.jnlp.cache.Resource.Status.PRECONNECT;
+import static net.sourceforge.jnlp.cache.Resource.Status.PREDOWNLOAD;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Pack200;
+import java.util.zip.GZIPInputStream;
+
+import net.sourceforge.jnlp.DownloadOptions;
+import net.sourceforge.jnlp.Version;
+import net.sourceforge.jnlp.runtime.JNLPRuntime;
+import net.sourceforge.jnlp.security.ConnectionFactory;
+import net.sourceforge.jnlp.util.HttpUtils;
+import net.sourceforge.jnlp.util.logging.OutputController;
+
+public class ResourceDownloader implements Runnable {
+
+    private final Resource resource;
+    private final Object lock;
+
+    public ResourceDownloader(Resource resource, Object lock) {
+        this.resource = resource;
+        this.lock = lock;
+    }
+
+    static int getUrlResponseCode(URL url, Map<String, String> requestProperties, ResourceTracker.RequestMethods requestMethod) throws IOException {
+        return getUrlResponseCodeWithRedirectonResult(url, requestProperties, requestMethod).result;
+    }
+
+    /**
+     * Connects to the given URL, and grabs a response code and redirecton if
+     * the URL uses the HTTP protocol, or returns an arbitrary valid HTTP
+     * response code.
+     *
+     * @return the response code if HTTP connection and redirection value, or
+     * HttpURLConnection.HTTP_OK and null if not.
+     * @throws IOException
+     */
+    static CodeWithRedirect getUrlResponseCodeWithRedirectonResult(URL url, Map<String, String> requestProperties, ResourceTracker.RequestMethods requestMethod) throws IOException {
+        CodeWithRedirect result = new CodeWithRedirect();
+        URLConnection connection = ConnectionFactory.getConnectionFactory().openConnection(url);
+
+        for (Map.Entry<String, String> property : requestProperties.entrySet()) {
+            connection.addRequestProperty(property.getKey(), property.getValue());
+        }
+
+        if (connection instanceof HttpURLConnection) {
+            HttpURLConnection httpConnection = (HttpURLConnection) connection;
+            httpConnection.setRequestMethod(requestMethod.toString());
+
+            int responseCode = httpConnection.getResponseCode();
+
+            /* Fully consuming current request helps with connection re-use
+             * See http://docs.oracle.com/javase/1.5.0/docs/guide/net/http-keepalive.html */
+            HttpUtils.consumeAndCloseConnectionSilently(httpConnection);
+
+            result.result = responseCode;
+        }
+
+        Map<String, List<String>> header = connection.getHeaderFields();
+        for (Map.Entry<String, List<String>> entry : header.entrySet()) {
+            OutputController.getLogger().log("Key : " + entry.getKey() + " ,Value : " + entry.getValue());
+        }
+        /*
+         * Do this only on 301,302,303(?)307,308>
+         * Now setting value for all, and lets upper stack to handle it
+         */
+        String possibleRedirect = connection.getHeaderField("Location");
+        if (possibleRedirect != null && possibleRedirect.trim().length() > 0) {
+            result.URL = new URL(possibleRedirect);
+        }
+        ConnectionFactory.getConnectionFactory().disconnect(connection);
+
+        return result;
+
+    }
+
+    @Override
+    public void run() {
+        if (resource.isSet(PRECONNECT) && !resource.hasFlags(EnumSet.of(ERROR, CONNECTING, CONNECTED))) {
+            resource.changeStatus(EnumSet.noneOf(Resource.Status.class), EnumSet.of(CONNECTING));
+            resource.fireDownloadEvent(); // fire CONNECTING
+            initializeResource();
+        }
+        if (resource.isSet(PREDOWNLOAD) && !resource.hasFlags(EnumSet.of(ERROR, DOWNLOADING, DOWNLOADED))) {
+            resource.changeStatus(EnumSet.noneOf(Resource.Status.class), EnumSet.of(DOWNLOADING));
+            resource.fireDownloadEvent(); // fire CONNECTING
+            downloadResource();
+        }
+    }
+
+    /**
+     * Open a URL connection and get the content length and other
+     * fields.
+     */
+    private void initializeResource() {
+        //verify connection
+        if(!JNLPRuntime.isOfflineForced()){
+            JNLPRuntime.detectOnline(resource.getLocation()/*or doenloadLocation*/);
+        }
+
+        CacheEntry entry = new CacheEntry(resource.getLocation(), resource.getRequestVersion());
+        entry.lock();
+
+        try {
+            File localFile = CacheUtil.getCacheFile(resource.getLocation(), resource.getDownloadVersion());
+            long size = 0;
+            boolean current = true;
+            //this can be null, as it is always filled in online mode, and never read in offline mode
+            URLConnection connection = null;
+            if (localFile != null) {
+                size = localFile.length();
+            } else if (!JNLPRuntime.isOnline()) {
+                OutputController.getLogger().log(OutputController.Level.ERROR_ALL, "You are trying to get resource " + resource.getLocation().toExternalForm() + " but you are in offline mode, and it is not in cache. Attempting to continue, but you may expect failure");
+            }
+            if (JNLPRuntime.isOnline()) {
+                // connect
+                URL finalLocation = findBestUrl(resource);
+
+                if (finalLocation == null) {
+                    OutputController.getLogger().log(OutputController.Level.ERROR_ALL, "Attempted to download " + resource.getLocation() + ", but failed to connect!");
+                    throw new NullPointerException("finalLocation == null"); // Caught below
+                }
+
+                resource.setDownloadLocation(finalLocation);
+                connection = ConnectionFactory.getConnectionFactory().openConnection(finalLocation); // this won't change so should be okay not-synchronized
+                connection.addRequestProperty("Accept-Encoding", "pack200-gzip, gzip");
+
+                size = connection.getContentLength();
+                current = CacheUtil.isCurrent(resource.getLocation(), resource.getRequestVersion(), connection.getLastModified()) && resource.getUpdatePolicy() != UpdatePolicy.FORCE;
+                if (!current) {
+                    if (entry.isCached()) {
+                        entry.markForDelete();
+                        entry.store();
+                        // Old entry will still exist. (but removed at cleanup)
+                        localFile = CacheUtil.makeNewCacheFile(resource.getLocation(), resource.getDownloadVersion());
+                        CacheEntry newEntry = new CacheEntry(resource.getLocation(), resource.getRequestVersion());
+                        newEntry.lock();
+                        entry.unlock();
+                        entry = newEntry;
+                    }
+                }
+            }
+            synchronized (resource) {
+                resource.setLocalFile(localFile);
+                // resource.connection = connection;
+                resource.setSize(size);
+                resource.changeStatus(EnumSet.of(PRECONNECT, CONNECTING), EnumSet.of(CONNECTED, PREDOWNLOAD));
+
+                // check if up-to-date; if so set as downloaded
+                if (current)
+                    resource.changeStatus(EnumSet.of(PREDOWNLOAD, DOWNLOADING), EnumSet.of(DOWNLOADED));
+            }
+
+            // update cache entry
+            if (!current && JNLPRuntime.isOnline()) {
+                entry.setRemoteContentLength(connection.getContentLengthLong());
+                entry.setLastModified(connection.getLastModified());
+            }
+
+            entry.setLastUpdated(System.currentTimeMillis());
+            entry.store();
+
+            synchronized (lock) {
+                lock.notifyAll(); // wake up wait's to check for completion
+            }
+            resource.fireDownloadEvent(); // fire CONNECTED
+
+            // explicitly close the URLConnection.
+            ConnectionFactory.getConnectionFactory().disconnect(connection);
+        } catch (Exception ex) {
+            OutputController.getLogger().log(ex);
+            resource.changeStatus(EnumSet.noneOf(Resource.Status.class), EnumSet.of(ERROR));
+            synchronized (lock) {
+                lock.notifyAll(); // wake up wait's to check for completion
+            }
+            resource.fireDownloadEvent(); // fire ERROR
+        } finally {
+            entry.unlock();
+        }
+    }
+
+    /**
+     * Returns the 'best' valid URL for the given resource.
+     * This first adjusts the file name to take into account file versioning
+     * and packing, if possible.
+     *
+     * @param resource the resource
+     * @return the best URL, or null if all failed to resolve
+     */
+    protected URL findBestUrl(Resource resource) {
+        DownloadOptions options = resource.getDownloadOptions();
+        if (options == null) {
+            options = new DownloadOptions(false, false);
+        }
+
+        List<URL> urls = new ResourceUrlCreator(resource, options).getUrls();
+        OutputController.getLogger().log(OutputController.Level.ERROR_DEBUG, "Finding best URL for: " + resource.getLocation() + " : " + options.toString());
+        OutputController.getLogger().log(OutputController.Level.ERROR_DEBUG, "All possible urls for "
+                + resource.toString() + " : " + urls);
+
+        for (ResourceTracker.RequestMethods requestMethod : ResourceTracker.RequestMethods.getValidRequestMethods()) {
+            for (int i = 0; i < urls.size(); i++) {
+                URL url = urls.get(i);
+                try {
+                    Map<String, String> requestProperties = new HashMap<>();
+                    requestProperties.put("Accept-Encoding", "pack200-gzip, gzip");
+
+                    CodeWithRedirect response = getUrlResponseCodeWithRedirectonResult(url, requestProperties, requestMethod);
+                    if (response.shouldRedirect()){
+                        if (response.URL == null) {
+                            OutputController.getLogger().log(OutputController.Level.ERROR_DEBUG, "Although " + resource.toString() + " got redirect " + response.result + " code for " + requestMethod + " request for " + url.toExternalForm() + " the target was null. Not following");
+                        } else {
+                            OutputController.getLogger().log(OutputController.Level.MESSAGE_DEBUG, "Resource " + resource.toString() + " got redirect " + response.result + " code for " + requestMethod + " request for " + url.toExternalForm() + " adding " + response.URL.toExternalForm()+" to list of possible urls");
+                            if (!JNLPRuntime.isAllowRedirect()){
+                                throw new RedirectionException("The resource " + url.toExternalForm() + " is being redirected (" + response.result + ") to " + response.URL.toExternalForm() + ". This is disabled by default. If you wont to allow it, run javaws with -allowredirect parameter.");
+                            }
+                            urls.add(response.URL);
+                        }
+                    } else if (response.isInvalid()) {
+                        OutputController.getLogger().log(OutputController.Level.ERROR_DEBUG, "For " + resource.toString() + " the server returned " + response.result + " code for " + requestMethod + " request for " + url.toExternalForm());
+                    } else {
+                        OutputController.getLogger().log(OutputController.Level.ERROR_DEBUG, "best url for " + resource.toString() + " is " + url.toString() + " by " + requestMethod);
+                        return url; /* This is the best URL */
+                    }
+                } catch (IOException e) {
+                    // continue to next candidate
+                    OutputController.getLogger().log(OutputController.Level.ERROR_DEBUG, "While processing " + url.toString() + " by " + requestMethod + " for resource " + resource.toString() + " got " + e + ": ");
+                    OutputController.getLogger().log(e);
+                }
+            }
+        }
+
+        /* No valid URL, return null */
+        return null;
+    }
+
+    private void downloadResource() {
+        URLConnection connection = null;
+        URL downloadFrom = resource.getDownloadLocation(); //Where to download from
+        URL downloadTo = resource.getLocation(); //Where to download to
+
+        try {
+            connection = getDownloadConnection(downloadFrom);
+
+            String contentEncoding = connection.getContentEncoding();
+
+            OutputController.getLogger().log(OutputController.Level.ERROR_DEBUG, "Downloading " + downloadTo + " using " +
+                    downloadFrom + " (encoding : " + contentEncoding + ") ");
+
+            boolean packgz = "pack200-gzip".equals(contentEncoding) ||
+                    downloadFrom.getPath().endsWith(".pack.gz");
+            boolean gzip = "gzip".equals(contentEncoding);
+
+            // It's important to check packgz first. If a stream is both
+            // pack200 and gz encoded, then con.getContentEncoding() could
+            // return ".gz", so if we check gzip first, we would end up
+            // treating a pack200 file as a jar file.
+
+            if (packgz) {
+                downloadPackGzFile(resource, connection, new URL(downloadFrom + ".pack.gz"), downloadTo);
+            } else if (gzip) {
+                downloadGZipFile(resource, connection, new URL(downloadFrom + ".gz"), downloadTo);
+            } else {
+                downloadFile(resource, connection, downloadTo);
+            }
+
+            resource.changeStatus(EnumSet.of(DOWNLOADING), EnumSet.of(DOWNLOADED));
+            synchronized (lock) {
+                lock.notifyAll(); // wake up wait's to check for completion
+            }
+            resource.fireDownloadEvent(); // fire DOWNLOADED
+        } catch (Exception ex) {
+            OutputController.getLogger().log(ex);
+            resource.changeStatus(EnumSet.noneOf(Resource.Status.class), EnumSet.of(ERROR));
+            synchronized (lock) {
+                lock.notifyAll();
+            }
+            resource.fireDownloadEvent(); // fire ERROR
+        } finally {
+            if (connection != null) {
+                ConnectionFactory.getConnectionFactory().disconnect(connection);
+            }
+        }
+    }
+
+    private URLConnection getDownloadConnection(URL location) throws IOException {
+        URLConnection con = ConnectionFactory.getConnectionFactory().openConnection(location);
+        con.addRequestProperty("Accept-Encoding", "pack200-gzip, gzip");
+        con.connect();
+        return con;
+    }
+
+    private void downloadPackGzFile(Resource resource, URLConnection connection, URL downloadFrom, URL downloadTo) throws IOException {
+        downloadFile(resource, connection, downloadFrom);
+
+        uncompressPackGz(downloadFrom, downloadTo, resource.getDownloadVersion());
+        storeEntryFields(new CacheEntry(downloadTo, resource.getDownloadVersion()), connection.getContentLength(), connection.getLastModified());
+    }
+
+    private void downloadGZipFile(Resource resource, URLConnection connection, URL downloadFrom, URL downloadTo) throws IOException {
+        downloadFile(resource, connection, downloadFrom);
+
+        uncompressGzip(downloadFrom, downloadTo, resource.getDownloadVersion());
+        storeEntryFields(new CacheEntry(downloadTo, resource.getDownloadVersion()), connection.getContentLength(), connection.getLastModified());
+    }
+
+    private void downloadFile(Resource resource, URLConnection connection, URL downloadLocation) throws IOException {
+        CacheEntry downloadEntry = new CacheEntry(downloadLocation, resource.getDownloadVersion());
+        OutputController.getLogger().log(OutputController.Level.ERROR_DEBUG, "Downloading file: " + downloadLocation + " into: " + downloadEntry.getCacheFile().getCanonicalPath());
+        if (!downloadEntry.isCurrent(connection.getLastModified())) {
+            writeDownloadToFile(resource, downloadLocation, new BufferedInputStream(connection.getInputStream()));
+        } else {
+            resource.setTransferred(CacheUtil.getCacheFile(downloadLocation, resource.getDownloadVersion()).length());
+        }
+
+        storeEntryFields(downloadEntry, connection.getContentLengthLong(), connection.getLastModified());
+    }
+
+    private void storeEntryFields(CacheEntry entry, long contentLength, long lastModified) {
+        entry.lock();
+        try {
+            entry.setRemoteContentLength(contentLength);
+            entry.setLastModified(lastModified);
+            entry.store();
+        } finally {
+            entry.unlock();
+        }
+    }
+
+    private void writeDownloadToFile(Resource resource, URL downloadLocation, InputStream in) throws IOException {
+        byte buf[] = new byte[1024];
+        int rlen;
+        OutputStream out = CacheUtil.getOutputStream(downloadLocation, resource.getDownloadVersion());
+        while (-1 != (rlen = in.read(buf))) {
+            resource.incrementTransferred(rlen);
+            out.write(buf, 0, rlen);
+        }
+
+        in.close();
+        out.close();
+    }
+
+    private void uncompressGzip(URL compressedLocation, URL uncompressedLocation, Version version) throws IOException {
+        OutputController.getLogger().log(OutputController.Level.ERROR_DEBUG, "Extracting gzip: " + compressedLocation + " to " + uncompressedLocation);
+        byte buf[] = new byte[1024];
+        int rlen;
+
+        GZIPInputStream gzInputStream = new GZIPInputStream(new FileInputStream(CacheUtil
+                .getCacheFile(compressedLocation, version)));
+        InputStream inputStream = new BufferedInputStream(gzInputStream);
+
+        BufferedOutputStream outputStream = new BufferedOutputStream(new FileOutputStream(CacheUtil
+                .getCacheFile(uncompressedLocation, version)));
+
+        while (-1 != (rlen = inputStream.read(buf))) {
+            outputStream.write(buf, 0, rlen);
+        }
+
+        outputStream.close();
+        inputStream.close();
+        gzInputStream.close();
+    }
+
+    private void uncompressPackGz(URL compressedLocation, URL uncompressedLocation, Version version) throws IOException {
+        OutputController.getLogger().log(OutputController.Level.ERROR_DEBUG, "Extracting packgz: " + compressedLocation + " to " + uncompressedLocation);
+
+        GZIPInputStream gzInputStream = new GZIPInputStream(new FileInputStream(CacheUtil
+                    .getCacheFile(compressedLocation, version)));
+        InputStream inputStream = new BufferedInputStream(gzInputStream);
+
+        JarOutputStream outputStream = new JarOutputStream(new FileOutputStream(CacheUtil
+                .getCacheFile(uncompressedLocation, version)));
+
+        Pack200.Unpacker unpacker = Pack200.newUnpacker();
+        unpacker.unpack(inputStream, outputStream);
+
+        outputStream.close();
+        inputStream.close();
+        gzInputStream.close();
+    }
+
+    /**
+     * Complex wrapper around return code with utility methods
+     * Default is HTTP_OK
+     */
+    private static class CodeWithRedirect {
+
+        int result = HttpURLConnection.HTTP_OK;
+        URL URL;


More information about the distro-pkg-dev mailing list