InvocationProxy: View a *Provider instance as a *Service.
This is used on multi-noded servers so that I can have a service interface for talking to the node in charge of a particular resource, and use the same code path even if that node is the current node. Perhaps this be more smoothly integrated in the future but for now it's a static utility you need to manually set up.
This commit is contained in:
@@ -0,0 +1,167 @@
|
|||||||
|
package com.threerings.presents.server;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationHandler;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Proxy;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import com.samskivert.util.MethodFinder;
|
||||||
|
|
||||||
|
import com.threerings.io.ObjectOutputStream;
|
||||||
|
|
||||||
|
import com.threerings.presents.client.InvocationService;
|
||||||
|
import com.threerings.presents.data.InvocationCodes;
|
||||||
|
import com.threerings.presents.server.InvocationException;
|
||||||
|
import com.threerings.presents.server.InvocationProvider;
|
||||||
|
|
||||||
|
import static com.threerings.presents.Log.log;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility methods for getting an InvocationService proxy for a InvocationProvider.
|
||||||
|
* This lets you view the methods on your FooProvider on the server through a
|
||||||
|
* FooService interface. This is used by multi-noded servers that have peer services
|
||||||
|
* to each other when they want to have one code path for making requests whether it's
|
||||||
|
* handeled locally or remotely.
|
||||||
|
*/
|
||||||
|
public class InvocationProxy
|
||||||
|
{
|
||||||
|
/** If true, create proxies in development mode: validate all arguments are streamable. */
|
||||||
|
public static boolean validate =
|
||||||
|
Boolean.getBoolean("com.threerings.presents.server.InvocationProxy.validate");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an InvocationService method for calling services directly on the provided provider
|
||||||
|
* implementation.
|
||||||
|
* Note: No checking is done to make sure that the specified provider is in any way related
|
||||||
|
* to the supplied service provider. Don't screw up!
|
||||||
|
*/
|
||||||
|
public static <S extends InvocationService<?>> S getProxy (
|
||||||
|
Class<S> serviceClass, final InvocationProvider provider)
|
||||||
|
{
|
||||||
|
return getProxy(serviceClass,
|
||||||
|
new ServiceHandler(serviceClass) {
|
||||||
|
protected Object invokeMethod (String name, Object[] args)
|
||||||
|
throws Throwable
|
||||||
|
{
|
||||||
|
// make a new arg array including the ClientObject (null)
|
||||||
|
Object[] pArgs = new Object[args.length + 1];
|
||||||
|
System.arraycopy(args, 0, pArgs, 1, args.length);
|
||||||
|
Method pMethod = methodFinder.findMethod(name, pArgs);
|
||||||
|
return pMethod.invoke(provider, pArgs);
|
||||||
|
}
|
||||||
|
final MethodFinder methodFinder = new MethodFinder(provider.getClass());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an InvocationService that fails on every request with the specified message.
|
||||||
|
*/
|
||||||
|
public static <S extends InvocationService<?>> S getFailureProxy (
|
||||||
|
Class<S> serviceClass, final String failureMessage)
|
||||||
|
{
|
||||||
|
return getProxy(serviceClass,
|
||||||
|
new ServiceHandler(serviceClass) {
|
||||||
|
protected Object invokeMethod (String name, Object[] args)
|
||||||
|
throws Throwable
|
||||||
|
{
|
||||||
|
throw new InvocationException(failureMessage);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convenience method to construct and cast a proxy.
|
||||||
|
*/
|
||||||
|
protected static <S> S getProxy (Class<S> serviceClass, InvocationHandler handler)
|
||||||
|
{
|
||||||
|
if (validate) {
|
||||||
|
final InvocationHandler base = handler;
|
||||||
|
handler = (proxy, method, args) -> {
|
||||||
|
for (Object o : args) {
|
||||||
|
if (!(o instanceof InvocationService.InvocationListener)) {
|
||||||
|
ensureStreamable(o);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return base.invoke(proxy, method, args);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return serviceClass.cast(Proxy.newProxyInstance(serviceClass.getClassLoader(),
|
||||||
|
new Class<?>[] { serviceClass }, handler));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ensure that the args are all streamable.
|
||||||
|
*/
|
||||||
|
protected static void ensureStreamable (Object anything)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
new ObjectOutputStream(new ByteArrayOutputStream()).writeObject(anything);
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A helper class that takes care of finding the InvocationListeners and properly dealing
|
||||||
|
* with exceptions.
|
||||||
|
*/
|
||||||
|
protected static abstract class ServiceHandler
|
||||||
|
implements InvocationHandler
|
||||||
|
{
|
||||||
|
public ServiceHandler (Class<? extends InvocationService<?>> clazz)
|
||||||
|
{
|
||||||
|
_serviceClass = clazz;
|
||||||
|
}
|
||||||
|
|
||||||
|
// from InvocationHandler
|
||||||
|
public final Object invoke (Object proxy, Method method, Object[] args)
|
||||||
|
{
|
||||||
|
// first locate the "primary listener"
|
||||||
|
InvocationService.InvocationListener listener = null;
|
||||||
|
for (Object o : args) {
|
||||||
|
if (o instanceof InvocationService.InvocationListener) {
|
||||||
|
listener = (InvocationService.InvocationListener)o;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
return invokeMethod(method.getName(), args);
|
||||||
|
|
||||||
|
} catch (Throwable t) {
|
||||||
|
if (listener == null) {
|
||||||
|
log.warning("No listener for uncaught exception in InvocationProxy",
|
||||||
|
"serviceClass", _serviceClass, "method", method.getName(), t);
|
||||||
|
// nothing to report back to!
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// see if we can find an InvocationException in the chain
|
||||||
|
for (Throwable cause = t; cause != null; cause = cause.getCause()) {
|
||||||
|
if (cause instanceof InvocationException) {
|
||||||
|
listener.requestFailed(cause.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// unknown error
|
||||||
|
log.warning("Throwable in InvocationProxy",
|
||||||
|
"serviceClass", _serviceClass, "method", method.getName(), t);
|
||||||
|
listener.requestFailed(InvocationCodes.INTERNAL_ERROR);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implement to invoke the service method.
|
||||||
|
*/
|
||||||
|
protected abstract Object invokeMethod (String name, Object[] args) throws Throwable;
|
||||||
|
|
||||||
|
/** The service class. */
|
||||||
|
protected final Class<? extends InvocationService<?>> _serviceClass;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user