/*
 * 
 * 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.security.policy;

import java.security.CodeSource;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Policy;
import java.security.ProtectionDomain;
import java.security.Security;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import net.jini.security.GrantPermission;

/**
 * Security policy provider that wraps the J2SE(TM) default
 * <a href="http://java.sun.com/j2se/1.4/docs/guide/security/PolicyFiles.html">
 * "PolicyFile" security policy provider</a> distributed as part of the
 * Java(TM) 2 Platform, Standard Edition.  This provider augments the J2SE
 * default policy provider in two ways: it provides an additional constructor
 * for creating a policy based on an explicitly named policy file, and supports
 * the use of {@link UmbrellaGrantPermission}s as shorthand notation for
 * {@link GrantPermission}s covering all permissions authorized to given
 * protection domains.
 *
 * @author Sun Microsystems, Inc.
 * @since 2.0
 *
 * @com.sun.jini.impl <!-- Implementation Specifics -->
 *
 * This implementation's no-argument constructor uses a default class name of
 * <code>"sun.security.provider.PolicyFile"</code> to instantiate base policy
 * objects, if the
 * <code>net.jini.security.policy.PolicyFileProvider.basePolicyClass</code>
 * security property is not set.
 */
public class PolicyFileProvider extends Policy {

    private static final String basePolicyClassProperty =
	"net.jini.security.policy." +
	"PolicyFileProvider.basePolicyClass";
    private static final String defaultBasePolicyClass =
	"sun.security.provider.PolicyFile";
    private static final String policyProperty = "java.security.policy";
    private static final Object propertyLock = new Object();

    private final String policyFile;
    private final Policy basePolicy;

    /**
     * Creates a <code>PolicyFileProvider</code> whose starting set of
     * permission mappings is the same as those that would result from
     * constructing a new instance of the J2SE default security policy provider
     * with the current <code>java.security.policy</code> system property
     * setting (if any), except that <code>UmbrellaGrantPermission</code>s are
     * expanded into <code>GrantPermission</code>s as described in the
     * documentation for {@link UmbrellaGrantPermission}.
     * <p>
     * The constructed <code>PolicyFileProvider</code> contains an instance of
     * the J2SE default security policy provider, which is created as follows:
     * if the
     * <code>net.jini.security.policy.PolicyFileProvider.basePolicyClass</code>
     * security property is set, then its value is interpreted as the class
     * name of the base (underlying) J2SE default policy provider; otherwise,
     * an implementation-specific default class name is used.  The base policy
     * is then instantiated using the no-arg public constructor of the named
     * class.  If the base policy class is not found or is not instantiable via
     * a public no-arg constructor, or if invocation of its constructor fails,
     * then a <code>PolicyInitializationException</code> is thrown.
     * <p>
     * Note that this constructor requires the appropriate
     * <code>"getProperty"</code> {@link java.security.SecurityPermission} to
     * read the
     * <code>net.jini.security.policy.PolicyFileProvider.basePolicyClass</code>
     * security property, and may require <code>"accessClassInPackage.*"</code>
     * {@link RuntimePermission}s, depending on the package of the base policy
     * class.
     *
     * @throws  PolicyInitializationException if unable to construct the base
     *          policy
     * @throws  SecurityException if there is a security manager and the
     *          calling context does not have adequate permissions to read the
     *          <code>net.jini.security.policy.PolicyFileProvider.basePolicyClass</code>
     *          security property, or if the calling context does not have
     *          adequate permissions to access the base policy class
     */
    public PolicyFileProvider() throws PolicyInitializationException {
	policyFile = null;

	String cname = Security.getProperty(basePolicyClassProperty);
	if (cname == null) {
	    cname = defaultBasePolicyClass;
	}
	try {
	    basePolicy = (Policy) Class.forName(cname).newInstance();
	} catch (SecurityException e) {
	    throw e;
	} catch (Exception e) {
	    throw new PolicyInitializationException(
		"unable to construct base policy", e);
	}
	ensureDependenciesResolved();
    }

