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:16:43 UTC

svn commit: r724986 [4/7] - in /incubator/river/jtsk/skunk/niclas1/services/mercury: ./ 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/mercury/ src/main/resources/ src/main...

Added: incubator/river/jtsk/skunk/niclas1/services/mercury/src/main/java/com/sun/jini/mercury/MailboxProxy.java
URL: http://svn.apache.org/viewvc/incubator/river/jtsk/skunk/niclas1/services/mercury/src/main/java/com/sun/jini/mercury/MailboxProxy.java?rev=724986&view=auto
==============================================================================
--- incubator/river/jtsk/skunk/niclas1/services/mercury/src/main/java/com/sun/jini/mercury/MailboxProxy.java (added)
+++ incubator/river/jtsk/skunk/niclas1/services/mercury/src/main/java/com/sun/jini/mercury/MailboxProxy.java Tue Dec  9 21:16:33 2008
@@ -0,0 +1,305 @@
+/*
+ * 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.mercury;
+
+import java.io.IOException;
+import java.io.InvalidObjectException;
+import java.io.ObjectInputStream;
+import java.io.ObjectStreamException;
+import java.io.Serializable;
+import java.rmi.RemoteException;
+import net.jini.admin.Administrable;
+import net.jini.core.constraint.MethodConstraints;
+import net.jini.core.constraint.RemoteMethodControl;
+import net.jini.core.lease.Lease;
+import net.jini.core.lease.LeaseDeniedException;
+import net.jini.event.MailboxPullRegistration;
+import net.jini.event.MailboxRegistration;
+import net.jini.event.PullEventMailbox;
+import net.jini.export.proxytrust.ProxyTrustIterator;
+import net.jini.export.proxytrust.SingletonProxyTrustIterator;
+import net.jini.id.ReferentUuid;
+import net.jini.id.ReferentUuids;
+import net.jini.id.Uuid;
+
+/**
+ * A MailboxProxy is a proxy for the event mailbox service.
+ * This is the object passed to clients of this service.
+ * It implements the <code>PullEventMailbox</code> and the
+ * <code>Administrable</code> interfaces.
+ *
+ * @author Sun Microsystems, Inc.
+ * @since 1.1
+ */
+class MailboxProxy implements PullEventMailbox,
+                              Administrable, Serializable, ReferentUuid
+{
+
+    private static final long serialVersionUID = 2L;
+
+    /**
+     * The reference to the event mailbox service implementation
+     *
+     * @serial
+     */
+    final MailboxBackEnd mailbox;
+
+    /**
+     * The proxy's <code>Uuid</code>
+     *
+     * @serial
+     */
+    final Uuid proxyID;
+
+    /**
+     * Creates a mailbox proxy, returning an instance
+     * that implements RemoteMethodControl if the server does too.
+     *
+     * @param mailbox the server proxy
+     * @param id      the ID of the server
+     */
+    static MailboxProxy create( MailboxBackEnd mailbox, Uuid id )
+    {
+        if( mailbox instanceof RemoteMethodControl )
+        {
+            return new ConstrainableMailboxProxy( mailbox, id, null );
+        }
+        else
+        {
+            return new MailboxProxy( mailbox, id );
+        }
+    }
+
+    /**
+     * Convenience constructor.
+     */
+    private MailboxProxy( MailboxBackEnd mailbox, Uuid proxyID )
+    {
+        if( mailbox == null || proxyID == null )
+        {
+            throw new IllegalArgumentException( "Cannot accept null arguments" );
+        }
+        this.mailbox = mailbox;
+        this.proxyID = proxyID;
+    }
+
+    // inherit javadoc from parent
+    public MailboxRegistration register( long duration )
+        throws RemoteException, LeaseDeniedException
+    {
+        // Check for a bad argument
+        // Note that -1 (i.e. Lease.ANY) is a valid request
+        if( duration < 1 && duration != Lease.ANY )
+        {
+            throw new IllegalArgumentException(
+                "Duration values must be positive" );
+        }
+        return mailbox.register( duration );
+    }
+
+    // inherit javadoc from parent
+    public MailboxPullRegistration pullRegister( long duration )
+        throws RemoteException, LeaseDeniedException
+    {
+        // Check for a bad argument
+        // Note that -1 (i.e. Lease.ANY) is a valid request
+        if( duration < 1 && duration != Lease.ANY )
+        {
+            throw new IllegalArgumentException(
+                "Duration values must be positive" );
+        }
+        return mailbox.pullRegister( duration );
+    }
+
+    // inherit javadoc from parent
+    public Object getAdmin() throws RemoteException
+    {
+        return mailbox.getAdmin();
+    }
+
+    /* From net.jini.id.ReferentUuid */
+    /**
+     * Returns the universally unique identifier that has been assigned to the
+     * resource this proxy represents.
+     *
+     * @return the instance of <code>Uuid</code> that is associated with the
+     *         resource this proxy represents. This method will not return
+     *         <code>null</code>.
+     * @see net.jini.id.ReferentUuid
+     */
+    public Uuid getReferentUuid()
+    {
+        return proxyID;
+    }
+
+    /**
+     * Proxies for servers with the same proxyID have the same hash code.
+     */
+    public int hashCode()
+    {
+        return proxyID.hashCode();
+    }
+
+    /**
+     * Proxies for servers with the same <code>proxyID</code> are
+     * considered equal.
+     */
+    public boolean equals( Object o )
+    {
+        return ReferentUuids.compare( this, o );
+    }
+
+    /**
+     * When an instance of this class is deserialized, this method is
+     * automatically invoked. This implementation of this method validates
+     * the state of the deserialized instance.
+     *
+     * @throws <code>InvalidObjectException</code>
+     *          if the state of the
+     *          deserialized instance of this class is found to be invalid.
+     */
+    private void readObject( ObjectInputStream s )
+        throws IOException, ClassNotFoundException
+    {
+        s.defaultReadObject();
+        /* Verify server */
+        if( mailbox == null )
+        {
+            throw new InvalidObjectException( "MailboxProxy.readObject "
+                                              + "failure - mailbox "
+                                              + "field is null" );
+        }//endif
+        /* Verify proxyID */
+        if( proxyID == null )
+        {
+            throw new InvalidObjectException( "MailboxProxy.proxyID "
+                                              + "failure - proxyID "
+                                              + "field is null" );
+        }//endif
+    }//end readObject
+
+    /**
+     * During deserialization of an instance of this class, if it is found
+     * that the stream contains no data, this method is automatically
+     * invoked. Because it is expected that the stream should always
+     * contain data, this implementation of this method simply declares
+     * that something must be wrong.
+     *
+     * @throws <code>InvalidObjectException</code>
+     *          to indicate that there
+     *          was no data in the stream during deserialization of an
+     *          instance of this class; declaring that something is wrong.
+     */
+    private void readObjectNoData() throws ObjectStreamException
+    {
+        throw new InvalidObjectException( "no data found when attempting to "
+                                          + "deserialize MailboxProxy instance" );
+    }//end readObjectNoData
+
+
+    /**
+     * A subclass of MailboxProxy that implements RemoteMethodControl.
+     */
+    final static class ConstrainableMailboxProxy extends MailboxProxy
+        implements RemoteMethodControl
+    {
+        private static final long serialVersionUID = 1L;
+
+        /**
+         * Creates an instance of this class.
+         */
+        private ConstrainableMailboxProxy( MailboxBackEnd mailbox, Uuid uuid,
+                                           MethodConstraints methodConstraints )
+        {
+            super( constrainServer( mailbox, methodConstraints ),
+                   uuid );
+        }
+
+        /**
+         * Returns a copy of the server proxy with the specified client
+         * constraints and methods mapping.
+         */
+        private static MailboxBackEnd constrainServer(
+            MailboxBackEnd mailbox,
+            MethodConstraints methodConstraints )
+        {
+            return (MailboxBackEnd)
+                ( (RemoteMethodControl) mailbox ).setConstraints( methodConstraints );
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public RemoteMethodControl setConstraints(
+            MethodConstraints constraints )
+        {
+            return new ConstrainableMailboxProxy( mailbox, proxyID,
+                                                  constraints );
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public MethodConstraints getConstraints()
+        {
+            return ( (RemoteMethodControl) mailbox ).getConstraints();
+        }
+
+        /* Note that the superclass's hashCode method is OK as is. */
+        /* Note that the superclass's equals method is OK as is. */
+
+        /**
+         * Returns a proxy trust iterator that is used in
+         * <code>ProxyTrustVerifier</code> to retrieve this object's
+         * trust verifier.
+         */
+        private ProxyTrustIterator getProxyTrustIterator()
+        {
+            return new SingletonProxyTrustIterator( mailbox );
+        }//end getProxyTrustIterator
+
+        /**
+         * Performs various functions related to the trust verification
+         * process for the current instance of this proxy class, as
+         * detailed in the description for this class.
+         *
+         * @throws <code>InvalidObjectException</code>
+         *          if any of the
+         *          requirements for trust verification (as detailed in the
+         *          class description) are not satisfied.
+         */
+        private void readObject( ObjectInputStream s )
+            throws IOException, ClassNotFoundException
+        {
+            /* Note that basic validation of the fields of this class was
+            * already performed in the readObject() method of this class'
+            * super class.
+            */
+            s.defaultReadObject();
+            // Verify that the server implements RemoteMethodControl
+            if( !( mailbox instanceof RemoteMethodControl ) )
+            {
+                throw new InvalidObjectException(
+                    "MailboxAdminProxy.readObject failure - mailbox " +
+                    "does not implement constrainable functionality " );
+            }//endif
+        }//end readObject 
+
+
+    }
+}

Added: incubator/river/jtsk/skunk/niclas1/services/mercury/src/main/java/com/sun/jini/mercury/MercuryPermission.java
URL: http://svn.apache.org/viewvc/incubator/river/jtsk/skunk/niclas1/services/mercury/src/main/java/com/sun/jini/mercury/MercuryPermission.java?rev=724986&view=auto
==============================================================================
--- incubator/river/jtsk/skunk/niclas1/services/mercury/src/main/java/com/sun/jini/mercury/MercuryPermission.java (added)
+++ incubator/river/jtsk/skunk/niclas1/services/mercury/src/main/java/com/sun/jini/mercury/MercuryPermission.java Tue Dec  9 21:16:33 2008
@@ -0,0 +1,61 @@
+/*
+ * 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.mercury;
+
+import net.jini.security.AccessPermission;
+
+/**
+ * Represents permissions that can be used to express the access control policy
+ * for the Mercury server exported with a
+ * {@link net.jini.jeri.BasicJeriExporter}. This class
+ * can be passed to {@link net.jini.jeri.BasicInvocationDispatcher},
+ * and then used in
+ * security policy permission grants. <p>
+ *
+ * An instance contains a name (also referred to as a "target name") but no
+ * actions list; you either have the named permission or you don't. The
+ * convention is that the target name is the non-qualified name of the remote
+ * method being invoked. Wildcard matches are supported using the syntax
+ * specified by {@link AccessPermission}. <p>
+ *
+ * The possible target names for use with a Mercury server are specified in the
+ * package documentation for {@link com.sun.jini.mercury}.
+ *
+ * @author Sun Microsystems, Inc.
+ * @since 2.0
+ */
+public class MercuryPermission extends AccessPermission
+{
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Creates an instance with the specified target name.
+     *
+     * @param name the target name
+     * @throws NullPointerException     if the target name is <code>null</code>
+     * @throws IllegalArgumentException if the target name does not match
+     *                                  the syntax specified in the comments at the beginning of the {@link
+     *                                  AccessPermission} class
+     */
+    public MercuryPermission( String name )
+    {
+        super( name );
+    }
+}

Added: incubator/river/jtsk/skunk/niclas1/services/mercury/src/main/java/com/sun/jini/mercury/NonActivatableMercuryImpl.java
URL: http://svn.apache.org/viewvc/incubator/river/jtsk/skunk/niclas1/services/mercury/src/main/java/com/sun/jini/mercury/NonActivatableMercuryImpl.java?rev=724986&view=auto
==============================================================================
--- incubator/river/jtsk/skunk/niclas1/services/mercury/src/main/java/com/sun/jini/mercury/NonActivatableMercuryImpl.java (added)
+++ incubator/river/jtsk/skunk/niclas1/services/mercury/src/main/java/com/sun/jini/mercury/NonActivatableMercuryImpl.java Tue Dec  9 21:16:33 2008
@@ -0,0 +1,56 @@
+/*
+ * 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.mercury;
+
+import com.sun.jini.start.LifeCycle;
+
+/**
+ * Convenience class intended for use with the
+ * {@link com.sun.jini.start.ServiceStarter} framework to start
+ * an implementation of Mercury that is not activatable, but which
+ * will log its state information to persistent storage.
+ *
+ * @author Sun Microsystems, Inc.
+ * @since 2.0
+ */
+class NonActivatableMercuryImpl extends MailboxImpl
+{
+
+    /**
+     * Constructs a new instance of <code>MailboxImpl</code> that is not
+     * activatable, but which will persist its state.
+     *
+     * @param configArgs <code>String</code> array whose elements are
+     *                   the arguments to use when creating the server.
+     * @param lifeCycle  instance of <code>LifeCycle</code> that, if
+     *                   non-<code>null</code>, will cause this object's
+     *                   <code>unregister</code> method to be invoked during
+     *                   shutdown to notify the service starter framework that
+     *                   the reference to this service's implementation can be
+     *                   'released' for garbage collection. A value of
+     *                   <code>null</code> for this argument is allowed.
+     * @throws Exception If there was a problem initializing the service.
+     */
+    NonActivatableMercuryImpl( String[] configArgs, LifeCycle lifeCycle )
+        throws Exception
+    {
+        super( configArgs, lifeCycle, true );//true ==> persistent
+    }//end constructor
+
+}//end class NonActivatableMercuryImpl
+

Added: incubator/river/jtsk/skunk/niclas1/services/mercury/src/main/java/com/sun/jini/mercury/PersistentEventLog.java
URL: http://svn.apache.org/viewvc/incubator/river/jtsk/skunk/niclas1/services/mercury/src/main/java/com/sun/jini/mercury/PersistentEventLog.java?rev=724986&view=auto
==============================================================================
--- incubator/river/jtsk/skunk/niclas1/services/mercury/src/main/java/com/sun/jini/mercury/PersistentEventLog.java (added)
+++ incubator/river/jtsk/skunk/niclas1/services/mercury/src/main/java/com/sun/jini/mercury/PersistentEventLog.java Tue Dec  9 21:16:33 2008
@@ -0,0 +1,1237 @@
+/*
+ * 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.mercury;
+
+import com.sun.jini.logging.Levels;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.util.ArrayList;
+import java.util.NoSuchElementException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import net.jini.core.event.RemoteEvent;
+import net.jini.id.Uuid;
+
+/**
+ * Class that implements the interface for an <tt>EventLog</tt>.
+ * This class encapsulates the details of reading/writing events from/to
+ * some underlying persistence mechanism.
+ *
+ * This class makes certain assumptions. First, the <tt>next</tt> and
+ * <tt>remove</tt> methods are intended to be called in pairs. If
+ * <tt>remove</tt> is not called, then subsequent calls to <tt>next</tt>
+ * will attempt to return the same object. Calling <tt>remove</tt>
+ * essentially advances the read pointer to the next object, if any.
+ * Second, if any <tt>IOExceptions</tt> are encountered during the reading
+ * or writing of an event the associated read/write pointer is advanced
+ * past the offending event. This means that events can be lost if I/O
+ * errors are encountered.
+ *
+ * @author Sun Microsystems, Inc.
+ * @since 2.0
+ */
+
+/*
+Implementation details:
+
+Event state information is maintained by a control log file 
+which maintains the current read and write counts/offsets.  This file
+is updated whenever next(), add() or remove() is called.  This file used to 
+initialize the state of the EventLog, if it exists, during the 
+initialization process.
+
+Events are logged into log files that maintain a fixed # of objects
+per file.  The writing process adds events to a log file until this limit
+is reached. A new log file is then created to handle the next set of events. 
+The reading process will delete a log file once all the contained events 
+have been successfully processed.  This serves as the garbage 
+collection mechanism. Note that the actual number of events per log is a 
+user-configurable environmental parameter via the 
+com.sun.jini.mercury.eventsPerLog property (default = 10).
+
+This class also employs the services of a StreamPool, which limits the
+number of concurrently open file descriptors. This limit is also a user
+configurable value via the com.sun.jini.mercury.streamPoolSize property 
+(default = 10).
+
+Event state is kept separate from the service's registration state in 
+order to keep the event logging implementation as flexible as possible.
+*/
+
+class PersistentEventLog implements EventLog
+{
+
+    //
+    // Class fields
+    //
+
+    /**
+     * <tt>Logger</tt> used for persistence-related debugging messages
+     */
+    private static final Logger persistenceLogger =
+        MailboxImpl.persistenceLogger;
+
+    /**
+     * Size of control data file: 4 longs * 8 bytes per long
+     */
+    private static final int CTLBLOCK_LEN = 8 * 4;
+
+    /**
+     * File suffix for the control file
+     */
+    private static final String CTLFILE_SUFFIX = "ctl";
+
+    /**
+     * File suffix for the log files
+     */
+    private static final String LOGFILE_SUFFIX = "log";
+
+    /**
+     * Default number of events per log file, if not overriden by the user
+     */
+    private static final long DEFAULT_EVENTS_PER_LOGFILE = 10L;
+
+    /**
+     * Maximum number of event objects per log file. The
+     * default is used unless overridden by <tt>eventsPerLogProperty</tt>.
+     */
+    // Should be final but the compiler complains.
+//TODO - make eventsPerLogFile & maxPoolSize configurable
+/*
+ * Notes: this probably needs to remain constant over service lifetime. Therefore,
+ * this really needs to be treated as an "initial value that persisted and recovered.
+ * Would like to piggy back off MailboxImpl recovery logic, but 1) it needs to recover
+ * first in order to determine "first time up" and 2) seems logically better to keep
+ * these parameters local to EventLog. Will probably need to develop EventLog recovery
+ * logic for this purpose.
+ */
+    private static final long eventsPerLogFile = DEFAULT_EVENTS_PER_LOGFILE;
+
+    /**
+     * Default size for the stream pool, if not overriden by the user
+     */
+    static final int DEFAULT_STREAM_POOL_SIZE = 10;
+
+    /**
+     * Maximum limit for the number of concurrent <tt>LogStream</tt>s
+     * that this pool will concurrently manage. The
+     * default is used unless overridden by <tt>streamPoolSizeProperty</tt>.
+     */
+    private static final int maxPoolSize = DEFAULT_STREAM_POOL_SIZE;
+
+    /**
+     * The <tt>StreamPool</tt> reference to be used for all
+     * instances of <tt>EventLog</tt>.
+     */
+    // Should be final but the compiler complains.
+    private static final StreamPool streamPool =
+        new StreamPool( maxPoolSize );
+
+    //
+    // Object fields
+    //
+
+    /**
+     * The associated <tt>Uuid</tt> for this <tt>EventLog</tt>.
+     */
+    private Uuid uuid = null;
+
+    /**
+     * The current number of written events.
+     */
+    private long wcount = 0;
+
+    /**
+     * The current number of read events.
+     */
+    private long rcount = 0;
+
+    /**
+     * The current write offset into the current "write" log.
+     */
+    private long wpos = 0;
+
+    /**
+     * The read offset into the current "read" log of the
+     * "last read" object.
+     */
+    private long rpos = 0;
+
+    /**
+     * The read offset of the next event. This gets updated to
+     * <tt>rpos</tt> once <tt>remove</tt> is called (indicating
+     * that the last event read was successful).
+     */
+    private long nextReadPos = 0;
+
+    /**
+     * The <tt>EventReader</tt> used to retrieve events.
+     */
+    private EventReader eventReader;
+
+    /**
+     * The <tt>EventWriter</tt> used to store events
+     */
+    private EventWriter eventWriter;
+
+    /**
+     * The <tt>File</tt> object of the event persistence directory
+     */
+    private File logDir;
+
+    /**
+     * The <tt>File</tt> object that will maintain the control data for
+     * for this <tt>EventLog</tt>.
+     */
+    private File controlFile;
+
+    /**
+     * The in memory buffer that holds the control data
+     */
+    private byte[] ctlbuf = new byte[CTLBLOCK_LEN];
+
+    /**
+     * Flag that is used to determine whether or not this object
+     * has been initialized.
+     */
+    private boolean initialized = false;
+
+    /**
+     * Flag that is used to determine whether or not this object
+     * has been closed.
+     */
+    private boolean closed = false;
+
+    private static final boolean debugState = false;
+
+    /**
+     * Simple constructor that takes a <tt>Uuid</tt> argument
+     * and a <tt>File</tt> argument.  These arguments are simply
+     * assigned to the appropriate internal fields.
+     *
+     * @throws IllegalArgumentException if any of the arguments are null
+     */
+    PersistentEventLog( Uuid uuid, File logDir )
+    {
+        if( logDir == null || uuid == null )
+        {
+            throw new IllegalArgumentException( "Arguments cannot be null" );
+        }
+        this.uuid = uuid;
+        this.logDir = logDir;
+
+        if( persistenceLogger.isLoggable( Level.FINEST ) )
+        {
+            persistenceLogger.log( Level.FINEST,
+                                   "PersistentEventLog for: {0}", uuid );
+        }
+    }
+
+    // Inherit documentation from supertype
+    public void init() throws IOException
+    {
+
+        if( initialized )
+        {
+            throw new InternalMailboxException(
+                "Trying to re-initialize control data "
+                + "for: " + uuid );
+        }
+
+        try
+        {
+            if( !logDir.exists() ) // Create log directory if it doesn't exist
+            {
+                logDir.mkdirs();
+            }
+
+            if( !logDir.isDirectory() ) // Verify that logDir is a directory
+            {
+                throw new FileNotFoundException( logDir.toString()
+                                                 + " is not a directory" );
+            }
+
+            controlFile = getControlFile();
+            if( controlFile.isFile() )
+            { // Recover state from existing file
+                if( persistenceLogger.isLoggable( Level.FINEST ) )
+                {
+                    persistenceLogger.log( Level.FINEST,
+                                           "EventLog::init() recovering data for ",
+                                           uuid );
+                }
+                readControlFile();
+            }
+            else
+            {
+                if( persistenceLogger.isLoggable( Level.FINEST ) )
+                {
+                    persistenceLogger.log( Level.FINEST,
+                                           "default initialization for ", uuid );
+                }
+            }
+
+            eventReader = new EventReader();
+            eventWriter = new EventWriter();
+
+        }
+        finally
+        {
+        }
+
+        printControlData( persistenceLogger, "After EventLog::init" );
+
+        initialized = true;
+
+        if( debugState )
+        {
+            assertInvariants();
+        }
+    }
+
+    // Inherit documentation from supertype
+    public void add( RemoteEvent event ) throws IOException
+    {
+
+        stateCheck();
+
+        if( debugState )
+        {
+            assertInvariants();
+        }
+
+        long logNum = getLogNum( wcount );
+        File log = getLogFile( logNum );
+        LogOutputStream out = null;
+        try
+        {
+            // Get output stream
+            out = streamPool.getLogOutputStream( log, wpos );
+
+            // Write the event
+            eventWriter.write( event, out );
+
+            // Update the control data
+            wpos = out.getOffset();
+            ++wcount;
+        }
+        catch( IOException ioe )
+        {
+            // We'll get interrupted when asked to shutdown.
+            // In this case, we can skip the call to nextWriteLog.
+            if( ioe instanceof InterruptedIOException )
+            {
+                if( persistenceLogger.isLoggable( Level.FINEST ) )
+                {
+                    persistenceLogger.log( Level.FINEST,
+                                           "EventLog::add() interrupted " );
+                }
+            }
+            else
+            {
+                if( persistenceLogger.isLoggable( Level.FINEST ) )
+                {
+                    persistenceLogger.log( Level.FINEST,
+                                           "EventLog::add() received IOException "
+                                           + "... skipping to next write log" );
+                }
+                nextWriteLog();
+            }
+            if( persistenceLogger.isLoggable( Level.FINEST ) )
+            {
+                persistenceLogger.log( Level.FINEST,
+                                       "Exception: ", ioe );
+            }
+            throw (IOException) ioe.fillInStackTrace();
+        }
+        finally
+        {
+            // Always make sure to release any obtained resources
+            if( out != null )
+            {
+                if( getLogNum( wcount ) == logNum )
+                {
+                    streamPool.releaseLogStream( out );
+                }
+                else
+                {
+                    // Done writing to this particular log file, so
+                    // remove it.
+                    streamPool.removeLogStream( out );
+                    wpos = 0;
+                }
+            }
+            // Write control data before returning
+            // TODO (FCS) - handle potential IOException
+            writeControlFile();
+        }
+
+        printControlData( persistenceLogger, "EventLog::add" );
+
+        if( debugState )
+        {
+            assertInvariants();
+        }
+    }
+
+    // Inherit documentation from supertype
+    public RemoteEvent next() throws IOException, ClassNotFoundException
+    {
+        boolean IOExceptionCaught = false;
+
+        stateCheck();
+
+        if( debugState )
+        {
+            assertInvariants();
+        }
+
+        // Check if empty
+        if( isEmpty() )
+        {
+            throw new NoSuchElementException();
+        }
+
+        long logNum = getLogNum( rcount );
+        File log = getLogFile( logNum );
+        LogInputStream in = null;
+        RemoteEvent evt = null;
+        try
+        {
+            // get the input stream
+            in = streamPool.getLogInputStream( log, rpos );
+            // read the event
+            evt = eventReader.read( in );
+            // update the control data
+            nextReadPos = in.getOffset();
+            // Don't increment "real" read count until event is delivered
+            // indicated by a call to remove().
+        }
+        catch( IOException ie )
+        {
+            // We'll get interrupted when asked to shutdown.
+            // In this case, we can skip the call to nextReadLog.
+            if( ie instanceof InterruptedIOException )
+            {
+                if( persistenceLogger.isLoggable( Level.FINEST ) )
+                {
+                    persistenceLogger.log( Level.FINEST,
+                                           "EventLog::next() interrupted " );
+                }
+            }
+            else
+            {
+                if( persistenceLogger.isLoggable( Level.FINEST ) )
+                {
+                    persistenceLogger.log( Level.FINEST,
+                                           "EventLog::next() received IOException "
+                                           + "... skipping to next read log" );
+                }
+                nextReadLog();
+                IOExceptionCaught = true;
+            }
+            if( persistenceLogger.isLoggable( Level.FINEST ) )
+            {
+                persistenceLogger.log( Level.FINEST,
+                                       "Exception: ", ie );
+            }
+            throw (IOException) ie.fillInStackTrace();
+        }
+        catch( ClassNotFoundException cnfe )
+        {
+            // Note that the read offset is still valid since the
+            // MarshalledObject extraction should always succeed.
+            // It's just that the RemoteEvent within it could not
+            // be reconstituted. Therefore, just skip to the next
+            // MarshalledObject in the stream.
+            nextReadPos = in.getOffset();
+            throw (ClassNotFoundException) cnfe.fillInStackTrace();
+        }
+        finally
+        {
+            // If an IOException occurs then the eventReader is in
+            // a bad state. Therefore, we get rid of our existing
+            // reference and create a fresh one. The underlying
+            // stream is also removed from from the pool, forcing
+            // a fresh copy to be returned on the next "get" request.
+            if( IOExceptionCaught )
+            {
+                //TODO (FCS) add close to interface
+                //eventReader.close();
+                eventReader = new EventReader();
+                if( in != null )
+                {
+                    streamPool.removeLogStream( in );
+                }
+                // Remove associated log file since we are skipping over it
+                if( !log.delete() )
+                {
+                    if( persistenceLogger.isLoggable( Levels.HANDLED ) )
+                    {
+                        persistenceLogger.log( Levels.HANDLED,
+                                               "Had trouble deleting {0}", log );
+                    }
+                }
+            }
+            else
+            {
+                if( in != null )
+                {
+                    streamPool.releaseLogStream( in );
+                }
+            }
+
+            // TODO (FCS) - handle potential IOException
+            writeControlFile();
+
+        }
+
+        printControlData( persistenceLogger, "After Event::next" );
+
+        if( debugState )
+        {
+            assertInvariants();
+        }
+
+        return evt;
+    }
+
+    // Inherit documentation from supertype
+    public RemoteEventData[] readAhead( int maxEvents )
+        throws IOException, ClassNotFoundException
+    {
+        boolean IOExceptionCaught = false;
+
+        stateCheck();
+
+        if( debugState )
+        {
+            assertInvariants();
+        }
+
+        // Check if empty
+        if( isEmpty() )
+        {
+            throw new NoSuchElementException();
+        }
+
+        long readCount = rcount;
+        long readPosition = rpos;
+        long logNum = getLogNum( readCount );
+        File log = getLogFile( logNum );
+        RemoteEvent evt = null;
+        ArrayList rData = new ArrayList();
+        int i = 0;
+        LogInputStream in = null;
+        boolean done = false;
+
+        printControlData( persistenceLogger, "Before read::readAhead" );
+        if( persistenceLogger.isLoggable( Level.FINEST ) )
+        {
+            persistenceLogger.log( Level.FINEST,
+                                   "EventLog::readAhead() maxEvents = {0}",
+                                   new Object[]{ new Integer( maxEvents ) } );
+        }
+
+        while( ( readCount < wcount ) &&
+               ( rData.size() < maxEvents ) &&
+               !done )
+        {
+            if( logNum != getLogNum( readCount ) )
+            {
+                logNum = getLogNum( readCount );
+                log = getLogFile( logNum );
+                readPosition = 0;
+            }
+            try
+            {
+                // get the input stream
+                in = streamPool.getLogInputStream( log, readPosition );
+                // read the event
+                evt = eventReader.read( in );
+                // update readCount and readPosition
+                readCount++;
+                readPosition = in.getOffset(); // offset to next unread event
+                //Generate new entry
+                rData.add( new RemoteEventData(
+                    evt,
+                    new RemoteEventDataCursor( readCount, readPosition ) ) );
+            }
+            catch( IOException ie )
+            {
+                // We'll get interrupted when asked to shutdown.
+                // In this case, we can skip the call to nextReadAheadLog.
+                if( ie instanceof InterruptedIOException )
+                {
+                    if( persistenceLogger.isLoggable( Level.FINEST ) )
+                    {
+                        persistenceLogger.log( Level.FINEST,
+                                               "EventLog::readAhead() interrupted " );
+                    }
+                    // Stop processing events
+                    done = true;
+                }
+                else
+                {
+                    if( persistenceLogger.isLoggable( Level.FINEST ) )
+                    {
+                        persistenceLogger.log( Level.FINEST,
+                                               "EventLog::readAhead() received IOException "
+                                               + "... skipping to next read log" );
+                    }
+                    readCount = nextReadAheadLog( readCount );
+                    // TODO - rcount = readCount;
+                    // TODO - if bump rcount you need to bump wcount too
+                    // TODO - if bump counts then you need to persist them
+                    readPosition = 0;
+                    IOExceptionCaught = true;
+                    if( persistenceLogger.isLoggable( Level.FINEST ) )
+                    {
+                        persistenceLogger.log( Level.FINEST,
+                                               "EventLog::readAhead() new readCount is {0}",
+                                               new Long( readCount ) );
+                    }
+                }
+                if( persistenceLogger.isLoggable( Levels.HANDLED ) )
+                {
+                    persistenceLogger.log( Levels.HANDLED,
+                                           "Exception: ", ie );
+                }
+            }
+            catch( ClassNotFoundException cnfe )
+            {
+                // Note that the read offset is still valid since the
+                // MarshalledObject extraction should always succeed.
+                // It's just that the RemoteEvent within it could not
+                // be reconstituted. Therefore, just skip to the next
+                // MarshalledObject in the stream.
+                readCount++;
+                readPosition = in.getOffset();
+                //Generate new entry
+                rData.add( new RemoteEventData(
+                    null,
+                    new RemoteEventDataCursor( readCount, readPosition ) ) );
+                if( persistenceLogger.isLoggable( Levels.HANDLED ) )
+                {
+                    persistenceLogger.log( Levels.HANDLED,
+                                           "Exception: ", cnfe );
+                }
+            }
+            finally
+            {
+                // If an IOException occurs then the eventReader is in 
+                // a bad state. Therefore, we get rid of our existing
+                // reference and create a fresh one. The underlying
+                // stream is also removed from from the pool, forcing
+                // a fresh copy to be returned on the next "get" request.
+                if( IOExceptionCaught )
+                {
+                    //TODO (FCS) add close to interface
+                    //eventReader.close();
+                    eventReader = new EventReader();
+                    if( in != null )
+                    {
+                        streamPool.removeLogStream( in );
+                        in = null;
+                    }
+                }
+                else
+                {
+                    if( in != null )
+                    {
+                        streamPool.releaseLogStream( in );
+                    }
+                    /* Shouldn't have to release since we have exclusive
+                     * access, but the next getLogInputStream assumes things
+                     * are on the freelist.
+                     */
+                }
+
+            }
+        }
+
+        printControlData( persistenceLogger, "After Event::readAhead" );
+
+        if( debugState )
+        {
+            assertInvariants();
+        }
+
+        return (RemoteEventData[]) rData.toArray( new RemoteEventData[0] );
+    }
+
+    // Inherit documentation from supertype
+    public boolean isEmpty() throws IOException
+    {
+        stateCheck();
+        return !( rcount < wcount );
+    }
+
+    // Inherit documentation from supertype
+    public void remove() throws IOException
+    {
+        stateCheck();
+
+        if( debugState )
+        {
+            assertInvariants();
+        }
+
+        // Get current log number
+        long logNum = getLogNum( rcount );
+
+        // Update read counts
+        if( rcount < wcount )
+        {
+            ++rcount;
+            rpos = nextReadPos;
+        }
+        else
+        {
+            throw new NoSuchElementException();
+        }
+
+        // Remove old log if we've advanced to a new
+        // log file (ie done reading the old log file).
+        if( getLogNum( rcount ) != logNum )
+        {
+            File log = getLogFile( logNum );
+            LogInputStream in = null;
+            try
+            {
+                in = streamPool.getLogInputStream( log, rpos );
+                streamPool.removeLogStream( in );
+            }
+            finally
+            {
+                if( !log.delete() )
+                {
+                    if( persistenceLogger.isLoggable( Levels.HANDLED ) )
+                    {
+                        persistenceLogger.log( Levels.HANDLED,
+                                               "Had trouble deleting {0}", log );
+                    }
+                }
+                rpos = nextReadPos = 0;
+            }
+        }
+
+        // Store control data
+        writeControlFile();
+
+        // Verify that state remains intact
+        assertInvariants();
+
+
+        printControlData( persistenceLogger, "After Event::remove" );
+
+    }
+
+    // Inherit documentation from supertype
+    public void moveAhead( Object cookie ) throws IOException
+    {
+        stateCheck();
+
+        if( debugState )
+        {
+            assertInvariants();
+        }
+
+        printControlData( persistenceLogger, "Before Event::moveAhead" );
+
+        long readCount = 0;
+        long readPosition = 0;
+        RemoteEventDataCursor cursor = (RemoteEventDataCursor) cookie;
+        if( cursor != null )
+        {
+            readCount = cursor.getReadCount();
+            readPosition = cursor.getReadPosition();
+        }
+        else
+        {
+            /* TODO - should throw NullPointerException, but we do 
+             * get called with null if client initially gets an empty set.
+             * Need to change getNextBatchDo() to skip this call 
+             * if cookie is null. Also, could just return at this point since
+             * nothing will be advanced.
+             */
+            readCount = rcount;
+            readPosition = rpos;
+        }
+
+        if( readCount > wcount )
+        {
+            throw new NoSuchElementException();
+        }
+
+        long currentLogNum = getLogNum( rcount );
+        long nextLogNum = getLogNum( readCount );
+        if( persistenceLogger.isLoggable( Level.FINEST ) )
+        {
+            persistenceLogger.log( Level.FINEST,
+                                   "EventLog::moveAhead() readCount = {0}, readPosition = {1}, "
+                                   + "currentLogNum = {2}, nextLogNum = {3}",
+                                   new Object[]{ new Long( readCount ), new Long( readPosition ),
+                                                 new Long( currentLogNum ), new Long( nextLogNum ) } );
+        }
+        File logFile = null;
+        LogInputStream in = null;
+        while( currentLogNum < nextLogNum )
+        {
+            try
+            {
+                logFile = getLogFile( currentLogNum );
+                in = streamPool.getLogInputStream( logFile, rpos );
+                streamPool.removeLogStream( in );
+                /*
+                 * NOTE - if file doesn't exist (corrupted) then
+                 * getLogInputStream throws an IOException and you
+                 * end up skipping the removeLogStream() call.
+                 * This "log" should eventually get cleared if once
+                 * the stream pool hits its limit.
+                 */
+
+            }
+            catch( IOException ioe )
+            {
+                if( persistenceLogger.isLoggable( Levels.HANDLED ) )
+                {
+                    persistenceLogger.log( Levels.HANDLED,
+                                           "Had trouble accessing old log file", ioe );
+                }
+            }
+            finally
+            {
+                if( logFile != null && !logFile.delete() )
+                {
+                    if( persistenceLogger.isLoggable( Levels.HANDLED ) )
+                    {
+                        persistenceLogger.log( Levels.HANDLED,
+                                               "Had trouble deleting {0}", logFile );
+                    }
+                }
+                if( persistenceLogger.isLoggable( Level.FINEST ) )
+                {
+                    persistenceLogger.log( Level.FINEST,
+                                           "Deleted {0}", logFile );
+                }
+                currentLogNum++;
+            }
+        }
+        rcount = readCount;
+        // If we started a new log file, then reset the read pointer.
+        if( ( rcount % eventsPerLogFile ) == 0 )
+        {
+            rpos = 0;
+        }
+        else
+        {
+            rpos = readPosition;
+        }
+
+        // Store control data
+        writeControlFile();
+
+        // Verify that state remains intact
+        assertInvariants();
+
+        printControlData( persistenceLogger, "After Event::moveAhead" );
+
+    }
+
+    // Inherit documentation from supertype
+    public void close() throws IOException
+    {
+        stateCheck();
+        if( debugState )
+        {
+            assertInvariants();
+        }
+
+        // Close read log
+        long logNum = getLogNum( rcount );
+        File log = getLogFile( logNum );
+        LogStream strm = null;
+        try
+        {
+            strm = streamPool.getLogInputStream( log, rpos );
+            streamPool.removeLogStream( strm );
+        }
+        catch( FileNotFoundException fnfe )
+        {
+            // Can happen if close is called just after you've finished
+            // reading a log. That is, the readCount is advanced to a new
+            // log which might not exist if a write hasn't taken place.
+            // TODO (FCS) - put a check to verify this condition and throw an
+            // exception otherwise?
+        }
+        catch( IOException ioe )
+        {
+            // catch, but ignore so as not to skip the following
+            // code.
+        }
+
+        // Close write log
+        logNum = getLogNum( wcount );
+        log = getLogFile( logNum );
+        try
+        {
+            strm = streamPool.getLogOutputStream( log, wpos );
+            streamPool.removeLogStream( strm );
+        }
+        catch( IOException ioe )
+        {
+            // catch, but ignore so as not to skip the following
+            // code.
+        }
+
+        // Close control log
+        try
+        {
+            strm = streamPool.getControlLog( controlFile );
+            streamPool.removeLogStream( strm );
+        }
+        catch( IOException ioe )
+        {
+            // catch, but ignore so as not to skip the following
+            // code.
+        }
+
+        //TODO (FCS) - flag that an IOException was caught and rethrow to
+        // indicate that there was a problem.
+
+        closed = true;
+
+        if( persistenceLogger.isLoggable( Level.FINEST ) )
+        {
+            persistenceLogger.log( Level.FINEST,
+                                   "EventLog::close for {0}", uuid );
+        }
+    }
+
+    // Inherit documentation from supertype
+    public void delete() throws IOException
+    {
+        if( !closed )
+        {
+            throw new IOException( "Cannot delete log until it is closed" );
+        }
+        removeDir( logDir );
+    }
+
+    /**
+     * Attempt to delete the associated event log persistence directory.
+     */
+    private static void removeDir( File logDir )
+    {
+        if( logDir == null || !logDir.isDirectory() )
+        {
+            return;
+        }
+
+        // Get the contents of this directory
+        String[] contents = logDir.list();
+        if( contents == null )  // Can happen if there was an IO error
+        {
+            return;
+        }
+
+        File entry = null;
+        if( persistenceLogger.isLoggable( Level.FINEST ) )
+        {
+            persistenceLogger.log( Level.FINEST,
+                                   "Deleting contents of: {0}", logDir );
+        }
+        for( int i = 0; i < contents.length; i++ )
+        {
+            entry = new File( logDir, contents[ i ] );
+            if( entry.isDirectory() )
+            {
+                removeDir( entry );
+            }
+            else
+            {
+                if( !entry.delete() )
+                {
+                    if( persistenceLogger.isLoggable( Levels.HANDLED ) )
+                    {
+                        persistenceLogger.log( Levels.HANDLED,
+                                               "Had trouble deleting file: {0}",
+                                               entry );
+                    }
+                }
+                else
+                {
+                    if( persistenceLogger.isLoggable( Level.FINEST ) )
+                    {
+                        persistenceLogger.log( Level.FINEST,
+                                               "Deleted file: {0}", entry );
+                    }
+                }
+            }
+        }
+
+        if( !logDir.delete() )
+        {
+            if( persistenceLogger.isLoggable( Levels.HANDLED ) )
+            {
+                persistenceLogger.log( Levels.HANDLED,
+                                       "Had trouble deleting directory: {0}",
+                                       logDir );
+            }
+        }
+        else
+        {
+            if( persistenceLogger.isLoggable( Level.FINEST ) )
+            {
+                persistenceLogger.log( Level.FINEST,
+                                       "Deleted directory: {0}", logDir );
+            }
+        }
+    }
+
+    /**
+     * Advance the "read" state to the next available log.
+     */
+    private void nextReadLog()
+    {
+        if( debugState )
+        {
+            assertInvariants();
+        }
+
+        // Normalize event count in case we were called due to a problem with
+        // one of the log files
+        long remainder = rcount % eventsPerLogFile;
+        rcount += ( eventsPerLogFile - remainder );
+        rpos = 0;
+
+        // Ensure that the writeLog data doesn't get behind
+        if( verifyInvariants() == false )
+        {
+            nextWriteLog();
+        }
+
+        // Assert that state is still valid
+        if( debugState )
+        {
+            assertInvariants();
+        }
+
+        printControlData( persistenceLogger, "EventLog::nextReadLog" );
+    }
+
+    /**
+     * Advance the temporary "read" state to the next available log.
+     */
+    private long nextReadAheadLog( long readCount )
+    {
+        // Normalize event count in case we were called due to a problem with
+        // one of the log files
+        long remainder = readCount % eventsPerLogFile;
+        readCount += ( eventsPerLogFile - remainder );
+
+        printControlData( persistenceLogger, "EventLog::nextReadAheadLog" );
+
+        return readCount;
+    }
+
+    /**
+     * Advance the "write" state to the next available log.
+     */
+    private void nextWriteLog()
+    {
+        // Don't check invariants at the top since we can get called
+        // from readNextLog() with invalid state.
+
+        // Normalize event count in case we were called due to a problem with
+        // one of the log files
+        long remainder = wcount % eventsPerLogFile;
+        wcount += ( eventsPerLogFile - remainder );
+        wpos = 0;
+
+        printControlData( persistenceLogger, "EventLog::nextWriteLog" );
+
+        if( debugState )
+        {
+            assertInvariants();
+        }
+    }
+
+    /**
+     * Output state information to the given <tt>Logger</tt>.
+     * This is intended for debugging purposes only.
+     */
+    private void printControlData( Logger logger, String msg )
+    {
+        if( logger.isLoggable( Level.FINEST ) )
+        {
+            logger.log( Level.FINEST, "{0}", msg );
+            logger.log( Level.FINEST, "ID: {0}", uuid );
+            logger.log( Level.FINEST, "ReadCount: {0}",
+                        new Long( rcount ) );
+            logger.log( Level.FINEST, "ReadPos: {0}",
+                        new Long( rpos ) );
+            logger.log( Level.FINEST, "NextReadPos: {0}",
+                        new Long( nextReadPos ) );
+            logger.log( Level.FINEST, "WriteCount: {0}",
+                        new Long( wcount ) );
+            logger.log( Level.FINEST, "WritePos: {0}",
+                        new Long( wpos ) );
+        }
+    }
+
+    /**
+     * Write state information to the underlying store.
+     */
+    private void writeControlFile() throws IOException
+    {
+        packLong( wcount, ctlbuf, 0 );
+        packLong( rcount, ctlbuf, 8 );
+        packLong( wpos, ctlbuf, 16 );
+        packLong( rpos, ctlbuf, 24 );
+
+        ControlLog ctl = streamPool.getControlLog( controlFile );
+        ctl.seek( 0L );
+        ctl.write( ctlbuf );
+        ctl.getFD().sync();
+        streamPool.releaseLogStream( ctl );
+    }
+
+    /**
+     * Read state information from the underlying store.
+     */
+    private void readControlFile() throws IOException
+    {
+        ControlLog ctl = streamPool.getControlLog( controlFile );
+        ctl.seek( 0L );
+        ctl.readFully( ctlbuf );
+        streamPool.releaseLogStream( ctl );
+
+        wcount = unpackLong( ctlbuf, 0 );
+        rcount = unpackLong( ctlbuf, 8 );
+        wpos = unpackLong( ctlbuf, 16 );
+        rpos = unpackLong( ctlbuf, 24 );
+    }
+
+    /**
+     * Utility method for packing a <tt>long</tt> into a <tt>byte</tt> array.
+     */
+    private static void packLong( long val, byte[] b, int off )
+    {
+        b[ off++ ] = (byte) ( val >>> 56 );
+        b[ off++ ] = (byte) ( val >>> 48 );
+        b[ off++ ] = (byte) ( val >>> 40 );
+        b[ off++ ] = (byte) ( val >>> 32 );
+        b[ off++ ] = (byte) ( val >>> 24 );
+        b[ off++ ] = (byte) ( val >>> 16 );
+        b[ off++ ] = (byte) ( val >>> 8 );
+        b[ off++ ] = (byte) ( val >>> 0 );
+    }
+
+    /**
+     * Utility method for unpacking a <tt>long</tt> from a <tt>byte</tt> array.
+     */
+    private static long unpackLong( byte[] b, int off )
+    {
+        return ( ( b[ off + 0 ] & 0xFFL ) << 56 ) +
+               ( ( b[ off + 1 ] & 0xFFL ) << 48 ) +
+               ( ( b[ off + 2 ] & 0xFFL ) << 40 ) +
+               ( ( b[ off + 3 ] & 0xFFL ) << 32 ) +
+               ( ( b[ off + 4 ] & 0xFFL ) << 24 ) +
+               ( ( b[ off + 5 ] & 0xFFL ) << 16 ) +
+               ( ( b[ off + 6 ] & 0xFFL ) << 8 ) +
+               ( ( b[ off + 7 ] & 0xFFL ) << 0 );
+    }
+
+    /**
+     * Utility method for returning the <tt>File</tt> associated with the
+     * given <tt>lognum</tt>.
+     */
+    private File getLogFile( long lognum )
+    {
+        return new File( logDir, lognum + "." + LOGFILE_SUFFIX ).getAbsoluteFile();
+    }
+
+    /**
+     * Utility method for returning the <tt>File</tt> that contains the
+     * state information for this log.
+     */
+    private File getControlFile()
+    {
+        return new File( logDir, "log." + CTLFILE_SUFFIX ).getAbsoluteFile();
+    }
+
+    /**
+     * Utility method for returning the log file number for the given
+     * (event) <tt>count</tt>.
+     */
+    private static long getLogNum( long count )
+    {
+        return ( count / eventsPerLogFile );
+    }
+
+
+    /**
+     * Asserts that the log is in a valid state.
+     *
+     * @throws IOException if the log is in an invalid state
+     */
+    private void stateCheck() throws IOException
+    {
+        if( !initialized )
+        {
+            throw new IOException( "Trying to use an uninitialized "
+                                   + "control data object for: " + uuid );
+        }
+        if( closed )
+        {
+            throw new IOException( "Attempt to access closed log file for : "
+                                   + uuid );
+        }
+    }
+
+    /**
+     * Utility method for checking if the object invariants are valid.
+     */
+    private boolean verifyInvariants()
+    {
+        if( ( wcount < rcount ) ||
+            ( ( getLogNum( wcount ) == getLogNum( rcount ) ) && ( wpos < rpos ) ) )
+        {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Utility method for asserting that the object invariants are valid.
+     *
+     * @throws InternalMailboxException if the invariants aren't valid
+     */
+    private void assertInvariants()
+    {
+        if( verifyInvariants() == false )
+        {
+            printControlData( persistenceLogger, "Dumping invalid state for " + uuid );
+            throw new InternalMailboxException( "Invalid state for " + uuid );
+        }
+    }
+}

Added: incubator/river/jtsk/skunk/niclas1/services/mercury/src/main/java/com/sun/jini/mercury/ProxyUtil.java
URL: http://svn.apache.org/viewvc/incubator/river/jtsk/skunk/niclas1/services/mercury/src/main/java/com/sun/jini/mercury/ProxyUtil.java?rev=724986&view=auto
==============================================================================
--- incubator/river/jtsk/skunk/niclas1/services/mercury/src/main/java/com/sun/jini/mercury/ProxyUtil.java (added)
+++ incubator/river/jtsk/skunk/niclas1/services/mercury/src/main/java/com/sun/jini/mercury/ProxyUtil.java Tue Dec  9 21:16:33 2008
@@ -0,0 +1,83 @@
+/*
+ * 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.mercury;
+
+import java.lang.reflect.Method;
+
+/**
+ * Convenience class that contains a package protected static utility method
+ * used by the proxy classes of this package in proxy trust verification
+ * process.
+ *
+ * Note that this class cannot be instantiated.
+ *
+ * @author Sun Microsystems, Inc.
+ */
+class ProxyUtil
+{
+
+    /**
+     * This class cannot be instantiated.
+     */
+    private ProxyUtil()
+    {
+        throw new AssertionError( "class cannot be instantiated" );
+    }//end constructor
+
+    /**
+     * Returns the public method for the specified <code>Class</code> type,
+     * method name, and array of parameter types.
+     * <p>
+     * This method is typically used in place of {@link Class#getMethod
+     * Class.getMethod} to get a method that should definitely be defined;
+     * thus, this method throws an error instead of an exception if the
+     * given method is missing.
+     * <p>
+     * This method is convenient for the initialization of a static
+     * variable for use as the <code>mappings</code> argument to
+     * {@link com.sun.jini.proxy.ConstrainableProxyUtil#translateConstraints
+     * ConstrainableProxyUtil.translateConstraints}.
+     *
+     * @param type           the <code>Class</code> type that defines the
+     *                       method of interest
+     * @param name           <code>String</code> containing the name of the
+     *                       method of interest
+     * @param parameterTypes the <code>Class</code> types of the parameters
+     *                       to the method of interest
+     * @return a <code>Method</code> object that provides information about,
+     *         and access to, the method of interest
+     * @throws <code>NoSuchMethodError</code> if the method of interest cannot
+     *                                        be found
+     * @throws <code>NullPointerException</code>
+     *                                        if <code>type</code> or
+     *                                        <code>name</code> is <code>null</code>
+     */
+    static Method getMethod( Class type,
+                             String name,
+                             Class[] parameterTypes )
+    {
+        try
+        {
+            return type.getMethod( name, parameterTypes );
+        }
+        catch( NoSuchMethodException e )
+        {
+            throw (Error) ( new NoSuchMethodError( e.getMessage() ).initCause( e ) );
+        }
+    }//end getMethod
+}//end class ProxyUtil

Added: incubator/river/jtsk/skunk/niclas1/services/mercury/src/main/java/com/sun/jini/mercury/ProxyVerifier.java
URL: http://svn.apache.org/viewvc/incubator/river/jtsk/skunk/niclas1/services/mercury/src/main/java/com/sun/jini/mercury/ProxyVerifier.java?rev=724986&view=auto
==============================================================================
--- incubator/river/jtsk/skunk/niclas1/services/mercury/src/main/java/com/sun/jini/mercury/ProxyVerifier.java (added)
+++ incubator/river/jtsk/skunk/niclas1/services/mercury/src/main/java/com/sun/jini/mercury/ProxyVerifier.java Tue Dec  9 21:16:33 2008
@@ -0,0 +1,178 @@
+/*
+ * 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.mercury;
+
+import com.sun.jini.landlord.ConstrainableLandlordLease;
+import com.sun.jini.landlord.Landlord;
+import com.sun.jini.landlord.LandlordProxyVerifier;
+import java.io.Serializable;
+import java.rmi.RemoteException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+import net.jini.core.constraint.MethodConstraints;
+import net.jini.core.constraint.RemoteMethodControl;
+import net.jini.export.proxytrust.TrustEquivalence;
+import net.jini.id.ReferentUuid;
+import net.jini.id.Uuid;
+import net.jini.security.TrustVerifier;
+
+/**
+ * Defines a trust verifier for the smart proxies of a Mercury server.
+ */
+final class ProxyVerifier implements TrustVerifier, Serializable
+{
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * Logger for logging information about this instance
+     */
+    private static final Logger logger =
+        Logger.getLogger( "net.jini.security.trust" );
+
+    private static final String proxyVerifierSourceClass =
+        ProxyVerifier.class.getName();
+
+    /**
+     * The Mercury server proxy.
+     */
+    private final RemoteMethodControl serverProxy;
+
+    /**
+     * The unique identifier associated with the backend server referenced
+     * by the <code>serverProxy</code>, used for comparison with the IDs
+     * extracted from the smart proxies being verified.
+     *
+     * @serial
+     */
+    private final Uuid proxyID;
+
+    /**
+     * Returns a verifier for the smart proxies of the specified Mercury server
+     * proxy.
+     *
+     * @param serverProxy the Mercury server proxy
+     * @throws UnsupportedOperationException if <code>serverProxy</code> does
+     *                                       not implement both {@link RemoteMethodControl} and {@link
+     *                                       TrustEquivalence}
+     */
+    ProxyVerifier( MailboxBackEnd serverProxy, Uuid proxyID )
+    {
+        if( !( serverProxy instanceof RemoteMethodControl ) )
+        {
+            throw new UnsupportedOperationException(
+                "No verifier available for non-constrainable service" );
+        }
+        else if( !( serverProxy instanceof TrustEquivalence ) )
+        {
+            throw new UnsupportedOperationException(
+                "Verifier requires service proxy to implement " +
+                "TrustEquivalence" );
+        }
+        else if( proxyID == null )
+        {
+            throw new IllegalArgumentException(
+                "Proxy id cannot be null" );
+        }
+        this.serverProxy = (RemoteMethodControl) serverProxy;
+        this.proxyID = proxyID;
+    }
+
+    /**
+     * @throws NullPointerException {@inheritDoc}
+     */
+    public boolean isTrustedObject( Object obj, TrustVerifier.Context ctx )
+        throws RemoteException
+    {
+        if( logger.isLoggable( Level.FINER ) )
+        {
+            logger.entering( proxyVerifierSourceClass, "isTrustedObject",
+                             new Object[]{ obj, ctx } );
+        }
+        if( obj == null || ctx == null )
+        {
+            throw new NullPointerException( "Arguments must not be null" );
+        }
+        RemoteMethodControl otherServerProxy;
+        Uuid inputProxyID = null;
+        if( obj instanceof Registration.ConstrainableRegistration )
+        {
+            Registration reg = (Registration) obj;
+            // verify sub-components
+            if( !isTrustedObject( reg.lease, ctx ) ||
+                !isTrustedObject( reg.listener, ctx ) )
+            {
+                return false;
+            }
+            otherServerProxy = (RemoteMethodControl) reg.mailbox;
+        }
+        else if( obj instanceof MailboxBackEnd &&
+                 obj instanceof RemoteMethodControl )
+        {
+            /* Inner proxy verification case. To simplify logic, below,
+	     * just assume the same Uuid that we have in hand.
+	     */
+            otherServerProxy = (RemoteMethodControl) obj;
+            inputProxyID = proxyID;
+        }
+        else if( obj instanceof MailboxProxy.ConstrainableMailboxProxy )
+        {
+            otherServerProxy = (RemoteMethodControl) ( (MailboxProxy) obj ).mailbox;
+            inputProxyID = ( (ReferentUuid) obj ).getReferentUuid();
+        }
+        else if( obj instanceof MailboxAdminProxy.ConstrainableMailboxAdminProxy )
+        {
+            otherServerProxy = (RemoteMethodControl) ( (MailboxAdminProxy) obj ).server;
+            inputProxyID = ( (ReferentUuid) obj ).getReferentUuid();
+        }
+        else if( obj instanceof ListenerProxy.ConstrainableListenerProxy )
+        {
+            otherServerProxy = (RemoteMethodControl) ( (ListenerProxy) obj ).server;
+        }
+        else if( obj instanceof ConstrainableLandlordLease )
+        {
+            final LandlordProxyVerifier lpv =
+                new LandlordProxyVerifier( (Landlord) serverProxy, proxyID );
+            return lpv.isTrustedObject( obj, ctx );
+        }
+        else
+        {
+            logger.log( Level.FINEST, "Object {0} is not a supported type",
+                        obj );
+            return false;
+        }
+
+        // For top-level proxies, quickly verify proxy Uuid
+        if( ( inputProxyID != null ) &&
+            !( proxyID.equals( inputProxyID ) ) )
+        {
+            return false;
+        }
+
+        MethodConstraints mc = otherServerProxy.getConstraints();
+        TrustEquivalence trusted =
+            (TrustEquivalence) serverProxy.setConstraints( mc );
+        boolean result = trusted.checkTrustEquivalence( otherServerProxy );
+        if( logger.isLoggable( Level.FINER ) )
+        {
+            logger.exiting( proxyVerifierSourceClass, "isTrustedObject",
+                            Boolean.valueOf( result ) );
+        }
+        return result;
+    }
+}

Added: incubator/river/jtsk/skunk/niclas1/services/mercury/src/main/java/com/sun/jini/mercury/Registration.java
URL: http://svn.apache.org/viewvc/incubator/river/jtsk/skunk/niclas1/services/mercury/src/main/java/com/sun/jini/mercury/Registration.java?rev=724986&view=auto
==============================================================================
--- incubator/river/jtsk/skunk/niclas1/services/mercury/src/main/java/com/sun/jini/mercury/Registration.java (added)
+++ incubator/river/jtsk/skunk/niclas1/services/mercury/src/main/java/com/sun/jini/mercury/Registration.java Tue Dec  9 21:16:33 2008
@@ -0,0 +1,448 @@
+/*
+ * 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.mercury;
+
+import com.sun.jini.landlord.ConstrainableLandlordLease;
+import com.sun.jini.proxy.ConstrainableProxyUtil;
+import com.sun.jini.proxy.ThrowThis;
+import java.io.IOException;
+import java.io.InvalidObjectException;
+import java.io.ObjectInputStream;
+import java.io.ObjectStreamException;
+import java.io.Serializable;
+import java.lang.reflect.Method;
+import java.rmi.RemoteException;
+import java.util.Collection;
+import net.jini.core.constraint.MethodConstraints;
+import net.jini.core.constraint.RemoteMethodControl;
+import net.jini.core.event.RemoteEventListener;
+import net.jini.core.lease.Lease;
+import net.jini.event.MailboxPullRegistration;
+import net.jini.event.MailboxRegistration;
+import net.jini.event.RemoteEventIterator;
+import net.jini.export.proxytrust.ProxyTrustIterator;
+import net.jini.export.proxytrust.SingletonProxyTrustIterator;
+import net.jini.id.ReferentUuid;
+import net.jini.id.ReferentUuids;
+import net.jini.id.Uuid;
+
+/**
+ * The <tt>Registration</tt> class is the client-side proxy
+ * returned to event mailbox clients as the result of the
+ * registration process. It implements the <tt>MailboxRegistration</tt>
+ * interface and delegates functionality to the mailbox service
+ * where necessary.
+ *
+ * @author Sun Microsystems, Inc.
+ * @since 1.1
+ */
+class Registration implements MailboxPullRegistration,
+                              Serializable, ReferentUuid
+{
+
+    private static final long serialVersionUID = 2L;
+
+    /**
+     * Unique identifier for this registration
+     */
+    final Uuid registrationID;
+
+    /**
+     * Reference to service implementation
+     */
+    final MailboxBackEnd mailbox;
+
+    /**
+     * Reference to service provided RemoteEventListener implementation
+     */
+    final ListenerProxy listener;
+
+    /**
+     * The service's registration lease
+     */
+    final Lease lease;
+
+    /**
+     * Creates a mailbox registration proxy, returning an instance
+     * that implements RemoteMethodControl if the server does too.
+     *
+     * @param server the server proxy
+     * @param id     the ID of the lease set
+     * @param lease  the lease set's lease
+     */
+    static Registration create( Uuid id, MailboxBackEnd server, Lease lease )
+    {
+        if( server instanceof RemoteMethodControl )
+        {
+            return new ConstrainableRegistration( id, server, lease, null );
+        }
+        else
+        {
+            return new Registration( id, server, lease );
+        }
+    }
+
+    /**
+     * Convenience constructor
+     */
+    private Registration( Uuid id, MailboxBackEnd srv, Lease l )
+    {
+        if( id == null || srv == null || l == null )
+        {
+            throw new IllegalArgumentException( "Cannot accept null arguments" );
+        }
+        registrationID = id;
+        mailbox = srv;
+        listener = ListenerProxy.create( id, srv );
+        lease = l;
+    }
+
+    // inherit javadoc from supertype
+    public Lease getLease()
+    {
+        return lease;
+    }
+
+    // inherit javadoc from supertype
+    public RemoteEventListener getListener()
+    {
+        return listener;
+    }
+
+    // inherit javadoc from supertype
+    public void enableDelivery( RemoteEventListener target )
+        throws RemoteException
+    {
+        // Prevent resubmission of this registration's listener
+        if( ( target instanceof ListenerProxy ) &&
+            ( listener.equals( (ListenerProxy) target ) ) )
+        {
+            throw new IllegalArgumentException( "Cannot resubmit " +
+                                                "a target that was provided by the EventMailbox service" );
+        }
+        else
+        { // OK to make the call, now
+            try
+            {
+                mailbox.enableDelivery( registrationID, target );
+            }
+            catch( ThrowThis tt )
+            {
+                tt.throwRemoteException();
+            }
+        }
+    }
+
+    // inherit javadoc from supertype
+    public void disableDelivery() throws RemoteException
+    {
+        try
+        {
+            mailbox.disableDelivery( registrationID );
+        }
+        catch( ThrowThis tt )
+        {
+            tt.throwRemoteException();
+        }
+    }
+
+    // inherit javadoc from supertype
+    public RemoteEventIterator getRemoteEvents()
+        throws RemoteException
+    {
+        RemoteEventIteratorImpl i = null;
+        try
+        {
+            RemoteEventIteratorData d = mailbox.getRemoteEvents( registrationID );
+            i = new RemoteEventIteratorImpl(
+                d.uuid, registrationID, mailbox, d.events );
+        }
+        catch( ThrowThis tt )
+        {
+            tt.throwRemoteException();
+        }
+        return i;
+    }
+
+    // inherit javadoc from supertype
+    public void addUnknownEvents( Collection unknownEvents )
+        throws RemoteException
+    {
+        //TODO - verify collection contains RemoteEvents
+        try
+        {
+            mailbox.addUnknownEvents( registrationID, unknownEvents );
+        }
+        catch( ThrowThis tt )
+        {
+            tt.throwRemoteException();
+        }
+
+    }
+
+    /* From net.jini.id.ReferentUuid */
+    /**
+     * Returns the universally unique identifier that has been assigned to the
+     * resource this proxy represents.
+     *
+     * @return the instance of <code>Uuid</code> that is associated with the
+     *         resource this proxy represents. This method will not return
+     *         <code>null</code>.
+     * @see net.jini.id.ReferentUuid
+     */
+    public Uuid getReferentUuid()
+    {
+        return registrationID;
+    }
+
+    /**
+     * Proxies with the same registrationID have the same hash code.
+     */
+    public int hashCode()
+    {
+        return registrationID.hashCode();
+    }
+
+    /**
+     * Proxies with the same registrationID are considered equal.
+     */
+    public boolean equals( Object o )
+    {
+        return ReferentUuids.compare( this, o );
+    }
+
+    /**
+     * When an instance of this class is deserialized, this method is
+     * automatically invoked. This implementation of this method validates
+     * the state of the deserialized instance.
+     *
+     * @throws <code>InvalidObjectException</code>
+     *          if the state of the
+     *          deserialized instance of this class is found to be invalid.
+     */
+    private void readObject( ObjectInputStream s )
+        throws IOException, ClassNotFoundException
+    {
+        s.defaultReadObject();
+        /* Verify server */
+        if( mailbox == null )
+        {
+            throw new InvalidObjectException( "Registration.readObject "
+                                              + "failure - mailbox "
+                                              + "field is null" );
+        }//endif
+        /* Verify registrationID */
+        if( registrationID == null )
+        {
+            throw new InvalidObjectException
+                ( "Registration.readObject "
+                  + "failure - registrationID field is null" );
+        }//endif
+        /* Verify regLease */
+        if( lease == null )
+        {
+            throw new InvalidObjectException
+                ( "Registration.readObject "
+                  + "failure - lease field is null" );
+        }//endif
+        /* Verify listener */
+        if( listener == null )
+        {
+            throw new InvalidObjectException
+                ( "Registration.readObject "
+                  + "failure - listener field is null" );
+        }//endif
+    }//end readObject
+
+    /**
+     * During deserialization of an instance of this class, if it is found
+     * that the stream contains no data, this method is automatically
+     * invoked. Because it is expected that the stream should always
+     * contain data, this implementation of this method simply declares
+     * that something must be wrong.
+     *
+     * @throws <code>InvalidObjectException</code>
+     *          to indicate that there
+     *          was no data in the stream during deserialization of an
+     *          instance of this class; declaring that something is wrong.
+     */
+    private void readObjectNoData() throws ObjectStreamException
+    {
+        throw new InvalidObjectException( "no data found when attempting to "
+                                          + "deserialize Registration instance" );
+    }//end readObjectNoData
+
+    /**
+     * A subclass of Registration that implements RemoteMethodControl.
+     */
+    final static class ConstrainableRegistration extends Registration
+        implements RemoteMethodControl
+    {
+        private static final long serialVersionUID = 1L;
+
+        // Mappings from client to server methods,
+        private static final Method[] methodMap1 = {
+            ProxyUtil.getMethod( MailboxPullRegistration.class,
+                                 "getRemoteEvents", new Class[]{ } ),
+            ProxyUtil.getMethod( MailboxBackEnd.class,
+                                 "getRemoteEvents", new Class[]{ Uuid.class } ),
+            // Use the same constraints for getNextBatch as getRemoteEvents
+            ProxyUtil.getMethod( MailboxPullRegistration.class,
+                                 "getRemoteEvents", new Class[]{ } ),
+            ProxyUtil.getMethod( MailboxBackEnd.class,
+                                 "getNextBatch", new Class[]{
+                    Uuid.class, Uuid.class, long.class, Object.class } ),
+            ProxyUtil.getMethod( MailboxPullRegistration.class,
+                                 "addUnknownEvents", new Class[]{ Collection.class } ),
+            ProxyUtil.getMethod( MailboxBackEnd.class,
+                                 "addUnknownEvents", new Class[]{ Uuid.class, Collection.class } ),
+            ProxyUtil.getMethod( MailboxRegistration.class,
+                                 "enableDelivery", new Class[]{ RemoteEventListener.class } ),
+            ProxyUtil.getMethod( MailboxBackEnd.class,
+                                 "enableDelivery", new Class[]{ Uuid.class,
+                                                                RemoteEventListener.class } ),
+            ProxyUtil.getMethod( MailboxRegistration.class,
+                                 "disableDelivery", new Class[]{ } ),
+            ProxyUtil.getMethod( MailboxBackEnd.class,
+                                 "disableDelivery", new Class[]{ Uuid.class } )
+        };
+        /**
+         * The client constraints placed on this proxy or <code>null</code>.
+         *
+         * @serial
+         */
+        private MethodConstraints methodConstraints;
+
+        /**
+         * Creates an instance of this class.
+         */
+        private ConstrainableRegistration( Uuid id, MailboxBackEnd server,
+                                           Lease lease, MethodConstraints methodConstraints )
+        {
+            super( id, constrainServer( server, methodConstraints ),
+                   lease );
+            this.methodConstraints = methodConstraints;
+        }
+
+        // inherit javadoc from supertype
+        public RemoteEventIterator getRemoteEvents( long maxEvents, long timeout )
+            throws RemoteException
+        {
+            //
+            //TODO - return constrained remote iterator impl
+            //
+            return super.getRemoteEvents();
+        }
+
+        /**
+         * Returns a copy of the server proxy with the specified client
+         * constraints and methods mapping.
+         */
+        private static MailboxBackEnd constrainServer(
+            MailboxBackEnd server,
+            MethodConstraints methodConstraints )
+        {
+            return (MailboxBackEnd)
+                ( (RemoteMethodControl) server ).setConstraints(
+                    ConstrainableProxyUtil.translateConstraints(
+                        methodConstraints, methodMap1 ) );
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public RemoteMethodControl setConstraints(
+            MethodConstraints constraints )
+        {
+            return new ConstrainableRegistration( registrationID, mailbox, lease,
+                                                  constraints );
+        }
+
+        /**
+         * {@inheritDoc}
+         */
+        public MethodConstraints getConstraints()
+        {
+            return methodConstraints;
+        }
+
+        /* Note that the superclass's hashCode method is OK as is. */
+        /* Note that the superclass's equals method is OK as is. */
+
+        /* Note that the superclass's hashCode method is OK as is. */
+
+        /**
+         * Returns a proxy trust iterator that is used in
+         * <code>ProxyTrustVerifier</code> to retrieve this object's
+         * trust verifier.
+         */
+        private ProxyTrustIterator getProxyTrustIterator()
+        {
+            return new SingletonProxyTrustIterator( mailbox );
+        }//end getProxyTrustIterator
+
+        /**
+         * Verifies that the registrationID, lease and mailbox fields are
+         * not null, that mailbox implements RemoteMethodControl, and that the
+         * mailbox proxy has the appropriate method constraints.
+         *
+         * @throws InvalidObjectException if registrationID, lease or mailbox
+         *                                is null, if mailbox does not implement RemoteMethodControl,
+         *                                or if server has the wrong constraints
+         */
+        private void readObject( ObjectInputStream s )
+            throws IOException, ClassNotFoundException
+        {
+            /* Note that basic validation of the fields of this class was
+             * already performed in the readObject() method of this class'
+             * super class.
+             */
+            s.defaultReadObject();
+            /* Verify the server and its constraints */
+            ConstrainableProxyUtil.verifyConsistentConstraints( methodConstraints,
+                                                                mailbox,
+                                                                methodMap1 );
+            if( !( lease instanceof ConstrainableLandlordLease ) )
+            {
+                throw new InvalidObjectException
+                    ( "Registration.readObject failure - "
+                      + "lease is not an instance of "
+                      + "ConstrainableLandlordLease" );
+            }//endif
+
+            if( !( listener instanceof ListenerProxy.ConstrainableListenerProxy ) )
+            {
+                throw new InvalidObjectException
+                    ( "Registration.readObject failure - "
+                      + "listener is not an instance of "
+                      + "ListenerProxy.ConstrainableListenerProxy" );
+            }//endif
+
+            /* Verify listener's ID */
+            if( registrationID !=
+                ( (ListenerProxy.ConstrainableListenerProxy) listener ).registrationID )
+            {
+                throw new InvalidObjectException
+                    ( "Registration.readObject "
+                      + "failure - listener ID "
+                      + "is not equal to "
+                      + "proxy ID" );
+            }            
+	}
+    }
+}