/*
 * 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.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
 * RiverClassLoader} with optional verification that the codebase URLs
 * used to load classes provide content integrity (see {@link
 * Security#verifyCodebaseIntegrity
 * Security.verifyCodebaseIntegrity}).
 *
 * 
 * @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
     * RiverClassLoader#loadClass(String,String,ClassLoader)
     * RiverClassLoader.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
     * RiverClassLoader#loadClass(String,String,ClassLoader)
     * RiverClassLoader.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>RiverClassLoader.loadClass</code>
     * throws a <code>ClassNotFoundException</code>, then this method
     * throws a <code>ClassNotFoundException</code>; if
     * <code>RiverClassLoader.loadClass</code> throws any other
     * exception, then this method throws that exception; otherwise,
     * this method returns the <code>Class</code> returned by
     * <code>RiverClassLoader.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>RiverClassLoader.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>RiverClassLoader.loadClass</code> throws a
     * <code>MalformedURLException</code>
     *
     * @throws ClassNotFoundException if
     * <code>RiverClassLoader.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 RiverClassLoader.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
     * RiverClassLoader#loadProxyClass(String,String[],ClassLoader)
     * RiverClassLoader.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
     * RiverClassLoader#loadProxyClass(String,String[],ClassLoader)
     * RiverClassLoader.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>RiverClassLoader.loadProxyClass</code> throws a
     * <code>ClassNotFoundException</code>, then this method throws a
     * <code>ClassNotFoundException</code>; if
     * <code>RiverClassLoader.loadProxyClass</code> throws any other
     * exception, then this method throws that exception; otherwise,
     * this method returns the <code>Class</code> returned by
     * <code>RiverClassLoader.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>RiverClassLoader.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>RiverClassLoader.loadProxyClass</code> throws a
     * <code>MalformedURLException</code>
     *
     * @throws ClassNotFoundException if
     * <code>RiverClassLoader.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 RiverClassLoader.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(); }
}
