You are viewing a plain text version of this content. The canonical link for it is here.
Posted to scm@geronimo.apache.org by da...@apache.org on 2006/04/12 04:48:10 UTC

svn commit: r393370 [2/2] - in /geronimo/sandbox/classloader: ./ src/ src/java/ src/java/org/ src/java/org/apache/ src/java/org/apache/geronimo/ src/java/org/apache/geronimo/kernel/ src/java/org/apache/geronimo/kernel/classloader/ src/java/org/apache/g...

Added: geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jarcache/CachedJarFile.java
URL: http://svn.apache.org/viewcvs/geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jarcache/CachedJarFile.java?rev=393370&view=auto
==============================================================================
--- geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jarcache/CachedJarFile.java (added)
+++ geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jarcache/CachedJarFile.java Tue Apr 11 19:48:07 2006
@@ -0,0 +1,113 @@
+/**
+ *
+ * Copyright 2005 The Apache Software Foundation
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.geronimo.kernel.classloader.jarcache;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.Permission;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.jar.Attributes;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+import java.util.zip.ZipEntry;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class CachedJarFile extends JarFile {
+    private static File DUMMY_JAR_FILE;
+
+    private static synchronized File getDummyJarFile() {
+        if (DUMMY_JAR_FILE == null || !DUMMY_JAR_FILE.isFile() || !DUMMY_JAR_FILE.canRead()) {
+            try {
+                DUMMY_JAR_FILE = File.createTempFile("geronimo-dummy", ".jar");
+                DUMMY_JAR_FILE.deleteOnExit();
+                new JarOutputStream(new FileOutputStream(DUMMY_JAR_FILE), new Manifest()).close();
+            } catch (IOException e) {
+                throw new ExceptionInInitializerError(e);
+            }
+        }
+        return DUMMY_JAR_FILE;
+    }
+
+    private final JarFile actualJarFile;
+    private final Permission permission;
+
+    public CachedJarFile(JarFile jarFile, Permission permission) throws IOException {
+        super(getDummyJarFile());
+        this.actualJarFile = jarFile;
+        this.permission = permission;
+    }
+
+    public Manifest getManifest() throws IOException {
+        Manifest original = actualJarFile.getManifest();
+        if (original == null) {
+            return null;
+        }
+
+        // make sure the original manifest is not modified
+        Manifest copy = new Manifest();
+        copy.getMainAttributes().putAll(original.getMainAttributes());
+        for (Iterator itr = original.getEntries().entrySet().iterator(); itr.hasNext();) {
+            Map.Entry entry = (Map.Entry) itr.next();
+            copy.getEntries().put(entry.getKey(), new Attributes((Attributes) entry.getValue()));
+        }
+        return copy;
+    }
+
+    public Permission getPermission() {
+        return permission;
+    }
+
+    public JarFile getActualJarFile() {
+        return actualJarFile;
+    }
+
+    public String getName() {
+        return actualJarFile.getName();
+    }
+
+    public int size() {
+        return actualJarFile.size();
+    }
+
+    public JarEntry getJarEntry(String name) {
+        return actualJarFile.getJarEntry(name);
+    }
+
+    public ZipEntry getEntry(String name) {
+        return actualJarFile.getEntry(name);
+    }
+
+    public Enumeration entries() {
+        return actualJarFile.entries();
+    }
+
+    public InputStream getInputStream(ZipEntry ze) throws IOException {
+        return actualJarFile.getInputStream(ze);
+    }
+
+    public void close() throws IOException {
+        // no op; do NOT close file while still in cache
+    }
+}

Added: geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jarcache/CachingJarOpener.java
URL: http://svn.apache.org/viewcvs/geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jarcache/CachingJarOpener.java?rev=393370&view=auto
==============================================================================
--- geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jarcache/CachingJarOpener.java (added)
+++ geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jarcache/CachingJarOpener.java Tue Apr 11 19:48:07 2006
@@ -0,0 +1,55 @@
+/**
+ *
+ * Copyright 2005 The Apache Software Foundation
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.geronimo.kernel.classloader.jarcache;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.jar.JarFile;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class CachingJarOpener implements JarOpener {
+    private final JarCache jarCache;
+
+    public CachingJarOpener(JarCache jarCache) {
+        this.jarCache = jarCache;
+    }
+
+    public JarFile openJarFile(java.net.JarURLConnection jarUrlConnection, JarUrlStreamHandler jarUrlStreamHandler) throws IOException {
+        URL baseUrl = getBaseUrl(jarUrlConnection, jarUrlStreamHandler);
+        return jarCache.getJarFile(baseUrl, jarUrlConnection);
+    }
+
+    public static URL getBaseUrl(java.net.JarURLConnection jarUrlConnection, JarUrlStreamHandler jarUrlStreamHandler) throws MalformedURLException {
+        URL url = jarUrlConnection.getURL();
+        String baseText = url.getPath();
+
+        int bangLoc = baseText.lastIndexOf("!");
+        String baseResourceText = baseText.substring(0, bangLoc);
+
+        // if this is a nested jar entry
+        if (baseResourceText.indexOf("!") >= 0) {
+            URL baseResource = new URL("jar", null, -1, baseResourceText, jarUrlStreamHandler);
+            return baseResource;
+        }
+
+        // normal non-nested jar entry
+        return jarUrlConnection.getJarFileURL();
+    }
+}

Added: geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jarcache/Handler.java
URL: http://svn.apache.org/viewcvs/geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jarcache/Handler.java?rev=393370&view=auto
==============================================================================
--- geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jarcache/Handler.java (added)
+++ geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jarcache/Handler.java Tue Apr 11 19:48:07 2006
@@ -0,0 +1,23 @@
+/**
+ *
+ * Copyright 2005 The Apache Software Foundation
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.geronimo.kernel.classloader.jarcache;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class Handler extends JarUrlStreamHandler {
+}

Added: geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jarcache/JarCache.java
URL: http://svn.apache.org/viewcvs/geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jarcache/JarCache.java?rev=393370&view=auto
==============================================================================
--- geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jarcache/JarCache.java (added)
+++ geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jarcache/JarCache.java Tue Apr 11 19:48:07 2006
@@ -0,0 +1,33 @@
+/**
+ *
+ * Copyright 2005 The Apache Software Foundation
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.geronimo.kernel.classloader.jarcache;
+
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.jar.JarFile;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public interface JarCache {
+    void destroy();
+
+    JarFile getJarFile(URL baseUrl) throws IOException;
+
+    JarFile getJarFile(URL baseUrl, URLConnection urlConnectionProperties) throws IOException;
+}

Added: geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jarcache/JarOpener.java
URL: http://svn.apache.org/viewcvs/geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jarcache/JarOpener.java?rev=393370&view=auto
==============================================================================
--- geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jarcache/JarOpener.java (added)
+++ geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jarcache/JarOpener.java Tue Apr 11 19:48:07 2006
@@ -0,0 +1,41 @@
+/**
+ *
+ * Copyright 2005 The Apache Software Foundation
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.geronimo.kernel.classloader.jarcache;
+
+import java.io.IOException;
+import java.util.jar.JarFile;
+
+/**
+ * Abstraction of JAR opener which allows to implement various caching
+ * policies. The opener receives URL pointing to the JAR file, along
+ * with other meta-information, as a JarURLConnection instance. Then it has
+ * to download the file (if it is remote) and open it.
+ *
+ * @version $Rev$ $Date$
+ */
+public interface JarOpener {
+    /**
+     * Given the URL connection (not yet connected), return JarFile
+     * representing the resource. This method is invoked as a part of
+     * the connect method in JarURLConnection.
+     *
+     * @param connection the connection for which the JAR file is required
+     * @return opened JAR file
+     * @throws IOException if I/O error occurs
+     */
+    public JarFile openJarFile(java.net.JarURLConnection connection, JarUrlStreamHandler jarUrlStreamHandler) throws IOException;
+}

