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

import java.lang.reflect.Method;
import java.rmi.Remote;
import java.rmi.activation.ActivationGroupDesc;
import java.rmi.activation.ActivationSystem;
import java.rmi.server.ExportException;
import java.util.Collection;
import net.jini.core.constraint.InvocationConstraints;
import net.jini.core.constraint.MethodConstraints;
import net.jini.jeri.BasicILFactory;
import net.jini.jeri.BasicInvocationDispatcher;
import net.jini.jeri.InvocationDispatcher;
import net.jini.jeri.ServerCapabilities;

/**
 * Invocation layer factory for exporting an {@link ActivationSystem}
 * to use Jini extensible remote invocation (Jini ERI), that is similar
 * to {@link BasicILFactory} except the remote object must be an
 * <code>ActivationSystem</code> instance and the returned dispatcher
 * optionally accepts calls from the local host and optionally enforces a
 * {@link GroupPolicy} on calls to {@link ActivationSystem#registerGroup
 * registerGroup} and {@link ActivationSystem#setActivationGroupDesc
 * setActivationGroupDesc}.
 *
 * 
 * 
 * @since 2.0
 * @see SystemAccessProxyTrustILFactory
 **/
public class SystemAccessILFactory extends BasicILFactory {
    /**
     * The group policy, if any.
     */
    private final GroupPolicy policy;

    /**
     * If true, check that client is calling from the local host.
     */
    private final boolean localAccessCheck;
    
    /**
     * Creates an invocation layer factory that creates an invocation
     * dispatcher with a {@link DefaultGroupPolicy} instance and a
     * <code>null</code> class loader.  This invocation dispatcher only
     * accepts calls from the local host and enforces the group policy on
     * calls to {@link ActivationSystem#registerGroup registerGroup} and
     * {@link ActivationSystem#setActivationGroupDesc
     * setActivationGroupDesc}.
     **/
    public SystemAccessILFactory() {
	this(new DefaultGroupPolicy(), null);
    }

    /**
     * Creates an invocation layer factory that creates an invocation
     * dispatcher with the specified group policy and the specified class
     * loader.  This invocation dispatcher only accepts calls from the
     * local host and enforces the specified group policy (if
     * non-<code>null</code>) on calls to {@link
     * ActivationSystem#registerGroup registerGroup} and {@link
     * ActivationSystem#setActivationGroupDesc setActivationGroupDesc}.
     *
     * @param loader the class loader, or <code>null</code>
     * @param policy the group policy, or <code>null</code>
     **/
    public SystemAccessILFactory(GroupPolicy policy, ClassLoader loader) {
	super(null, null, loader);
	this.policy = policy;
	this.localAccessCheck = true;
    }

    /**
     * Creates a factory with a <code>null</code> class loader, the
     * specified server constraints, the {@link SystemPermission}
     * permission class, and a {@link DefaultGroupPolicy} instance.
     *
     * @param serverConstraints the server constraints, or <code>null</code>
     **/
    public SystemAccessILFactory(MethodConstraints serverConstraints) {
	this(serverConstraints, SystemPermission.class,
	     new DefaultGroupPolicy(), null);
    }

    /**
     * Creates a factory with the specified server constraints, permission
     * class, group policy, and class loader.  This factory creates an
     * invocation dispatcher that enforces the specified group policy (if
     * non-<code>null</code>) on calls to {@link
     * ActivationSystem#registerGroup registerGroup} and {@link
     * ActivationSystem#setActivationGroupDesc setActivationGroupDesc}.
     *
     * @param serverConstraints the server constraints, or <code>null</code>
     * @param permClass the permission class, or <code>null</code>
     * @param policy the group policy, or <code>null</code>
     * @param loader the class loader, or <code>null</code>
     * @throws IllegalArgumentException if the permission class is abstract, is
     * not a subclass of {@link java.security.Permission}, or does not have
     * a public constructor that has either one <code>String</code> parameter
     * or one {@link Method} parameter and has no declared exceptions
     **/
    public SystemAccessILFactory(MethodConstraints serverConstraints,
				 Class permClass,
				 GroupPolicy policy,
				 ClassLoader loader)
    {
	super(serverConstraints, permClass, loader);
	this.policy = policy;
	this.localAccessCheck = false;
    }

    /**
     * Returns a {@link SystemDispatcher} instance constructed with the
     * specified methods, the class loader specified during construction,
     * the remote object, server capabilities, and the server constraints,
     * permission class, and group policy that this factory was constructed
     * with and a flag indicating whether the dispatcher should only accept
     * calls from the local host.
     *
     * @return a {@link SystemDispatcher} instance constructed with the
     * specified methods, remote object, and server capabilities, and the
     * server constraints, permission class, group policy, local host access
     * check condition, and class loader that this factory was constructed
     * with
     * @throws NullPointerException {@inheritDoc}
     **/
    protected InvocationDispatcher
	createInvocationDispatcher(Collection methods,
				   Remote impl,
				   ServerCapabilities caps)
	throws ExportException
    {
	if (impl == null) {
	    throw new NullPointerException("impl cannot be null");
	} else if (!(impl instanceof ActivationSystem)) {
	    throw new ExportException("cannot create dispatcher",
				      new IllegalArgumentException(
					  "impl must be an ActivationSystem"));
	}
	return new SystemDispatcher(methods, impl, caps,
				    getServerConstraints(),
				    getPermissionClass(), policy,
				    localAccessCheck, getClassLoader());
    }

