/* * 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.Util; import com.sun.jini.logging.Levels; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.io.Serializable; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; import java.net.ProtocolException; import java.rmi.ConnectIOException; import java.rmi.MarshalException; import java.rmi.RemoteException; import java.rmi.UnexpectedException; import java.rmi.UnmarshalException; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.logging.Logger; import net.jini.core.constraint.Integrity; import net.jini.core.constraint.InvocationConstraint; import net.jini.core.constraint.InvocationConstraints; import net.jini.core.constraint.MethodConstraints; import net.jini.core.constraint.RemoteMethodControl; import net.jini.io.MarshalInputStream; import net.jini.io.MarshalOutputStream; import net.jini.io.UnsupportedConstraintException; import net.jini.io.context.IntegrityEnforcement; import net.jini.security.proxytrust.TrustEquivalence; /** * A basic implementation of the <code>InvocationHandler</code> interface. * This invocation handler implements Java(TM) Remote Method Invocation * (Java RMI) call semantics when handling * a remote invocation to a remote object. * * <p><code>BasicInvocationHandler</code> instances contain an * <code>ObjectEndpoint</code>, optional client constraints, and * optional server constraints. The client and server constraints * control the handling of remote methods, and they are represented as * {@link MethodConstraints} objects that map remote methods to * corresponding per-method constraints. * * <p>Invocation requests sent via the {@link #invoke invoke} method use * the protocol defined in that method. This invocation handler also * assumes that the return value conforms to the protocol outlined in the * {@link BasicInvocationDispatcher#dispatch * BasicInvocationDispatcher.dispatch} method. * * @author Sun Microsystems, Inc. * @see BasicInvocationDispatcher * @since 2.0 * * @com.sun.jini.impl * * This implementation's {@link #invoke invoke} method throws {@link * IllegalArgumentException} if a remote invocation is to be made and * the <code>proxy</code> argument is an instance of an interface * whose binary name is * <code>javax.management.MBeanServerConnection</code> or any of the * names produced by the following procedure: * * <blockquote> * * For each resource named * <code>com/sun/jini/proxy/resources/InvocationHandler.moreProhibitedProxyInterfaces</code> * that is visible to the system class loader, the contents of the * resource are parsed as UTF-8 text to produce a list of interface * names. The resource must contain a list of binary names of * interfaces, one per line. Space and tab characters surrounding * each name, as well as blank lines, are ignored. The comment * character is <tt>'#'</tt>; on each line, all characters starting * with the first comment character are ignored. * * </blockquote> * * <p>This implementation uses the {@link Logger} named * <code>net.jini.jeri.BasicInvocationHandler</code> to log * information at the following levels: * * <table summary="Describes what is logged by BasicInvocationHandler at * various logging levels" border=1 cellpadding=5> * * <tr> <th> Level <th> Description * * <tr> <td> {@link Levels#FAILED FAILED} <td> exception thrown from final * attempt to communicate a remote call * * <tr> <td> {@link Levels#HANDLED HANDLED} <td> exception caught in * attempt to communicate a remote call * * <tr> <td> {@link Level#FINE FINE} <td> remote method being invoked * * <tr> <td> {@link Level#FINE FINE} <td> successful return of remote call * * <tr> <td> {@link Level#FINEST FINEST} <td> more detailed information on * the above (for example, actual argument and return values) * * </table> **/ public class BasicInvocationHandler implements InvocationHandler, TrustEquivalence, Serializable { private static final long serialVersionUID = -783920361025791412L; /** * invoke logger **/ private static final Logger logger = Logger.getLogger("net.jini.jeri.BasicInvocationHandler"); /** size of the method constraint cache (per instance) */ private static final int CACHE_SIZE = 3; /** * The object endpoint for communicating with the remote object. * * @serial **/ private final ObjectEndpoint oe; /** * The client constraints, or <code>null</code>. * * @serial **/ private final MethodConstraints clientConstraints; /** * The server constraints, or <code>null</code>. * * @serial **/ private final MethodConstraints serverConstraints; /* * The method constraint cache maps remote methods to their * combined client and server constraints: */ /** lock guarding cacheIndex, methodCache, and constraintCache */ private transient Object cacheLock = new Object(); /** next index to (over)write in the method constraint cache */ private transient int cacheIndex; /** method constraint cache keys (unused entries are null) */ private transient Method[] methodCache; /** method constraint cache values (unused entries are null) */ private transient InvocationConstraints[] constraintCache; /** * Creates a new <code>BasicInvocationHandler</code> with the * specified <code>ObjectEndpoint</code> and server constraints. * * <p>The client constraints of the created * <code>BasicInvocationHandler</code> will be <code>null</code>. * * @param oe the <code>ObjectEndpoint</code> for this invocation handler * @param serverConstraints the server constraints, or <code>null</code> * * @throws NullPointerException if <code>oe</code> is <code>null</code> **/ public BasicInvocationHandler(ObjectEndpoint oe, MethodConstraints serverConstraints) { if (oe == null) { throw new NullPointerException(); } this.oe = oe; this.clientConstraints = null; this.serverConstraints = serverConstraints; } /** * Creates a new <code>BasicInvocationHandler</code> with the * specified client constraints and with the same * <code>ObjectEndpoint</code> and server constraints as the given * other <code>BasicInvocationHandler</code>. * * <p>This constructor is intended for use by the * <code>BasicInvocationHandler</code> implementation of the * {@link #setClientConstraints} method. To create a copy of a * given <code>BasicInvocationHandler</code> with new client * constraints, use the {@link RemoteMethodControl#setConstraints * RemoteMethodControl.setConstraints} method on the containing * proxy. * * @param other the <code>BasicInvocationHandler</code> to obtain the * <code>ObjectEndpoint</code> and server constraints from * @param clientConstraints the client constraints, or * <code>null</code> * * @throws NullPointerException if <code>other</code> is * <code>null</code> **/ public BasicInvocationHandler(BasicInvocationHandler other, MethodConstraints clientConstraints) { this.oe = other.oe; this.clientConstraints = clientConstraints; this.serverConstraints = other.serverConstraints; } /** * Processes a method invocation made on the encapsulating * proxy instance, <code>proxy</code>, and returns the result. * This method is invoked when a method is invoked on a proxy * instance that this handler is associated with. * * <p><code>BasicInvocationHandler</code> implements this method * as follows: * * <p>If <code>method</code> is one of the following methods, it * is processed as described below: * * <ul> * * <li>{@link Object#hashCode Object.hashCode}: Returns the hash * code value for the proxy. * * <li>{@link Object#equals Object.equals}: Returns * <code>true</code> if the argument (<code>args[0]</code>) is an * instance of a dynamic proxy class that implements the same * ordered set of interfaces as <code>proxy</code> and this * invocation handler is equal to the invocation handler of that * argument, and returns <code>false</code> otherwise. * * <li>{@link Object#toString Object.toString}: Returns a string * representation of the proxy. * * <li>{@link RemoteMethodControl#setConstraints * RemoteMethodControl.setConstraints}: Returns a new proxy * instance of the same class as <code>proxy</code> containing a * new invocation handler with the specified new client * constraints. The new invocation handler is created by invoking * the {@link #setClientConstraints setClientConstraints} method * of this object with the specified client constraints * (<code>args[0]</code>). An exception is thrown if * <code>proxy</code> is not an instance of a dynamic proxy class * containing this invocation handler. * * <li>{@link RemoteMethodControl#getConstraints * RemoteMethodControl.getConstraints}: Returns this * <code>BasicInvocationHandler</code>'s client constraints. * * <li>{@link TrustEquivalence#checkTrustEquivalence * TrustEquivalence.checkTrustEquivalence}: Returns * <code>true</code> if the argument (<code>args[0]</code>) is an * instance of a dynamic proxy class that implements the same * ordered set of interfaces as <code>proxy</code> and invoking * the {@link #checkTrustEquivalence checkTrustEquivalence} method * of this object with the invocation handler of that argument * returns <code>true</code>, and returns <code>false</code> * otherwise. * * </ul> * * <p>Otherwise, a remote call is made as follows: * * <p>The object endpoint's {@link ObjectEndpoint#newCall newCall} * method is invoked to obtain an {@link OutboundRequestIterator}, * passing constraints obtained by combining the client and server * constraints for the specified remote method and making them * absolute. If the returned iterator's {@link * OutboundRequestIterator#hasNext hasNext} method returns * <code>false</code>, then this method throws a {@link * ConnectIOException}. Otherwise, the iterator is used to make * one or more attempts to communicate the remote call. Each * attempt proceeds as follows: * * <blockquote> * * The iterator's {@link OutboundRequestIterator#next next} method * is invoked to obtain an {@link OutboundRequest OutboundRequest} * for the current attempt. If <code>next</code> returns * normally, the request's {@link * OutboundRequest#getUnfulfilledConstraints * getUnfulfilledConstraints} method is invoked, and if the * returned requirements or preferences include {@link * Integrity#YES Integrity.YES}, object integrity is enforced for * the current remote call attempt. If the returned requirements * include any constraint other than an {@link Integrity} * constraint, an {@link UnsupportedConstraintException} is * generated and, as described below, wrapped and handled like any * other <code>Exception</code> thrown from a remote call attempt. * Otherwise, the marshalling of the remote call proceeds with the * following steps in order: * * <ul> * * <li>A byte of value <code>0x00</code> is written to the request * output stream of the <code>OutboundRequest</code> to indicate * the version of the marshalling protocol being used. * * <li>A byte is written to specify object integrity enforcement: * <code>0x01</code> if object integrity is being enforced for * this remote call attempt, and <code>0x00</code> otherwise. * * <li>A client context collection is created containing an {@link * IntegrityEnforcement} element that reflects whether or not * object integrity is being enforced for this remote call * attempt. * * <li>The {@link #createMarshalOutputStream * createMarshalOutputStream} method is invoked, passing * <code>proxy</code>, <code>method</code>, the * <code>OutboundRequest</code>, and the client context, to create * the marshal output stream for marshalling the remote call. * * <li>The {@link #marshalMethod marshalMethod} method of this * invocation handler is invoked with <code>proxy</code>, * <code>method</code>, the marshal output stream, and the client * context. * * <li>The {@link #marshalArguments marshalArguments} method of * this invocation handler is invoked with <code>proxy</code>, * <code>method</code>, <code>args</code>, the marshal output * stream, and the client context. * * <li>The marshal output stream is closed. * * </ul> * * <p>Then the object endpoint's {@link ObjectEndpoint#executeCall * executeCall} method is invoked with the * <code>OutboundRequest</code>. If <code>executeCall</code> * returns a <code>RemoteException</code>, then this method throws * that exception (and thus the remote call attempt iteration * terminates). If <code>executeCall</code> returns * <code>null</code>, then the unmarshalling of the call response * proceeds as follows: * * <p>A byte is read from the response input stream of the * <code>OutboundRequest</code>: * * <ul> * * <li>If the byte is <code>0x00</code>, indicating a marshalling * protocol version mismatch, a {@link ProtocolException} is * generated and, as described below, wrapped and handled like any * other <code>Exception</code> thrown from a remote call attempt. * * <li>If the byte is <code>0x01</code>, indicating a normal * return, the {@link #createMarshalInputStream * createMarshalInputStream} method is invoked, passing * <code>proxy</code>, <code>method</code>, the * <code>OutboundRequest</code>, a <code>boolean</code> indicating * whether or not object integrity is being enforced, and the * client context, to create the marshal input stream for * unmarshalling the response, and the {@link #unmarshalReturn * unmarshalReturn} method of this invocation handler is invoked * with <code>proxy</code>, <code>method</code>, the marshal input * stream, and the client context. This method returns the value * returned by <code>unmarshalReturn</code> (and thus the remote * call attempt iteration terminates). * * <li>If the byte is <code>0x02</code>, indicating an exceptional * return, a marshal input stream is created by calling the * <code>createMarshalInputStream</code> method as described for * the previous case, and the {@link #unmarshalThrow * unmarshalThrow} method of this invocation handler is invoked * with <code>proxy</code>, <code>method</code>, the marshal input * stream, and the client context. This method throws the * exception returned by <code>unmarshalThrow</code> (and thus the * remote call attempt iteration terminates). * * <li>If the byte is any other value, a * <code>ProtocolException</code> is generated and, as described * below, wrapped and handled like any other * <code>Exception</code> thrown from a remote call attempt. * * </ul> * * <p>If an <code>IOException</code> is thrown during the attempt * to communicate the remote call, then it is wrapped in a * <code>RemoteException</code> as follows: * * <ul> * * <li>If <code>marshalMethod</code> was not invoked for this * attempt, or if an invocation of {@link * OutboundRequest#getDeliveryStatus getDeliveryStatus} on the * <code>OutboundRequest</code> returns <code>false</code>, or if * a marshalling protocol version mismatch was detected, then * * <ul> * * <li>if the <code>IOException</code> is a {@link * java.net.UnknownHostException java.net.UnknownHostException}, * it is wrapped in a {@link java.rmi.UnknownHostException * java.rmi.UnknownHostException}; * * <li>if it is a {@link java.net.ConnectException * java.net.ConnectException}, it is wrapped in a {@link * java.rmi.ConnectException java.rmi.ConnectException}; * * <li>if it is any other <code>IOException</code>, it is wrapped * in a {@link ConnectIOException}. * * </ul> * * <li>Otherwise, if <code>executeCall</code> was not invoked for * this attempt, the <code>IOException</code> is wrapped in a * {@link MarshalException}, and if <code>executeCall</code> was * invoked, it is wrapped in an {@link UnmarshalException}. * * </ul> * * <p>If a {@link ClassNotFoundException} is thrown * during the unmarshalling, then it is wrapped in an {@link * UnmarshalException}. * * <p>In all cases, either the request output stream and the * response input stream will be closed or the * <code>OutboundRequest</code> will be aborted before this * attempt completes. * * </blockquote> * * <p>If an attempt to communicate the remote call throws an * <code>Exception</code> (other than an exception returned by * <code>executeCall</code> or <code>unmarshalThrow</code>, which * terminates the remote call attempt iteration), then if * <code>marshalMethod</code> was not invoked for the attempt or * if an invocation of <code>getDeliveryStatus</code> on the * <code>OutboundRequest</code> returns <code>false</code>, then * if the iterator's <code>hasNext</code> method returns * <code>true</code>, then another attempt is made. Otherwise, * this method throws the <code>Exception</code> thrown by the * last attempt (possibly wrapped as described above). * * <p>Note that invoking a remote method on a remote object via this * invoke method preserves "at-most-once" call semantics. At-most-once * call semantics guarantees that the remote call will either a) not * execute, b) partially execute, or c) execute exactly once at the remote * site. With Java RMI's at-most-once call semantics, arguments may be * marshalled more than once for a given remote call. * * <p>A subclass can override this method to handle the methods of * any additional non-remote interfaces implemented by the proxy * or to otherwise control invocation handling behavior. * * <p>The semantics of this method are unspecified if the * arguments could not have been produced by an instance of some * valid dynamic proxy class containing this invocation handler. * This method throws {@link IllegalArgumentException} if * <code>proxy</code> is an instance of * <code>InvocationHandler</code> or, if a remote call is to be * made, any of the superinterfaces of <code>proxy</code>'s class * have a method with the same name and parameter types as * <code>method</code> but that does not declare * <code>RemoteException</code> or a superclass of * <code>RemoteException</code> in its <code>throws</code> clause * (even if such a method is not a member of any of the direct * superinterfaces of <code>proxy</code>'s class because of * overriding). * * @throws Throwable {@inheritDoc} * * @see java.lang.reflect.UndeclaredThrowableException **/ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (proxy instanceof InvocationHandler) { throw new IllegalArgumentException( "proxy cannot be an invocation handler"); } else if (method.getDeclaringClass() == Object.class) { return invokeObjectMethod(proxy, method, args); } else if (method.getDeclaringClass() == RemoteMethodControl.class) { /* * REMIND: This optimization (testing the identity of the * method's declaring class) fails if the proxy's class * implements, instead of RemoteMethodControl directly, a * subinterface of RemoteMethodControl that overrides the * setConstraints method. This problem could be fixed by * using a more expensive Class.isAssignableFrom check, * but even that would fail if the proxy class implements, * prior to a subinterface of RemoteMethodControl, another * interface that also declares a method with the same * signature as RemoteMethodControl.setConstraints (this * case does seem less unlikely). It seems that to cover * all cases, we should we testing the signature (name and * parameter types) of the method directly. */ return invokeRemoteMethodControlMethod(proxy, method, args); } else if (method.getDeclaringClass() == TrustEquivalence.class) { /* * REMIND: Ditto. */ return invokeTrustEquivalenceMethod(proxy, method, args); } else { return invokeRemoteMethod(proxy, method, args); } } /** * Handles java.lang.Object methods. **/ private Object invokeObjectMethod(Object proxy, Method method, Object[] args) { String name = method.getName(); if (name.equals("hashCode")) { return new Integer(hashCode()); } else if (name.equals("equals")) { Object obj = args[0]; boolean b = proxy == obj || (obj != null && Util.sameProxyClass(proxy, obj) && equals(Proxy.getInvocationHandler(obj))); return Boolean.valueOf(b); } else if (name.equals("toString")) { return proxyToString(proxy); } else { throw new IllegalArgumentException( "unexpected Object method: " + method); } } /** * Handles RemoteMethodControl methods. **/ private Object invokeRemoteMethodControlMethod(Object proxy, Method method, Object[] args) { String name = method.getName(); if (name.equals("setConstraints")) { if (Proxy.getInvocationHandler(proxy) != this) { throw new IllegalArgumentException("not proxy for this"); } Class proxyClass = proxy.getClass(); return Proxy.newProxyInstance( getProxyLoader(proxyClass), proxyClass.getInterfaces(), setClientConstraints((MethodConstraints) args[0])); // IllegalArgumentException means proxy argument was bogus } else if (name.equals("getConstraints")) { return clientConstraints; } else { throw new AssertionError(method); } } /** * Handles TrustEquivalence methods. **/ private Object invokeTrustEquivalenceMethod(Object proxy, Method method, Object[] args) { String name = method.getName(); if (name.equals("checkTrustEquivalence")) { Object obj = args[0]; boolean b = proxy == obj || (obj != null && Util.sameProxyClass(proxy, obj) && checkTrustEquivalence(Proxy.getInvocationHandler(obj))); return Boolean.valueOf(b); } else { throw new AssertionError(method); } } /** * Holds information about the communication failure of a remote * call attempt. **/ private static class Failure { /** exception representing the communication failure */ final Throwable exception; /** failure is safe to retry (and possibly worth retrying) after */ final boolean retry; Failure(Throwable exception, boolean retry) { this.exception = exception; this.retry = retry; } } /** * Handles remote methods. **/ private Object invokeRemoteMethod(Object proxy, Method method, Object[] args) throws Throwable { Util.checkProxyRemoteMethod(proxy.getClass(), method); InvocationConstraints constraints = getConstraints(method); if (logger.isLoggable(Level.FINE)) { logCall(method, args, constraints); } OutboundRequestIterator iter = oe.newCall(constraints); if (!iter.hasNext()) { throw new ConnectIOException("iterator produced no requests", new IOException("iterator produced no requests")); } Failure failure = null; do { if (logger.isLoggable(Levels.HANDLED)) { if (failure != null) { logThrow(Levels.HANDLED, method, failure.exception, false); } } Object result = invokeRemoteMethodOnce(proxy, method, args, iter, constraints); if (result instanceof Failure) { failure = (Failure) result; } else { return result; } } while (failure.retry && iter.hasNext()); /* * If all attempts failed with communication failures, then * throw the exception representing the last communication * failure. */ if (logger.isLoggable(Levels.FAILED)) { if (failure != null) { logThrow(Levels.FAILED, method, failure.exception, false); } } throw failure.exception; } /** * Make one attempt to invoke a remote method. * * If this method returns an object that is not a Failure * instance, then the overall remote invocation should return that * object. If this method throws an exception, then the overall * remote invocation should throw that exception (no further * retry). * * If this method returns a Failure instance, then this attempt to * invoke the remote method failed due a communication failure. * If the retry flag of the Failure instance is true, then the * failure is safe to retry (in light of "at most once" execution * semantics) and possibly worth retrying. **/ private Object invokeRemoteMethodOnce(Object proxy, Method method, Object[] args, OutboundRequestIterator iter, InvocationConstraints constraints) throws Throwable { /* * Initiate remote call request. */ OutboundRequest request; try { request = iter.next(); } catch (Exception e) { if (e instanceof IOException) { e = wrapSafeIOException((IOException) e, oe); } return new Failure(e, true); } /* * Marshal method and arguments. */ boolean ok = false; boolean integrity = false; boolean wroteMethod = false; Collection context; try { /* * Check the unfulfilled constraints. If the unfulfilled * requirements include Integrity.YES, then we must verify * codebase integrity at this level. If there are any * non-Integrity unfulfilled requirements, we cannot * satisfy them, so this request attempt must fail. */ InvocationConstraints unfulfilled = request.getUnfulfilledConstraints(); for (Iterator i = unfulfilled.requirements().iterator(); i.hasNext();) { InvocationConstraint c = (InvocationConstraint) i.next(); if (c == Integrity.YES) { integrity = true; } else if (!(c instanceof Integrity)) { throw new UnsupportedConstraintException( "cannot satisfy unfulfilled constraint: " + c); } // REMIND: support ConstraintAlternatives containing Integrity? } /* * Even if Integrity.YES wasn't a requirement, we will * satisfy a preference for it. */ if (!integrity) { for (Iterator i = unfulfilled.preferences().iterator(); i.hasNext();) { InvocationConstraint c = (InvocationConstraint) i.next(); if (c == Integrity.YES) { integrity = true; break; // no need to examine preferences further } // NYI: support ConstraintAlternatives containing Integrity } } OutputStream ros = request.getRequestOutputStream(); ros.write(0x00); // marshalling protocol version ros.write(integrity ? 0x01 : 0x00); // integrity context = new ArrayList(1); Util.populateContext(context, integrity); ObjectOutputStream out = createMarshalOutputStream(proxy, method, request, context); wroteMethod = true; marshalMethod(proxy, method, out, context); args = (args == null) ? new Object[0] : args; marshalArguments(proxy, method, args, out, context); out.close(); ok = true; } catch (Exception e) { if (e instanceof IOException) { if (wroteMethod && request.getDeliveryStatus()) { e = new MarshalException("error marshalling arguments", e); } else { e = wrapSafeIOException((IOException) e, oe); } } return new Failure(e, !wroteMethod || !request.getDeliveryStatus()); } finally { if (!ok) { request.abort(); } } /* * Execute call and unmarshal return value or exception. */ ok = false; boolean versionMismatch = false; Object returnValue = null; Throwable throwable = null; try { throwable = oe.executeCall(request); if (throwable == null) { InputStream ris = request.getResponseInputStream(); int responseCode = ris.read(); if (responseCode == -1) { throw new EOFException("connection closed by server"); } else if (responseCode == 0x00) { versionMismatch = true; throw new ProtocolException( "marshalling protocol version mismatch"); } ObjectInputStream in = createMarshalInputStream(proxy, method, request, integrity, context); switch (responseCode) { case 0x01: returnValue = unmarshalReturn(proxy, method, in, context); if (logger.isLoggable(Level.FINE)) { logReturn(method, returnValue); } break; case 0x02: throwable = unmarshalThrow(proxy, method, in, context); break; default: throw new ProtocolException( "invalid response code " + responseCode); } in.close(); } ok = true; } catch (Exception e) { boolean retry = false; if (e instanceof IOException) { if (!versionMismatch && request.getDeliveryStatus()) { e = new UnmarshalException( "exception unmarshalling response", e); } else { e = wrapSafeIOException((IOException) e, oe); retry = !versionMismatch; } } else if (e instanceof ClassNotFoundException) { e = new UnmarshalException("error unmarshalling response", e); } return new Failure(e, !versionMismatch || !request.getDeliveryStatus()); } finally { if (!ok) { request.abort(); } } /* * Throw exception or return the return value. */ if (throwable != null) { if (logger.isLoggable(Levels.FAILED)) { logThrow(Levels.FAILED, method, throwable, true); } throw throwable; } else { return returnValue; } } /** * Wraps an IOException that occurred while attempting to * communicate a remote call in a RemoteException that will * indicate that the failed remote invocation is safe to retry * without violating "at most once" execution semantics. **/ private static RemoteException wrapSafeIOException(IOException ioe, ObjectEndpoint oe) { if (ioe instanceof java.net.UnknownHostException) { return new java.rmi.UnknownHostException( "unknown host in " + oe, ioe); } else if (ioe instanceof java.net.ConnectException) { return new java.rmi.ConnectException( "connection refused or timed out to " + oe, ioe); } else { return new ConnectIOException( "I/O exception connecting to " + oe, ioe); } } /** * Returns the combined absolute client and server constraints for * the specified method, getting the constraints from the cache if * possible, and creating and updating the cache if necessary. **/ private InvocationConstraints getConstraints(Method method) { if (clientConstraints == null && serverConstraints == null) { return InvocationConstraints.EMPTY; } synchronized (cacheLock) { if (methodCache == null) { methodCache = new Method[CACHE_SIZE]; constraintCache = new InvocationConstraints[CACHE_SIZE]; cacheIndex = CACHE_SIZE - 1; } else { for (int i = CACHE_SIZE; --i >= 0; ) { if (methodCache[i] == method) { return constraintCache[i].makeAbsolute(); } } } InvocationConstraints constraints = InvocationConstraints.combine( clientConstraints == null ? null : clientConstraints.getConstraints(method), serverConstraints == null ? null : serverConstraints.getConstraints(method)); methodCache[cacheIndex] = method; constraintCache[cacheIndex] = constraints; cacheIndex = (cacheIndex == 0) ? CACHE_SIZE - 1 : cacheIndex - 1; return constraints.makeAbsolute(); } } /** * Returns a copy of this invocation handler with the specified * constraints as its new client constraints. * * <p><code>BasicInvocationHandler</code> implements this method * as follows: * * <p>This method looks for a public constructor declared by the * class of this object with two parameters, the first parameter * type being the class of this object and the second parameter * type being {@link MethodConstraints}. If found, the * constructor is invoked with this instance and the specified * constraints, and the resulting object is returned. If the * constructor could not be found or was not accessible, or if the * constructor invocation throws an exception, an * <code>UndeclaredThrowableException</code> is thrown. * * <p>A subclass can override this method to control how the * invocation handler is copied. * * @param constraints the new client constraints, or * <code>null</code> * * @return a copy of this invocation handler with the specified * constraints as its client constraints **/ protected InvocationHandler setClientConstraints( MethodConstraints constraints) { Class c = getClass(); try { Constructor constructor = c.getConstructor(new Class[] { c, MethodConstraints.class }); return (BasicInvocationHandler) constructor.newInstance(new Object[] { this, constraints }); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new UndeclaredThrowableException( e, "exception constructing invocation handler"); } } /** * Returns a new {@link ObjectOutputStream} instance to use to write * objects to the request output stream obtained by invoking the {@link * OutboundRequest#getRequestOutputStream getRequestOutputStream} method * on the given <code>request</code>. * * <p><code>BasicInvocationHandler</code> implements this method * to return a new {@link MarshalOutputStream} instance * constructed with the output stream obtained from * <code>request</code> as specified above and an unmodifiable * view of the supplied <code>context</code> collection. * * <p>A subclass can override this method to control how the * marshal input stream is created or implemented. * * @param proxy the proxy instance * @param method the remote method invoked * @param request the outbound request * @param context the client context * @return a new {@link ObjectOutputStream} instance for marshalling * a call request * @throws IOException if an I/O exception occurs * @throws NullPointerException if any argument is <code>null</code> **/ protected ObjectOutputStream createMarshalOutputStream(Object proxy, Method method, OutboundRequest request, Collection context) throws IOException { if (proxy == null || method == null) { throw new NullPointerException(); } OutputStream out = request.getRequestOutputStream(); Collection unmodContext = Collections.unmodifiableCollection(context); return new MarshalOutputStream(out, unmodContext); } /** * Returns a new {@link ObjectInputStream} instance to use to read * objects from the response input stream obtained by invoking the {@link * OutboundRequest#getResponseInputStream getResponseInputStream} method * on the given <code>request</code>. * * <p><code>BasicInvocationHandler</code> implements this method * to return a new {@link MarshalInputStream} instance constructed * with the input stream obtained from <code>request</code> as * specified above for the input stream <code>in</code>, the class * loader of <code>proxy</code>'s class for * <code>defaultLoader</code> and <code>verifierLoader</code>, * this method's <code>integrity</code> argument for * <code>verifyCodebaseIntegrity</code>, and an unmodifiable view * of <code>context</code> for the <code>context</code> * collection. The {@link * MarshalInputStream#useCodebaseAnnotations * useCodebaseAnnotations} method is invoked on the created stream * before it is returned. * * <p>An exception is thrown if <code>proxy</code> is not an instance * of a dynamic proxy class containing this invocation handler. * * <p>A subclass can override this method to control how the * marshal input stream is created or implemented. * * @param proxy the proxy instance * @param method the remote method invoked * @param request the outbound request * @param integrity whether or not to verify codebase integrity * @param context the client context * @return a new {@link ObjectInputStream} instance for unmarshalling * a call response * @throws IOException if an I/O exception occurs * @throws NullPointerException if any argument is <code>null</code> **/ protected ObjectInputStream createMarshalInputStream(Object proxy, Method method, OutboundRequest request, boolean integrity, Collection context) throws IOException { if (method == null) { throw new NullPointerException(); } if (Proxy.getInvocationHandler(proxy) != this) { throw new IllegalArgumentException("not proxy for this"); } ClassLoader proxyLoader = getProxyLoader(proxy.getClass()); Collection unmodContext = Collections.unmodifiableCollection(context); MarshalInputStream in = new MarshalInputStream(request.getResponseInputStream(), proxyLoader, integrity, proxyLoader, unmodContext); in.useCodebaseAnnotations(); return in; } /** * Returns the class loader for the specified proxy class. */ private static ClassLoader getProxyLoader(final Class proxyClass) { return (ClassLoader) AccessController.doPrivileged(new PrivilegedAction() { public Object run() { return proxyClass.getClassLoader(); } }); } /** * Marshals a representation of the given <code>method</code> to * the outgoing request stream, <code>out</code>. For each remote * call, the <code>invoke</code> method calls this method to * marshal a representation of the method. * * <p><code>BasicInvocationHandler</code> implements this method * to write the JRMP method hash (defined in section 8.3 of the * Java RMI specification) for the given method to the output stream * using the {@link ObjectOutputStream#writeLong writeLong} * method. * * <p>A subclass can override this method to control how the remote * method is marshalled. * * @param proxy the proxy instance that the method was invoked on * @param method the <code>Method</code> instance corresponding * to the interface method invoked on the proxy * instance. The declaring class of the * <code>Method</code> object will be the interface that * the method was declared in, which may be a * superinterface of the proxy interface that the proxy * class inherits the method through. * @param out outgoing request stream for the remote call * @param context the client context * * @throws IOException if an I/O exception occurs * @throws NullPointerException if any argument is <code>null</code> **/ protected void marshalMethod(Object proxy, Method method, ObjectOutputStream out, Collection context) throws IOException { if (proxy == null || method == null || context == null) { throw new NullPointerException(); } out.writeLong(Util.getMethodHash(method)); } /** * Marshals the arguments for the specified remote method to the outgoing * request stream, <code>out</code>. For each remote call, the * <code>invoke</code> method calls this method to marshal arguments. * * <p><code>BasicInvocationHandler</code> implements this method * marshal each argument as follows: * * <p>If the corresponding declared parameter type is primitive, then the * the primitive value is written to the stream (for example, if the type * is <code>int.class</code>, then the primitive <code>int</code> value * is written to the stream using the <code>writeInt</code> method). * Otherwise, the argument is written to the stream using the * <code>writeObject</code> method. * * <p>A subclass can override this method to marshal the arguments * in an alternative context, perform pre- or post-processing on * the arguments, marshal additional implicit data, or otherwise * control how the arguments are marshalled. * * @param proxy the proxy instance that the method was invoked on * @param method the <code>Method</code> instance corresponding * to the interface method invoked on the proxy * instance. The declaring class of the * <code>Method</code> object will be the interface that * the method was declared in, which may be a * superinterface of the proxy interface that the proxy * class inherits the method through. * @param args an array of objects containing the values of the * arguments passed in the method invocation on the * proxy instance. If an argument's corresponding declared * parameter type is primitive, then its value is * represented with an instance of the corresponding primitive * wrapper class, such as <code>java.lang.Integer</code> or * <code>java.lang.Boolean</code>. * @param out outgoing request stream for the remote call * @param context the client context * * @throws IOException if an I/O exception occurs * @throws NullPointerException if any argument is <code>null</code> **/ protected void marshalArguments(Object proxy, Method method, Object[] args, ObjectOutputStream out, Collection context) throws IOException { if (proxy == null || args == null || out == null || context == null) { throw new NullPointerException(); } Class[] types = method.getParameterTypes(); for (int i = 0; i < types.length; i++) { Util.marshalValue(types[i], args[i], out); } } /** * Unmarshals the return value for the specified remote method from the * incoming response stream, <code>in</code>. In the case that a value is * returned from the invocation on the remote object, this method is * called to unmarshal that return value. * * <p><code>BasicInvocationHandler</code> implements this method * as follows: * * <p>If the return type of the method is void, then no return value is * read from the stream and <code>null</code> is returned. If the return * type is a primitive type, then the primitive value is read from the * stream (for example, if the type is <code>int.class</code>, then the * primitive <code>int</code> value is read from the stream using the * <code>readInt</code> method). Otherwise, the return value is read from * the stream using the <code>readObject</code> method. * * <p>A subclass can override this method to unmarshal the return * value in an alternative context, perform post-processing on the * return value, unmarshal additional implicit data, or otherwise * control how the return value is unmarshalled. * * @param proxy the proxy instance that the method was invoked on * @param method the <code>Method</code> instance corresponding * to the interface method invoked on the proxy * instance. The declaring class of the * <code>Method</code> object will be the interface that * the method was declared in, which may be a * superinterface of the proxy interface that the proxy * class inherits the method through. * @param in the incoming result stream for the remote call * @param context the client context * @return the unmarshalled return value of the method invocation on * the proxy instance. If the declared return value of the * interface method is a primitive type, then the * value returned by <code>invoke</code> will be an * instance of the corresponding primitive wrapper * class; otherwise, it will be a type assignable to * the declared return type. * @throws IOException if an I/O exception occurs * @throws ClassNotFoundException if a class could not be found during * unmarshalling * @throws NullPointerException if any argument is <code>null</code> **/ protected Object unmarshalReturn(Object proxy, Method method, ObjectInputStream in, Collection context) throws IOException, ClassNotFoundException { if (proxy == null || in == null || context == null) { throw new NullPointerException(); } Class returnType = method.getReturnType(); Object returnValue = null; if (returnType != void.class) { returnValue = Util.unmarshalValue(returnType, in); } return returnValue; } /** * Unmarshals the throwable for the specified remote method from the * incoming response stream, <code>in</code>, and returns the result. In * the case that an exception was thrown as a result of the * invocation on the remote object, this method is called to unmarshal * that throwable. * * <p><code>BasicInvocationHandler</code> implements this method * to return the throwable unmarshalled from the stream using the * <code>readObject</code> method. If the unmarshalled throwable * is a checked exception that is not assignable to any exception * in the <code>throws</code> clause of the method implemented by the * <code>proxy</code>'s class, then: if there is no public member * method of <code>proxy</code>'s class with the same name and * parameter types as <code>method</code> an {@link * IllegalArgumentException} is thrown, otherwise that exception * is wrapped in an {@link UnexpectedException} and the wrapped * exception is returned. * * <p>A subclass can override this method to unmarshal the return * value in an alternative context, perform post-processing on the * return value, unmarshal additional implicit data, or otherwise * control how the return value is unmarshalled. * * @param proxy the proxy instance that the method was invoked on * @param method the <code>Method</code> instance corresponding * to the interface method invoked on the proxy * instance. The declaring class of the * <code>Method</code> object will be the interface that * the method was declared in, which may be a * superinterface of the proxy interface that the proxy * class inherits the method through. * @param context the client context * @param in the incoming result stream for the remote call * @return the unmarshalled exception to throw from the method * invocation on the proxy instance. The exception's type * must be assignable either to any of the exception types * declared in the <code>throws</code> clause of the interface * method or to the unchecked exception types * <code>java.lang.RuntimeException</code> or * <code>java.lang.Error</code>. * @throws IOException if an I/O exception occurs * @throws ClassNotFoundException if a class could not be found during * unmarshalling * @throws NullPointerException if any argument is <code>null</code> **/ protected Throwable unmarshalThrow(Object proxy, Method method, ObjectInputStream in, Collection context) throws IOException, ClassNotFoundException { if (proxy == null || method == null || context == null) { throw new NullPointerException(); } Throwable t = (Throwable) in.readObject(); Util.exceptionReceivedFromServer(t); if (!(t instanceof RuntimeException || t instanceof Error)) { Class cl = proxy.getClass(); try { method = cl.getMethod(method.getName(), method.getParameterTypes()); } catch (NoSuchMethodException e) { throw (IllegalArgumentException) new IllegalArgumentException().initCause(e); } Class[] exTypes = method.getExceptionTypes(); Class thrownType = t.getClass(); for (int i = 0; i < exTypes.length; i++) { if (exTypes[i].isAssignableFrom(thrownType)) { return t; } } UnexpectedException wrapper = new UnexpectedException("unexpected exception"); wrapper.detail = t; t = wrapper; } return t; } /** * Log the start of an outbound remote call. **/ private void logCall(Method method, Object[] args, InvocationConstraints constraints) { String msg = "outbound call {0}.{1} to {2}\n{3}"; if (logger.isLoggable(Level.FINEST)) { msg = "outbound call {0}.{1} to {2}\nargs {4}\n{3}"; } Object xargs = (args == null) ? Collections.EMPTY_LIST : Arrays.asList(args); logger.logp(Level.FINE, this.getClass().getName(), "invoke", msg, new Object[] { method.getDeclaringClass().getName(), method.getName(), oe, constraints, xargs}); } /** * Log the return of an outbound remote call. **/ private void logReturn(Method method, Object res) { String msg = "outbound call {0}.{1} returns"; if (logger.isLoggable(Level.FINEST) && method.getReturnType() != void.class) { msg = "outbound call {0}.{1} returns {2}"; } logger.logp(Level.FINE, this.getClass().getName(), "invoke", msg, new Object[]{method.getDeclaringClass().getName(), method.getName(), res}); } /** * Log the throw of an outbound remote call. **/ private void logThrow(Level level, Method method, Throwable t, boolean isRemote) { LogRecord lr = new LogRecord(level, isRemote ? "outbound call {0}.{1} remotely throws" : "outbound call {0}.{1} locally throws"); lr.setLoggerName(logger.getName()); lr.setSourceClassName(this.getClass().getName()); lr.setSourceMethodName("invoke"); lr.setParameters(new Object[]{method.getDeclaringClass().getName(), method.getName()}); lr.setThrown(t); logger.log(lr); } /** * Returns this <code>BasicInvocationHandler</code>'s * <code>ObjectEndpoint</code>. * * @return the <code>ObjectEndpoint</code> **/ public final ObjectEndpoint getObjectEndpoint() { return oe; } /** * Returns this <code>BasicInvocationHandler</code>'s client * constraints. * * @return the client constraints **/ public final MethodConstraints getClientConstraints() { return clientConstraints; } /** * Returns this <code>BasicInvocationHandler</code>'s server * constraints. * * @return the server constraints **/ public final MethodConstraints getServerConstraints() { return serverConstraints; } /** * Returns the hash code value for this invocation handler. * * @return the hash code value for this invocation handler **/ public int hashCode() { return oe.hashCode(); } /** * Compares the specified object with this * <code>BasicInvocationHandler</code> for equality. * * <p><code>BasicInvocationHandler</code> implements this method * to return <code>true</code> if and only if * * <ul> * * <li>the specified object has the same class as this object, * * <li>the <code>ObjectEndpoint</code> in the specified object has the * same class and is equal to the object endpoint in this object, and * * <li>the client constraints and server constraints in the specified * object are equal to the ones in this object. * * </ul> * * <p>A subclass should override this method if adds instance * state that affects equality. * * @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 == null || getClass() != obj.getClass()) { return false; } BasicInvocationHandler other = (BasicInvocationHandler) obj; return Util.sameClassAndEquals(oe, other.oe) && Util.equals(clientConstraints, other.clientConstraints) && Util.equals(serverConstraints, other.serverConstraints); } /** * 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><code>BasicInvocationHandler</code> implements this method * to return <code>true</code> if and only if * * <ul> * * <li>the specified object has the same class as this object, * * <li>this object's <code>ObjectEndpoint</code> is an instance of * {@link TrustEquivalence} and invoking its * <code>checkTrustEquivalence</code> method with the specified * object's <code>ObjectEndpoint</code> returns <code>true</code>, * and * * <li>the client constraints and server constraints in the * specified object are equal to the ones in this object. * * </ul> * * <p>A subclass should override this method to perform any * additional checks that are necessary. **/ public boolean checkTrustEquivalence(Object obj) { if (obj == this) { return true; } else if (obj == null || getClass() != obj.getClass()) { return false; } BasicInvocationHandler other = (BasicInvocationHandler) obj; return Util.checkTrustEquivalence(oe, other.oe) && Util.equals(clientConstraints, other.clientConstraints) && Util.equals(serverConstraints, other.serverConstraints); } /** * Returns a string representation of this * <code>BasicInvocationHandler</code>. * * @return a string representation of this * <code>BasicInvocationHandler</code> **/ public String toString() { return Util.getUnqualifiedName(getClass()) + "[" + oe + "]"; } /** * Returns a string representation for a proxy that uses this invocation * handler. **/ private String proxyToString(Object proxy) { Class[] interfaces = proxy.getClass().getInterfaces(); if (interfaces.length == 0) { return "Proxy[" + this + "]"; } String iface = interfaces[0].getName(); int dot = iface.lastIndexOf('.'); if (dot >= 0) { iface = iface.substring(dot + 1); } return "Proxy[" + iface + "," + this + "]"; } /** * @throws InvalidObjectException if the object endpoint is * <code>null</code> **/ private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); if (oe == null) { throw new InvalidObjectException("null object endpoint"); } cacheLock = new Object(); } /** * @throws InvalidObjectException unconditionally **/ private void readObjectNoData() throws InvalidObjectException { throw new InvalidObjectException("no data in stream; class: " + this.getClass().getName()); } }