/*
* 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;
import java.lang.ref.SoftReference;
import java.net.MalformedURLException;
import java.rmi.server.RMIClassLoader;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Map;
import java.util.WeakHashMap;
import net.jini.security.Security;
/**
* Provides static methods for loading classes using {@link
* RMIClassLoader} with optional verification that the codebase URLs
* used to load classes provide content integrity (see {@link
* Security#verifyCodebaseIntegrity
* Security.verifyCodebaseIntegrity}).
*
* @author Sun Microsystems, Inc.
* @since 2.0
**/
public final class ClassLoading {
/**
* per-thread cache (weakly) mapping verifierLoader values to
* (soft) sets of codebase values that have been verified (to
* provide content integrity) with the verifierLoader value
**/
private static final ThreadLocal perThreadCache = new ThreadLocal() {
protected Object initialValue() { return new WeakHashMap(); }
};
/**
* Loads a class using {@link
* RMIClassLoader#loadClass(String,String,ClassLoader)
* RMIClassLoader.loadClass}, optionally verifying that the
* codebase URLs provide content integrity.
*
* <p>If <code>verifyCodebaseIntegrity</code> is <code>true</code>
* and <code>codebase</code> is not <code>null</code>, then this
* method invokes {@link Security#verifyCodebaseIntegrity
* Security.verifyCodebaseIntegrity} with <code>codebase</code> as
* the first argument and <code>verifierLoader</code> as the
* second argument (this invocation may be skipped if a previous
* invocation of this method or {@link #loadProxyClass
* loadProxyClass} has already invoked
* <code>Security.verifyCodebaseIntegrity</code> with the same
* value of <code>codebase</code> and the same effective value of
* <code>verifierLoader</code> as arguments without it throwing an
* exception). If <code>Security.verifyCodebaseIntegrity</code>
* throws a <code>SecurityException</code>, then this method
* proceeds as if <code>codebase</code> were <code>null</code>.
* If <code>Security.verifyCodebaseIntegrity</code> throws any
* other exception, then this method throws that exception.
*
* <p>This method then invokes {@link
* RMIClassLoader#loadClass(String,String,ClassLoader)
* RMIClassLoader.loadClass} with <code>codebase</code> as the
* first argument (or <code>null</code> if in the previous step
* <code>Security.verifyCodebaseIntegrity</code> was invoked and
* it threw a <code>SecurityException</code>), <code>name</code>
* as the second argument, and <code>defaultLoader</code> as the
* third argument. If <code>RMIClassLoader.loadClass</code>
* throws a <code>ClassNotFoundException</code>, then this method
* throws a <code>ClassNotFoundException</code>; if
* <code>RMIClassLoader.loadClass</code> throws any other
* exception, then this method throws that exception; otherwise,
* this method returns the <code>Class</code> returned by
* <code>RMIClassLoader.loadClass</code>.
*
* @param codebase the list of URLs (separated by spaces) to load
* the class from, or <code>null</code>
*
* @param name the name of the class to load
*
* @param defaultLoader the class loader value (possibly
* <code>null</code>) to pass as the <code>defaultLoader</code>
* argument to <code>RMIClassLoader.loadClass</code>
*
* @param verifyCodebaseIntegrity if <code>true</code>, verify
* that the codebase URLs provide content integrity
*
* @param verifierLoader the class loader value (possibly
* <code>null</code>) to pass to
* <code>Security.verifyCodebaseIntegrity</code>, if
* <code>verifyCodebaseIntegrity</code> is <code>true</code>
*
* @return the <code>Class</code> object representing the loaded
* class
*
* @throws MalformedURLException if
* <code>Security.verifyCodebaseIntegrity</code> or
* <code>RMIClassLoader.loadClass</code> throws a
* <code>MalformedURLException</code>
*
* @throws ClassNotFoundException if
* <code>RMIClassLoader.loadClass</code> throws a
* <code>ClassNotFoundException</code>
*
* @throws NullPointerException if <code>name</code> is
* <code>null</code>
**/
public static Class loadClass(String codebase,
String name,
ClassLoader defaultLoader,
boolean verifyCodebaseIntegrity,
ClassLoader verifierLoader)
throws MalformedURLException, ClassNotFoundException
{
SecurityException verifyException = null;
if (verifyCodebaseIntegrity && codebase != null) {
try {
verifyIntegrity(codebase, verifierLoader);
} catch (SecurityException e) {
verifyException = e;
codebase = null;
}
}
try {
return RMIClassLoader.loadClass(codebase, name, defaultLoader);
} catch (ClassNotFoundException e) {
if (verifyException != null) {
// assume that the verify exception is more important
throw new ClassNotFoundException(e.getMessage(),
verifyException);
} else {
throw e;
}
}
}
/**
* Loads a dynamic proxy class using {@link
* RMIClassLoader#loadProxyClass(String,String[],ClassLoader)
* RMIClassLoader.loadProxyClass}, optionally verifying that the
* codebase URLs provide content integrity.
*
* <p>If <code>verifyCodebaseIntegrity</code> is <code>true</code>
* and <code>codebase</code> is not <code>null</code>, then this
* method invokes {@link Security#verifyCodebaseIntegrity
* Security.verifyCodebaseIntegrity} with <code>codebase</code> as
* the first argument and <code>verifierLoader</code> as the
* second argument (this invocation may be skipped if a previous
* invocation of this method or {@link #loadClass loadClass} has
* already invoked <code>Security.verifyCodebaseIntegrity</code>
* with the same value of <code>codebase</code> and the same
* effective value of <code>verifierLoader</code> as arguments
* without it throwing an exception). If
* <code>Security.verifyCodebaseIntegrity</code> throws a
* <code>SecurityException</code>, then this method proceeds as if
* <code>codebase</code> were <code>null</code>. If
* <code>Security.verifyCodebaseIntegrity</code> throws any other
* exception, then this method throws that exception.
*
* <p>This method invokes {@link
* RMIClassLoader#loadProxyClass(String,String[],ClassLoader)
* RMIClassLoader.loadProxyClass} with <code>codebase</code> as
* the first argument (or <code>null</code> if in the previous
* step <code>Security.verifyCodebaseIntegrity</code> was invoked
* and it threw a <code>SecurityException</code>),
* <code>interfaceNames</code> as the second argument, and
* <code>defaultLoader</code> as the third argument. If
* <code>RMIClassLoader.loadProxyClass</code> throws a
* <code>ClassNotFoundException</code>, then this method throws a
* <code>ClassNotFoundException</code>; if
* <code>RMIClassLoader.loadProxyClass</code> throws any other
* exception, then this method throws that exception; otherwise,
* this method returns the <code>Class</code> returned by
* <code>RMIClassLoader.loadProxyClass</code>.
*
* @param codebase the list of URLs (separated by spaces) to load
* classes from, or <code>null</code>
*
* @param interfaceNames the names of the interfaces for the proxy
* class to implement
*
* @param defaultLoader the class loader value (possibly
* <code>null</code>) to pass as the <code>defaultLoader</code>
* argument to <code>RMIClassLoader.loadProxyClass</code>
*
* @param verifyCodebaseIntegrity if <code>true</code>, verify
* that the codebase URLs provide content integrity
*
* @param verifierLoader the class loader value (possibly
* <code>null</code>) to pass to
* <code>Security.verifyCodebaseIntegrity</code>, if
* <code>verifyCodebaseIntegrity</code> is <code>true</code>
*
* @return the <code>Class</code> object representing the loaded
* dynamic proxy class
*
* @throws MalformedURLException if
* <code>Security.verifyCodebaseIntegrity</code> or
* <code>RMIClassLoader.loadProxyClass</code> throws a
* <code>MalformedURLException</code>
*
* @throws ClassNotFoundException if
* <code>RMIClassLoader.loadProxyClass</code> throws a
* <code>ClassNotFoundException</code>
*
* @throws NullPointerException if <code>interfaceNames</code> is
* <code>null</code> or if any element of
* <code>interfaceNames</code> is <code>null</code>
**/
public static Class loadProxyClass(String codebase,
String[] interfaceNames,
ClassLoader defaultLoader,
boolean verifyCodebaseIntegrity,
ClassLoader verifierLoader)
throws MalformedURLException, ClassNotFoundException
{
SecurityException verifyException = null;
if (verifyCodebaseIntegrity && codebase != null) {
try {
verifyIntegrity(codebase, verifierLoader);
} catch (SecurityException e) {
verifyException = e;
codebase = null;
}
}
try {
return RMIClassLoader.loadProxyClass(codebase, interfaceNames,
defaultLoader);
} catch (ClassNotFoundException e) {
if (verifyException != null) {
// assume that the verify exception is more important
throw new ClassNotFoundException(e.getMessage(),
verifyException);
} else {
throw e;
}
}
}
/**
* Wraps Security.verifyCodebaseIntegrity with caching for
* performance. (Perhaps such caching should be done by
* Security.verifyCodebaseIntegrity instead.)
**/
private static void verifyIntegrity(String codebase,
ClassLoader verifierLoader)
throws MalformedURLException
{
/*
* Check if we've already verified the same codebase in this
* thread using the same verifierLoader value.
*/
Map verifierLoaderCache = (Map) perThreadCache.get();
// defend against varying context class loader value of thread
ClassLoader verifierLoaderKey =
(verifierLoader != null ? verifierLoader :
(ClassLoader) AccessController.doPrivileged(
new PrivilegedAction() {
public Object run() {
return Thread.currentThread().getContextClassLoader();
}
}));
Map verifiedCodebases =
(Map) verifierLoaderCache.get(verifierLoaderKey);
if (verifiedCodebases != null &&
verifiedCodebases.containsKey(codebase))
{
return;
}
Security.verifyCodebaseIntegrity(codebase, verifierLoader);
/*
* Remember that we've verified this codebase in this thread
* with the given verifierLoader value.
*/
if (verifiedCodebases == null) {
verifiedCodebases = new WeakHashMap();
verifierLoaderCache.put(verifierLoaderKey, verifiedCodebases);
}
verifiedCodebases.put(codebase, new SoftReference(codebase));
return;
}
private ClassLoading() { throw new AssertionError(); }
}