Added: geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jarcache/JarUrlConnection.java
URL: http://svn.apache.org/viewcvs/geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jarcache/JarUrlConnection.java?rev=393370&view=auto
==============================================================================
--- geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jarcache/JarUrlConnection.java (added)
+++ geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jarcache/JarUrlConnection.java Tue Apr 11 19:48:07 2006
@@ -0,0 +1,104 @@
+/**
+ *
+ * Copyright 2005 The Apache Software Foundation
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.geronimo.kernel.classloader.jarcache;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.security.Permission;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+/**
+ * Alternative implementation of {@link java.net.JarURLConnection} which
+ * supports a customizable policies for opening the JarFile. This policy can
+ * be used to implement support for nested jars and caching.
+ *
+ * @version $Rev$ $Date$
+ */
+public class JarUrlConnection extends java.net.JarURLConnection {
+    private final JarUrlStreamHandler jarUrlStreamHandler;
+    private boolean connected;
+    private JarFile jarFile;
+    private JarEntry jarEntry;
+    private String entryName;
+
+    /**
+     * Creates JarUrlConnection for a given URL, using specified JAR opener.
+     *
+     * @param url the URL to open connection to
+     */
+    public JarUrlConnection(URL url, JarUrlStreamHandler jarUrlStreamHandler) throws IOException {
+        super(url);
+        this.jarUrlStreamHandler = jarUrlStreamHandler;
+    }
+
+    public synchronized void connect() throws IOException {
+        if (connected) return;
+
+        JarOpener opener = jarUrlStreamHandler.getOpener();
+        jarFile = opener.openJarFile(this, jarUrlStreamHandler);
+        if (jarFile != null) {
+            URL url = getURL();
+            String baseText = url.getPath();
+
+            int bangLoc = baseText.lastIndexOf("!");
+            if (bangLoc <= (baseText.length() - 2) && baseText.charAt(bangLoc + 1) == '/') {
+                if (bangLoc + 2 == baseText.length()) {
+                    entryName = null;
+                } else {
+                    entryName = baseText.substring(bangLoc + 2);
+                }
+            } else {
+                throw new MalformedURLException("No !/ in url: " + url.toExternalForm());
+            }
+
+            if (entryName != null) {
+                jarEntry = jarFile.getJarEntry(entryName);
+                if (jarEntry == null) {
+                    throw new FileNotFoundException("Entry " + entryName + " not found in " + jarFile.getName());
+                }
+            }
+        }
+        connected = true;
+    }
+
+    public synchronized String getEntryName() {
+        return entryName;
+    }
+
+    public synchronized JarFile getJarFile() throws IOException {
+        connect();
+        return jarFile;
+    }
+
+    public synchronized JarEntry getJarEntry() throws IOException {
+        connect();
+        return jarEntry;
+    }
+
+    public synchronized InputStream getInputStream() throws IOException {
+        connect();
+        return jarFile.getInputStream(jarEntry);
+    }
+
+    public Permission getPermission() throws IOException {
+        return getJarFileURL().openConnection().getPermission();
+    }
+}

