/*
 * Copyright 2002 Tryllian BV and Otto Moerbeek
 * http://www.tryllian.com
 * otto@tryllian.com
 */

package net.drijf.javaone;

import java.net.URL;
import java.net.URLClassLoader;
import java.security.AccessController;
import java.security.CodeSource;
import java.security.PermissionCollection;
import java.security.Policy;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import java.security.cert.Certificate;

/**
 * Implements a class loader that uses the delegating policy to assign
 * permissions. Furthermore, a class is checked for
 * <code>ClassLoadPermission</code> during loading.
 *
 * @author Otto Moerbeek
 * @see ClassLoadPermission
 */
public class DelegatingCL extends URLClassLoader 
{
    /**
     * The policy used to determine permissions.
     */
    private Policy policy;

    /**
     * Construct a new delegating URL classloader, given a policy.
     *
     * @param urls the urls to look for classes.
     * @param parent the parent class loader.
     * @param policy the policy to use when determining permissions.
     */
    public DelegatingCL(URL[] urls, ClassLoader parent, Policy policy)
    {
        super(urls, parent);
        this.policy = policy;
    }

    /**
     * Find a class, with an additional check on
     * <code>ClassLoadPermission</code>.
     *
     * @param name the name of the class to load.
     * @return a loaded <code>Class</code>object.
     * @throws ClassNotFoundException if either the class could not be
     * found, or the class is denied <code>ClassLoadPermission</code>.
     *
     * @see ClassLoadPermission
     */
    protected Class findClass(final String name)
        throws ClassNotFoundException
    {
        // First try to load the class using the URL class loader.
        final Class loadedClass = super.findClass(name);

        // If the class is loaded, get its protection domain.
        ProtectionDomain pd = (ProtectionDomain) AccessController.doPrivileged(
            new PrivilegedAction() {
                    public Object run() {
                        return loadedClass.getProtectionDomain();
                        
                    }
                });
        
        // If the protectiondomain is lacking ClassLoadPermission, do
        // as if the class could not be found.
        if (!pd.implies(new ClassLoadPermission(name))) {
            throw new ClassNotFoundException(getMessage(name, pd));
        }

        return loadedClass;
    }

    /**
     * Return the permission associated with a codesource according
     * to the policy associated with this classloader.
     *
     * @param cs the code source to return the permissions for.
     * @return the permission collection associated with the code
     * source according to the policy.
     */
    protected PermissionCollection getPermissions(CodeSource cs) {
        return policy.getPermissions(cs);
    }


    /**
     * A helper method for formatting classloading denied error
     * messages.
     * @param name the name of the class that was denied access.
     * @param pd the protectiondomain of the class that was denied access.
     */
    private static String getMessage(String name, ProtectionDomain pd) {
        CodeSource cs = pd.getCodeSource();
        StringBuffer buf = new StringBuffer();
        buf.append("Class ");
        buf.append(name);
        buf.append(" denied ClassLoadPermission; URL = ");
        buf.append(cs != null ? cs.getLocation().toString() : "unknown");
        buf.append("; Certificate list is [");
        if (cs != null) {
            Certificate[] certs = cs.getCertificates();
            if (certs != null) {
                for (int i = 0; i < certs.length; i++) {
                    buf.append(certs[i].toString());
                    if (i < certs.length - 1) {
                        buf.append(',');
                    }
                }
            }
        }
        buf.append(']');
        return buf.toString();
    }
}


