1 /*
   2  * Copyright (c) 1997, 2006, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package sun.rmi.server;
  27 
  28 import java.io.IOException;
  29 import java.lang.reflect.Constructor;
  30 import java.lang.reflect.InvocationTargetException;
  31 import java.net.ServerSocket;
  32 import java.rmi.MarshalledObject;
  33 import java.rmi.NoSuchObjectException;
  34 import java.rmi.Remote;
  35 import java.rmi.RemoteException;
  36 import java.rmi.activation.Activatable;
  37 import java.rmi.activation.ActivationDesc;
  38 import java.rmi.activation.ActivationException;
  39 import java.rmi.activation.ActivationGroup;
  40 import java.rmi.activation.ActivationGroupID;
  41 import java.rmi.activation.ActivationID;
  42 import java.rmi.activation.UnknownObjectException;
  43 import java.rmi.server.RMIClassLoader;
  44 import java.rmi.server.RMIServerSocketFactory;
  45 import java.rmi.server.RMISocketFactory;
  46 import java.rmi.server.UnicastRemoteObject;
  47 import java.security.AccessController;
  48 import java.security.PrivilegedActionException;
  49 import java.security.PrivilegedExceptionAction;
  50 import java.util.ArrayList;
  51 import java.util.Hashtable;
  52 import java.util.List;
  53 import sun.rmi.registry.RegistryImpl;
  54 
  55 /**
  56  * The default activation group implementation.
  57  *
  58  * @author      Ann Wollrath
  59  * @since       1.2
  60  * @see         java.rmi.activation.ActivationGroup
  61  */
  62 public class ActivationGroupImpl extends ActivationGroup {
  63 
  64     // use serialVersionUID from JDK 1.2.2 for interoperability
  65     private static final long serialVersionUID = 5758693559430427303L;
  66 
  67     /** maps persistent IDs to activated remote objects */
  68     private final Hashtable<ActivationID,ActiveEntry> active =
  69         new Hashtable<ActivationID,ActiveEntry>();
  70     private boolean groupInactive = false;
  71     private final ActivationGroupID groupID;
  72     private final List<ActivationID> lockedIDs = new ArrayList<ActivationID>();
  73 
  74     /**
  75      * Creates a default activation group implementation.
  76      *
  77      * @param id the group's identifier
  78      * @param data ignored
  79      */
  80     public ActivationGroupImpl(ActivationGroupID id, MarshalledObject<?> data)
  81         throws RemoteException
  82     {
  83         super(id);
  84         groupID = id;
  85 
  86         /*
  87          * Unexport activation group impl and attempt to export it on
  88          * an unshared anonymous port.  See 4692286.
  89          */
  90         unexportObject(this, true);
  91         RMIServerSocketFactory ssf = new ServerSocketFactoryImpl();
  92         UnicastRemoteObject.exportObject(this, 0, null, ssf);
  93 
  94         if (System.getSecurityManager() == null) {
  95             try {
  96                 // Provide a default security manager.
  97                 System.setSecurityManager(new SecurityManager());
  98 
  99             } catch (Exception e) {
 100                 throw new RemoteException("unable to set security manager", e);
 101             }
 102         }
 103     }
 104 
 105     /**
 106      * Trivial server socket factory used to export the activation group
 107      * impl on an unshared port.
 108      */
 109     private static class ServerSocketFactoryImpl
 110         implements RMIServerSocketFactory
 111     {
 112         public ServerSocket createServerSocket(int port) throws IOException
 113         {
 114             RMISocketFactory sf = RMISocketFactory.getSocketFactory();
 115             if (sf == null) {
 116                 sf = RMISocketFactory.getDefaultSocketFactory();
 117             }
 118             return sf.createServerSocket(port);
 119         }
 120     }
 121 
 122     /*
 123      * Obtains a lock on the ActivationID id before returning. Allows only one
 124      * thread at a time to hold a lock on a particular id.  If the lock for id
 125      * is in use, all requests for an equivalent (in the Object.equals sense)
 126      * id will wait for the id to be notified and use the supplied id as the
 127      * next lock. The caller of "acquireLock" must execute the "releaseLock"
 128      * method" to release the lock and "notifyAll" waiters for the id lock
 129      * obtained from this method.  The typical usage pattern is as follows:
 130      *
 131      * try {
 132      *    acquireLock(id);
 133      *    // do stuff pertaining to id...
 134      * } finally {
 135      *    releaseLock(id);
 136      *    checkInactiveGroup();
 137      * }
 138      */
 139     private void acquireLock(ActivationID id) {
 140 
 141         ActivationID waitForID;
 142 
 143         for (;;) {
 144 
 145             synchronized (lockedIDs) {
 146                 int index = lockedIDs.indexOf(id);
 147                 if (index < 0) {
 148                     lockedIDs.add(id);
 149                     return;
 150                 } else {
 151                     waitForID = lockedIDs.get(index);
 152                 }
 153             }
 154 
 155             synchronized (waitForID) {
 156                 synchronized (lockedIDs) {
 157                     int index = lockedIDs.indexOf(waitForID);
 158                     if (index < 0) continue;
 159                     ActivationID actualID = lockedIDs.get(index);
 160                     if (actualID != waitForID)
 161                         /*
 162                          * don't wait on an id that won't be notified.
 163                          */
 164                         continue;
 165                 }
 166 
 167                 try {
 168                     waitForID.wait();
 169                 } catch (InterruptedException ignore) {
 170                 }
 171             }
 172         }
 173 
 174     }
 175 
 176     /*
 177      * Releases the id lock obtained via the "acquireLock" method and then
 178      * notifies all threads waiting on the lock.
 179      */
 180     private void releaseLock(ActivationID id) {
 181         synchronized (lockedIDs) {
 182             id = lockedIDs.remove(lockedIDs.indexOf(id));
 183         }
 184 
 185         synchronized (id) {
 186             id.notifyAll();
 187         }
 188     }
 189 
 190     /**
 191      * Creates a new instance of an activatable remote object. The
 192      * <code>Activator</code> calls this method to create an activatable
 193      * object in this group. This method should be idempotent; a call to
 194      * activate an already active object should return the previously
 195      * activated object.
 196      *
 197      * Note: this method assumes that the Activator will only invoke
 198      * newInstance for the same object in a serial fashion (i.e.,
 199      * the activator will not allow the group to see concurrent requests
 200      * to activate the same object.
 201      *
 202      * @param id the object's activation identifier
 203      * @param desc the object's activation descriptor
 204      * @return a marshalled object containing the activated object's stub
 205      */
 206     public MarshalledObject<? extends Remote>
 207                                       newInstance(final ActivationID id,
 208                                                   final ActivationDesc desc)
 209         throws ActivationException, RemoteException
 210     {
 211         RegistryImpl.checkAccess("ActivationInstantiator.newInstance");
 212 
 213         if (!groupID.equals(desc.getGroupID()))
 214             throw new ActivationException("newInstance in wrong group");
 215 
 216         try {
 217             acquireLock(id);
 218             synchronized (this) {
 219                 if (groupInactive == true)
 220                     throw new InactiveGroupException("group is inactive");
 221             }
 222 
 223             ActiveEntry entry = active.get(id);
 224             if (entry != null)
 225                 return entry.mobj;
 226 
 227             String className = desc.getClassName();
 228 
 229             final Class<? extends Remote> cl =
 230                 RMIClassLoader.loadClass(desc.getLocation(), className)
 231                 .asSubclass(Remote.class);
 232             Remote impl = null;
 233 
 234             final Thread t = Thread.currentThread();
 235             final ClassLoader savedCcl = t.getContextClassLoader();
 236             ClassLoader objcl = cl.getClassLoader();
 237             final ClassLoader ccl = covers(objcl, savedCcl) ? objcl : savedCcl;
 238 
 239             /*
 240              * Fix for 4164971: allow non-public activatable class
 241              * and/or constructor, create the activatable object in a
 242              * privileged block
 243              */
 244             try {
 245                 /*
 246                  * The code below is in a doPrivileged block to
 247                  * protect against user code which code might have set
 248                  * a global socket factory (in which case application
 249                  * code would be on the stack).
 250                  */
 251                 impl = AccessController.doPrivileged(
 252                       new PrivilegedExceptionAction<Remote>() {
 253                       public Remote run() throws InstantiationException,
 254                           NoSuchMethodException, IllegalAccessException,
 255                           InvocationTargetException
 256                       {
 257                           Constructor<? extends Remote> constructor =
 258                               cl.getDeclaredConstructor(
 259                                   ActivationID.class, MarshalledObject.class);
 260                           constructor.setAccessible(true);
 261                           try {
 262                               /*
 263                                * Fix for 4289544: make sure to set the
 264                                * context class loader to be the class
 265                                * loader of the impl class before
 266                                * constructing that class.
 267                                */
 268                               t.setContextClassLoader(ccl);
 269                               return constructor.newInstance(id,
 270                                                              desc.getData());
 271                           } finally {
 272                               t.setContextClassLoader(savedCcl);
 273                           }
 274                       }
 275                   });
 276             } catch (PrivilegedActionException pae) {
 277                 Throwable e = pae.getException();
 278 
 279                 // narrow the exception's type and rethrow it
 280                 if (e instanceof InstantiationException) {
 281                     throw (InstantiationException) e;
 282                 } else if (e instanceof NoSuchMethodException) {
 283                     throw (NoSuchMethodException) e;
 284                 } else if (e instanceof IllegalAccessException) {
 285                     throw (IllegalAccessException) e;
 286                 } else if (e instanceof InvocationTargetException) {
 287                     throw (InvocationTargetException) e;
 288                 } else if (e instanceof RuntimeException) {
 289                     throw (RuntimeException) e;
 290                 } else if (e instanceof Error) {
 291                     throw (Error) e;
 292                 }
 293             }
 294 
 295             entry = new ActiveEntry(impl);
 296             active.put(id, entry);
 297             return entry.mobj;
 298 
 299         } catch (NoSuchMethodException e) {
 300             /* user forgot to provide activatable constructor? */
 301             throw new ActivationException
 302                 ("Activatable object must provide an activation"+
 303                  " constructor", e);
 304 
 305         } catch (NoSuchMethodError e) {
 306             /* code recompiled and user forgot to provide
 307              *  activatable constructor?
 308              */
 309             throw new ActivationException
 310                 ("Activatable object must provide an activation"+
 311                  " constructor", e );
 312 
 313         } catch (InvocationTargetException e) {
 314             throw new ActivationException("exception in object constructor",
 315                                           e.getTargetException());
 316 
 317         } catch (Exception e) {
 318             throw new ActivationException("unable to activate object", e);
 319         } finally {
 320             releaseLock(id);
 321             checkInactiveGroup();
 322         }
 323     }
 324 
 325 
 326    /**
 327     * The group's <code>inactiveObject</code> method is called
 328     * indirectly via a call to the <code>Activatable.inactive</code>
 329     * method. A remote object implementation must call
 330     * <code>Activatable</code>'s <code>inactive</code> method when
 331     * that object deactivates (the object deems that it is no longer
 332     * active). If the object does not call
 333     * <code>Activatable.inactive</code> when it deactivates, the
 334     * object will never be garbage collected since the group keeps
 335     * strong references to the objects it creates. <p>
 336     *
 337     * The group's <code>inactiveObject</code> method
 338     * unexports the remote object from the RMI runtime so that the
 339     * object can no longer receive incoming RMI calls. This call will
 340     * only succeed if the object has no pending/executing calls. If
 341     * the object does have pending/executing RMI calls, then false
 342     * will be returned.
 343     *
 344     * If the object has no pending/executing calls, the object is
 345     * removed from the RMI runtime and the group informs its
 346     * <code>ActivationMonitor</code> (via the monitor's
 347     * <code>inactiveObject</code> method) that the remote object is
 348     * not currently active so that the remote object will be
 349     * re-activated by the activator upon a subsequent activation
 350     * request.
 351     *
 352     * @param id the object's activation identifier
 353     * @returns true if the operation succeeds (the operation will
 354     * succeed if the object in currently known to be active and is
 355     * either already unexported or is currently exported and has no
 356     * pending/executing calls); false is returned if the object has
 357     * pending/executing calls in which case it cannot be deactivated
 358     * @exception UnknownObjectException if object is unknown (may already
 359     * be inactive)
 360     * @exception RemoteException if call informing monitor fails
 361     */
 362     public boolean inactiveObject(ActivationID id)
 363         throws ActivationException, UnknownObjectException, RemoteException
 364     {
 365 
 366         try {
 367             acquireLock(id);
 368             synchronized (this) {
 369                 if (groupInactive == true)
 370                     throw new ActivationException("group is inactive");
 371             }
 372 
 373             ActiveEntry entry = active.get(id);
 374             if (entry == null) {
 375                 // REMIND: should this be silent?
 376                 throw new UnknownObjectException("object not active");
 377             }
 378 
 379             try {
 380                 if (Activatable.unexportObject(entry.impl, false) == false)
 381                     return false;
 382             } catch (NoSuchObjectException allowUnexportedObjects) {
 383             }
 384 
 385             try {
 386                 super.inactiveObject(id);
 387             } catch (UnknownObjectException allowUnregisteredObjects) {
 388             }
 389 
 390             active.remove(id);
 391 
 392         } finally {
 393             releaseLock(id);
 394             checkInactiveGroup();
 395         }
 396 
 397         return true;
 398     }
 399 
 400     /*
 401      * Determines if the group has become inactive and
 402      * marks it as such.
 403      */
 404     private void checkInactiveGroup() {
 405         boolean groupMarkedInactive = false;
 406         synchronized (this) {
 407             if (active.size() == 0 && lockedIDs.size() == 0 &&
 408                 groupInactive == false)
 409             {
 410                 groupInactive = true;
 411                 groupMarkedInactive = true;
 412             }
 413         }
 414 
 415         if (groupMarkedInactive) {
 416             try {
 417                 super.inactiveGroup();
 418             } catch (Exception ignoreDeactivateFailure) {
 419             }
 420 
 421             try {
 422                 UnicastRemoteObject.unexportObject(this, true);
 423             } catch (NoSuchObjectException allowUnexportedGroup) {
 424             }
 425         }
 426     }
 427 
 428     /**
 429      * The group's <code>activeObject</code> method is called when an
 430      * object is exported (either by <code>Activatable</code> object
 431      * construction or an explicit call to
 432      * <code>Activatable.exportObject</code>. The group must inform its
 433      * <code>ActivationMonitor</code> that the object is active (via
 434      * the monitor's <code>activeObject</code> method) if the group
 435      * hasn't already done so.
 436      *
 437      * @param id the object's identifier
 438      * @param obj the remote object implementation
 439      * @exception UnknownObjectException if object is not registered
 440      * @exception RemoteException if call informing monitor fails
 441      */
 442     public void activeObject(ActivationID id, Remote impl)
 443         throws ActivationException, UnknownObjectException, RemoteException
 444     {
 445 
 446         try {
 447             acquireLock(id);
 448             synchronized (this) {
 449                 if (groupInactive == true)
 450                     throw new ActivationException("group is inactive");
 451             }
 452             if (!active.contains(id)) {
 453                 ActiveEntry entry = new ActiveEntry(impl);
 454                 active.put(id, entry);
 455                 // created new entry, so inform monitor of active object
 456                 try {
 457                     super.activeObject(id, entry.mobj);
 458                 } catch (RemoteException e) {
 459                     // daemon can still find it by calling newInstance
 460                 }
 461             }
 462         } finally {
 463             releaseLock(id);
 464             checkInactiveGroup();
 465         }
 466     }
 467 
 468     /**
 469      * Entry in table for active object.
 470      */
 471     private static class ActiveEntry {
 472         Remote impl;
 473         MarshalledObject<Remote> mobj;
 474 
 475         ActiveEntry(Remote impl) throws ActivationException {
 476             this.impl =  impl;
 477             try {
 478                 this.mobj = new MarshalledObject<Remote>(impl);
 479             } catch (IOException e) {
 480                 throw new
 481                     ActivationException("failed to marshal remote object", e);
 482             }
 483         }
 484     }
 485 
 486     /**
 487      * Returns true if the first argument is either equal to, or is a
 488      * descendant of, the second argument.  Null is treated as the root of
 489      * the tree.
 490      */
 491     private static boolean covers(ClassLoader sub, ClassLoader sup) {
 492         if (sup == null) {
 493             return true;
 494         } else if (sub == null) {
 495             return false;
 496         }
 497         do {
 498             if (sub == sup) {
 499                 return true;
 500             }
 501             sub = sub.getParent();
 502         } while (sub != null);
 503         return false;
 504     }
 505 }