/* * 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.*; import java.rmi.server.ExportException; import java.rmi.server.RMIClassLoader; 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.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">•</font> * <th scope="col" align="left" colspan="2"> <font size="+1"><code> * loginContext</code></font> * <tr valign="top"> <td>   <th scope="row" align="right"> * Type: <td> <code>{@link javax.security.auth.login.LoginContext}</code> * <tr valign="top"> <td>   <th scope="row" align="right"> * Default: <td> <code>null</code> * <tr valign="top"> <td>   <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">•</font> * <th scope="col" align="left" colspan="2"> <font size="+1"><code> * inheritGroupSubject</code></font> * <tr valign="top"> <td>   <th scope="row" align="right"> * Type: <td> <code>boolean</code> * <tr valign="top"> <td>   <th scope="row" align="right"> * Default: <td> <code>false</code> * <tr valign="top"> <td>   <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">•</font> * <th scope="col" align="left" colspan="2"> <font size="+1"><code> * instantiatorExporter</code></font> * <tr valign="top"> <td>   <th scope="row" align="right"> * Type: <td> <code>{@link net.jini.export.Exporter}</code> * <tr valign="top"> <td>   <th scope="row" align="right"> * Default: <td> retains existing JRMP export of instantiator * <tr valign="top"> <td>   <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">•</font> * <th scope="col" align="left" colspan="2"> <font size="+1"><code> * monitorPreparer</code></font> * <tr valign="top"> <td>   <th scope="row" align="right"> * Type: <td> <code>{@link net.jini.security.ProxyPreparer}</code> * <tr valign="top"> <td>   <th scope="row" align="right"> * Default: <td> <code>new {@link * net.jini.security.BasicProxyPreparer}()</code> * <tr valign="top"> <td>   <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">•</font> * <th scope="col" align="left" colspan="2"> <font size="+1"><code> * systemPreparer</code></font> * <tr valign="top"> <td>   <th scope="row" align="right"> * Type: <td> <code>{@link net.jini.security.ProxyPreparer}</code> * <tr valign="top"> <td>   <th scope="row" align="right"> * Default: <td> <code>new {@link * net.jini.security.BasicProxyPreparer}()</code> * <tr valign="top"> <td>   <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">•</font> * <th scope="col" align="left" colspan="2"> <font size="+1"><code> * unexportTimeout</code></font> * <tr valign="top"> <td>   <th scope="row" align="right"> * Type: <td> <code>int</code> * <tr valign="top"> <td>   <th scope="row" align="right"> * Default: <td> <code>60000</code> * <tr valign="top"> <td>   <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">•</font> * <th scope="col" align="left" colspan="2"> <font size="+1"><code> * unexportWait</code></font> * <tr valign="top"> <td>   <th scope="row" align="right"> * Type: <td> <code>int</code> * <tr valign="top"> <td>   <th scope="row" align="right"> * Default: <td> <code>10</code> * <tr valign="top"> <td>   <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>. * * @author Sun Microsystems, Inc. * * @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, new Integer(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 * RMIClassLoader#loadClass(String,String) RMIClassLoader.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 = RMIClassLoader.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); } } } }