/*
 * 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 com.sun.jini.thread.Executor;
import com.sun.jini.thread.GetThreadPoolAction;
import com.sun.jini.proxy.BasicProxyTrustVerifier;
import java.io.IOException;
import java.io.ObjectStreamClass;
import java.io.ObjectStreamField;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.rmi.MarshalledObject;
import java.rmi.NoSuchObjectException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.activation.Activatable;
import java.rmi.activation.ActivationDesc;
import java.rmi.activation.ActivationException;
import java.rmi.activation.ActivationGroupDesc;
import java.rmi.activation.ActivationGroupID;
import java.rmi.activation.ActivationID;
import java.rmi.activation.ActivationInstantiator;
import java.rmi.activation.ActivationMonitor;
import java.rmi.activation.ActivationSystem;
import java.rmi.activation.UnknownObjectException;
import java.rmi.server.ExportException;
import java.rmi.server.RemoteObject;
import java.rmi.server.UnicastRemoteObject;
import java.security.AccessController;
import java.security.Permission;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.security.auth.Subject;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import net.jini.activation.ActivationGroup;
import net.jini.config.Configuration;
import net.jini.config.ConfigurationException;
import net.jini.config.ConfigurationProvider;
import net.jini.export.Exporter;
import net.jini.export.ProxyAccessor;
import net.jini.jeri.BasicJeriExporter;
import net.jini.jeri.tcp.TcpServerEndpoint;
import net.jini.loader.RiverClassLoader;
import net.jini.security.BasicProxyPreparer;
import net.jini.security.ProxyPreparer;
import net.jini.security.Security;
import net.jini.security.TrustVerifier;
import net.jini.security.proxytrust.ServerProxyTrust;

/**
 * The default activation group implementation for phoenix.  Instances of
 * this class are configurable through a {@link Configuration}, as detailed
 * further below, and provide the necessary support to allow exporter-based
 * remote objects to go inactive.  Instances of this class support the
 * creation of remote objects through the normal activatable constructor;
 * an activatable remote object must either implement the {@link
 * ProxyAccessor} interface to return a suitable proxy for the remote
 * object, or the remote object must itself be serializable and marshalling
 * the object must produce a suitable proxy for the remote object.
 * 
 * <p>An instance of this class can be configured by specifying an
 * {@link ActivationGroupData} instance containing configuration options
 * as the initialization data for the activation group. Typically
 * this is accomplished indirectly, by setting the
 * <code>groupConfig</code> configuration entry for
 * phoenix itself. The following entries are obtained from the configuration,
 * all for the component named <code>com.sun.jini.phoenix</code>:
 *
 *  <table summary="Describes the loginContext configuration entry"
 *         border="0" cellpadding="2">
 *    <tr valign="top">
 *      <th scope="col" summary="layout"> <font size="+1">&#X2022;</font>
 *      <th scope="col" align="left" colspan="2"> <font size="+1"><code>
 *      loginContext</code></font>
 *    <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *      Type: <td> <code>{@link javax.security.auth.login.LoginContext}</code>
 *    <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *      Default: <td> <code>null</code>
 *    <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *      Description: <td> JAAS login context
 *  </table>
 *
 *  <table summary="Describes the inheritGroupSubject configuration entry"
 *         border="0" cellpadding="2">
 *    <tr valign="top">
 *      <th scope="col" summary="layout"> <font size="+1">&#X2022;</font>
 *      <th scope="col" align="left" colspan="2"> <font size="+1"><code>
 *      inheritGroupSubject</code></font>
 *    <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *      Type: <td> <code>boolean</code>
 *    <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *      Default: <td> <code>false</code>
 *    <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *      Description: <td> if <code>true</code>, group subject is inherited
 *		when an activatable object is created 
 *  </table>
 *
 *  <table summary="Describes the instantiatorExporter configuration entry"
 *         border="0" cellpadding="2">
 *    <tr valign="top">
 *      <th scope="col" summary="layout"> <font size="+1">&#X2022;</font>
 *      <th scope="col" align="left" colspan="2"> <font size="+1"><code>
 *      instantiatorExporter</code></font>
 *    <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *      Type: <td> <code>{@link net.jini.export.Exporter}</code>
 *    <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *      Default: <td> retains existing JRMP export of instantiator
 *    <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *      Description: <td> {@link java.rmi.activation.ActivationInstantiator}
 *		exporter
 *  </table>
 *
 *  <table summary="Describes the monitorPreparer configuration entry"
 *         border="0" cellpadding="2">
 *    <tr valign="top">
 *      <th scope="col" summary="layout"> <font size="+1">&#X2022;</font>
 *      <th scope="col" align="left" colspan="2"> <font size="+1"><code>
 *      monitorPreparer</code></font>
 *    <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *      Type: <td> <code>{@link net.jini.security.ProxyPreparer}</code>
 *    <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *      Default: <td> <code>new {@link
 *		net.jini.security.BasicProxyPreparer}()</code> 
 *    <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *      Description: <td> {@link java.rmi.activation.ActivationMonitor}
 *		proxy preparer 
 *  </table>
 *
 *  <table summary="Describes the systemPreparer configuration entry"
 *         border="0" cellpadding="2">
 *    <tr valign="top">
 *      <th scope="col" summary="layout"> <font size="+1">&#X2022;</font>
 *      <th scope="col" align="left" colspan="2"> <font size="+1"><code>
 *      systemPreparer</code></font>
 *    <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *      Type: <td> <code>{@link net.jini.security.ProxyPreparer}</code>
 *    <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *      Default: <td> <code>new {@link
 *		net.jini.security.BasicProxyPreparer}()</code> 
 *    <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *      Description: <td> {@link java.rmi.activation.ActivationSystem}
 *		proxy preparer 
 *  </table>
 *
 *  <table summary="Describes the unexportTimeout configuration entry"
 *         border="0" cellpadding="2">
 *    <tr valign="top">
 *      <th scope="col" summary="layout"> <font size="+1">&#X2022;</font>
 *      <th scope="col" align="left" colspan="2"> <font size="+1"><code>
 *      unexportTimeout</code></font>
 *    <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *      Type: <td> <code>int</code>
 *    <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *      Default: <td> <code>60000</code>
 *    <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *      Description: <td> maximum time in milliseconds to wait for
 *		in-progress calls to finish before forcibly unexporting the
 *		group when going inactive 
 *  </table>
 *
 *  <table summary="Describes the unexportWait configuration entry"
 *         border="0" cellpadding="2">
 *    <tr valign="top">
 *      <th scope="col" summary="layout"> <font size="+1">&#X2022;</font>
 *      <th scope="col" align="left" colspan="2"> <font size="+1"><code>
 *      unexportWait</code></font>
 *    <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *      Type: <td> <code>int</code>
 *    <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *      Default: <td> <code>10</code>
 *    <tr valign="top"> <td> &nbsp <th scope="row" align="right">
 *      Description: <td> milliseconds to wait between unexport attempts
 *		when going inactive 
 *  </table>
 * 
 * <p>This class depends on its {@link #createGroup createGroup} method being
 * called to initialize the activation group. As such, this class cannot be
 * used in conjunction with the standard <code>rmid</code>.
 *
 * 
 * 
 * @since 2.0
 **/
