You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@river.apache.org by ni...@apache.org on 2008/12/10 06:12:29 UTC

svn commit: r724976 [2/6] - in /incubator/river/jtsk/skunk/niclas1/services/phoenix: ./ src/ src/main/ src/main/java/ src/main/java/com/ src/main/java/com/sun/ src/main/java/com/sun/jini/ src/main/java/com/sun/jini/phoenix/ src/main/java/com/sun/jini/p...

Added: incubator/river/jtsk/skunk/niclas1/services/phoenix/src/main/java/com/sun/jini/phoenix/Activation.java
URL: http://svn.apache.org/viewvc/incubator/river/jtsk/skunk/niclas1/services/phoenix/src/main/java/com/sun/jini/phoenix/Activation.java?rev=724976&view=auto
==============================================================================
--- incubator/river/jtsk/skunk/niclas1/services/phoenix/src/main/java/com/sun/jini/phoenix/Activation.java (added)
+++ incubator/river/jtsk/skunk/niclas1/services/phoenix/src/main/java/com/sun/jini/phoenix/Activation.java Tue Dec  9 21:12:27 2008
@@ -0,0 +1,2261 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.sun.jini.phoenix;
+
+import com.sun.jini.proxy.BasicProxyTrustVerifier;
+import com.sun.jini.proxy.MarshalledWrapper;
+import com.sun.jini.reliableLog.LogHandler;
+import com.sun.jini.reliableLog.ReliableLog;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Serializable;
+import java.net.URL;
+import java.rmi.ConnectException;
+import java.rmi.ConnectIOException;
+import java.rmi.MarshalledObject;
+import java.rmi.NoSuchObjectException;
+import java.rmi.NotBoundException;
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+import java.rmi.activation.ActivationDesc;
+import java.rmi.activation.ActivationException;
+import java.rmi.activation.ActivationGroupDesc;
+import java.rmi.activation.ActivationGroupID;
+import java.rmi.activation.ActivationID;
+import java.rmi.activation.ActivationInstantiator;
+import java.rmi.activation.ActivationMonitor;
+import java.rmi.activation.ActivationSystem;
+import java.rmi.activation.UnknownGroupException;
+import java.rmi.activation.UnknownObjectException;
+import java.rmi.registry.LocateRegistry;
+import java.rmi.registry.Registry;
+import java.rmi.server.UID;
+import java.security.CodeSource;
+import java.security.PrivilegedExceptionAction;
+import java.security.ProtectionDomain;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.MissingResourceException;
+import java.util.Properties;
+import java.util.ResourceBundle;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import javax.security.auth.Subject;
+import javax.security.auth.login.LoginContext;
+import javax.security.auth.login.LoginException;
+import net.jini.config.Configuration;
+import net.jini.config.ConfigurationException;
+import net.jini.config.ConfigurationNotFoundException;
+import net.jini.config.ConfigurationProvider;
+import net.jini.config.NoSuchEntryException;
+import net.jini.core.constraint.RemoteMethodControl;
+import net.jini.export.Exporter;
+import net.jini.export.proxytrust.ServerProxyTrust;
+import net.jini.io.MarshalInputStream;
+import net.jini.io.MarshalOutputStream;
+import net.jini.io.MarshalledInstance;
+import net.jini.jeri.BasicILFactory;
+import net.jini.jeri.BasicJeriExporter;
+import net.jini.jeri.ServerEndpoint;
+import net.jini.jeri.tcp.TcpServerEndpoint;
+import net.jini.security.BasicProxyPreparer;
+import net.jini.security.ProxyPreparer;
+import net.jini.security.TrustVerifier;
+
+/**
+ * Phoenix main class.
+ *
+ * @author Sun Microsystems, Inc.
+ * @since 2.0
+ */
+class Activation implements Serializable
+{
+    private static final long serialVersionUID = -6825932652725866242L;
+    private static final String PHOENIX = "com.sun.jini.phoenix";
+    private static ResourceBundle resources = null;
+    private static Logger logger = Logger.getLogger( PHOENIX );
+    private static final int PHOENIX_PORT = 1198;
+    private static final Object logLock = new Object();
+
+    /**
+     * maps activation id uid to its respective group id
+     */
+    private Map idTable = new HashMap();
+    /**
+     * maps group id to its GroupEntry groups
+     */
+    private Map groupTable = new HashMap();
+
+    /**
+     * login context
+     */
+    private transient LoginContext login;
+    /**
+     * number of simultaneous group exec's
+     */
+    private transient int groupSemaphore;
+    /**
+     * counter for numbering groups
+     */
+    private transient int groupCounter = 0;
+    /**
+     * persistent store
+     */
+    private transient ReliableLog log;
+    /**
+     * number of log updates since last log snapshot
+     */
+    private transient int numUpdates = 0;
+    /**
+     * take log snapshot after this many updates
+     */
+    private transient int snapshotInterval;
+    /**
+     * the default java command for groups
+     */
+    private transient String[] command;
+    /**
+     * timeout on wait for child process to be created or destroyed
+     */
+    private transient long groupTimeout;
+    /**
+     * timeout on wait for unexport to succeed
+     */
+    private transient long unexportTimeout;
+    /**
+     * timeout on wait between unexport attempts
+     */
+    private transient long unexportWait;
+    /**
+     * ActivatorImpl instance
+     */
+    private transient Activator activator;
+    /**
+     * exporter for activator
+     */
+    private transient Exporter activatorExporter;
+    /**
+     * stub for activator
+     */
+    private transient Activator activatorStub;
+    /**
+     * SystemImpl instance
+     */
+    private transient ActivationSystem system;
+    /**
+     * exporter for system
+     */
+    private transient Exporter systemExporter;
+    /**
+     * stub for system
+     */
+    private transient ActivationSystem systemStub;
+    /**
+     * MonitorImpl instance
+     */
+    private transient ActivationMonitor monitor;
+    /**
+     * exporter for monitor
+     */
+    private transient Exporter monitorExporter;
+    /**
+     * stub for monitor
+     */
+    private transient ActivationMonitor monitorStub;
+    /**
+     * RegistryImpl instance
+     */
+    private transient Registry registry;
+    /**
+     * exporter for registry
+     */
+    private transient Exporter registryExporter;
+    /**
+     * stub for registry
+     */
+    private transient Registry registryStub;
+    /**
+     * MarshalledObject(ActivationGroupData) or null
+     */
+    private transient MarshalledObject groupData;
+    /**
+     * Location of ActivationGroupImpl or null
+     */
+    private transient String groupLocation;
+    /**
+     * preparer for ActivationInstantiators
+     */
+    private transient ProxyPreparer groupPreparer;
+    private transient GroupOutputHandler outputHandler;
+    /**
+     * true if shutdown has been called
+     */
+    private volatile boolean shuttingDown = false;
+    /**
+     * Runtime shutdown hook
+     */
+    private transient Thread shutdownHook;
+    /**
+     * Non-null if phoenix was started by the service starter
+     */
+    private transient PhoenixStarter starter;
+
+    /**
+     * Create an uninitialized instance of Activation that can be
+     * populated with log data.  This is only called when the initial
+     * snapshot is taken during the first incarnation of phoenix.
+     */
+    private Activation()
+    {
+    }
+
+    private void init( ReliableLog log,
+                       LoginContext login,
+                       Configuration config,
+                       String[] configOptions,
+                       PhoenixStarter starter )
+        throws Exception
+    {
+        this.log = log;
+        this.login = login;
+        this.starter = starter;
+        groupSemaphore = getInt( config, "groupThrottle", 3 );
+        snapshotInterval = getInt( config, "persistenceSnapshotThreshold", 200 );
+        groupTimeout = getInt( config, "groupTimeout", 60000 );
+        unexportTimeout = getInt( config, "unexportTimeout", 60000 );
+        unexportWait = getInt( config, "unexportWait", 10 );
+        String[] opts = (String[]) config.getEntry(
+            PHOENIX, "groupOptions", String[].class, new String[0] );
+        command = new String[opts.length + 2];
+        command[ 0 ] = ( System.getProperty( "java.home" ) +
+                         File.separator + "bin" + File.separator + "java" );
+        System.arraycopy( opts, 0, command, 1, opts.length );
+        command[ command.length - 1 ] =
+            "com.sun.jini.phoenix.ActivationGroupInit";
+        shutdownHook = new ShutdownHook();
+        Runtime.getRuntime().addShutdownHook( shutdownHook );
+        groupPreparer = getPreparer( config, "instantiatorPreparer" );
+        groupData = new MarshalledObject( new ActivationGroupData( (String[])
+            config.getEntry( PHOENIX, "groupConfig",
+                             String[].class, configOptions ) ) );
+        outputHandler = (GroupOutputHandler) config.getEntry(
+            PHOENIX, "groupOutputHandler", GroupOutputHandler.class,
+            new GroupOutputHandler()
+            {
+                public void handleOutput( ActivationGroupID id,
+                                          ActivationGroupDesc desc,
+                                          long incarnation,
+                                          String groupName,
+                                          InputStream out,
+                                          InputStream err )
+                {
+                    PipeWriter.plugTogetherPair(
+                        groupName, out, System.out, err, System.err );
+                }
+            } );
+
+        groupLocation = (String)
+            config.getEntry( PHOENIX, "groupLocation",
+                             String.class, getDefaultGroupLocation() );
+
+        ActivationGroupID[] gids = (ActivationGroupID[])
+            groupTable.keySet().toArray(
+                new ActivationGroupID[groupTable.size()] );
+        activator = new ActivatorImpl();
+        ServerEndpoint se = TcpServerEndpoint.getInstance( PHOENIX_PORT );
+        activatorExporter =
+            getExporter( config, "activatorExporter",
+                         new BasicJeriExporter( se, new BasicILFactory(),
+                                                false, true,
+                                                PhoenixConstants.ACTIVATOR_UUID ) );
+        system = new SystemImpl();
+        systemExporter =
+            getExporter( config, "systemExporter",
+                         new BasicJeriExporter( se, new SystemAccessILFactory(),
+                                                false, true,
+                                                PhoenixConstants.ACTIVATION_SYSTEM_UUID ) );
+        monitor = new MonitorImpl();
+        monitorExporter =
+            getExporter( config, "monitorExporter",
+                         new BasicJeriExporter( se, new AccessILFactory() ) );
+        registry = new RegistryImpl();
+        registryExporter =
+            getExporter( config, "registryExporter", new RegistrySunExporter() );
+        monitorStub = (ActivationMonitor) monitorExporter.export( monitor );
+        synchronized( activatorExporter )
+        {
+            systemStub = (ActivationSystem) systemExporter.export( system );
+            activatorStub = (Activator) activatorExporter.export( activator );
+        }
+        registryStub = (Registry) registryExporter.export( registry );
+        logger.info( getTextResource( "phoenix.daemon.started" ) );
+        for( int i = gids.length; --i >= 0; )
+        {
+            try
+            {
+                getGroupEntry( gids[ i ] ).restartServices();
+            }
+            catch( UnknownGroupException e )
+            {
+            }
+        }
+    }
+
+    private static String getDefaultGroupLocation()
+    {
+        ProtectionDomain pd = Activation.class.getProtectionDomain();
+        CodeSource cs = pd.getCodeSource();
+        URL location = null;
+        if( cs != null )
+        {
+            location = cs.getLocation();
+        }
+        if( location != null )
+        {
+            String loc = location.toString();
+            if( loc.endsWith( ".jar" ) )
+            {
+                return loc.substring( 0, loc.length() - 4 ) + "-group.jar";
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Return a configuration for the specified options.
+     */
+    private static Configuration getConfig( String[] configOptions,
+                                            PhoenixStarter starter )
+        throws ConfigurationException
+    {
+        try
+        {
+            return ConfigurationProvider.getInstance(
+                configOptions, Activation.class.getClassLoader() );
+
+        }
+        catch( ConfigurationNotFoundException e )
+        {
+            if( starter == null )
+            {
+                bomb( "phoenix.missing.config",
+                      Arrays.asList( configOptions ).toString() );
+            }
+            throw e;
+        }
+    }
+
+    /**
+     * Return an int configuration entry.
+     */
+    private static int getInt( Configuration config, String name, int defValue )
+        throws ConfigurationException
+    {
+        return ( (Integer) config.getEntry( PHOENIX, name, int.class,
+                                            new Integer( defValue ) ) ).intValue();
+    }
+
+    /**
+     * Return the exporter with the specified name from the specified
+     * configuration.
+     */
+    private static Exporter getExporter( Configuration config,
+                                         String name,
+                                         Exporter defaultExporter )
+        throws ConfigurationException
+    {
+        return (Exporter)
+            config.getEntry( PHOENIX, name, Exporter.class, defaultExporter );
+    }
+
+    /**
+     * Return a ProxyPreparer configuration entry.
+     */
+    private static ProxyPreparer getPreparer( Configuration config, String name )
+        throws ConfigurationException
+    {
+        return (ProxyPreparer) config.getEntry( PHOENIX, name,
+                                                ProxyPreparer.class,
+                                                new BasicProxyPreparer() );
+    }
+
+    class ActivatorImpl extends AbstractActivator implements ServerProxyTrust
+    {
+        ActivatorImpl()
+        {
+        }
+
+        public MarshalledWrapper activate( ActivationID id, boolean force )
+            throws ActivationException
+        {
+            UID uid = getUID( id );
+            return getGroupEntry( uid ).activate( uid, force );
+        }
+
+        public TrustVerifier getProxyVerifier()
+        {
+            return new ConstrainableAID.Verifier( activatorStub );
+        }
+    }
+
+    class MonitorImpl extends AbstractMonitor implements ServerProxyTrust
+    {
+        MonitorImpl()
+        {
+        }
+
+        public void inactiveObject( ActivationID id )
+            throws UnknownObjectException
+        {
+            UID uid = getUID( id );
+            getGroupEntry( uid ).inactiveObject( uid );
+        }
+
+        public void activeObject( ActivationID id, MarshalledObject mobj )
+            throws UnknownObjectException
+        {
+            UID uid = getUID( id );
+            getGroupEntry( uid ).activeObject( uid, mobj );
+        }
+
+        public void inactiveGroup( ActivationGroupID id,
+                                   long incarnation )
+            throws UnknownGroupException
+        {
+            getGroupEntry( id ).inactiveGroup( incarnation, false );
+        }
+
+        public TrustVerifier getProxyVerifier()
+        {
+            return new BasicProxyTrustVerifier( monitorStub );
+        }
+    }
+
+    class SystemImpl extends AbstractSystem implements ServerProxyTrust
+    {
+        SystemImpl()
+        {
+        }
+
+        /**
+         * returns a ConstrainableAID
+         */
+        public ActivationID registerObject( ActivationDesc desc )
+            throws ActivationException
+        {
+            UID uid = new UID();
+            ActivationGroupID groupID = desc.getGroupID();
+            getGroupEntry( groupID ).registerObject( uid, desc, true );
+            synchronized( activatorExporter )
+            {
+                return getAID( uid );
+            }
+        }
+
+        public void unregisterObject( ActivationID id )
+            throws ActivationException
+        {
+            UID uid = getUID( id );
+            getGroupEntry( getGroupID( uid ) ).unregisterObject( uid, true );
+        }
+
+        public ActivationGroupID registerGroup( ActivationGroupDesc desc )
+            throws ActivationException
+        {
+            ActivationGroupID id = new ActivationGroupID( systemStub );
+            synchronized( logLock )
+            {
+                addLogRecord( new LogRegisterGroup( id, desc ) );
+                GroupEntry entry = new GroupEntry( id, desc );
+                synchronized( groupTable )
+                {
+                    groupTable.put( id, entry );
+                }
+            }
+            return id;
+        }
+
+        public ActivationMonitor activeGroup( ActivationGroupID id,
+                                              ActivationInstantiator group,
+                                              long incarnation )
+            throws ActivationException, RemoteException
+        {
+            group = (ActivationInstantiator) groupPreparer.prepareProxy( group );
+            getGroupEntry( id ).activeGroup( group, incarnation );
+            return monitorStub;
+        }
+
+        public void unregisterGroup( ActivationGroupID id )
+            throws ActivationException
+        {
+            GroupEntry groupEntry;
+            synchronized( groupTable )
+            {
+                groupEntry = getGroupEntry( id );
+                groupTable.remove( id );
+            }
+            groupEntry.unregisterGroup( true );
+        }
+
+        public ActivationDesc setActivationDesc( ActivationID id,
+                                                 ActivationDesc desc )
+            throws ActivationException
+        {
+            UID uid = getUID( id );
+            if( !getGroupID( uid ).equals( desc.getGroupID() ) )
+            {
+                throw new ActivationException(
+                    "ActivationDesc contains wrong group" );
+            }
+            return getGroupEntry( uid ).setActivationDesc( uid, desc, true );
+        }
+
+        public ActivationGroupDesc setActivationGroupDesc(
+            ActivationGroupID id,
+            ActivationGroupDesc desc )
+            throws ActivationException
+        {
+            return getGroupEntry( id ).setActivationGroupDesc( id, desc, true );
+        }
+
+        public ActivationDesc getActivationDesc( ActivationID id )
+            throws UnknownObjectException
+        {
+            UID uid = getUID( id );
+            return getGroupEntry( uid ).getActivationDesc( uid );
+        }
+
+        public ActivationGroupDesc getActivationGroupDesc( ActivationGroupID id )
+            throws UnknownGroupException
+        {
+            return getGroupEntry( id ).desc;
+        }
+
+        public void shutdown()
+        {
+            synchronized( Activation.this )
+            {
+                if( !shuttingDown )
+                {
+                    shuttingDown = true;
+                    new Shutdown().start();
+                }
+            }
+        }
+
+        public Map getActivationGroups()
+        {
+            synchronized( groupTable )
+            {
+                Map map = new HashMap( groupTable.size() );
+                for( Iterator iter = groupTable.values().iterator();
+                     iter.hasNext(); )
+                {
+                    GroupEntry entry = (GroupEntry) iter.next();
+                    if( !entry.removed )
+                    {
+                        map.put( entry.groupID, entry.desc );
+                    }
+                }
+                return map;
+            }
+        }
+
+        public Map getActivatableObjects( ActivationGroupID id )
+            throws UnknownGroupException
+        {
+            synchronized( activatorExporter )
+            {
+                // to wait for it to be exported
+            }
+            return getGroupEntry( id ).getActivatableObjects();
+        }
+
+        public TrustVerifier getProxyVerifier()
+        {
+            return new BasicProxyTrustVerifier( systemStub );
+        }
+    }
+
+    /**
+     * A read-only registry containing a single entry for the system.
+     */
+    class RegistryImpl extends AbstractRegistry
+    {
+        /**
+         * The name of the single entry
+         */
+        private final String NAME = ActivationSystem.class.getName();
+
+        RegistryImpl()
+        {
+        }
+
+        /**
+         * Returns the single object if the specified name matches the single
+         * name, otherwise throws NotBoundException.
+         */
+        public Remote lookup( String name ) throws NotBoundException
+        {
+            if( name.equals( NAME ) )
+            {
+                return systemStub;
+            }
+            throw new NotBoundException( name );
+        }
+
+        /**
+         * Always throws SecurityException.
+         */
+        public void bind( String name, Remote obj )
+        {
+            throw new SecurityException( "read-only registry" );
+        }
+
+        /**
+         * Always throws SecurityException.
+         */
+        public void unbind( String name )
+        {
+            throw new SecurityException( "read-only registry" );
+        }
+
+        /**
+         * Always throws SecurityException.
+         */
+        public void rebind( String name, Remote obj )
+        {
+            throw new SecurityException( "read-only registry" );
+        }
+
+        /**
+         * Returns a list containing the single name.
+         */
+        public String[] list()
+        {
+            return new String[]{ NAME };
+        }
+    }
+
+    /**
+     * If shutting down, throw an ActivationException.
+     */
+    private void checkShutdown() throws ActivationException
+    {
+        if( shuttingDown )
+        {
+            throw new ActivationException(
+                "activation system is shutting down" );
+        }
+    }
+
+    /**
+     * Thread to shutdown phoenix.
+     */
+    private class Shutdown extends Thread
+    {
+        Shutdown()
+        {
+            super( "Shutdown" );
+        }
+
+        public void run()
+        {
+            try
+            {
+                long stop = System.currentTimeMillis() + unexportTimeout;
+                boolean force = false;
+                while( !registryExporter.unexport( force ) ||
+                       !activatorExporter.unexport( force ) ||
+                       !systemExporter.unexport( force ) ||
+                       !monitorExporter.unexport( force ) )
+                {
+                    long rem = stop - System.currentTimeMillis();
+                    if( rem <= 0 )
+                    {
+                        force = true;
+                    }
+                    else
+                    {
+                        try
+                        {
+                            Thread.sleep( Math.min( rem, unexportWait ) );
+                        }
+                        catch( InterruptedException e )
+                        {
+                        }
+                    }
+                }
+                // destroy all child processes (groups)
+                GroupEntry[] groupEntries;
+                synchronized( groupTable )
+                {
+                    groupEntries = (GroupEntry[]) groupTable.values().
+                        toArray( new GroupEntry[groupTable.size()] );
+                }
+                for( int i = 0; i < groupEntries.length; i++ )
+                {
+                    groupEntries[ i ].shutdown();
+                }
+
+                Runtime.getRuntime().removeShutdownHook( shutdownHook );
+                try
+                {
+                    synchronized( logLock )
+                    {
+                        log.close();
+                    }
+                }
+                catch( IOException e )
+                {
+                }
+                try
+                {
+                    if( login != null )
+                    {
+                        login.logout();
+                    }
+                }
+                catch( LoginException e )
+                {
+                }
+            }
+            catch( Throwable t )
+            {
+                logger.log( Level.WARNING, "exception during shutdown", t );
+            }
+            finally
+            {
+                logger.info( getTextResource( "phoenix.daemon.shutdown" ) );
+                if( starter == null )
+                {
+                    System.exit( 0 );
+                }
+                else
+                {
+                    starter.unregister();
+                }
+            }
+        }
+    }
+
+    /**
+     * Thread to destroy children in the event of abnormal termination.
+     */
+    private class ShutdownHook extends Thread
+    {
+        ShutdownHook()
+        {
+            super( "Phoenix Shutdown Hook" );
+        }
+
+        public void run()
+        {
+            synchronized( Activation.this )
+            {
+                shuttingDown = true;
+            }
+            // destroy all child processes (groups) quickly
+            synchronized( groupTable )
+            {
+                for( Iterator iter = groupTable.values().iterator();
+                     iter.hasNext(); )
+                {
+                    ( (GroupEntry) iter.next() ).shutdownFast();
+                }
+            }
+        }
+    }
+
+    private ActivationID getAID( UID uid )
+    {
+        if( activatorStub instanceof RemoteMethodControl )
+        {
+            return new ConstrainableAID( activatorStub, uid );
+        }
+        else
+        {
+            return new AID( activatorStub, uid );
+        }
+    }
+
+    private static UID getUID( ActivationID id ) throws UnknownObjectException
+    {
+        Class c = id.getClass();
+        if( c == AID.class || c == ConstrainableAID.class )
+        {
+            return ( (AID) id ).getUID();
+        }
+        throw new UnknownObjectException( "object unknown" );
+    }
+
+    /**
+     * Returns the groupID for a given id of an object in the group.
+     * Throws UnknownObjectException if the object is not registered.
+     */
+    private ActivationGroupID getGroupID( UID uid )
+        throws UnknownObjectException
+    {
+        synchronized( idTable )
+        {
+            ActivationGroupID groupID = (ActivationGroupID) idTable.get( uid );
+            if( groupID != null )
+            {
+                return groupID;
+            }
+        }
+        throw new UnknownObjectException( "object unknown" );
+    }
+
+    /**
+     * Returns the group entry for the group id. Throws
+     * UnknownGroupException if the group is not registered.
+     */
+    private GroupEntry getGroupEntry( ActivationGroupID id )
+        throws UnknownGroupException
+    {
+        if( id.getClass() == ActivationGroupID.class )
+        {
+            synchronized( groupTable )
+            {
+                GroupEntry entry = (GroupEntry) groupTable.get( id );
+                if( entry != null && !entry.removed )
+                {
+                    return entry;
+                }
+            }
+        }
+        throw new UnknownGroupException( "group unknown" );
+    }
+
+    /**
+     * Returns the group entry for the object's id. Throws
+     * UnknownObjectException if the object is not registered.
+     */
+    private GroupEntry getGroupEntry( UID uid ) throws UnknownObjectException
+    {
+        ActivationGroupID gid = getGroupID( uid );
+        synchronized( groupTable )
+        {
+            GroupEntry entry = (GroupEntry) groupTable.get( gid );
+            if( entry != null )
+            {
+                return entry;
+            }
+        }
+        throw new UnknownObjectException( "object's group removed" );
+    }
+
+    /**
+     * Container for group information: group's descriptor, group's
+     * instantiator, flag to indicate pending group creation, and
+     * table of the group's active objects.
+     *
+     * WARNING: GroupEntry objects should not be written into log file
+     * updates.  GroupEntrys are inner classes of Activation and they
+     * can not be serialized independent of this class.  If the
+     * complete Activation system is written out as a log update, the
+     * point of having updates is nullified.
+     */
+    private class GroupEntry implements Serializable
+    {
+
+        private static final long serialVersionUID = 7222464070032993304L;
+        private static final int MAX_TRIES = 2;
+        private static final int NORMAL = 0;
+        private static final int CREATING = 1;
+        private static final int TERMINATE = 2;
+        private static final int TERMINATING = 3;
+
+        ActivationGroupDesc desc;
+        ActivationGroupID groupID;
+        long incarnation = 0;
+        Map objects = new HashMap( 11 );
+        HashSet restartSet = new HashSet();
+
+        transient ActivationInstantiator group = null;
+        transient int status = NORMAL;
+        transient long waitTime = 0;
+        transient String groupName = null;
+        transient Process child = null;
+        transient boolean removed = false;
+        transient Watchdog watchdog = null;
+
+        GroupEntry( ActivationGroupID groupID, ActivationGroupDesc desc )
+        {
+            this.groupID = groupID;
+            this.desc = desc;
+        }
+
+        void restartServices()
+        {
+            Iterator iter = null;
+
+            synchronized( this )
+            {
+                if( restartSet.isEmpty() )
+                {
+                    return;
+                }
+
+                /*
+             * Clone the restartSet so the set does not have to be locked
+             * during iteration. Locking the restartSet could cause
+             * deadlock if an object we are restarting caused another
+             * object in this group to be activated.
+             */
+                iter = ( (Set) restartSet.clone() ).iterator();
+            }
+
+            while( iter.hasNext() )
+            {
+                UID uid = (UID) iter.next();
+                try
+                {
+                    activate( uid, true );
+                }
+                catch( Exception e )
+                {
+                    if( shuttingDown )
+                    {
+                        return;
+                    }
+                    logger.log( Level.WARNING, "unable to restart service", e );
+                }
+            }
+        }
+
+        synchronized void activeGroup( ActivationInstantiator inst,
+                                       long instIncarnation )
+            throws ActivationException
+        {
+            if( group != null && group.equals( inst ) &&
+                incarnation == instIncarnation )
+            {
+                return;
+            }
+            else if( child != null && status != CREATING )
+            {
+                throw new ActivationException( "group not being created" );
+            }
+            else if( incarnation != instIncarnation )
+            {
+                throw new ActivationException( "invalid incarnation" );
+            }
+            else if( group != null )
+            {
+                throw new ActivationException( "group already active" );
+            }
+
+            group = inst;
+            status = NORMAL;
+            notifyAll();
+        }
+
+        private void checkRemoved() throws UnknownGroupException
+        {
+            if( removed )
+            {
+                throw new UnknownGroupException( "group removed" );
+            }
+        }
+
+        private ObjectEntry getObjectEntry( UID uid )
+            throws UnknownObjectException
+        {
+            if( removed )
+            {
+                throw new UnknownObjectException( "object's group removed" );
+            }
+            ObjectEntry objEntry = (ObjectEntry) objects.get( uid );
+            if( objEntry == null )
+            {
+                throw new UnknownObjectException( "object unknown" );
+            }
+            return objEntry;
+        }
+
+        synchronized void registerObject( UID uid,
+                                          ActivationDesc desc,
+                                          boolean addRecord )
+            throws ActivationException
+        {
+            checkRemoved();
+            synchronized( logLock )
+            {
+                if( addRecord )
+                {
+                    addLogRecord( new LogRegisterObject( uid, desc ) );
+                }
+                objects.put( uid, new ObjectEntry( desc ) );
+                if( desc.getRestartMode() )
+                {
+                    restartSet.add( uid );
+                }
+                synchronized( idTable )
+                {
+                    idTable.put( uid, groupID );
+                }
+            }
+        }
+
+        synchronized void unregisterObject( UID uid, boolean addRecord )
+            throws ActivationException
+        {
+            ObjectEntry objEntry = getObjectEntry( uid );
+            synchronized( logLock )
+            {
+                if( addRecord )
+                {
+                    addLogRecord( new LogUnregisterObject( uid ) );
+                }
+                objEntry.removed();
+                objects.remove( uid );
+                if( objEntry.desc.getRestartMode() )
+                {
+                    restartSet.remove( uid );
+                }
+                synchronized( idTable )
+                {
+                    idTable.remove( uid );
+                }
+            }
+        }
+
+        synchronized Map getActivatableObjects()
+        {
+            Map map = new HashMap( objects.size() );
+            for( Iterator iter = objects.entrySet().iterator();
+                 iter.hasNext(); )
+            {
+                Map.Entry ent = (Map.Entry) iter.next();
+                map.put( getAID( (UID) ent.getKey() ),
+                         ( (ObjectEntry) ent.getValue() ).desc );
+            }
+            return map;
+        }
+
+        synchronized void unregisterGroup( boolean addRecord )
+            throws ActivationException
+        {
+            checkRemoved();
+
+            synchronized( logLock )
+            {
+                if( addRecord )
+                {
+                    addLogRecord( new LogUnregisterGroup( groupID ) );
+                }
+                removed = true;
+                for( Iterator iter = objects.entrySet().iterator();
+                     iter.hasNext(); )
+                {
+                    Map.Entry ent = (Map.Entry) iter.next();
+                    UID uid = (UID) ent.getKey();
+                    synchronized( idTable )
+                    {
+                        idTable.remove( uid );
+                    }
+                    ObjectEntry objEntry = (ObjectEntry) ent.getValue();
+                    objEntry.removed();
+                }
+                objects.clear();
+                restartSet.clear();
+                reset();
+                childGone();
+            }
+        }
+
+        synchronized ActivationDesc setActivationDesc( UID uid,
+                                                       ActivationDesc desc,
+                                                       boolean addRecord )
+            throws ActivationException
+        {
+            ObjectEntry objEntry = getObjectEntry( uid );
+            synchronized( logLock )
+            {
+                if( addRecord )
+                {
+                    addLogRecord( new LogUpdateDesc( uid, desc ) );
+                }
+                ActivationDesc oldDesc = objEntry.desc;
+                objEntry.desc = desc;
+                if( desc.getRestartMode() )
+                {
+                    restartSet.add( uid );
+                }
+                else
+                {
+                    restartSet.remove( uid );
+                }
+                return oldDesc;
+            }
+        }
+
+        synchronized ActivationDesc getActivationDesc( UID uid )
+            throws UnknownObjectException
+        {
+            return getObjectEntry( uid ).desc;
+        }
+
+        synchronized ActivationGroupDesc setActivationGroupDesc(
+            ActivationGroupID id,
+            ActivationGroupDesc desc,
+            boolean addRecord )
+            throws ActivationException
+        {
+            checkRemoved();
+            synchronized( logLock )
+            {
+                if( addRecord )
+                {
+                    addLogRecord( new LogUpdateGroupDesc( id, desc ) );
+                }
+                ActivationGroupDesc oldDesc = this.desc;
+                this.desc = desc;
+                return oldDesc;
+            }
+        }
+
+        synchronized void inactiveGroup( long incarnation, boolean failure )
+            throws UnknownGroupException
+        {
+            checkRemoved();
+            if( this.incarnation != incarnation )
+            {
+                throw new UnknownGroupException( "invalid incarnation" );
+            }
+            reset();
+            if( failure )
+            {
+                terminate();
+            }
+            else if( child != null && status == NORMAL )
+            {
+                status = TERMINATE;
+                watchdog.noRestart();
+            }
+        }
+
+        synchronized void activeObject( UID uid, MarshalledObject mobj )
+            throws UnknownObjectException
+        {
+            getObjectEntry( uid ).stub =
+                new MarshalledWrapper( new MarshalledInstance( mobj ) );
+        }
+
+        synchronized void inactiveObject( UID uid )
+            throws UnknownObjectException
+        {
+            getObjectEntry( uid ).reset();
+        }
+
+        private void reset()
+        {
+            group = null;
+            for( Iterator iter = objects.values().iterator(); iter.hasNext(); )
+            {
+                ( (ObjectEntry) iter.next() ).reset();
+            }
+        }
+
+        private void childGone()
+        {
+            if( child != null )
+            {
+                child = null;
+                watchdog.dispose();
+                watchdog = null;
+                status = NORMAL;
+                notifyAll();
+            }
+        }
+
+        private void terminate()
+        {
+            if( child != null && status != TERMINATING )
+            {
+                child.destroy();
+                status = TERMINATING;
+                waitTime = System.currentTimeMillis() + groupTimeout;
+                notifyAll();
+            }
+        }
+
+        private void await()
+        {
+            while( true )
+            {
+                switch( status )
+                {
+                case NORMAL:
+                    return;
+                case TERMINATE:
+                    terminate();
+                case TERMINATING:
+                    try
+                    {
+                        child.exitValue();
+                    }
+                    catch( IllegalThreadStateException e )
+                    {
+                        long now = System.currentTimeMillis();
+                        if( waitTime > now )
+                        {
+                            try
+                            {
+                                wait( waitTime - now );
+                            }
+                            catch( InterruptedException ee )
+                            {
+                            }
+                            continue;
+                        }
+                        logger.log( Level.WARNING,
+                                    "group did not terminate: {0}", groupName );
+                    }
+                    childGone();
+                    return;
+                case CREATING:
+                    try
+                    {
+                        wait();
+                    }
+                    catch( InterruptedException e )
+                    {
+                    }
+                }
+            }
+        }
+
+        // no synchronization to avoid delay wrt getInstantiator
+        void shutdownFast()
+        {
+            Process p = child;
+            if( p != null )
+            {
+                p.destroy();
+            }
+        }
+
+        synchronized void shutdown()
+        {
+            reset();
+            terminate();
+            await();
+        }
+
+        MarshalledWrapper activate( UID uid, boolean force )
+            throws ActivationException
+        {
+            Exception detail = null;
+
+            /*
+            * Attempt to activate object and reattempt (several times)
+            * if activation fails due to communication problems.
+            */
+            for( int tries = MAX_TRIES; tries > 0; tries-- )
+            {
+                ActivationInstantiator inst;
+                long curIncarnation;
+
+                // look up object to activate
+                ObjectEntry objEntry;
+                synchronized( this )
+                {
+                    objEntry = getObjectEntry( uid );
+                    // if not forcing activation, return cached stub
+                    if( !force && objEntry.stub != null )
+                    {
+                        return objEntry.stub;
+                    }
+                    inst = getInstantiator( groupID );
+                    curIncarnation = incarnation;
+                }
+
+                boolean groupInactive = false;
+                boolean failure = false;
+                // activate object
+                try
+                {
+                    return objEntry.activate( uid, force, inst );
+                }
+                catch( NoSuchObjectException e )
+                {
+                    groupInactive = true;
+                    detail = e;
+                }
+                catch( ConnectException e )
+                {
+                    groupInactive = true;
+                    failure = true;
+                    detail = e;
+                }
+                catch( ConnectIOException e )
+                {
+                    groupInactive = true;
+                    failure = true;
+                    detail = e;
+                }
+                catch( InactiveGroupException e )
+                {
+                    groupInactive = true;
+                    detail = e;
+                }
+                catch( RemoteException e )
+                {
+                    // REMIND: wait some here before continuing?
+                    if( detail == null )
+                    {
+                        detail = e;
+                    }
+                }
+
+                if( groupInactive )
+                {
+                    // group has failed; mark inactive
+                    try
+                    {
+                        getGroupEntry( groupID ).inactiveGroup( curIncarnation,
+                                                                failure );
+                    }
+                    catch( UnknownGroupException e )
+                    {
+                        // not a problem
+                    }
+                }
+            }
+
+            /**
+             * signal that object activation failed, nested exception
+             * specifies what exception occurred when the object did not
+             * activate
+             */
+            throw new ActivationException( "object activation failed after " +
+                                           MAX_TRIES + " tries", detail );
+        }
+
+        /**
+         * Returns the instantiator for the group specified by id and
+         * entry. If the group is currently inactive, exec some
+         * bootstrap code to create the group.
+         */
+        private ActivationInstantiator getInstantiator( ActivationGroupID id )
+            throws ActivationException
+        {
+            await();
+            if( group != null )
+            {
+                return group;
+            }
+            checkRemoved();
+            boolean acquired = false;
+            try
+            {
+                groupName = Pstartgroup();
+                acquired = true;
+                String[] argv = activationArgs( desc );
+                if( logger.isLoggable( Level.FINE ) )
+                {
+                    logger.log( Level.FINE, "{0} exec {1}",
+                                new Object[]{ groupName, Arrays.asList( argv ) } );
+                }
+                try
+                {
+                    child = Runtime.getRuntime().exec( argv );
+                    status = CREATING;
+                    ++incarnation;
+                    watchdog = new Watchdog();
+                    watchdog.start();
+
+                    synchronized( logLock )
+                    {
+                        addLogRecord(
+                            new LogGroupIncarnation( id, incarnation ) );
+                    }
+
+                    outputHandler.handleOutput( id, desc, incarnation,
+                                                groupName,
+                                                child.getInputStream(),
+                                                child.getErrorStream() );
+                    MarshalOutputStream out =
+                        new MarshalOutputStream( child.getOutputStream(),
+                                                 Collections.EMPTY_LIST );
+                    out.writeObject( id );
+                    ActivationGroupDesc gd = desc;
+                    if( gd.getClassName() == null )
+                    {
+                        MarshalledObject data = gd.getData();
+                        if( data == null )
+                        {
+                            data = groupData;
+                        }
+                        String loc = gd.getLocation();
+                        if( loc == null )
+                        {
+                            loc = groupLocation;
+                        }
+                        gd = new ActivationGroupDesc(
+                            "com.sun.jini.phoenix.ActivationGroupImpl",
+                            loc,
+                            data,
+                            gd.getPropertyOverrides(),
+                            gd.getCommandEnvironment() );
+                    }
+                    out.writeObject( gd );
+                    out.writeLong( incarnation );
+                    out.flush();
+                    out.close();
+
+                }
+                catch( Exception e )
+                {
+                    terminate();
+                    if( e instanceof ActivationException )
+                    {
+                        throw (ActivationException) e;
+                    }
+                    else
+                    {
+                        throw new ActivationException(
+                            "unable to create activation group", e );
+                    }
+                }
+                try
+                {
+                    long now = System.currentTimeMillis();
+                    long stop = now + groupTimeout;
+                    do
+                    {
+                        wait( stop - now );
+                        if( group != null )
+                        {
+                            return group;
+                        }
+                        now = System.currentTimeMillis();
+                        // protect against premature return from wait
+                    }
+                    while( status == CREATING && now < stop );
+                }
+                catch( InterruptedException e )
+                {
+                }
+                terminate();
+                throw new ActivationException(
+                    "timeout creating child process" );
+            }
+            finally
+            {
+                if( acquired )
+                {
+                    Vstartgroup();
+                }
+            }
+        }
+
+        /**
+         * Waits for process termination and then restarts services.
+         */
+        private class Watchdog extends Thread
+        {
+            private Process groupProcess = child;
+            private long groupIncarnation = incarnation;
+            private boolean canInterrupt = true;
+            private boolean shouldQuit = false;
+            private boolean shouldRestart = true;
+
+            Watchdog()
+            {
+                super( "Watchdog-" + groupName + "-" + incarnation );
+                setDaemon( true );
+            }
+
+            public void run()
+            {
+                if( shouldQuit )
+                {
+                    return;
+                }
+                /*
+             * Wait for the group to crash or exit.
+             */
+                try
+                {
+                    groupProcess.waitFor();
+                }
+                catch( InterruptedException exit )
+                {
+                    return;
+                }
+
+                boolean restart = false;
+                synchronized( GroupEntry.this )
+                {
+                    if( shouldQuit )
+                    {
+                        return;
+                    }
+                    canInterrupt = false;
+                    interrupted(); // clear interrupt bit
+                    /*
+                  * Since the group crashed, we should
+                  * reset the entry before activating objects
+                  */
+                    if( groupIncarnation == incarnation )
+                    {
+                        restart = shouldRestart && !shuttingDown;
+                        reset();
+                        childGone();
+                    }
+                }
+
+                /*
+             * Activate those objects that require restarting
+             * after a crash.
+             */
+                if( restart )
+                {
+                    restartServices();
+                }
+            }
+
+            /**
+             * Marks this thread as one that is no longer needed.
+             * If the thread is in a state in which it can be interrupted,
+             * then the thread is interrupted.
+             */
+            void dispose()
+            {
+                shouldQuit = true;
+                if( canInterrupt )
+                {
+                    interrupt();
+                }
+            }
+
+            /**
+             * Marks this thread as no longer needing to restart objects.
+             */
+            void noRestart()
+            {
+                shouldRestart = false;
+            }
+        }
+    }
+
+    private String[] activationArgs( ActivationGroupDesc desc )
+    {
+        ActivationGroupDesc.CommandEnvironment cmdenv;
+        cmdenv = desc.getCommandEnvironment();
+
+        // argv is the literal command to exec
+        List argv = new ArrayList();
+
+        // Command name/path
+        argv.add( ( cmdenv != null && cmdenv.getCommandPath() != null )
+                  ? cmdenv.getCommandPath()
+                  : command[ 0 ] );
+
+        // Group-specific command options
+        if( cmdenv != null && cmdenv.getCommandOptions() != null )
+        {
+            argv.addAll( Arrays.asList( cmdenv.getCommandOptions() ) );
+        }
+
+        // Properties become -D parameters
+        Properties props = desc.getPropertyOverrides();
+        if( props != null )
+        {
+            for( Enumeration p = props.propertyNames(); p.hasMoreElements(); )
+            {
+                String name = (String) p.nextElement();
+                /* Note on quoting: it would be wrong
+             * here, since argv will be passed to
+             * Runtime.exec, which should not parse
+             * arguments or split on whitespace.
+             */
+                argv.add( "-D" + name + "=" + props.getProperty( name ) );
+            }
+        }
+
+        // finally, phoenix-global command options and the classname
+        int i;
+        for( i = 1; i < command.length; i++ )
+        {
+            argv.add( command[ i ] );
+        }
+
+        String[] realArgv = new String[argv.size()];
+        System.arraycopy( argv.toArray(), 0, realArgv, 0,
+                          realArgv.length );
+
+        return ( realArgv );
+    }
+
+    private class ObjectEntry implements Serializable
+    {
+        private static final long serialVersionUID = -808474359039620126L;
+        /**
+         * descriptor for object
+         */
+        ActivationDesc desc;
+        /**
+         * the stub (if active)
+         */
+        volatile transient MarshalledWrapper stub = null;
+        volatile transient boolean removed = false;
+
+        ObjectEntry( ActivationDesc desc )
+        {
+            this.desc = desc;
+        }
+
+        synchronized MarshalledWrapper activate( UID uid,
+                                                 boolean force,
+                                                 ActivationInstantiator inst )
+            throws RemoteException, ActivationException
+        {
+            /* stub could be set to null by a concurrent group reset */
+            MarshalledWrapper nstub = stub;
+            if( removed )
+            {
+                throw new UnknownObjectException( "object removed" );
+            }
+            else if( !force && nstub != null )
+            {
+                return nstub;
+            }
+            MarshalledInstance marshalledProxy =
+                new MarshalledInstance( inst.newInstance( getAID( uid ), desc ) );
+            nstub = new MarshalledWrapper( marshalledProxy );
+            stub = nstub;
+            return nstub;
+        }
+
+        void reset()
+        {
+            stub = null;
+        }
+
+        void removed()
+        {
+            removed = true;
+        }
+    }
+
+    /**
+     * Adds a record to the activation log. If the number of updates
+     * passes a predetermined threshold, record a snapshot before
+     * adding the record to the log.
+     */
+    private void addLogRecord( LogRecord rec ) throws ActivationException
+    {
+        assert Thread.holdsLock( logLock );
+
+        checkShutdown();
+        if( numUpdates >= snapshotInterval )
+        {
+            snapshot();
+        }
+        try
+        {
+            log.update( rec, true );
+            numUpdates++;
+        }
+        catch( Exception e )
+        {
+            logger.log( Level.WARNING, "log update throws", e );
+            snapshot();
+        }
+    }
+
+    private void snapshot() throws ActivationException
+    {
+        assert Thread.holdsLock( logLock );
+        try
+        {
+            log.snapshot();
+            numUpdates = 0;
+        }
+        catch( Exception e )
+        {
+            logger.log( Level.SEVERE, "log snapshot throws", e );
+            try
+            {
+                system.shutdown();
+            }
+            catch( RemoteException ignore )
+            {
+                // can't happen
+            }
+            throw new ActivationException( "log snapshot failed", e );
+        }
+    }
+
+    /**
+     * Handler for the log that knows how to take the initial snapshot
+     * and apply an update (a LogRecord) to the current state.
+     */
+    private static class ActLogHandler extends LogHandler
+    {
+
+        private Activation state = null;
+
+        ActLogHandler()
+        {
+        }
+
+        public Activation getState()
+        {
+            return state;
+        }
+
+        public void snapshot( OutputStream out ) throws Exception
+        {
+            if( state == null )
+            {
+                state = new Activation();
+            }
+            MarshalOutputStream s =
+                new MarshalOutputStream( out, Collections.EMPTY_LIST );
+            s.writeObject( state );
+            s.flush();
+        }
+
+        public void recover( InputStream in ) throws Exception
+        {
+            MarshalInputStream s =
+                new MarshalInputStream( in,
+                                        ActLogHandler.class.getClassLoader(),
+                                        false, null, Collections.EMPTY_LIST );
+            s.useCodebaseAnnotations();
+            state = (Activation) s.readObject();
+        }
+
+        public void writeUpdate( OutputStream out, Object value )
+            throws Exception
+        {
+            MarshalOutputStream s =
+                new MarshalOutputStream( out, Collections.EMPTY_LIST );
+            s.writeObject( value );
+            s.flush();
+        }
+
+        public void readUpdate( InputStream in ) throws Exception
+        {
+            MarshalInputStream s =
+                new MarshalInputStream( in,
+                                        ActLogHandler.class.getClassLoader(),
+                                        false, null, Collections.EMPTY_LIST );
+            s.useCodebaseAnnotations();
+            applyUpdate( s.readObject() );
+        }
+
+        public void applyUpdate( Object update ) throws Exception
+        {
+            ( (LogRecord) update ).apply( state );
+        }
+    }
+
+    /**
+     * Abstract class for all log records. The subclass contains
+     * specific update information and implements the apply method
+     * that applies the update information contained in the record
+     * to the current state.
+     */
+    private static abstract class LogRecord implements Serializable
+    {
+
+        private static final long serialVersionUID = 8395140512322687529L;
+
+        abstract Object apply( Object state ) throws Exception;
+    }
+
+    /**
+     * Log record for registering an object.
+     */
+    private static class LogRegisterObject extends LogRecord
+    {
+
+        private static final long serialVersionUID = -6280336276146085143L;
+        private UID uid;
+        private ActivationDesc desc;
+
+        LogRegisterObject( UID uid, ActivationDesc desc )
+        {
+            this.uid = uid;
+            this.desc = desc;
+        }
+
+        Object apply( Object state )
+        {
+            try
+            {
+                ( (Activation) state ).getGroupEntry( desc.getGroupID() ).
+                    registerObject( uid, desc, false );
+            }
+            catch( Exception e )
+            {
+                logger.log( Level.WARNING, "log recovery throws", e );
+            }
+            return state;
+        }
+    }
+
+    /**
+     * Log record for unregistering an object.
+     */
+    private static class LogUnregisterObject extends LogRecord
+    {
+
+        private static final long serialVersionUID = 6269824097396935501L;
+        private UID uid;
+
+        LogUnregisterObject( UID uid )
+        {
+            this.uid = uid;
+        }
+
+        Object apply( Object state )
+        {
+            try
+            {
+                ( (Activation) state ).getGroupEntry( uid ).unregisterObject(
+                    uid, false );
+            }
+            catch( Exception e )
+            {
+                logger.log( Level.WARNING, "log recovery throws", e );
+            }
+            return state;
+        }
+    }
+
+    /**
+     * Log record for registering a group.
+     */
+    private static class LogRegisterGroup extends LogRecord
+    {
+
+        private static final long serialVersionUID = -1966827458515403625L;
+        private ActivationGroupID id;
+        private ActivationGroupDesc desc;
+
+        LogRegisterGroup( ActivationGroupID id, ActivationGroupDesc desc )
+        {
+            this.id = id;
+            this.desc = desc;
+        }
+
+        Object apply( Object state )
+        {
+            // modify state directly
+            // can't ask a nonexistent GroupEntry to register itself
+            ( (Activation) state ).groupTable.put( id, ( (Activation) state ).new
+                GroupEntry( id, desc ) );
+            return state;
+        }
+    }
+
+    /**
+     * Log record for updating an activation desc
+     */
+    private static class LogUpdateDesc extends LogRecord
+    {
+
+        private static final long serialVersionUID = 545511539051179885L;
+
+        private UID uid;
+        private ActivationDesc desc;
+
+        LogUpdateDesc( UID uid, ActivationDesc desc )
+        {
+            this.uid = uid;
+            this.desc = desc;
+        }
+
+        Object apply( Object state )
+        {
+            try
+            {
+                ( (Activation) state ).getGroupEntry( uid ).
+                    setActivationDesc( uid, desc, false );
+            }
+            catch( Exception e )
+            {
+                logger.log( Level.WARNING, "log recovery throws", e );
+            }
+            return state;
+        }
+    }
+
+    /**
+     * Log record for unregistering a group.
+     */
+    private static class LogUpdateGroupDesc extends LogRecord
+    {
+
+        private static final long serialVersionUID = -1271300989218424337L;
+        private ActivationGroupID id;
+        private ActivationGroupDesc desc;
+
+        LogUpdateGroupDesc( ActivationGroupID id, ActivationGroupDesc desc )
+        {
+            this.id = id;
+            this.desc = desc;
+        }
+
+        Object apply( Object state )
+        {
+            try
+            {
+                ( (Activation) state ).getGroupEntry( id ).
+                    setActivationGroupDesc( id, desc, false );
+            }
+            catch( Exception e )
+            {
+                logger.log( Level.WARNING, "log recovery throws", e );
+            }
+            return state;
+        }
+    }
+
+    /**
+     * Log record for unregistering a group.
+     */
+    private static class LogUnregisterGroup extends LogRecord
+    {
+
+        private static final long serialVersionUID = -3356306586522147344L;
+        private ActivationGroupID id;
+
+        LogUnregisterGroup( ActivationGroupID id )
+        {
+            this.id = id;
+        }
+
+        Object apply( Object state )
+        {
+            GroupEntry entry = (GroupEntry)
+                ( (Activation) state ).groupTable.remove( id );
+            try
+            {
+                entry.unregisterGroup( false );
+            }
+            catch( Exception e )
+            {
+                logger.log( Level.WARNING, "log recovery throws", e );
+            }
+            return state;
+        }
+    }
+
+    /**
+     * Log record for an active group incarnation
+     */
+    private static class LogGroupIncarnation extends LogRecord
+    {
+
+        private static final long serialVersionUID = 4146872747377631897L;
+        private ActivationGroupID id;
+        private long inc;
+
+        LogGroupIncarnation( ActivationGroupID id, long inc )
+        {
+            this.id = id;
+            this.inc = inc;
+        }
+
+        Object apply( Object state )
+        {
+            try
+            {
+                GroupEntry entry = ( (Activation) state ).getGroupEntry( id );
+                entry.incarnation = inc;
+            }
+            catch( Exception e )
+            {
+                logger.log( Level.WARNING, "log recovery throws", e );
+            }
+            return state;
+        }
+    }
+
+    private static void usage()
+    {
+        System.err.println(
+            MessageFormat.format( getTextResource( "phoenix.usage" ),
+                                  new String[]{ Activation.class.getName() } ) );
+        System.exit( 1 );
+    }
+
+    private static void bomb( String error )
+    {
+        System.err.println( "phoenix: " + error ); // $NON-NLS$
+        usage();
+    }
+
+    private static void bomb( String res, String val )
+    {
+        bomb( MessageFormat.format( getTextResource( res ), new String[]{ val } ) );
+    }
+
+    /**
+     * Starts phoenix. See the
+     * <a href="package-summary.html#package_description">package
+     * documentation</a> for details.
+     *
+     * @param args command line options
+     */
+    public static void main( String[] args )
+    {
+        if( System.getSecurityManager() == null )
+        {
+            System.setSecurityManager( new SecurityManager() );
+        }
+        boolean stop = false;
+        if( args.length > 0 && args[ 0 ].equals( "-stop" ) )
+        {
+            stop = true;
+            String[] nargs = new String[args.length - 1];
+            System.arraycopy( args, 1, nargs, 0, nargs.length );
+            args = nargs;
+        }
+        else if( args.length == 1 && args[ 0 ].equals( "-help" ) )
+        {
+            usage();
+        }
+        try
+        {
+            main( args, stop, null );
+        }
+        catch( Exception e )
+        {
+            System.err.println( MessageFormat.format(
+                getTextResource( "phoenix.unexpected.exception" ),
+                new Object[]{ e.getMessage() } ) );
+            e.printStackTrace();
+            System.exit( 1 );
+        }
+    }
+
+    /**
+     * Returns the ActivationSystem proxy (used by the PhoenixStarter).
+     */
+    ActivationSystem getActivationSystemProxy()
+    {
+        return systemStub;
+    }
+
+    /**
+     * Starts phoenix and returns a reference to the recovered
+     * <code>Activation</code> instance.
+     *
+     * @param configOptions the configuration options for the configuration
+     * @param stop          if <code>true</code>, initiates shutdown of the
+     *                      activation system on the "registryHost" and "registryPort" obtained
+     *                      from the configuration
+     * @param starter       the <code>PhoenixStarter</code> instance, or
+     *                      <code>null</code>
+     */
+    static Activation main( final String[] configOptions,
+                            final boolean stop,
+                            final PhoenixStarter starter )
+        throws Exception
+    {
+        final Configuration config = getConfig( configOptions, starter );
+        final LoginContext login =
+            (LoginContext) config.getEntry( PHOENIX, "loginContext",
+                                            LoginContext.class, null );
+        if( login != null )
+        {
+            login.login();
+        }
+        PrivilegedExceptionAction action = new PrivilegedExceptionAction()
+        {
+            public Object run() throws Exception
+            {
+                if( stop )
+                {
+                    assert starter == null;
+                    shutdown( config );
+                    System.exit( 0 );
+                }
+                String logName = null;
+                try
+                {
+                    logName = (String) config.getEntry(
+                        PHOENIX, "persistenceDirectory", String.class );
+                }
+                catch( NoSuchEntryException e )
+                {
+                    if( starter == null )
+                    {
+                        bomb( "phoenix.missing.log", null );
+                    }
+                    else
+                    {
+                        throw e;
+                    }
+                }
+                ActLogHandler handler = new ActLogHandler();
+                ReliableLog log = new ReliableLog( logName, handler );
+                log.recover();
+                Activation state = handler.getState();
+                if( state == null )
+                {
+                    log.snapshot();
+                    state = handler.getState();
+                }
+                state.init( log, login, config, configOptions, starter );
+                if( starter == null )
+                {
+                    // prevent exit
+                    while( true )
+                    {
+                        try
+                        {
+                            Thread.sleep( Long.MAX_VALUE );
+                        }
+                        catch( InterruptedException e )
+                        {
+                        }
+                    }
+                }
+                else
+                {
+                    return state;
+                }
+            }
+        };
+        if( login != null )
+        {
+            return (Activation)
+                Subject.doAsPrivileged( login.getSubject(), action, null );
+        }
+        else
+        {
+            return (Activation) action.run();
+        }
+    }
+
+    /**
+     * Shut down an activation system daemon, using the specified
+     * configuration location to obtain the host and port of the daemon's
+     * registry, the client constraints for the remote call, and the
+     * permissions to grant to the system proxy.
+     */
+    private static void shutdown( Configuration config ) throws Exception
+    {
+        String host = (String) config.getEntry( PHOENIX, "registryHost",
+                                                String.class, null );
+        int port = getInt( config, "registryPort",
+                           ActivationSystem.SYSTEM_PORT );
+        Registry reg = LocateRegistry.getRegistry( host, port );
+        ActivationSystem sys =
+            (ActivationSystem) reg.lookup( ActivationSystem.class.getName() );
+        ProxyPreparer sysPreparer = getPreparer( config, "systemPreparer" );
+        sys = (ActivationSystem) sysPreparer.prepareProxy( sys );
+        sys.shutdown();
+    }
+
+    /**
+     * Retrieves text resources from the locale-specific properties file.
+     */
+    static String getTextResource( String key )
+    {
+        if( resources == null )
+        {
+            try
+            {
+                resources = ResourceBundle.getBundle(
+                    "com.sun.jini.phoenix.resources.phoenix" );
+            }
+            catch( MissingResourceException mre )
+            {
+                return "[missing resource file: " + key + "]";
+            }
+        }
+
+        try
+        {
+            return resources.getString( key );
+        }
+        catch( MissingResourceException mre )
+        {
+            return "[missing resource: " + key + "]";
+        }
+    }
+
+    /*
+     * Dijkstra semaphore operations to limit the number of subprocesses
+     * phoenix attempts to make at once.
+     */
+
+    /**
+     * Acquire the group semaphore and return a group name.  Each
+     * Pstartgroup must be followed by a Vstartgroup.  The calling thread
+     * will wait until there are fewer than <code>N</code> other threads
+     * holding the group semaphore.  The calling thread will then acquire
+     * the semaphore and return.
+     */
+    private synchronized String Pstartgroup() throws ActivationException
+    {
+        while( true )
+        {
+            checkShutdown();
+            // Wait until positive, then decrement.
+            if( groupSemaphore > 0 )
+            {
+                groupSemaphore--;
+                return "Group-" + groupCounter++;
+            }
+            try
+            {
+                wait();
+            }
+            catch( InterruptedException e )
+            {
+            }
+        }
+    }
+
+    /**
+     * Release the group semaphore.  Every P operation must be
+     * followed by a V operation.  This may cause another thread to
+     * wake up and return from its P operation.
+     */
+    private synchronized void Vstartgroup()
+    {
+        // Increment and notify a waiter (not necessarily FIFO).
+	groupSemaphore++;
+	notifyAll();
+    }
+}

Added: incubator/river/jtsk/skunk/niclas1/services/phoenix/src/main/java/com/sun/jini/phoenix/ActivationAdmin.java
URL: http://svn.apache.org/viewvc/incubator/river/jtsk/skunk/niclas1/services/phoenix/src/main/java/com/sun/jini/phoenix/ActivationAdmin.java?rev=724976&view=auto
==============================================================================
--- incubator/river/jtsk/skunk/niclas1/services/phoenix/src/main/java/com/sun/jini/phoenix/ActivationAdmin.java (added)
+++ incubator/river/jtsk/skunk/niclas1/services/phoenix/src/main/java/com/sun/jini/phoenix/ActivationAdmin.java Tue Dec  9 21:12:27 2008
@@ -0,0 +1,69 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.sun.jini.phoenix;
+
+import java.rmi.Remote;
+import java.rmi.RemoteException;
+import java.rmi.activation.ActivationGroupID;
+import java.rmi.activation.UnknownGroupException;
+import java.util.Map;
+
+/**
+ * An administrative interface for the activation system daemon. This
+ * interface is implemented directly by the same proxy that implements
+ * {@link java.rmi.activation.ActivationSystem}.
+ *
+ * @author Sun Microsystems, Inc.
+ * @since 2.0
+ */
+public interface ActivationAdmin extends Remote
+{
+    /**
+     * Returns a map from {@link ActivationGroupID} to
+     * {@link java.rmi.activation.ActivationGroupDesc} for all registered
+     * activation groups. The map contains a snapshot of the state at
+     * the time of the call; subsequent state changes are not reflected
+     * in the map, nor do changes in the map cause changes in the state
+     * of the activation system daemon.
+     *
+     * @return a map from <code>ActivationGroupID</code> to
+     *         <code>ActivationGroupDesc</code> for all registered activation groups
+     * @throws RemoteException if a communication-related exception occurs
+     */
+    Map getActivationGroups() throws RemoteException;
+
+    /**
+     * Returns a map from {@link java.rmi.activation.ActivationID} to
+     * {@link java.rmi.activation.ActivationDesc} for all activatable objects
+     * registered in the group registered under the specified activation
+     * group identifier. The map contains a snapshot of the state at
+     * the time of the call; subsequent state changes are not reflected
+     * in the map, nor do changes in the map cause changes in the state
+     * of the activation system daemon.
+     *
+     * @param id activation group identifier
+     * @return a map from <code>ActivationID</code> to
+     *         <code>ActivationDesc</code> for all activatable objects registered in
+     *         the group registered under the specified activation group identifier
+     * @throws UnknownGroupException if no group is registered under the
+     *                               specified activation group identifier
+     * @throws RemoteException       if a communication-related exception occurs
+     */
+    Map getActivatableObjects( ActivationGroupID id )
+        throws UnknownGroupException, RemoteException;
+}

Added: incubator/river/jtsk/skunk/niclas1/services/phoenix/src/main/java/com/sun/jini/phoenix/ActivationGroupData.java
URL: http://svn.apache.org/viewvc/incubator/river/jtsk/skunk/niclas1/services/phoenix/src/main/java/com/sun/jini/phoenix/ActivationGroupData.java?rev=724976&view=auto
==============================================================================
--- incubator/river/jtsk/skunk/niclas1/services/phoenix/src/main/java/com/sun/jini/phoenix/ActivationGroupData.java (added)
+++ incubator/river/jtsk/skunk/niclas1/services/phoenix/src/main/java/com/sun/jini/phoenix/ActivationGroupData.java Tue Dec  9 21:12:27 2008
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ * 
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.sun.jini.phoenix;
+
+import java.io.Serializable;
+
+/**
+ * Initialization data for {@link ActivationGroupImpl}, to control the
+ * activation group's configuration. An instance of this is specified
+ * in the <code>ActivationGroupDesc</code> for the group.
+ *
+ * @author Sun Microsystems, Inc.
+ * @since 2.0
+ */
+public class ActivationGroupData implements Serializable
+{
+    private static final long serialVersionUID = 1135616886784731068L;
+
+    /**
+     * The configuration options.
+     *
+     * @serial
+     */
+    private final String[] config;
+
+    /**
+     * Constructs an instance with the specified
+     * {@link net.jini.config.Configuration} options, if any.
+     *
+     * @param config the configuration options, or <code>null</code>
+     */
+    public ActivationGroupData( String[] config )
+    {
+        this.config = ( config == null ? null : (String[]) config.clone() );
+    }
+
+    /**
+     * Returns the configuration options, or <code>null</code>.
+     *
+     * @return the configuration options, or <code>null</code>
+     */
+    public String[] getConfig()
+    {
+        return config == null ? null : (String[]) config.clone();
+    }
+}