/*
 * 
 * Copyright 2004 Sun Microsystems, Inc. All Rights Reserved.
 * 
 * The contents of this file are subject to the Sun Community Source
 * License v3.0/Jini Technology Specific Attachment v 1.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://wwws.sun.com/software/jini/licensing/SCSL3_JiniTSA1.html.
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 * 
 * The Reference Code is Jini Technology Core Platform code, v2.0. The
 * Developer of the Reference Code is Sun Microsystems, Inc.
 * 
 * Contributor(s): Sun Microsystems, Inc.
 * 
 * The contents of this file comply with the Jini Technology Core
 * Platform Compatibility Kit, v2.0A.
 * 
 * Tester(s): Sun Microsystems, Inc.
 * 
 * Test Platform(s):
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Solaris(TM)SPARC/x86
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Linux (Intel x86)
 * Java 2 SDK, Standard Edition, Version 1.4.2_03 for Windows (Intel Platform)
 * 
 */

package net.jini.loader;

import java.net.MalformedURLException;
import java.rmi.server.RMIClassLoader;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
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
     * (weak) sets of class loaders 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>This method invokes {@link
     * RMIClassLoader#loadClass(String,String,ClassLoader)
     * RMIClassLoader.loadClass} with <code>codebase</code> as the
     * first argument, <code>name</code> as the second argument, and
     * <code>defaultLoader</code> as the third argument.  If
     * <code>RMIClassLoader.loadClass</code> throws an exception, then
     * this method throws that exception.
     *
     * <p>If <code>verifyCodebaseIntegrity</code> is <code>true</code>
     * and <code>codebase</code> is not <code>null</code>, then the
     * following verification procedure is performed (this procedure
     * 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):
     *
     * <blockquote>
     *
     * If the defining class loader of the <code>Class</code> returned
     * by <code>RMIClassLoader.loadClass</code> is not an ancestor of
     * or identical to <code>defaultLoader</code> or the current
     * thread's context class loader, then before causing the
     * <code>Class</code> to be initialized, this method invokes
     * {@link Security#verifyCodebaseIntegrity
     * Security.verifyCodebaseIntegrity} with <code>codebase</code> as
     * the first argument and <code>verifierLoader</code> as the
     * second argument.  If
     * <code>Security.verifyCodebaseIntegrity</code> throws a
     * <code>SecurityException</code>, then this method throws a
     * <code>ClassNotFoundException</code> containing the
     * <code>SecurityException</code> as its cause.
     *
     * </blockquote>
     *
     * <p>If no exception is thrown, then 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 codebase URLs used to load the class 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>RMIClassLoader.loadClass</code> or
     * <code>Security.verifyCodebaseIntegrity</code> throws a
     * <code>MalformedURLException</code>
     *
     * @throws ClassNotFoundException if
     * <code>RMIClassLoader.loadClass</code> throws a
     * <code>ClassNotFoundException</code> or if
     * <code>Security.verifyCodebaseIntegrity</code> throws a
     * <code>SecurityException</code>
     *
     * @throws NullPointerException if <code>name</code> is
     * <code>null</code>
     **/
    public static Class loadClass(final String codebase,
				  String name,
				  final ClassLoader defaultLoader,
				  boolean verifyCodebaseIntegrity,
				  final ClassLoader verifierLoader)
	throws MalformedURLException, ClassNotFoundException
    {
	final Class c =
	    RMIClassLoader.loadClass(codebase, name, defaultLoader);
	if (verifyCodebaseIntegrity && codebase != null) {
	    try {
		Security.doPrivileged(new PrivilegedExceptionAction() {
		    public Object run()
			throws MalformedURLException, ClassNotFoundException
		    {
			verifyIntegrity(c, codebase, defaultLoader,
					verifierLoader);
			return null;
		    }
		});
	    } catch (PrivilegedActionException e) {
		Throwable cause = e.getCause();
		if (cause instanceof MalformedURLException) {
		    throw (MalformedURLException) cause;
		} else {
		    throw (ClassNotFoundException) cause;
		}
	    }
	}
	return c;
    }

    /**
     * Loads a dynamic proxy class using {@link
     * RMIClassLoader#loadProxyClass(String,String[],ClassLoader)
     * RMIClassLoader.loadProxyClass}, optionally verifying that the
     * codebase URLs provide content integrity.
     *
     * <p>This method invokes {@link
     * RMIClassLoader#loadProxyClass(String,String[],ClassLoader)
     * RMIClassLoader.loadProxyClass} with <code>codebase</code> as
     * the first argument, <code>interfaceNames</code> as the second
     * argument, and <code>defaultLoader</code> as the third argument.
     * If <code>RMIClassLoader.loadProxyClass</code> throws an
     * exception, then this method throws that exception.
     *
     * <p>If <code>verifyCodebaseIntegrity</code> is <code>true</code>
     * and <code>codebase</code> is not <code>null</code>, then the
     * following verification procedure is performed (this procedure
     * 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):
     *
     * <blockquote>
     *
     * If the defining class loader of any of the direct
     * superinterfaces of the proxy class returned by
     * <code>RMIClassLoader.loadProxyClass</code> is not an ancestor
     * of or identical to <code>defaultLoader</code> or the current
     * thread's context class loader, then before causing any such
     * interface to be initialized, this method invokes {@link
     * Security#verifyCodebaseIntegrity
     * Security.verifyCodebaseIntegrity} with <code>codebase</code> as
     * the first argument and <code>verifierLoader</code> as the
     * second argument.  If
     * <code>Security.verifyCodebaseIntegrity</code> throws a
     * <code>SecurityException</code>, then this method throws a
     * <code>ClassNotFoundException</code> containing the
     * <code>SecurityException</code> as its cause.
     *
     * </blockquote>
     *
     * <p>If no exception is thrown, then 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 codebase URLs used to load the interfaces 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>RMIClassLoader.loadProxyClass</code> or
     * <code>Security.verifyCodebaseIntegrity</code> throws a
     * <code>MalformedURLException</code>
     *
     * @throws ClassNotFoundException if
     * <code>RMIClassLoader.loadProxyClass</code> throws a
     * <code>ClassNotFoundException</code> or if
     * <code>Security.verifyCodebaseIntegrity</code> throws a
     * <code>SecurityException</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(final String codebase,
				       String[] interfaceNames,
				       final ClassLoader defaultLoader,
				       boolean verifyCodebaseIntegrity,
				       final ClassLoader verifierLoader)
	throws MalformedURLException, ClassNotFoundException
    {
	final Class c =
	    RMIClassLoader.loadProxyClass(codebase, interfaceNames,
					  defaultLoader);
	if (verifyCodebaseIntegrity && codebase != null) {
	    try {
		Security.doPrivileged(new PrivilegedExceptionAction() {
		    public Object run()
			throws MalformedURLException, ClassNotFoundException
		    {
			Class[] interfaces = c.getInterfaces();
			for (int i = 0; i < interfaces.length; i++) {
			    if (verifyIntegrity(interfaces[i], codebase,
						defaultLoader, verifierLoader))
			    {
				break;
			    }
			}
			return null;
		    }
		});
	    } catch (PrivilegedActionException e) {
		Throwable cause = e.getCause();
		if (cause instanceof MalformedURLException) {
		    throw (MalformedURLException) cause;
		} else {
		    throw (ClassNotFoundException) cause;
		}
	    }
	}
	return c;
    }

    /**
     * If the class loader of the specified class is not an ancestor
     * of or identical to the default loader or the current thread's
     * context class loader, invokes Security.verifyCodebaseIntegrity
     * with the codebase and the verifier loader, and if that throws a
     * SecurityException, throws a ClassNotFoundException with that
     * SecurityException as the cause; returns true if the codebase
     * was successfully verified, and false if verification of the
     * codebase was skipped because of the class loader relationship.
     **/
    private static boolean verifyIntegrity(Class c,
					   String codebase,
					   ClassLoader defaultLoader,
					   ClassLoader verifierLoader)
	throws MalformedURLException, ClassNotFoundException
    {
	ClassLoader loader = c.getClassLoader();

	/*
	 * Check if we've already verified a class with the same
	 * loader 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 :
	     Thread.currentThread().getContextClassLoader());
	Map verifiedLoaders = (Map) verifierLoaderCache.get(verifierLoaderKey);
	if (verifiedLoaders != null && verifiedLoaders.containsKey(loader)) {
	    return true;
	}

	if (!covers(defaultLoader, loader)) {
	    ClassLoader ccl = Thread.currentThread().getContextClassLoader();
	    if (defaultLoader == ccl || !covers(ccl, loader)) {
		try {
		    Security.verifyCodebaseIntegrity(codebase, verifierLoader);
		} catch (SecurityException e) {
		    throw new ClassNotFoundException(c.getName(), e);
		}

		/*
		 * Remember that we've verified a class from the loader
		 * in this thread with the given verifierLoader value.
		 */
		if (verifiedLoaders == null) {
		    verifiedLoaders = new WeakHashMap();
		    verifierLoaderCache.put(verifierLoaderKey,
					    verifiedLoaders);
		}
		verifiedLoaders.put(loader, null);

		return true;
	    }
	}
	return false;
    }

    /**
     * Returns true if the first class loader argument is either a
     * descendant of or identical to the second class loader argument.
     * The class loader value null is considered the root of the tree.
     **/
    private static boolean covers(ClassLoader loader, ClassLoader ancestor) {
	if (ancestor == null) {
	    return true;
	} else if (loader == null) {
	    return false;
	}
	do {
	    if (loader == ancestor) {
		return true;
	    }
	    loader = loader.getParent();
	} while (loader != null);
	return false;
    }

    private ClassLoading() { throw new AssertionError(); }
}
