Package net.jini.jeri

Source Code of net.jini.jeri.BasicObjectEndpoint$DgcBatchContext

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

/**
* 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>
**/
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 streamBatches = new WeakHashMap(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;

    /**
     * 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) {
  if (ep == null) {
      throw new NullPointerException("null endpoint");
  }
  if (id == null) {
      throw new NullPointerException("null object identifier");
  }
  this.ep = ep;
  this.id = id;
  this.dgc = enableDGC;

  /*
   * 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));
  }
    }

    /**
     * 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?
      // REMIND: Do we want to read a server-supplied reason string?
      return new NoSuchObjectException("no such object in table");

  case 0x01:
      return null;

  default:
      // REMIND: close the response input stream?
      // REMIND: Do we really want this failure mode here?
      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();
  if (ep == null) {
      throw new InvalidObjectException("null endpoint");
  }
  if (id == null) {
      throw new InvalidObjectException("null object identifier");
  }
     
  /*
   * 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?
       */
      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);
  }
    }

    /**
     * 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();
  }
    }
}
TOP

Related Classes of net.jini.jeri.BasicObjectEndpoint$DgcBatchContext

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.