jdk/src/share/classes/sun/rmi/server/UnicastServerRef.java
changeset 2 90ce3da70b43
child 5506 202f599c92aa
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/sun/rmi/server/UnicastServerRef.java	Sat Dec 01 00:00:00 2007 +0000
@@ -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;
+        }
+    }
+}