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.ByteArrayOutputStream;
  29 import java.io.File;
  30 import java.io.FileOutputStream;
  31 import java.io.IOException;
  32 import java.io.InputStream;
  33 import java.io.ObjectOutputStream;
  34 import java.io.OutputStream;
  35 import java.io.PrintStream;
  36 import java.io.PrintWriter;
  37 import java.io.Serializable;
  38 import java.lang.Process;
  39 import java.lang.reflect.InvocationTargetException;
  40 import java.lang.reflect.Method;
  41 import java.net.InetAddress;
  42 import java.net.ServerSocket;
  43 import java.net.Socket;
  44 import java.net.SocketAddress;
  45 import java.net.SocketException;
  46 import java.nio.channels.Channel;
  47 import java.nio.channels.ServerSocketChannel;
  48 import java.rmi.AccessException;
  49 import java.rmi.AlreadyBoundException;
  50 import java.rmi.ConnectException;
  51 import java.rmi.ConnectIOException;
  52 import java.rmi.MarshalledObject;
  53 import java.rmi.NoSuchObjectException;
  54 import java.rmi.NotBoundException;
  55 import java.rmi.Remote;
  56 import java.rmi.RemoteException;
  57 import java.rmi.activation.ActivationDesc;
  58 import java.rmi.activation.ActivationException;
  59 import java.rmi.activation.ActivationGroupDesc;
  60 import java.rmi.activation.ActivationGroup;
  61 import java.rmi.activation.ActivationGroupID;
  62 import java.rmi.activation.ActivationID;
  63 import java.rmi.activation.ActivationInstantiator;
  64 import java.rmi.activation.ActivationMonitor;
  65 import java.rmi.activation.ActivationSystem;
  66 import java.rmi.activation.Activator;
  67 import java.rmi.activation.UnknownGroupException;
  68 import java.rmi.activation.UnknownObjectException;
  69 import java.rmi.registry.Registry;
  70 import java.rmi.server.ObjID;
  71 import java.rmi.server.RMIClassLoader;
  72 import java.rmi.server.RMIClientSocketFactory;
  73 import java.rmi.server.RMIServerSocketFactory;
  74 import java.rmi.server.RemoteObject;
  75 import java.rmi.server.RemoteServer;
  76 import java.rmi.server.UnicastRemoteObject;
  77 import java.security.AccessControlException;
  78 import java.security.AccessController;
  79 import java.security.AllPermission;
  80 import java.security.CodeSource;
  81 import java.security.Permission;
  82 import java.security.PermissionCollection;
  83 import java.security.Permissions;
  84 import java.security.Policy;
  85 import java.security.PrivilegedAction;
  86 import java.security.PrivilegedExceptionAction;
  87 import java.security.cert.Certificate;
  88 import java.text.MessageFormat;
  89 import java.util.ArrayList;
  90 import java.util.Arrays;
  91 import java.util.Date;
  92 import java.util.Enumeration;
  93 import java.util.HashMap;
  94 import java.util.HashSet;
  95 import java.util.Iterator;
  96 import java.util.List;
  97 import java.util.Map;
  98 import java.util.MissingResourceException;
  99 import java.util.Properties;
 100 import java.util.ResourceBundle;
 101 import java.util.Set;
 102 import sun.rmi.log.LogHandler;
 103 import sun.rmi.log.ReliableLog;
 104 import sun.rmi.registry.RegistryImpl;
 105 import sun.rmi.runtime.NewThreadAction;
 106 import sun.rmi.server.UnicastServerRef;
 107 import sun.rmi.transport.LiveRef;
 108 import sun.security.action.GetBooleanAction;
 109 import sun.security.action.GetIntegerAction;
 110 import sun.security.action.GetPropertyAction;
 111 import sun.security.provider.PolicyFile;
 112 import com.sun.rmi.rmid.ExecPermission;
 113 import com.sun.rmi.rmid.ExecOptionPermission;
 114 
 115 /**
 116  * The Activator facilitates remote object activation. A "faulting"
 117  * remote reference calls the activator's <code>activate</code> method
 118  * to obtain a "live" reference to a activatable remote object. Upon
 119  * receiving a request for activation, the activator looks up the
 120  * activation descriptor for the activation identifier, id, determines
 121  * the group in which the object should be activated and invokes the
 122  * activate method on the object's activation group (described by the
 123  * remote interface <code>ActivationInstantiator</code>). The
 124  * activator initiates the execution of activation groups as
 125  * necessary. For example, if an activation group for a specific group
 126  * identifier is not already executing, the activator will spawn a
 127  * child process for the activation group. <p>
 128  *
 129  * The activator is responsible for monitoring and detecting when
 130  * activation groups fail so that it can remove stale remote references
 131  * from its internal tables. <p>
 132  *
 133  * @author      Ann Wollrath
 134  * @since       1.2
 135  */
 136 public class Activation implements Serializable {
 137 
 138     /*
 139      * Synchronization occurs over several objects in this code.
 140      * These objects are:
 141      *
 142      *  - startupLock
 143      *  - initLock
 144      *  - Activation.this
 145      *  - instances of GroupEntry
 146      *  - log (a ReliableLog)
 147      *  - idTable (a HashMap)
 148      *  - groupTable (a HashMap)
 149      *
 150      * The startupLock and initLock locks are used only during
 151      * initialization and shutdown. The lock on Activation.this
 152      * is used to guard groupSemaphore and to guard writes to the
 153      * shuttingDown flag (which is volatile and which is read
 154      * without synchronization).
 155      *
 156      * Several operations on GroupEntry instances hold a lock on
 157      * that instance while performing logging operations and while
 158      * while manipulating idTable or groupTable.
 159      *
 160      * Log operations, notably addLogRecord(), hold a lock on the
 161      * log instance. (Methods of the log itself also take locks
 162      * on the log object.) The log.snapshot() operation serializes
 163      * the log, including instances of Activation (this class),
 164      * which in turn must lock idTable and groupTable to prevent
 165      * concurrent modification during serialization.
 166      *
 167      * The idTable and groupTable HashMaps are locked while simple
 168      * operations are performed on them. Note that we cannot
 169      * straightforwardly convert these to ConcurrentHashMap,
 170      * because it has a different serialized form from HashMap.
 171      *
 172      * During serialization, the writeObject() method of this class
 173      * takes locks on groupTable and idTable in that order.
 174      * 
 175      * Based on the above paths through the code, a partial
 176      * ordering for the above locks must be as follows:
 177      *
 178      * 1. GroupEntry instances
 179      * 2. log
 180      * 3. groupTable
 181      * 4. idTable
 182      *
 183      * For example, code may lock the log while holding the
 184      * lock for a GroupEntry, but code must not lock the log
 185      * while holding a lock on groupTable.
 186      */
 187 
 188     /** indicate compatibility with JDK 1.2 version of class */
 189     private static final long serialVersionUID = 2921265612698155191L;
 190 
 191     private static final byte MAJOR_VERSION = 1;
 192     private static final byte MINOR_VERSION = 0;
 193 
 194     /** exec policy object */
 195     private static Object execPolicy;
 196     private static Method execPolicyMethod;
 197     private static boolean debugExec;
 198 
 199     /** maps activation id to its respective group id */
 200     private Map<ActivationID,ActivationGroupID> idTable =
 201         new HashMap<ActivationID,ActivationGroupID>();
 202     /** maps group id to its GroupEntry groups */
 203     private Map<ActivationGroupID,GroupEntry> groupTable =
 204         new HashMap<ActivationGroupID,GroupEntry>();
 205 
 206     private byte majorVersion = MAJOR_VERSION;
 207     private byte minorVersion = MINOR_VERSION;
 208 
 209     /** number of simultaneous group exec's */
 210     private transient int groupSemaphore;
 211     /** counter for numbering groups */
 212     private transient int groupCounter;
 213     /** reliable log to hold descriptor table */
 214     private transient ReliableLog log;
 215     /** number of updates since last snapshot */
 216     private transient int numUpdates;
 217 
 218     /** the java command */
 219     // accessed by GroupEntry
 220     private transient String[] command;
 221     /** timeout on wait for child process to be created or destroyed */
 222     private static final long groupTimeout =
 223         getInt("sun.rmi.activation.groupTimeout", 60000);
 224     /** take snapshot after this many updates */
 225     private static final int snapshotInterval =
 226         getInt("sun.rmi.activation.snapshotInterval", 200);
 227     /** timeout on wait for child process to be created */
 228     private static final long execTimeout =
 229         getInt("sun.rmi.activation.execTimeout", 30000);
 230 
 231     private static final Object initLock = new Object();
 232     private static boolean initDone = false;
 233 
 234     // this should be a *private* method since it is privileged
 235     private static int getInt(String name, int def) {
 236         return AccessController.doPrivileged(new GetIntegerAction(name, def));
 237     }
 238 
 239     private transient Activator activator;
 240     private transient Activator activatorStub;
 241     private transient ActivationSystem system;
 242     private transient ActivationSystem systemStub;
 243     private transient ActivationMonitor monitor;
 244     private transient Registry registry;
 245     private transient volatile boolean shuttingDown = false;
 246     private transient volatile Object startupLock;
 247     private transient Thread shutdownHook;
 248 
 249     private static ResourceBundle resources = null;
 250 
 251     /**
 252      * Create an uninitialized instance of Activation that can be
 253      * populated with log data.  This is only called when the initial
 254      * snapshot is taken during the first incarnation of rmid.
 255      */
 256     private Activation() {}
 257 
 258     /**
 259      * Recover activation state from the reliable log and initialize
 260      * activation services.
 261      */
 262     private static void startActivation(int port,
 263                                         RMIServerSocketFactory ssf,
 264                                         String logName,
 265                                         String[] childArgs)
 266         throws Exception
 267     {
 268         ReliableLog log = new ReliableLog(logName, new ActLogHandler());
 269         Activation state = (Activation) log.recover();
 270         state.init(port, ssf, log, childArgs);
 271     }
 272 
 273     /**
 274      * Initialize the Activation instantiation; start activation
 275      * services.
 276      */
 277     private void init(int port,
 278                       RMIServerSocketFactory ssf,
 279                       ReliableLog log,
 280                       String[] childArgs)
 281         throws Exception
 282     {
 283         // initialize
 284         this.log = log;
 285         numUpdates = 0;
 286         shutdownHook =  new ShutdownHook();
 287         groupSemaphore = getInt("sun.rmi.activation.groupThrottle", 3);
 288         groupCounter = 0;
 289         Runtime.getRuntime().addShutdownHook(shutdownHook);
 290         ActivationGroupID[] gids =
 291             groupTable.keySet().toArray(
 292                 new ActivationGroupID[groupTable.size()]);
 293 
 294         synchronized (startupLock = new Object()) {
 295             // all the remote methods briefly synchronize on startupLock
 296             // (via checkShutdown) to make sure they don't happen in the
 297             // middle of this block.  This block must not cause any such
 298             // incoming remote calls to happen, or deadlock would result!
 299             activator = new ActivatorImpl(port, ssf);
 300             activatorStub = (Activator) RemoteObject.toStub(activator);
 301             system = new ActivationSystemImpl(port, ssf);
 302             systemStub = (ActivationSystem) RemoteObject.toStub(system);
 303             monitor = new ActivationMonitorImpl(port, ssf);
 304             initCommand(childArgs);
 305             registry = new SystemRegistryImpl(port, null, ssf, systemStub);
 306 
 307             if (ssf != null) {
 308                 synchronized (initLock) {
 309                     initDone = true;
 310                     initLock.notifyAll();
 311                 }
 312             }
 313         }
 314         startupLock = null;
 315 
 316         // restart services
 317         for (int i = gids.length; --i >= 0; ) {
 318             try {
 319                 getGroupEntry(gids[i]).restartServices();
 320             } catch (UnknownGroupException e) {
 321                 System.err.println(
 322                     getTextResource("rmid.restart.group.warning"));
 323                 e.printStackTrace();
 324             }
 325         }
 326     }
 327 
 328     private void writeObject(ObjectOutputStream ostream) throws IOException {
 329         // Synchronize on groupTable and idTable while writing them to the
 330         // object stream in order to avoid ConcurrentModificationException. It
 331         // might be preferable to synchronize them individually, but then we'd
 332         // have to write the fields individually instead of using
 333         // defaultWriteObject().
 334         synchronized (groupTable) {
 335             synchronized (idTable) {
 336                 ostream.defaultWriteObject();
 337             }
 338         }
 339     }
 340 
 341     private static class SystemRegistryImpl extends RegistryImpl {
 342 
 343         private static final String NAME = ActivationSystem.class.getName();
 344         private final ActivationSystem systemStub;
 345 
 346         SystemRegistryImpl(int port,
 347                            RMIClientSocketFactory csf,
 348                            RMIServerSocketFactory ssf,
 349                            ActivationSystem systemStub)
 350             throws RemoteException
 351         {
 352             super(port, csf, ssf);
 353             this.systemStub = systemStub;
 354         }
 355 
 356         /**
 357          * Returns the activation system stub if the specified name
 358          * matches the activation system's class name, otherwise
 359          * returns the result of invoking super.lookup with the specified
 360          * name.
 361          */
 362         public Remote lookup(String name)
 363             throws RemoteException, NotBoundException
 364         {
 365             if (name.equals(NAME)) {
 366                 return systemStub;
 367             } else {
 368                 return super.lookup(name);
 369             }
 370         }
 371 
 372         public String[] list() throws RemoteException {
 373             String[] list1 = super.list();
 374             int length = list1.length;
 375             String[] list2 = new String[length + 1];
 376             if (length > 0) {
 377                 System.arraycopy(list1, 0, list2, 0, length);
 378             }
 379             list2[length] = NAME;
 380             return list2;
 381         }
 382 
 383         public void bind(String name, Remote obj)
 384             throws RemoteException, AlreadyBoundException, AccessException
 385         {
 386             if (name.equals(NAME)) {
 387                 throw new AccessException(
 388                     "binding ActivationSystem is disallowed");
 389             } else {
 390                 super.bind(name, obj);
 391             }
 392         }
 393 
 394         public void unbind(String name)
 395             throws RemoteException, NotBoundException, AccessException
 396         {
 397             if (name.equals(NAME)) {
 398                 throw new AccessException(
 399                     "unbinding ActivationSystem is disallowed");
 400             } else {
 401                 super.unbind(name);
 402             }
 403         }
 404 
 405 
 406         public void rebind(String name, Remote obj)
 407             throws RemoteException, AccessException
 408         {
 409             if (name.equals(NAME)) {
 410                 throw new AccessException(
 411                     "binding ActivationSystem is disallowed");
 412             } else {
 413                 super.rebind(name, obj);
 414             }
 415         }
 416     }
 417 
 418 
 419     class ActivatorImpl extends RemoteServer implements Activator {
 420         // Because ActivatorImpl has a fixed ObjID, it can be
 421         // called by clients holding stale remote references.  Each of
 422         // its remote methods, then, must check startupLock (calling
 423         // checkShutdown() is easiest).
 424 
 425         private static final long serialVersionUID = -3654244726254566136L;
 426 
 427         /**
 428          * Construct a new Activator on a specified port.
 429          */
 430         ActivatorImpl(int port, RMIServerSocketFactory ssf)
 431             throws RemoteException
 432         {
 433             /* Server ref must be created and assigned before remote object
 434              * 'this' can be exported.
 435              */
 436             LiveRef lref =
 437                 new LiveRef(new ObjID(ObjID.ACTIVATOR_ID), port, null, ssf);
 438             UnicastServerRef uref = new UnicastServerRef(lref);
 439             ref = uref;
 440             uref.exportObject(this, null, false);
 441         }
 442 
 443         public MarshalledObject<? extends Remote> activate(ActivationID id,
 444                                                            boolean force)
 445             throws ActivationException, UnknownObjectException, RemoteException
 446         {
 447             checkShutdown();
 448             return getGroupEntry(id).activate(id, force);
 449         }
 450     }
 451 
 452     class ActivationMonitorImpl extends UnicastRemoteObject
 453         implements ActivationMonitor
 454     {
 455         private static final long serialVersionUID = -6214940464757948867L;
 456 
 457         ActivationMonitorImpl(int port, RMIServerSocketFactory ssf)
 458             throws RemoteException
 459         {
 460             super(port, null, ssf);
 461         }
 462 
 463         public void inactiveObject(ActivationID id)
 464             throws UnknownObjectException, RemoteException
 465         {
 466             try {
 467                 checkShutdown();
 468             } catch (ActivationException e) {
 469                 return;
 470             }
 471             RegistryImpl.checkAccess("Activator.inactiveObject");
 472             getGroupEntry(id).inactiveObject(id);
 473         }
 474 
 475         public void activeObject(ActivationID id,
 476                                  MarshalledObject<? extends Remote> mobj)
 477             throws UnknownObjectException, RemoteException
 478         {
 479             try {
 480                 checkShutdown();
 481             } catch (ActivationException e) {
 482                 return;
 483             }
 484             RegistryImpl.checkAccess("ActivationSystem.activeObject");
 485             getGroupEntry(id).activeObject(id, mobj);
 486         }
 487 
 488         public void inactiveGroup(ActivationGroupID id,
 489                                   long incarnation)
 490             throws UnknownGroupException, RemoteException
 491         {
 492             try {
 493                 checkShutdown();
 494             } catch (ActivationException e) {
 495                 return;
 496             }
 497             RegistryImpl.checkAccess("ActivationMonitor.inactiveGroup");
 498             getGroupEntry(id).inactiveGroup(incarnation, false);
 499         }
 500     }
 501 
 502 
 503     class ActivationSystemImpl
 504         extends RemoteServer
 505         implements ActivationSystem
 506     {
 507         private static final long serialVersionUID = 9100152600327688967L;
 508 
 509         // Because ActivationSystemImpl has a fixed ObjID, it can be
 510         // called by clients holding stale remote references.  Each of
 511         // its remote methods, then, must check startupLock (calling
 512         // checkShutdown() is easiest).
 513         ActivationSystemImpl(int port, RMIServerSocketFactory ssf)
 514             throws RemoteException
 515         {
 516             /* Server ref must be created and assigned before remote object
 517              * 'this' can be exported.
 518              */
 519             LiveRef lref = new LiveRef(new ObjID(4), port, null, ssf);
 520             UnicastServerRef uref = new UnicastServerRef(lref);
 521             ref = uref;
 522             uref.exportObject(this, null);
 523         }
 524 
 525         public ActivationID registerObject(ActivationDesc desc)
 526             throws ActivationException, UnknownGroupException, RemoteException
 527         {
 528             checkShutdown();
 529             RegistryImpl.checkAccess("ActivationSystem.registerObject");
 530 
 531             ActivationGroupID groupID = desc.getGroupID();
 532             ActivationID id = new ActivationID(activatorStub);
 533             getGroupEntry(groupID).registerObject(id, desc, true);
 534             return id;
 535         }
 536 
 537         public void unregisterObject(ActivationID id)
 538             throws ActivationException, UnknownObjectException, RemoteException
 539         {
 540             checkShutdown();
 541             RegistryImpl.checkAccess("ActivationSystem.unregisterObject");
 542             getGroupEntry(id).unregisterObject(id, true);
 543         }
 544 
 545         public ActivationGroupID registerGroup(ActivationGroupDesc desc)
 546             throws ActivationException, RemoteException
 547         {
 548             checkShutdown();
 549             RegistryImpl.checkAccess("ActivationSystem.registerGroup");
 550             checkArgs(desc, null);
 551 
 552             ActivationGroupID id = new ActivationGroupID(systemStub);
 553             GroupEntry entry = new GroupEntry(id, desc);
 554             // table insertion must take place before log update
 555             synchronized (groupTable) {
 556                 groupTable.put(id, entry);
 557             }
 558             addLogRecord(new LogRegisterGroup(id, desc));
 559             return id;
 560         }
 561 
 562         public ActivationMonitor activeGroup(ActivationGroupID id,
 563                                              ActivationInstantiator group,
 564                                              long incarnation)
 565             throws ActivationException, UnknownGroupException, RemoteException
 566         {
 567             checkShutdown();
 568             RegistryImpl.checkAccess("ActivationSystem.activeGroup");
 569 
 570             getGroupEntry(id).activeGroup(group, incarnation);
 571             return monitor;
 572         }
 573 
 574         public void unregisterGroup(ActivationGroupID id)
 575             throws ActivationException, UnknownGroupException, RemoteException
 576         {
 577             checkShutdown();
 578             RegistryImpl.checkAccess("ActivationSystem.unregisterGroup");
 579 
 580             // remove entry before unregister so state is updated before
 581             // logged
 582             GroupEntry entry;
 583             synchronized (groupTable) {
 584                 entry = getGroupEntry(id);
 585                 groupTable.remove(id);
 586             }
 587             entry.unregisterGroup(true);
 588         }
 589 
 590         public ActivationDesc setActivationDesc(ActivationID id,
 591                                                 ActivationDesc desc)
 592             throws ActivationException, UnknownObjectException, RemoteException
 593         {
 594             checkShutdown();
 595             RegistryImpl.checkAccess("ActivationSystem.setActivationDesc");
 596 
 597             if (!getGroupID(id).equals(desc.getGroupID())) {
 598                 throw new ActivationException(
 599                     "ActivationDesc contains wrong group");
 600             }
 601             return getGroupEntry(id).setActivationDesc(id, desc, true);
 602         }
 603 
 604         public ActivationGroupDesc setActivationGroupDesc(ActivationGroupID id,
 605                                                           ActivationGroupDesc desc)
 606             throws ActivationException, UnknownGroupException, RemoteException
 607         {
 608             checkShutdown();
 609             RegistryImpl.checkAccess(
 610                 "ActivationSystem.setActivationGroupDesc");
 611 
 612             checkArgs(desc, null);
 613             return getGroupEntry(id).setActivationGroupDesc(id, desc, true);
 614         }
 615 
 616         public ActivationDesc getActivationDesc(ActivationID id)
 617             throws ActivationException, UnknownObjectException, RemoteException
 618         {
 619             checkShutdown();
 620             RegistryImpl.checkAccess("ActivationSystem.getActivationDesc");
 621 
 622             return getGroupEntry(id).getActivationDesc(id);
 623         }
 624 
 625         public ActivationGroupDesc getActivationGroupDesc(ActivationGroupID id)
 626             throws ActivationException, UnknownGroupException, RemoteException
 627         {
 628             checkShutdown();
 629             RegistryImpl.checkAccess
 630                 ("ActivationSystem.getActivationGroupDesc");
 631 
 632             return getGroupEntry(id).desc;
 633         }
 634 
 635         /**
 636          * Shutdown the activation system. Destroys all groups spawned by
 637          * the activation daemon and exits the activation daemon.
 638          */
 639         public void shutdown() throws AccessException {
 640             RegistryImpl.checkAccess("ActivationSystem.shutdown");
 641 
 642             Object lock = startupLock;
 643             if (lock != null) {
 644                 synchronized (lock) {
 645                     // nothing
 646                 }
 647             }
 648 
 649             synchronized (Activation.this) {
 650                 if (!shuttingDown) {
 651                     shuttingDown = true;
 652                     (new Shutdown()).start();
 653                 }
 654             }
 655         }
 656     }
 657 
 658     private void checkShutdown() throws ActivationException {
 659         // if the startup critical section is running, wait until it
 660         // completes/fails before continuing with the remote call.
 661         Object lock = startupLock;
 662         if (lock != null) {
 663             synchronized (lock) {
 664                 // nothing
 665             }
 666         }
 667 
 668         if (shuttingDown == true) {
 669             throw new ActivationException(
 670                 "activation system shutting down");
 671         }
 672     }
 673 
 674     private static void unexport(Remote obj) {
 675         for (;;) {
 676             try {
 677                 if (UnicastRemoteObject.unexportObject(obj, false) == true) {
 678                     break;
 679                 } else {
 680                     Thread.sleep(100);
 681                 }
 682             } catch (Exception e) {
 683                 continue;
 684             }
 685         }
 686     }
 687 
 688     /**
 689      * Thread to shutdown rmid.
 690      */
 691     private class Shutdown extends Thread {
 692         Shutdown() {
 693             super("rmid Shutdown");
 694         }
 695 
 696         public void run() {
 697             try {
 698                 /*
 699                  * Unexport activation system services
 700                  */
 701                 unexport(activator);
 702                 unexport(system);
 703 
 704                 // destroy all child processes (groups)
 705                 GroupEntry[] groupEntries;
 706                 synchronized (groupTable) {
 707                     groupEntries = groupTable.values().
 708                         toArray(new GroupEntry[groupTable.size()]);
 709                 }
 710                 for (GroupEntry groupEntry : groupEntries) {
 711                     groupEntry.shutdown();
 712                 }
 713 
 714                 Runtime.getRuntime().removeShutdownHook(shutdownHook);
 715 
 716                 /*
 717                  * Unexport monitor safely since all processes are destroyed.
 718                  */
 719                 unexport(monitor);
 720 
 721                 /*
 722                  * Close log file, fix for 4243264: rmid shutdown thread
 723                  * interferes with remote calls in progress.  Make sure
 724                  * the log file is only closed when it is impossible for
 725                  * its closure to interfere with any pending remote calls.
 726                  * We close the log when all objects in the rmid VM are
 727                  * unexported.
 728                  */
 729                 try {
 730                     synchronized (log) {
 731                         log.close();
 732                     }
 733                 } catch (IOException e) {
 734                 }
 735 
 736             } finally {
 737                 /*
 738                  * Now exit... A System.exit should only be done if
 739                  * the RMI activation system daemon was started up
 740                  * by the main method below (in which should always
 741                  * be the case since the Activation contructor is private).
 742                  */
 743                 System.err.println(getTextResource("rmid.daemon.shutdown"));
 744                 System.exit(0);
 745             }
 746         }
 747     }
 748 
 749     /** Thread to destroy children in the event of abnormal termination. */
 750     private class ShutdownHook extends Thread {
 751         ShutdownHook() {
 752             super("rmid ShutdownHook");
 753         }
 754 
 755         public void run() {
 756             synchronized (Activation.this) {
 757                 shuttingDown = true;
 758             }
 759 
 760             // destroy all child processes (groups) quickly
 761             synchronized (groupTable) {
 762                 for (GroupEntry groupEntry : groupTable.values()) {
 763                     groupEntry.shutdownFast();
 764                 }
 765             }
 766         }
 767     }
 768 
 769     /**
 770      * Returns the groupID for a given id of an object in the group.
 771      * Throws UnknownObjectException if the object is not registered.
 772      */
 773     private ActivationGroupID getGroupID(ActivationID id)
 774         throws UnknownObjectException
 775     {
 776         synchronized (idTable) {
 777             ActivationGroupID groupID = idTable.get(id);
 778             if (groupID != null) {
 779                 return groupID;
 780             }
 781         }
 782         throw new UnknownObjectException("unknown object: " + id);
 783     }
 784 
 785     /**
 786      * Returns the group entry for the group id. Throws
 787      * UnknownGroupException if the group is not registered.
 788      */
 789     private GroupEntry getGroupEntry(ActivationGroupID id)
 790         throws UnknownGroupException
 791     {
 792         if (id.getClass() == ActivationGroupID.class) {
 793             synchronized (groupTable) {
 794                 GroupEntry entry = groupTable.get(id);
 795                 if (entry != null && !entry.removed) {
 796                     return entry;
 797                 }
 798             }
 799         }
 800         throw new UnknownGroupException("group unknown");
 801     }
 802 
 803     /**
 804      * Returns the group entry for the object's id. Throws
 805      * UnknownObjectException if the object is not registered or the
 806      * object's group is not registered.
 807      */
 808     private GroupEntry getGroupEntry(ActivationID id)
 809         throws UnknownObjectException
 810     {
 811         ActivationGroupID gid = getGroupID(id);
 812         synchronized (groupTable) {
 813             GroupEntry entry = groupTable.get(gid);
 814             if (entry != null) {
 815                 return entry;
 816             }
 817         }
 818         throw new UnknownObjectException("object's group removed");
 819     }
 820 
 821     /**
 822      * Container for group information: group's descriptor, group's
 823      * instantiator, flag to indicate pending group creation, and
 824      * table of the group's actived objects.
 825      *
 826      * WARNING: GroupEntry objects should not be written into log file
 827      * updates.  GroupEntrys are inner classes of Activation and they
 828      * can not be serialized independent of this class.  If the
 829      * complete Activation system is written out as a log update, the
 830      * point of having updates is nullified.
 831      */
 832     private class GroupEntry implements Serializable {
 833 
 834         /** indicate compatibility with JDK 1.2 version of class */
 835         private static final long serialVersionUID = 7222464070032993304L;
 836         private static final int MAX_TRIES = 2;
 837         private static final int NORMAL = 0;
 838         private static final int CREATING = 1;
 839         private static final int TERMINATE = 2;
 840         private static final int TERMINATING = 3;
 841 
 842         ActivationGroupDesc desc = null;
 843         ActivationGroupID groupID = null;
 844         long incarnation = 0;
 845         Map<ActivationID,ObjectEntry> objects =
 846             new HashMap<ActivationID,ObjectEntry>();
 847         Set<ActivationID> restartSet = new HashSet<ActivationID>();
 848 
 849         transient ActivationInstantiator group = null;
 850         transient int status = NORMAL;
 851         transient long waitTime = 0;
 852         transient String groupName = null;
 853         transient Process child = null;
 854         transient boolean removed = false;
 855         transient Watchdog watchdog = null;
 856 
 857         GroupEntry(ActivationGroupID groupID, ActivationGroupDesc desc) {
 858             this.groupID = groupID;
 859             this.desc = desc;
 860         }
 861 
 862         void restartServices() {
 863             Iterator<ActivationID> iter = null;
 864 
 865             synchronized (this) {
 866                 if (restartSet.isEmpty()) {
 867                     return;
 868                 }
 869 
 870                 /*
 871                  * Clone the restartSet so the set does not have to be locked
 872                  * during iteration. Locking the restartSet could cause
 873                  * deadlock if an object we are restarting caused another
 874                  * object in this group to be activated.
 875                  */
 876                 iter = (new HashSet<ActivationID>(restartSet)).iterator();
 877             }
 878 
 879             while (iter.hasNext()) {
 880                 ActivationID id = iter.next();
 881                 try {
 882                     activate(id, true);
 883                 } catch (Exception e) {
 884                     if (shuttingDown) {
 885                         return;
 886                     }
 887                     System.err.println(
 888                         getTextResource("rmid.restart.service.warning"));
 889                     e.printStackTrace();
 890                 }
 891             }
 892         }
 893 
 894         synchronized void activeGroup(ActivationInstantiator inst,
 895                                       long instIncarnation)
 896             throws ActivationException, UnknownGroupException
 897         {
 898             if (incarnation != instIncarnation) {
 899                 throw new ActivationException("invalid incarnation");
 900             }
 901 
 902             if (group != null) {
 903                 if (group.equals(inst)) {
 904                     return;
 905                 } else {
 906                     throw new ActivationException("group already active");
 907                 }
 908             }
 909 
 910             if (child != null && status != CREATING) {
 911                 throw new ActivationException("group not being created");
 912             }
 913 
 914             group = inst;
 915             status = NORMAL;
 916             notifyAll();
 917         }
 918 
 919         private void checkRemoved() throws UnknownGroupException {
 920             if (removed) {
 921                 throw new UnknownGroupException("group removed");
 922             }
 923         }
 924 
 925         private ObjectEntry getObjectEntry(ActivationID id)
 926             throws UnknownObjectException
 927         {
 928             if (removed) {
 929                 throw new UnknownObjectException("object's group removed");
 930             }
 931             ObjectEntry objEntry = objects.get(id);
 932             if (objEntry == null) {
 933                 throw new UnknownObjectException("object unknown");
 934             }
 935             return objEntry;
 936         }
 937 
 938         synchronized void registerObject(ActivationID id,
 939                                          ActivationDesc desc,
 940                                          boolean addRecord)
 941             throws UnknownGroupException, ActivationException
 942         {
 943             checkRemoved();
 944             objects.put(id, new ObjectEntry(desc));
 945             if (desc.getRestartMode() == true) {
 946                 restartSet.add(id);
 947             }
 948 
 949             // table insertion must take place before log update
 950             synchronized (idTable) {
 951                 idTable.put(id, groupID);
 952             }
 953 
 954             if (addRecord) {
 955                 addLogRecord(new LogRegisterObject(id, desc));
 956             }
 957         }
 958 
 959         synchronized void unregisterObject(ActivationID id, boolean addRecord)
 960             throws UnknownGroupException, ActivationException
 961         {
 962             ObjectEntry objEntry = getObjectEntry(id);
 963             objEntry.removed = true;
 964             objects.remove(id);
 965             if (objEntry.desc.getRestartMode() == true) {
 966                 restartSet.remove(id);
 967             }
 968 
 969             // table insertion must take place before log update
 970             synchronized (idTable) {
 971                 idTable.remove(id);
 972             }
 973             if (addRecord) {
 974                 addLogRecord(new LogUnregisterObject(id));
 975             }
 976         }
 977 
 978         synchronized void unregisterGroup(boolean addRecord)
 979            throws UnknownGroupException, ActivationException
 980         {
 981             checkRemoved();
 982             removed = true;
 983             for (Map.Entry<ActivationID,ObjectEntry> entry :
 984                      objects.entrySet())
 985             {
 986                 ActivationID id = entry.getKey();
 987                 synchronized (idTable) {
 988                     idTable.remove(id);
 989                 }
 990                 ObjectEntry objEntry = entry.getValue();
 991                 objEntry.removed = true;
 992             }
 993             objects.clear();
 994             restartSet.clear();
 995             reset();
 996             childGone();
 997 
 998             // removal should be recorded before log update
 999             if (addRecord) {
1000                 addLogRecord(new LogUnregisterGroup(groupID));
1001             }
1002         }
1003 
1004         synchronized ActivationDesc setActivationDesc(ActivationID id,
1005                                                       ActivationDesc desc,
1006                                                       boolean addRecord)
1007             throws UnknownObjectException, UnknownGroupException,
1008                    ActivationException
1009         {
1010             ObjectEntry objEntry = getObjectEntry(id);
1011             ActivationDesc oldDesc = objEntry.desc;
1012             objEntry.desc = desc;
1013             if (desc.getRestartMode() == true) {
1014                 restartSet.add(id);
1015             } else {
1016                 restartSet.remove(id);
1017             }
1018             // restart information should be recorded before log update
1019             if (addRecord) {
1020                 addLogRecord(new LogUpdateDesc(id, desc));
1021             }
1022 
1023             return oldDesc;
1024         }
1025 
1026         synchronized ActivationDesc getActivationDesc(ActivationID id)
1027             throws UnknownObjectException, UnknownGroupException
1028         {
1029             return getObjectEntry(id).desc;
1030         }
1031 
1032         synchronized ActivationGroupDesc setActivationGroupDesc(
1033                 ActivationGroupID id,
1034                 ActivationGroupDesc desc,
1035                 boolean addRecord)
1036             throws UnknownGroupException, ActivationException
1037         {
1038             checkRemoved();
1039             ActivationGroupDesc oldDesc = this.desc;
1040             this.desc = desc;
1041             // state update should occur before log update
1042             if (addRecord) {
1043                 addLogRecord(new LogUpdateGroupDesc(id, desc));
1044             }
1045             return oldDesc;
1046         }
1047 
1048         synchronized void inactiveGroup(long incarnation, boolean failure)
1049             throws UnknownGroupException
1050         {
1051             checkRemoved();
1052             if (this.incarnation != incarnation) {
1053                 throw new UnknownGroupException("invalid incarnation");
1054             }
1055 
1056             reset();
1057             if (failure) {
1058                 terminate();
1059             } else if (child != null && status == NORMAL) {
1060                 status = TERMINATE;
1061                 watchdog.noRestart();
1062             }
1063         }
1064 
1065         synchronized void activeObject(ActivationID id,
1066                                        MarshalledObject<? extends Remote> mobj)
1067                 throws UnknownObjectException
1068         {
1069             getObjectEntry(id).stub = mobj;
1070         }
1071 
1072         synchronized void inactiveObject(ActivationID id)
1073             throws UnknownObjectException
1074         {
1075             getObjectEntry(id).reset();
1076         }
1077 
1078         private synchronized void reset() {
1079             group = null;
1080             for (ObjectEntry objectEntry : objects.values()) {
1081                 objectEntry.reset();
1082             }
1083         }
1084 
1085         private void childGone() {
1086             if (child != null) {
1087                 child = null;
1088                 watchdog.dispose();
1089                 watchdog = null;
1090                 status = NORMAL;
1091                 notifyAll();
1092             }
1093         }
1094 
1095         private void terminate() {
1096             if (child != null && status != TERMINATING) {
1097                 child.destroy();
1098                 status = TERMINATING;
1099                 waitTime = System.currentTimeMillis() + groupTimeout;
1100                 notifyAll();
1101             }
1102         }
1103 
1104         private void await() {
1105             while (true) {
1106                 switch (status) {
1107                 case NORMAL:
1108                     return;
1109                 case TERMINATE:
1110                     terminate();
1111                 case TERMINATING:
1112                     try {
1113                         child.exitValue();
1114                     } catch (IllegalThreadStateException e) {
1115                         long now = System.currentTimeMillis();
1116                         if (waitTime > now) {
1117                             try {
1118                                 wait(waitTime - now);
1119                             } catch (InterruptedException ee) {
1120                             }
1121                             continue;
1122                         }
1123                         // REMIND: print message that group did not terminate?
1124                     }
1125                     childGone();
1126                     return;
1127                 case CREATING:
1128                     try {
1129                         wait();
1130                     } catch (InterruptedException e) {
1131                     }
1132                 }
1133             }
1134         }
1135 
1136         // no synchronization to avoid delay wrt getInstantiator
1137         void shutdownFast() {
1138             Process p = child;
1139             if (p != null) {
1140                 p.destroy();
1141             }
1142         }
1143 
1144         synchronized void shutdown() {
1145             reset();
1146             terminate();
1147             await();
1148         }
1149 
1150         MarshalledObject<? extends Remote> activate(ActivationID id,
1151                                                     boolean force)
1152             throws ActivationException
1153         {
1154             Exception detail = null;
1155 
1156             /*
1157              * Attempt to activate object and reattempt (several times)
1158              * if activation fails due to communication problems.
1159              */
1160             for (int tries = MAX_TRIES; tries > 0; tries--) {
1161                 ActivationInstantiator inst;
1162                 long currentIncarnation;
1163 
1164                 // look up object to activate
1165                 ObjectEntry objEntry;
1166                 synchronized (this) {
1167                     objEntry = getObjectEntry(id);
1168                     // if not forcing activation, return cached stub
1169                     if (!force && objEntry.stub != null) {
1170                         return objEntry.stub;
1171                     }
1172                     inst = getInstantiator(groupID);
1173                     currentIncarnation = incarnation;
1174                 }
1175 
1176                 boolean groupInactive = false;
1177                 boolean failure = false;
1178                 // activate object
1179                 try {
1180                     return objEntry.activate(id, force, inst);
1181                 } catch (NoSuchObjectException e) {
1182                     groupInactive = true;
1183                     detail = e;
1184                 } catch (ConnectException e) {
1185                     groupInactive = true;
1186                     failure = true;
1187                     detail = e;
1188                 } catch (ConnectIOException e) {
1189                     groupInactive = true;
1190                     failure = true;
1191                     detail = e;
1192                 } catch (InactiveGroupException e) {
1193                     groupInactive = true;
1194                     detail = e;
1195                 } catch (RemoteException e) {
1196                     // REMIND: wait some here before continuing?
1197                     if (detail == null) {
1198                         detail = e;
1199                     }
1200                 }
1201 
1202                 if (groupInactive) {
1203                     // group has failed or is inactive; mark inactive
1204                     try {
1205                         System.err.println(
1206                             MessageFormat.format(
1207                                 getTextResource("rmid.group.inactive"),
1208                                 detail.toString()));
1209                         detail.printStackTrace();
1210                         getGroupEntry(groupID).
1211                             inactiveGroup(currentIncarnation, failure);
1212                     } catch (UnknownGroupException e) {
1213                         // not a problem
1214                     }
1215                 }
1216             }
1217 
1218             /**
1219              * signal that group activation failed, nested exception
1220              * specifies what exception occurred when the group did not
1221              * activate
1222              */
1223             throw new ActivationException("object activation failed after " +
1224                                           MAX_TRIES + " tries", detail);
1225         }
1226 
1227         /**
1228          * Returns the instantiator for the group specified by id and
1229          * entry. If the group is currently inactive, exec some
1230          * bootstrap code to create the group.
1231          */
1232         private ActivationInstantiator getInstantiator(ActivationGroupID id)
1233             throws ActivationException
1234         {
1235             assert Thread.holdsLock(this);
1236 
1237             await();
1238             if (group != null) {
1239                 return group;
1240             }
1241             checkRemoved();
1242             boolean acquired = false;
1243 
1244             try {
1245                 groupName = Pstartgroup();
1246                 acquired = true;
1247                 String[] argv = activationArgs(desc);
1248                 checkArgs(desc, argv);
1249 
1250                 if (debugExec) {
1251                     StringBuffer sb = new StringBuffer(argv[0]);
1252                     int j;
1253                     for (j = 1; j < argv.length; j++) {
1254                         sb.append(' ');
1255                         sb.append(argv[j]);
1256                     }
1257                     System.err.println(
1258                         MessageFormat.format(
1259                             getTextResource("rmid.exec.command"),
1260                             sb.toString()));
1261                 }
1262 
1263                 try {
1264                     child = Runtime.getRuntime().exec(argv);
1265                     status = CREATING;
1266                     ++incarnation;
1267                     watchdog = new Watchdog();
1268                     watchdog.start();
1269                     addLogRecord(new LogGroupIncarnation(id, incarnation));
1270 
1271                     // handle child I/O streams before writing to child
1272                     PipeWriter.plugTogetherPair
1273                         (child.getInputStream(), System.out,
1274                          child.getErrorStream(), System.err);
1275 
1276                     MarshalOutputStream out =
1277                         new MarshalOutputStream(child.getOutputStream());
1278                     out.writeObject(id);
1279                     out.writeObject(desc);
1280                     out.writeLong(incarnation);
1281                     out.flush();
1282                     out.close();
1283 
1284 
1285                 } catch (IOException e) {
1286                     terminate();
1287                     throw new ActivationException(
1288                         "unable to create activation group", e);
1289                 }
1290 
1291                 try {
1292                     long now = System.currentTimeMillis();
1293                     long stop = now + execTimeout;
1294                     do {
1295                         wait(stop - now);
1296                         if (group != null) {
1297                             return group;
1298                         }
1299                         now = System.currentTimeMillis();
1300                     } while (status == CREATING && now < stop);
1301                 } catch (InterruptedException e) {
1302                 }
1303 
1304                 terminate();
1305                 throw new ActivationException(
1306                         (removed ?
1307                          "activation group unregistered" :
1308                          "timeout creating child process"));
1309             } finally {
1310                 if (acquired) {
1311                     Vstartgroup();
1312                 }
1313             }
1314         }
1315 
1316         /**
1317          * Waits for process termination and then restarts services.
1318          */
1319         private class Watchdog extends Thread {
1320             private final Process groupProcess = child;
1321             private final long groupIncarnation = incarnation;
1322             private boolean canInterrupt = true;
1323             private boolean shouldQuit = false;
1324             private boolean shouldRestart = true;
1325 
1326             Watchdog() {
1327                 super("WatchDog-"  + groupName + "-" + incarnation);
1328                 setDaemon(true);
1329             }
1330 
1331             public void run() {
1332 
1333                 if (shouldQuit) {
1334                     return;
1335                 }
1336 
1337                 /*
1338                  * Wait for the group to crash or exit.
1339                  */
1340                 try {
1341                     groupProcess.waitFor();
1342                 } catch (InterruptedException exit) {
1343                     return;
1344                 }
1345 
1346                 boolean restart = false;
1347                 synchronized (GroupEntry.this) {
1348                     if (shouldQuit) {
1349                         return;
1350                     }
1351                     canInterrupt = false;
1352                     interrupted(); // clear interrupt bit
1353                     /*
1354                      * Since the group crashed, we should
1355                      * reset the entry before activating objects
1356                      */
1357                     if (groupIncarnation == incarnation) {
1358                         restart = shouldRestart && !shuttingDown;
1359                         reset();
1360                         childGone();
1361                     }
1362                 }
1363 
1364                 /*
1365                  * Activate those objects that require restarting
1366                  * after a crash.
1367                  */
1368                 if (restart) {
1369                     restartServices();
1370                 }
1371             }
1372 
1373             /**
1374              * Marks this thread as one that is no longer needed.
1375              * If the thread is in a state in which it can be interrupted,
1376              * then the thread is interrupted.
1377              */
1378             void dispose() {
1379                 shouldQuit = true;
1380                 if (canInterrupt) {
1381                     interrupt();
1382                 }
1383             }
1384 
1385             /**
1386              * Marks this thread as no longer needing to restart objects.
1387              */
1388             void noRestart() {
1389                 shouldRestart = false;
1390             }
1391         }
1392     }
1393 
1394     private String[] activationArgs(ActivationGroupDesc desc) {
1395         ActivationGroupDesc.CommandEnvironment cmdenv;
1396         cmdenv = desc.getCommandEnvironment();
1397 
1398         // argv is the literal command to exec
1399         List<String> argv = new ArrayList<String>();
1400 
1401         // Command name/path
1402         argv.add((cmdenv != null && cmdenv.getCommandPath() != null)
1403                     ? cmdenv.getCommandPath()
1404                     : command[0]);
1405 
1406         // Group-specific command options
1407         if (cmdenv != null && cmdenv.getCommandOptions() != null) {
1408             argv.addAll(Arrays.asList(cmdenv.getCommandOptions()));
1409         }
1410 
1411         // Properties become -D parameters
1412         Properties props = desc.getPropertyOverrides();
1413         if (props != null) {
1414             for (Enumeration<?> p = props.propertyNames();
1415                  p.hasMoreElements();)
1416             {
1417                 String name = (String) p.nextElement();
1418                 /* Note on quoting: it would be wrong
1419                  * here, since argv will be passed to
1420                  * Runtime.exec, which should not parse
1421                  * arguments or split on whitespace.
1422                  */
1423                 argv.add("-D" + name + "=" + props.getProperty(name));
1424             }
1425         }
1426 
1427         /* Finally, rmid-global command options (e.g. -C options)
1428          * and the classname
1429          */
1430         for (int i = 1; i < command.length; i++) {
1431             argv.add(command[i]);
1432         }
1433 
1434         String[] realArgv = new String[argv.size()];
1435         System.arraycopy(argv.toArray(), 0, realArgv, 0, realArgv.length);
1436 
1437         return realArgv;
1438     }
1439 
1440     private void checkArgs(ActivationGroupDesc desc, String[] cmd)
1441         throws SecurityException, ActivationException
1442     {
1443         /*
1444          * Check exec command using execPolicy object
1445          */
1446         if (execPolicyMethod != null) {
1447             if (cmd == null) {
1448                 cmd = activationArgs(desc);
1449             }
1450             try {
1451                 execPolicyMethod.invoke(execPolicy, desc, cmd);
1452             } catch (InvocationTargetException e) {
1453                 Throwable targetException = e.getTargetException();
1454                 if (targetException instanceof SecurityException) {
1455                     throw (SecurityException) targetException;
1456                 } else {
1457                     throw new ActivationException(
1458                         execPolicyMethod.getName() + ": unexpected exception",
1459                         e);
1460                 }
1461             } catch (Exception e) {
1462                 throw new ActivationException(
1463                     execPolicyMethod.getName() + ": unexpected exception", e);
1464             }
1465         }
1466     }
1467 
1468     private static class ObjectEntry implements Serializable {
1469 
1470         private static final long serialVersionUID = -5500114225321357856L;
1471 
1472         /** descriptor for object */
1473         ActivationDesc desc;
1474         /** the stub (if active) */
1475         volatile transient MarshalledObject<? extends Remote> stub = null;
1476         volatile transient boolean removed = false;
1477 
1478         ObjectEntry(ActivationDesc desc) {
1479             this.desc = desc;
1480         }
1481 
1482         synchronized MarshalledObject<? extends Remote>
1483             activate(ActivationID id,
1484                      boolean force,
1485                      ActivationInstantiator inst)
1486             throws RemoteException, ActivationException
1487         {
1488             MarshalledObject<? extends Remote> nstub = stub;
1489             if (removed) {
1490                 throw new UnknownObjectException("object removed");
1491             } else if (!force && nstub != null) {
1492                 return nstub;
1493             }
1494 
1495             nstub = inst.newInstance(id, desc);
1496             stub = nstub;
1497             /*
1498              * stub could be set to null by a group reset, so return
1499              * the newstub here to prevent returning null.
1500              */
1501             return nstub;
1502         }
1503 
1504         void reset() {
1505             stub = null;
1506         }
1507     }
1508 
1509     /**
1510      * Add a record to the activation log. If the number of updates
1511      * passes a predetermined threshold, record a snapshot.
1512      */
1513     private void addLogRecord(LogRecord rec) throws ActivationException {
1514         synchronized (log) {
1515             checkShutdown();
1516             try {
1517                 log.update(rec, true);
1518             } catch (Exception e) {
1519                 numUpdates = snapshotInterval;
1520                 System.err.println(getTextResource("rmid.log.update.warning"));
1521                 e.printStackTrace();
1522             }
1523             if (++numUpdates < snapshotInterval) {
1524                 return;
1525             }
1526             try {
1527                 log.snapshot(this);
1528                 numUpdates = 0;
1529             } catch (Exception e) {
1530                 System.err.println(
1531                     getTextResource("rmid.log.snapshot.warning"));
1532                 e.printStackTrace();
1533                 try {
1534                     // shutdown activation system because snapshot failed
1535                     system.shutdown();
1536                 } catch (RemoteException ignore) {
1537                     // can't happen
1538                 }
1539                 // warn the client of the original update problem
1540                 throw new ActivationException("log snapshot failed", e);
1541             }
1542         }
1543     }
1544 
1545     /**
1546      * Handler for the log that knows how to take the initial snapshot
1547      * and apply an update (a LogRecord) to the current state.
1548      */
1549     private static class ActLogHandler extends LogHandler {
1550 
1551         ActLogHandler() {
1552         }
1553 
1554         public Object initialSnapshot()
1555         {
1556             /**
1557              * Return an empty Activation object.  Log will update
1558              * this object with recovered state.
1559              */
1560             return new Activation();
1561         }
1562 
1563         public Object applyUpdate(Object update, Object state)
1564             throws Exception
1565         {
1566             return ((LogRecord) update).apply(state);
1567         }
1568 
1569     }
1570 
1571     /**
1572      * Abstract class for all log records. The subclass contains
1573      * specific update information and implements the apply method
1574      * that applys the update information contained in the record
1575      * to the current state.
1576      */
1577     private static abstract class LogRecord implements Serializable {
1578         /** indicate compatibility with JDK 1.2 version of class */
1579         private static final long serialVersionUID = 8395140512322687529L;
1580         abstract Object apply(Object state) throws Exception;
1581     }
1582 
1583     /**
1584      * Log record for registering an object.
1585      */
1586     private static class LogRegisterObject extends LogRecord {
1587         /** indicate compatibility with JDK 1.2 version of class */
1588         private static final long serialVersionUID = -6280336276146085143L;
1589         private ActivationID id;
1590         private ActivationDesc desc;
1591 
1592         LogRegisterObject(ActivationID id, ActivationDesc desc) {
1593             this.id = id;
1594             this.desc = desc;
1595         }
1596 
1597         Object apply(Object state) {
1598             try {
1599                 ((Activation) state).getGroupEntry(desc.getGroupID()).
1600                     registerObject(id, desc, false);
1601             } catch (Exception ignore) {
1602                 System.err.println(
1603                     MessageFormat.format(
1604                         getTextResource("rmid.log.recover.warning"),
1605                         "LogRegisterObject"));
1606                 ignore.printStackTrace();
1607             }
1608             return state;
1609         }
1610     }
1611 
1612     /**
1613      * Log record for unregistering an object.
1614      */
1615     private static class LogUnregisterObject extends LogRecord {
1616         /** indicate compatibility with JDK 1.2 version of class */
1617         private static final long serialVersionUID = 6269824097396935501L;
1618         private ActivationID id;
1619 
1620         LogUnregisterObject(ActivationID id) {
1621             this.id = id;
1622         }
1623 
1624         Object apply(Object state) {
1625             try {
1626                 ((Activation) state).getGroupEntry(id).
1627                     unregisterObject(id, false);
1628             } catch (Exception ignore) {
1629                 System.err.println(
1630                     MessageFormat.format(
1631                         getTextResource("rmid.log.recover.warning"),
1632                         "LogUnregisterObject"));
1633                 ignore.printStackTrace();
1634             }
1635             return state;
1636         }
1637     }
1638 
1639     /**
1640      * Log record for registering a group.
1641      */
1642     private static class LogRegisterGroup extends LogRecord {
1643         /** indicate compatibility with JDK 1.2 version of class */
1644         private static final long serialVersionUID = -1966827458515403625L;
1645         private ActivationGroupID id;
1646         private ActivationGroupDesc desc;
1647 
1648         LogRegisterGroup(ActivationGroupID id, ActivationGroupDesc desc) {
1649             this.id = id;
1650             this.desc = desc;
1651         }
1652 
1653         Object apply(Object state) {
1654             // modify state directly; cant ask a nonexistent GroupEntry
1655             // to register itself.
1656             ((Activation) state).groupTable.put(id, ((Activation) state).new
1657                                                 GroupEntry(id, desc));
1658             return state;
1659         }
1660     }
1661 
1662     /**
1663      * Log record for udpating an activation desc
1664      */
1665     private static class LogUpdateDesc extends LogRecord {
1666         /** indicate compatibility with JDK 1.2 version of class */
1667         private static final long serialVersionUID = 545511539051179885L;
1668 
1669         private ActivationID id;
1670         private ActivationDesc desc;
1671 
1672         LogUpdateDesc(ActivationID id, ActivationDesc desc) {
1673             this.id = id;
1674             this.desc = desc;
1675         }
1676 
1677         Object apply(Object state) {
1678             try {
1679                 ((Activation) state).getGroupEntry(id).
1680                     setActivationDesc(id, desc, false);
1681             } catch (Exception ignore) {
1682                 System.err.println(
1683                     MessageFormat.format(
1684                         getTextResource("rmid.log.recover.warning"),
1685                         "LogUpdateDesc"));
1686                 ignore.printStackTrace();
1687             }
1688             return state;
1689         }
1690     }
1691 
1692     /**
1693      * Log record for unregistering a group.
1694      */
1695     private static class LogUpdateGroupDesc extends LogRecord {
1696         /** indicate compatibility with JDK 1.2 version of class */
1697         private static final long serialVersionUID = -1271300989218424337L;
1698         private ActivationGroupID id;
1699         private ActivationGroupDesc desc;
1700 
1701         LogUpdateGroupDesc(ActivationGroupID id, ActivationGroupDesc desc) {
1702             this.id = id;
1703             this.desc = desc;
1704         }
1705 
1706         Object apply(Object state) {
1707             try {
1708                 ((Activation) state).getGroupEntry(id).
1709                     setActivationGroupDesc(id, desc, false);
1710             } catch (Exception ignore) {
1711                 System.err.println(
1712                     MessageFormat.format(
1713                         getTextResource("rmid.log.recover.warning"),
1714                         "LogUpdateGroupDesc"));
1715                 ignore.printStackTrace();
1716             }
1717             return state;
1718         }
1719     }
1720 
1721     /**
1722      * Log record for unregistering a group.
1723      */
1724     private static class LogUnregisterGroup extends LogRecord {
1725         /** indicate compatibility with JDK 1.2 version of class */
1726         private static final long serialVersionUID = -3356306586522147344L;
1727         private ActivationGroupID id;
1728 
1729         LogUnregisterGroup(ActivationGroupID id) {
1730             this.id = id;
1731         }
1732 
1733         Object apply(Object state) {
1734             GroupEntry entry = ((Activation) state).groupTable.remove(id);
1735             try {
1736                 entry.unregisterGroup(false);
1737             } catch (Exception ignore) {
1738                 System.err.println(
1739                     MessageFormat.format(
1740                         getTextResource("rmid.log.recover.warning"),
1741                         "LogUnregisterGroup"));
1742                 ignore.printStackTrace();
1743             }
1744             return state;
1745         }
1746     }
1747 
1748     /**
1749      * Log record for an active group incarnation
1750      */
1751     private static class LogGroupIncarnation extends LogRecord {
1752         /** indicate compatibility with JDK 1.2 version of class */
1753         private static final long serialVersionUID = 4146872747377631897L;
1754         private ActivationGroupID id;
1755         private long inc;
1756 
1757         LogGroupIncarnation(ActivationGroupID id, long inc) {
1758             this.id = id;
1759             this.inc = inc;
1760         }
1761 
1762         Object apply(Object state) {
1763             try {
1764                 GroupEntry entry = ((Activation) state).getGroupEntry(id);
1765                 entry.incarnation = inc;
1766             } catch (Exception ignore) {
1767                 System.err.println(
1768                     MessageFormat.format(
1769                         getTextResource("rmid.log.recover.warning"),
1770                         "LogGroupIncarnation"));
1771                 ignore.printStackTrace();
1772             }
1773             return state;
1774         }
1775     }
1776 
1777     /**
1778      * Initialize command to exec a default group.
1779      */
1780     private void initCommand(String[] childArgs) {
1781         command = new String[childArgs.length + 2];
1782         AccessController.doPrivileged(new PrivilegedAction<Void>() {
1783             public Void run() {
1784                 try {
1785                     command[0] = System.getProperty("java.home") +
1786                         File.separator + "bin" + File.separator + "java";
1787                 } catch (Exception e) {
1788                     System.err.println(
1789                         getTextResource("rmid.unfound.java.home.property"));
1790                     command[0] = "java";
1791                 }
1792                 return null;
1793             }
1794         });
1795         System.arraycopy(childArgs, 0, command, 1, childArgs.length);
1796         command[command.length-1] = "sun.rmi.server.ActivationGroupInit";
1797     }
1798 
1799     private static void bomb(String error) {
1800         System.err.println("rmid: " + error); // $NON-NLS$
1801         System.err.println(MessageFormat.format(getTextResource("rmid.usage"),
1802                     "rmid"));
1803         System.exit(1);
1804     }
1805 
1806     /**
1807      * The default policy for checking a command before it is executed
1808      * makes sure the appropriate com.sun.rmi.rmid.ExecPermission and
1809      * set of com.sun.rmi.rmid.ExecOptionPermissions have been granted.
1810      */
1811     public static class DefaultExecPolicy {
1812 
1813         public void checkExecCommand(ActivationGroupDesc desc, String[] cmd)
1814             throws SecurityException
1815         {
1816             PermissionCollection perms = getExecPermissions();
1817 
1818             /*
1819              * Check properties overrides.
1820              */
1821             Properties props = desc.getPropertyOverrides();
1822             if (props != null) {
1823                 Enumeration<?> p = props.propertyNames();
1824                 while (p.hasMoreElements()) {
1825                     String name = (String) p.nextElement();
1826                     String value = props.getProperty(name);
1827                     String option = "-D" + name + "=" + value;
1828                     try {
1829                         checkPermission(perms,
1830                             new ExecOptionPermission(option));
1831                     } catch (AccessControlException e) {
1832                         if (value.equals("")) {
1833                             checkPermission(perms,
1834                                 new ExecOptionPermission("-D" + name));
1835                         } else {
1836                             throw e;
1837                         }
1838                     }
1839                 }
1840             }
1841 
1842             /*
1843              * Check group class name (allow nothing but the default),
1844              * code location (must be null), and data (must be null).
1845              */
1846             String groupClassName = desc.getClassName();
1847             if ((groupClassName != null &&
1848                  !groupClassName.equals(
1849                     ActivationGroupImpl.class.getName())) ||
1850                 (desc.getLocation() != null) ||
1851                 (desc.getData() != null))
1852             {
1853                 throw new AccessControlException(
1854                     "access denied (custom group implementation not allowed)");
1855             }
1856 
1857             /*
1858              * If group descriptor has a command environment, check
1859              * command and options.
1860              */
1861             ActivationGroupDesc.CommandEnvironment cmdenv;
1862             cmdenv = desc.getCommandEnvironment();
1863             if (cmdenv != null) {
1864                 String path = cmdenv.getCommandPath();
1865                 if (path != null) {
1866                     checkPermission(perms, new ExecPermission(path));
1867                 }
1868 
1869                 String[] options = cmdenv.getCommandOptions();
1870                 if (options != null) {
1871                     for (String option : options) {
1872                         checkPermission(perms,
1873                                         new ExecOptionPermission(option));
1874                     }
1875                 }
1876             }
1877         }
1878 
1879         /**
1880          * Prints warning message if installed Policy is the default Policy
1881          * implementation and globally granted permissions do not include
1882          * AllPermission or any ExecPermissions/ExecOptionPermissions.
1883          */
1884         static void checkConfiguration() {
1885             Policy policy =
1886                 AccessController.doPrivileged(new PrivilegedAction<Policy>() {
1887                     public Policy run() {
1888                         return Policy.getPolicy();
1889                     }
1890                 });
1891             if (!(policy instanceof PolicyFile)) {
1892                 return;
1893             }
1894             PermissionCollection perms = getExecPermissions();
1895             for (Enumeration<Permission> e = perms.elements();
1896                  e.hasMoreElements();)
1897             {
1898                 Permission p = e.nextElement();
1899                 if (p instanceof AllPermission ||
1900                     p instanceof ExecPermission ||
1901                     p instanceof ExecOptionPermission)
1902                 {
1903                     return;
1904                 }
1905             }
1906             System.err.println(getTextResource("rmid.exec.perms.inadequate"));
1907         }
1908 
1909         private static PermissionCollection getExecPermissions() {
1910             /*
1911              * The approach used here is taken from the similar method
1912              * getLoaderAccessControlContext() in the class
1913              * sun.rmi.server.LoaderHandler.
1914              */
1915 
1916             // obtain permissions granted to all code in current policy
1917             PermissionCollection perms = AccessController.doPrivileged(
1918                 new PrivilegedAction<PermissionCollection>() {
1919                     public PermissionCollection run() {
1920                         CodeSource codesource =
1921                             new CodeSource(null, (Certificate[]) null);
1922                         Policy p = Policy.getPolicy();
1923                         if (p != null) {
1924                             return p.getPermissions(codesource);
1925                         } else {
1926                             return new Permissions();
1927                         }
1928                     }
1929                 });
1930 
1931             return perms;
1932         }
1933 
1934         private static void checkPermission(PermissionCollection perms,
1935                                             Permission p)
1936             throws AccessControlException
1937         {
1938             if (!perms.implies(p)) {
1939                 throw new AccessControlException(
1940                    "access denied " + p.toString());
1941             }
1942         }
1943     }
1944 
1945     /**
1946      * Main program to start the activation system. <br>
1947      * The usage is as follows: rmid [-port num] [-log dir].
1948      */
1949     public static void main(String[] args) {
1950         boolean stop = false;
1951 
1952         // Create and install the security manager if one is not installed
1953         // already.
1954         if (System.getSecurityManager() == null) {
1955             System.setSecurityManager(new SecurityManager());
1956         }
1957 
1958         try {
1959             int port = ActivationSystem.SYSTEM_PORT;
1960             RMIServerSocketFactory ssf = null;
1961 
1962             /*
1963              * If rmid has an inherited channel (meaning that it was
1964              * launched from inetd), set the server socket factory to
1965              * return the inherited server socket.
1966              **/
1967             Channel inheritedChannel = AccessController.doPrivileged(
1968                 new PrivilegedExceptionAction<Channel>() {
1969                     public Channel run() throws IOException {
1970                         return System.inheritedChannel();
1971                     }
1972                 });
1973 
1974             if (inheritedChannel != null &&
1975                 inheritedChannel instanceof ServerSocketChannel)
1976             {
1977                 /*
1978                  * Redirect System.err output to a file.
1979                  */
1980                 AccessController.doPrivileged(
1981                     new PrivilegedExceptionAction<Void>() {
1982                         public Void run() throws IOException {
1983                             File file =
1984                                 File.createTempFile("rmid-err", null, null);
1985                             PrintStream errStream =
1986                                 new PrintStream(new FileOutputStream(file));
1987                             System.setErr(errStream);
1988                             return null;
1989                         }
1990                     });
1991 
1992                 ServerSocket serverSocket =
1993                     ((ServerSocketChannel) inheritedChannel).socket();
1994                 port = serverSocket.getLocalPort();
1995                 ssf = new ActivationServerSocketFactory(serverSocket);
1996 
1997                 System.err.println(new Date());
1998                 System.err.println(getTextResource(
1999                                        "rmid.inherited.channel.info") +
2000                                        ": " + inheritedChannel);
2001             }
2002 
2003             String log = null;
2004             List<String> childArgs = new ArrayList<String>();
2005 
2006             /*
2007              * Parse arguments
2008              */
2009             for (int i = 0; i < args.length; i++) {
2010                 if (args[i].equals("-port")) {
2011                     if (ssf != null) {
2012                         bomb(getTextResource("rmid.syntax.port.badarg"));
2013                     }
2014                     if ((i + 1) < args.length) {
2015                         try {
2016                             port = Integer.parseInt(args[++i]);
2017                         } catch (NumberFormatException nfe) {
2018                             bomb(getTextResource("rmid.syntax.port.badnumber"));
2019                         }
2020                     } else {
2021                         bomb(getTextResource("rmid.syntax.port.missing"));
2022                     }
2023 
2024                 } else if (args[i].equals("-log")) {
2025                     if ((i + 1) < args.length) {
2026                         log = args[++i];
2027                     } else {
2028                         bomb(getTextResource("rmid.syntax.log.missing"));
2029                     }
2030 
2031                 } else if (args[i].equals("-stop")) {
2032                     stop = true;
2033 
2034                 } else if (args[i].startsWith("-C")) {
2035                     childArgs.add(args[i].substring(2));
2036 
2037                 } else {
2038                     bomb(MessageFormat.format(
2039                         getTextResource("rmid.syntax.illegal.option"),
2040                         args[i]));
2041                 }
2042             }
2043 
2044             if (log == null) {
2045                 if (ssf != null) {
2046                     bomb(getTextResource("rmid.syntax.log.required"));
2047                 } else {
2048                     log = "log";
2049                 }
2050             }
2051 
2052             debugExec = AccessController.doPrivileged(
2053                 new GetBooleanAction("sun.rmi.server.activation.debugExec"));
2054 
2055             /**
2056              * Determine class name for activation exec policy (if any).
2057              */
2058             String execPolicyClassName = AccessController.doPrivileged(
2059                 new GetPropertyAction("sun.rmi.activation.execPolicy", null));
2060             if (execPolicyClassName == null) {
2061                 if (!stop) {
2062                     DefaultExecPolicy.checkConfiguration();
2063                 }
2064                 execPolicyClassName = "default";
2065             }
2066 
2067             /**
2068              * Initialize method for activation exec policy.
2069              */
2070             if (!execPolicyClassName.equals("none")) {
2071                 if (execPolicyClassName.equals("") ||
2072                     execPolicyClassName.equals("default"))
2073                 {
2074                     execPolicyClassName = DefaultExecPolicy.class.getName();
2075                 }
2076 
2077                 try {
2078                     Class<?> execPolicyClass =
2079                         RMIClassLoader.loadClass(execPolicyClassName);
2080                     execPolicy = execPolicyClass.newInstance();
2081                     execPolicyMethod =
2082                         execPolicyClass.getMethod("checkExecCommand",
2083                                                   ActivationGroupDesc.class,
2084                                                   String[].class);
2085                 } catch (Exception e) {
2086                     if (debugExec) {
2087                         System.err.println(
2088                             getTextResource("rmid.exec.policy.exception"));
2089                         e.printStackTrace();
2090                     }
2091                     bomb(getTextResource("rmid.exec.policy.invalid"));
2092                 }
2093             }
2094 
2095             if (stop == true) {
2096                 final int finalPort = port;
2097                 AccessController.doPrivileged(new PrivilegedAction<Void>() {
2098                     public Void run() {
2099                         System.setProperty("java.rmi.activation.port",
2100                                            Integer.toString(finalPort));
2101                         return null;
2102                     }
2103                 });
2104                 ActivationSystem system = ActivationGroup.getSystem();
2105                 system.shutdown();
2106                 System.exit(0);
2107             }
2108 
2109             /*
2110              * Fix for 4173960: Create and initialize activation using
2111              * a static method, startActivation, which will build the
2112              * Activation state in two ways: if when rmid is run, no
2113              * log file is found, the ActLogHandler.recover(...)
2114              * method will create a new Activation instance.
2115              * Alternatively, if a logfile is available, a serialized
2116              * instance of activation will be read from the log's
2117              * snapshot file.  Log updates will be applied to this
2118              * Activation object until rmid's state has been fully
2119              * recovered.  In either case, only one instance of
2120              * Activation is created.
2121              */
2122             startActivation(port, ssf, log,
2123                             childArgs.toArray(new String[childArgs.size()]));
2124 
2125             // prevent activator from exiting
2126             while (true) {
2127                 try {
2128                     Thread.sleep(Long.MAX_VALUE);
2129                 } catch (InterruptedException e) {
2130                 }
2131             }
2132         } catch (Exception e) {
2133             System.err.println(
2134                 MessageFormat.format(
2135                     getTextResource("rmid.unexpected.exception"), e));
2136             e.printStackTrace();
2137         }
2138         System.exit(1);
2139     }
2140 
2141     /**
2142      * Retrieves text resources from the locale-specific properties file.
2143      */
2144     private static String getTextResource(String key) {
2145         if (Activation.resources == null) {
2146             try {
2147                 Activation.resources = ResourceBundle.getBundle(
2148                     "sun.rmi.server.resources.rmid");
2149             } catch (MissingResourceException mre) {
2150             }
2151             if (Activation.resources == null) {
2152                 // throwing an Error is a bit extreme, methinks
2153                 return ("[missing resource file: " + key + "]");
2154             }
2155         }
2156 
2157         String val = null;
2158         try {
2159             val = Activation.resources.getString (key);
2160         } catch (MissingResourceException mre) {
2161         }
2162 
2163         if (val == null) {
2164             return ("[missing resource: " + key + "]");
2165         } else {
2166             return val;
2167         }
2168     }
2169 
2170     /*
2171      * Dijkstra semaphore operations to limit the number of subprocesses
2172      * rmid attempts to make at once.
2173      */
2174     /**
2175      * Acquire the group semaphore and return a group name.  Each
2176      * Pstartgroup must be followed by a Vstartgroup.  The calling thread
2177      * will wait until there are fewer than <code>N</code> other threads
2178      * holding the group semaphore.  The calling thread will then acquire
2179      * the semaphore and return.
2180      */
2181     private synchronized String Pstartgroup() throws ActivationException {
2182         while (true) {
2183             checkShutdown();
2184             // Wait until positive, then decrement.
2185             if (groupSemaphore > 0) {
2186                 groupSemaphore--;
2187                 return "Group-" + groupCounter++;
2188             }
2189 
2190             try {
2191                 wait();
2192             } catch (InterruptedException e) {
2193             }
2194         }
2195     }
2196 
2197     /**
2198      * Release the group semaphore.  Every P operation must be
2199      * followed by a V operation.  This may cause another thread to
2200      * wake up and return from its P operation.
2201      */
2202     private synchronized void Vstartgroup() {
2203         // Increment and notify a waiter (not necessarily FIFO).
2204         groupSemaphore++;
2205         notifyAll();
2206     }
2207 
2208     /**
2209      * A server socket factory to use when rmid is launched via 'inetd'
2210      * with 'wait' status.  This socket factory's 'createServerSocket'
2211      * method returns the server socket specified during construction that
2212      * is specialized to delay accepting requests until the
2213      * 'initDone' flag is 'true'.  The server socket supplied to
2214      * the constructor should be the server socket obtained from the
2215      * ServerSocketChannel returned from the 'System.inheritedChannel'
2216      * method.
2217      **/
2218     private static class ActivationServerSocketFactory
2219         implements RMIServerSocketFactory
2220     {
2221         private final ServerSocket serverSocket;
2222 
2223         /**
2224          * Constructs an 'ActivationServerSocketFactory' with the specified
2225          * 'serverSocket'.
2226          **/
2227         ActivationServerSocketFactory(ServerSocket serverSocket) {
2228             this.serverSocket = serverSocket;
2229         }
2230 
2231         /**
2232          * Returns the server socket specified during construction wrapped
2233          * in a 'DelayedAcceptServerSocket'.
2234          **/
2235         public ServerSocket createServerSocket(int port)
2236             throws IOException
2237         {
2238             return new DelayedAcceptServerSocket(serverSocket);
2239         }
2240 
2241     }
2242 
2243     /**
2244      * A server socket that delegates all public methods to the underlying
2245      * server socket specified at construction.  The accept method is
2246      * overridden to delay calling accept on the underlying server socket
2247      * until the 'initDone' flag is 'true'.
2248      **/
2249     private static class DelayedAcceptServerSocket extends ServerSocket {
2250 
2251         private final ServerSocket serverSocket;
2252 
2253         DelayedAcceptServerSocket(ServerSocket serverSocket)
2254             throws IOException
2255         {
2256             this.serverSocket = serverSocket;
2257         }
2258 
2259         public void bind(SocketAddress endpoint) throws IOException {
2260             serverSocket.bind(endpoint);
2261         }
2262 
2263         public void bind(SocketAddress endpoint, int backlog)
2264                 throws IOException
2265         {
2266             serverSocket.bind(endpoint, backlog);
2267         }
2268 
2269         public InetAddress getInetAddress() {
2270             return serverSocket.getInetAddress();
2271         }
2272 
2273         public int getLocalPort() {
2274             return serverSocket.getLocalPort();
2275         }
2276 
2277         public SocketAddress getLocalSocketAddress() {
2278             return serverSocket.getLocalSocketAddress();
2279         }
2280 
2281         /**
2282          * Delays calling accept on the underlying server socket until the
2283          * remote service is bound in the registry.
2284          **/
2285         public Socket accept() throws IOException {
2286             synchronized (initLock) {
2287                 try {
2288                     while (!initDone) {
2289                         initLock.wait();
2290                     }
2291                 } catch (InterruptedException ignore) {
2292                     throw new AssertionError(ignore);
2293                 }
2294             }
2295             return serverSocket.accept();
2296         }
2297 
2298         public void close() throws IOException {
2299             serverSocket.close();
2300         }
2301 
2302         public ServerSocketChannel getChannel() {
2303             return serverSocket.getChannel();
2304         }
2305 
2306         public boolean isBound() {
2307             return serverSocket.isBound();
2308         }
2309 
2310         public boolean isClosed() {
2311             return serverSocket.isClosed();
2312         }
2313 
2314         public void setSoTimeout(int timeout)
2315             throws SocketException
2316         {
2317             serverSocket.setSoTimeout(timeout);
2318         }
2319 
2320         public int getSoTimeout() throws IOException {
2321             return serverSocket.getSoTimeout();
2322         }
2323 
2324         public void setReuseAddress(boolean on) throws SocketException {
2325             serverSocket.setReuseAddress(on);
2326         }
2327 
2328         public boolean getReuseAddress() throws SocketException {
2329             return serverSocket.getReuseAddress();
2330         }
2331 
2332         public String toString() {
2333             return serverSocket.toString();
2334         }
2335 
2336         public void setReceiveBufferSize(int size)
2337             throws SocketException
2338         {
2339             serverSocket.setReceiveBufferSize(size);
2340         }
2341 
2342         public int getReceiveBufferSize()
2343             throws SocketException
2344         {
2345             return serverSocket.getReceiveBufferSize();
2346         }
2347     }
2348 }
2349 
2350 /**
2351  * PipeWriter plugs together two pairs of input and output streams by
2352  * providing readers for input streams and writing through to
2353  * appropriate output streams.  Both output streams are annotated on a
2354  * per-line basis.
2355  *
2356  * @author Laird Dornin, much code borrowed from Peter Jones, Ken
2357  *         Arnold and Ann Wollrath.
2358  */
2359 class PipeWriter implements Runnable {
2360 
2361     /** stream used for buffering lines */
2362     private ByteArrayOutputStream bufOut;
2363 
2364     /** count since last separator */
2365     private int cLast;
2366 
2367     /** current chunk of input being compared to lineSeparator.*/
2368     private byte[] currSep;
2369 
2370     private PrintWriter out;
2371     private InputStream in;
2372 
2373     private String pipeString;
2374     private String execString;
2375 
2376     private static String lineSeparator;
2377     private static int lineSeparatorLength;
2378 
2379     private static int numExecs = 0;
2380 
2381     static {
2382         lineSeparator = AccessController.doPrivileged(
2383             new GetPropertyAction("line.separator"));
2384         lineSeparatorLength = lineSeparator.length();
2385     }
2386 
2387     /**
2388      * Create a new PipeWriter object. All methods of PipeWriter,
2389      * except plugTogetherPair, are only accesible to PipeWriter
2390      * itself.  Synchronization is unnecessary on functions that will
2391      * only be used internally in PipeWriter.
2392      *
2393      * @param in input stream from which pipe input flows
2394      * @param out output stream to which log messages will be sent
2395      * @param dest String which tags output stream as 'out' or 'err'
2396      * @param nExecs number of execed processes, Activation groups.
2397      */
2398     private PipeWriter
2399         (InputStream in, OutputStream out, String tag, int nExecs) {
2400 
2401         this.in = in;
2402         this.out = new PrintWriter(out);
2403 
2404         bufOut = new ByteArrayOutputStream();
2405         currSep = new byte[lineSeparatorLength];
2406 
2407         /* set unique pipe/pair annotations */
2408         execString = ":ExecGroup-" +
2409             Integer.toString(nExecs) + ':' + tag + ':';
2410     }
2411 
2412     /**
2413      * Create a thread to listen and read from input stream, in.  buffer
2414      * the data that is read until a marker which equals lineSeparator
2415      * is read.  Once such a string has been discovered; write out an
2416      * annotation string followed by the buffered data and a line
2417      * separator.
2418      */
2419     public void run() {
2420         byte[] buf = new byte[256];
2421         int count;
2422 
2423         try {
2424             /* read bytes till there are no more. */
2425             while ((count = in.read(buf)) != -1) {
2426                 write(buf, 0, count);
2427             }
2428 
2429             /*  flush internal buffer... may not have ended on a line
2430              *  separator, we also need a last annotation if
2431              *  something was left.
2432              */
2433             String lastInBuffer = bufOut.toString();
2434             bufOut.reset();
2435             if (lastInBuffer.length() > 0) {
2436                 out.println (createAnnotation() + lastInBuffer);
2437                 out.flush();                    // add a line separator
2438                                                 // to make output nicer
2439             }
2440 
2441         } catch (IOException e) {
2442         }
2443     }
2444 
2445     /**
2446      * Write a subarray of bytes.  Pass each through write byte method.
2447      */
2448     private void write(byte b[], int off, int len) throws IOException {
2449 
2450         if (len < 0) {
2451             throw new ArrayIndexOutOfBoundsException(len);
2452         }
2453         for (int i = 0; i < len; ++ i) {
2454             write(b[off + i]);
2455         }
2456     }
2457 
2458     /**
2459      * Write a byte of data to the stream.  If we have not matched a
2460      * line separator string, then the byte is appended to the internal
2461      * buffer.  If we have matched a line separator, then the currently
2462      * buffered line is sent to the output writer with a prepended
2463      * annotation string.
2464      */
2465     private void write(byte b) throws IOException {
2466         int i = 0;
2467 
2468         /* shift current to the left */
2469         for (i = 1 ; i < (currSep.length); i ++) {
2470             currSep[i-1] = currSep[i];
2471         }
2472         currSep[i-1] = b;
2473         bufOut.write(b);
2474 
2475         /* enough characters for a separator? */
2476         if ( (cLast >= (lineSeparatorLength - 1)) &&
2477              (lineSeparator.equals(new String(currSep))) ) {
2478 
2479             cLast = 0;
2480 
2481             /* write prefix through to underlying byte stream */
2482             out.print(createAnnotation() + bufOut.toString());
2483             out.flush();
2484             bufOut.reset();
2485 
2486             if (out.checkError()) {
2487                 throw new IOException
2488                     ("PipeWriter: IO Exception when"+
2489                      " writing to output stream.");
2490             }
2491 
2492         } else {
2493             cLast++;
2494         }
2495     }
2496 
2497     /**
2498      * Create an annotation string to be printed out after
2499      * a new line and end of stream.
2500      */
2501     private String createAnnotation() {
2502 
2503         /* construct prefix for log messages:
2504          * date/time stamp...
2505          */
2506         return ((new Date()).toString()  +
2507                  /* ... print pair # ... */
2508                  (execString));
2509     }
2510 
2511     /**
2512      * Allow plugging together two pipes at a time, to associate
2513      * output from an execed process.  This is the only publicly
2514      * accessible method of this object; this helps ensure that
2515      * synchronization will not be an issue in the annotation
2516      * process.
2517      *
2518      * @param in input stream from which pipe input comes
2519      * @param out output stream to which log messages will be sent
2520      * @param in1 input stream from which pipe input comes
2521      * @param out1 output stream to which log messages will be sent
2522      */
2523     static void plugTogetherPair(InputStream in,
2524                                  OutputStream out,
2525                                  InputStream in1,
2526                                  OutputStream out1) {
2527         Thread inThread = null;
2528         Thread outThread = null;
2529 
2530         int nExecs = getNumExec();
2531 
2532         /* start RMI threads to read output from child process */
2533         inThread = AccessController.doPrivileged(
2534             new NewThreadAction(new PipeWriter(in, out, "out", nExecs),
2535                                 "out", true));
2536         outThread = AccessController.doPrivileged(
2537             new NewThreadAction(new PipeWriter(in1, out1, "err", nExecs),
2538                                 "err", true));
2539         inThread.start();
2540         outThread.start();
2541     }
2542 
2543     private static synchronized int getNumExec() {
2544         return numExecs++;
2545     }
2546 }