public class ActivationGroupImpl extends ActivationGroup
    implements ServerProxyTrust
{
    private static final long serialVersionUID = 5758693559430427303L;

    private static final String PHOENIX = "com.sun.jini.phoenix";
    /** instance has not been created */
    private static final int UNUSED = 0;
    /** in the middle of createGroup */
    private static final int CREATING = 1;
    /** constructor (including activeGroup) has succeeded */
    private static final int CREATED = 2;
    /** createGroup has succeeded */
    private static final int ACTIVE = 3;
    /** group is inactive */
    private static final int INACTIVE = 4;
    /** parameter types for activatable constructor */
    private static Class[] paramTypes = {
	ActivationID.class, MarshalledObject.class
    };

    /* avoid serial field clutter */
    private static final ObjectStreamField[] serialPersistentFields =
						ObjectStreamClass.NO_FIELDS;

    private static int state = UNUSED;
    private static long incarnation;
    /** original unprepared group id */
    private static ActivationGroupID groupID;

    /** server LoginContext or null */
    private static LoginContext login;
    private static Exporter exporter;
    /** true if calls should be refused, false otherwise */
    private static boolean refuseCalls = false;
    /** monitor proxy preparer */
    private static ProxyPreparer monPreparer;
    private static ActivationMonitor monitor;
    /** timeout on wait for unexport to succeed */
    private static long unexportTimeout;
    /** timeout on wait between unexport attempts */
    private static long unexportWait;
    /** maps ActivationID to ActiveEntry */
    private static Map active = new HashMap();
    /** ActivationIDs with operations in progress */
    private static List lockedIDs = new ArrayList();
    /** true if group subject is inherited when creating activatable objects */
    private static boolean inheritGroupSubject;

    /** permission to check for monitor's activeObject call */
    private final static Permission activeObjectPermission =
        new MonitorPermission(
	    "java.rmi.activation.ActivationMonitor.activeObject");
    
    /** permission to check for monitor's activeObject call */
    private static Permission inactiveObjectPermission =
        new MonitorPermission(
	    "java.rmi.activation.ActivationMonitor.inactiveObject");
    
    /** proxy for this activation group */
    private ActivationInstantiator proxy;

    /**
     * Creates an {@link java.rmi.activation.ActivationGroup} instance and
     * returns it. An {@link ActivationGroupData} instance is extracted from
     * the initialization data, and a {@link Configuration} is obtained by
     * calling
     * {@link net.jini.config.ConfigurationProvider#getInstance
     * Configuration.Provider.getInstance} with the configuration options from
     * that instance. A {@link LoginContext} is obtained from the
     * <code>loginContext</code> configuration entry, if one exists; if the
     * value is not <code>null</code>, a login is performed on that context,
     * and the resulting {@link Subject} (set to be read-only) is used as the
     * subject when executing the rest of this method. The subject is also
     * used for all subsequent remote calls by this class to the
     * {@link ActivationMonitor}. The {@link ActivationSystem} proxy
     * (obtained from the <code>ActivationGroupID</code>) is passed to the
     * {@link ProxyPreparer} given by the <code>systemPreparer</code>
     * configuration entry, if one exists; a new
     * <code>ActivationGroupID</code> is constructed with the resulting proxy.
     * An {@link Exporter} instance is obtained from the
     * <code>instantiatorExporter</code> configuration entry, if one exists;
     * this exporter will be used (in the constructor of this class) to export
     * the group. A <code>ProxyPreparer</code> instance is obtained from the
     * <code>monitorPreparer</code> configuration entry, if one exists; this
     * preparer will be used (in the constructor of this class) to prepare the
     * <code>ActivationMonitor</code>. A call is then made to
     * {@link ActivationGroup#createGroup ActivationGroup.createGroup} with
     * the new group identifier, the activation group descriptor, and the
     * group incarnation number, and the result of that call is returned.
     *
     * @param id the activation group identifier
     * @param desc the activation group descriptor
     * @param incarnation the group's incarnation number (zero on initial
     * creation)
     * @return the created activation group
     * @throws ActivationException if a group already exists or if an
     * exception occurs during group creation
     */
    public static synchronized
	java.rmi.activation.ActivationGroup createGroup(
					      final ActivationGroupID id,
					      final ActivationGroupDesc desc,
					      final long incarnation)
        throws ActivationException
    {
	if (state != UNUSED) {
	    throw new ActivationException("group previously created");
	}
	try {
	    final Configuration config = getConfiguration(desc.getData());
	    login = (LoginContext) config.getEntry(
			PHOENIX, "loginContext", LoginContext.class, null);
	    if (login != null) {
		login.login();
	    }
		
	    inheritGroupSubject =
		((Boolean) config.getEntry(
		    PHOENIX, "inheritGroupSubject", boolean.class,
		    Boolean.FALSE)).booleanValue();
	    
	    return (java.rmi.activation.ActivationGroup) doAction(
		new PrivilegedExceptionAction() {
		    public Object run() throws Exception {
			ProxyPreparer sysPreparer =
			    getPreparer(config, "systemPreparer");
			monPreparer = getPreparer(config,
						  "monitorPreparer");
			TcpServerEndpoint se =
			    TcpServerEndpoint.getInstance(0);
			Exporter defaultExporter =
			    new BasicJeriExporter(se, new AccessILFactory());
			exporter = (Exporter) config.getEntry(
				PHOENIX, "instantiatorExporter",
				Exporter.class, defaultExporter);
			if (exporter == null) {
			    exporter = new AlreadyExportedExporter();
			}
			refuseCalls =
			    !(exporter instanceof AlreadyExportedExporter);
			unexportTimeout = getInt(config, "unexportTimeout",
						 60000);
			unexportWait = getInt(config, "unexportWait", 10);
			ActivationSystem sys = (ActivationSystem)
			    sysPreparer.prepareProxy(id.getSystem());
			ActivationGroupImpl.incarnation = incarnation;
			groupID = id;
			state = CREATING;
			ActivationGroupID gid = (sys == id.getSystem() ?
						 id : new WrappedGID(id, sys));
			Object group = ActivationGroup.createGroup(
						    gid, desc, incarnation);
			state = ACTIVE;
			return group;
		    }
		});
	} catch (ActivationException e) {
	    throw e;
	} catch (Exception e) {
	    throw new ActivationException("creation failed", e);
	} finally {
	    if (state != ACTIVE) {
		checkInactiveGroup();
	    }
	}
    }

    /**
     * Returns the configuration obtained from the specified marshalled
     * object. 
     */
    private static Configuration getConfiguration(MarshalledObject mobj)
	throws ConfigurationException, IOException, ClassNotFoundException
    {
	ActivationGroupData data = (ActivationGroupData) mobj.get();
	ClassLoader cl = ActivationGroupImpl.class.getClassLoader();
	ClassLoader ccl = Thread.currentThread().getContextClassLoader();
	if (!covers(cl, ccl)) {
	    cl = ccl;
	}
	return ConfigurationProvider.getInstance(data.getConfig(), cl);
    }
    
    /**
     * Returns true if the first argument is either equal to, or is a
     * descendant of, the second argument.  Null is treated as the root of
     * the tree.
     */
    private static boolean covers(ClassLoader sub, ClassLoader sup) {
	if (sup == null) {
	    return true;
	} else if (sub == null) {
	    return false;
	}
	do {
	    if (sub == sup) {
		return true;
	    }
	    sub = sub.getParent();
	} while (sub != null);
	return false;
    }

    /**
     * Return a ProxyPreparer configuration entry.
     */
    private static ProxyPreparer getPreparer(Configuration config, String name)
	throws ConfigurationException
    {
	return (ProxyPreparer) config.getEntry(PHOENIX, name,
					       ProxyPreparer.class,
					       new BasicProxyPreparer());
    }

    /**
     * Return an int configuration entry.
     */
    private static int getInt(Configuration config, String name, int defValue)
	throws ConfigurationException
    {
	return ((Integer) config.getEntry(PHOENIX, name, int.class,
					  Integer.valueOf(defValue))).intValue();
    }

    /**
     * ActivationGroupID containing a prepared ActivationSystem proxy and
     * the original ActivationGroupID (with unprepared ActivationSystem
     * proxy), that writeReplaces itself to the original.
     */
    private static class WrappedGID extends ActivationGroupID {
	/** Original gid */
	private final ActivationGroupID id;
	/** Prepared system proxy */
	private final ActivationSystem sys;

	WrappedGID(ActivationGroupID id, ActivationSystem sys) {
	    super(sys);
	    this.id = id;
	    this.sys = sys;
	}

	/* override */
	public ActivationSystem getSystem() {
	    return sys;
	}

	private Object writeReplace() {
	    return id;
	}
    }

    /**
     * Creates an instance with the specified group identifier and
     * initialization data. This constructor must be called indirectly,
     * via {@link #createGroup createGroup}. By default, this instance
     * automatically exports itself as a {@link UnicastRemoteObject}. (This
     * is a limitation of the existing activation system design.) If an
     * {@link Exporter} was obtained by {@link #createGroup createGroup},
     * then this instance is unexported from the JRMP runtime and re-exported
     * using that exporter. (Any incoming remote calls received on the
     * original JRMP export before this instance can be unexported will be
     * refused with a security exception thrown.) The
     * {@link ActivationSystem#activeGroup activeGroup} method of the
     * activation system proxy (in the group identifier) is called to
     * make the group active. The returned {@link ActivationMonitor} proxy
     * is passed to the corresponding {@link ProxyPreparer} obtained by
     * <code>createGroup</code>. Note that after this constructor returns,
     * {@link ActivationGroup#createGroup ActivationGroup.createGroup} will
     * also call <code>activeGroup</code> (so the activation system must
     * accept idempotent calls to that method), but the
     * <code>ActivationMonitor</code> proxy returned by that call will not be
     * used.
     * 
     * @param id the activation group identifier
     * @param data group initialization data (ignored)
     * @throws RemoteException if the group could not be exported or
     * made active, or proxy preparation fails
     * @throws ActivationException if the constructor was not called
     * indirectly from <code>createGroup</code>
     */
    public ActivationGroupImpl(ActivationGroupID id, MarshalledObject data)
	throws ActivationException, RemoteException
    {
	super(id);
	synchronized (ActivationGroupImpl.class) {
	    if (state != CREATING) {
		throw new ActivationException("not called from createGroup");
	    }
	}
	if (refuseCalls) {
	    unexportObject(this, true);
	    refuseCalls = false;
	}
	proxy = (ActivationInstantiator) exporter.export(this);
	try {
	    monitor = (ActivationMonitor) monPreparer.prepareProxy(
			  id.getSystem().activeGroup(id, proxy, incarnation));
	    state = CREATED;
	} finally {
	    if (state != CREATED) {
		exporter.unexport(true);
	    }
	}
	monPreparer = null;
    }

    public TrustVerifier getProxyVerifier() {
	return new BasicProxyTrustVerifier(proxy);
    }
    
    /**
     * Exporter for an object that is already exported to JRMP.
     */
    private static class AlreadyExportedExporter implements Exporter {
	/**
	 * A strong reference to the impl is OK because ActivationGroup
	 * also holds a strong reference.
	 */
	private Remote impl;

	AlreadyExportedExporter() {
	}

	public synchronized Remote export(Remote impl) throws ExportException {
	    this.impl = impl;
	    try {
		return RemoteObject.toStub(impl);
	    } catch (NoSuchObjectException e) {
		throw new ExportException("no stub found", e);
	    }
	}

	public synchronized boolean unexport(boolean force) {
	    try {
		if (impl != null &&
		    !UnicastRemoteObject.unexportObject(impl, force))
		{
		    return false;
		}
	    } catch (NoSuchObjectException e) {
	    }
	    impl = null;
	    return true;
	}
    }

    /**
     * Returns the proxy for this remote object. Group creation was designed
     * to rely on automatic stub replacement (as provided by the JRMP runtime),
     * which is not supported by all exporters.
     *
     * @return the proxy for this remote object
     */
    protected Object writeReplace() {
	return proxy;
    }

    /*
     * Obtains a lock on the ActivationID id before returning. Allows only one
     * thread at a time to hold a lock on a particular id.  If the lock for id
     * is in use, all requests for an equivalent (in the .equals sense)
     * id will wait for the id to be notified and use the supplied id as the
     * next lock. The caller of "acquireLock" must execute the "releaseLock"
     * method" to release the lock and "notifyAll" waiters for the id lock
     * obtained from this method.  The typical usage pattern is as follows:
     *
     * acquireLock(id);
     * try {
     *    // do stuff pertaining to id...
     * } finally {
     *    releaseLock(id);
     * }
     */
    private void acquireLock(ActivationID id) {
	while (true) {
	    synchronized (id) {
		synchronized (lockedIDs) {
		    int index = lockedIDs.indexOf(id);
		    if (index < 0) {
			lockedIDs.add(id);
			return;
		    }
		    ActivationID lockedID =
			(ActivationID) lockedIDs.get(index);
		    if (lockedID != id) {
			// don't wait on an id that won't be notified
			id = lockedID;
			continue;
		    }
		}
		try {
		    id.wait();
		} catch (InterruptedException ignore) {
		}
	    }
	}
    }

    /*
     * Releases the id lock obtained via the "acquireLock" method and then
     * notifies all threads waiting on the lock.
     */
    private void releaseLock(ActivationID id) {
	synchronized (lockedIDs) {
	    id = (ActivationID) lockedIDs.remove(lockedIDs.indexOf(id));
	}
	synchronized (id) {
	    id.notifyAll();
	}
    }

    /**
     * Creates a new instance of an activatable remote object and returns
     * a marshalled object containing the activated object's proxy.
     *
     * <p>If an active object already exists for the specified identifier,
     * the existing marshalled object for it is returned.
     *
     * <p>Otherwise:
     *
     * <p>The class for the object is loaded by invoking {@link
     * RiverClassLoader#loadClass(String,String) RiverClassLoader.loadClass}
     * passing the class location (obtained by invoking {@link
     * ActivationDesc#getLocation getLocation} on the activation
     * descriptor) and the class name (obtained by invoking {@link
     * ActivationDesc#getClassName getClassName} on the activation
     * descriptor).
     *
     * <p>The new instance is constructed as follows. If the class defines
     * a constructor with two parameters of type {@link ActivationID} and
     * {@link MarshalledObject}, that constructor is called with the
     * specified activation identifier and the initialization data from the
     * specified activation descriptor. Otherwise, an
     * <code>ActivationException</code> is thrown.
     *
     * <p>If the class loader of the object's class is a descendant of the
     * current context class loader, then that class loader is set as the
     * context class loader when the constructor is called.
     *
     * <p>If the <code>inheritGroupSubject</code> configuration entry is
     * <code>true</code> then the constructor is invoked in an action
     * passed to the {@link Security#doPrivileged Security.doPrivileged}
     * method; otherwise the constructor is invoked in an action passed to
     * the {@link AccessController#doPrivileged
     * AccessController.doPrivileged} method.
     *
     * <p>A proxy for the newly created instance is returned as follows:
     *
     * <ul><li>If the newly created instance implements {@link
     * ProxyAccessor}, a proxy is obtained by invoking the {@link
     * ProxyAccessor#getProxy getProxy} method on that instance. If the
     * obtained proxy is not <code>null</code>, that proxy is returned in a
     * <code>MarshalledObject</code>; otherwise, an
     * <code>ActivationException</code> is thrown.
     *
     * <li>If the newly created instance does not implement
     * <code>ProxyAccessor</code>, the instance is returned in a
     * <code>MarshalledObject</code>.  In this case, the instance must be
     * serializable, and marshalling the instance must produce a suitable
     * proxy for the remote object (for example, the object implements
     * {@link java.io.Serializable} and defines a <code>writeReplace</code>
     * method that returns the object's proxy).
     * </ul>
     *
     * <p>If both the remote object and the activation group are exported
     * using JRMP, then automatic stub replacement will produce the desired
     * result, but otherwise the remote object implementation must provide
     * a means for this group to obtain its proxy as indicated above.
     *
     * @throws ActivationException if the object's class could not be
     * loaded, if the loaded class does not define the appropriate
     * constructor, or any exception occurs activating the object
     **/
    public MarshalledObject newInstance(final ActivationID id,
					final ActivationDesc desc)
	throws ActivationException
    {
	synchronized (ActivationGroupImpl.class) {
	    if (refuseCalls) {
		throw new SecurityException("call refused");
	    }
	}

	acquireLock(id);
	try {
	    ActiveEntry entry;
	    synchronized (ActivationGroupImpl.class) {
		if (state != ACTIVE) {
		    throw new InactiveGroupException("group not active");
		}
		entry = (ActiveEntry) active.get(id);
	    }
	    if (entry != null) {
		return entry.mobj;
	    }

	    String className = desc.getClassName();
	    final Class cl = RiverClassLoader.loadClass(desc.getLocation(),
						      className);
	    final Thread t = Thread.currentThread();
	    final ClassLoader savedCcl = t.getContextClassLoader();
	    final ClassLoader ccl =
		covers(cl.getClassLoader(), savedCcl) ?
		cl.getClassLoader() : savedCcl;

	    Remote impl = null;

	    /*
	     * Fix for 4164971: allow non-public activatable class
	     * and/or constructor, create the activatable object in a
	     * privileged block
	     */
	    try {
		PrivilegedExceptionAction action =
		   new PrivilegedExceptionAction() {
		      public Object run() throws InstantiationException,
			  NoSuchMethodException, IllegalAccessException,
			  InvocationTargetException, ActivationException
		      {
			  Object[] params = new Object[] {id, desc.getData()};
			  Constructor constructor =
			      cl.getDeclaredConstructor(paramTypes);
			  constructor.setAccessible(true);
			  try {
			      /*
			       * Fix for 4289544: make sure to set the
			       * context class loader to be the class
			       * loader of the impl class before
			       * constructing that class.
			       */
			      t.setContextClassLoader(ccl);
			      return constructor.newInstance(params);
			  } finally {
			      t.setContextClassLoader(savedCcl);
			  }
		      }
		   };

		/*
		 * The activatable object is created is in a doPrivileged
		 * block to protect against user code which might have set
		 * a global socket factory (in which case application code
		 * would be on the stack).
		 */
		impl = (Remote) (inheritGroupSubject ?
				 Security.doPrivileged(action) :
				 AccessController.doPrivileged(action));
			
	    } catch (PrivilegedActionException pae) {
		throw pae.getException();
	    }

	    entry = new ActiveEntry(impl);
	    synchronized (ActivationGroupImpl.class) {
		active.put(id, entry);
	    }
	    return entry.mobj;
	} catch (NoSuchMethodException e) {
	    /* user forgot to provide activatable constructor? */
	    throw new ActivationException(
		"activation constructor not defined", e);
	} catch (NoSuchMethodError e) {
	    /* code recompiled and user forgot to provide
	     *  activatable constructor?
	     */
	    throw new ActivationException(
		"activation constructor not defined", e);
	} catch (InvocationTargetException e) {
	    throw new ActivationException("exception constructing object",
					  e.getTargetException());
	} catch (ActivationException e) {
	    throw e;
	} catch (Exception e) {
	    throw new ActivationException("unable to activate object", e);
	} finally {
	    releaseLock(id);
	    checkInactiveGroup();
	}
    }

    /**
     * Attempts to make the remote object that is associated with the
     * specified activation identifier, and that was exported as a JRMP
     * {@link java.rmi.activation.Activatable} object, inactive. This method
     * calls <code>Activatable.unexportObject</code> with the active remote
     * object and <code>false</code>, to unexport the object. If that call
     * returns <code>false</code>, this method returns <code>false</code>.
     * If that call returns <code>true</code>, the object is marked inactive
     * in this virtual machine, the superclass <code>inactiveObject</code>
     * method is called with the same activation identifier, with the
     * <code>ActivationMonitor</code> constraints (if any) set as
     * contextual client constraints, and with the group's subject (if any)
     * set as the executing subject, and this method returns <code>true</code>.
     *
     * @param id the activation identifier
     * @return <code>true</code> if the object was successfully made
     * inactive; <code>false</code> otherwise
     * @throws UnknownObjectException if the object is not known to be
     * active (it may already be inactive)
     * @throws ActivationException if an activation error occurs
     * @throws InactiveGroupException if the group is inactive
     * @throws RemoteException if the remote call to the activation
     * monitor fails
     *
     * @throws SecurityException if a security manager exists and invoking
     * its {@link SecurityManager#checkPermission checkPermission} method
     * with the permission <code>{@link MonitorPermission}("java.rmi.activation.ActivationMonitor.inactiveObject")</code>
     * throws a <code>SecurityException</code> 
     **/
    public boolean inactiveObject(final ActivationID id)
	throws ActivationException, RemoteException
    {
	SecurityManager security = System.getSecurityManager();
	if (security != null) {
	    security.checkPermission(inactiveObjectPermission);
	}
	acquireLock(id);
	try {
	    ActiveEntry entry;
	    synchronized (ActivationGroupImpl.class) {
		if (state != ACTIVE) {
		    throw new InactiveGroupException("group not active");
		}
		entry = (ActiveEntry) active.get(id);
	    }
	    if (entry == null) {
		// REMIND: should this be silent?
		throw new UnknownObjectException("object not active");
	    }

	    try {
		if (!Activatable.unexportObject(entry.impl, false)) {
		    return false;
		}
	    } catch (NoSuchObjectException allowUnexportedObjects) {
	    }

	    try {
		doAction(new PrivilegedExceptionAction() {
		    public Object run() throws Exception {
			monitor.inactiveObject(id);
			return null;
		    }
		});
	    } catch (UnknownObjectException allowUnregisteredObjects) {
	    }

	    synchronized (ActivationGroupImpl.class) {
		active.remove(id);
	    }
	} finally {
	    releaseLock(id);
	    checkInactiveGroup();
	}

	return true;
    }

    /**
     * Attempts to make the remote object that is associated with the
     * specified activation identifier, and that was exported through the
     * specified exporter, inactive. The {@link Exporter#unexport unexport}
     * method of the specified exporter is called with <code>false</code>
     * as an argument. If that call returns <code>false</code>, this method
     * returns <code>false</code>. If that call returns <code>true</code>,
     * the object is marked inactive in this virtual machine, the
     * superclass <code>inactiveObject</code> method is called with the
     * activation identifier, with the <code>ActivationMonitor</code>
     * constraints (if any) set as contextual client constraints, and with
     * the group's subject (if any) set as the executing subject, and this
     * method returns <code>true</code>.
     *
     * @param id the activation identifier
     * @param exporter the exporter to use to unexport the object
     * @return <code>true</code> if the object was successfully made
     * inactive; <code>false</code> otherwise
     * @throws UnknownObjectException if the object is not known to be
     * active (it may already be inactive)
     * @throws ActivationException if an activation error occurs
     * @throws InactiveGroupException if the group is inactive
     * @throws RemoteException if the remote call to the activation monitor
     * fails
     * @throws SecurityException if a security manager exists and invoking
     * its {@link SecurityManager#checkPermission checkPermission} method
     * with the permission <code>{@link MonitorPermission}("java.rmi.activation.ActivationMonitor.inactiveObject")</code>
     * throws a <code>SecurityException</code> 
     **/
    public boolean inactiveObject(final ActivationID id, Exporter exporter)
	throws ActivationException, RemoteException
    {
	SecurityManager security = System.getSecurityManager();
	if (security != null) {
	    security.checkPermission(inactiveObjectPermission);
	}
	acquireLock(id);
	try {
	    ActiveEntry entry;
	    synchronized (ActivationGroupImpl.class) {
		if (state != ACTIVE) {
		    throw new InactiveGroupException("group not active");
		}
		entry = (ActiveEntry) active.get(id);
	    }
	    if (entry == null) {
		// REMIND: should this be silent?
		throw new UnknownObjectException("object not active");
	    }

	    if (!exporter.unexport(false)) {
		return false;
	    }

	    try {
		doAction(new PrivilegedExceptionAction() {
		    public Object run() throws Exception {
			monitor.inactiveObject(id);
			return null;
		    }
		});
	    } catch (UnknownObjectException allowUnregisteredObjects) {
	    }

	    synchronized (ActivationGroupImpl.class) {
		active.remove(id);
	    }
	} finally {
	    releaseLock(id);
	    checkInactiveGroup();
	}

	return true;
    }

    /*
     * Determines if the group has become inactive and if so,
     * marks it as such, notifies the daemon, and unexports the group.
     */
    private static void checkInactiveGroup() {
	synchronized (ActivationGroupImpl.class) {
	    if (state == ACTIVE) {
		if (!active.isEmpty() || !lockedIDs.isEmpty()) {
		    return;
		}
		state = INACTIVE;
	    } else if (state == INACTIVE) {
		return;
	    } else if (state == CREATED) {
		state = UNUSED;
	    } else {
		if (login != null) {
		    try {
			login.logout();
		    } catch (LoginException e) {
		    }
		    login = null;
		}
		state = UNUSED;
		return;
	    }
	}
	try {
	    doAction(new PrivilegedExceptionAction() {
		public Object run() throws Exception {
		    monitor.inactiveGroup(groupID, incarnation);
		    return null;
		}
	    });
	} catch (Exception ignoreDeactivateFailure) {
	}
	Runnable action = new Runnable() {
	    public void run() {
		long stop = System.currentTimeMillis() + unexportTimeout;
		// allow a failing newInstance call to finish first
		boolean force = false;
		while (!exporter.unexport(force)) {
		    long rem = stop - System.currentTimeMillis();
		    if (rem <= 0) {
			force = true;
		    } else {
			try {
			    Thread.sleep(Math.min(rem, unexportWait));
			} catch (InterruptedException e) {
			}
		    }
		}
		if (login != null) {
		    try {
			login.logout();
		    } catch (LoginException e) {
		    }
		}
	    }
	};
	if (state == UNUSED) {
	    action.run();
	} else {
	    Executor systemThreadPool =
		(Executor) AccessController.doPrivileged(
		    (login == null) ?
		    (PrivilegedAction) new GetThreadPoolAction(false) :
		    new PrivilegedAction() {
			public Object run() {
			    return Subject.doAsPrivileged(
					      login.getSubject(),
					      new GetThreadPoolAction(false),
					      null);
			}
		});
	    systemThreadPool.execute(action, "UnexportGroup");
	}
    }

    /**
     * Marks the object as active in this virtual machine, and calls the
     * superclass <code>activeObject</code> method with the same arguments,
     * with the <code>ActivationMonitor</code> constraints (if any) set as
     * contextual client constraints, and with the group's subject (if any)
     * set as the executing subject. Any <code>RemoteException</code>
     * thrown by this call is caught and ignored. If the object is already
     * marked as active in this virtual machine, this method simply
     * returns.
     *
     * @param id the activation identifier
     * @param impl the active remote object
     * @throws UnknownObjectException if no object is registered under
     * the specified activation identifier
     * @throws ActivationException if an activation error occurs
     * @throws InactiveGroupException if the group is inactive
     * @throws SecurityException if a security manager exists and invoking
     * its {@link SecurityManager#checkPermission checkPermission} method
     * with the permission <code>{@link MonitorPermission}("java.rmi.activation.ActivationMonitor.activeObject")</code>
     * throws a <code>SecurityException</code> 
     **/
    public void activeObject(final ActivationID id, Remote impl)
	throws ActivationException
    {
	SecurityManager security = System.getSecurityManager();
	if (security != null) {
	    security.checkPermission(activeObjectPermission);
	}
	final ActiveEntry entry = new ActiveEntry(impl);
	acquireLock(id);
	try {
	    synchronized (ActivationGroupImpl.class) {
		if (state != ACTIVE) {
		    throw new InactiveGroupException("group not active");
		}
		if (active.containsKey(id)) {
		    return;
		}
		active.put(id, entry);
	    }
	    // created new entry, so inform monitor of active object
	    try {
		doAction(new PrivilegedExceptionAction() {
		    public Object run() throws Exception {
			monitor.activeObject(id, entry.mobj);
			return null;
		    }
		});
	    } catch (RemoteException ignore) {
		// daemon can still find it by calling newInstance
	    }
	} finally {
	    releaseLock(id);
	    checkInactiveGroup();
	}
    }

    /**
     * Execute the specified action on behalf of the server subject without
     * requiring the caller to have doAsPrivileged permission.
     */
    private static Object doAction(final PrivilegedExceptionAction action)
	throws ActivationException, RemoteException
    {
	try {
	    if (login == null) {
		return AccessController.doPrivileged(action);
	    } else {
		return AccessController.doPrivileged(
		    new PrivilegedExceptionAction() {
		        public Object run() throws Exception {
			    try {
				return Subject.doAsPrivileged(
					   login.getSubject(), action, null);
			    } catch (PrivilegedActionException e) {
				throw e.getException();
			    }
			}
		});
	    }
	} catch (PrivilegedActionException e) {
	    Exception ex = e.getException();
	    if (ex instanceof RemoteException) {
		throw (RemoteException) ex;
	    } else if (ex instanceof ActivationException) {
		throw (ActivationException) ex;
	    } else {
		throw new ActivationException("unexpected failure", ex);
	    }
	}
    }

    /**
     * Entry in table for active object.
     */
    private static class ActiveEntry {
	Remote impl;
	MarshalledObject mobj;

	ActiveEntry(Remote impl) throws ActivationException {
	    this.impl = impl;
	    try {
		Object proxy ;
		if (impl instanceof ProxyAccessor) {
		    proxy = ((ProxyAccessor) impl).getProxy();
		    if (proxy == null) {
			throw new ActivationException(
			    "ProxyAccessor.getProxy returned null");
		    }
		} else {
		    proxy = impl;
		}
		this.mobj = new MarshalledObject(proxy);
	    } catch (IOException e) {
		throw new ActivationException(
		    "failed to marshal remote object", e);
	    }
	}
    }
}
