/*
 * 
 * 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 com.sun.jini.start;

import com.sun.jini.collection.WeakIdentityMap;
import net.jini.security.policy.DynamicPolicy;
import java.security.AccessController;
import java.security.AllPermission;
import java.security.CodeSource;
import java.security.Permission;
import java.security.Permissions;
import java.security.PermissionCollection;
import java.security.Policy;
import java.security.Principal;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;

/**
 * Security policy provider which handles permission queries and grants by
 * delegating to different policy providers depending on the class loader
 * involved.  Each <code>LoaderSplitPolicyProvider</code> instance wraps two
 * underlying policy providers:
 * <ul>
 * <li>a class-loader specific policy provider, consulted for permission
 * queries/grants pertaining to that class loader, any child class loaders that
 * delegate to it, or the <code>null</code> class loader, and
 * <li>a default policy provider, consulted for all other operations (aside
 * from {@link #refresh}, which applies to both policies).
 * </ul>
 *
 * @author Sun Microsystems, Inc.
 * 
 * @since 2.0
 */
public class LoaderSplitPolicyProvider 
    extends Policy implements DynamicPolicy
{
    private static final ProtectionDomain myDomain = (ProtectionDomain)
	AccessController.doPrivileged(new PrivilegedAction() {
	    public Object run() {
		return LoaderSplitPolicyProvider.class.getProtectionDomain();
	    }
	});

    private final ClassLoader loader;
    private final Policy loaderPolicy;
    private final Policy defaultPolicy;
    private final WeakIdentityMap delegateMap = new WeakIdentityMap();

    /**
     * Creates a new <code>LoaderSplitPolicyProvider</code> instance which
     * delegates to <code>loaderPolicy</code> any permission query/grant
     * operations involving protection domains or classes with the given class
     * loader, any child class loader of the given class loader, or the
     * <code>null</code> class loader; all other operations are delegated to
     * <code>defaultPolicy</code> (with the exception of <code>refresh</code>,
     * which applies to both policies).
     *
     * @param	loader class loader for which associated permission query/grant
     * 		operations should be forwarded to <code>loaderPolicy</code>
     * @param	loaderPolicy class loader-specific security policy provider
     * @param	defaultPolicy default security policy provider
     * @throws	NullPointerException if <code>loader</code>,
     * 		<code>loaderPolicy</code> or <code>defaultPolicy</code> is
     * 		<code>null</code>
     */
    public LoaderSplitPolicyProvider(ClassLoader loader,
				     Policy loaderPolicy,
				     Policy defaultPolicy)
    {
	if (loader == null || loaderPolicy == null || defaultPolicy == null) {
	    throw new NullPointerException();
	}
	this.loader = loader;
	this.loaderPolicy = loaderPolicy;
	this.defaultPolicy = defaultPolicy;
	ensureDependenciesResolved();
    }

    /**
     * Delegates to the corresponding <code>getPermissions</code> method of the
     * underlying default policy.
     * 
     * @param	source code source for which to look up permissions
     * @return	set of permissions allowed for the given code source
     */
    public PermissionCollection getPermissions(CodeSource source) {
	return defaultPolicy.getPermissions(source);
    }

    /**
     * If the given protection domain is the protection domain of this class,
     * then a newly-created <code>PermissionCollection</code> containing {@link
     * AllPermission} is returned.  If not, delegates to the corresponding
     * <code>getPermissions</code> method of the underlying policy associated
     * with the loader of the given class (the loader-specific policy if the
     * class loader is <code>null</code>, the same as or a child of the loader
     * specified in the constructor for this instance, or the default loader
     * otherwise).
     * 
     * @param	domain protection domain for which to look up permissions
     * @return	set of permissions allowed for given protection domain
     */
    public PermissionCollection getPermissions(ProtectionDomain domain) {
	if (domain == myDomain) {
	    PermissionCollection pc = new Permissions();
	    pc.add(new AllPermission());
	    return pc;
	} else {
	    return getDelegate(domain.getClassLoader()).getPermissions(domain);
	}
    }

    /**
     * If the given protection domain is the protection domain of this class,
     * then <code>true</code> is returned.  If not, delegates to the
     * <code>implies</code> method of the underlying policy associated with the
     * loader of the given class (the loader-specific policy if the class
     * loader is <code>null</code>, the same as or a child of the loader
     * specified in the constructor for this instance, or the default loader
     * otherwise).
     * 
     * @param	domain protection domain in which to check implication
     * @param	permission permission to test implication of
     * @return	<code>true</code> if permission is implied by permissions of
     * 		given protection domain, <code>false</code> otherwise
     */
    public boolean implies(ProtectionDomain domain, Permission permission) {
	return domain == myDomain ||
	       getDelegate(domain.getClassLoader()).implies(domain,
							    permission);
    }

    /**
     * Invokes <code>refresh</code> on both the loader-specific and default
     * underlying policy providers.
     */
    public void refresh() {
	loaderPolicy.refresh();
	defaultPolicy.refresh();
    }

    /**
     * Returns <code>true</code> if both of the underlying policy providers
     * implement {@link DynamicPolicy} and return <code>true</code> from calls
     * to <code>grantSupported</code>; returns <code>false</code> otherwise.
     *
     * @return	{@inheritDoc}
     */
    public boolean grantSupported() {
	return loaderPolicy instanceof DynamicPolicy &&
	       ((DynamicPolicy) loaderPolicy).grantSupported() &&
	       defaultPolicy instanceof DynamicPolicy &&
	       ((DynamicPolicy) defaultPolicy).grantSupported();
    }

    /**
     * If both underlying policy providers support dynamic grants, delegates to
     * the <code>grant</code> method of the underlying policy associated with
     * the loader of the given class (the loader-specific policy if the class
     * loader is <code>null</code>, the same as or a child of the loader
     * specified in the constructor for this instance, or the default loader
     * otherwise).  If at least one of the underlying policy providers does not
     * support dynamic grants, throws an
     * <code>UnsupportedOperationException</code>.
     *
     * @param   cl {@inheritDoc}
     * @param	principals {@inheritDoc}
     * @param	permissions {@inheritDoc}
     * @throws	UnsupportedOperationException {@inheritDoc}
     * @throws	SecurityException {@inheritDoc}
     * @throws	NullPointerException {@inheritDoc}
     */
    public void grant(Class cl, 
		      Principal[] principals, 
		      Permission[] permissions) 
    {
	if (!grantSupported()) {
	    throw new UnsupportedOperationException("grants not supported");
	}
	((DynamicPolicy) getDelegate(getClassLoader(cl))).grant(
						cl, principals, permissions);
    }

    /**
     * If both underlying policy providers support dynamic grants, delegates to
     * the <code>getGrants</code> method of the underlying policy associated
     * with the loader of the given class (the loader-specific policy if the
     * class loader is <code>null</code>, the same as or a child of the loader
     * specified in the constructor for this instance, or the default loader
     * otherwise).  If at least one of the underlying policy providers does not
     * support dynamic grants, throws an
     * <code>UnsupportedOperationException</code>.
     *
     * @param   cl {@inheritDoc}
     * @param   principals {@inheritDoc}
     * @return  {@inheritDoc}
     * @throws	UnsupportedOperationException {@inheritDoc}
     * @throws	NullPointerException {@inheritDoc}
     */
    public Permission[] getGrants(Class cl, Principal[] principals) {
	if (!grantSupported()) {
	    throw new UnsupportedOperationException("grants not supported");
	}
	return ((DynamicPolicy) getDelegate(getClassLoader(cl))).getGrants(
							       cl, principals);
    }

    /**
     * Ensures that any classes depended on by this policy provider are
     * resolved.  This is to preclude lazy resolution of such classes during
     * operation of the provider, which can result in deadlock as described by
     * bug 4911907.
     */
    private void ensureDependenciesResolved() {
	// force class resolution by pre-invoking method called by implies()
	getDelegate(loader);
    }

    private Policy getDelegate(final ClassLoader ldr) {
	if (ldr == null) {
	    /* This special case is needed so that implies queries from the
	     * BasicInvocationDispatcher.checkClientPermission method
	     * will be handled by the loader-specific (i.e., service) policy.
	     */
	    return loaderPolicy;
	}
	Policy p;
	synchronized (delegateMap) {
	    p = (Policy) delegateMap.get(ldr);
	}
	if (p == null) {
	    p = (Policy) AccessController.doPrivileged(new PrivilegedAction() {
		public Object run() {
		    for (ClassLoader l = ldr; l != null; l = l.getParent())
		    {
			if (l == loader) {
			    return loaderPolicy;
			}
		    }
		    return defaultPolicy;
		}
	    });
	    synchronized (delegateMap) {
		delegateMap.put(ldr, p);
	    }
	}
	return p;
    }

    private static ClassLoader getClassLoader(final Class cl) {
	return (ClassLoader) AccessController.doPrivileged(
	    new PrivilegedAction() {
		public Object run() { return cl.getClassLoader(); }
	    });
    }
}