Added: geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jarcache/JarUrlStreamHandler.java
URL: http://svn.apache.org/viewcvs/geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jarcache/JarUrlStreamHandler.java?rev=393370&view=auto
==============================================================================
--- geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jarcache/JarUrlStreamHandler.java (added)
+++ geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jarcache/JarUrlStreamHandler.java Tue Apr 11 19:48:07 2006
@@ -0,0 +1,114 @@
+/**
+ *
+ * Copyright 2005 The Apache Software Foundation
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.geronimo.kernel.classloader.jarcache;
+
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLStreamHandler;
+
+/**
+ * Alternative implementation of URLStreamHandler for JAR files which
+ * supports a customizable policies for opening the JarFile. This policy can
+ * be used to implement support for nested jars and caching.
+ *
+ * @version $Rev$ $Date$
+ */
+public class JarUrlStreamHandler extends URLStreamHandler {
+    private final JarOpener opener;
+
+    /**
+     * Create new JarUrlStreamHandler that will use its separate URL cache
+     * managed by a newly created {@link CachingJarOpener} instance.
+     */
+    public JarUrlStreamHandler() {
+        this(new CachingJarOpener(new TempJarCache()));
+    }
+
+    /**
+     * Create new JarUrlStreamHandler that will use specified
+     * JarOpener.
+     *
+     * @param opener JAR opener that handles file download and caching
+     */
+    public JarUrlStreamHandler(JarOpener opener) {
+        this.opener = opener;
+    }
+
+    public JarOpener getOpener() {
+        return opener;
+    }
+
+    public URLConnection openConnection(URL url) throws IOException {
+        try {
+            String urlPath = url.getPath();
+            if (urlPath.startsWith("jar:")) {
+                urlPath = urlPath.substring("jar:".length());
+                setURL(url, "jar", "", -1, null, null, urlPath, null, null);
+            }
+            return new JarUrlConnection(url, this);
+        } catch (IOException e) {
+            throw e;
+        } catch (RuntimeException e) {
+            throw e;
+        } catch (Error e) {
+            throw e;
+        } catch (Throwable e) {
+            throw new AssertionError(e);
+        }
+    }
+
+    protected void parseURL(URL url, String spec, int start, int limit) {
+        String specPath = spec.substring(start, limit);
+
+        String urlPath = null;
+
+        if (specPath.charAt(0) == '/') {
+            urlPath = specPath;
+        } else if (specPath.charAt(0) == '!') {
+            String relPath = url.getFile();
+
+            int bangLoc = relPath.lastIndexOf("!");
+
+            if (bangLoc < 0) {
+                urlPath = relPath + specPath;
+            } else {
+                urlPath = relPath.substring(0,
+                        bangLoc) + specPath;
+            }
+        } else {
+            String relPath = url.getFile();
+
+            if (relPath != null) {
+                int lastSlashLoc = relPath.lastIndexOf("/");
+
+                if (lastSlashLoc < 0) {
+                    urlPath = "/" + specPath;
+                } else {
+                    urlPath = relPath.substring(0, lastSlashLoc + 1) + specPath;
+                }
+            } else {
+                urlPath = specPath;
+            }
+        }
+
+        if (urlPath.startsWith("jar:")) {
+            urlPath = urlPath.substring("jar:".length());
+        }
+        setURL(url, "jar", "", -1, null, null, urlPath, null, null);
+    }
+}

