aboutsummaryrefslogtreecommitdiff
path: root/src/share/classes/sun/rmi/server/ActivationGroupImpl.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/share/classes/sun/rmi/server/ActivationGroupImpl.java')
-rw-r--r--src/share/classes/sun/rmi/server/ActivationGroupImpl.java505
1 files changed, 505 insertions, 0 deletions
diff --git a/src/share/classes/sun/rmi/server/ActivationGroupImpl.java b/src/share/classes/sun/rmi/server/ActivationGroupImpl.java
new file mode 100644
index 000000000..950ada2c2
--- /dev/null
+++ b/src/share/classes/sun/rmi/server/ActivationGroupImpl.java
@@ -0,0 +1,505 @@
+/*
+ * Copyright 1997-2006 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+
+package sun.rmi.server;
+
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.net.ServerSocket;
+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.ActivationGroup;
+import java.rmi.activation.ActivationGroupID;
+import java.rmi.activation.ActivationID;
+import java.rmi.activation.UnknownObjectException;
+import java.rmi.server.RMIClassLoader;
+import java.rmi.server.RMIServerSocketFactory;
+import java.rmi.server.RMISocketFactory;
+import java.rmi.server.UnicastRemoteObject;
+import java.security.AccessController;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.List;
+import sun.rmi.registry.RegistryImpl;
+
+/**
+ * The default activation group implementation.
+ *
+ * @author Ann Wollrath
+ * @since 1.2
+ * @see java.rmi.activation.ActivationGroup
+ */
+public class ActivationGroupImpl extends ActivationGroup {
+
+ // use serialVersionUID from JDK 1.2.2 for interoperability
+ private static final long serialVersionUID = 5758693559430427303L;
+
+ /** maps persistent IDs to activated remote objects */
+ private final Hashtable<ActivationID,ActiveEntry> active =
+ new Hashtable<ActivationID,ActiveEntry>();
+ private boolean groupInactive = false;
+ private final ActivationGroupID groupID;
+ private final List<ActivationID> lockedIDs = new ArrayList<ActivationID>();
+
+ /**
+ * Creates a default activation group implementation.
+ *
+ * @param id the group's identifier
+ * @param data ignored
+ */
+ public ActivationGroupImpl(ActivationGroupID id, MarshalledObject<?> data)
+ throws RemoteException
+ {
+ super(id);
+ groupID = id;
+
+ /*
+ * Unexport activation group impl and attempt to export it on
+ * an unshared anonymous port. See 4692286.
+ */
+ unexportObject(this, true);
+ RMIServerSocketFactory ssf = new ServerSocketFactoryImpl();
+ UnicastRemoteObject.exportObject(this, 0, null, ssf);
+
+ if (System.getSecurityManager() == null) {
+ try {
+ // Provide a default security manager.
+ System.setSecurityManager(new SecurityManager());
+
+ } catch (Exception e) {
+ throw new RemoteException("unable to set security manager", e);
+ }
+ }
+ }
+
+ /**
+ * Trivial server socket factory used to export the activation group
+ * impl on an unshared port.
+ */
+ private static class ServerSocketFactoryImpl
+ implements RMIServerSocketFactory
+ {
+ public ServerSocket createServerSocket(int port) throws IOException
+ {
+ RMISocketFactory sf = RMISocketFactory.getSocketFactory();
+ if (sf == null) {
+ sf = RMISocketFactory.getDefaultSocketFactory();
+ }
+ return sf.createServerSocket(port);
+ }
+ }
+
+ /*
+ * 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 Object.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:
+ *
+ * try {
+ * acquireLock(id);
+ * // do stuff pertaining to id...
+ * } finally {
+ * releaseLock(id);
+ * checkInactiveGroup();
+ * }
+ */
+ private void acquireLock(ActivationID id) {
+
+ ActivationID waitForID;
+
+ for (;;) {
+
+ synchronized (lockedIDs) {
+ int index = lockedIDs.indexOf(id);
+ if (index < 0) {
+ lockedIDs.add(id);
+ return;
+ } else {
+ waitForID = lockedIDs.get(index);
+ }
+ }
+
+ synchronized (waitForID) {
+ synchronized (lockedIDs) {
+ int index = lockedIDs.indexOf(waitForID);
+ if (index < 0) continue;
+ ActivationID actualID = lockedIDs.get(index);
+ if (actualID != waitForID)
+ /*
+ * don't wait on an id that won't be notified.
+ */
+ continue;
+ }
+
+ try {
+ waitForID.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 = lockedIDs.remove(lockedIDs.indexOf(id));
+ }
+
+ synchronized (id) {
+ id.notifyAll();
+ }
+ }
+
+ /**
+ * Creates a new instance of an activatable remote object. The
+ * <code>Activator</code> calls this method to create an activatable
+ * object in this group. This method should be idempotent; a call to
+ * activate an already active object should return the previously
+ * activated object.
+ *
+ * Note: this method assumes that the Activator will only invoke
+ * newInstance for the same object in a serial fashion (i.e.,
+ * the activator will not allow the group to see concurrent requests
+ * to activate the same object.
+ *
+ * @param id the object's activation identifier
+ * @param desc the object's activation descriptor
+ * @return a marshalled object containing the activated object's stub
+ */
+ public MarshalledObject<? extends Remote>
+ newInstance(final ActivationID id,
+ final ActivationDesc desc)
+ throws ActivationException, RemoteException
+ {
+ RegistryImpl.checkAccess("ActivationInstantiator.newInstance");
+
+ if (!groupID.equals(desc.getGroupID()))
+ throw new ActivationException("newInstance in wrong group");
+
+ try {
+ acquireLock(id);
+ synchronized (this) {
+ if (groupInactive == true)
+ throw new InactiveGroupException("group is inactive");
+ }
+
+ ActiveEntry entry = active.get(id);
+ if (entry != null)
+ return entry.mobj;
+
+ String className = desc.getClassName();
+
+ final Class<? extends Remote> cl =
+ RMIClassLoader.loadClass(desc.getLocation(), className)
+ .asSubclass(Remote.class);
+ Remote impl = null;
+
+ final Thread t = Thread.currentThread();
+ final ClassLoader savedCcl = t.getContextClassLoader();
+ ClassLoader objcl = cl.getClassLoader();
+ final ClassLoader ccl = covers(objcl, savedCcl) ? objcl : savedCcl;
+
+ /*
+ * Fix for 4164971: allow non-public activatable class
+ * and/or constructor, create the activatable object in a
+ * privileged block
+ */
+ try {
+ /*
+ * The code below is in a doPrivileged block to
+ * protect against user code which code might have set
+ * a global socket factory (in which case application
+ * code would be on the stack).
+ */
+ impl = AccessController.doPrivileged(
+ new PrivilegedExceptionAction<Remote>() {
+ public Remote run() throws InstantiationException,
+ NoSuchMethodException, IllegalAccessException,
+ InvocationTargetException
+ {
+ Constructor<? extends Remote> constructor =
+ cl.getDeclaredConstructor(
+ ActivationID.class, MarshalledObject.class);
+ 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(id,
+ desc.getData());
+ } finally {
+ t.setContextClassLoader(savedCcl);
+ }
+ }
+ });
+ } catch (PrivilegedActionException pae) {
+ Throwable e = pae.getException();
+
+ // narrow the exception's type and rethrow it
+ if (e instanceof InstantiationException) {
+ throw (InstantiationException) e;
+ } else if (e instanceof NoSuchMethodException) {
+ throw (NoSuchMethodException) e;
+ } else if (e instanceof IllegalAccessException) {
+ throw (IllegalAccessException) e;
+ } else if (e instanceof InvocationTargetException) {
+ throw (InvocationTargetException) e;
+ } else if (e instanceof RuntimeException) {
+ throw (RuntimeException) e;
+ } else if (e instanceof Error) {
+ throw (Error) e;
+ }
+ }
+
+ entry = new ActiveEntry(impl);
+ active.put(id, entry);
+ return entry.mobj;
+
+ } catch (NoSuchMethodException e) {
+ /* user forgot to provide activatable constructor? */
+ throw new ActivationException
+ ("Activatable object must provide an activation"+
+ " constructor", e);
+
+ } catch (NoSuchMethodError e) {
+ /* code recompiled and user forgot to provide
+ * activatable constructor?
+ */
+ throw new ActivationException
+ ("Activatable object must provide an activation"+
+ " constructor", e );
+
+ } catch (InvocationTargetException e) {
+ throw new ActivationException("exception in object constructor",
+ e.getTargetException());
+
+ } catch (Exception e) {
+ throw new ActivationException("unable to activate object", e);
+ } finally {
+ releaseLock(id);
+ checkInactiveGroup();
+ }
+ }
+
+
+ /**
+ * The group's <code>inactiveObject</code> method is called
+ * indirectly via a call to the <code>Activatable.inactive</code>
+ * method. A remote object implementation must call
+ * <code>Activatable</code>'s <code>inactive</code> method when
+ * that object deactivates (the object deems that it is no longer
+ * active). If the object does not call
+ * <code>Activatable.inactive</code> when it deactivates, the
+ * object will never be garbage collected since the group keeps
+ * strong references to the objects it creates. <p>
+ *
+ * The group's <code>inactiveObject</code> method
+ * unexports the remote object from the RMI runtime so that the
+ * object can no longer receive incoming RMI calls. This call will
+ * only succeed if the object has no pending/executing calls. If
+ * the object does have pending/executing RMI calls, then false
+ * will be returned.
+ *
+ * If the object has no pending/executing calls, the object is
+ * removed from the RMI runtime and the group informs its
+ * <code>ActivationMonitor</code> (via the monitor's
+ * <code>inactiveObject</code> method) that the remote object is
+ * not currently active so that the remote object will be
+ * re-activated by the activator upon a subsequent activation
+ * request.
+ *
+ * @param id the object's activation identifier
+ * @returns true if the operation succeeds (the operation will
+ * succeed if the object in currently known to be active and is
+ * either already unexported or is currently exported and has no
+ * pending/executing calls); false is returned if the object has
+ * pending/executing calls in which case it cannot be deactivated
+ * @exception UnknownObjectException if object is unknown (may already
+ * be inactive)
+ * @exception RemoteException if call informing monitor fails
+ */
+ public boolean inactiveObject(ActivationID id)
+ throws ActivationException, UnknownObjectException, RemoteException
+ {
+
+ try {
+ acquireLock(id);
+ synchronized (this) {
+ if (groupInactive == true)
+ throw new ActivationException("group is inactive");
+ }
+
+ ActiveEntry entry = active.get(id);
+ if (entry == null) {
+ // REMIND: should this be silent?
+ throw new UnknownObjectException("object not active");
+ }
+
+ try {
+ if (Activatable.unexportObject(entry.impl, false) == false)
+ return false;
+ } catch (NoSuchObjectException allowUnexportedObjects) {
+ }
+
+ try {
+ super.inactiveObject(id);
+ } catch (UnknownObjectException allowUnregisteredObjects) {
+ }
+
+ active.remove(id);
+
+ } finally {
+ releaseLock(id);
+ checkInactiveGroup();
+ }
+
+ return true;
+ }
+
+ /*
+ * Determines if the group has become inactive and
+ * marks it as such.
+ */
+ private void checkInactiveGroup() {
+ boolean groupMarkedInactive = false;
+ synchronized (this) {
+ if (active.size() == 0 && lockedIDs.size() == 0 &&
+ groupInactive == false)
+ {
+ groupInactive = true;
+ groupMarkedInactive = true;
+ }
+ }
+
+ if (groupMarkedInactive) {
+ try {
+ super.inactiveGroup();
+ } catch (Exception ignoreDeactivateFailure) {
+ }
+
+ try {
+ UnicastRemoteObject.unexportObject(this, true);
+ } catch (NoSuchObjectException allowUnexportedGroup) {
+ }
+ }
+ }
+
+ /**
+ * The group's <code>activeObject</code> method is called when an
+ * object is exported (either by <code>Activatable</code> object
+ * construction or an explicit call to
+ * <code>Activatable.exportObject</code>. The group must inform its
+ * <code>ActivationMonitor</code> that the object is active (via
+ * the monitor's <code>activeObject</code> method) if the group
+ * hasn't already done so.
+ *
+ * @param id the object's identifier
+ * @param obj the remote object implementation
+ * @exception UnknownObjectException if object is not registered
+ * @exception RemoteException if call informing monitor fails
+ */
+ public void activeObject(ActivationID id, Remote impl)
+ throws ActivationException, UnknownObjectException, RemoteException
+ {
+
+ try {
+ acquireLock(id);
+ synchronized (this) {
+ if (groupInactive == true)
+ throw new ActivationException("group is inactive");
+ }
+ if (!active.contains(id)) {
+ ActiveEntry entry = new ActiveEntry(impl);
+ active.put(id, entry);
+ // created new entry, so inform monitor of active object
+ try {
+ super.activeObject(id, entry.mobj);
+ } catch (RemoteException e) {
+ // daemon can still find it by calling newInstance
+ }
+ }
+ } finally {
+ releaseLock(id);
+ checkInactiveGroup();
+ }
+ }
+
+ /**
+ * Entry in table for active object.
+ */
+ private static class ActiveEntry {
+ Remote impl;
+ MarshalledObject<Remote> mobj;
+
+ ActiveEntry(Remote impl) throws ActivationException {
+ this.impl = impl;
+ try {
+ this.mobj = new MarshalledObject<Remote>(impl);
+ } catch (IOException e) {
+ throw new
+ ActivationException("failed to marshal remote object", e);
+ }
+ }
+ }
+
+ /**
+ * 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;
+ }
+}