/* * Written by Dawid Kurzyniec and released to the public domain, as explained * at http://creativecommons.org/licenses/publicdomain */ package edu.emory.mathcs.util.classloader.jar; import edu.emory.mathcs.util.classloader.ResourceUtils; import edu.emory.mathcs.util.io.RedirectibleInput; import edu.emory.mathcs.util.io.RedirectingInputStream; import java.io.*; 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.security.cert.Certificate; import java.util.HashMap; 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.Manifest; import java.util.zip.ZipEntry; /** * Implementation of {@link edu.emory.mathcs.util.classloader.jar.JarURLConnection.JarOpener} that caches downloaded * JAR files in a local file system. * * @see edu.emory.mathcs.util.classloader.jar.JarURLConnection * @see edu.emory.mathcs.util.classloader.jar.JarURLStreamHandler * * @author Dawid Kurzyniec * @version 1.0 */ @SuppressWarnings("unchecked") public class JarProxy implements JarURLConnection.JarOpener { private final Map cache = new HashMap(); public JarProxy() {} public JarFile openJarFile(java.net.JarURLConnection conn) throws IOException { URL url = conn.getJarFileURL(); CachedJarFile result; synchronized (cache) { result = (CachedJarFile)cache.get(url); } if (result != null) { SecurityManager security = System.getSecurityManager(); if (security != null) { security.checkPermission(result.perm); } return result; } // we have to download and open the JAR; yet it may be a local file try { URI uri = new URI(url.toString()); if (ResourceUtils.isLocalFile(uri)) { File file = new File(uri); Permission perm = new FilePermission(file.getAbsolutePath(), "read"); result = new CachedJarFile(file, perm, false); } } catch (URISyntaxException e) { // apparently not a local file } if (result == null) { final URLConnection jarconn = url.openConnection(); // set up the properties based on the JarURLConnection jarconn.setAllowUserInteraction(conn.getAllowUserInteraction()); jarconn.setDoInput(conn.getDoInput()); jarconn.setDoOutput(conn.getDoOutput()); jarconn.setIfModifiedSince(conn.getIfModifiedSince()); Map map = conn.getRequestProperties(); for (Iterator itr = map.entrySet().iterator(); itr.hasNext();) { Map.Entry entry = (Map.Entry)itr.next(); jarconn.setRequestProperty((String)entry.getKey(), (String)entry.getValue()); } jarconn.setUseCaches(conn.getUseCaches()); final InputStream in = getJarInputStream(jarconn); try { result = (CachedJarFile) AccessController.doPrivileged(new PrivilegedExceptionAction() { public Object run() throws IOException { File file = File.createTempFile("jar_cache", ""); FileOutputStream out = new FileOutputStream(file); try { RedirectibleInput r = new RedirectingInputStream(in, false, false); int len = r.redirectAll(out); out.flush(); if (len == 0) { // e.g. HttpURLConnection: "NOT_MODIFIED" return null; } } finally { out.close(); } return new CachedJarFile(file, jarconn.getPermission(), true); } }); } catch (PrivilegedActionException pae) { throw (IOException)pae.getException(); } finally { in.close(); } } // if no input came (e.g. due to NOT_MODIFIED), do not cache if (result == null) return null; // optimistic locking synchronized (cache) { CachedJarFile asyncResult = (CachedJarFile) cache.get(url); if (asyncResult != null) { // some other thread already retrieved the file; return w/o // security check since we already succeeded in getting past it result.closeCachedFile(); return asyncResult; } cache.put(url, result); return result; } } protected InputStream getJarInputStream(URLConnection conn) throws IOException { return conn.getInputStream(); } protected void clear() { Map cache; synchronized (this.cache) { cache = new HashMap(this.cache); this.cache.clear(); } for (Iterator itr = cache.values().iterator(); itr.hasNext();) { CachedJarFile jfile = (CachedJarFile)itr.next(); try { jfile.closeCachedFile(); } catch (IOException e) { // best-effort } } } protected void finalize() { clear(); } private static class CachedJarFile extends JarFile { final Permission perm; CachedJarFile(File file, Permission perm, boolean tmp) throws IOException { super(file, true, JarFile.OPEN_READ | (tmp ? JarFile.OPEN_DELETE : 0)); this.perm = perm; } public Manifest getManifest() throws IOException { Manifest orig = super.getManifest(); if (orig == null) return null; // make sure the original manifest is not modified Manifest man = new Manifest(); man.getMainAttributes().putAll(orig.getMainAttributes()); for (Iterator itr = orig.getEntries().entrySet().iterator(); itr.hasNext();) { Map.Entry entry = (Map.Entry)itr.next(); man.getEntries().put((String)entry.getKey(), new Attributes((Attributes)entry.getValue())); } return man; } public ZipEntry getEntry(String name) { // super.getJarEntry() would result in stack overflow return super.getEntry(name); } protected void finalize() throws IOException { closeCachedFile(); } protected void closeCachedFile() throws IOException { super.close(); } public void close() throws IOException { // no op; do NOT close file while still in cache } private static class Entry extends JarEntry { JarEntry jentry; Entry(JarEntry jentry) { super(jentry); this.jentry = jentry; } public Certificate[] getCertificates() { Certificate[] certs = jentry.getCertificates(); return (certs == null ? null : (Certificate[])certs.clone()); } public Attributes getAttributes() throws IOException { Attributes attr = jentry.getAttributes(); return (attr == null ? null : new Attributes(attr)); } } } }