    /**
     * Creates a <code>PolicyFileProvider</code> whose starting set of
     * permission mappings is the same as those that would result from
     * constructing a new instance of the J2SE default security policy provider
     * with the <code>java.security.policy</code> system property set to the
     * value of <code>policyFile</code>, except that
     * <code>UmbrellaGrantPermission</code>s are expanded into
     * <code>GrantPermission</code>s as described in the documentation for
     * {@link UmbrellaGrantPermission}.
     * <p>
     * The constructed <code>PolicyFileProvider</code> contains an instance of
     * the J2SE default security policy provider, which is created as described
     * in the documentation for {@link #PolicyFileProvider()}.  Before
     * instantiating the base (underlying) J2SE default policy provider, this
     * constructor sets the <code>java.security.policy</code> system property
     * to the value of <code>policyFile</code>; after instantiation of the base
     * policy instance has completed (normally or otherwise), the
     * <code>java.security.policy</code> system property is reset to its prior
     * value.  Internal synchronization ensures that concurrent calls to this
     * constructor and/or the {@link #refresh} method of this class (which may
     * also modify <code>java.security.policy</code>) will not interfere with
     * the <code>java.security.policy</code> values set and restored by each.
     * <p>
     * Note that this constructor requires {@link java.util.PropertyPermission}
     * to read and write the <code>java.security.policy</code> system property,
     * {@link java.security.SecurityPermission} to read the
     * <code>net.jini.security.policy.PolicyFileProvider.basePolicyClass</code>
     * security property, and may require <code>"accessClassInPackage.*"</code>
     * {@link RuntimePermission}s, depending on the package of the base policy
     * class.
     *
     * @param	policyFile URL string specifying location of the policy file to
     * 		use
     * @throws	NullPointerException if <code>policyFile</code> is
     * 		<code>null</code>
     * @throws  PolicyInitializationException if unable to construct the base
     *          policy
     * @throws  SecurityException if there is a security manager and the
     *          calling context does not have adequate permissions to read and
     *          write the <code>java.security.policy</code> system property, to
     *          read the
     *          <code>net.jini.security.policy.PolicyFileProvider.basePolicyClass</code>
     *          security property, or to access the base policy class
     */
    public PolicyFileProvider(String policyFile) 
	throws PolicyInitializationException
    {
	if (policyFile == null) {
	    throw new NullPointerException();
	}
	this.policyFile = policyFile;

	String cname = Security.getProperty(basePolicyClassProperty);
	if (cname == null) {
	    cname = defaultBasePolicyClass;
	}
	try {
	    Class cl = Class.forName(cname);
	    synchronized (propertyLock) {
		String oldp = System.getProperty(policyProperty);
		try {
		    System.setProperty(policyProperty, policyFile);
		    basePolicy = (Policy) cl.newInstance();
		} finally {
		    System.setProperty(policyProperty, oldp);
		}
	    }
	} catch (SecurityException e) {
	    throw e;
	} catch (Exception e) {
	    throw new PolicyInitializationException(
		"unable to construct base policy", e);
	}
	ensureDependenciesResolved();
    }

    // documentation inherited from Policy.getPermissions
    public PermissionCollection getPermissions(CodeSource source) {
	PermissionCollection pc = basePolicy.getPermissions(source);
	expandUmbrella(pc);
	return pc;
    }

    // documentation inherited from Policy.getPermissions
    public PermissionCollection getPermissions(ProtectionDomain domain) {
	PermissionCollection pc = basePolicy.getPermissions(domain);
	expandUmbrella(pc);
	return pc;
    }

    // documentation inherited from Policy.implies
    public boolean implies(ProtectionDomain domain, Permission permission) {
	// REMIND: cache expanded permission collections?
	return (basePolicy.implies(domain, permission) ||
		(permission instanceof GrantPermission &&
		 getPermissions(domain).implies(permission)));
    }

    /**
     * Refreshes the policy configuration by calling <code>refresh</code> on
     * the base policy.  If this <code>PolicyFileProvider</code> instance was
     * constructed with an explicit policy file value, then the
     * <code>java.security.policy</code> system property is set to that value
     * prior to invoking <code>refresh</code> on the base policy; once the base
     * policy <code>refresh</code> call has completed, the
     * <code>java.security.policy</code> system property is reset to its prior
     * value.  Internal synchronization ensures that concurrent invocations of
     * this method and/or the {@link #PolicyFileProvider(String)} constructor
     * (which also modifies <code>java.security.policy</code>) will not
     * interfere with the <code>java.security.policy</code> values set and
     * restored by each.
     */
    public void refresh() {
	if (policyFile != null) {
	    synchronized (propertyLock) {
		String oldp = System.getProperty(policyProperty);
		try {
		    System.setProperty(policyProperty, policyFile);
		    basePolicy.refresh();
		} finally {
		    System.setProperty(policyProperty, oldp);
		}
	    }
	} else {
	    basePolicy.refresh();
	}
    }

    /**
     * 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 resolution of GrantPermission and UmbrellaGrantPermission
	new GrantPermission(new UmbrellaGrantPermission());
    }

    private static void expandUmbrella(PermissionCollection pc) {
	if (pc.implies(new UmbrellaGrantPermission())) {
	    List l = Collections.list(pc.elements());
	    pc.add(new GrantPermission(
		       (Permission[]) l.toArray(new Permission[l.size()])));
	}
    }
}
