/* * 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.activation; import com.sun.jini.action.GetBooleanAction; import com.sun.jini.jeri.internal.runtime.Util; import com.sun.jini.logging.Levels; import java.io.IOException; import java.io.InvalidObjectException; import java.io.ObjectInputStream; import java.io.Serializable; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Proxy; import java.lang.reflect.UndeclaredThrowableException; import java.rmi.ConnectException; import java.rmi.ConnectIOException; import java.rmi.MarshalException; import java.rmi.NoSuchObjectException; import java.rmi.Remote; import java.rmi.RemoteException; import java.rmi.ServerError; import java.rmi.ServerException; import java.rmi.UnknownHostException; import java.rmi.activation.ActivateFailedException; import java.rmi.activation.ActivationException; import java.rmi.activation.ActivationID; import java.rmi.activation.UnknownObjectException; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.NoSuchElementException; import java.util.logging.Level; import java.util.logging.LogRecord; import java.util.logging.Logger; import net.jini.constraint.BasicMethodConstraints; import net.jini.core.constraint.InvocationConstraints; import net.jini.core.constraint.MethodConstraints; import net.jini.core.constraint.RemoteMethodControl; import net.jini.io.UnsupportedConstraintException; import net.jini.security.Security; import net.jini.security.proxytrust.ProxyTrustIterator; import net.jini.security.proxytrust.TrustEquivalence; /** * An invocation handler for activatable remote objects. If the client * constraints of this activatable invocation handler are not * <code>null</code>, then the invocation handler's underlying proxy (if * any) must implement {@link RemoteMethodControl} or a remote invocation * will fail with an {@link UnsupportedConstraintException}. * * @author Sun Microsystems, Inc. * @since 2.0 * * @com.sun.jini.impl * * This implementation recognizes the following system property: * <ul> * <li><code>com.sun.jini.activation.enableActivateGrant</code> - This * property is interpreted as a <code>boolean</code> value (see {@link * Boolean#getBoolean Boolean.getBoolean}). If <code>true</code>, this * implementation invokes {@link Security#grant(Class,Class) Security.grant} * as defined in the specification. * </ul> * * <p>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.activation.ActivatableInvocationHandler</code> to log * information at the following levels: * * <table summary="Describes what is logged by ActivatableInvocationHandler * 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#FAILED FAILED} <td> exception thrown activating * the object * * <tr> <td> {@link Levels#HANDLED HANDLED} <td> exception caught in * attempt to communicate a remote call * * </table> **/ public final class ActivatableInvocationHandler implements InvocationHandler, TrustEquivalence, Serializable { private static final long serialVersionUID = -428224070630550856L; /** * The number of times to retry a call, with varying degrees of * reactivation in between. */ private static final int MAX_RETRIES = 3; /** logger */ private static final Logger logger = Logger.getLogger("net.jini.activation.ActivatableInvocationHandler"); /** * Constructor parameter classes for proxy classes. */ private static final Class[] constructorArgs = new Class[]{InvocationHandler.class}; /** * Flag to enable use of Security.grant. */ private static final boolean enableGrant = ((Boolean) AccessController.doPrivileged(new GetBooleanAction( "com.sun.jini.activation.enableActivateGrant"))) .booleanValue(); /** * The activation identifier. * @serial */ private final ActivationID id; /** * The underlying proxy or <code>null</code>. * @serial */ private Remote uproxy; /** * The client constraints or <code>null</code>. * @serial */ private final MethodConstraints clientConstraints; /* * The getProxyTrustIterator method object. */ private static final Method getPtiMethod; static { try { getPtiMethod = ActivatableInvocationHandler.class. getDeclaredMethod("getProxyTrustIterator", new Class[0]); } catch (NoSuchMethodException nsme) { throw new AssertionError(nsme); } } /** * Creates an instance with the specified activation identifier, a * possibly-<code>null</code> underlying proxy, and <code>null</code> * client constraints. If the underlying proxy implements {@link * RemoteMethodControl} and its constraints are not <code>null</code>, * the underlying proxy of this instance is a copy of that proxy with * <code>null</code> constraints. * * @param id the activation identifier * @param underlyingProxy an underlying proxy, or <code>null</code> * @throws NullPointerException if <code>id</code> is <code>null</code> **/ public ActivatableInvocationHandler(ActivationID id, Remote underlyingProxy) { if (id == null) { throw new NullPointerException("id is null"); } this.id = id; this.uproxy = underlyingProxy; this.clientConstraints = null; /* * If underlying proxy's constraints are non-null, * set underlying proxy to a copy of the underlying * proxy with null constraints. */ if (uproxy instanceof RemoteMethodControl) { MethodConstraints uproxyConstraints = ((RemoteMethodControl) uproxy).getConstraints(); if (uproxyConstraints != null) { uproxy = (Remote) ((RemoteMethodControl) uproxy). setConstraints(null); } } } /** * Returns true if the constraints on the underlying proxy (if it * implements {@link RemoteMethodControl}) are equivalent to the * constraints of this invocation handler, or if the underlying proxy * does not implement RemoteMethodControl. */ private boolean hasConsistentConstraints() { if (uproxy instanceof RemoteMethodControl) { MethodConstraints uproxyConstraints = ((RemoteMethodControl) uproxy).getConstraints(); return (clientConstraints == null ? uproxyConstraints == null : clientConstraints.equals(uproxyConstraints)); } else { return true; } } /** * Creates an instance with the specified activation identifier, optional * underlying proxy, and client constraints. This constructor assumes * that the client constraints are equivalent to the constraints on the * underlying proxy. **/ private ActivatableInvocationHandler( ActivationID id, Remote underlyingProxy, MethodConstraints clientConstraints) { this.id = id; this.uproxy = underlyingProxy; this.clientConstraints = clientConstraints; } /** * Returns the activation identifier supplied during construction of * this invocation handler. * * @return the activation identifier */ public ActivationID getActivationID() { return id; } /** * Returns the current value for the underlying proxy. * * @return the underlying proxy */ public synchronized Object getCurrentProxy() { return uproxy; } /** * 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>If the specified method is one of the following * <code>java.lang.Object</code> methods, it will be processed as follows: * <p><ul> * <li>{@link Object#equals equals}: returns * <code>true</code> if the argument is an * instance of a dynamically generated {@link Proxy} * class that implements the same ordered set of interfaces as the * specified proxy, and this invocation handler is equal to the invocation * handler of that parameter, and returns <code>false</code> otherwise. * <li>{@link Object#hashCode hashCode}: returns the hash code for the * proxy. * <li>{@link Object#toString toString}: returns a string * representation of the specified <code>proxy</code> object. * </ul> * * <p>If the specified method is {@link RemoteMethodControl#setConstraints * RemoteMethodControl.setConstraints}, then if <code>proxy</code> is an * instance of a dynamic proxy class containing this invocation * handler, returns a new proxy containing a copy of this invocation * handler with the same activation identifier, the new specified * client constraints (<code>args[0]</code>), and the current * underlying proxy, or if the current underlying proxy implements * {@link RemoteMethodControl}, a copy of that proxy with the new * specified client constraints. An exception is thrown if * <code>proxy</code> is not an instance of a dynamic proxy class * containing this invocation handler. * * <p>If the specified method is {@link RemoteMethodControl#getConstraints * RemoteMethodControl.getConstraints}, returns the client constraints. * * <p>If the specified method is * {@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 is, a class generated by {@link Proxy}) that implements the * same interfaces as the specified proxy and calling the * {@link #checkTrustEquivalence checkTrustEquivalence} method of this * invocation handler with the invocation handler of that argument * returns <code>true</code>, and returns <code>false</code> otherwise. * * <p>For all other methods, a remote invocation is made as follows: * * <p>A single set of absolute constraints (if any) is used for the * duration of the remote invocation, including any activation that may * occur. * * <p><ul> * <li>If the underlying proxy is non-<code>null</code>, the method is * invoked as follows: * * <ul> * <li>If the client constraints of this object are not * <code>null</code> and the underlying proxy does not implement {@link * RemoteMethodControl} then an {@link UnsupportedConstraintException} * is thrown. * * <li>If <code>method</code>'s declaring class is not * <code>public</code>, the underlying proxy is an instance of the * <code>method</code>'s declaring class, and the underlying proxy's * class is <code>public</code>, then a <code>public</code> method with * the same name and parameter types is obtained from the underlying * proxy's class, and if such a method exists, that method is * reflectively invoked on the underlying proxy passing it the * specified <code>args</code> and the result is returned; otherwise if * such a method doesn't exist an <code>ActivateFailedException</code> * is thrown with {@link NoSuchMethodException} as the cause. * * <li>Otherwise, the original <code>method</code> is reflectively * invoked on the underlying proxy passing it the specified * <code>args</code>. * </ul> * * <p>If this reflective invocation throws an exception other than * {@link IllegalAccessException}, {@link IllegalArgumentException}, * the <code>ActivateFailedException</code> described above, or * an {@link InvocationTargetException} containing {@link * ConnectException}, {@link ConnectIOException}, {@link * NoSuchObjectException}, or {@link UnknownHostException}, then if the * exception an {@link InvocationTargetException} the contained * exception is thrown to the caller, otherwise the exception is thrown * directly. * * <p><li>If the underlying proxy is <code>null</code> or if the * reflective invocation does not throw an exception to the caller as * described above: * <ul> * * <li>If permitted by some implementation-specific mechanism, * dynamically grants permissions to the class loader of the * activation identifier's class by invoking {@link * Security#grant(Class,Class) Security.grant} passing the class of the * proxy and the class of the activation identifier. If this * invocation throws an {@link UnsupportedOperationException}, the * exception is ignored. * * <li>A new proxy is obtained by invoking the {@link * ActivationID#activate activate} method on the activation identifier, * passing <code>false</code> as the argument. That method must return * an instance of a dynamic {@link Proxy} class, with an invocation * handler that is an instance of this class, containing the same * activation identifier. If the returned proxy does not meet this * criteria, then an {@link ActivateFailedException} is thrown. If the * <code>activate</code> call throws {@link ConnectException}, then * a new <code>ConnectException</code> is thrown with the original * <code>ConnectException</code> as the cause. If the * <code>activate</code> call throws {@link RemoteException}, then * {@link ConnectIOException} is thrown with the * <code>RemoteException</code> as the cause. If the * <code>activate</code> call throws {@link UnknownObjectException}, then * {@link NoSuchObjectException} is thrown with the * <code>UnknownObjectException</code> as the cause. Finally, if the * <code>activate</code> call throws {@link ActivationException}, * then {@link ActivateFailedException} is thrown with the * <code>ActivationException</code> as the cause. * * <li>If a valid, new proxy is returned by the <code>activate</code> * call, the underlying proxy of the new proxy is obtained from the new * proxy's activatable invocation handler. If the obtained underlying * proxy implements <code>RemoteMethodControl</code>, this invocation * handler's underlying proxy is set to a copy of the obtained * underlying proxy with the client constraints of this instance. * Otherwise, this invocation handler's underlying proxy is set to the * obtained underlying proxy. * * <li>The reflective invocation is then retried (as above) on the new * underlying proxy. Activation and retry can occur up to three * times. On subsequent attempts, <code>true</code> will be passed to * the activation identifier's <code>activate</code> method, if passing * <code>false</code> returned the same underlying proxy as before or * if <code>NoSuchObjectException</code> was thrown by the call to the * underlying proxy. * * <li>If the final attempt at reflective invocation throws * <code>IllegalAccessException</code> or * <code>IllegalArgumentException</code> an * <code>ActivateFailedException</code> is thrown with the original * exception as the cause. If this reflective invocation throws * <code>InvocationTargetException</code>, the contained target * exception is thrown. * </ul> * </ul> * * <p>The implementation of remote method invocation defined by this class * preserves at-most-once call semantics: the remote call either does not * execute, partially executes, or executes exactly once at the remote * site. Note that for remote calls to activatable objects, arguments may * be marshalled more than once. * * <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 invocation 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 { Class decl = method.getDeclaringClass(); if (proxy instanceof InvocationHandler) { throw new IllegalArgumentException( "proxy cannot be an invocation handler"); } else if (decl == Object.class) { return invokeObjectMethod(proxy, method, args); } else if (decl == RemoteMethodControl.class) { return invokeRemoteMethodControlMethod(proxy, method, args); } else if (decl == TrustEquivalence.class) { 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) throws Throwable { String name = method.getName(); if (name.equals("setConstraints")) { if (Proxy.getInvocationHandler(proxy) != this) { throw new IllegalArgumentException("not proxy for this"); } Remote newProxy; synchronized (this) { newProxy = uproxy; } MethodConstraints mc = (MethodConstraints) args[0]; if (newProxy instanceof RemoteMethodControl) { newProxy = (Remote) ((RemoteMethodControl) newProxy).setConstraints(mc); } Class proxyClass = proxy.getClass(); return Proxy.newProxyInstance( getProxyLoader(proxyClass), proxyClass.getInterfaces(), new ActivatableInvocationHandler(id, newProxy, mc)); // 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); } } /** * Handles remote methods. **/ private Object invokeRemoteMethod(Object proxy, Method method, Object[] args) throws Throwable { Util.checkProxyRemoteMethod(proxy.getClass(), method); /* * Make the relative constraints (if any) on the method absolute. * If there are relative constraints, create new method constraints * to set on the underlying proxy before invoking a method. */ MethodConstraints convertedConstraints = null; if (clientConstraints != null) { InvocationConstraints relativeConstraints = clientConstraints.getConstraints(method); InvocationConstraints absoluteConstraints = relativeConstraints.makeAbsolute(); if (relativeConstraints != absoluteConstraints) { convertedConstraints = new BasicMethodConstraints(absoluteConstraints); } } boolean force = false; Remote currProxy; Failure failure = null; /* * Attempt object activation if underlying proxy is unknown. */ synchronized (this) { if (uproxy == null) { activate(force, proxy, method); force = true; } currProxy = uproxy; } for (int retries = MAX_RETRIES; --retries >= 0; ) { if (logger.isLoggable(Levels.HANDLED)) { if (failure != null) { logThrow(Levels.HANDLED, "outbound call", "invokeRemoteMethod", method, failure.exception); } } if ((clientConstraints != null) && !(currProxy instanceof RemoteMethodControl)) { throw new UnsupportedConstraintException( "underlying proxy does not implement RemoteMethodControl"); } /* * Set constraints on target proxy only if relative constraints * were made absolute. */ Remote targetProxy = (convertedConstraints == null ? currProxy : (Remote) ((RemoteMethodControl) currProxy). setConstraints(convertedConstraints)); /* * Invoke method on target proxy. */ Object result = invokeMethod(targetProxy, method, args); if (result instanceof Failure) { failure = (Failure) result; if (!failure.retry || retries <= 0) { break; } } else { return result; } /* * Reattempt activation. */ synchronized (this) { if (uproxy == null || currProxy.equals(uproxy)) { activate(force, proxy, method); if (currProxy.equals(uproxy) && (failure.exception instanceof NoSuchObjectException) && !force) { activate(true, proxy, method); } force = true; } else { force = false; } currProxy = uproxy; } } if (logger.isLoggable(Levels.FAILED)) { logThrow(Levels.FAILED, "outbound call", "invokeRemoteMethod", method, failure.exception); } throw failure.exception; } /** * Log the throw of an outbound remote call. */ private void logThrow(Level level, String logRecordText, String sourceMethodName, Method method, Throwable t) { LogRecord lr = new LogRecord(level, logRecordText + " {0}.{1} throws"); lr.setLoggerName(logger.getName()); lr.setSourceClassName(this.getClass().getName()); lr.setSourceMethodName(sourceMethodName); lr.setParameters(new Object[]{method.getDeclaringClass().getName(), method.getName()}); lr.setThrown(t); logger.log(lr); } /** * 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; } } /** * Reflectively invokes the method on the supplied proxy and returns * the result in a <code>Result</code> object. If a * <code>RemoteException</code> is thrown as a result of the remote * invocation, then the result object contains a non-<code>null</code> * exception and the retry field indicates whether invocation retry * is possible without violating "at-most-once" call semantics. If the * result object contains a <code>null</code> exception, then the value * field is the return value of the remote invocation. **/ private Object invokeMethod(Object proxy, Method m, Object[] args) throws Throwable { try { return invokeMethod0(proxy, m, args); } catch (NoSuchObjectException e) { return new Failure(e, true); } catch (ConnectException e) { return new Failure(e, true); } catch (UnknownHostException e) { return new Failure(e, true); } catch (ConnectIOException e) { return new Failure(e, true); } catch (MarshalException e) { return new Failure(e, false); } catch (ServerError e) { return new Failure(e, false); } catch (ServerException e) { return new Failure(e, false); } catch (RemoteException e) { synchronized (this) { if (proxy.equals(uproxy)) { uproxy = null; } } return new Failure(e, e instanceof ActivateFailedException); } catch (Exception e) { return new Failure(e, false); } } /** * Reflectively invokes the method on the supplied <code>proxy</code> * as follows: * * <p>If the <code>method</code>'s declaring class is not * <code>public</code>, the proxy is an instance of the * <code>method</code>'s declaring class, and the proxy class is * <code>public</code>, a <code>public</code> method with the same name and * parameter types is obtained from the proxy class, and if such a * method exists, that method is * reflectively invoked on the proxy passing it the specified * <code>args</code> and the result is returned, otherwise if such a * method doesn't exist an <code>ActivateFailedException</code> * is thrown with {@link NoSuchMethodException} as the cause. * * <p>Otherwise, the original <code>method</code> is reflectively * invoked on the proxy passing it the specified * <code>args</code> and the result is returned. * * <p>If the reflective invocation throws * <code>IllegalAccessException</code> or * <code>IllegalArgumentException</code>, an * <code>ActivateFailedException</code> exception is thrown with the * original exception as the cause. If the reflective invocation throws * {@link InvocationTargetException} the contained target exception is * thrown. * * @param proxy a proxy * @param m a method * @param arg arguments * @return result of reflective invocation * @throws ActivateFailedException if the reflective invocation throws * <code>IllegalAccessException</code> or * <code>IllegalArgumentException</code> * @throws Throwable if the reflective invocation throws * <code>InvocationTargetException</code>, the contained * target exception is thrown **/ private static Object invokeMethod0(Object proxy, Method m, Object[] args) throws Throwable { Class mcl = m.getDeclaringClass(); if (!Modifier.isPublic(mcl.getModifiers())) { Class cl = proxy.getClass(); if (mcl.isAssignableFrom(cl) && Modifier.isPublic(cl.getModifiers())) { try { m = cl.getMethod(m.getName(), m.getParameterTypes()); } catch (NoSuchMethodException nsme) { throw new ActivateFailedException("bad proxy"). initCause(nsme); } } } try { return m.invoke(proxy, args); } catch (IllegalAccessException e) { throw new ActivateFailedException("bad proxy").initCause(e); } catch (IllegalArgumentException e) { throw new ActivateFailedException("bad proxy").initCause(e); } catch (InvocationTargetException e) { throw e.getTargetException(); } } /** * Returns a proxy trust iterator for an activatable object that is * suitable for use by {@link * net.jini.security.proxytrust.ProxyTrustVerifier}. * * <p>The iterator produces the current underlying proxy on each * iteration. The iterator produces up to three elements, but after * the first element, iteration terminates unless the exception set by * a call to {@link ProxyTrustIterator#setException setException} on * the previous iteration is an instance of {@link ConnectException}, * {@link ConnectIOException}, {@link NoSuchObjectException}, or {@link * UnknownHostException}. * * <p>On each iteration, if the current underlying proxy is * <code>null</code> or the same as the underlying proxy produced by * the previous iteration: * * <p>A new proxy is obtained by invoking the {@link * ActivationID#activate activate} method on the activation identifier, * passing <code>false</code> as the argument. That method must return * an instance of a dynamic {@link Proxy} class, with an invocation * handler that is an instance of this class, containing the same * activation identifier. If this activation * throws one of the following exceptions, the exception is thrown * by the <code>next</code> method of the iterator and the iteration * terminates: * * <blockquote>If the proxy returned by the <code>activate</code> call * does not meet the criteria listed above, then an {@link * ActivateFailedException} is thrown. If the <code>activate</code> * call throws {@link RemoteException}, then {@link ConnectIOException} * is thrown with the <code>RemoteException</code> as the cause. If * the <code>activate</code> call throws {@link UnknownHostException}, * then {@link NoSuchObjectException} is thrown with the * <code>UnknownHostException</code> as the cause. Finally, if the * <code>activate</code> call throws {@link ActivationException}, then * {@link ActivateFailedException} is thrown with the * <code>ActivationException</code> as the cause. * </blockquote> * * <p>If a valid, new proxy is returned by the <code>activate</code> * call, the underlying proxy of the new proxy is obtained from the new * proxy's activatable invocation handler. If the obtained underlying * proxy implements <code>RemoteMethodControl</code>, this invocation * handler's underlying proxy is set to a copy of the obtained * underlying proxy with the client constraints of this instance. * Otherwise, this invocation handler's underlying proxy is set to the * obtained underlying proxy. * * <p>On the first call to the activation identifier's * <code>activate</code> method, <code>false</code> is passed as an * argument; on subsequent calls <code>true</code> will be passed, if * passing <code>false</code> returned the same underlying proxy as * before (when compared using the <code>equals</code> method) or if * the exception passed to <code>setException</code> is an instance of * <code>NoSuchObjectException</code>. If an activation attempt results * in an exception, that exception is thrown by the <code>next</code> * method of the iterator and iteration terminates. * * @return a proxy trust iterator suitable for use by * <code>ProxyTrustVerifier</code> **/ protected ProxyTrustIterator getProxyTrustIterator() { return new ProxyTrustIterator() { private int retries = MAX_RETRIES + 1; private boolean force = false; private Remote currProxy = null; private Exception fail = null; private RemoteException ex = null; private boolean advance = true; public synchronized boolean hasNext() { if (advance) { advance = false; if (--retries < 0) { } else if (retries == MAX_RETRIES || ex instanceof NoSuchObjectException || ex instanceof ConnectException || ex instanceof UnknownHostException || ex instanceof ConnectIOException) { try { synchronized (ActivatableInvocationHandler.this) { if (uproxy == null || uproxy.equals(currProxy)) { activate(force, null, getPtiMethod); if (uproxy.equals(currProxy) && ex instanceof NoSuchObjectException && !force) { activate(true, null, getPtiMethod); } force = true; } else { force = false; } currProxy = uproxy; } } catch (Exception e) { fail = e; retries = 0; } } else { retries = -1; if (!(ex == null || ex instanceof MarshalException || ex instanceof ServerException || ex instanceof ServerError)) { synchronized (ActivatableInvocationHandler.this) { if (currProxy.equals(uproxy)) { uproxy = null; } } } } } return retries >= 0; } public synchronized Object next() throws RemoteException { if (!hasNext()) { throw new NoSuchElementException(); } advance = true; if (fail == null) { return currProxy; } else if (fail instanceof RemoteException) { throw (RemoteException) fail; } else { throw (RuntimeException) fail; } } public synchronized void setException(RemoteException e) { if (e == null) { throw new NullPointerException("exception is null"); } else if (retries > MAX_RETRIES || !advance || fail != null) { throw new IllegalStateException(); } ex = e; } }; } /** * 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>ActivatableInvocationHandler</code> implements this * method as follows: * * <p>This method returns <code>true</code> if and only if the * following conditions are met: * <ul> * <li> The specified object has the same class as this object. * <li> This object's activation identifier implements {@link * TrustEquivalence}. * <li> Invoking the <code>checkTrustEquivalence</code> method on * this object's activation identifier passing the specified object's * activation identifier returns <code>true</code>. * <li> The client constraints in the specified object are equal to the * ones in this object. * </ul> * * <p>The underlying proxy of the specified object is set to * <code>null</code> if this method returns <code>true</code> and any * of the following conditions are met: * <ul> * <li> This object's underlying proxy is <code>null</code>. * <li> This object's underlying proxy is not an instance of {@link * TrustEquivalence}. * <li> Invoking the <code>checkTrustEquivalence</code> method on this * object's underlying proxy, passing the underlying proxy of the * specified object, returns <code>false</code>. * </ul> **/ public boolean checkTrustEquivalence(Object obj) { if (this == obj) { return true; } else if (!(obj instanceof ActivatableInvocationHandler) || !(id instanceof TrustEquivalence)) { return false; } ActivatableInvocationHandler other = (ActivatableInvocationHandler) obj; if (!((TrustEquivalence) id).checkTrustEquivalence(other.id) || (clientConstraints != other.clientConstraints && (clientConstraints == null || !clientConstraints.equals(other.clientConstraints)))) { return false; } Remote currProxy; synchronized (this) { currProxy = uproxy; } Remote otherProxy; synchronized (other) { otherProxy = other.uproxy; } if (currProxy == null || !(currProxy instanceof TrustEquivalence) || !((TrustEquivalence) currProxy).checkTrustEquivalence(otherProxy)) { synchronized (other) { other.uproxy = null; } } return true; } /** * Activate the object (see activate0). **/ private void activate(boolean force, Object proxy, Method method) throws Exception { assert Thread.holdsLock(this); try { activate0(force, proxy); } catch (Exception e) { if (logger.isLoggable(Levels.FAILED)) { logThrow(Levels.FAILED, "activating object for call", "activate", method, e); } throw e; } } /** * Activate the object and update the underlying proxy. The force * argument is passed on to the activate method of the activation * identifier. If this method does not throw an exception, the value * of uproxy is updated to the value returned by calling the id's * activate method. Note: The caller should be synchronized on "this" * while calling this method. * * @param force boolean to pass to activation id's activate method * @param proxy outer proxy from which dynamic grants are inherited, * or null **/ private void activate0(boolean force, Object proxy) throws RemoteException { assert Thread.holdsLock(this); uproxy = null; try { if (proxy != null && enableGrant) { try { Security.grant(proxy.getClass(), id.getClass()); } catch (UnsupportedOperationException uoe) { } } Object newProxy = id.activate(force); if (!Proxy.isProxyClass(newProxy.getClass())) { throw new ActivateFailedException("invalid proxy"); } InvocationHandler obj = Proxy.getInvocationHandler(newProxy); if (!(obj instanceof ActivatableInvocationHandler)) { throw new ActivateFailedException("invalid proxy handler"); } ActivatableInvocationHandler handler = (ActivatableInvocationHandler) obj; if (!id.equals(handler.id)) { throw new ActivateFailedException("unexpected activation id"); } Remote newUproxy = handler.uproxy; if (newUproxy == null) { throw new ActivateFailedException("null underlying proxy"); } else if (newUproxy instanceof RemoteMethodControl) { newUproxy = (Remote) ((RemoteMethodControl) newUproxy). setConstraints(clientConstraints); } uproxy = newUproxy; } catch (ConnectException e) { throw new ConnectException("activation failed", e); } catch (RemoteException e) { throw new ConnectIOException("activation failed", e); } catch (UnknownObjectException e) { throw new NoSuchObjectException("object not registered"); } catch (ActivationException e) { throw new ActivateFailedException("activation failed", e); } } /** * Compares the specified object with this * <code>ActivatableInvocationHandler</code> for equality. * * <p> This method returns <code>true</code> if and only if the * specified object has the same class as this object, and the * activation identifier and client constraints in the specified object * are equal to the ones in this object. **/ public boolean equals(Object obj) { if (this == obj) { return true; } else if (!(obj instanceof ActivatableInvocationHandler)) { return false; } ActivatableInvocationHandler other = (ActivatableInvocationHandler) obj; return (id.equals(other.id) && (clientConstraints == other.clientConstraints || (clientConstraints != null && clientConstraints.equals(other.clientConstraints)))); } /** * Returns a hash code value for this object. */ public int hashCode() { return id.hashCode(); } /** * Returns a string representation of this object. */ public String toString() { return "ActivatableInvocationHandler[" + id + ", " + uproxy + "]"; } /** * Returns a string representation for a proxy that uses this invocation * handler. */ private String proxyToString(Object proxy) { Class[] interfaces = proxy.getClass().getInterfaces(); Class iface = null; for (int i = interfaces.length; --i >= 0; iface = interfaces[i]) { if (interfaces[i] == RemoteMethodControl.class) { break; } } if (iface == null) { return "Proxy[" + this + "]"; } String n = iface.getName(); int dot = n.lastIndexOf('.'); if (dot >= 0) { n = n.substring(dot + 1); } return "Proxy[" + n + "," + this + "]"; } /** * Verifies that the activation identifier is not <code>null</code>, * and that the constraints on this invocation handler and the * underlying proxy are consistent. * * @throws InvalidObjectException if the activation identifier is * <code>null</code>, or if the underlying proxy implements {@link * RemoteMethodControl} and the constraints on the underlying proxy are * not equivalent to this invocation handler's constraints **/ private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); if (id == null) { throw new InvalidObjectException("id is null"); } else { if (!hasConsistentConstraints()) { throw new InvalidObjectException( "inconsistent constraints between underlying proxy and invocation handler"); } } } /** * 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(); } }); } }