As mentioned I've been experimenting with an invariant validating ObjectInputStream, that's backward and forward compatible with Java's Object Serialization stream protocol.

No changes have been made to ObjectOutputStream.

ObjectInputStream has been overridden and reading from the stream has been completely reimplemented.

Classes are still required to implement Serializable, however readObject methods are not called and fields are not set reflectively after construction.

After considering all possibilities, I still find myself favouring constructors.

Definition of "Cumbersome":
    Slow or complicated and therefore inefficient.

During implementation, I've found that static invarient check methods are often shared with other constructors.

If another constructor already has a static invariant checking method, you only need call that constructor.

Performance wise, constructors significantly outperform setting every field using reflection, the more fields an Object has, the greater the performance benefit.

My experience is using constructors is often easier to understand than readObject methods.

Because constructors can be chained, I can perform final freezes in one constructor, then publish safely using another, existing readObject methods cannot provide this functionality. If a final freeze occurs after the readObject method terminates, there is no way to fix existing code that uses unsafe publication.

See that attached example, this is actual production code (with the addition of @AtomicSerial), Java's RMI also has a DGC implementation. In this example using standard Serialization, because a final freeze occurs after readObject exits, the implementation uses unsafe publication, all guarantees are off.

The annotations I've used are:
@AtomicSerial - indicates serial constructor is present.
@ReadInput - provides access to a ReadObject implementation for direct interaction with the stream, prior to object construction, provided for backward compatiblility.

However the existing readObject alternative is all too often:
    Insecure and unsafely published.

The real question:

Is secure Serialization worth the additional work?

To those who would value it, I think the answer will be yes, to those that don't, perhaps not?

Regards,

Peter.




