blob: b928cfb9a4609fd639b049b8bf4b562373b96782 [file] [log] [blame]
/*
* Copyright (c) 1996, 2006, 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.lang.reflect.Method;
import java.rmi.MarshalException;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.ServerException;
import java.rmi.UnmarshalException;
import java.rmi.server.Operation;
import java.rmi.server.RemoteCall;
import java.rmi.server.RemoteObject;
import java.rmi.server.RemoteRef;
import java.security.AccessController;
import sun.rmi.runtime.Log;
import sun.rmi.transport.Connection;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.StreamRemoteCall;
import sun.security.action.GetBooleanAction;
/**
* NOTE: There is a JDK-internal dependency on the existence of this
* class's getLiveRef method (as it is inherited by UnicastRef2) in
* the implementation of javax.management.remote.rmi.RMIConnector.
**/
public class UnicastRef implements RemoteRef {
/**
* Client-side transport log.
*/
public static final Log clientRefLog =
Log.getLog("sun.rmi.client.ref", "transport", Util.logLevel);
/**
* Client-side call log.
*/
public static final Log clientCallLog =
Log.getLog("sun.rmi.client.call", "RMI",
AccessController.doPrivileged(
new GetBooleanAction("sun.rmi.client.logCalls")));
protected LiveRef ref;
/**
* Create a new (empty) Unicast remote reference.
*/
public UnicastRef() {
}
/**
* Create a new Unicast RemoteRef.
*/
public UnicastRef(LiveRef liveRef) {
ref = liveRef;
}
/**
* Returns the current value of this UnicastRef's underlying
* LiveRef.
*
* NOTE: There is a JDK-internal dependency on the existence of
* this method (as it is inherited by UnicastRef) in the
* implementation of javax.management.remote.rmi.RMIConnector.
**/
public LiveRef getLiveRef() {
return ref;
}
/**
* Invoke a method. This form of delegating method invocation
* to the reference allows the reference to take care of
* setting up the connection to the remote host, marshalling
* some representation for the method and parameters, then
* communicating the method invocation to the remote host.
* This method either returns the result of a method invocation
* on the remote object which resides on the remote host or
* throws a RemoteException if the call failed or an
* application-level exception if the remote invocation throws
* an exception.
*
* @param obj the proxy for the remote object
* @param method the method to be invoked
* @param params the parameter list
* @param opnum a hash that may be used to represent the method
* @since 1.2
*/
public Object invoke(Remote obj,
Method method,
Object[] params,
long opnum)
throws Exception
{
if (clientRefLog.isLoggable(Log.VERBOSE)) {
clientRefLog.log(Log.VERBOSE, "method: " + method);
}
if (clientCallLog.isLoggable(Log.VERBOSE)) {
logClientCall(obj, method);
}
Connection conn = ref.getChannel().newConnection();
RemoteCall call = null;
boolean reuse = true;
/* If the call connection is "reused" early, remember not to
* reuse again.
*/
boolean alreadyFreed = false;
try {
if (clientRefLog.isLoggable(Log.VERBOSE)) {
clientRefLog.log(Log.VERBOSE, "opnum = " + opnum);
}
// create call context
call = new StreamRemoteCall(conn, ref.getObjID(), -1, opnum);
// marshal parameters
try {
ObjectOutput out = call.getOutputStream();
marshalCustomCallData(out);
Class<?>[] types = method.getParameterTypes();
for (int i = 0; i < types.length; i++) {
marshalValue(types[i], params[i], out);
}
} catch (IOException e) {
clientRefLog.log(Log.BRIEF,
"IOException marshalling arguments: ", e);
throw new MarshalException("error marshalling arguments", e);
}
// unmarshal return
call.executeCall();
try {
Class<?> rtype = method.getReturnType();
if (rtype == void.class)
return null;
ObjectInput in = call.getInputStream();
/* StreamRemoteCall.done() does not actually make use
* of conn, therefore it is safe to reuse this
* connection before the dirty call is sent for
* registered refs.
*/
Object returnValue = unmarshalValue(rtype, in);
/* we are freeing the connection now, do not free
* again or reuse.
*/
alreadyFreed = true;
/* if we got to this point, reuse must have been true. */
clientRefLog.log(Log.BRIEF, "free connection (reuse = true)");
/* Free the call's connection early. */
ref.getChannel().free(conn, true);
return returnValue;
} catch (IOException e) {
clientRefLog.log(Log.BRIEF,
"IOException unmarshalling return: ", e);
throw new UnmarshalException("error unmarshalling return", e);
} catch (ClassNotFoundException e) {
clientRefLog.log(Log.BRIEF,
"ClassNotFoundException unmarshalling return: ", e);
throw new UnmarshalException("error unmarshalling return", e);
} finally {
try {
call.done();
} catch (IOException e) {
/* WARNING: If the conn has been reused early,
* then it is too late to recover from thrown
* IOExceptions caught here. This code is relying
* on StreamRemoteCall.done() not actually
* throwing IOExceptions.
*/
reuse = false;
}
}
} catch (RuntimeException e) {
/*
* Need to distinguish between client (generated by the
* invoke method itself) and server RuntimeExceptions.
* Client side RuntimeExceptions are likely to have
* corrupted the call connection and those from the server
* are not likely to have done so. If the exception came
* from the server the call connection should be reused.
*/
if ((call == null) ||
(((StreamRemoteCall) call).getServerException() != e))
{
reuse = false;
}
throw e;
} catch (RemoteException e) {
/*
* Some failure during call; assume connection cannot
* be reused. Must assume failure even if ServerException
* or ServerError occurs since these failures can happen
* during parameter deserialization which would leave
* the connection in a corrupted state.
*/
reuse = false;
throw e;
} catch (Error e) {
/* If errors occurred, the connection is most likely not
* reusable.
*/
reuse = false;
throw e;
} finally {
/* alreadyFreed ensures that we do not log a reuse that
* may have already happened.
*/
if (!alreadyFreed) {
if (clientRefLog.isLoggable(Log.BRIEF)) {
clientRefLog.log(Log.BRIEF, "free connection (reuse = " +
reuse + ")");
}
ref.getChannel().free(conn, reuse);
}
}
}
protected void marshalCustomCallData(ObjectOutput out) throws IOException
{}
/**
* Marshal value to an ObjectOutput sink using RMI's serialization
* format for parameters or return values.
*/
protected static void marshalValue(Class<?> type, Object value,
ObjectOutput out)
throws IOException
{
if (type.isPrimitive()) {
if (type == int.class) {
out.writeInt(((Integer) value).intValue());
} else if (type == boolean.class) {
out.writeBoolean(((Boolean) value).booleanValue());
} else if (type == byte.class) {
out.writeByte(((Byte) value).byteValue());
} else if (type == char.class) {
out.writeChar(((Character) value).charValue());
} else if (type == short.class) {
out.writeShort(((Short) value).shortValue());
} else if (type == long.class) {
out.writeLong(((Long) value).longValue());
} else if (type == float.class) {
out.writeFloat(((Float) value).floatValue());
} else if (type == double.class) {
out.writeDouble(((Double) value).doubleValue());
} else {
throw new Error("Unrecognized primitive type: " + type);
}
} else {
out.writeObject(value);
}
}
/**
* Unmarshal value from an ObjectInput source using RMI's serialization
* format for parameters or return values.
*/
protected static Object unmarshalValue(Class<?> type, ObjectInput in)
throws IOException, ClassNotFoundException
{
if (type.isPrimitive()) {
if (type == int.class) {
return Integer.valueOf(in.readInt());
} else if (type == boolean.class) {
return Boolean.valueOf(in.readBoolean());
} else if (type == byte.class) {
return Byte.valueOf(in.readByte());
} else if (type == char.class) {
return Character.valueOf(in.readChar());
} else if (type == short.class) {
return Short.valueOf(in.readShort());
} else if (type == long.class) {
return Long.valueOf(in.readLong());
} else if (type == float.class) {
return Float.valueOf(in.readFloat());
} else if (type == double.class) {
return Double.valueOf(in.readDouble());
} else {
throw new Error("Unrecognized primitive type: " + type);
}
} else {
return in.readObject();
}
}
/**
* Create an appropriate call object for a new call on this object.
* Passing operation array and index, allows the stubs generator to
* assign the operation indexes and interpret them. The RemoteRef
* may need the operation to encode in for the call.
*/
public RemoteCall newCall(RemoteObject obj, Operation[] ops, int opnum,
long hash)
throws RemoteException
{
clientRefLog.log(Log.BRIEF, "get connection");
Connection conn = ref.getChannel().newConnection();
try {
clientRefLog.log(Log.VERBOSE, "create call context");
/* log information about the outgoing call */
if (clientCallLog.isLoggable(Log.VERBOSE)) {
logClientCall(obj, ops[opnum]);
}
RemoteCall call =
new StreamRemoteCall(conn, ref.getObjID(), opnum, hash);
try {
marshalCustomCallData(call.getOutputStream());
} catch (IOException e) {
throw new MarshalException("error marshaling " +
"custom call data");
}
return call;
} catch (RemoteException e) {
ref.getChannel().free(conn, false);
throw e;
}
}
/**
* Invoke makes the remote call present in the RemoteCall object.
*
* Invoke will raise any "user" exceptions which
* should pass through and not be caught by the stub. If any
* exception is raised during the remote invocation, invoke should
* take care of cleaning up the connection before raising the
* "user" or remote exception.
*/
public void invoke(RemoteCall call) throws Exception {
try {
clientRefLog.log(Log.VERBOSE, "execute call");
call.executeCall();
} catch (RemoteException e) {
/*
* Call did not complete; connection can't be reused.
*/
clientRefLog.log(Log.BRIEF, "exception: ", e);
free(call, false);
throw e;
} catch (Error e) {
/* If errors occurred, the connection is most likely not
* reusable.
*/
clientRefLog.log(Log.BRIEF, "error: ", e);
free(call, false);
throw e;
} catch (RuntimeException e) {
/*
* REMIND: Since runtime exceptions are no longer wrapped,
* we can't assue that the connection was left in
* a reusable state. Is this okay?
*/
clientRefLog.log(Log.BRIEF, "exception: ", e);
free(call, false);
throw e;
} catch (Exception e) {
/*
* Assume that these other exceptions are user exceptions
* and leave the connection in a reusable state.
*/
clientRefLog.log(Log.BRIEF, "exception: ", e);
free(call, true);
/* reraise user (and unknown) exceptions. */
throw e;
}
/*
* Don't free the connection if an exception did not
* occur because the stub needs to unmarshal the
* return value. The connection will be freed
* by a call to the "done" method.
*/
}
/**
* Private method to free a connection.
*/
private void free(RemoteCall call, boolean reuse) throws RemoteException {
Connection conn = ((StreamRemoteCall)call).getConnection();
ref.getChannel().free(conn, reuse);
}
/**
* Done should only be called if the invoke returns successfully
* (non-exceptionally) to the stub. It allows the remote reference to
* clean up (or reuse) the connection.
*/
public void done(RemoteCall call) throws RemoteException {
/* Done only uses the connection inside the call to obtain the
* channel the connection uses. Once all information is read
* from the connection, the connection may be freed.
*/
clientRefLog.log(Log.BRIEF, "free connection (reuse = true)");
/* Free the call connection early. */
free(call, true);
try {
call.done();
} catch (IOException e) {
/* WARNING: If the conn has been reused early, then it is
* too late to recover from thrown IOExceptions caught
* here. This code is relying on StreamRemoteCall.done()
* not actually throwing IOExceptions.
*/
}
}
/**
* Log the details of an outgoing call. The method parameter is either of
* type java.lang.reflect.Method or java.rmi.server.Operation.
*/
void logClientCall(Object obj, Object method) {
clientCallLog.log(Log.VERBOSE, "outbound call: " +
ref + " : " + obj.getClass().getName() +
ref.getObjID().toString() + ": " + method);
}
/**
* Returns the class of the ref type to be serialized
*/
public String getRefClass(ObjectOutput out) {
return "UnicastRef";
}
/**
* Write out external representation for remote ref.
*/
public void writeExternal(ObjectOutput out) throws IOException {
ref.write(out, false);
}
/**
* 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
{
ref = LiveRef.read(in, false);
}
//----------------------------------------------------------------------;
/**
* Method from object, forward from RemoteObject
*/
public String remoteToString() {
return Util.getUnqualifiedName(getClass()) + " [liveRef: " + ref + "]";
}
/**
* default implementation of hashCode for remote objects
*/
public int remoteHashCode() {
return ref.hashCode();
}
/** default implementation of equals for remote objects
*/
public boolean remoteEquals(RemoteRef sub) {
if (sub instanceof UnicastRef)
return ref.remoteEquals(((UnicastRef)sub).ref);
return false;
}
}