    /**
     * A subclass of {@link BasicInvocationDispatcher} for
     * <code>ActivationSystem</code> instances that optionally enforces a
     * {@link GroupPolicy} on calls to <code>registerGroup</code> and
     * <code>setActivationGroupDesc</code>.
     */
    public static class SystemDispatcher extends BasicInvocationDispatcher {
	/**
	 * The group policy, if any.
	 */
	private final GroupPolicy policy;

	/**
	 * If true, check that client is calling from the local host.
	 */
	private final boolean localAccessCheck;

	/**
	 * Creates an invocation dispatcher to receive incoming remote calls
	 * for the specified methods, for a server and transport with the
	 * specified capabilities, enforcing the specified constraints, and
	 * performing preinvocation access control using the specified
	 * permission class and group policy.  The specified class loader
	 * is used by the {@link #createMarshalInputStream
	 * createMarshalInputStream} method.
	 * 
	 * <p>For each combination of constraints that might need to be
	 * enforced (obtained by calling the {@link
	 * MethodConstraints#possibleConstraints possibleConstraints}
	 * method on the specified server constraints, or using an empty
	 * constraints instance of the specified server constraints
	 * instance is <code>null</code>), calling the {@link
	 * ServerCapabilities#checkConstraints checkConstraints}
	 * method of the specified capabilities object with those
	 * constraints must return <code>true</code>, or an
	 * <code>ExportException</code> is thrown.
	 *
	 * @param methods a collection of {@link Method} instances for the
	 * remote methods
	 * @param impl the remote object
	 * @param serverCaps the transport capabilities of the server
	 * @param serverConstraints the server constraints, or
	 * <code>null</code>
	 * @param permClass the permission class, or <code>null</code>
	 * @param policy the group policy, or <code>null</code>
	 * @param localAccessCheck if <code>true</code>, calls are only
	 * accepted from the local host
	 * @param loader the class loader, or <code>null</code>
	 * @throws IllegalArgumentException if <code>impl</code> is not an
	 * instance of {@link ActivationSystem} or if the permission class is
	 * abstract, is not a subclass of {@link java.security.Permission},
	 * or does not have a public constructor that has either one
	 * <code>String</code> parameter or one {@link Method} parameter and
	 * has no declared exceptions, or if any element of
	 * <code>methods</code> is not a {@link Method} instance
	 * @throws NullPointerException if <code>impl</code>,
	 * <code>methods</code> or <code>serverCaps</code> is
	 * <code>null</code>, or if <code>methods</code> contains a
	 * <code>null</code> element 
	 * @throws ExportException if any of the possible server constraints
	 * cannot be satisfied according to the specified server capabilities
	 **/
	public SystemDispatcher(Collection methods,
				Remote impl,
				ServerCapabilities serverCaps,
				MethodConstraints serverConstraints,
				Class permClass,
				GroupPolicy policy,
				boolean localAccessCheck,
				ClassLoader loader)
	    throws ExportException
	{
	    super(methods, serverCaps, serverConstraints, permClass, loader);
	    if (impl == null) {
		throw new NullPointerException("impl is null");
	    }
	    if (!(impl instanceof ActivationSystem)) {
		throw new IllegalArgumentException(
		    "impl not an ActivationSystem instance");
	    }
	    this.policy = policy;
	    this.localAccessCheck = localAccessCheck;
	}

	/**
	 * Checks that the client is calling from the local host.
	 *
	 * @throws java.security.AccessControlException if the client is not
	 * calling from the local host
	 */
	protected void checkAccess(Remote impl,
				   Method method,
				   InvocationConstraints constraints,
				   Collection context)
	{
	    if (localAccessCheck) {
		LocalAccess.check();
	    } else {
		super.checkAccess(impl, method, constraints, context);
	    }
	}

	/**
	 * Checks the group policy as necessary, and then calls the superclass
	 * <code>invoke</code> method with the same arguments, and returns
	 * the result. If the policy is not <code>null</code> and the
	 * specified method is <code>ActivationSystem.registerGroup</code> or
	 * <code>ActivationSystem.setActivationGroupDesc</code>, the policy
	 * is checked by calling its {@link GroupPolicy#checkGroup checkGroup}
	 * method with the <code>ActivationGroupDesc</code> argument.
	 *
	 * @throws SecurityException if the caller is not permitted to use
	 * the specified group descriptor
	 */
	protected Object invoke(Remote impl,
				Method method,
				Object[] args,
				Collection context)
	    throws Throwable
	{
	    if (policy != null) {
		String op = method.getName();
		if (op.equals("registerGroup") ||
		    op.equals("setActivationGroupDesc"))
		{
		    policy.checkGroup(
				  (ActivationGroupDesc) args[args.length - 1]);
		}
	    }
	    return super.invoke(impl, method, args, context);
	}
    }
}