/*
 * 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 net.jini.jeri;

import com.sun.jini.jeri.internal.runtime.DgcClient;
import com.sun.jini.jeri.internal.runtime.Util;
import com.sun.jini.logging.Levels;
import java.io.EOFException;
import java.io.IOException;
import java.io.InvalidObjectException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectInputValidation;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.rmi.NoSuchObjectException;
import java.rmi.RemoteException;
import java.rmi.UnmarshalException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.jini.core.constraint.InvocationConstraints;
import net.jini.id.Uuid;
import net.jini.id.UuidFactory;
import net.jini.io.ObjectStreamContext;
import net.jini.io.context.AcknowledgmentSource;
import net.jini.security.proxytrust.TrustEquivalence;
import org.apache.river.api.io.AtomicSerial;
import org.apache.river.api.io.AtomicSerial.GetArg;
import org.apache.river.api.io.AtomicSerial.ReadInput;
import org.apache.river.api.io.AtomicSerial.ReadObject;

/**
 * References a remote object with an {@link Endpoint Endpoint} for
 * sending requests to the object and a {@link Uuid Uuid} to identify
 * the object at that <code>Endpoint</code>.
 *
 * <p>In addition to the <code>Endpoint</code> and the
 * <code>Uuid</code>, <code>BasicObjectEndpoint</code> instances also
 * contain a flag indicating whether or not the instance participates
 * in distributed garbage collection (DGC).
 *
 * <p>The {@link #newCall newCall} method can be used to send a
 * request to the remote object that this object references.
 *
 * <h4>Distributed Garbage Collection</h4>
 *
 * The <code>BasicObjectEndpoint</code> class acts as the <i>DGC
 * client</i> for all of its instances that participate in DGC (which
 * are called live remote references).  That is, it tracks the
 * existence and reachability of live remote references and makes
 * <i>dirty calls</i> and <i>clean calls</i> to the associated
 * server-side DGC implementations, as described below.
 *
 * <p>The server-side behavior of dirty and clean calls is specified
 * by {@link BasicJeriExporter}.  When the DGC client makes a dirty or
 * clean call to a given <code>Endpoint</code>, the behavior is
 * effectively that of using a <code>BasicObjectEndpoint</code>
 * containing that <code>Endpoint</code> and the object identifier
 * <code>d32cd1bc-273c-11b2-8841-080020c9e4a1</code> (and that doesn't
 * itself participate in DGC), wrapped in a {@link
 * net.jini.jeri.BasicInvocationHandler} with no client or server
 * constraints, wrapped in an instance of a dynamic proxy class that
 * implements an interface with the following remote methods:
 *
 * <pre>
 *     long dirty(Uuid clientID, long sequenceNum, Uuid[] ids)
 *         throws {@link RemoteException};
 *
 *     void clean(Uuid clientID, long sequenceNum, Uuid[] ids, boolean strong)
 *         throws RemoteException;
 * </pre>
 *
 * <code>clientID</code> is the DGC client's universally unique
 * identifier, which is generated using {@link UuidFactory#generate
 * UuidFactory.generate}.  <code>sequenceNum</code> identifies the
 * sequence number of the dirty or clean call with respect to all
 * other dirty and clean calls made by the same DGC client (regardless
 * of the <code>Endpoint</code> that the calls are made to).  All
 * dirty and clean calls made by a DGC client must have a unique
 * sequence number that monotonically increases with the temporal
 * order of the states (of reachable live remote references) that the
 * calls assert.  A dirty call asserts that live remote references
 * with the called <code>Endpoint</code> and each of the object
 * identifiers in <code>ids</code> exist for the identified DGC
 * client.  A clean call asserts that there are no (longer) live
 * remote references with the called <code>Endpoint</code> and each of
 * the object identifiers in <code>ids</code> for the identified DGC
 * client.
 *
 * <p>The tracked live remote references are categorized by their
 * <code>Endpoint</code> and further categorized by their object
 * identifier (with the <code>Endpoint</code> and object identifier
 * pair identifying a remote object).  When a new live remote
 * reference is created, either by construction or deserialization, it
 * is remembered among the live remote references with the same
 * <code>Endpoint</code> and object identifier, and its reachability
 * is tracked with a phantom reference.  If there is not already a
 * live remote reference with the same <code>Endpoint</code> and
 * object identifier, the DGC client makes a dirty call to the
 * server-side DGC implementation at that <code>Endpoint</code>, with
 * that object identifier in the <code>ids</code> argument.  Dirty
 * calls for multiple newly created live remote references with the
 * same <code>Endpoint</code> may be batched as one dirty call (such
 * as for multiple live remote references deserialized from the same
 * stream).
 *
 * <p>Each successful dirty call establishes or renews a lease for the
 * DGC client with the server-side DGC implementation at the
 * <code>Endpoint</code> that the dirty call was made to.  The
 * duration of the lease granted by the server is conveyed as the
 * return value of the dirty call, in milliseconds starting from some
 * time during the processing of the dirty call.  While there are live
 * remote references with a given <code>Endpoint</code>, the DGC
 * client attempts to maintain a valid lease with that
 * <code>Endpoint</code> by renewing its lease with successive dirty
 * calls.  The DGC client should take into consideration network and
 * processing latencies of the previous dirty call and the next
 * required dirty call in choosing when to renew a lease.  If the DGC
 * client has reason to assume that its lease with a given
 * <code>Endpoint</code> might have expired, then in subsequent dirty
 * calls to that <code>Endpoint</code>, it should include the object
 * identifiers of all currently reachable live remote references with
 * that <code>Endpoint</code>.  If a dirty call returns a negative
 * lease duration or throws a {@link NoSuchObjectException}, the DGC
 * client should refrain from making further dirty calls to the same
 * <code>Endpoint</code> until a new live remote reference with that
 * <code>Endpoint</code> is created.
 *
 * <p>If a dirty call fails with a communication exception other than
 * a <code>NoSuchObjectException</code>, the DGC client implementation
 * should make a reasonable effort to retry the dirty call (in a
 * network-friendly manner).  Also, after such a failed dirty call for
 * a given <code>Endpoint</code> and object identifier, any clean call
 * that is made for that same <code>Endpoint</code> and object
 * identifier within a reasonable amount of time should pass
 * <code>true</code> for the <code>strong</code> argument, in case the
 * failed dirty call does eventually get delivered to the server after
 * such a clean call has been processed.
 *
 * <p>When the last remaining live remote reference with a given
 * <code>Endpoint</code> and object identifier is detected to be
 * phantom reachable, the DGC client makes a clean call to the
 * server-side DGC implementation at that <code>Endpoint</code>, with
 * that object identifier in the <code>ids</code> argument.  Clean
 * calls for several object identifiers at the same
 * <code>Endpoint</code> may be batched as one clean call (such as
 * when multiple live remote references with the same
 * <code>Endpoint</code> and different object identifiers are detected
 * to be phantom reachable at the same time).
 *
 * <p>If a clean call fails with a communication exception other than
 * a <code>NoSuchObjectException</code>, the DGC client implementation
 * should make a reasonable effort to retry the clean call, in a
 * network-friendly manner, especially while the DGC client's lease
 * for the <code>Endpoint</code> remains valid (or while dirty calls
 * for the same <code>Endpoint</code> succeed).
 *
 * @author Sun Microsystems, Inc.
 * @since 2.0
 *
 * @com.sun.jini.impl
 *
 * <p>This implementation uses the {@link Logger} named
 * <code>net.jini.jeri.BasicObjectEndpoint</code> to log information
 * at the following levels:
 *
 * <table summary="Describes what is logged by BasicObjectEndpoint at
 *        various logging levels" border=1 cellpadding=5>
 *
 * <tr> <th> Level <th> Description
 *
 * <tr> <td> {@link Levels#HANDLED HANDLED} <td> failure of DGC dirty
 * or clean call
 *
 * <tr> <td> {@link Level#FINEST FINEST} <td> detailed implementation
 * activity
 *
 * </table>
 **/
