/*
* 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.jeri;
import com.sun.jini.action.GetBooleanAction;
import com.sun.jini.jeri.internal.runtime.Util;
import com.sun.jini.jeri.internal.runtime.WeakKey;
import com.sun.jini.logging.Levels;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.lang.ref.ReferenceQueue;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
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.ServerNotActiveException;
import java.security.AccessControlException;
import java.security.AccessController;
import java.security.CodeSource;
import java.security.Permission;
import java.security.Principal;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import javax.security.auth.Subject;
import net.jini.core.constraint.Integrity;
import net.jini.core.constraint.InvocationConstraint;
import net.jini.core.constraint.InvocationConstraints;
import net.jini.core.constraint.MethodConstraints;
import net.jini.export.ServerContext;
import net.jini.io.MarshalInputStream;
import net.jini.io.MarshalOutputStream;
import net.jini.io.UnsupportedConstraintException;
import net.jini.io.context.ClientSubject;
import net.jini.security.AccessPermission;
import net.jini.security.proxytrust.ProxyTrust;
import net.jini.security.proxytrust.ProxyTrustVerifier;
import net.jini.security.proxytrust.ServerProxyTrust;
/**
* A basic implementation of the {@link InvocationDispatcher} interface,
* providing preinvocation access control for
* remote objects exported using {@link BasicJeriExporter}.
*
* <p>This invocation dispatcher handles incoming remote method invocations
* initiated by proxies using {@link BasicInvocationHandler}, and expects
* that a dispatched request, encapsulated in the {@link InboundRequest}
* object passed to the {@link #dispatch dispatch} method, was sent using
* the protocol implemented by <code>BasicInvocationHandler</code>.
*
* <p>A basic permission-based preinvocation access control mechanism is
* provided. A permission class can be specified when an invocation
* dispatcher is constructed; instances of that class are constructed using
* either a {@link Method} instance or a <code>String</code> representing
* the remote method being invoked. The class can have a constructor with a
* <code>Method</code> parameter to permit an arbitrary mapping to the
* actual permission target name and actions; otherwise, the class must
* have a constructor taking the fully qualified name of the remote method
* as a <code>String</code>. For each incoming call on a remote object, the
* client subject must be granted the associated permission for that remote
* method. (Access control for an individual remote method can effectively
* be disabled by granting the associated permission to all protection
* domains.) A simple subclass of {@link AccessPermission} is typically
* used as the permission class.
*
* <p>Other access control mechanisms can be implemented by subclassing this
* class and overriding the various protected methods.
*
* <p>This class is designed to support dispatching remote calls to the
* {@link ProxyTrust#getProxyVerifier ProxyTrust.getProxyVerifier} method
* to the local {@link ServerProxyTrust#getProxyVerifier
* ServerProxyTrust.getProxyVerifier} method of a remote object, to allow a
* remote object to be exported in such a way that its proxy can be
* directly trusted by clients as well as in such a way that its proxy can
* be trusted by clients using {@link ProxyTrustVerifier}.
*
* @author Sun Microsystems, Inc.
* @see BasicInvocationHandler
* @since 2.0
*
* @com.sun.jini.impl
*
* This implementation uses the following system property:
* <dl>
* <dt><code>com.sun.jini.jeri.server.suppressStackTrace</code>
* <dd>If <code>true</code>, removes server-side stack traces before
* marshalling an exception thrown as a result of a remote call. The
* default value is <code>false</code>.
* </dl>
*
* <p>This implementation uses the {@link Logger} named
* <code>net.jini.jeri.BasicInvocationDispatcher</code> to log
* information at the following levels:
*
* <table summary="Describes what is logged by BasicInvocationDispatcher at
* various logging levels" border=1 cellpadding=5>
*
* <tr> <th> Level <th> Description
*
* <tr> <td> {@link Levels#FAILED FAILED} <td> exception that caused a request
* to be aborted
*
* <tr> <td> {@link Levels#FAILED FAILED} <td> exceptional result of a
* remote call
*
* <tr> <td> {@link Level#FINE FINE} <td> incoming remote call
*
* <tr> <td> {@link Level#FINE FINE} <td> successful return of remote call
*
* <tr> <td> {@link Level#FINEST FINEST} <td> more detailed information on
* the above (for example, actual argument and return values)
*
* </table>
**/
public class BasicInvocationDispatcher implements InvocationDispatcher {
/** Marshal stream protocol version. */
static final byte VERSION = 0x0;
/** Marshal stream protocol version mismatch. */
static final byte MISMATCH = 0x0;
/** Normal return (with or without return value). */
static final byte RETURN = 0x01;
/** Exceptional return. */
static final byte THROW = 0x02;
/** The class loader used by createMarshalInputStream */
private final ClassLoader loader;
/** The server constraints. */
private final MethodConstraints serverConstraints;
/**
* Constructor for the Permission class, that has either one String
* or one Method parameter, or null.
*/
private final Constructor permConstructor;
/** True if permConstructor has a Method parameter. */
private final boolean permUsesMethod;
/** Map from Method to Permission. */
private final Map permissions;
/** Map from Long method hash to Method, for all remote methods. */
private final Map methods;
/** Map from WeakKey(Subject) to ProtectionDomain. */
private static final Map domains = new HashMap();
/** Reference queue for the weak keys in the domains map. */
private static final ReferenceQueue queue = new ReferenceQueue();
/** dispatch logger */
private static final Logger logger =
Logger.getLogger("net.jini.jeri.BasicInvocationDispatcher");
/**
* Flag to remove server-side stack traces before marshalling
* exceptions thrown by remote invocations to this VM
*/
private static final boolean suppressStackTraces =
((Boolean) AccessController.doPrivileged(new GetBooleanAction(
"com.sun.jini.jeri.server.suppressStackTraces")))
.booleanValue();
/** Empty codesource. */
private static final CodeSource emptyCS =
new CodeSource(null, (Certificate[]) null);
/** ProtectionDomain containing the empty codesource. */
private static final ProtectionDomain emptyPD =
new ProtectionDomain(emptyCS, null, null, null);
/** Cached getClassLoader permission */
private static final Permission getClassLoaderPermission =
new RuntimePermission("getClassLoader");
/**
* Creates an invocation dispatcher to receive incoming remote calls
* for the specified methods, for a server and transport with the
* specified capabilities, enforcing the specified constraints,
* performing preinvocation access control using the specified
* permission class (if any). The specified class loader is used by
* the {@link #createMarshalInputStream createMarshalInputStream}
* method.
*
* <p>For each combination of constraints that might need to be
* enforced (obtained by calling the {@link
* MethodConstraints#possibleConstraints possibleConstraints} method on
* the specified server constraints, or using an empty constraints
* instance if the specified server constraints instance is
* <code>null</code>), calling the {@link
* ServerCapabilities#checkConstraints checkConstraints} method of the
* specified capabilities object with those constraints must return
* constraints containing at most an {@link Integrity} constraint as a
* requirement, or an <code>ExportException</code> is thrown.
*
* @param methods a collection of {@link Method} instances for the
* remote methods
* @param serverCapabilities the transport capabilities of the server
* @param serverConstraints the server constraints, or <code>null</code>
* @param permissionClass the permission class, or <code>null</code>
* @param loader the class loader, or <code>null</code>
*
* @throws SecurityException if the permission class is not
* <code>null</code> and is in a named package and a
* security manager exists and invoking its
* <code>checkPackageAccess</code> method with the package
* name of the permission class throws a
* <code>SecurityException</code>
* @throws IllegalArgumentException if the permission class
* is abstract, is not <code>public</code>, is not a subclass
* of {@link Permission}, or does not have a public
* constructor that has either one <code>String</code>
* parameter or one {@link Method} parameter and has no
* declared exceptions, or if any element of
* <code>methods</code> is not a {@link Method} instance
* @throws NullPointerException if <code>methods</code> or
* <code>serverCapabilities</code> is <code>null</code>, or if
* <code>methods</code> contains a <code>null</code> element
* @throws ExportException if any of the possible server constraints
* cannot be satisfied according to the specified server
* capabilities
**/
public BasicInvocationDispatcher(Collection methods,
ServerCapabilities serverCapabilities,
MethodConstraints serverConstraints,
Class permissionClass,
ClassLoader loader)
throws ExportException
{
if (serverCapabilities == null) {
throw new NullPointerException();
}
this.methods = new HashMap();
this.loader = loader;
for (Iterator iter = methods.iterator(); iter.hasNext(); ) {
Object m = iter.next();
if (m == null) {
throw new NullPointerException("methods contains null");
} else if (!(m instanceof Method)) {
throw new IllegalArgumentException(
"methods must contain only Methods");
}
this.methods.put(new Long(Util.getMethodHash((Method) m)), m);
}
this.serverConstraints = serverConstraints;
if (permissionClass != null) {
Util.checkPackageAccess(permissionClass);
}
permConstructor = getConstructor(permissionClass);
permUsesMethod =
(permConstructor != null &&
permConstructor.getParameterTypes()[0] == Method.class);
permissions = (permConstructor == null ?
null :
new IdentityHashMap(methods.size() + 2));
try {
if (serverConstraints == null) {
checkConstraints(serverCapabilities,
InvocationConstraints.EMPTY);
} else {
Iterator iter = serverConstraints.possibleConstraints();
while (iter.hasNext()) {
checkConstraints(serverCapabilities,
(InvocationConstraints) iter.next());
}
}
} catch (UnsupportedConstraintException e) {
throw new ExportException(
"server does not support some constraints", e);
}
}
/**
* Check that the only unfulfilled requirement is Integrity.
*/
private static void checkConstraints(ServerCapabilities serverCapabilities,
InvocationConstraints constraints)
throws UnsupportedConstraintException
{
InvocationConstraints unfulfilled =
serverCapabilities.checkConstraints(constraints);
for (Iterator i = unfulfilled.requirements().iterator(); i.hasNext();)
{
InvocationConstraint c = (InvocationConstraint) i.next();
if (!(c instanceof Integrity)) {
throw new UnsupportedConstraintException(
"cannot satisfy unfulfilled constraint: " + c);
}
// REMIND: support ConstraintAlternatives containing Integrity?
}
}
/**
* Returns the class loader specified during construction.
*
* @return the class loader
*/
protected final ClassLoader getClassLoader() {
return loader;
}
/**
* Checks that the specified class is a valid permission class for use in
* preinvocation access control.
*
* @param permissionClass the permission class, or <code>null</code>
* @throws IllegalArgumentException if the permission class is abstract,
* is not a subclass of {@link Permission}, or does not have a public
* constructor that has either one <code>String</code> parameter or one
* {@link Method} parameter and has no declared exceptions
**/
public static void checkPermissionClass(Class permissionClass) {
getConstructor(permissionClass);
}
/**
* Checks that the specified class is a subclass of Permission, is
* public, is not abstract, and has the right one-parameter Method or
* String constructor, and returns that constructor, otherwise throws
* IllegalArgumentException.
**/
private static Constructor getConstructor(Class permissionClass) {
if (permissionClass == null) {
return null;
} else {
int mods = permissionClass.getModifiers();
if (!Permission.class.isAssignableFrom(permissionClass) ||
Modifier.isAbstract(mods) || !Modifier.isPublic(mods))
{
throw new IllegalArgumentException("bad permission class");
}
}
try {
Constructor permConstructor =
permissionClass.getConstructor(new Class[]{Method.class});
if (permConstructor.getExceptionTypes().length == 0) {
return permConstructor;
}
} catch (NoSuchMethodException e) {
}
try {
Constructor permConstructor =
permissionClass.getConstructor(new Class[]{String.class});
if (permConstructor.getExceptionTypes().length == 0) {
return permConstructor;
}
} catch (NoSuchMethodException ee) {
}
throw new IllegalArgumentException("bad permission class");
}
/**
* Dispatches the specified inbound request to the specified remote object.
* When used in conjunction with {@link BasicJeriExporter}, this
* method is called in a context that has the security context and
* context class loader specified by
* {@link BasicJeriExporter#export BasicJeriExporter.export}.
*
* <p><code>BasicInvocationDispatcher</code> implements this method to
* execute the following actions in order:
*
* <ul>
* <li>A byte specifying the marshal stream protocol version is read
* from the request input stream of the inbound request. If any
* exception is thrown when reading this byte, the inbound request is
* aborted and this method returns. If the byte is not
* <code>0x00</code>, two byte values of <code>0x00</code> (indicating
* a marshal stream protocol version mismatch) are written to the
* response output stream of the inbound request, the output stream is
* closed, and this method returns.
*
* <li>If the version byte is <code>0x00</code>, a second byte
* specifying object integrity is read from the same stream. If any
* exception is thrown when reading this byte, the inbound request is
* aborted and this method returns. Object integrity will be enforced
* if the value read is not <code>0x00</code>, but will not be enforced
* if the value is <code>0x00</code>. An {@link
* net.jini.io.context.IntegrityEnforcement} element is then added to
* the server context, reflecting whether or not object integrity is
* being enforced.
*
* <li>The {@link #createMarshalInputStream createMarshalInputStream}
* method of this invocation dispatcher is called, passing the remote
* object, the inbound request, a boolean indicating if object
* integrity is being enforced, and the server context, to create the
* marshal input stream for unmarshalling the request.
*
* <li>The {@link #unmarshalMethod unmarshalMethod} of this
* invocation dispatcher is called with the remote object, the marshal
* input stream, and the server context to obtain the remote method.
*
* <li> The {@link InboundRequest#checkConstraints checkConstraints}
* method of the inbound request is called with the constraints that
* must be enforced for that remote method, obtained by passing the
* remote method to the {@link MethodConstraints#getConstraints
* getConstraints} method of this invocation dispatcher's server
* constraints, and adding {@link Integrity#YES Integrity.YES} as a
* requirement if object integrity is being enforced. If the
* unfulfilled requirements returned by <code>checkConstraints</code>
* contains a constraint that is not an instance of {@link Integrity}
* or if integrity is not being enforced and the returned requirements
* contains the element <code>Integrity.YES</code>, an
* <code>UnsupportedConstraintException</code> is sent back to the
* caller as described further below. Otherwise, the {@link
* #checkAccess checkAccess} method of this invocation dispatcher is
* called with the remote object, the remote method, the enforced
* constraints, and the server context.
*
* <li>The method arguments are obtained by calling the {@link
* #unmarshalArguments unmarshalArguments} method of this invocation
* dispatcher with the remote object, the remote method, the marshal
* input stream, and the server context.
*
* <li>If any exception is thrown during this unmarshalling, that exception
* is sent back to the caller as described further below; however, if the
* exception is a checked exception ({@link IOException},
* {@link ClassNotFoundException}, or {@link NoSuchMethodException}), the
* exception is first wrapped in an {@link UnmarshalException} and the
* wrapped exception is sent back.
*
* <li>Otherwise, if unmarshalling is successful, the {@link #invoke
* invoke} method of this invocation dispatcher is then called with the
* remote object, the remote method, the arguments returned by
* <code>unmarshalArguments</code>, and the server context. If
* <code>invoke</code> throws an exception, that exception is sent back
* to the caller as described further below.
*
* <li>The input stream is closed whether or not an exception was
* thrown unmarshalling the arguments or invoking the method.
*
* <li>If <code>invoke</code> returns normally, a byte value of
* <code>0x01</code> is written to the response output stream of the
* inbound request. Then the {@link #createMarshalOutputStream
* createMarshalOutputStream} method of this invocation dispatcher is
* called, passing the remote object, the remote method, the inbound
* request, and the server context, to create the marshal output stream
* for marshalling the response. Then the {@link #marshalReturn
* marshalReturn} method of this invocation dispatcher is called with
* the remote object, the remote method, the value returned by
* <code>invoke</code>, the marshal output stream, and the server
* context. Then the marshal output stream is closed. Any exception
* thrown during this marshalling is ignored.
*
* <li>When an exception is sent back to the caller, a byte value of
* <code>0x02</code> is written to the response output stream of the
* inbound request. Then a marshal output stream is created by calling
* the <code>createMarshalOutputStream</code> method as described above
* (but with a <code>null</code> remote method if one was not
* successfully unmarshalled). Then the {@link #marshalThrow
* marshalThrow} method of this invocation dispatcher is called with
* the remote object, the remote method (or <code>null</code> if one
* was not successfully unmarshalled), the exception, the marshal
* output stream, and the server context. Then the marshal output
* stream is closed. Any exception thrown during this marshalling is
* ignored. If the exception being sent back is a
* <code>RemoteException</code>, it is wrapped in a {@link
* ServerException} and the wrapped exception is passed to
* <code>marshalThrow</code>. If the exception being sent back is an
* <code>Error</code>, it is wrapped in a {@link ServerError} and the
* wrapped exception is passed to <code>marshalThrow</code>. If the
* exception being sent back occurred before or during the call to
* <code>unmarshalMethod</code>, then the remote method passed to
* <code>marshalThrow</code> is <code>null</code>.
* </ul>
*
* @throws NullPointerException {@inheritDoc}
**/
public void dispatch(Remote impl,
InboundRequest request,
Collection context)
{
if (impl == null || context == null) {
throw new NullPointerException();
}
/*
* Read (and check) version number and integrity flag.
*/
InputStream rin = null;
boolean integrity;
try {
rin = request.getRequestInputStream();
switch (rin.read()) {
case VERSION:
break;
case -1:
throw new EOFException();
default:
rin.close();
OutputStream ros = request.getResponseOutputStream();
ros.write(MISMATCH);
ros.write(VERSION);
ros.close();
return;
}
switch (rin.read()) {
case 0:
integrity = false;
break;
case -1:
throw new EOFException();
default:
integrity = true;
}
} catch (Throwable t) {
if (logger.isLoggable(Levels.FAILED)) {
logThrow(impl, t);
}
request.abort();
return;
}
Method method = null;
Object returnValue= null;
Throwable t = null;
boolean fromImpl = false;
Util.populateContext(context, integrity);
ObjectInputStream in = null;
try {
/*
* Unmarshal method and check security constraints.
*/
in = createMarshalInputStream(impl, request, integrity, context);
method = unmarshalMethod(impl, in, context);
InvocationConstraints sc =
(serverConstraints == null ?
InvocationConstraints.EMPTY :
serverConstraints.getConstraints(method));
if (integrity && !sc.requirements().contains(Integrity.YES)) {
Collection requirements = new ArrayList(sc.requirements());
requirements.add(Integrity.YES);
sc = new InvocationConstraints(requirements, sc.preferences());
}
InvocationConstraints unfulfilled = request.checkConstraints(sc);
for (Iterator i = unfulfilled.requirements().iterator();
i.hasNext();)
{
InvocationConstraint c = (InvocationConstraint) i.next();
if (!(c instanceof Integrity) ||
(!integrity && c == Integrity.YES))
{
throw new UnsupportedConstraintException(
"cannot satisfy unfulfilled constraint: " + c);
}
// REMIND: support ConstraintAlternatives containing Integrity?
}
checkAccess(impl, method, sc, context);
/*
* Unmarshal arguments.
*/
Object[] args = unmarshalArguments(impl, method, in, context);
if (logger.isLoggable(Level.FINE)) {
logCall(impl, method, args);
}
/*
* Invoke method on remote object.
*/
try {
returnValue = invoke(impl, method, args, context);
if (logger.isLoggable(Level.FINE)) {
logReturn(impl, method, returnValue);
}
} catch (Throwable tt) {
t = tt;
fromImpl = true;
}
} catch (RuntimeException e) {
t = e;
} catch (Exception e) {
t = new UnmarshalException("unmarshalling method/arguments", e);
} catch (Throwable tt) {
t = tt;
} finally {
if (in != null) {
try {
in.close();
} catch (IOException ignore) {
}
}
}
/*
* Marshal return value or exception.
*/
try {
request.getResponseOutputStream().write(t == null ?
RETURN : THROW);
ObjectOutputStream out =
createMarshalOutputStream(impl, method, request, context);
if (t != null) {
if (logger.isLoggable(Levels.FAILED)) {
logThrow(impl, method, t, fromImpl);
}
if (t instanceof RemoteException) {
t = new ServerException("RemoteException in server thread",
(Exception) t);
} else if (t instanceof Error) {
t = new ServerError("Error in server thread", (Error) t);
}
if (suppressStackTraces) {
Util.clearStackTraces(t);
}
marshalThrow(impl, method, t, out, context);
} else {
marshalReturn(impl, method, returnValue, out, context);
}
out.close();
} catch (Throwable tt) {
/*
* All exceptions are fatal at this point. There is no
* recovery if a problem occurs writing the result, so
* abort the call and return. But first try to close the
* response output stream, in case the IOException was
* able to be serialized for the client successfully.
*/
try {
request.getResponseOutputStream().close();
} catch (IOException ignore) {
}
request.abort();
if (logger.isLoggable(Levels.FAILED)) {
logThrow(impl, tt);
}
}
}
/**
* Returns a new marshal input stream to use to read objects from the
* request input stream obtained by invoking the {@link
* InboundRequest#getRequestInputStream getRequestInputStream} method
* on the given <code>request</code>.
*
* <p><code>BasicInvocationDispatcher</code> implements this method as
* follows:
*
* <p>First, a class loader is selected to use as the
* <code>defaultLoader</code> and the <code>verifierLoader</code> for
* the marshal input stream instance. If the class loader specified at
* construction is not <code>null</code>, the selected loader is that
* loader. Otherwise, if a security manager exists, its {@link
* SecurityManager#checkPermission checkPermission} method is invoked
* with the permission <code>{@link
* RuntimePermission}("getClassLoader")</code>; this invocation may
* throw a <code>SecurityException</code>. If the above security check
* succeeds, the selected loader is the class loader of
* <code>impl</code>'s class.
*
* <p>This method returns a new {@link MarshalInputStream} instance
* constructed with the input stream (obtained from the
* <code>request</code> as specified above) for the input stream
* <code>in</code>, the selected loader for <code>defaultLoader</code>
* and <code>verifierLoader</code>, the boolean <code>integrity</code>
* for <code>verifyCodebaseIntegrity</code>, and an unmodifiable view
* of <code>context</code> for the <code>context</code> collection.
* The {@link MarshalInputStream#useCodebaseAnnotations
* useCodebaseAnnotations} method is invoked on the created stream
* before it is returned.
*
* <p>A subclass can override this method to control how the marshal input
* stream is created or implemented.
*
* @param impl the remote object
* @param request the inbound request
* @param integrity <code>true</code> if object integrity is being
* enforced for the remote call, and <code>false</code> otherwise
* @param context the server context
* @return a new marshal input stream for unmarshalling a call request
* @throws IOException if an I/O exception occurs
* @throws NullPointerException if any argument is <code>null</code>
**/
protected ObjectInputStream
createMarshalInputStream(Object impl,
InboundRequest request,
boolean integrity,
Collection context)
throws IOException
{
ClassLoader streamLoader;
if (loader != null) {
streamLoader = getClassLoader();
} else {
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkPermission(getClassLoaderPermission);
}
streamLoader = impl.getClass().getClassLoader();
}
Collection unmodContext = Collections.unmodifiableCollection(context);
MarshalInputStream in =
new MarshalInputStream(request.getRequestInputStream(),
streamLoader, integrity,
streamLoader, unmodContext);
in.useCodebaseAnnotations();
return in;
}
/**
* Returns a new marshal output stream to use to write objects to the
* response output stream obtained by invoking the {@link
* InboundRequest#getResponseOutputStream getResponseOutputStream}
* method on the given <code>request</code>.
*
* <p>This method will be called with a <code>null</code>
* <code>method</code> argument if an <code>IOException</code> occurred
* when reading method information from the incoming call stream.
*
* <p><code>BasicInvocationDispatcher</code> implements this method to
* return a new {@link MarshalOutputStream} instance constructed with
* the output stream obtained from the <code>request</code> as
* specified above and an unmodifiable view of the given
* <code>context</code> collection.
*
* <p>A subclass can override this method to control how the marshal output
* stream is created or implemented.
*
* @param impl the remote object
* @param method the possibly-<code>null</code> <code>Method</code>
* instance corresponding to the interface method invoked on
* the remote object
* @param request the inbound request
* @param context the server context
* @return a new marshal output stream for marshalling a call response
* @throws IOException if an I/O exception occurs
* @throws NullPointerException if <code>impl</code>,
* <code>request</code>, or <code>context</code> is
* <code>null</code>
**/
protected ObjectOutputStream
createMarshalOutputStream(Object impl,
Method method,
InboundRequest request,
Collection context)
throws IOException
{
if (impl == null) {
throw new NullPointerException();
}
OutputStream out = request.getResponseOutputStream();
Collection unmodContext = Collections.unmodifiableCollection(context);
return new MarshalOutputStream(out, unmodContext);
}
/**
* Checks that the client has permission to invoke the specified method on
* the specified remote object.
*
* <p><code>BasicInvocationDispatcher</code> implements this method as
* follows:
*
* <p>If a permission class was specified when this invocation
* dispatcher was constructed, {@link #checkClientPermission
* checkClientPermission} is called with a permission constructed from
* the permission class. If the permission class has a constructor with
* a <code>Method</code> parameter, the permission is constructed by
* passing the specified method to that constructor. Otherwise the
* permission is constructed by passing the fully qualified name of the
* method to the constructor with a <code>String</code> parameter,
* where the argument is formed by concatenating the name of the
* declaring class of the specified method and the name of the method,
* separated by ".".
*
* <p>A subclass can override this method to implement other preinvocation
* access control mechanisms.
*
* @param impl the remote object
* @param method the remote method
* @param constraints the enforced constraints for the specified
* method, or <code>null</code>
* @param context the server context
* @throws SecurityException if the current client subject does not
* have permission to invoke the method
* @throws IllegalStateException if the current thread is not executing an
* incoming remote call for a remote object
* @throws NullPointerException if <code>impl</code>,
* <code>method</code>, or <code>context</code> is
* <code>null</code>
**/
protected void checkAccess(Remote impl,
Method method,
InvocationConstraints constraints,
Collection context)
{
if (impl == null || method == null || context == null) {
throw new NullPointerException();
}
if (permConstructor == null) {
return;
}
Permission perm;
synchronized (permissions) {
perm = (Permission) permissions.get(method);
}
if (perm == null) {
try {
perm = (Permission) permConstructor.newInstance(new Object[]{
permUsesMethod ?
(Object) method :
method.getDeclaringClass().getName() + "." +
method.getName()});
} catch (InvocationTargetException e) {
Throwable t = e.getTargetException();
if (t instanceof Error) {
throw (Error) t;
}
throw (RuntimeException) t;
} catch (Exception e) {
throw new RuntimeException("unexpected exception", e);
}
synchronized (permissions) {
permissions.put(method, perm);
}
}
checkClientPermission(perm);
}
/**
* Checks that the client subject for the current remote call has the
* specified permission. The client subject is obtained by calling {@link
* ServerContext#getServerContextElement
* ServerContext.getServerContextElement}, passing the class {@link
* ClientSubject}, and then calling the {@link
* ClientSubject#getClientSubject getClientSubject} method of the returned
* element (if any). If a security manager is installed, a {@link
* ProtectionDomain} is constructed with an empty {@link CodeSource}
* (<code>null</code> location and certificates), <code>null</code>
* permissions, <code>null</code> class loader, and the principals from
* the client subject (if any), and the <code>implies</code> method of
* that protection domain is invoked with the specified permission. If
* <code>true</code> is returned, this method returns normally, otherwise
* a <code>SecurityException</code> is thrown. If no security
* manager is installed, this method returns normally.
*
* <p>Note that the permission grant required to satisfy this check must
* be to the client's principals alone (or a subset thereof); it cannot be
* qualified by what code is being executed. At the point in a remote call
* where this method is intended to be used, the useful "call stack" only
* exists at the other end of the remote call (on the client side), and so
* cannot meaningfully enter into the access control decision.
*
* @param permission the requested permission
* @throws SecurityException if the current client subject has not
* been granted the specified permission
* @throws IllegalStateException if the current thread is not executing
* an incoming remote method for a remote object
* @throws NullPointerException if <code>permission</code> is
* <code>null</code>
**/
public static void checkClientPermission(final Permission permission) {
if (permission == null) {
throw new NullPointerException();
}
Subject client =
(Subject) AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
try {
return Util.getClientSubject();
} catch (ServerNotActiveException e) {
throw new IllegalStateException("server not active");
}
}
});
if (System.getSecurityManager() == null) {
return;
}
ProtectionDomain pd;
if (client == null) {
pd = emptyPD;
} else {
synchronized (domains) {
WeakKey k;
while ((k = (WeakKey) queue.poll()) != null) {
domains.remove(k);
}
pd = (ProtectionDomain) domains.get(new WeakKey(client));
if (pd == null) {
Set set = client.getPrincipals();
Principal[] prins =
(Principal[]) set.toArray(new Principal[set.size()]);
pd = new ProtectionDomain(emptyCS, null, null, prins);
domains.put(new WeakKey(client, queue), pd);
}
}
}
boolean ok = pd.implies(permission);
// XXX what about logging
if (!ok) {
throw new AccessControlException("access denied " + permission);
}
}
/**
* Unmarshals a method representation from the marshal input stream,
* <code>in</code>, and returns the <code>Method</code> object
* corresponding to that representation. For each remote call, the
* <code>dispatch</code> method calls this method to unmarshal the
* method representation.
*
* <p><code>BasicInvocationDispatcher</code> implements this method to
* call the <code>readLong</code> method on the marshal input stream to
* read the method's representation encoded as a JRMP method hash
* (defined in section 8.3 of the Java(TM) Remote Method Invocation
* (Java RMI) specification) and return its
* corresponding <code>Method</code> object chosen from the collection
* of methods passed to the constructor of this invocation dispatcher.
* If more than one method has the same hash, it is arbitrary as to
* which one is returned.
*
* <p>A subclass can override this method to control how the remote
* method is unmarshalled.
*
* @param impl the remote object
* @param in the marshal input stream for the remote call
* @param context the server context passed to the {@link #dispatch
* dispatch} method for the remote call being processed
* @return a <code>Method</code> object corresponding to the method
* representation
* @throws IOException if an I/O exception occurs
* @throws NoSuchMethodException if the method representation does not
* correspond to a valid method
* @throws ClassNotFoundException if a class could not be found during
* unmarshalling
* @throws NullPointerException if any argument is <code>null</code>
**/
protected Method unmarshalMethod(Remote impl,
ObjectInputStream in,
Collection context)
throws IOException, NoSuchMethodException, ClassNotFoundException
{
if (impl == null || context == null) {
throw new NullPointerException();
}
long hash = in.readLong();
Method method = (Method) methods.get(new Long(hash));
if (method == null) {
throw new NoSuchMethodException(
"unrecognized method hash: method not supported by remote object");
}
return method;
}
/**
* Unmarshals the arguments for the specified remote <code>method</code>
* from the specified marshal input stream, <code>in</code>, and returns an
* <code>Object</code> array containing the arguments read. For each
* remote call, the <code>dispatch</code> method calls this method to
* unmarshal arguments.
*
* <p><code>BasicInvocationDispatcher</code> implements this method to
* unmarshal each argument as follows:
*
* <p>If the corresponding declared parameter type is primitive, then
* the primitive value is read from the stream using the
* corresponding <code>read</code> method for that primitive type (for
* example, if the type is <code>int.class</code>, then the primitive
* <code>int</code> value is read to the stream using the
* <code>readInt</code> method) and the value is wrapped in the
* corresponding primitive wrapper class for that type (e.g.,
* <code>Integer</code> for <code>int</code>, etc.). Otherwise, the
* argument is read from the stream using the <code>readObject</code>
* method and returned as is.
*
* <p>A subclass can override this method to unmarshal the arguments in an
* alternative context, perform post-processing on the arguments,
* unmarshal additional implicit data, or otherwise control how the
* arguments are unmarshalled. In general, the context used should mirror
* the context in which the arguments are manipulated in the
* implementation of the remote object.
*
* @param impl the remote object
* @param method the <code>Method</code> instance corresponding
* to the interface method invoked on the remote object
* @param in the incoming request stream for the remote call
* @param context the server context passed to the {@link #dispatch
* dispatch} method for the remote call being processed
* @return an <code>Object</code> array containing
* the unmarshalled arguments. If an argument's corresponding
* declared parameter type is primitive, then its value is
* represented with an instance of the corresponding primitive
* wrapper class; otherwise, the value for that argument is an
* object of a class assignable to the declared parameter type.
* @throws IOException if an I/O exception occurs
* @throws ClassNotFoundException if a class could not be found during
* unmarshalling
* @throws NullPointerException if any argument is <code>null</code>
**/
protected Object[] unmarshalArguments(Remote impl,
Method method,
ObjectInputStream in,
Collection context)
throws IOException, ClassNotFoundException
{
if (impl == null || in == null || context == null) {
throw new NullPointerException();
}
Class[] types = method.getParameterTypes();
Object[] args = new Object[types.length];
for (int i = 0; i < types.length; i++) {
args[i] = Util.unmarshalValue(types[i], in);
}
return args;
}
/**
* Invokes the specified <code>method</code> on the specified remote
* object <code>impl</code>, with the specified arguments.
* If the invocation completes normally, the return value will be
* returned by this method. If the invocation throws an exception,
* this method will throw the same exception.
*
* <p><code>BasicInvocationDispatcher</code> implements this method as
* follows:
*
* <p>If the specified method is not set accessible or is not a
* <code>public</code> method of a <code>public</code> class an
* <code>IllegalArgumentException</code> is thrown.
*
* <p>If the specified method is {@link ProxyTrust#getProxyVerifier
* ProxyTrust.getProxyVerifier} and the remote object is an instance of
* {@link ServerProxyTrust}, the {@link ServerProxyTrust#getProxyVerifier
* getProxyVerifier} method of the remote object is called and the result
* is returned.
*
* <p>Otherwise, the specified method's <code>invoke</code> method is
* called with the specified remote object and the specified arguments,
* and the result is returned. If <code>invoke</code> throws an {@link
* InvocationTargetException}, that exception is caught and the target
* exception inside it is thrown to the caller. Any other exception
* thrown during any of this computation is thrown to the caller.
*
* <p>A subclass can override this method to invoke the method in an
* alternative context, perform pre- or post-processing, or otherwise
* control how the method is invoked.
*
* @param impl the remote object
* @param method the <code>Method</code> instance corresponding
* to the interface method invoked on the remote object
* @param args the method arguments
* @param context the server context passed to the {@link #dispatch
* dispatch} method for the remote call being processed
* @return the result of the method invocation on <code>impl</code>
* @throws NullPointerException if any argument is <code>null</code>
* @throws Throwable the exception thrown from the method invocation
* on <code>impl</code>
**/
protected Object invoke(Remote impl,
Method method,
Object[] args,
Collection context)
throws Throwable
{
if (impl == null || args == null || context == null) {
throw new NullPointerException();
}
if (!method.isAccessible() &&
!(Modifier.isPublic(method.getDeclaringClass().getModifiers()) &&
Modifier.isPublic(method.getModifiers())))
{
throw new IllegalArgumentException(
"method not public or set accessible");
}
Class decl = method.getDeclaringClass();
if (decl == ProxyTrust.class &&
method.getName().equals("getProxyVerifier") &&
impl instanceof ServerProxyTrust)
{
if (args.length != 0) {
throw new IllegalArgumentException("incorrect arguments");
}
return ((ServerProxyTrust) impl).getProxyVerifier();
}
try {
return method.invoke(impl, args);
} catch (InvocationTargetException e) {
throw e.getTargetException();
}
}
/**
* Marshals the specified return value for the specified remote method
* to the marshal output stream, <code>out</code>. After invoking
* the method on the remote object <code>impl</code>, the
* <code>dispatch</code> method calls this method to marshal the value
* returned from the invocation on that remote object.
*
* <p><code>BasicInvocationDispatcher</code> implements this method as
* follows:
*
* <p>If the declared return type of the method is void, then no return
* value is written to the stream. If the return type is a primitive
* type, then the primitive value is written to the stream (for
* example, if the type is <code>int.class</code>, then the primitive
* <code>int</code> value is written to the stream using the
* <code>writeInt</code> method). Otherwise, the return value is
* written to the stream using the <code>writeObject</code> method.
*
* <p>A subclass can override this method to marshal the return value in an
* alternative context, perform pre- or post-processing on the return
* value, marshal additional implicit data, or otherwise control how the
* return value is marshalled. In general, the context used should mirror
* the context in which the result is computed in the implementation of
* the remote object.
*
* @param impl the remote object
* @param method the <code>Method</code> instance corresponding
* to the interface method invoked on the remote object
* @param returnValue the return value to marshal to the stream
* @param out the marshal output stream
* @param context the server context passed to the {@link #dispatch
* dispatch} method for the remote call being processed
* @throws IOException if an I/O exception occurs
* @throws NullPointerException if <code>impl</code>,
* <code>method</code>, <code>out</code>, or
* <code>context</code> is <code>null</code>
**/
protected void marshalReturn(Remote impl,
Method method,
Object returnValue,
ObjectOutputStream out,
Collection context)
throws IOException
{
if (impl == null || out == null || context == null) {
throw new NullPointerException();
}
Class returnType = method.getReturnType();
if (returnType != void.class) {
Util.marshalValue(returnType, returnValue, out);
}
}
/**
* Marshals the <code>throwable</code> for the specified remote method
* to the marshal output stream, <code>out</code>. For each method
* invocation on <code>impl</code> that throws an exception, this
* method is called to marshal the throwable. This method is also
* called if an exception occurs reading the method information from
* the incoming call stream, as a result of calling {@link
* #unmarshalMethod unmarshalMethod}; in this case, the
* <code>Method</code> instance will be <code>null</code>.
*
* <p><code>BasicInvocationDispatcher</code> implements this method to
* marshal the throwable to the stream using the
* <code>writeObject</code> method.
*
* <p>A subclass can override this method to marshal the throwable in an
* alternative context, perform pre- or post-processing on the throwable,
* marshal additional implicit data, or otherwise control how the throwable
* is marshalled. In general, the context used should mirror the context
* in which the exception is generated in the implementation of the
* remote object.
*
* @param impl the remote object
* @param method the possibly-<code>null</code> <code>Method</code>
* instance corresponding to the interface method invoked on
* the remote object
* @param throwable a throwable to marshal to the stream
* @param out the marshal output stream
* @param context the server context
* @throws IOException if an I/O exception occurs
* @throws NullPointerException if <code>impl</code>,
* <code>throwable</code>, <code>out</code>, or
* <code>context</code> is <code>null</code>
**/
protected void marshalThrow(Remote impl,
Method method,
Throwable throwable,
ObjectOutputStream out,
Collection context)
throws IOException
{
if (impl == null || throwable == null || context == null) {
throw new NullPointerException();
}
out.writeObject(throwable);
}
/**
* Log the start of a remote call.
*/
private void logCall(Remote impl, Method method, Object[] args) {
String msg = "inbound call {0}.{1} to {2} from {3}\nclient {4}";
if (logger.isLoggable(Level.FINEST)) {
msg = "inbound call {0}.{1} to {2} from {3}\nargs {5}\nclient {4}";
}
Subject client = getClientSubject();
Set prins = (client != null) ? client.getPrincipals() : null;
String host = null;
try {
host = Util.getClientHostString();
} catch (ServerNotActiveException e) {
}
logger.log(Level.FINE, msg,
new Object[]{method.getDeclaringClass().getName(),
method.getName(), impl, host,
prins, Arrays.asList(args)});
}
/**
* Log the return of an inbound call.
*/
private void logReturn(Remote impl, Method method, Object res) {
String msg = "inbound call {0}.{1} to {2} returns";
if (logger.isLoggable(Level.FINEST) &&
method.getReturnType() != void.class)
{
msg = "inbound call {0}.{1} to {2} returns {3}";
}
logger.logp(Level.FINE, this.getClass().getName(), "dispatch", msg,
new Object[]{method.getDeclaringClass().getName(),
method.getName(), impl, res});
}
/**
* Log the remote throw of an inbound call.
*/
private void logThrow(Remote impl,
Method method,
Throwable t,
boolean fromImpl)
{
LogRecord lr = new LogRecord(
Levels.FAILED,
fromImpl ?
"inbound call {0}.{1} to {2} remotely throws" :
(logger.isLoggable(Level.FINEST) ?
"inbound call {0}.{1} to {2} dispatch remotely throws\nclient {3}" :
"inbound call {0}.{1} to {2} dispatch remotely throws"));
lr.setLoggerName(logger.getName());
lr.setSourceClassName(this.getClass().getName());
lr.setSourceMethodName("dispatch");
lr.setParameters(new Object[]{(method == null ?
"<unknown>" :
method.getDeclaringClass().getName()),
(method == null ?
"<unknown>" : method.getName()),
impl, getClientSubject()});
lr.setThrown(t);
logger.log(lr);
}
/**
* Log the local throw of an inbound call.
*/
private void logThrow(Remote impl, Throwable t) {
LogRecord lr = new LogRecord(Levels.FAILED,
logger.isLoggable(Level.FINEST) ?
"{0} locally throws\nclient {1}" :
"{0} locally throws");
lr.setLoggerName(logger.getName());
lr.setSourceClassName(this.getClass().getName());
lr.setSourceMethodName("dispatch");
lr.setParameters(new Object[]{impl, getClientSubject()});
lr.setThrown(t);
logger.log(lr);
}
/**
* Return the current client subject or <code>null</code> if not
* currently executing a remote call.
*/
private static Subject getClientSubject() {
return (Subject) AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
try {
return Util.getClientSubject();
} catch (ServerNotActiveException e) {
return null;
}
}
});
}
}