/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 net.jini.loader.pref; import com.sun.jini.loader.pref.internal.PreferredResources; import java.io.IOException; import java.io.InputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FilePermission; import java.net.HttpURLConnection; import java.net.JarURLConnection; import java.net.MalformedURLException; import java.net.SocketPermission; import java.net.URL; import java.net.URLClassLoader; import java.net.URLConnection; import java.net.URLStreamHandler; import java.net.URLStreamHandlerFactory; import java.security.AccessControlContext; import java.security.AccessController; import java.security.CodeSource; import java.security.Permission; import java.security.PermissionCollection; import java.security.Permissions; import java.security.ProtectionDomain; import java.security.Policy; import java.security.PrivilegedAction; import java.security.PrivilegedActionException; import java.security.PrivilegedExceptionAction; import java.security.cert.Certificate; import java.util.Enumeration; import java.util.Set; import java.util.HashSet; import net.jini.loader.ClassAnnotation; import net.jini.loader.DownloadPermission; /** * A class loader that supports preferred classes. * * <p>A preferred class is a class that is to be loaded by a class * loader without the loader delegating to its parent class loader * first. Resources may also be preferred. * * <p>Like {@link java.net.URLClassLoader}, * <code>PreferredClassLoader</code> loads classes and resources from * a search path of URLs. If a URL in the path ends with a * <code>'/'</code>, it is assumed to refer to a directory; otherwise, * the URL is assumed to refer to a JAR file. * * <p>The location of the first URL in the path can contain a * <i>preferred list</i> for the entire path. A preferred list * declares names of certain classes and other resources throughout * the path as being <i>preferred</i> or not. When a * <code>PreferredClassLoader</code> is asked to load a class or * resource that is preferred (according to the preferred list) and * the class or resource exists in the loader's path of URLs, the * loader will not delegate first to its parent class loader as it * otherwise would do; instead, it will attempt to load the class or * resource from its own path of URLs only. * * <p>The preferred list for a path of URLs, if one exists, is located * relative to the first URL in the path. If the first URL refers to * a JAR file, then the preferred list is the contents of the file * named <code>"META-INF/PREFERRED.LIST"</code> within that JAR file. * If the first URL refers to a directory, then the preferred list is * the contents of the file at the location * <code>"META-INF/PREFERRED.LIST"</code> relative to that directory * URL. If there is no preferred list at the required location, then * no classes or resources are preferred for the path of URLs. A * preferred list at any other location (such as relative to one of * the other URLs in the path) is ignored. * * <p>Note that a class or resource is only considered to be preferred * if the preferred list declares the name of the class or resource as * being preferred and the class or resource actually exists in the * path of URLs. * * <h3>Preferred List Syntax</h3> * * A preferred list is a UTF-8 encoded text file, with lines separated * by CR LF, LF, or CR (not followed by an LF). Multiple * whitespace characters in a line are equivalent to a single * whitespace character, and whitespace characters at the beginning or * end of a line are ignored. If the first non-whitespace character * of a line is <code>'#'</code>, the line is a comment and is * equivalent to a blank line. * * <p>The first line of a preferred list must contain a version * number in the following format: * * <pre> * PreferredResources-Version: 1.<i>x</i> * </pre> * * This specification defines only version 1.0, but * <code>PreferredClassLoader</code> will parse any version * 1.<i>x</i>, <i>x</i>>=0 with the format and semantics specified * here. * * <p>After the version number line, a preferred list comprises an * optional default preferred entry followed by zero or more named * preferred entries. A preferred list must contain either a default * preferred entry or at least one named preferred entry. Blank lines * are allowed before and after preferred entries, as well as between * the lines of a named preferred entry. * * <p>A default preferred entry is a single line in the following * format: * * <pre> * Preferred: <i>preferred-setting</i> * </pre> * * where <i>preferred-setting</i> is a non-empty sequence of * characters. If <i>preferred-setting</i> equals <code>"true"</code> * (case insensitive), then resource names not matched by any of the * named preferred entries are by default preferred; otherwise, * resource names not matched by any of the named preferred entries * are by default not preferred. If there is no default preferred * entry, then resource names are by default not preferred. * * <p>A named preferred entry is two lines in the following format: * * <pre> * Name: <i>name-expression</i> * Preferred: <i>preferred-setting</i> * </pre> * * where <i>name-expression</i> and <i>preferred-setting</i> are * non-empty sequences of characters. If <i>preferred-setting</i> * equals <code>"true"</code> (case insensitive), then resource names * that are matched by <i>name-expression</i> (and not any more * specific named preferred entries) are preferred; otherwise, * resource names that are matched by <i>name-expression</i> (and not * any more specific named preferred entries) are not preferred. * * <p>If <i>name-expression</i> ends with <code>".class"</code>, it * matches a class whose binary name is <i>name-expression</i> without * the <code>".class"</code> suffix and with each <code>'/'</code> * character replaced with a <code>'.'</code>. It also matches any * class whose binary name starts with that same value followed by a * <code>'$'</code>; this rule is intended to match nested classes * that have an enclosing class of that name, so that the preferred * settings of a class and all of its nested classes are the same by * default. It is possible, but strongly discouraged, to override the * preferred setting of a nested class with a named preferred entry * that explicitly matches the nested class's binary name. * * <p><i>name-expression</i> may match arbitrary resource names as * well as class names, with path elements separated by * <code>'/'</code> characters. * * <p>If <i>name-expression</i> ends with <code>"/"</code> or * <code>"/*"</code>, then the entry is a directory wildcard entry * that matches all resources (including classes) in the named * directory. If <i>name-expression</i> ends with <code>"/-"</code>, * then the entry is a namespace wildcard entry that matches all * resources (including classes) in the named directory and all of its * subdirectories. * * <p>When more than one named preferred entry matches a class or * resource name, then the most specific entry takes precedence. A * non-wildcard entry is more specific than a wildcard entry. A * directory wildcard entry is more specific than a namespace wildcard * entry. A namespace wildcard entry with more path elements is more * specific than a namespace wildcard entry with fewer path elements. * Given two non-wildcard entries, the entry with the longer * <i>name-expression</i> is more specific (this rule is only * significant when matching a class). The order of named preferred * entries is insignificant. * * <h3>Example Preferred List</h3> * * <p>Following is an example preferred list: * * <pre> * PreferredResources-Version: 1.0 * Preferred: false * * Name: com/foo/FooBar.class * Preferred: true * * Name: com/foo/* * Preferred: false * * Name: com/foo/- * Preferred: true * * Name: image-files/* * Preferred: mumble * </pre> * * <p>The class <code>com.foo.FooBar</code> is preferred, as well as * any nested classes that have it as an enclosing class. All other * classes in the <code>com.foo</code> package are not preferred * because of the directory wildcard entry. Classes in subpackages of * <code>com.foo</code> are preferred because of the namespace * wildcard entry. Resources in the directory <code>"com/foo/"</code> * are not preferred, and resources in subdirectories of * <code>"com/foo/"</code> are preferred. Resources in the directory * <code>"image-files/"</code> are not preferred because preferred * settings other than <code>"true"</code> are interpreted as false. * Classes that are in a package named <code>com.bar</code> are not * preferred because of the default preferred entry. * * @author Sun Microsystems, Inc. * @since 2.0 **/ public class PreferredClassLoader extends URLClassLoader implements ClassAnnotation { /** * well known name of resource that contains the preferred list in * a path of URLs **/ private static final String PREF_NAME = "META-INF/PREFERRED.LIST"; /** first URL in the path, or null if none */ private final URL firstURL; /** class annotation string for classes defined by this loader */ private final String exportAnnotation; /** permissions required to access loader through public API */ private final Permissions permissions; /** security context for loading classes and resources */ private final AccessControlContext acc; /** permission required to download code? */ private final boolean requireDlPerm; /** URLStreamHandler to use when creating new "jar:" URLs */ private final URLStreamHandler jarHandler; /** PreferredResources for this loader (null if no preferred list) */ private PreferredResources preferredResources; /** true if preferredResources has been successfully initialized */ private boolean preferredResourcesInitialized = false; private static final Permission downloadPermission = new DownloadPermission(); /** * Creates a new <code>PreferredClassLoader</code> that loads * classes and resources from the specified path of URLs and * delegates to the specified parent class loader. * * <p>If <code>exportAnnotation</code> is not <code>null</code>, * then it will be used as the return value of the loader's {@link * #getClassAnnotation getClassAnnotation} method. If * <code>exportAnnotation</code> is <code>null</code>, the * loader's <code>getClassAnnotation</code> method will return a * space-separated list of the URLs in the specified path. The * <code>exportAnnotation</code> parameter can be used to specify * so-called "export" URLs, from which other parties should load * classes defined by the loader and which are different from the * "import" URLs that the classes are actually loaded from. * * <p>If <code>requireDlPerm</code> is <code>true</code>, the * loader's {@link #getPermissions getPermissions} method will * require that the {@link CodeSource} of any class defined by the * loader is granted {@link DownloadPermission}. * * @param urls the path of URLs to load classes and resources from * * @param parent the parent class loader for delegation * * @param exportAnnotation the export class annotation string to * use for classes defined by this loader, or <code>null</code> * * @param requireDlPerm if <code>true</code>, the loader will only * define classes with a {@link CodeSource} that is granted {@link * DownloadPermission} * * @throws SecurityException if there is a security manager and an * invocation of its {@link SecurityManager#checkCreateClassLoader * checkCreateClassLoader} method fails **/ public PreferredClassLoader(URL[] urls, ClassLoader parent, String exportAnnotation, boolean requireDlPerm) { this(urls, parent, exportAnnotation, requireDlPerm, null); } /** * Creates a new <code>PreferredClassLoader</code> that loads * classes and resources from the specified path of URLs, * delegates to the specified parent class loader, and uses the * specified {@link URLStreamHandlerFactory} when creating new URL * objects. This constructor passes <code>factory</code> to the * superclass constructor that has a * <code>URLStreamHandlerFactory</code> parameter. * * <p>If <code>exportAnnotation</code> is not <code>null</code>, * then it will be used as the return value of the loader's {@link * #getClassAnnotation getClassAnnotation} method. If * <code>exportAnnotation</code> is <code>null</code>, the * loader's <code>getClassAnnotation</code> method will return a * space-separated list of the URLs in the specified path. The * <code>exportAnnotation</code> parameter can be used to specify * so-called "export" URLs, from which other parties should load * classes defined by the loader and which are different from the * "import" URLs that the classes are actually loaded from. * * <p>If <code>requireDlPerm</code> is <code>true</code>, the * loader's {@link #getPermissions getPermissions} method will * require that the {@link CodeSource} of any class defined by the * loader is granted {@link DownloadPermission}. * * @param urls the path of URLs to load classes and resources from * * @param parent the parent class loader for delegation * * @param exportAnnotation the export class annotation string to * use for classes defined by this loader, or <code>null</code> * * @param requireDlPerm if <code>true</code>, the loader will only * define classes with a {@link CodeSource} that is granted {@link * DownloadPermission} * * @param factory the <code>URLStreamHandlerFactory</code> to use * when creating new URL objects, or <code>null</code> * * @throws SecurityException if there is a security manager and an * invocation of its {@link SecurityManager#checkCreateClassLoader * checkCreateClassLoader} method fails * * @since 2.1 **/ public PreferredClassLoader(URL[] urls, ClassLoader parent, String exportAnnotation, boolean requireDlPerm, URLStreamHandlerFactory factory) { super(urls, parent, factory); firstURL = (urls.length > 0 ? urls[0] : null); if (exportAnnotation != null) { this.exportAnnotation = exportAnnotation; } else { /* * Caching the value of class annotation string here * assumes that the protected method addURL() is never * called on this class loader. */ this.exportAnnotation = urlsToPath(urls); } this.requireDlPerm = requireDlPerm; if (factory != null) { jarHandler = factory.createURLStreamHandler("jar"); } else { jarHandler = null; } acc = AccessController.getContext(); /* * Precompute the permissions required to access the loader. */ permissions = new Permissions(); addPermissionsForURLs(urls, permissions, false); } /** * Convert an array of URL objects into a corresponding string * containing a space-separated list of URLs. * * Note that if the array has zero elements, the return value is * null, not the empty string. */ static String urlsToPath(URL[] urls) { if (urls.length == 0) { return null; } else if (urls.length == 1) { return urls[0].toExternalForm(); } else { StringBuffer path = new StringBuffer(urls[0].toExternalForm()); for (int i = 1; i < urls.length; i++) { path.append(' '); path.append(urls[i].toExternalForm()); } return path.toString(); } } /** * Creates a new instance of <code>PreferredClassLoader</code> * that loads classes and resources from the specified path of * URLs and delegates to the specified parent class loader. * * <p>The <code>exportAnnotation</code> and * <code>requireDlPerm</code> parameters have the same semantics * as they do for the constructors. * * <p>The {@link #loadClass loadClass} method of the returned * <code>PreferredClassLoader</code> will, if there is a security * manager, invoke its {@link SecurityManager#checkPackageAccess * checkPackageAccess} method with the package name of the class * to load before attempting to load the class; this could result * in a <code>SecurityException</code> being thrown from * <code>loadClass</code>. * * @param urls the path of URLs to load classes and resources from * * @param parent the parent class loader for delegation * * @param exportAnnotation the export class annotation string to * use for classes defined by this loader, or <code>null</code> * * @param requireDlPerm if <code>true</code>, the loader will only * define classes with a {@link CodeSource} that is granted {@link * DownloadPermission} * * @return the new <code>PreferredClassLoader</code> instance * * @throws SecurityException if the current security context does * not have the permissions necessary to connect to all of the * URLs in <code>urls</code> **/ public static PreferredClassLoader newInstance(final URL[] urls, final ClassLoader parent, final String exportAnnotation, final boolean requireDlPerm) { /* ensure caller has permission to access all urls */ Permissions perms = new Permissions(); addPermissionsForURLs(urls, perms, false); checkPermissions(perms); AccessControlContext acc = getLoaderAccessControlContext(urls); /* Use privileged status to return a new class loader instance */ return (PreferredClassLoader) AccessController.doPrivileged(new PrivilegedAction() { public Object run() { return new PreferredFactoryClassLoader(urls, parent, exportAnnotation, requireDlPerm); } }, acc); } /** * If a preferred list exists relative to the first URL of this * loader's path, sets this loader's PreferredResources according * to that preferred list. If no preferred list exists relative * to the first URL, leaves this loader's PreferredResources null. * * Throws IOException if an I/O exception occurs from which the * existence of a preferred list relative to the first URL cannot * be definitely determined. * * This method must only be invoked while synchronized on this * PreferredClassLoader, and it must not be invoked again after it * has completed successfully. **/ private void initializePreferredResources() throws IOException { assert Thread.holdsLock(this); assert preferredResources == null; if (firstURL != null) { InputStream prefIn = getPreferredInputStream(firstURL); if (prefIn != null) { try { preferredResources = new PreferredResources(prefIn); } finally { try { prefIn.close(); } catch (IOException e) { } } } } } /** * Returns an InputStream from which the preferred list relative * to the specified URL can be read, or null if the there is * definitely no preferred list relative to the URL. If the URL's * path ends with "/", then the preferred list is sought at the * location "META-INF/PREFERRED.LIST" relative to the URL; * otherwise, the URL is assumed to refer to a JAR file, and the * preferred list is sought within that JAR file, as the entry * named "META-INF/PREFERRED.LIST". * * Throws IOException if an I/O exception occurs from which the * existence of a preferred list relative to the specified URL * cannot be definitely determined. **/ private InputStream getPreferredInputStream(URL firstURL) throws IOException { URL prefListURL = null; try { URL baseURL; // base URL to load PREF_NAME relative to if (firstURL.getFile().endsWith("/")) { // REMIND: track 4915051 baseURL = firstURL; } else { /* * First determine if the JAR file exists by attempting to * access it directly, without using a "jar:" URL, because * the "jar:" URL handler can mask the distinction between * definite lack of existence and less definitive errors. * Unfortunately, this direct access circumvents the JAR * file caching done by the "jar:" handler, so it ends up * causing a duplicate request of the JAR file on first * use. (For HTTP-protocol URLs, the initial request will * use HEAD instead of GET.) * * After determining that the JAR file exists, attempt to * retrieve the preferred list using a "jar:" URL, like * URLClassLoader uses to load resources from a JAR file. */ if (jarExists(firstURL)) { baseURL = getBaseJarURL(firstURL); } else { return null; } } prefListURL = new URL(baseURL, PREF_NAME); URLConnection preferredConnection = getPreferredConnection(prefListURL, false); if (preferredConnection != null) { return preferredConnection.getInputStream(); } else { return null; } } catch (IOException e) { /* * Assume that any IOException thrown while attempting to * access a "file:" URL and any FileNotFoundException * implies that there is definitely no preferred list * relative to the specified URL. */ if (firstURL.getProtocol().equals("file") || e instanceof FileNotFoundException) { return null; } else { throw e; } } } /* cache existence of jar files referenced by codebase urls */ private static final Set existSet = new HashSet(11); /* * Determine if a jar file in a given URL location exists. If the * jar exists record the jar file's URL in a cache of URL strings. * * Recording the existence of the jar prevents the need to * re-determine the jar's existence on subsequent downloads of the * jar in potentially different preferred class loaders. */ private boolean jarExists(URL firstURL) throws IOException { boolean exists; synchronized (existSet) { exists = existSet.contains(firstURL); } if (!exists) { exists = (getPreferredConnection(firstURL, true) != null); if (exists) { synchronized (existSet) { existSet.add(firstURL); } } } return exists; } /** * Returns a "jar:" URL for the root directory of the JAR file at * the specified URL. If this loader was constructed with a * URLStreamHandlerFactory, then the returned URL will have a * URLStreamHandler that was created by the factory. **/ private URL getBaseJarURL(final URL url) throws MalformedURLException { if (jarHandler == null) { return new URL("jar", "", -1, url + "!/"); } else { try { return (URL) AccessController.doPrivileged( new PrivilegedExceptionAction() { public Object run() throws MalformedURLException { return new URL("jar", "", -1, url + "!/", jarHandler); } }); } catch (PrivilegedActionException e) { throw (MalformedURLException) e.getCause(); } } } /** * Obtain a url connection from which an input stream that * contains a preferred list can be obtained. * * For http urls, attempts to use http response codes to * determine if a preferred list exists or is definitely not * found. Simply attempts to open a connection to other kinds * of non-file urls. If the attempt fails, an IOException is * thrown to user code. * * Returns null if the preferred list definitely does not * exist. Rethrows all indefinite IOExceptions generated * while trying to open a connection to the preferred list. * * The caller has the option to close the connection after the * resource has been detected (as will happen when probing for a * PREFERRED.LIST). */ private URLConnection getPreferredConnection(URL url, boolean closeAfter) throws IOException { URLConnection preferredConnection = null; if (url.getProtocol().equals("file")) { return url.openConnection(); } URLConnection closeConn = null; URLConnection conn = null; try { closeConn = url.openConnection(); conn = closeConn; /* check status of http urls */ if (conn instanceof HttpURLConnection) { HttpURLConnection hconn = (HttpURLConnection) conn; if (closeAfter) { hconn.setRequestMethod("HEAD"); } int responseCode = hconn.getResponseCode(); switch (responseCode) { case HttpURLConnection.HTTP_OK: case HttpURLConnection.HTTP_NOT_AUTHORITATIVE: /* the preferred list exists */ break; /* 404, not found appears to be handled by * HttpURLConnection (FileNotFoundException is * thrown), but to be safe do the right thing here as * well. */ case HttpURLConnection.HTTP_NOT_FOUND: case HttpURLConnection.HTTP_FORBIDDEN: case HttpURLConnection.HTTP_GONE: /* list definitely does not exist */ conn = null; break; default: /* indefinite response code */ throw new IOException("Indefinite http response for " + "preferred list request:" + hconn.getResponseMessage()); } } } finally { if (closeAfter && (closeConn != null)) { /* clean up after... */ try { closeConn.getInputStream().close(); } catch (IOException e) { } } } return conn; } /** * Returns <code>true</code> if a class or resource with the * specified name is preferred for this class loader, and * <code>false</code> if a class or resource with the specified * name is not preferred for this loader. * * <p>If <code>isClass</code> is <code>true</code>, then * <code>name</code> is interpreted as the binary name of a class; * otherwise, <code>name</code> is interpreted as the full path of * a resource. * * <p>This method only returns <code>true</code> if a class or * resource with the specified name exists in the this loader's * path of URLs and the name is preferred in the preferred list. * This method returns <code>false</code> if the name is not * preferred in the preferred list or if the name is preferred * with the default preferred entry or a wildcard preferred entry * and the class or resource does not exist in the path of URLs. * * @param name the name of the class or resource * * @param isClass <code>true</code> if <code>name</code> is a * binary class name, and <code>false</code> if <code>name</code> * is the full path of a resource * * @return <code>true</code> if a class or resource named * <code>name</code> is preferred for this loader, and * <code>false</code> if a class or resource named * <code>name</code> is not preferred for this loader * * @throws IOException if the preferred list cannot definitely be * determined to exist or not exist, or if the preferred list * contains a syntax error, or if the name is preferred with the * default preferred entry or a wildcard preferred entry and the * class or resource cannot definitely be determined to exist or * not exist in the path of URLs, or if the name is preferred with * a non-wildcard entry and the class or resource does not exist * or cannot definitely be determined to exist in the path of URLs **/ protected boolean isPreferredResource(final String name, final boolean isClass) throws IOException { try { return ((Boolean) AccessController.doPrivileged( new PrivilegedExceptionAction() { public Object run() throws IOException { boolean b = isPreferredResource0(name, isClass); return Boolean.valueOf(b); } }, acc)).booleanValue(); } catch (PrivilegedActionException e) { throw (IOException) e.getException(); } } /* * Perform the work to determine if a resource name is preferred. */ private synchronized boolean isPreferredResource0(String name, boolean isClass) throws IOException { if (!preferredResourcesInitialized) { initializePreferredResources(); preferredResourcesInitialized = true; } if (preferredResources == null) { return false; // no preferred list: nothing is preferred } String resourceName = name; if (isClass) { /* class name -> resource name */ resourceName = name.replace('.', '/') + ".class"; } /* * Determine if the class name is preferred. Making this * distinction is somewhat tricky because we need to cache the * preferred state (i.e. if the name is preferred and its * resource exists) in a way that avoids duplication of * preferred information - state information is stored back * into the preferred resources object for this class loader * and not held in a separate preferred settings cache. */ boolean resourcePreferred = false; int state = preferredResources.getNameState(resourceName, isClass); switch (state) { case PreferredResources.NAME_NOT_PREFERRED: resourcePreferred = false; break; case PreferredResources.NAME_PREFERRED_RESOURCE_EXISTS: resourcePreferred = true; break; case PreferredResources.NAME_NO_PREFERENCE: Boolean wildcardPref = preferredResources.getWildcardPreference(resourceName); if (wildcardPref == null) { /* preferredDefault counts as a wild card */ wildcardPref = preferredResources.getDefaultPreference(); } if (wildcardPref.booleanValue()) { resourcePreferred = findResourceUpdateState(name, resourceName); } break; case PreferredResources.NAME_PREFERRED: resourcePreferred = findResourceUpdateState(name, resourceName); if (!resourcePreferred) { throw new IOException("no resource found for " + "complete preferred name"); } break; default: throw new Error("unknown preference state"); } return resourcePreferred; } /* * Determine if a resource for a given preferred name exists. If * the resource exists record its new state in the * preferredResources object. * * This method must only be invoked while synchronized on this * PreferredClassLoader. */ private boolean findResourceUpdateState(String name, String resourceName) throws IOException { assert Thread.holdsLock(this); boolean resourcePreferred = false; if (findResource(resourceName) != null) { /* the resource is know to exist */ preferredResources.setNameState(resourceName, PreferredResources.NAME_PREFERRED_RESOURCE_EXISTS); resourcePreferred = true; } return resourcePreferred; } /** * Loads a class with the specified name. * * <p><code>PreferredClassLoader</code> implements this method as * follows: * * <p>This method first invokes {@link #findLoadedClass * findLoadedClass} with <code>name</code>; if * <code>findLoadedClass</code> returns a non-<code>null</code> * <code>Class</code>, then this method returns that * <code>Class</code>. * * <p>Otherwise, this method invokes {@link #isPreferredResource * isPreferredResource} with <code>name</code> as the first * argument and <code>true</code> as the second argument: * * <ul> * * <li>If <code>isPreferredResource</code> throws an * <code>IOException</code>, then this method throws a * <code>ClassNotFoundException</code> containing the * <code>IOException</code> as its cause. * * <li>If <code>isPreferredResource</code> returns * <code>true</code>, then this method invokes {@link #findClass * findClass} with <code>name</code>. If <code>findClass</code> * throws an exception, then this method throws that exception. * Otherwise, this method returns the <code>Class</code> returned * by <code>findClass</code>, and if <code>resolve</code> is * <code>true</code>, {@link #resolveClass resolveClass} is * invoked with the <code>Class</code> before returning. * * <li>If <code>isPreferredResource</code> returns * <code>false</code>, then this method invokes the superclass * implementation of {@link ClassLoader#loadClass(String,boolean) * loadClass} with <code>name</code> and <code>resolve</code> and * returns the result. If the superclass's <code>loadClass</code> * throws an exception, then this method throws that exception. * * </ul> * * @param name the binary name of the class to load * * @param resolve if <code>true</code>, then {@link #resolveClass * resolveClass} will be invoked with the loaded class before * returning * * @return the loaded class * * @throws ClassNotFoundException if the class could not be found **/ protected synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { // First, check if the class has already been loaded Class c = findLoadedClass(name); if (c == null) { boolean preferred; try { preferred = isPreferredResource(name, true); } catch (IOException e) { throw new ClassNotFoundException(name + " (could not determine preferred setting; " + (firstURL != null ? "first URL: \"" + firstURL + "\"" : "no URLs") + ")", e); } if (preferred) { c = findClass(name); if (resolve) { resolveClass(c); } return c; } else { return super.loadClass(name, resolve); } } return c; } /** * Gets a resource with the specified name. * * <p><code>PreferredClassLoader</code> implements this method as * follows: * * <p>This method invokes {@link #isPreferredResource * isPreferredResource} with <code>name</code> as the first * argument and <code>false</code> as the second argument: * * <ul> * * <li>If <code>isPreferredResource</code> throws an * <code>IOException</code>, then this method returns * <code>null</code>. * * <li>If <code>isPreferredResource</code> returns * <code>true</code>, then this method invokes {@link * #findResource findResource} with <code>name</code> and returns * the result. * * <li>If <code>isPreferredResource</code> returns * <code>false</code>, then this method invokes the superclass * implementation of {@link ClassLoader#getResource getResource} * with <code>name</code> and returns the result. * * </ul> * * @param name the name of the resource to get * * @return a <code>URL</code> for the resource, or * <code>null</code> if the resource could not be found **/ public URL getResource(String name) { try { return (isPreferredResource(name, false) ? findResource(name) : super.getResource(name)); } catch (IOException e) { } return null; } /* * Work around 4841786: wrap ClassLoader.definePackage so that if * it throws an IllegalArgumentException because an ancestor * loader "defined" the named package since the last time that * ClassLoader.getPackage was invoked, then just return the * result of invoking ClassLoader.getPackage again here. * Fortunately, URLClassLoader.defineClass ignores the value * returned by this method. */ protected Package definePackage(String name, String specTitle, String specVersion, String specVendor, String implTitle, String implVersion, String implVendor, URL sealBase) { try { return super.definePackage(name, specTitle, specVersion, specVendor, implTitle, implVersion, implVendor, sealBase); } catch (IllegalArgumentException e) { return getPackage(name); } } /** * {@inheritDoc} * * <p><code>PreferredClassLoader</code> implements this method as * follows: * * <p>If this <code>PreferredClassLoader</code> was constructed * with a non-<code>null</code> export class annotation string, * then this method returns that string. Otherwise, this method * returns a space-separated list of this loader's path of URLs. **/ public String getClassAnnotation() { return exportAnnotation; } /** * Check that the current access control context has all of the * permissions necessary to load classes from this loader. */ void checkPermissions() { checkPermissions(permissions); } /** * Check that the current access control context has all of the * given permissions. */ private static void checkPermissions(Permissions perms) { SecurityManager sm = System.getSecurityManager(); if (sm != null) { // should never be null? Enumeration en = perms.elements(); while (en.hasMoreElements()) { sm.checkPermission((Permission) en.nextElement()); } } } /** * Returns the static permissions to be automatically granted to * classes loaded from the specified {@link CodeSource} and * defined by this class loader. * * <p><code>PreferredClassLoader</code> implements this method as * follows: * * <p>If there is a security manager and this * <code>PreferredClassLoader</code> was constructed to enforce * {@link DownloadPermission}, then this method checks that the * current security policy grants the specified * <code>CodeSource</code> the permission * <code>DownloadPermission("permit")</code>; if that check fails, * then this method throws a <code>SecurityException</code>. * * <p>Then this method invokes the superclass implementation of * {@link #getPermissions getPermissions} and returns the result. * * @param codeSource the <code>CodeSource</code> to return the * permissions to be granted to * * @return the permissions to be granted to the * <code>CodeSource</code> * * @throws SecurityException if there is a security manager, this * <code>PreferredClassLoader</code> was constructed to enforce * <code>DownloadPermission</code>, and the current security * policy does not grant the specified <code>CodeSource</code> the * permission <code>DownloadPermission("permit")</code> **/ protected PermissionCollection getPermissions(CodeSource codeSource) { if (requireDlPerm) { SecurityManager sm = System.getSecurityManager(); if (sm != null) { ProtectionDomain pd = new ProtectionDomain(codeSource, null, this, null); if (!pd.implies(downloadPermission)) { throw new SecurityException( "CodeSource not permitted to define class: " + codeSource); } } } return super.getPermissions(codeSource); } /** * Returns a string representation of this class loader. **/ public String toString() { return super.toString() + "[\"" + exportAnnotation + "\"]"; } /** * Return the access control context that a loader for the given * codebase URL path should execute with. */ static AccessControlContext getLoaderAccessControlContext(URL[] urls) { /* * The approach used here is taken from the similar method * getAccessControlContext() in the sun.applet.AppletPanel class. */ // begin with permissions granted to all code in current policy PermissionCollection perms = (PermissionCollection) AccessController.doPrivileged(new PrivilegedAction() { public Object run() { CodeSource codesource = new CodeSource(null, (Certificate[]) null); Policy p = java.security.Policy.getPolicy(); if (p != null) { return p.getPermissions(codesource); } else { return new Permissions(); } } }); // createClassLoader permission needed to create loader in context perms.add(new RuntimePermission("createClassLoader")); // add permissions to read any "java.*" property perms.add(new java.util.PropertyPermission("java.*","read")); // add permissions required to load from codebase URL path addPermissionsForURLs(urls, perms, true); /* * Create an AccessControlContext that consists of a single * protection domain with only the permissions calculated above. */ ProtectionDomain pd = new ProtectionDomain( new CodeSource((urls.length > 0 ? urls[0] : null), (Certificate[]) null), perms); return new AccessControlContext(new ProtectionDomain[] { pd }); } /** * Adds to the specified permission collection the permissions * necessary to load classes from a loader with the specified URL * path; if "forLoader" is true, also adds URL-specific * permissions necessary for the security context that such a * loader operates within, such as permissions necessary for * granting automatic permissions to classes defined by the * loader. A given permission is only added to the collection if * it is not already implied by the collection. **/ static void addPermissionsForURLs(URL[] urls, PermissionCollection perms, boolean forLoader) { for (int i = 0; i < urls.length; i++) { URL url = urls[i]; try { URLConnection urlConnection = url.openConnection(); Permission p = urlConnection.getPermission(); if (p != null) { if (p instanceof FilePermission) { /* * If the codebase is a file, the permission required * to actually read classes from the codebase URL is * the permission to read all files beneath the last * directory in the file path, either because JAR * files can refer to other JAR files in the same * directory, or because permission to read a * directory is not implied by permission to read the * contents of a directory, which all that might be * granted. */ String path = p.getName(); int endIndex = path.lastIndexOf(File.separatorChar); if (endIndex != -1) { path = path.substring(0, endIndex+1); if (path.endsWith(File.separator)) { path += "-"; } Permission p2 = new FilePermission(path, "read"); if (!perms.implies(p2)) { perms.add(p2); } } else { /* * No directory separator: use permission to * read the file. */ if (!perms.implies(p)) { perms.add(p); } } } else { if (!perms.implies(p)) { perms.add(p); } /* * If the purpose of these permissions is to grant * them to an instance of a URLClassLoader subclass, * we must add permission to connect to and accept * from the host of non-"file:" URLs, otherwise the * getPermissions() method of URLClassLoader will * throw a security exception. */ if (forLoader) { // get URL with meaningful host component URL hostURL = url; for (URLConnection conn = urlConnection; conn instanceof JarURLConnection;) { hostURL = ((JarURLConnection) conn).getJarFileURL(); conn = hostURL.openConnection(); } String host = hostURL.getHost(); if (host != null && p.implies(new SocketPermission(host, "resolve"))) { Permission p2 = new SocketPermission(host, "connect,accept"); if (!perms.implies(p2)) { perms.add(p2); } } } } } } catch (IOException e) { /* * This shouldn't happen, although it is declared to be * thrown by openConnection() and getPermission(). If it * does, don't bother granting or requiring any permissions * for this URL. */ } } } }