@AtomicSerial
public final class BasicObjectEndpoint
    implements ObjectEndpoint, TrustEquivalence, Serializable
{
    private static final long serialVersionUID = 3235008605817758127L;

    /** local client-side DGC implementation */
    private static final DgcClient dgcClient = new DgcClient();

    /**
     * maps ObjectInputStream to DgcBatchContext
     * REMIND: We'd really like to use a weak *identity* hash map here--
     * does the lack of equals() security here create a risk?
     */
    private static final Map<ObjectInputStream,DgcBatchContext> streamBatches 
            = new WeakHashMap<ObjectInputStream,DgcBatchContext>(11);

    /**
     * The endpoint to send remote call requests to.
     *
     * @serial
     **/
    private final Endpoint ep;

    /**
     * The object identifier for the remote object.
     *
     * @serial
     **/
    private final Uuid id;

    /**
     * Flag indicating whether or not this
     * <code>BasicObjectEndpoint</code> participates in DGC.
     *
     * @serial
     **/
    private final boolean dgc;

    /** optional local reference to remote object (to maintain reachability) */
    private transient Object impl;
    
    private static boolean checkSerial(Endpoint ep, Uuid id) throws 
InvalidObjectException{
        try {
            return check(ep, id);
        } catch (RuntimeException ex){
            InvalidObjectException e = new InvalidObjectException("Invariants 
not satisfied");
            e.initCause(ex);
            throw e;
        }
    }
    
    private static boolean check(Endpoint ep, Uuid id){
        if (ep == null) {
            throw new NullPointerException("null endpoint");
        }
        if (id == null) {
            throw new NullPointerException("null object identifier");
        }
        return true;
    }
    
    /**
     * {@AtomicSerial} to maintain backward compatibility with existing serial 
form. 
     * @return 
     */
    @ReadInput
    private static ReadObject getRO(){
        return new RO();
    }
    
    /**
     * {@link AtomicSerial} constructor.
     * @param arg
     * @throws IOException 
     */
    BasicObjectEndpoint(GetArg arg) throws IOException {
        this(checkSerial((Endpoint) arg.get("ep", null), (Uuid) arg.get("id", 
null)),
            (Endpoint) arg.get("ep", null),
            (Uuid)arg.get("id", null),
            arg.get("dgc", false)
        ); // Invariants are checked and final fields frozen.
        /*
         * If DGC-enabled, register this live reference instance to be
         * tracked by the local client-side DGC implementation.  Use
         * an ObjectInputValidation for delayed registration so that
         * multiple live references in the object graph being
         * deserialized can be registered in one batch.
         */
        if (dgc) {
            RO r = (RO) arg.getReader();
            r.batchContext.addLiveRef(this); // Safe publication of this.
        }
    }
    
    private BasicObjectEndpoint(boolean check, Endpoint ep, Uuid id, boolean 
enableDGC){
        this.ep = ep;
        this.id = id;
        this.dgc = enableDGC;
        // final field freeze occurs after constructor exits.
    }

    /**
     * Creates a new <code>BasicObjectEndpoint</code> to reference a
     * remote object at the specified <code>Endpoint</code> with the
     * specified <code>Uuid</code>.
     *
     * @param ep the endpoint to send remote call requests for the
     * remote object to
     *
     * @param id the object identifier for the remote object
     *
     * @param enableDGC flag indicating whether or not the
     * <code>BasicObjectEndpoint</code> participates in DGC
     *
     * @throws NullPointerException if <code>e</code> or
     * <code>id</code> is <code>null</code>
     **/
    public BasicObjectEndpoint(Endpoint ep, Uuid id, boolean enableDGC) {
        this(check(ep, id), ep, id, enableDGC); // Check invariants, freeze 
final fields.
        /*
         * If DGC-enabled, register this live reference instance to be
         * tracked by the local client-side DGC system.
         */
        if (dgc) {
            dgcClient.registerRefs(ep, Collections.singleton(this)); // Safe 
publication of this reference.
        }
    }

    /**
     * Creates a new BasicObjectEndpoint to reference the supplied
     * remote object in the current virtual machine with the specified
     * Endpoint, Uuid, and enableDGC status.
     **/
    BasicObjectEndpoint(Endpoint ep, Uuid id, boolean enableDGC, Object impl) {
        this.ep = ep;
        this.id = id;
        this.dgc = enableDGC;

        if (dgc) {
            /*
             * When this constructor is used, this live reference refers to a
             * remote object in the current VM, so instead of registering for
             * participation in the DGC protocol, we just keep a direct strong
             * reference to the impl to maintain its reachability.
             */
            this.impl = impl;
        }
    }

    /**
     * {@inheritDoc}
     *
     * <p>This method first invokes {@link Endpoint#newRequest
     * newRequest} on this <code>BasicObjectEndpoint</code>'s
     * contained <code>Endpoint</code> with the specified constraints
     * to obtain an <code>OutboundRequestIterator</code>.  It then
     * wraps the obtained iterator in another
     * <code>OutboundRequestIterator</code> and returns the wrapped
     * iterator.
     *
     * <p>The methods of the returned
     * <code>OutboundRequestIterator</code> behave as follows:
     *
     * <p>{@link OutboundRequestIterator#hasNext boolean hasNext()}:
     *
     * <blockquote>
     *
     * <p>Returns <code>true</code> if this iterator supports making
     * at least one more attempt to communicate the remote call, and
     * <code>false</code> otherwise.
     *
     * <p>This method invokes <code>hasNext</code> on the underlying
     * iterator and returns the result.
     *
     * <p>The security context in which this method is invoked may be
     * used for subsequent verification of security permissions; see
     * the {@link OutboundRequestIterator#next next} method
     * specification for more details.
     *
     * </blockquote>
     *
     * <p>{@link OutboundRequestIterator#next OutboundRequest next()}:
     *
     * <blockquote>
     *
     * <p>Initiates an attempt to communicate the remote call to the
     * referenced remote object.
     *
     * <p>This method invokes <code>next</code> on the underlying
     * iterator to obtain an <code>OutboundRequest</code>.  Then it
     * writes the object identifier of the
     * <code>BasicObjectEndpoint</code> that produced this iterator to
     * the request's output stream by invoking {@link
     * Uuid#write(OutputStream) Uuid.write(OutputStream)} with the
     * request output stream, and then it returns the request.
     *
     * <p>Throws {@link NoSuchElementException} if this iterator does
     * not support making another attempt to communicate the remote
     * call (that is, if <code>hasNext</code> would return
     * <code>false</code>).
     *
     * <p>Throws {@link IOException} if an <code>IOException</code> is
     * thrown by the invocation of <code>next</code> on the underlying
     * iterator or by the subsequent I/O operations.
     *
     * <p>Throws {@link SecurityException} if a
     * <code>SecurityException</code> is thrown by the invocation of
     * <code>next</code> on the underlying iterator or by the
     * subsequent I/O operations.
     *
     * </blockquote>
     *
     * @throws NullPointerException {@inheritDoc}
     **/
    public OutboundRequestIterator newCall(InvocationConstraints constraints) {
        final OutboundRequestIterator iter = ep.newRequest(constraints);
        return new OutboundRequestIterator() {

            public boolean hasNext() {
                return iter.hasNext();
            }

            public OutboundRequest next() throws IOException {
                OutboundRequest call = iter.next();
                boolean ok = false;
                try {
                    id.write(call.getRequestOutputStream());
                    ok = true;
                } finally {
                    if (!ok) {
                        call.abort();   // because caller cannot
                    }
                }
                return call;
            }
        };
    }

    /**
     * {@inheritDoc}
     *
     * <p>This method reads a byte from the response input stream of
     * <code>call</code>.  If an {@link IOException} is thrown reading
     * the byte, that exception is thrown to the caller.  If reading
     * the byte otherwise indicates EOF, an {@link EOFException} is
     * thrown to the caller.  If the byte is <code>0x00</code>, then
     * this method returns a {@link NoSuchObjectException} indicating
     * that there is no remote object exported with the object
     * identifier at the remote endpoint.  If the byte is
     * <code>0x01</code>, then this method returns <code>null</code>,
     * indicating that a remote object corresponding to the object
     * identifier and endpoint is exported, and thus the caller may
     * proceed to read the response of the remote call.  If the byte
     * is any other value, this method returns an {@link
     * UnmarshalException} indicating that a protocol error occurred.
     *
     * @throws NullPointerException {@inheritDoc}
     **/
    public RemoteException executeCall(OutboundRequest call)
        throws IOException
    {
        // assume that the request output stream has been closed

        int status = call.getResponseInputStream().read();
        switch (status) {

        case -1:
            throw new EOFException();

        case 0x00:
            // REMIND: close the response input stream?
            Exception ex = null;
            try {
                call.getResponseInputStream().close();
            } catch (IOException e){
                ex = e;
            }
            // REMIND: Do we want to read a server-supplied reason string?
            if (ex != null){
                return new NoSuchObjectException("no such object in table, 
input stream close threw IOException: " + ex);
            }
            return new NoSuchObjectException("no such object in table");

        case 0x01:
            return null;

        default:
            // REMIND: close the response input stream?
            Exception exc = null;
            try {
                call.getResponseInputStream().close();
            } catch (IOException e){
                exc = e;
            }
            // REMIND: Do we really want this failure mode here?
            if (exc != null){
                return new UnmarshalException("unexpected invocation status: " +
                                          Integer.toHexString(status), exc);
            }
            return new UnmarshalException("unexpected invocation status: " +
                                          Integer.toHexString(status));
        }
    }

    /**
     * Returns the <code>Endpoint</code> for the referenced remote
     * object.
     *
     * @return the <code>Endpoint</code> for the referenced remote
     * object
     **/
    public Endpoint getEndpoint() {
        return ep;
    }

    /**
     * Returns the object identifier for the referenced remote object.
     *
     * @return the object identifier for the referenced remote object
     **/
    public Uuid getObjectIdentifier() {
        return id;
    }

    /**
     * Returns <code>true</code> if this
     * <code>BasicObjectEndpoint</code> participates in DGC and
     * <code>false</code> otherwise.
     *
     * @return <code>true</code> if this
     * <code>BasicObjectEndpoint</code> participates in DGC and
     * <code>false</code> otherwise
     **/
    public boolean getEnableDGC() {
        return dgc;
    }

    /**
     * Returns the hash code value for this
     * <code>BasicObjectEndpoint</code>.
     *
     * @return the hash code value for this
     * <code>BasicObjectEndpoint</code>
     **/
    public int hashCode() {
        return id.hashCode();
    }

    /**
     * Compares the specified object with this
     * <code>BasicObjectEndpoint</code> for equality.
     *
     * <p>This method returns <code>true</code> if and only if
     *
     * <ul>
     *
     * <li>the specified object is also a
     * <code>BasicObjectEndpoint</code>,
     *
     * <li>the object identifier and <code>enableDGC</code> flag in
     * the specified object are equal to the ones in this object, and
     *
     * <li>the <code>Endpoint</code> in the specified object has
     * the same class and is equal to the one in this object.
     *
     * </ul>
     *
     * @param obj the object to compare with
     *
     * @return <code>true</code> if <code>obj</code> is equivalent to
     * this object; <code>false</code> otherwise
     **/
    public boolean equals(Object obj) {
        if (obj == this) {
            return true;
        } else if (!(obj instanceof BasicObjectEndpoint)) {
            return false;
        }
        BasicObjectEndpoint other = (BasicObjectEndpoint) obj;
        return
            id.equals(other.id) &&
            dgc == other.dgc &&
            Util.sameClassAndEquals(ep, other.ep);
    }

    /**
     * Returns <code>true</code> if the specified object (which is not
     * yet known to be trusted) is equivalent in trust, content, and
     * function to this known trusted object, and <code>false</code>
     * otherwise.
     *
     * <p>This method returns <code>true</code> if and only if
     *
     * <ul>
     *
     * <li>the specified object is also a
     * <code>BasicObjectEndpoint</code>,
     *
     * <li>the object identifier and <code>enableDGC</code> flag in
     * the specified object are equal to the ones in this object, and
     *
     * <li>this object's <code>Endpoint</code> is an instance of
     * {@link TrustEquivalence} and invoking its
     * <code>checkTrustEquivalence</code> method with the specified
     * object's <code>Endpoint</code> returns <code>true</code>.
     *
     * </ul>
     **/
    public boolean checkTrustEquivalence(Object obj) {
        if (obj == this) {
            return true;
        } else if (!(obj instanceof BasicObjectEndpoint)) {
            return false;
        }
        BasicObjectEndpoint other = (BasicObjectEndpoint) obj;
        return
            id.equals(other.id) &&
            dgc == other.dgc &&
            Util.checkTrustEquivalence(ep, other.ep);
    }

    /**
     * Returns a string representation of this
     * <code>BasicObjectEndpoint</code>.
     *
     * @return a string representation of this
     * <code>BasicObjectEndpoint</code>
     **/
    public String toString() {
        return
            "BasicObjectEndpoint[" + (dgc ? "DGC," : "") + id + "," + ep + "]";
    }

    /**
     * If this <code>BasicObjectEndpoint</code> participates in DGC
     * and if <code>out</code> is an instance of {@link
     * ObjectStreamContext} and its context collection contains an
     * {@link AcknowledgmentSource}, ensures that an {@link
     * net.jini.io.context.AcknowledgmentSource.Listener} is
     * registered (with the <code>AcknowledgmentSource</code>) that
     * will hold a strong reference to this
     * <code>BasicObjectEndpoint</code> until the listener's {@link
     * net.jini.io.context.AcknowledgmentSource.Listener#acknowledgmentReceived
     * acknowledgmentReceived} method is invoked (or some other
     * implementation-specific event occurs, such as a timeout
     * expiration).
     **/
    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();

        if (dgc && out instanceof ObjectStreamContext) {
            Collection context =
                ((ObjectStreamContext) out).getObjectStreamContext();
            for (Iterator i = context.iterator(); i.hasNext();) {
                Object e = i.next();
                if (e instanceof AcknowledgmentSource) {
                    AcknowledgmentSource ackSource = (AcknowledgmentSource) e;
                    ackSource.addAcknowledgmentListener(new AckListener(this));
                    break;
                }
            }
        }
    }

    /**
     * Holds a strong reference to a BasicObjectEndpoint until an
     * acknowledgment has been received.
     *
     * REMIND: Clear reference after a certain timeout regardless?
     **/
    private static class AckListener implements AcknowledgmentSource.Listener {
        private volatile BasicObjectEndpoint ref;
        AckListener(BasicObjectEndpoint ref) { this.ref = ref; }
        public void acknowledgmentReceived(boolean received) { ref = null; }
    }

    /**
     * If this <code>BasicObjectEndpoint</code> participates in DGC,
     * initiates asynchronous DGC activity for it.
     *
     * @throws InvalidObjectException if the <code>Endpoint</code> or
     * the object identifier is <code>null</code>
     **/
    private void readObject(ObjectInputStream in)
        throws IOException, ClassNotFoundException
    {
        in.defaultReadObject();
        checkSerial(ep, id);
            
        /*
         * If DGC-enabled, register this live reference instance to be
         * tracked by the local client-side DGC implementation.  Use
         * an ObjectInputValidation for delayed registration so that
         * multiple live references in the object graph being
         * deserialized can be registered in one batch.
         */
        if (dgc) {
            /*
             * REMIND: short circuit lookup with thread local,
             * to avoid synchronization overhead in the common case?
             *
             * Peter Firmstone, 13th December 2014: This synchronization
             * appears to be uncontended in stress tests; there is no 
             * need to optimise at this time.
             */
            DgcBatchContext batchContext;
            synchronized (streamBatches) {
                batchContext = (DgcBatchContext) streamBatches.get(in);
                if (batchContext == null) {
                    batchContext = new DgcBatchContext();
                    try {                               // REMIND: priority??
                        in.registerValidation(batchContext, 0);
                    } catch (InvalidObjectException e) { // should be NPE
                        throw new AssertionError();
                    }
                    streamBatches.put(in, batchContext);
                }
            }
            batchContext.addLiveRef(this); // Unsafe publication, final fields 
haven't been frozen yet.
        }
    }
    
    private static final class RO implements ReadObject{
        
        DgcBatchContext batchContext;

        @Override
        public void read(ObjectInput in) throws IOException, 
ClassNotFoundException {
            /*
             * REMIND: short circuit lookup with thread local,
             * to avoid synchronization overhead in the common case?
             *
             * Peter Firmstone, 13th December 2014: This synchronization
             * appears to be uncontended in stress tests; there is no 
             * need to optimise at this time.
             */
            synchronized (streamBatches) {
                batchContext = (DgcBatchContext) streamBatches.get(in);
                if (batchContext == null) {
                    batchContext = new DgcBatchContext();
                    try {                               // REMIND: priority??
                        ((ObjectInputStream) 
in).registerValidation(batchContext, 0);
                    } catch (InvalidObjectException e) { // should be NPE
                        throw new AssertionError();
                    }
                    streamBatches.put((ObjectInputStream) in, batchContext);
                }
            }
        }
        
    }

    /**
     * Collects live references to be registered with the local
     * client-side DGC implementation and registers them in
     * Endpoint-specific batches.
     *
     * REMIND: lack of thread safety OK?
     **/
    private static class DgcBatchContext implements ObjectInputValidation {

        /** maps Endpoint to List<BasicObjectEndpoint> */
        private final Map endpointTable = new HashMap(3);

        DgcBatchContext() { }

        void addLiveRef(BasicObjectEndpoint ref) {
            /*
             * Organize collected live references into separate lists
             * for each distinct endpoint.
             */
            Endpoint endpoint = ref.getEndpoint();
            Collection refList = (Collection) endpointTable.get(endpoint);
            if (refList == null) {
                refList = new ArrayList();
                endpointTable.put(endpoint, refList);
            }
            refList.add(ref);
        }

        public void validateObject() {  // doesn't throw InvalidObjectException
            /*
             * Perform a batch DGC registration for each list of
             * live references to the same endpoint.
             */
            Iterator iter = endpointTable.entrySet().iterator();
            while (iter.hasNext()) {
                Map.Entry entry = (Map.Entry) iter.next();
                Endpoint endpoint = (Endpoint) entry.getKey();
                Collection refList = (Collection) entry.getValue();
                dgcClient.registerRefs(endpoint, refList);
            }
            endpointTable.clear();
        }
    }
}

Reply via email to