/*
* 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.action.GetPropertyAction;
import com.sun.jini.logging.Levels;
import com.sun.jini.logging.LogUtil;
import java.io.FilePermission;
import java.io.IOException;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLClassLoader;
import java.rmi.server.RMIClassLoader;
import java.rmi.server.RMIClassLoaderSpi;
import java.security.AccessController;
import java.security.CodeSource;
import java.security.Permission;
import java.security.Permissions;
import java.security.PrivilegedAction;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.WeakHashMap;
import java.util.logging.Logger;
import java.util.logging.Level;
import net.jini.loader.ClassAnnotation;
import net.jini.loader.DownloadPermission;
/**
* An <code>RMIClassLoader</code> provider that supports preferred
* classes.
*
* <p>See the {@link RMIClassLoader} specification for information
* about how to install and configure the <code>RMIClassLoader</code>
* service provider.
*
* <p><code>PreferredClassProvider</code> uses instances of {@link
* PreferredClassLoader} to load classes from codebase URL paths
* supplied to <code>RMIClassLoader.loadClass</code> methods.
*
* <p><code>PreferredClassProvider</code> does not enforce {@link
* DownloadPermission} by default, but a subclass can configure it to
* do so by passing <code>true</code> as the argument to the
* <code>protected</code> constructor.
*
* <p>By overriding the {@link #getClassAnnotation(ClassLoader)
* getClassAnnotation(ClassLoader)} method, a subclass can also
* configure the class annotations to be used for classes defined by
* the system class loader, its ancestor class loaders, and any class
* loader that is not an instance of {@link ClassAnnotation} or {@link
* URLClassLoader}.
*
* <h3>Common Terms and Behaviors</h3>
*
* The following section defines terms and describes behaviors common
* to how <code>PreferredClassProvider</code> implements the abstract
* methods of <code>RMIClassLoaderSpi</code>. Where applicable, these
* definitions and descriptions are relative to the instance of
* <code>PreferredClassProvider</code> on which a method is invoked
* and the context in which it is invoked.
*
* <p>The <i>annotation string</i> for a class loader is determined by
* the following procedure:
*
* <ul>
*
* <li>If the loader is the system class loader or an ancestor of the
* system class loader (including the bootstrap class loader), the
* annotation string is the result of invoking {@link
* #getClassAnnotation(ClassLoader) getClassAnnotation(ClassLoader)}
* with the loader.
*
* <li>Otherwise, if the loader is an instance of {@link
* ClassAnnotation}, the annotation string is the result of invoking
* {@link ClassAnnotation#getClassAnnotation getClassAnnotation} on
* the loader.
*
* <li>Otherwise, if the loader is an instance of {@link
* URLClassLoader}, the annotation string is a space-separated list of
* the URLs returned by an invocation of {@link URLClassLoader#getURLs
* getURLs} on the loader.
*
* <li>Otherwise, the annotation string is the result of invoking
* {@link #getClassAnnotation(ClassLoader)
* getClassAnnotation(ClassLoader)} with the loader.
*
* </ul>
*
* The <i>annotation URL path</i> for a class loader is the path of
* URLs obtained by parsing the annotation string for the loader as a
* list of URLs separated by spaces, where each URL is parsed as with
* the {@link URL#URL(String) URL(String)} constructor; if such
* parsing would result in a {@link MalformedURLException}, then the
* annotation URL path for the loader is only defined to the extent
* that it is not equal to any other path of URLs.
*
* <p>A <code>PreferredClassProvider</code> maintains an internal
* table of class loader instances indexed by keys that comprise a
* path of URLs and a parent class loader. The table does not
* strongly reference the class loader instances, in order to allow
* them (and the classes they have defined) to be garbage collected
* when they are not otherwise reachable.
*
* <p>The methods {@link #loadClass loadClass}, {@link #loadProxyClass
* loadProxyClass}, and {@link #getClassLoader getClassLoader}, which
* each have a <code>String</code> parameter named
* <code>codebase</code>, have the following behaviors in common:
*
* <ul>
*
* <li><code>codebase</code> may be <code>null</code>. If it is not
* <code>null</code>, it is interpreted as a path of URLs by parsing
* it as a list of URLs separated by spaces, where each URL is parsed
* as with the <code>URL(String)</code> constructor; this could result
* in a {@link MalformedURLException}. This path of URLs is the
* <i>codebase URL path</i> for the invocation.
*
* <li>A class loader known as the <i>codebase loader</i> is chosen
* based on <code>codebase</code> and the current thread's context
* class loader as follows. If <code>codebase</code> is
* <code>null</code>, then the codebase loader is the current thread's
* context class loader. Otherwise, for each non-<code>null</code>
* loader starting with the current thread's context class loader and
* continuing with each successive parent class loader, if the
* codebase URL path is equal to the loader's annotation URL path,
* then the codebase loader is that loader. If no such matching
* loader is found, then the codebase loader is the loader in this
* <code>PreferredClassProvider</code>'s internal table with the
* codebase URL path as the key's path of URLs and the current
* thread's context class loader as the key's parent class loader. If
* no such entry exists in the table, then one is created by invoking
* {@link #createClassLoader createClassLoader} with the codebase URL
* path, the current thread's context class loader, and the
* <code>boolean</code> <code>requireDlPerm</code> value that this
* <code>PreferredClassProvider</code> was constructed with; the
* created loader is added to the table, and it is chosen as the
* codebase loader.
*
* <li>The current security context has <i>permission to access the
* codebase loader</i> if it has the appropriate permission for each
* of the URLs in the codebase loader's annotation URL path, where the
* appropriate permission for a URL is defined as follows. If the
* result of invoking {@link URL#openConnection
* openConnection()}.{@link URLConnection#getPermission
* getPermission()} on the <code>URL</code> object is not a {@link
* FilePermission} or if it is a <code>FilePermission</code> whose
* name does not contain a directory separator, then that permission
* is the appropriate permission. If it is a
* <code>FilePermission</code> whose name contains a directory
* separator, then the appropriate permission is a
* <code>FilePermission</code> with action <code>"read"</code> and the
* same name except with the last path segment replaced with
* <code>"-"</code> (that is, permission to read all files in the same
* directory and all subdirectories).
*
* </ul>
*
* <p>When <code>PreferredClassProvider</code> attempts to load a
* class (or interface) named <code><i>N</i></code> using class loader
* <code><i>L</i></code>, it does so in a manner equivalent to
* evaluating the following expression:
*
* <pre>
* Class.forName(<code><i>N</i></code>, false, <code><i>L</i></code>)
* </pre>
*
* In particular, the case of <code><i>N</i></code> being the binary
* name of an array class is supported.
*
* @author Sun Microsystems, Inc.
* @since 2.0
*
* @com.sun.jini.impl
*
* <p>This implementation uses the {@link Logger} named
* <code>net.jini.loader.pref.PreferredClassProvider</code> to log
* information at the following levels:
*
* <table summary="Describes what is logged by PreferredClassProvider
* at various logging levels" border=1 cellpadding=5>
*
* <tr> <th> Level <th> Description
*
* <tr> <td> {@link Levels#FAILED FAILED} <td> class loading failures
*
* <tr> <td> {@link Levels#HANDLED HANDLED} <td> exceptions caught
* during class loading operations
*
* <tr> <td> {@link Level#FINE FINE} <td> invocations of {@link
* #loadClass loadClass} and {@link #loadProxyClass loadProxyClass}
*
* <tr> <td> {@link Level#FINEST FINEST} <td> detailed activity of
* <code>loadClass</code> and <code>loadProxyClass</code>
* implementations
*
* </table>
**/
public class PreferredClassProvider extends RMIClassLoaderSpi {
/** encodings for primitive array class element types */
private static final String PRIMITIVE_TYPES = "BCDFIJSZ";
/** download from codebases with no dl perm allowed? */
private final boolean requireDlPerm;
/** true if constructor has completed successfully */
private final boolean initialized;
/** provider logger */
private static final Logger logger =
Logger.getLogger("net.jini.loader.pref.PreferredClassProvider");
private static final Permission getClassLoaderPermission =
new RuntimePermission("getClassLoader");
/**
* value of "java.rmi.server.codebase" property, as cached at class
* initialization time. It may contain malformed URLs.
*/
private static String codebaseProperty = null;
static {
String prop = (String) AccessController.doPrivileged(
new GetPropertyAction("java.rmi.server.codebase"));
if (prop != null && prop.trim().length() > 0) {
codebaseProperty = prop;
}
}
/** table of "local" class loaders */
private static final Map localLoaders =
Collections.synchronizedMap(new WeakHashMap());
static {
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
for (ClassLoader loader = ClassLoader.getSystemClassLoader();
loader != null;
loader = loader.getParent())
{
localLoaders.put(loader, null);
}
return null;
}
});
}
/**
* table mapping codebase URL path and context class loader pairs
* to class loader instances. Entries hold class loaders with weak
* references, so this table does not prevent loaders from being
* garbage collected.
*/
private final Map loaderTable = new HashMap();
/** reference queue for cleared class loader entries */
private final ReferenceQueue refQueue = new ReferenceQueue();
/**
* Creates a new <code>PreferredClassProvider</code>.
*
* <p>This constructor is used by the {@link RMIClassLoader}
* service provider location mechanism when
* <code>PreferredClassProvider</code> is configured as the
* <code>RMIClassLoader</code> provider class.
*
* <p>If there is a security manager, its {@link
* SecurityManager#checkCreateClassLoader checkCreateClassLoader}
* method is invoked; this could result in a
* <code>SecurityException</code>.
*
* <p>{@link DownloadPermission} is not enforced by the created
* provider.
*
* @throws SecurityException if there is a security manager and
* the invocation of its <code>checkCreateClassLoader</code>
* method fails
**/
public PreferredClassProvider() {
this(false);
}
/**
* Creates a new <code>PreferredClassProvider</code>.
*
* <p>This constructor is used by subclasses to control whether
* or not {@link DownloadPermission} is enforced.
*
* <p>If there is a security manager, its {@link
* SecurityManager#checkCreateClassLoader checkCreateClassLoader}
* method is invoked; this could result in a
* <code>SecurityException</code>.
*
* @param requireDlPerm if <code>true</code>, the class loaders
* created by the provider will only define classes with a {@link
* CodeSource} that is granted {@link DownloadPermission}
*
* @throws SecurityException if there is a security manager and
* the invocation of its <code>checkCreateClassLoader</code>
* method fails
**/
protected PreferredClassProvider(boolean requireDlPerm) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkCreateClassLoader();
}
this.requireDlPerm = requireDlPerm;
initialized = true;
}
private void checkInitialized() {
if (!initialized) {
throw new SecurityException("uninitialized instance");
}
}
/**
* Map to hold permissions needed to check the URLs of
* URLClassLoader objects.
*/
private final Map classLoaderPerms = new WeakHashMap();
/*
* Check permissions to load from the specified loader. The
* supplied URLs must be the loader's annotation URLs unless the
* loader is identical to the "parent" argument-- this method
* checks for permission to access those URLs.
*
* If the specified loader is identical to the "parent" argument,
* no permissions are checked, because the caller could have used
* an empty codebase to achieve the same effect anyway.
*/
private void checkLoader(ClassLoader loader, ClassLoader parent,
URL[] urls)
{
SecurityManager sm = System.getSecurityManager();
if ((sm != null) && (loader != null) && (loader != parent)) {
assert urlsMatchLoaderAnnotation(urls, loader);
if (loader.getClass() == PreferredClassLoader.class) {
((PreferredClassLoader) loader).checkPermissions();
} else {
Permissions perms;
synchronized (classLoaderPerms) {
perms = (Permissions) classLoaderPerms.get(loader);
if (perms == null) {
perms = new Permissions();
PreferredClassLoader.addPermissionsForURLs(
urls, perms, false);
classLoaderPerms.put(loader, perms);
}
}
Enumeration en = perms.elements();
while (en.hasMoreElements()) {
sm.checkPermission((Permission) en.nextElement());
}
}
}
}
/**
* Provides the implementation for {@link
* RMIClassLoaderSpi#loadClass(String,String,ClassLoader)}.
*
* <p><code>PreferredClassProvider</code> implements this method
* as follows:
*
* <p>If <code>name</code> is the binary name of an array class
* (of one or more dimensions) with a primitive element type, this
* method returns the <code>Class</code> for that array class.
*
* <p>Otherwise, if <code>defaultLoader</code> is not
* <code>null</code> and any of the following conditions are true:
*
* <ul>
*
* <li>There is no security manager.
*
* <li>The codebase loader is not the current thread's context
* class loader and the current security context does not have
* permission to access the codebase loader.
*
* <li><code>codebase</code> is <code>null</code>.
*
* <li>The specified codebase URL path is equal to the annotation
* URL path of <code>defaultLoader</code>.
*
* <li>The codebase loader is not an instance of {@link
* PreferredClassLoader}.
*
* <li>The codebase loader is an instance of
* <code>PreferredClassLoader</code> and an invocation of {@link
* PreferredClassLoader#isPreferredResource isPreferredResource}
* on the codebase loader with the class name described below as
* the first argument and <code>true</code> as the second argument
* returns <code>false</code>. If <code>name</code> is the binary
* name of an array class (of one or more dimensions) with a
* element type that is a reference type, the class name passed to
* <code>isPreferredResource</code> is the binary name of that
* element type; otherwise, the class name passed to
* <code>isPreferredResource</code> is <code>name</code>. This
* invocation is only done if none of the previous conditions are
* true. If <code>isPreferredResource</code> throws an
* <code>IOException</code>, this method throws a
* <code>ClassNotFoundException</code>.
*
* </ul>
*
* then this method attempts to load the class with the specified
* name using <code>defaultLoader</code>. If this attempt
* succeeds, this method returns the resulting <code>Class</code>;
* if it throws a <code>ClassNotFoundException</code>, this method
* proceeds as follows.
*
* <p>Otherwise, this method attempts to load the class with the
* specified name using the codebase loader, if there is a
* security manager and the current security context has
* permission to access the codebase loader, or using the current
* thread's context class loader otherwise. If this attempt
* succeeds, this method returns the resulting <code>Class</code>;
* if it throws a <code>ClassNotFoundException</code>, this method
* throws a <code>ClassNotFoundException</code>.
*
* @param codebase the codebase URL path as a space-separated list
* of URLs, or <code>null</code>
*
* @param name the binary name of the class to load
*
* @param defaultLoader additional contextual class loader
* to use, or <code>null</code>
*
* @return the <code>Class</code> object representing the loaded class
*
* @throws MalformedURLException if <code>codebase</code> is
* non-<code>null</code> and contains an invalid URL
*
* @throws ClassNotFoundException if a definition for the class
* could not be loaded
**/
public Class loadClass(String codebase,
String name,
ClassLoader defaultLoader)
throws MalformedURLException, ClassNotFoundException
{
checkInitialized();
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE,
"name=\"{0}\", codebase={1}, defaultLoader={2}",
new Object[] {
name,
codebase != null ? "\"" + codebase + "\"" : null,
defaultLoader
});
}
URL[] codebaseURLs = pathToURLs(codebase); // may be null
// throws MalformedURLException
/*
* Process array class names.
*/
String elementTypeName = null;
int len = name.length();
if (len > 0 && name.charAt(0) == '[') {
int i = 1;
char c = 0;
while (i < len && (c = name.charAt(i)) == '[') {
i++;
}
if (len == i + 1 && PRIMITIVE_TYPES.indexOf(c) != -1) {
return Class.forName(name);
}
if (len > i + 2 && c == 'L' && name.charAt(len - 1) == ';') {
elementTypeName = name.substring(i + 1, len - 1);
}
}
/*
* Try defaultLoader cases that don't require determining the
* codebase loader.
*/
SecurityManager sm = System.getSecurityManager();
if (defaultLoader != null &&
(sm == null || codebaseURLs == null ||
urlsMatchLoaderAnnotation(codebaseURLs, defaultLoader)))
{
try {
Class c = Class.forName(name, false, defaultLoader);
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "class \"{0}\" found " +
"via defaultLoader, defined by {1}",
new Object[] { name, getClassLoader(c) });
}
return c;
} catch (ClassNotFoundException e) {
defaultLoader = null; // don't try defaultLoader again
}
}
/*
* Determine the codebase loader.
*/
ClassLoader contextLoader = getRMIContextClassLoader();
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST,
"(thread context class loader: {0})", contextLoader);
}
ClassLoader codebaseLoader = lookupLoader(codebaseURLs, contextLoader);
/*
* Try remaining defaultLoader cases that don't require
* checking permission to access the codebase loader.
*/
if (defaultLoader != null &&
!(codebaseLoader instanceof PreferredClassLoader))
{
try {
Class c = Class.forName(name, false, defaultLoader);
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "class \"{0}\" found " +
"via defaultLoader, defined by {1}",
new Object[] { name, getClassLoader(c) });
}
return c;
} catch (ClassNotFoundException e) {
defaultLoader = null; // don't try defaultLoader again
}
}
/*
* Check permission to access the codebase loader.
*/
SecurityException secEx = null;
if (sm != null) {
try {
checkLoader(codebaseLoader, contextLoader, codebaseURLs);
} catch (SecurityException e) {
secEx = e;
}
}
/*
* Try remining defaultLoader cases.
*/
if (defaultLoader != null) {
boolean tryDL = secEx != null;
if (!tryDL) {
try {
tryDL = !((PreferredClassLoader) codebaseLoader).
isPreferredResource(elementTypeName != null ?
elementTypeName : name, true);
} catch (IOException e) {
ClassNotFoundException cnfe =
new ClassNotFoundException(name +
" (could not determine preferred setting;" +
" original codebase: \"" + codebase + "\")", e);
if (logger.isLoggable(Levels.FAILED)) {
LogUtil.logThrow(logger, Levels.FAILED,
PreferredClassProvider.class, "loadClass",
"class \"{0}\" not found, " +
"could not obtain preferred value",
new Object[] { name }, cnfe);
}
throw cnfe;
}
}
if (tryDL) {
try {
Class c = Class.forName(name, false, defaultLoader);
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST, "class \"{0}\" found " +
"via defaultLoader, defined by {1}",
new Object[] { name, getClassLoader(c) });
}
return c;
} catch (ClassNotFoundException e) {
}
}
}
/*
* Done with defaultLoader, try the codebase loader or the
* context class loader as appropriate.
*/
try {
Class c = Class.forName(name, false,
(sm != null && secEx == null ?
codebaseLoader : contextLoader));
if (logger.isLoggable(Level.FINEST)) {
String message;
if (sm == null) {
message = "class \"{0}\" found " +
" via thread context class loader " +
" (no security manager), defined by {1}";
} else if (secEx != null) {
message = "class \"{0}\" found " +
" via thread context class loader " +
" (access to codebase loader denied), defined by {1}";
} else {
message = "class \"{0}\" found " +
"via codebase loader, defined by {1}";
}
logger.log(Level.FINEST, message,
new Object[] { name, getClassLoader(c) });
}
return c;
} catch (ClassNotFoundException e) {
if (sm == null) {
ClassNotFoundException cnfe =
new ClassNotFoundException(e.getMessage() +
" (no security manager: codebase loader disabled)", e);
if (logger.isLoggable(Levels.FAILED)) {
LogUtil.logThrow(logger, Levels.FAILED,
PreferredClassProvider.class, "loadClass",
"class \"{0}\" not found " +
"via thread context class loader " +
"(no security manager)",
new Object[] { name }, cnfe);
}
throw cnfe;
} else if (secEx != null) {
/*
* Presumably the original security exception is
* preferable to throw, but log both exceptions.
*/
if (logger.isLoggable(Levels.HANDLED)) {
LogUtil.logThrow(logger, Levels.HANDLED,
PreferredClassProvider.class, "loadClass",
"class \"{0}\" not found " +
"via thread context class loader " +
"(access to codebase loader denied)",
new Object[] { name }, e);
}
ClassNotFoundException cnfe =
new ClassNotFoundException(e.getMessage() +
" (access to codebase loader denied)", secEx);
if (logger.isLoggable(Levels.FAILED)) {
LogUtil.logThrow(logger, Levels.FAILED,
PreferredClassProvider.class, "loadClass",
"class \"{0}\" not found " +
"via thread context class loader " +
"(access to codebase loader denied)",
new Object[] { name }, cnfe);
}
throw cnfe;
} else {
if (logger.isLoggable(Levels.FAILED)) {
LogUtil.logThrow(logger, Levels.FAILED,
PreferredClassProvider.class, "loadClass",
"class \"{0}\" not found via codebase loader",
new Object[] { name }, e);
}
throw e;
}
}
}
/**
* Provides the implementation for {@link
* RMIClassLoaderSpi#getClassAnnotation(Class)}.
*
* <p><code>PreferredClassProvider</code> implements this method
* as follows:
*
* <p>If <code>cl</code> is an array class (of one or more
* dimensions) with a primitive element type, this method returns
* <code>null</code>.
*
* <p>Otherwise, this method returns the annotation string for the
* defining class loader of <code>cl</code>, except that if the
* annotation string would be determined by an invocation of
* {@link URLClassLoader#getURLs URLClassLoader.getURLs} on that
* loader and the current security context does not have the
* permissions necessary to connect to each URL returned by that
* invocation (where the permission to connect to a URL is
* determined by invoking {@link URL#openConnection
* openConnection()}.{@link URLConnection#getPermission
* getPermission()} on the <code>URL</code> object), this method
* returns the result of invoking {@link
* #getClassAnnotation(ClassLoader)
* getClassAnnotation(ClassLoader)} with the loader instead.
*
* @param cl the class to obtain the annotation string for
*
* @return the annotation string for the class, or
* <code>null</code>
**/
public String getClassAnnotation(Class cl) {
checkInitialized();
String name = cl.getName();
/*
* Class objects for arrays of primitive types never need an
* annotation, because they never need to be (or can be) downloaded.
*
* REMIND: should we (not) be annotating classes that are in
* "java.*" packages?
*/
int nameLength = name.length();
if (nameLength > 0 && name.charAt(0) == '[') {
// skip past all '[' characters (see bugid 4211906)
int i = 1;
while (nameLength > i && name.charAt(i) == '[') {
i++;
}
if (nameLength > i && name.charAt(i) != 'L') {
return null;
}
}
return getLoaderAnnotation(getClassLoader(cl), true);
}
/**
* Returns the annotation string for the specified class loader.
*
* <p>This method is invoked in order to determine the annotation
* string for the system class loader, an ancestor of the system
* class loader, any class loader that is not an instance of
* {@link ClassAnnotation} or {@link URLClassLoader}, or (for an
* invocation of {@link #getClassAnnotation(Class)
* getClassAnnotation(Class)}) a <code>URLClassLoader</code> for
* which the current security context does not have the
* permissions necessary to connect to all of its URLs.
*
* <p><code>PreferredClassProvider</code> implements this method
* as follows:
*
* <p>This method returns the value of the system property
* <code>"java.rmi.server.codebase"</code> (or possibly an earlier
* cached value).
*
* @param loader the class loader to obtain the annotation string
* for
*
* @return the annotation string for the class loader, or
* <code>null</code>
**/
protected String getClassAnnotation(ClassLoader loader) {
checkInitialized();
return codebaseProperty;
}
/**
* Returns the annotation string for the specified class loader
* (possibly null). If check is true and the annotation would be
* determined from an invocation of URLClassLoader.getURLs() on
* the loader, only return the true annotation if the current
* security context has permission to connect to all of the URLs.
**/
private String getLoaderAnnotation(ClassLoader loader, boolean check) {
if (isLocalLoader(loader)) {
return getClassAnnotation(loader);
}
/*
* Get the codebase URL path for the class loader, if it supports
* such a notion (i.e., if it is a URLClassLoader or subclass).
*/
String annotation = null;
if (loader instanceof ClassAnnotation) {
/*
* If the class loader is one of our RMI class loaders, we have
* already computed the class annotation string, and no
* permissions are required to know the URLs.
*/
annotation = ((ClassAnnotation) loader).getClassAnnotation();
} else if (loader instanceof URLClassLoader) {
try {
URL[] urls = ((URLClassLoader) loader).getURLs();
if (urls != null) {
if (check) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
Permissions perms = new Permissions();
for (int i = 0; i < urls.length; i++) {
Permission p =
urls[i].openConnection().getPermission();
if (p != null) {
if (!perms.implies(p)) {
sm.checkPermission(p);
perms.add(p);
}
}
}
}
}
annotation = PreferredClassLoader.urlsToPath(urls);
}
} catch (SecurityException e) {
/*
* If access was denied to the knowledge of the class
* loader's URLs, fall back to the default behavior.
*/
} catch (IOException e) {
/*
* This shouldn't happen, although it is declared to be
* thrown by openConnection() and getPermission(). If it
* does happen, forget about this class loader's URLs and
* fall back to the default behavior.
*/
}
}
if (annotation != null) {
return annotation;
} else {
return getClassAnnotation(loader);
}
}
/**
* Return true if the given loader is the system class loader or
* its parent (i.e. the loader for installed extensions) or the null
* class loader
*/
private static boolean isLocalLoader(ClassLoader loader) {
return (loader == null || localLoaders.containsKey(loader));
}
/**
* Provides the implementation for {@link
* RMIClassLoaderSpi#getClassLoader(String)}.
*
* <p><code>PreferredClassProvider</code> implements this method
* as follows:
*
* <p>If there is a security manager, its
* <code>checkPermission</code> method is invoked with a
* <code>RuntimePermission("getClassLoader")</code> permission;
* this could result in a <code>SecurityException</code>. Also,
* if there is a security manager, the codebase loader is not the
* current thread's context class loader, and the current security
* context does not have permission to access the codebase loader,
* this method throws a <code>SecurityException</code>.
*
* <p>This method returns the codebase loader if there is a
* security manager, or the current thread's context class loader
* otherwise.
*
* @param codebase the codebase URL path as a space-separated list
* of URLs, or <code>null</code>
*
* @return a class loader for the specified codebase URL path
*
* @throws MalformedURLException if <code>codebase</code> is
* non-<code>null</code> and contains an invalid URL
*
* @throws SecurityException if there is a security manager and
* the invocation of its <code>checkPermission</code> method
* fails, or if the current security context does not have the
* permissions necessary to connect to all of the URLs in the
* codebase URL path
**/
public ClassLoader getClassLoader(String codebase)
throws MalformedURLException
{
checkInitialized();
URL[] codebaseURLs = pathToURLs(codebase); // may be null
// throws MalformedURLException
ClassLoader contextLoader = getRMIContextClassLoader();
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(getClassLoaderPermission);
} else {
return contextLoader;
}
ClassLoader codebaseLoader = lookupLoader(codebaseURLs, contextLoader);
checkLoader(codebaseLoader, contextLoader, codebaseURLs);
return codebaseLoader;
}
/**
* Provides the implementation of {@link
* RMIClassLoaderSpi#loadProxyClass(String,String[],ClassLoader)}.
*
* <p><code>PreferredClassProvider</code> implements this method
* as follows:
*
* <p>If <code>defaultLoader</code> is not <code>null</code> and
* any of the following conditions are true:
*
* <ul>
*
* <li>There is no security manager.
*
* <li>The codebase loader is not the current thread's context
* class loader and the current security context does not have
* permission to access the codebase loader.
*
* <li><code>codebase</code> is <code>null</code>.
*
* <li>The specified codebase URL path is equal to the annotation
* URL path of <code>defaultLoader</code>.
*
* <li>The codebase loader is not an instance of {@link
* PreferredClassLoader}.
*
* <li>The codebase loader is an instance of
* <code>PreferredClassLoader</code> and an invocation of {@link
* PreferredClassLoader#isPreferredResource isPreferredResource}
* on the codebase loader for each element of
* <code>interfaces</code>, with the element as the first argument
* and <code>true</code> as the second argument, all return
* <code>false</code>. These invocations are only done if none of
* the previous conditions are true. If any invocation of
* <code>isPreferredResource</code> throws an
* <code>IOException</code>, this method throws a
* <code>ClassNotFoundException</code>.
*
* </ul>
*
* then this method attempts to load all of the interfaces named
* by the elements of <code>interfaces</code> using
* <code>defaultLoader</code>. If all of the interfaces are
* loaded successfully, then
*
* <ul>
*
* <li>If all of the loaded interfaces are <code>public</code>: if
* there is a security manager, the codebase loader is the current
* thread's context class loader or the current security context
* has permission to access the codebase loader, and the
* annotation URL path for the codebase loader is not equal to the
* annotation URL path for <code>defaultLoader</code>, this method
* first attempts to get a dynamic proxy class (using {@link
* Proxy#getProxyClass Proxy.getProxyClass}) that is defined by
* the codebase loader and that implements all of the interfaces,
* and if this attempt succeeds, this method returns the resulting
* <code>Class</code>. Otherwise, this method attempts to get a
* dynamic proxy class that is defined by
* <code>defaultLoader</code> and that implements all of the
* interfaces. If that attempt succeeds, this method returns the
* resulting <code>Class</code>; if it throws an
* <code>IllegalArgumentException</code>, this method throws a
* <code>ClassNotFoundException</code>.
*
* <li>If all of the non-<code>public</code> interfaces are
* defined by the same class loader: this method attempts to get a
* dynamic proxy class that is defined by that loader and that
* implements all of the interfaces. If this attempt succeeds,
* this method returns the resulting <code>Class</code>; if it
* throws an <code>IllegalArgumentException</code>, this method
* throws a <code>ClassNotFoundException</code>.
*
* <li>Otherwise (if there are two or more non-<code>public</code>
* interfaces defined by different class loaders): this method
* throws a <code>LinkageError</code>.
*
* </ul>
*
* If any of the attempts to load one of the interfaces throws a
* <code>ClassNotFoundException</code>, this method proceeds as
* follows.
*
* <p>Otherwise, this method attempts to load all of the
* interfaces named by the elements of <code>interfaces</code>
* using the codebase loader, if there is a security manager and
* the current security context has permission to access the
* codebase loader, or using the current thread's context class
* loader otherwise. If all of the interfaces are loaded
* successfully, then
*
* <ul>
*
* <li>If all of the loaded interfaces are <code>public</code>:
* this method attempts to get a dynamic proxy class that is
* defined by the loader used to load the interfaces and that
* implements all of the interfaces. If this attempt succeeds,
* this method returns the resulting <code>Class</code>; if it
* throws an <code>IllegalArgumentException</code>, this method
* throws a <code>ClassNotFoundException</code>.
*
* <li>If all of the non-<code>public</code> interfaces are
* defined by the same class loader: this method attempts to get a
* dynamic proxy class that is defined by that loader and that
* implements all of the interfaces. If this attempt succeeds,
* this method returns the resulting <code>Class</code>; if it
* throws an <code>IllegalArgumentException</code>, this method
* throws a <code>ClassNotFoundException</code>.
*
* <li>Otherwise (if there are two or more non-<code>public</code>
* interfaces defined by different class loaders): this method
* throws a <code>LinkageError</code>.
*
* </ul>
*
* If any of the attempts to load one of the interfaces throws a
* <code>ClassNotFoundException</code>, this method throws a
* <code>ClassNotFoundException</code>.
*
* @param codebase the codebase URL path as a space-separated list
* of URLs, or <code>null</code>
*
* @param interfaceNames the binary names of the interfaces for
* the proxy class to implement
*
* @return a dynamic proxy class that implements the named
* interfaces
*
* @param defaultLoader additional contextual class loader to use,
* or <code>null</code>
*
* @throws MalformedURLException if <code>codebase</code> is
* non-<code>null</code> and contains an invalid URL
*
* @throws ClassNotFoundException if a definition for one of the
* named interfaces could not be loaded, or if creation of the
* dynamic proxy class failed (such as if
* <code>Proxy.getProxyClass</code> would throw an
* <code>IllegalArgumentException</code> for the given interface
* list)
**/
public Class loadProxyClass(String codebase,
String[] interfaceNames,
ClassLoader defaultLoader)
throws MalformedURLException, ClassNotFoundException
{
checkInitialized();
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE,
"interfaces={0}, codebase={1}, defaultLoader={2}",
new Object[] {
Arrays.asList(interfaceNames),
codebase != null ? "\"" + codebase + "\"" : null,
defaultLoader
});
}
URL[] codebaseURLs = pathToURLs(codebase); // may be null
// throws MalformedURLException
/*
* Determine the codebase loader.
*/
ClassLoader contextLoader = getRMIContextClassLoader();
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST,
"(thread context class loader: {0})", contextLoader);
}
ClassLoader codebaseLoader = lookupLoader(codebaseURLs, contextLoader);
/*
* Check permission to access the codebase loader.
*/
SecurityManager sm = System.getSecurityManager();
SecurityException secEx = null;
if (sm != null) {
try {
checkLoader(codebaseLoader, contextLoader, codebaseURLs);
} catch (SecurityException e) {
secEx = e;
}
}
/*
* Try all defaultLoader cases.
*/
if (defaultLoader != null) {
boolean codebaseMatchesDL = false;
boolean tryDL =
sm == null || secEx != null ||
codebaseURLs == null;
if (!tryDL) {
codebaseMatchesDL =
urlsMatchLoaderAnnotation(codebaseURLs, defaultLoader);
tryDL = codebaseMatchesDL ||
!(codebaseLoader instanceof PreferredClassLoader) ||
!interfacePreferred((PreferredClassLoader) codebaseLoader,
interfaceNames, codebase);
// throws ClassNotFoundException if IOException occurs
}
if (tryDL) {
try {
boolean preferCodebaseLoader =
sm != null && secEx == null && !codebaseMatchesDL;
Class c = loadProxyClass(interfaceNames,
defaultLoader, "defaultLoader",
codebaseLoader,
preferCodebaseLoader);
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST,
getProxySuccessLogMessage(sm, secEx),
getClassLoader(c));
}
return c;
} catch (ClassNotFoundException e) {
} catch (IllegalArgumentException e) {
ClassNotFoundException cnfe = new ClassNotFoundException(
"dynamic proxy class creation failed", e);
if (logger.isLoggable(Levels.FAILED)) {
logger.log(Levels.FAILED,
"dynamic proxy class creation failed", e);
}
throw cnfe;
}
}
}
/*
* Try the codebase loader or the context class loader as
* appropriate.
*/
ClassLoader loader;
String loaderName;
if (sm != null && secEx == null) {
loader = codebaseLoader;
loaderName = "codebase loader";
} else {
loader = contextLoader;
loaderName = "thread context class loader";
}
try {
Class c = loadProxyClass(interfaceNames, loader, loaderName,
null, false);
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST,
getProxySuccessLogMessage(sm, secEx),
getClassLoader(c));
}
return c;
} catch (ClassNotFoundException e) {
if (sm == null) {
ClassNotFoundException cnfe =
new ClassNotFoundException(e.getMessage() +
" (no security manager: codebase loader disabled)", e);
if (logger.isLoggable(Levels.FAILED)) {
logger.log(Levels.FAILED,
"proxy class resolution failed (no security manager)",
cnfe);
}
throw cnfe;
} else if (secEx != null) {
/*
* Presumably the original security exception is
* preferable to throw, but log both exceptions.
*/
if (logger.isLoggable(Levels.HANDLED)) {
logger.log(Levels.HANDLED,
"proxy class resolution failed " +
"(access to codebase loader denied)", e);
}
ClassNotFoundException cnfe =
new ClassNotFoundException(e.getMessage() +
" (access to codebase loader denied)", secEx);
if (logger.isLoggable(Levels.FAILED)) {
logger.log(Levels.FAILED,
"proxy class resolution failed " +
"(access to codebase loader denied)", cnfe);
}
throw cnfe;
} else {
if (logger.isLoggable(Levels.FAILED)) {
logger.log(Levels.FAILED,
"proxy class resolution failed", e);
}
throw e;
}
} catch (IllegalArgumentException e) {
ClassNotFoundException cnfe = new ClassNotFoundException(
"dynamic proxy class creation failed", e);
if (logger.isLoggable(Levels.FAILED)) {
logger.log(Levels.FAILED,
"dynamic proxy class creation failed", e);
}
throw cnfe;
}
}
private static String getProxySuccessLogMessage(SecurityManager sm,
SecurityException secEx)
{
if (sm == null) {
return "(no security manager) proxy class defined by {0}";
} else if (secEx != null) {
return
"(access to codebase loader denied) " +
"proxy class defined by {0}";
} else {
return "proxy class defined by {0}";
}
}
/**
* Attempts to load the specified interfaces by name using the
* specified loader, and if that is successul, attempts to get a
* dynamic proxy class that implements those interfaces.
*
* If tryOtherLoaderFirst is true, attempts to get the proxy class
* defined by the specified other loader first, and if that fails,
* falls back to get the proxy class defined by the same loader
* used to load the interfaces; otherwise, only attempts to get
* the proxy class defined by the same loader used to load the
* interfaces.
*
* Throws a ClassNotFoundException if attempting to load any of
* the interfaces throws a ClassNotFoundException. Throws
* IllegalArgumentException if the final attempt to get a proxy
* class throws an IllegalArgumentException.
**/
private Class loadProxyClass(String[] interfaceNames,
ClassLoader interfaceLoader,
String interfaceLoaderName,
ClassLoader otherLoader,
boolean tryOtherLoaderFirst)
throws ClassNotFoundException
{
Class[] classObjs = new Class[interfaceNames.length];
boolean[] nonpublic = { false };
ClassLoader proxyLoader =
loadProxyInterfaces(interfaceNames, interfaceLoader,
classObjs, nonpublic);
if (logger.isLoggable(Level.FINEST)) {
ClassLoader[] definingLoaders = new ClassLoader[classObjs.length];
for (int i = 0; i < definingLoaders.length; i++) {
definingLoaders[i] = getClassLoader(classObjs[i]);
}
logger.log(Level.FINEST,
"proxy interfaces loaded via {0}, defined by {1}",
new Object[] {
interfaceLoaderName, Arrays.asList(definingLoaders)
});
}
if (!nonpublic[0]) {
if (tryOtherLoaderFirst) {
try {
return Proxy.getProxyClass(otherLoader, classObjs);
} catch (IllegalArgumentException e) {
}
}
proxyLoader = interfaceLoader;
}
return Proxy.getProxyClass(proxyLoader, classObjs);
}
/**
* Returns true if at least one of the specified interface names
* is preferred for the specified class loader; returns false if
* none of them are preferred. Throws ClassNotFoundException if
* PreferredClassLoader.isPreferredResource throws IOException
* (although isPreferredResource isn't necessarily invoked for all
* of the specified names, because this method short circuits on
* the first invocation that returns true). The codebase argument
* is the original codebase URL path passed to loadProxyClass,
* which typically but not necessarily equals the loader's import
* URL path.
**/
private boolean interfacePreferred(PreferredClassLoader codebaseLoader,
String[] interfaceNames,
String codebase)
throws ClassNotFoundException
{
for (int p = 0; p < interfaceNames.length; p++) {
try {
if (((PreferredClassLoader) codebaseLoader).
isPreferredResource(interfaceNames[p], true))
{
return true;
}
} catch (IOException e) {
ClassNotFoundException cnfe =
new ClassNotFoundException(interfaceNames[p] +
" (could not determine preferred setting;" +
" original codebase: \"" + codebase + "\")", e);
if (logger.isLoggable(Levels.FAILED)) {
LogUtil.logThrow(logger, Levels.FAILED,
PreferredClassProvider.class, "loadProxyClass",
"class \"{0}\" not found, " +
"could not obtain preferred value",
new Object[] { interfaceNames[p] }, cnfe);
}
throw cnfe;
}
}
return false;
}
/**
* Returns an array of URLs corresponding to the annotation string
* for the specified class loader, or null if the annotation
* string is null.
**/
private URL[] getLoaderAnnotationURLs(ClassLoader loader)
throws MalformedURLException
{
return pathToURLs(getLoaderAnnotation(loader, false));
}
/**
* Returns true if the specified path of URLs is equal to the
* annotation URLs of the specified loader, and false otherwise.
**/
private boolean urlsMatchLoaderAnnotation(URL[] urls, ClassLoader loader) {
try {
return Arrays.equals(urls, getLoaderAnnotationURLs(loader));
} catch (MalformedURLException e) {
return false;
}
}
/*
* Load Class objects for the names in the interfaces array from
* the given class loader.
*
* We pass classObjs and useNonpublic array to avoid needing a
* multi-element return value. useNonpublic is an array to enable
* the method to take a boolean argument by reference.
*
* useNonpublic array is needed to signal when the return value of
* this method should be used as the proxy class loader. Because
* null represents a valid class loader, that value is
* insufficient to signal that the return value should not be used
* as the proxy class loader.
*/
private ClassLoader loadProxyInterfaces(String[] interfaces,
ClassLoader loader,
Class[] classObjs,
boolean[] useNonpublic)
throws ClassNotFoundException
{
/* loader of a non-public interface class */
ClassLoader nonpublic = null;
for (int i = 0; i < interfaces.length; i++) {
Class cl =
(classObjs[i] = Class.forName(interfaces[i], false, loader));
if (!Modifier.isPublic(cl.getModifiers())) {
ClassLoader current = getClassLoader(cl);
if (logger.isLoggable(Level.FINEST)) {
logger.logp(Level.FINEST,
PreferredClassProvider.class.getName(),
"loadProxyClass",
"non-public interface \"{0}\" defined by {1}",
new Object[] { interfaces[i], current });
}
if (!useNonpublic[0]) {
nonpublic = current;
useNonpublic[0] = true;
} else if (current != nonpublic) {
throw new IllegalAccessError(
"non-public interfaces defined in different " +
"class loaders");
}
}
}
return nonpublic;
}
/**
* Convert a string containing a space-separated list of URLs into a
* corresponding array of URL objects, throwing a MalformedURLException
* if any of the URLs are invalid. This method returns null if the
* specified string is null.
*
* @param path the string path to be converted to an array of urls
* @return the string path converted to an array of URLs, or null
* @throws MalformedURLException if the string path of urls contains a
* mal-formed url which can not be converted into a url object.
*/
private static URL[] pathToURLs(String path) throws MalformedURLException {
if (path == null) {
return null;
}
synchronized (pathToURLsCache) {
Object[] v = (Object[]) pathToURLsCache.get(path);
if (v != null) {
return ((URL[])v[0]);
}
}
StringTokenizer st = new StringTokenizer(path); // divide by spaces
URL[] urls = new URL[st.countTokens()];
for (int i = 0; st.hasMoreTokens(); i++) {
urls[i] = new URL(st.nextToken());
}
synchronized (pathToURLsCache) {
pathToURLsCache.put(path,
new Object[] {urls, new SoftReference(path)});
}
return urls;
}
/** map from weak(key=string) to [URL[], soft(key)] */
private static Map pathToURLsCache = new WeakHashMap(5);
/**
* Return the class loader to be used as the parent for an RMI class
* loader used in the current execution context.
*/
private static ClassLoader getRMIContextClassLoader() {
/*
* The current implementation simply uses the current thread's
* context class loader.
*/
return (ClassLoader) AccessController.doPrivileged(
new PrivilegedAction() {
public Object run() {
return Thread.currentThread().getContextClassLoader();
}
});
}
/**
* Return the origin class loader for the <code>pathURLs</code> or
* null if the origin loader was not present in the delegation
* hierarchy.
*
* Preferred classes introduces the "class boomerang" problem to
* RMI class loading. A class boomerang occurs when a class which
* is marked preferred is accessible from the codebase of a
* virtual machine (VM) and is loaded by that VM. Since the VM
* has a copy of the class in its own resources and the class is
* "returning to its origin" the class should not be preferred.
* If the class is preferred, it will be loaded in a class loader
* for the local codebase annotation. As a result, the class'
* type will not be compatible with types defined from the local
* definition of the class file in the relevant VM.
*
* A boomerang can also occur when a new child loader for a URL
* path is created but an ancestor of the new class loader has the
* same URL path as the new class loader. In such cases the new
* class loader should not be created. The incoming class should
* be loaded from the origin ancestor instead.
*
* A simple example of a class boomerang occurs when when a VM
* makes a remote method call to itself. Suppose an object whose
* class was loaded locally in that VM and is preferred in the
* codebase of the VM is passed in the call. When the VM receives
* its own call, an instance of the unmarshalled parameter will
* not be assignable to instances that were defined by local
* classes (i.e. never unmarshalled).
*
* In order to work around class boomerang problems, the preferred
* class provider lookupLoader algorithm is different from the
* analogous algorithm in LoaderHandler. To avoid boomerangs, the
* lookupLoader method of this class attempts to locate the
* "origin" class loader of an incoming class in a remote method
* call. Loading classes from their origin loader instead of in a
* preferred circumvents type compatibility conflicts.
*
* To find origin loaders, the lookupLoader method calls
* findOriginLoader() before locating or creating new
* PreferredClassLoaders. An origin loader is found by searching
* up the delegation hierarchy above the parent (context) class
* loader for a loader that has an export path which matches the
* parameter path.
*/
private ClassLoader findOriginLoader(final URL[] pathURLs,
final ClassLoader parent)
{
return (ClassLoader) AccessController.doPrivileged(
new PrivilegedAction() {
public Object run() {
return findOriginLoader0(pathURLs, parent);
}
});
}
private ClassLoader findOriginLoader0(URL[] pathURLs, ClassLoader parent) {
for (ClassLoader ancestor = parent;
ancestor != null;
ancestor = ancestor.getParent())
{
URL[] ancestorURLs;
try {
ancestorURLs = getLoaderAnnotationURLs(ancestor);
} catch (MalformedURLException e) {
// this ancestor's annotation must not match pathURLs
continue;
}
/* check if found a matching ancestor loader */
if (Arrays.equals(pathURLs, ancestorURLs)) {
if (logger.isLoggable(Level.FINEST)) {
logger.log(Level.FINEST,
"using an existing ancestor class loader " +
"which serves the requested codebase urls: {0}, " +
"urls: {1}",
new Object[] {
ancestor,
(ancestorURLs != null ?
Arrays.asList(ancestorURLs) : null)
});
}
return ancestor;
}
}
return null;
}
/**
* Look up the class loader for the given codebase URL path
* and the given parent class loader. A new class loader instance
* will be created and returned if no match is found.
*/
private ClassLoader lookupLoader(final URL[] urls,
final ClassLoader parent)
{
/*
* If the requested codebase is null, then PreferredClassProvider
* assumes that the class is expected to be a "platform" class
* with respect to the parent class loader.
*/
if (urls == null) {
return parent;
}
/*
* If the requested codebase URL path is empty, the supplied
* parent class loader will be sufficient.
*
* REMIND: To be conservative, this optimization is commented out
* for now so that it does not open a security hole in the future
* by providing untrusted code with direct access to the public
* loadClass() method of a class loader instance that it cannot
* get a reference to. (It's an unlikely optimization anyway.)
*
* if (urls.length == 0) {
* return parent;
* }
*/
synchronized (loaderTable) {
/*
* Take this opportunity to remove from the table entries
* whose weak references have been cleared.
*/
Object ref;
while ((ref = refQueue.poll()) != null) {
if (ref instanceof LoaderKey) {
LoaderKey key = (LoaderKey) ref;
loaderTable.remove(key);
} else if (ref instanceof LoaderEntry) {
LoaderEntry entry = (LoaderEntry) ref;
if (!entry.removed) { // ignore entries removed below
loaderTable.remove(entry.key);
}
}
}
/*
* Look up the codebase URL path and parent class loader pair
* in the table of RMI class loaders.
*/
LoaderKey key = new LoaderKey(urls, parent);
LoaderEntry entry = (LoaderEntry) loaderTable.get(key);
ClassLoader loader;
if (entry == null ||
(loader = (ClassLoader) entry.get()) == null)
{
/*
* If entry was in table but it's weak reference was cleared,
* remove it from the table and mark it as explicitly cleared,
* so that new matching entry that we put in the table will
* not be erroneously removed when this entry is processed
* from the weak reference queue.
*/
if (entry != null) {
loaderTable.remove(key);
entry.removed = true;
}
/*
* An existing loader with the given URL path and
* parent was not found. Perform the following steps
* to obtain an appropriate loader:
*
* Search for an ancestor of the parent class loader
* whose export urls match the parameter URL path
*
* If a matching ancestor could not be found, create a
* new class loader instance for the requested
* codebase URL path and parent class loader. The
* loader instance is created within an access control
* context restricted to the permissions necessary to
* load classes from its codebase URL path.
*/
loader = findOriginLoader(urls, parent);
if (loader == null) {
loader = createClassLoader(urls, parent, requireDlPerm);
}
/*
* Finally, create an entry to hold the new loader with a
* weak reference and store it in the table with the key.
*/
entry = new LoaderEntry(key, loader);
loaderTable.put(key, entry);
}
return loader;
}
}
/**
* Creates the class loader for this
* <code>PreferredClassProvider</code> to use to load classes from
* the specified path of URLs with the specified delegation
* parent.
*
* <p><code>PreferredClassProvider</code> implements this method
* as follows:
*
* <p>This method creates a new instance of {@link
* PreferredClassLoader} that loads classes and resources from
* <code>urls</code>, delegates to <code>parent</code>, and
* enforces {@link DownloadPermission} if
* <code>requireDlPerm</code> is <code>true</code>. The created
* loader uses a restricted security context to ensure that the
* URL retrieval operations undertaken by the loader cannot
* exercise a permission that is not implied by the permissions
* necessary to access the loader as a codebase loader for the
* specified path of URLs.
*
* @param urls the path of URLs to load classes and resources from
*
* @param parent the parent class loader for delegation
*
* @param requireDlPerm if <code>true</code>, the loader must only
* define classes with a {@link CodeSource} that is granted
* <code>DownloadPermission</code>
*
* @return the created class loader
*
* @since 2.1
**/
protected ClassLoader createClassLoader(final URL[] urls,
final ClassLoader parent,
final boolean requireDlPerm)
{
checkInitialized();
return (ClassLoader)
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
return new PreferredClassLoader(urls, parent, null,
requireDlPerm);
}
}, PreferredClassLoader.getLoaderAccessControlContext(urls));
}
/**
* Loader table key: a codebaser URL path and a weak reference to
* a parent class loader (possibly null). The weak reference is
* registered with "refQueue" so that the entry can be removed
* after the loader has become unreachable.
**/
private class LoaderKey extends WeakReference {
private final URL[] urls;
private final boolean nullParent;
private final int hashValue;
public LoaderKey(URL[] urls, ClassLoader parent) {
super(parent, refQueue);
this.urls = urls;
nullParent = (parent == null);
int h = nullParent ? 0 : parent.hashCode();
for (int i = 0; i < urls.length; i++) {
h ^= urls[i].hashCode();
}
hashValue = h;
}
public int hashCode() {
return hashValue;
}
public boolean equals(Object obj) {
if (obj == this) {
return true;
} else if (!(obj instanceof LoaderKey)) {
return false;
}
LoaderKey other = (LoaderKey) obj;
ClassLoader parent;
return (nullParent ? other.nullParent
: ((parent = (ClassLoader) get()) != null &&
parent == other.get()))
&& Arrays.equals(urls, other.urls);
}
}
/**
* Loader table value: a weak reference to a class loader. The
* weak reference is registered with "refQueue" so that the entry
* can be removed after the loader has become unreachable.
**/
private class LoaderEntry extends WeakReference {
public final LoaderKey key; // for efficient removal
/**
* set to true if the entry has been removed from the table
* because it has been replaced, so it should not be attempted
* to be removed again
*/
public boolean removed = false;
public LoaderEntry(LoaderKey key, ClassLoader loader) {
super(loader, refQueue);
this.key = key;
}
}
private static ClassLoader getClassLoader(final Class c) {
return (ClassLoader) AccessController.doPrivileged(
new PrivilegedAction() {
public Object run() { return c.getClassLoader(); }
});
}
}