/* * Copyright (c) 1996, 2005, Oracle and/or its affiliates. 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. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.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 hashToMethod_Map = null; /** * A weak hash map, mapping classes to hash maps that map method * hashes to method objects. **/ private static final WeakClassHashMap> hashToMethod_Maps = new HashToMethod_Maps(); /** cache of impl classes that have no corresponding skeleton class */ private static final Map,?> withoutSkeletons = Collections.synchronizedMap(new WeakHashMap,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(); try { Class clazz = Class.forName("sun.rmi.transport.DGCImpl_Skel"); if (clazz.isAssignableFrom(skel.getClass())) { ((MarshalInputStream)in).useCodebaseOnly(); } } catch (ClassNotFoundException ignore) { } 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> { HashToMethod_Maps() {} protected Map computeValue(Class remoteClass) { Map map = new HashMap<>(); 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() { public Void run() { m.setAccessible(true); return null; } }); map.put(Util.computeMethodHash(m), m); } } } } return map; } } }