diff options
Diffstat (limited to 'libjava/classpath/java/net/URLClassLoader.java')
-rw-r--r-- | libjava/classpath/java/net/URLClassLoader.java | 1171 |
1 files changed, 1171 insertions, 0 deletions
diff --git a/libjava/classpath/java/net/URLClassLoader.java b/libjava/classpath/java/net/URLClassLoader.java new file mode 100644 index 0000000..8ebc3e0 --- /dev/null +++ b/libjava/classpath/java/net/URLClassLoader.java @@ -0,0 +1,1171 @@ +/* URLClassLoader.java -- ClassLoader that loads classes from one or more URLs + Copyright (C) 1999, 2000, 2001, 2002, 2003, 2004, 2005 + Free Software Foundation, Inc. + +This file is part of GNU Classpath. + +GNU Classpath is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU Classpath is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU Classpath; see the file COPYING. If not, write to the +Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301 USA. + +Linking this library statically or dynamically with other modules is +making a combined work based on this library. Thus, the terms and +conditions of the GNU General Public License cover the whole +combination. + +As a special exception, the copyright holders of this library give you +permission to link this library with independent modules to produce an +executable, regardless of the license terms of these independent +modules, and to copy and distribute the resulting executable under +terms of your choice, provided that you also meet, for each linked +independent module, the terms and conditions of the license of that +module. An independent module is a module which is not derived from +or based on this library. If you modify this library, you may extend +this exception to your version of the library, but you are not +obligated to do so. If you do not wish to do so, delete this +exception statement from your version. */ + + +package java.net; + +import java.io.ByteArrayOutputStream; +import java.io.EOFException; +import java.io.File; +import java.io.FileInputStream; +import java.io.FilePermission; +import java.io.IOException; +import java.io.InputStream; +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.CodeSource; +import java.security.PermissionCollection; +import java.security.PrivilegedAction; +import java.security.SecureClassLoader; +import java.security.cert.Certificate; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.StringTokenizer; +import java.util.Vector; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.Manifest; + + +/** + * A secure class loader that can load classes and resources from + * multiple locations. Given an array of <code>URL</code>s this class + * loader will retrieve classes and resources by fetching them from + * possible remote locations. Each <code>URL</code> is searched in + * order in which it was added. If the file portion of the + * <code>URL</code> ends with a '/' character then it is interpreted + * as a base directory, otherwise it is interpreted as a jar file from + * which the classes/resources are resolved. + * + * <p>New instances can be created by two static + * <code>newInstance()</code> methods or by three public + * contructors. Both ways give the option to supply an initial array + * of <code>URL</code>s and (optionally) a parent classloader (that is + * different from the standard system class loader).</p> + * + * <p>Normally creating a <code>URLClassLoader</code> throws a + * <code>SecurityException</code> if a <code>SecurityManager</code> is + * installed and the <code>checkCreateClassLoader()</code> method does + * not return true. But the <code>newInstance()</code> methods may be + * used by any code as long as it has permission to acces the given + * <code>URL</code>s. <code>URLClassLoaders</code> created by the + * <code>newInstance()</code> methods also explicitly call the + * <code>checkPackageAccess()</code> method of + * <code>SecurityManager</code> if one is installed before trying to + * load a class. Note that only subclasses of + * <code>URLClassLoader</code> can add new URLs after the + * URLClassLoader had been created. But it is always possible to get + * an array of all URLs that the class loader uses to resolve classes + * and resources by way of the <code>getURLs()</code> method.</p> + * + * <p>Open issues: + * <ul> + * + * <li>Should the URLClassLoader actually add the locations found in + * the manifest or is this the responsibility of some other + * loader/(sub)class? (see <a + * href="http://java.sun.com/products/jdk/1.4/docs/guide/extensions/spec.html"> + * Extension Mechanism Architecture - Bundles Extensions</a>)</li> + * + * <li>How does <code>definePackage()</code> and sealing work + * precisely?</li> + * + * <li>We save and use the security context (when a created by + * <code>newInstance()</code> but do we have to use it in more + * places?</li> + * + * <li>The use of <code>URLStreamHandler</code>s has not been tested.</li> + * + * </ul> + * </p> + * + * @since 1.2 + * + * @author Mark Wielaard (mark@klomp.org) + * @author Wu Gansha (gansha.wu@intel.com) + */ +public class URLClassLoader extends SecureClassLoader +{ + // Class Variables + + /** + * A global cache to store mappings between URLLoader and URL, + * so we can avoid do all the homework each time the same URL + * comes. + * XXX - Keeps these loaders forever which prevents garbage collection. + */ + private static HashMap urlloaders = new HashMap(); + + /** + * A cache to store mappings between handler factory and its + * private protocol handler cache (also a HashMap), so we can avoid + * create handlers each time the same protocol comes. + */ + private static HashMap factoryCache = new HashMap(5); + + // Instance variables + + /** Locations to load classes from */ + private final Vector urls = new Vector(); + + /** + * Store pre-parsed information for each url into this vector: each + * element is a URL loader. A jar file has its own class-path + * attribute which adds to the URLs that will be searched, but this + * does not add to the list of urls. + */ + private final Vector urlinfos = new Vector(); + + /** Factory used to get the protocol handlers of the URLs */ + private final URLStreamHandlerFactory factory; + + /** + * The security context when created from <code>newInstance()</code> + * or null when created through a normal constructor or when no + * <code>SecurityManager</code> was installed. + */ + private final AccessControlContext securityContext; + + // Helper classes + + /** + * A <code>URLLoader</code> contains all logic to load resources from a + * given base <code>URL</code>. + */ + abstract static class URLLoader + { + /** + * Our classloader to get info from if needed. + */ + final URLClassLoader classloader; + + /** + * The base URL from which all resources are loaded. + */ + final URL baseURL; + + /** + * A <code>CodeSource</code> without any associated certificates. + * It is common for classes to not have certificates associated + * with them. If they come from the same <code>URLLoader</code> + * then it is safe to share the associated <code>CodeSource</code> + * between them since <code>CodeSource</code> is immutable. + */ + final CodeSource noCertCodeSource; + + URLLoader(URLClassLoader classloader, URL baseURL) + { + this(classloader, baseURL, baseURL); + } + + URLLoader(URLClassLoader classloader, URL baseURL, URL overrideURL) + { + this.classloader = classloader; + this.baseURL = baseURL; + this.noCertCodeSource = new CodeSource(overrideURL, null); + } + + /** + * Returns a <code>Resource</code> loaded by this + * <code>URLLoader</code>, or <code>null</code> when no + * <code>Resource</code> with the given name exists. + */ + abstract Resource getResource(String s); + + /** + * Returns the <code>Manifest</code> associated with the + * <code>Resource</code>s loaded by this <code>URLLoader</code> or + * <code>null</code> there is no such <code>Manifest</code>. + */ + Manifest getManifest() + { + return null; + } + + Vector getClassPath() + { + return null; + } + } + + /** + * A <code>Resource</code> represents a resource in some + * <code>URLLoader</code>. It also contains all information (e.g., + * <code>URL</code>, <code>CodeSource</code>, <code>Manifest</code> and + * <code>InputStream</code>) that is necessary for loading resources + * and creating classes from a <code>URL</code>. + */ + abstract static class Resource + { + final URLLoader loader; + final String name; + + Resource(URLLoader loader, String name) + { + this.loader = loader; + this.name = name; + } + + /** + * Returns the non-null <code>CodeSource</code> associated with + * this resource. + */ + CodeSource getCodeSource() + { + Certificate[] certs = getCertificates(); + if (certs == null) + return loader.noCertCodeSource; + else + return new CodeSource(loader.baseURL, certs); + } + + /** + * Returns <code>Certificates</code> associated with this + * resource, or null when there are none. + */ + Certificate[] getCertificates() + { + return null; + } + + /** + * Return a <code>URL</code> that can be used to access this resource. + */ + abstract URL getURL(); + + /** + * Returns the size of this <code>Resource</code> in bytes or + * <code>-1</code> when unknown. + */ + abstract int getLength(); + + /** + * Returns the non-null <code>InputStream</code> through which + * this resource can be loaded. + */ + abstract InputStream getInputStream() throws IOException; + } + + /** + * A <code>JarURLLoader</code> is a type of <code>URLLoader</code> + * only loading from jar url. + */ + static final class JarURLLoader extends URLLoader + { + final JarFile jarfile; // The jar file for this url + final URL baseJarURL; // Base jar: url for all resources loaded from jar + + Vector classPath; // The "Class-Path" attribute of this Jar's manifest + + public JarURLLoader(URLClassLoader classloader, URL baseURL) + { + super(classloader, baseURL); + + // Cache url prefix for all resources in this jar url. + String external = baseURL.toExternalForm(); + StringBuffer sb = new StringBuffer(external.length() + 6); + sb.append("jar:"); + sb.append(external); + sb.append("!/"); + String jarURL = sb.toString(); + + this.classPath = null; + URL baseJarURL = null; + JarFile jarfile = null; + try + { + baseJarURL = + new URL(null, jarURL, classloader.getURLStreamHandler("jar")); + + jarfile = + ((JarURLConnection) baseJarURL.openConnection()).getJarFile(); + + Manifest manifest; + Attributes attributes; + String classPathString; + + if ((manifest = jarfile.getManifest()) != null + && (attributes = manifest.getMainAttributes()) != null + && ((classPathString + = attributes.getValue(Attributes.Name.CLASS_PATH)) + != null)) + { + this.classPath = new Vector(); + + StringTokenizer st = new StringTokenizer(classPathString, " "); + while (st.hasMoreElements ()) + { + String e = st.nextToken (); + try + { + URL url = new URL(baseURL, e); + this.classPath.add(url); + } + catch (java.net.MalformedURLException xx) + { + // Give up + } + } + } + } + catch (IOException ioe) + { + /* ignored */ + } + + this.baseJarURL = baseJarURL; + this.jarfile = jarfile; + } + + /** get resource with the name "name" in the jar url */ + Resource getResource(String name) + { + if (jarfile == null) + return null; + + if (name.startsWith("/")) + name = name.substring(1); + + JarEntry je = jarfile.getJarEntry(name); + if (je != null) + return new JarURLResource(this, name, je); + else + return null; + } + + Manifest getManifest() + { + try + { + return (jarfile == null) ? null : jarfile.getManifest(); + } + catch (IOException ioe) + { + return null; + } + } + + Vector getClassPath() + { + return classPath; + } + } + + static final class JarURLResource extends Resource + { + private final JarEntry entry; + + JarURLResource(JarURLLoader loader, String name, JarEntry entry) + { + super(loader, name); + this.entry = entry; + } + + InputStream getInputStream() throws IOException + { + return ((JarURLLoader) loader).jarfile.getInputStream(entry); + } + + int getLength() + { + return (int) entry.getSize(); + } + + Certificate[] getCertificates() + { + // We have to get the entry from the jar file again, because the + // certificates will not be available until the entire entry has + // been read. + return ((JarEntry) ((JarURLLoader) loader).jarfile.getEntry(name)) + .getCertificates(); + } + + URL getURL() + { + try + { + return new URL(((JarURLLoader) loader).baseJarURL, name, + loader.classloader.getURLStreamHandler("jar")); + } + catch (MalformedURLException e) + { + InternalError ie = new InternalError(); + ie.initCause(e); + throw ie; + } + } + } + + /** + * Loader for remote directories. + */ + static final class RemoteURLLoader extends URLLoader + { + private final String protocol; + + RemoteURLLoader(URLClassLoader classloader, URL url) + { + super(classloader, url); + protocol = url.getProtocol(); + } + + /** + * Get a remote resource. + * Returns null if no such resource exists. + */ + Resource getResource(String name) + { + try + { + URL url = + new URL(baseURL, name, classloader.getURLStreamHandler(protocol)); + URLConnection connection = url.openConnection(); + + // Open the connection and check the stream + // just to be sure it exists. + int length = connection.getContentLength(); + InputStream stream = connection.getInputStream(); + + // We can do some extra checking if it is a http request + if (connection instanceof HttpURLConnection) + { + int response = + ((HttpURLConnection) connection).getResponseCode(); + if (response / 100 != 2) + return null; + } + + if (stream != null) + return new RemoteResource(this, name, url, stream, length); + else + return null; + } + catch (IOException ioe) + { + return null; + } + } + } + + /** + * A resource from some remote location. + */ + static final class RemoteResource extends Resource + { + private final URL url; + private final InputStream stream; + private final int length; + + RemoteResource(RemoteURLLoader loader, String name, URL url, + InputStream stream, int length) + { + super(loader, name); + this.url = url; + this.stream = stream; + this.length = length; + } + + InputStream getInputStream() throws IOException + { + return stream; + } + + public int getLength() + { + return length; + } + + public URL getURL() + { + return url; + } + } + + /** + * A <code>FileURLLoader</code> is a type of <code>URLLoader</code> + * only loading from file url. + */ + static final class FileURLLoader extends URLLoader + { + File dir; //the file for this file url + + FileURLLoader(URLClassLoader classloader, URL url) + { + super(classloader, url); + dir = new File(baseURL.getFile()); + } + + /** get resource with the name "name" in the file url */ + Resource getResource(String name) + { + File file = new File(dir, name); + if (file.exists() && !file.isDirectory()) + return new FileResource(this, name, file); + return null; + } + } + + static final class FileResource extends Resource + { + final File file; + + FileResource(FileURLLoader loader, String name, File file) + { + super(loader, name); + this.file = file; + } + + InputStream getInputStream() throws IOException + { + return new FileInputStream(file); + } + + public int getLength() + { + return (int) file.length(); + } + + public URL getURL() + { + try + { + return new URL(loader.baseURL, name, + loader.classloader.getURLStreamHandler("file")); + } + catch (MalformedURLException e) + { + InternalError ie = new InternalError(); + ie.initCause(e); + throw ie; + } + } + } + + // Constructors + + /** + * Creates a URLClassLoader that gets classes from the supplied URLs. + * To determine if this classloader may be created the constructor of + * the super class (<code>SecureClassLoader</code>) is called first, which + * can throw a SecurityException. Then the supplied URLs are added + * in the order given to the URLClassLoader which uses these URLs to + * load classes and resources (after using the default parent ClassLoader). + * + * @exception SecurityException if the SecurityManager disallows the + * creation of a ClassLoader. + * @param urls Locations that should be searched by this ClassLoader when + * resolving Classes or Resources. + * @see SecureClassLoader + */ + public URLClassLoader(URL[] urls) throws SecurityException + { + super(); + this.factory = null; + this.securityContext = null; + addURLs(urls); + } + + /** + * Creates a <code>URLClassLoader</code> that gets classes from the supplied + * <code>URL</code>s. + * To determine if this classloader may be created the constructor of + * the super class (<code>SecureClassLoader</code>) is called first, which + * can throw a SecurityException. Then the supplied URLs are added + * in the order given to the URLClassLoader which uses these URLs to + * load classes and resources (after using the supplied parent ClassLoader). + * @exception SecurityException if the SecurityManager disallows the + * creation of a ClassLoader. + * @exception SecurityException + * @param urls Locations that should be searched by this ClassLoader when + * resolving Classes or Resources. + * @param parent The parent class loader used before trying this class + * loader. + * @see SecureClassLoader + */ + public URLClassLoader(URL[] urls, ClassLoader parent) + throws SecurityException + { + super(parent); + this.factory = null; + this.securityContext = null; + addURLs(urls); + } + + // Package-private to avoid a trampoline constructor. + /** + * Package-private constructor used by the static + * <code>newInstance(URL[])</code> method. Creates an + * <code>URLClassLoader</code> with the given parent but without any + * <code>URL</code>s yet. This is used to bypass the normal security + * check for creating classloaders, but remembers the security + * context which will be used when defining classes. The + * <code>URL</code>s to load from must be added by the + * <code>newInstance()</code> method in the security context of the + * caller. + * + * @param securityContext the security context of the unprivileged code. + */ + URLClassLoader(ClassLoader parent, AccessControlContext securityContext) + { + super(parent); + this.factory = null; + this.securityContext = securityContext; + } + + /** + * Creates a URLClassLoader that gets classes from the supplied URLs. + * To determine if this classloader may be created the constructor of + * the super class (<CODE>SecureClassLoader</CODE>) is called first, which + * can throw a SecurityException. Then the supplied URLs are added + * in the order given to the URLClassLoader which uses these URLs to + * load classes and resources (after using the supplied parent ClassLoader). + * It will use the supplied <CODE>URLStreamHandlerFactory</CODE> to get the + * protocol handlers of the supplied URLs. + * @exception SecurityException if the SecurityManager disallows the + * creation of a ClassLoader. + * @exception SecurityException + * @param urls Locations that should be searched by this ClassLoader when + * resolving Classes or Resources. + * @param parent The parent class loader used before trying this class + * loader. + * @param factory Used to get the protocol handler for the URLs. + * @see SecureClassLoader + */ + public URLClassLoader(URL[] urls, ClassLoader parent, + URLStreamHandlerFactory factory) + throws SecurityException + { + super(parent); + this.securityContext = null; + this.factory = factory; + addURLs(urls); + + // If this factory is still not in factoryCache, add it, + // since we only support three protocols so far, 5 is enough + // for cache initial size + synchronized (factoryCache) + { + if (factory != null && factoryCache.get(factory) == null) + factoryCache.put(factory, new HashMap(5)); + } + } + + // Methods + + /** + * Adds a new location to the end of the internal URL store. + * @param newUrl the location to add + */ + protected void addURL(URL newUrl) + { + urls.add(newUrl); + addURLImpl(newUrl); + } + + private void addURLImpl(URL newUrl) + { + synchronized (urlloaders) + { + if (newUrl == null) + return; // Silently ignore... + + // Reset the toString() value. + thisString = null; + + // Check global cache to see if there're already url loader + // for this url. + URLLoader loader = (URLLoader) urlloaders.get(newUrl); + if (loader == null) + { + String file = newUrl.getFile(); + String protocol = newUrl.getProtocol(); + + // Check that it is not a directory + if (! (file.endsWith("/") || file.endsWith(File.separator))) + loader = new JarURLLoader(this, newUrl); + else if ("file".equals(protocol)) + loader = new FileURLLoader(this, newUrl); + else + loader = new RemoteURLLoader(this, newUrl); + + // Cache it. + urlloaders.put(newUrl, loader); + } + + urlinfos.add(loader); + + Vector extraUrls = loader.getClassPath(); + if (extraUrls != null) + { + Iterator it = extraUrls.iterator(); + while (it.hasNext()) + { + URL url = (URL)it.next(); + URLLoader extraLoader = (URLLoader) urlloaders.get(url); + if (! urlinfos.contains (extraLoader)) + addURLImpl(url); + } + } + + } + } + + /** + * Adds an array of new locations to the end of the internal URL store. + * @param newUrls the locations to add + */ + private void addURLs(URL[] newUrls) + { + for (int i = 0; i < newUrls.length; i++) + addURL(newUrls[i]); + } + + /** + * Defines a Package based on the given name and the supplied manifest + * information. The manifest indicates the tile, version and + * vendor information of the specification and implementation and wheter the + * package is sealed. If the Manifest indicates that the package is sealed + * then the Package will be sealed with respect to the supplied URL. + * + * @exception IllegalArgumentException If this package name already exists + * in this class loader + * @param name The name of the package + * @param manifest The manifest describing the specification, + * implementation and sealing details of the package + * @param url the code source url to seal the package + * @return the defined Package + */ + protected Package definePackage(String name, Manifest manifest, URL url) + throws IllegalArgumentException + { + Attributes attr = manifest.getMainAttributes(); + String specTitle = attr.getValue(Attributes.Name.SPECIFICATION_TITLE); + String specVersion = attr.getValue(Attributes.Name.SPECIFICATION_VERSION); + String specVendor = attr.getValue(Attributes.Name.SPECIFICATION_VENDOR); + String implTitle = attr.getValue(Attributes.Name.IMPLEMENTATION_TITLE); + String implVersion = attr.getValue(Attributes.Name.IMPLEMENTATION_VERSION); + String implVendor = attr.getValue(Attributes.Name.IMPLEMENTATION_VENDOR); + + // Look if the Manifest indicates that this package is sealed + // XXX - most likely not completely correct! + // Shouldn't we also check the sealed attribute of the complete jar? + // http://java.sun.com/products/jdk/1.4/docs/guide/extensions/spec.html#bundled + // But how do we get that jar manifest here? + String sealed = attr.getValue(Attributes.Name.SEALED); + if ("false".equals(sealed)) + // make sure that the URL is null so the package is not sealed + url = null; + + return definePackage(name, specTitle, specVersion, specVendor, implTitle, + implVersion, implVendor, url); + } + + /** + * Finds (the first) class by name from one of the locations. The locations + * are searched in the order they were added to the URLClassLoader. + * + * @param className the classname to find + * @exception ClassNotFoundException when the class could not be found or + * loaded + * @return a Class object representing the found class + */ + protected Class findClass(final String className) + throws ClassNotFoundException + { + // Just try to find the resource by the (almost) same name + String resourceName = className.replace('.', '/') + ".class"; + Resource resource = findURLResource(resourceName); + if (resource == null) + throw new ClassNotFoundException(className + " not found in " + this); + + // Try to read the class data, create the CodeSource, Package and + // construct the class (and watch out for those nasty IOExceptions) + try + { + byte[] data; + InputStream in = resource.getInputStream(); + try + { + int length = resource.getLength(); + if (length != -1) + { + // We know the length of the data. + // Just try to read it in all at once + data = new byte[length]; + int pos = 0; + while (length - pos > 0) + { + int len = in.read(data, pos, length - pos); + if (len == -1) + throw new EOFException("Not enough data reading from: " + + in); + pos += len; + } + } + else + { + // We don't know the data length. + // Have to read it in chunks. + ByteArrayOutputStream out = new ByteArrayOutputStream(4096); + byte[] b = new byte[4096]; + int l = 0; + while (l != -1) + { + l = in.read(b); + if (l != -1) + out.write(b, 0, l); + } + data = out.toByteArray(); + } + } + finally + { + in.close(); + } + final byte[] classData = data; + + // Now get the CodeSource + final CodeSource source = resource.getCodeSource(); + + // Find out package name + String packageName = null; + int lastDot = className.lastIndexOf('.'); + if (lastDot != -1) + packageName = className.substring(0, lastDot); + + if (packageName != null && getPackage(packageName) == null) + { + // define the package + Manifest manifest = resource.loader.getManifest(); + if (manifest == null) + definePackage(packageName, null, null, null, null, null, null, + null); + else + definePackage(packageName, manifest, resource.loader.baseURL); + } + + // And finally construct the class! + SecurityManager sm = System.getSecurityManager(); + Class result = null; + if (sm != null && securityContext != null) + { + result = (Class)AccessController.doPrivileged + (new PrivilegedAction() + { + public Object run() + { + return defineClass(className, classData, + 0, classData.length, + source); + } + }, securityContext); + } + else + result = defineClass(className, classData, 0, classData.length, source); + + super.setSigners(result, resource.getCertificates()); + return result; + } + catch (IOException ioe) + { + ClassNotFoundException cnfe; + cnfe = new ClassNotFoundException(className + " not found in " + this); + cnfe.initCause(ioe); + throw cnfe; + } + } + + // Cached String representation of this URLClassLoader + private String thisString; + + /** + * Returns a String representation of this URLClassLoader giving the + * actual Class name, the URLs that are searched and the parent + * ClassLoader. + */ + public String toString() + { + synchronized (urlloaders) + { + if (thisString == null) + { + StringBuffer sb = new StringBuffer(); + sb.append(this.getClass().getName()); + sb.append("{urls=[" ); + URL[] thisURLs = getURLs(); + for (int i = 0; i < thisURLs.length; i++) + { + sb.append(thisURLs[i]); + if (i < thisURLs.length - 1) + sb.append(','); + } + sb.append(']'); + sb.append(", parent="); + sb.append(getParent()); + sb.append('}'); + thisString = sb.toString(); + } + return thisString; + } + } + + /** + * Finds the first occurrence of a resource that can be found. The locations + * are searched in the order they were added to the URLClassLoader. + * + * @param resourceName the resource name to look for + * @return the URLResource for the resource if found, null otherwise + */ + private Resource findURLResource(String resourceName) + { + int max = urlinfos.size(); + for (int i = 0; i < max; i++) + { + URLLoader loader = (URLLoader) urlinfos.elementAt(i); + if (loader == null) + continue; + + Resource resource = loader.getResource(resourceName); + if (resource != null) + return resource; + } + return null; + } + + /** + * Finds the first occurrence of a resource that can be found. + * + * @param resourceName the resource name to look for + * @return the URL if found, null otherwise + */ + public URL findResource(String resourceName) + { + Resource resource = findURLResource(resourceName); + if (resource != null) + return resource.getURL(); + + // Resource not found + return null; + } + + /** + * If the URLStreamHandlerFactory has been set this return the appropriate + * URLStreamHandler for the given protocol, if not set returns null. + * + * @param protocol the protocol for which we need a URLStreamHandler + * @return the appropriate URLStreamHandler or null + */ + URLStreamHandler getURLStreamHandler(String protocol) + { + if (factory == null) + return null; + + URLStreamHandler handler; + synchronized (factoryCache) + { + // Check if there're handler for the same protocol in cache. + HashMap cache = (HashMap) factoryCache.get(factory); + handler = (URLStreamHandler) cache.get(protocol); + if (handler == null) + { + // Add it to cache. + handler = factory.createURLStreamHandler(protocol); + cache.put(protocol, handler); + } + } + return handler; + } + + /** + * Finds all the resources with a particular name from all the locations. + * + * @exception IOException when an error occurs accessing one of the + * locations + * @param resourceName the name of the resource to lookup + * @return a (possible empty) enumeration of URLs where the resource can be + * found + */ + public Enumeration findResources(String resourceName) + throws IOException + { + Vector resources = new Vector(); + int max = urlinfos.size(); + for (int i = 0; i < max; i++) + { + URLLoader loader = (URLLoader) urlinfos.elementAt(i); + Resource resource = loader.getResource(resourceName); + if (resource != null) + resources.add(resource.getURL()); + } + return resources.elements(); + } + + /** + * Returns the permissions needed to access a particular code + * source. These permissions includes those returned by + * <code>SecureClassLoader.getPermissions()</code> and the actual + * permissions to access the objects referenced by the URL of the + * code source. The extra permissions added depend on the protocol + * and file portion of the URL in the code source. If the URL has + * the "file" protocol ends with a '/' character then it must be a + * directory and a file Permission to read everything in that + * directory and all subdirectories is added. If the URL had the + * "file" protocol and doesn't end with a '/' character then it must + * be a normal file and a file permission to read that file is + * added. If the <code>URL</code> has any other protocol then a + * socket permission to connect and accept connections from the host + * portion of the URL is added. + * + * @param source The codesource that needs the permissions to be accessed + * @return the collection of permissions needed to access the code resource + * @see java.security.SecureClassLoader#getPermissions() + */ + protected PermissionCollection getPermissions(CodeSource source) + { + // XXX - This implementation does exactly as the Javadoc describes. + // But maybe we should/could use URLConnection.getPermissions()? + // First get the permissions that would normally be granted + PermissionCollection permissions = super.getPermissions(source); + + // Now add any extra permissions depending on the URL location. + URL url = source.getLocation(); + String protocol = url.getProtocol(); + if (protocol.equals("file")) + { + String file = url.getFile(); + + // If the file end in / it must be an directory. + if (file.endsWith("/") || file.endsWith(File.separator)) + { + // Grant permission to read everything in that directory and + // all subdirectories. + permissions.add(new FilePermission(file + "-", "read")); + } + else + { + // It is a 'normal' file. + // Grant permission to access that file. + permissions.add(new FilePermission(file, "read")); + } + } + else + { + // Grant permission to connect to and accept connections from host + String host = url.getHost(); + if (host != null) + permissions.add(new SocketPermission(host, "connect,accept")); + } + + return permissions; + } + + /** + * Returns all the locations that this class loader currently uses the + * resolve classes and resource. This includes both the initially supplied + * URLs as any URLs added later by the loader. + * @return All the currently used URLs + */ + public URL[] getURLs() + { + return (URL[]) urls.toArray(new URL[urls.size()]); + } + + /** + * Creates a new instance of a <code>URLClassLoader</code> that gets + * classes from the supplied <code>URL</code>s. This class loader + * will have as parent the standard system class loader. + * + * @param urls the initial URLs used to resolve classes and + * resources + * + * @return the class loader + * + * @exception SecurityException when the calling code does not have + * permission to access the given <code>URL</code>s + */ + public static URLClassLoader newInstance(URL[] urls) + throws SecurityException + { + return newInstance(urls, null); + } + + /** + * Creates a new instance of a <code>URLClassLoader</code> that gets + * classes from the supplied <code>URL</code>s and with the supplied + * loader as parent class loader. + * + * @param urls the initial URLs used to resolve classes and + * resources + * @param parent the parent class loader + * + * @return the class loader + * + * @exception SecurityException when the calling code does not have + * permission to access the given <code>URL</code>s + */ + public static URLClassLoader newInstance(URL[] urls, final ClassLoader parent) + throws SecurityException + { + SecurityManager sm = System.getSecurityManager(); + if (sm == null) + return new URLClassLoader(urls, parent); + else + { + final Object securityContext = sm.getSecurityContext(); + + // XXX - What to do with anything else then an AccessControlContext? + if (! (securityContext instanceof AccessControlContext)) + throw new SecurityException("securityContext must be AccessControlContext: " + + securityContext); + + URLClassLoader loader = + (URLClassLoader) AccessController.doPrivileged(new PrivilegedAction() + { + public Object run() + { + return new URLClassLoader(parent, + (AccessControlContext) securityContext); + } + }); + loader.addURLs(urls); + return loader; + } + } +} |