diff options
Diffstat (limited to 'src/share/classes/sun/rmi/server/UnicastServerRef.java')
-rw-r--r-- | src/share/classes/sun/rmi/server/UnicastServerRef.java | 563 |
1 files changed, 563 insertions, 0 deletions
diff --git a/src/share/classes/sun/rmi/server/UnicastServerRef.java b/src/share/classes/sun/rmi/server/UnicastServerRef.java new file mode 100644 index 000000000..63510db4f --- /dev/null +++ b/src/share/classes/sun/rmi/server/UnicastServerRef.java @@ -0,0 +1,563 @@ +/* + * Copyright 1996-2005 Sun Microsystems, Inc. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Sun designates this + * particular file as subject to the "Classpath" exception as provided + * by Sun in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +package sun.rmi.server; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.io.PrintStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.rmi.MarshalException; +import java.rmi.Remote; +import java.rmi.RemoteException; +import java.rmi.ServerError; +import java.rmi.ServerException; +import java.rmi.UnmarshalException; +import java.rmi.server.ExportException; +import java.rmi.server.RemoteCall; +import java.rmi.server.RemoteRef; +import java.rmi.server.RemoteStub; +import java.rmi.server.ServerNotActiveException; +import java.rmi.server.ServerRef; +import java.rmi.server.Skeleton; +import java.rmi.server.SkeletonNotFoundException; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.WeakHashMap; +import sun.rmi.runtime.Log; +import sun.rmi.transport.LiveRef; +import sun.rmi.transport.Target; +import sun.rmi.transport.tcp.TCPTransport; +import sun.security.action.GetBooleanAction; + +/** + * UnicastServerRef implements the remote reference layer server-side + * behavior for remote objects exported with the "UnicastRef" reference + * type. + * + * @author Ann Wollrath + * @author Roger Riggs + * @author Peter Jones + */ +public class UnicastServerRef extends UnicastRef + implements ServerRef, Dispatcher +{ + /** value of server call log property */ + public static final boolean logCalls = AccessController.doPrivileged( + new GetBooleanAction("java.rmi.server.logCalls")); + + /** server call log */ + public static final Log callLog = + Log.getLog("sun.rmi.server.call", "RMI", logCalls); + + // use serialVersionUID from JDK 1.2.2 for interoperability + private static final long serialVersionUID = -7384275867073752268L; + + /** flag to enable writing exceptions to System.err */ + private static final boolean wantExceptionLog = + AccessController.doPrivileged( + new GetBooleanAction("sun.rmi.server.exceptionTrace")); + + private boolean forceStubUse = false; + + /** + * flag to remove server-side stack traces before marshalling + * exceptions thrown by remote invocations to this VM + */ + private static final boolean suppressStackTraces = + AccessController.doPrivileged( + new GetBooleanAction( + "sun.rmi.server.suppressStackTraces")); + + /** + * skeleton to dispatch remote calls through, for 1.1 stub protocol + * (may be null if stub class only uses 1.2 stub protocol) + */ + private transient Skeleton skel; + + /** maps method hash to Method object for each remote method */ + private transient Map<Long,Method> hashToMethod_Map = null; + + /** + * A weak hash map, mapping classes to hash maps that map method + * hashes to method objects. + **/ + private static final WeakClassHashMap<Map<Long,Method>> hashToMethod_Maps = + new HashToMethod_Maps(); + + /** cache of impl classes that have no corresponding skeleton class */ + private static final Map<Class<?>,?> withoutSkeletons = + Collections.synchronizedMap(new WeakHashMap<Class<?>,Void>()); + + /** + * Create a new (empty) Unicast server remote reference. + */ + public UnicastServerRef() { + } + + /** + * Construct a Unicast server remote reference for a specified + * liveRef. + */ + public UnicastServerRef(LiveRef ref) { + super(ref); + } + + /** + * Construct a Unicast server remote reference to be exported + * on the specified port. + */ + public UnicastServerRef(int port) { + super(new LiveRef(port)); + } + + /** + * Constructs a UnicastServerRef to be exported on an + * anonymous port (i.e., 0) and that uses a pregenerated stub class + * (NOT a dynamic proxy instance) if 'forceStubUse' is 'true'. + * + * This constructor is only called by the method + * UnicastRemoteObject.exportObject(Remote) passing 'true' for + * 'forceStubUse'. The UnicastRemoteObject.exportObject(Remote) method + * returns RemoteStub, so it must ensure that the stub for the + * exported object is an instance of a pregenerated stub class that + * extends RemoteStub (instead of an instance of a dynamic proxy class + * which is not an instance of RemoteStub). + **/ + public UnicastServerRef(boolean forceStubUse) { + this(0); + this.forceStubUse = forceStubUse; + } + + /** + * With the addition of support for dynamic proxies as stubs, this + * method is obsolete because it returns RemoteStub instead of the more + * general Remote. It should not be called. It sets the + * 'forceStubUse' flag to true so that the stub for the exported object + * is forced to be an instance of the pregenerated stub class, which + * extends RemoteStub. + * + * Export this object, create the skeleton and stubs for this + * dispatcher. Create a stub based on the type of the impl, + * initialize it with the appropriate remote reference. Create the + * target defined by the impl, dispatcher (this) and stub. + * Export that target via the Ref. + **/ + public RemoteStub exportObject(Remote impl, Object data) + throws RemoteException + { + forceStubUse = true; + return (RemoteStub) exportObject(impl, data, false); + } + + /** + * Export this object, create the skeleton and stubs for this + * dispatcher. Create a stub based on the type of the impl, + * initialize it with the appropriate remote reference. Create the + * target defined by the impl, dispatcher (this) and stub. + * Export that target via the Ref. + */ + public Remote exportObject(Remote impl, Object data, + boolean permanent) + throws RemoteException + { + Class implClass = impl.getClass(); + Remote stub; + + try { + stub = Util.createProxy(implClass, getClientRef(), forceStubUse); + } catch (IllegalArgumentException e) { + throw new ExportException( + "remote object implements illegal remote interface", e); + } + if (stub instanceof RemoteStub) { + setSkeleton(impl); + } + + Target target = + new Target(impl, this, stub, ref.getObjID(), permanent); + ref.exportObject(target); + hashToMethod_Map = hashToMethod_Maps.get(implClass); + return stub; + } + + /** + * Return the hostname of the current client. When called from a + * thread actively handling a remote method invocation the + * hostname of the client is returned. + * @exception ServerNotActiveException If called outside of servicing + * a remote method invocation. + */ + public String getClientHost() throws ServerNotActiveException { + return TCPTransport.getClientHost(); + } + + /** + * Discovers and sets the appropriate skeleton for the impl. + */ + public void setSkeleton(Remote impl) throws RemoteException { + if (!withoutSkeletons.containsKey(impl.getClass())) { + try { + skel = Util.createSkeleton(impl); + } catch (SkeletonNotFoundException e) { + /* + * Ignore exception for skeleton class not found, because a + * skeleton class is not necessary with the 1.2 stub protocol. + * Remember that this impl's class does not have a skeleton + * class so we don't waste time searching for it again. + */ + withoutSkeletons.put(impl.getClass(), null); + } + } + } + + /** + * Call to dispatch to the remote object (on the server side). + * The up-call to the server and the marshalling of return result + * (or exception) should be handled before returning from this + * method. + * @param obj the target remote object for the call + * @param call the "remote call" from which operation and + * method arguments can be obtained. + * @exception IOException If unable to marshal return result or + * release input or output streams + */ + public void dispatch(Remote obj, RemoteCall call) throws IOException { + // positive operation number in 1.1 stubs; + // negative version number in 1.2 stubs and beyond... + int num; + long op; + + try { + // read remote call header + ObjectInput in; + try { + in = call.getInputStream(); + num = in.readInt(); + if (num >= 0) { + if (skel != null) { + oldDispatch(obj, call, num); + return; + } else { + throw new UnmarshalException( + "skeleton class not found but required " + + "for client version"); + } + } + op = in.readLong(); + } catch (Exception readEx) { + throw new UnmarshalException("error unmarshalling call header", + readEx); + } + + /* + * Since only system classes (with null class loaders) will be on + * the execution stack during parameter unmarshalling for the 1.2 + * stub protocol, tell the MarshalInputStream not to bother trying + * to resolve classes using its superclasses's default method of + * consulting the first non-null class loader on the stack. + */ + MarshalInputStream marshalStream = (MarshalInputStream) in; + marshalStream.skipDefaultResolveClass(); + + Method method = hashToMethod_Map.get(op); + if (method == null) { + throw new UnmarshalException("unrecognized method hash: " + + "method not supported by remote object"); + } + + // if calls are being logged, write out object id and operation + logCall(obj, method); + + // unmarshal parameters + Class[] types = method.getParameterTypes(); + Object[] params = new Object[types.length]; + + try { + unmarshalCustomCallData(in); + for (int i = 0; i < types.length; i++) { + params[i] = unmarshalValue(types[i], in); + } + } catch (java.io.IOException e) { + throw new UnmarshalException( + "error unmarshalling arguments", e); + } catch (ClassNotFoundException e) { + throw new UnmarshalException( + "error unmarshalling arguments", e); + } finally { + call.releaseInputStream(); + } + + // make upcall on remote object + Object result; + try { + result = method.invoke(obj, params); + } catch (InvocationTargetException e) { + throw e.getTargetException(); + } + + // marshal return value + try { + ObjectOutput out = call.getResultStream(true); + Class rtype = method.getReturnType(); + if (rtype != void.class) { + marshalValue(rtype, result, out); + } + } catch (IOException ex) { + throw new MarshalException("error marshalling return", ex); + /* + * This throw is problematic because when it is caught below, + * we attempt to marshal it back to the client, but at this + * point, a "normal return" has already been indicated, + * so marshalling an exception will corrupt the stream. + * This was the case with skeletons as well; there is no + * immediately obvious solution without a protocol change. + */ + } + } catch (Throwable e) { + logCallException(e); + + ObjectOutput out = call.getResultStream(false); + if (e instanceof Error) { + e = new ServerError( + "Error occurred in server thread", (Error) e); + } else if (e instanceof RemoteException) { + e = new ServerException( + "RemoteException occurred in server thread", + (Exception) e); + } + if (suppressStackTraces) { + clearStackTraces(e); + } + out.writeObject(e); + } finally { + call.releaseInputStream(); // in case skeleton doesn't + call.releaseOutputStream(); + } + } + + protected void unmarshalCustomCallData(ObjectInput in) + throws IOException, ClassNotFoundException + {} + + /** + * Handle server-side dispatch using the RMI 1.1 stub/skeleton + * protocol, given a non-negative operation number that has + * already been read from the call stream. + * + * @param obj the target remote object for the call + * @param call the "remote call" from which operation and + * method arguments can be obtained. + * @param op the operation number + * @exception IOException if unable to marshal return result or + * release input or output streams + */ + public void oldDispatch(Remote obj, RemoteCall call, int op) + throws IOException + { + long hash; // hash for matching stub with skeleton + + try { + // read remote call header + ObjectInput in; + try { + in = call.getInputStream(); + hash = in.readLong(); + } catch (Exception readEx) { + throw new UnmarshalException("error unmarshalling call header", + readEx); + } + + // if calls are being logged, write out object id and operation + logCall(obj, skel.getOperations()[op]); + unmarshalCustomCallData(in); + // dispatch to skeleton for remote object + skel.dispatch(obj, call, op, hash); + + } catch (Throwable e) { + logCallException(e); + + ObjectOutput out = call.getResultStream(false); + if (e instanceof Error) { + e = new ServerError( + "Error occurred in server thread", (Error) e); + } else if (e instanceof RemoteException) { + e = new ServerException( + "RemoteException occurred in server thread", + (Exception) e); + } + if (suppressStackTraces) { + clearStackTraces(e); + } + out.writeObject(e); + } finally { + call.releaseInputStream(); // in case skeleton doesn't + call.releaseOutputStream(); + } + } + + /** + * Clear the stack trace of the given Throwable by replacing it with + * an empty StackTraceElement array, and do the same for all of its + * chained causative exceptions. + */ + public static void clearStackTraces(Throwable t) { + StackTraceElement[] empty = new StackTraceElement[0]; + while (t != null) { + t.setStackTrace(empty); + t = t.getCause(); + } + } + + /** + * Log the details of an incoming call. The method parameter is either of + * type java.lang.reflect.Method or java.rmi.server.Operation. + */ + private void logCall(Remote obj, Object method) { + if (callLog.isLoggable(Log.VERBOSE)) { + String clientHost; + try { + clientHost = getClientHost(); + } catch (ServerNotActiveException snae) { + clientHost = "(local)"; // shouldn't happen + } + callLog.log(Log.VERBOSE, "[" + clientHost + ": " + + obj.getClass().getName() + + ref.getObjID().toString() + ": " + + method + "]"); + } + } + + /** + * Log the exception detail of an incoming call. + */ + private void logCallException(Throwable e) { + // if calls are being logged, log them + if (callLog.isLoggable(Log.BRIEF)) { + String clientHost = ""; + try { + clientHost = "[" + getClientHost() + "] "; + } catch (ServerNotActiveException snae) { + } + callLog.log(Log.BRIEF, clientHost + "exception: ", e); + } + + // write exceptions (only) to System.err if desired + if (wantExceptionLog) { + java.io.PrintStream log = System.err; + synchronized (log) { + log.println(); + log.println("Exception dispatching call to " + + ref.getObjID() + " in thread \"" + + Thread.currentThread().getName() + + "\" at " + (new Date()) + ":"); + e.printStackTrace(log); + } + } + } + + /** + * Returns the class of the ref type to be serialized. + */ + public String getRefClass(ObjectOutput out) { + return "UnicastServerRef"; + } + + /** + * Return the client remote reference for this remoteRef. + * In the case of a client RemoteRef "this" is the answer. + * For a server remote reference, a client side one will have to + * found or created. + */ + protected RemoteRef getClientRef() { + return new UnicastRef(ref); + } + + /** + * Write out external representation for remote ref. + */ + public void writeExternal(ObjectOutput out) throws IOException { + } + + /** + * Read in external representation for remote ref. + * @exception ClassNotFoundException If the class for an object + * being restored cannot be found. + */ + public void readExternal(ObjectInput in) + throws IOException, ClassNotFoundException + { + // object is re-exported elsewhere (e.g., by UnicastRemoteObject) + ref = null; + skel = null; + } + + + /** + * A weak hash map, mapping classes to hash maps that map method + * hashes to method objects. + **/ + private static class HashToMethod_Maps + extends WeakClassHashMap<Map<Long,Method>> + { + HashToMethod_Maps() {} + + protected Map<Long,Method> computeValue(Class<?> remoteClass) { + Map<Long,Method> map = new HashMap<Long,Method>(); + for (Class<?> cl = remoteClass; + cl != null; + cl = cl.getSuperclass()) + { + for (Class<?> intf : cl.getInterfaces()) { + if (Remote.class.isAssignableFrom(intf)) { + for (Method method : intf.getMethods()) { + final Method m = method; + /* + * Set this Method object to override language + * access checks so that the dispatcher can invoke + * methods from non-public remote interfaces. + */ + AccessController.doPrivileged( + new PrivilegedAction<Void>() { + public Void run() { + m.setAccessible(true); + return null; + } + }); + map.put(Util.computeMethodHash(m), m); + } + } + } + } + return map; + } + } +} |