Added: geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jarcache/TempJarCache.java
URL: http://svn.apache.org/viewcvs/geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jarcache/TempJarCache.java?rev=393370&view=auto
==============================================================================
--- geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jarcache/TempJarCache.java (added)
+++ geronimo/sandbox/classloader/src/java/org/apache/geronimo/kernel/classloader/jarcache/TempJarCache.java Tue Apr 11 19:48:07 2006
@@ -0,0 +1,272 @@
+/**
+ *
+ * Copyright 2005 The Apache Software Foundation
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.geronimo.kernel.classloader.jarcache;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FilePermission;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.security.AccessController;
+import java.security.Permission;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.jar.JarFile;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class TempJarCache implements JarCache {
+    private final LinkedHashMap cache = new LinkedHashMap();
+    private final ReferenceQueue referenceQueue = new ReferenceQueue();
+    private final File cacheDir;
+
+    public TempJarCache() {
+        String tmpdir = System.getProperty("org.apache.geronimo.tmpdir", System.getProperty("java.io.tmpdir"));
+        this.cacheDir = new File(new File(tmpdir, "geronimo"), "urlcache");
+        verifyCacheDirectory();
+    }
+
+    public TempJarCache(File cacheDir) {
+        this.cacheDir = cacheDir;
+        verifyCacheDirectory();
+    }
+
+    protected void verifyCacheDirectory() {
+        cacheDir.mkdirs();
+        if (!cacheDir.exists()) {
+            throw new IllegalArgumentException("Could not create jar cache directory: " + cacheDir.getAbsolutePath());
+        }
+        if (!cacheDir.isDirectory()) {
+            throw new IllegalArgumentException("Cache directory is not a directory: " + cacheDir.getAbsolutePath());
+        }
+    }
+
+    public void destroy() {
+        Map cache;
+        synchronized (this.cache) {
+            cache = new HashMap(this.cache);
+            this.cache.clear();
+        }
+
+        for (Iterator i = cache.values().iterator(); i.hasNext();) {
+            WeakReference weakReference = (WeakReference) i.next();
+            weakReference.clear();
+        }
+    }
+
+    public JarFile getJarFile(URL baseUrl) throws IOException {
+        return getJarFile(baseUrl, null);
+    }
+
+    public JarFile getJarFile(URL baseUrl, URLConnection urlConnectionProperties) throws IOException {
+        // first you get to do some cleaning
+        reapFiles();
+
+        // first check the cache
+        CachedJarFile cachedJarFile = null;
+        synchronized (cache) {
+            WeakReference reference = (WeakReference) cache.get(baseUrl);
+            if (reference != null) {
+                cachedJarFile = (CachedJarFile) reference.get();
+            }
+        }
+        if (cachedJarFile != null) {
+            SecurityManager security = System.getSecurityManager();
+            if (security != null) {
+                security.checkPermission(cachedJarFile.getPermission());
+            }
+            return cachedJarFile;
+        }
+
+        // check if the baseUrl is a jar on the local file system
+        try {
+            URI uri = new URI(baseUrl.toString());
+            if (isLocalFile(uri)) {
+                File file = new File(uri);
+                Permission perm = new FilePermission(file.getAbsolutePath(), "read");
+                cachedJarFile = new CachedJarFile(new JarFile(file), perm);
+            }
+        }
+        catch (URISyntaxException e) {
+            // apparently not a local file
+        }
+
+        // if not a local jar, download to the cache
+        if (cachedJarFile == null) {
+            URLConnection urlConnection = baseUrl.openConnection();
+
+            if (urlConnectionProperties != null) {
+                // set up the properties based on the JarURLConnection
+                urlConnection.setAllowUserInteraction(urlConnectionProperties.getAllowUserInteraction());
+                urlConnection.setDoInput(urlConnectionProperties.getDoInput());
+                urlConnection.setDoOutput(urlConnectionProperties.getDoOutput());
+                urlConnection.setIfModifiedSince(urlConnectionProperties.getIfModifiedSince());
+
+                Map map = urlConnectionProperties.getRequestProperties();
+                for (Iterator itr = map.entrySet().iterator(); itr.hasNext();) {
+                    Map.Entry entry = (Map.Entry) itr.next();
+                    urlConnection.setRequestProperty((String) entry.getKey(), (String) entry.getValue());
+                }
+
+                urlConnection.setUseCaches(urlConnectionProperties.getUseCaches());
+            }
+
+            cachedJarFile = cacheFile(urlConnection);
+        }
+
+        // if no input came (e.g. due to NOT_MODIFIED), do not cache
+        if (cachedJarFile == null) return null;
+
+        // optimistic locking
+        synchronized (cache) {
+            WeakReference reference = (WeakReference) cache.get(baseUrl);
+            if (reference != null) {
+                CachedJarFile asyncResult = (CachedJarFile) reference.get();
+                if (asyncResult != null) {
+                    // some other thread already retrieved the file; return w/o
+                    // security check since we already succeeded in getting past it
+                    cachedJarFile.getActualJarFile().close();
+                    return asyncResult;
+                }
+            }
+            cache.put(baseUrl, new WeakReference(cachedJarFile, referenceQueue));
+            return cachedJarFile;
+        }
+    }
+
+    public void reapFiles() {
+        for (Reference reference = referenceQueue.poll(); reference != null; reference = referenceQueue.poll()) {
+            reference.clear();
+        }
+    }
+
+    private CachedJarFile cacheFile(final URLConnection urlConnection) throws IOException {
+        CachedJarFile cachedJarFile = null;
+        final InputStream in = urlConnection.getInputStream();
+
+        try {
+            cachedJarFile = (CachedJarFile) AccessController.doPrivileged(new PrivilegedExceptionAction() {
+                public Object run() throws IOException {
+                    File file = File.createTempFile("jar_cache", "", cacheDir);
+                    FileOutputStream out = null;
+                    try {
+                        out = new FileOutputStream(file);
+                        writeAll(in, out);
+                        safeClose(out);
+                        out = null;
+
+                        JarFile jarFile = new JarFile(file, true, JarFile.OPEN_READ | JarFile.OPEN_DELETE);
+                        return new CachedJarFile(jarFile, urlConnection.getPermission());
+                    } catch (IOException e) {
+                        safeClose(out);
+                        file.delete();
+                        throw e;
+                    }
+                }
+            });
+        }
+        catch (PrivilegedActionException pae) {
+            throw (IOException) pae.getException();
+        } finally {
+            safeClose(in);
+        }
+        return cachedJarFile;
+    }
+
+    public class CacheJarFileReference extends WeakReference {
+        private final JarFile actualJarFile;
+
+        public CacheJarFileReference(CachedJarFile cachedJarFile, ReferenceQueue q) {
+            super(cachedJarFile, q);
+            actualJarFile = cachedJarFile.getActualJarFile();
+        }
+
+        public void clear() {
+            try {
+                actualJarFile.close();
+            } catch (IOException ignroed) {
+            }
+            super.clear();
+        }
+
+        public JarFile getActualJarFile() {
+            return actualJarFile;
+        }
+    }
+
+    private static void safeClose(OutputStream thing) {
+        if (thing != null) {
+            try {
+                thing.close();
+            } catch (IOException ignored) {
+            }
+        }
+    }
+
+    private static void safeClose(InputStream thing) {
+        if (thing != null) {
+            try {
+                thing.close();
+            } catch (IOException ignored) {
+            }
+        }
+    }
+
+    private static void writeAll(InputStream in, OutputStream out) throws IOException {
+        byte[] buffer = new byte[4096];
+        int count;
+        while ((count = in.read(buffer)) > 0) {
+            out.write(buffer, 0, count);
+        }
+        out.flush();
+    }
+
+    private static boolean isLocalFile(URI uri) {
+        if (!uri.isAbsolute()) {
+            return false;
+        }
+        if (uri.isOpaque()) {
+            return false;
+        }
+        if (!"file".equalsIgnoreCase(uri.getScheme())) {
+            return false;
+        }
+        if (uri.getAuthority() != null) {
+            return false;
+        }
+        if (uri.getFragment() != null) {
+            return false;
+        }
+        if (uri.getQuery() != null) {
+            return false;
+        }
+        return uri.getPath().length() > 0;
+    }
+}

Added: geronimo/sandbox/classloader/src/test/org/apache/geronimo/kernel/classloader/jar/JarUrlTest.java
URL: http://svn.apache.org/viewcvs/geronimo/sandbox/classloader/src/test/org/apache/geronimo/kernel/classloader/jar/JarUrlTest.java?rev=393370&view=auto
==============================================================================
--- geronimo/sandbox/classloader/src/test/org/apache/geronimo/kernel/classloader/jar/JarUrlTest.java (added)
+++ geronimo/sandbox/classloader/src/test/org/apache/geronimo/kernel/classloader/jar/JarUrlTest.java Tue Apr 11 19:48:07 2006
@@ -0,0 +1,147 @@
+/**
+ *
+ * Copyright 2005 The Apache Software Foundation
+ *
+ *  Licensed under the Apache License, Version 2.0 (the "License");
+ *  you may not use this file except in compliance with the License.
+ *  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.geronimo.kernel.classloader.jar;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.io.OutputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.jar.JarFile;
+import java.util.jar.JarOutputStream;
+import java.util.zip.ZipEntry;
+
+import junit.framework.TestCase;
+import org.apache.geronimo.kernel.classloader.jarcache.CachingJarOpener;
+import org.apache.geronimo.kernel.classloader.jarcache.JarOpener;
+import org.apache.geronimo.kernel.classloader.jarcache.JarUrlConnection;
+import org.apache.geronimo.kernel.classloader.jarcache.JarUrlStreamHandler;
+import org.apache.geronimo.kernel.classloader.jarcache.TempJarCache;
+
+/**
+ * @version $Rev$ $Date$
+ */
+public class JarUrlTest extends TestCase {
+    private static final String TEXT = "This is a test";
+    private JarOpener jarOpener;
+    private File outerFile;
+    private File nestedFile;
+    private File cacheDir;
+    private File doubleNestedFile;
+
+    public void testUnnestedEntry() throws Exception {
+        URL url = new URL("jar", null, -1, outerFile.toURL().toString() + "!/foo", new JarUrlStreamHandler(jarOpener));
+        URLConnection urlConnection = url.openConnection();
+        assertTrue(urlConnection instanceof JarUrlConnection);
+
+        String text = readLine(urlConnection);
+
+        assertEquals(TEXT + "-foo", text);
+    }
+
+    public void testNestedEntry() throws Exception {
+        URL url = new URL("jar", null, -1, outerFile.toURL().toString() + "!/nested.jar!/bar", new JarUrlStreamHandler(jarOpener));
+        URLConnection urlConnection = url.openConnection();
+        assertTrue(urlConnection instanceof JarUrlConnection);
+
+        String text = readLine(urlConnection);
+
+        assertEquals(TEXT + "-bar", text);
+    }
+
+    public void testDoubleNestedEntry() throws Exception {
+        URL url = new URL("jar", null, -1, outerFile.toURL().toString() + "!/nested.jar!/doubleNested.jar!/baz", new JarUrlStreamHandler(jarOpener));
+        URLConnection urlConnection = url.openConnection();
+        assertTrue(urlConnection instanceof JarUrlConnection);
+
+        String text = readLine(urlConnection);
+
+        assertEquals(TEXT + "-baz", text);
+    }
+
+    private String readLine(URLConnection urlConnection) throws IOException {
+        urlConnection.connect();
+        InputStream in = urlConnection.getInputStream();
+        LineNumberReader reader = new LineNumberReader(new InputStreamReader(in));
+        String text = reader.readLine();
+        in.close();
+        return text;
+    }
+
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        cacheDir = new File("target/jarcache");
+        jarOpener = new CachingJarOpener(new TempJarCache(cacheDir));
+
+        doubleNestedFile = new File("target/doubleNested.jar");
+        JarFile doubleNestedJarFile = createJarFile(doubleNestedFile, "baz", null);
+        assertNotNull(doubleNestedJarFile.getEntry("baz"));
+
+        nestedFile = new File("target/nested.jar");
+        JarFile nestedJarFile = createJarFile(nestedFile, "bar", doubleNestedFile);
+        assertNotNull(nestedJarFile.getEntry("bar"));
+        assertNotNull(nestedJarFile.getEntry("doubleNested.jar"));
+
+        outerFile = new File("target/outer.jar");
+        JarFile outterJarFile = createJarFile(outerFile, "foo", nestedFile);
+        assertNotNull(outterJarFile.getEntry("foo"));
+        assertNotNull(outterJarFile.getEntry("nested.jar"));
+    }
+
+    private JarFile createJarFile(File file, String name, File nestedFile) throws IOException {
+        JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(file));
+        jarOutputStream.putNextEntry(new ZipEntry(name));
+        jarOutputStream.write((TEXT + "-" + name).getBytes());
+        jarOutputStream.closeEntry();
+
+        if (nestedFile != null) {
+            jarOutputStream.putNextEntry(new ZipEntry(nestedFile.getName()));
+            FileInputStream in = new FileInputStream(nestedFile);
+            writeAll(in, jarOutputStream);
+            in.close();
+            jarOutputStream.closeEntry();
+        }
+
+        jarOutputStream.flush();
+        jarOutputStream.close();
+
+        return new JarFile(file);
+    }
+
+    private static void writeAll(InputStream in, OutputStream out) throws IOException {
+        byte[] buffer = new byte[4096];
+        int count;
+        while ((count = in.read(buffer)) > 0) {
+            out.write(buffer, 0, count);
+        }
+        out.flush();
+    }
+
+    protected void tearDown() throws Exception {
+        doubleNestedFile.delete();
+        nestedFile.delete();
+        outerFile.delete();
+        cacheDir.delete();
+        super.tearDown();
+    }
+}