Author: evan
Date: Fri Feb 20 00:54:35 2009
New Revision: 746079
URL: http://svn.apache.org/viewvc?rev=746079&view=rev
Log:
This CL allows handlers to accept POJO input. So instead of
List<Object> getResponse(RequestItem request)
you can use
List<Object> getResponse(MyInputType request)
This also supports no-arg requests, so
List<Object> getSupportedFields(RequestItem request)
becomes
List<Object> getSupportedFields()
Modified:
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/DefaultHandlerRegistry.java
incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/protocol/DefaultHandlerRegistryTest.java
incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/protocol/TestHandler.java
Modified:
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/DefaultHandlerRegistry.java
URL:
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/DefaultHandlerRegistry.java?rev=746079&r1=746078&r2=746079&view=diff
==============================================================================
---
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/DefaultHandlerRegistry.java
(original)
+++
incubator/shindig/trunk/java/common/src/main/java/org/apache/shindig/protocol/DefaultHandlerRegistry.java
Fri Feb 20 00:54:35 2009
@@ -31,6 +31,7 @@
import com.google.inject.Inject;
import com.google.inject.Injector;
import com.google.inject.Provider;
+import com.google.inject.Singleton;
import com.google.inject.name.Named;
import org.json.JSONException;
@@ -53,6 +54,7 @@
* Default implementation of HandlerRegistry. Bind to appropriately
* annotated handlers.
*/
+...@singleton
public class DefaultHandlerRegistry implements HandlerRegistry {
private static final Logger logger =
Logger.getLogger(DefaultHandlerRegistry.class.getName());
@@ -163,7 +165,7 @@
throw new IllegalStateException("Attempt to bind unannotated service
implementation " +
handlerType.getName());
}
- Service service = (Service) handlerType.getAnnotation(Service.class);
+ Service service = handlerType.getAnnotation(Service.class);
for (Method m : handlerType.getMethods()) {
if (m.isAnnotationPresent(Operation.class)) {
@@ -177,46 +179,37 @@
private void createRestHandler(Provider<?> handlerProvider,
Service service, Operation op, Method m) {
- // Check for request item subclass constructor
- Class<?> requestItemType = m.getParameterTypes()[0];
try {
- if (RequestItem.class.isAssignableFrom(requestItemType)) {
- if (requestItemType == RequestItem.class) {
- requestItemType = BaseRequestItem.class;
- }
- Constructor<?> reqItemConstructor =
- requestItemType.getConstructor(Map.class, SecurityToken.class,
BeanConverter.class,
- BeanJsonConverter.class);
- String opName = m.getName();
- if (!StringUtils.isEmpty(op.name())) {
- opName = op.name();
- }
- RestInvocationHandler restHandler = new RestInvocationHandler(service,
op, m,
- handlerProvider, beanJsonConverter, reqItemConstructor,
- new ExecutionListenerWrapper(service.name(), opName,
executionListener));
- String serviceName = service.name();
-
- Map<String, SortedSet<RestPath>> methods =
serviceMethodPathMap.get(serviceName);
- if (methods == null) {
- methods = Maps.newHashMap();
- serviceMethodPathMap.put(serviceName, methods);
- }
-
- for (String httpMethod : op.httpMethods()) {
- if (!StringUtils.isEmpty(httpMethod)) {
- httpMethod = httpMethod.toUpperCase();
- SortedSet<RestPath> sortedSet = methods.get(httpMethod);
- if (sortedSet == null) {
- sortedSet = Sets.newTreeSet();
- methods.put(httpMethod, sortedSet);
- }
-
- if (StringUtils.isEmpty(op.path())) {
- sortedSet.add(new RestPath("/" + serviceName + service.path(),
restHandler));
- } else {
- // Use the standard service name and constant prefix as the key
- sortedSet.add(new RestPath("/" + serviceName + op.path(),
restHandler));
- }
+ MethodCaller methodCaller = new MethodCaller(m, true);
+ String opName = m.getName();
+ if (!StringUtils.isEmpty(op.name())) {
+ opName = op.name();
+ }
+ RestInvocationHandler restHandler = new RestInvocationHandler(op,
methodCaller,
+ handlerProvider, beanJsonConverter,
+ new ExecutionListenerWrapper(service.name(), opName,
executionListener));
+ String serviceName = service.name();
+
+ Map<String, SortedSet<RestPath>> methods =
serviceMethodPathMap.get(serviceName);
+ if (methods == null) {
+ methods = Maps.newHashMap();
+ serviceMethodPathMap.put(serviceName, methods);
+ }
+
+ for (String httpMethod : op.httpMethods()) {
+ if (!StringUtils.isEmpty(httpMethod)) {
+ httpMethod = httpMethod.toUpperCase();
+ SortedSet<RestPath> sortedSet = methods.get(httpMethod);
+ if (sortedSet == null) {
+ sortedSet = Sets.newTreeSet();
+ methods.put(httpMethod, sortedSet);
+ }
+
+ if (StringUtils.isEmpty(op.path())) {
+ sortedSet.add(new RestPath("/" + serviceName + service.path(),
restHandler));
+ } else {
+ // Use the standard service name and constant prefix as the key
+ sortedSet.add(new RestPath("/" + serviceName + op.path(),
restHandler));
}
}
}
@@ -226,29 +219,20 @@
}
- private void createRpcHandler(Provider<?> handlerProvider,
- Service service, Operation op, Method m) {
- // Check for request item subclass constructor
- Class<?> requestItemType = m.getParameterTypes()[0];
+ private void createRpcHandler(Provider<?> handlerProvider, Service service,
+ Operation op, Method m) {
try {
- if (RequestItem.class.isAssignableFrom(requestItemType)) {
- if (requestItemType == RequestItem.class) {
- requestItemType = BaseRequestItem.class;
- }
- Constructor<?> reqItemConstructor =
- requestItemType.getConstructor(JSONObject.class,
SecurityToken.class,
- BeanConverter.class,
- BeanJsonConverter.class);
- String opName = m.getName();
- // Use the override if its defined
- if (op.name().length() > 0) {
- opName = op.name();
- }
- RpcInvocationHandler rpcHandler =
- new RpcInvocationHandler(m, handlerProvider, beanJsonConverter,
reqItemConstructor,
- new ExecutionListenerWrapper(service.name(), opName,
executionListener));
- rpcOperations.put(service.name() + "." + opName, rpcHandler);
- }
+ MethodCaller methodCaller = new MethodCaller(m, false);
+
+ String opName = m.getName();
+ // Use the override if its defined
+ if (op.name().length() > 0) {
+ opName = op.name();
+ }
+ RpcInvocationHandler rpcHandler =
+ new RpcInvocationHandler(methodCaller, handlerProvider,
beanJsonConverter,
+ new ExecutionListenerWrapper(service.name(), opName,
executionListener));
+ rpcOperations.put(service.name() + "." + opName, rpcHandler);
} catch (NoSuchMethodException nme) {
logger.log(Level.INFO, "No RPC binding for " + service.name() + "." +
m.getName());
}
@@ -274,48 +258,35 @@
}
}
+
/**
* Proxy binding for an RPC operation. We allow binding to methods that
* return non-Future types by wrapping them in ImmediateFuture.
*/
static final class RpcInvocationHandler {
- private Method receiver;
- Provider<?> handlerProvider;
- BeanJsonConverter beanJsonConverter;
- Constructor<?> requestItemConstructor;
- ExecutionListenerWrapper listener;
- private RpcInvocationHandler(Method receiver,
+ private Provider<?> handlerProvider;
+ private BeanJsonConverter beanJsonConverter;
+ private ExecutionListenerWrapper listener;
+ private MethodCaller methodCaller;
+
+ private RpcInvocationHandler(MethodCaller methodCaller,
Provider<?> handlerProvider,
BeanJsonConverter beanJsonConverter,
- Constructor<?> reqItemConstructor,
ExecutionListenerWrapper listener) {
- this.receiver = receiver;
this.handlerProvider = handlerProvider;
this.beanJsonConverter = beanJsonConverter;
- this.requestItemConstructor = reqItemConstructor;
this.listener = listener;
+ this.methodCaller = methodCaller;
}
public Future<?> execute(JSONObject rpc, SecurityToken token,
BeanConverter converter) {
try {
- RequestItem item;
- if (rpc.has("params")) {
- item = (RequestItem) requestItemConstructor.newInstance(
- (JSONObject) rpc.get("params"), token, converter,
beanJsonConverter);
- } else {
- item = (RequestItem) requestItemConstructor.newInstance(new
JSONObject(), token,
- converter, beanJsonConverter);
- }
+ JSONObject params = rpc.has("params") ? (JSONObject)rpc.get("params")
: new JSONObject();
+ RequestItem item = methodCaller.getRpcRequestItem(params, token,
beanJsonConverter);
+
listener.executing(item);
- Object result = receiver.invoke(handlerProvider.get(), item);
- if (result instanceof Future) {
- return (Future<?>) result;
- }
- return ImmediateFuture.newInstance(result);
- } catch (InvocationTargetException ite) {
- // Unwrap these
- return ImmediateFuture.errorInstance(ite.getTargetException());
+ return methodCaller.call(handlerProvider.get(), item);
} catch (Exception e) {
return ImmediateFuture.errorInstance(e);
}
@@ -345,30 +316,22 @@
* return non-Future types by wrapping them in ImmediateFuture.
*/
static final class RestInvocationHandler {
- final Method receiver;
final Provider<?> handlerProvider;
- final Service service;
final Operation operation;
- final String[] expectedUrl;
final BeanJsonConverter beanJsonConverter;
- final Constructor<?> requestItemConstructor;
final ExecutionListenerWrapper listener;
+ final MethodCaller methodCaller;
- private RestInvocationHandler(Service service,
- Operation operation,
- Method receiver,
+ private RestInvocationHandler(Operation operation,
+ MethodCaller methodCaller,
Provider<?> handlerProvider,
BeanJsonConverter beanJsonConverter,
- Constructor<?> requestItemConstructor,
ExecutionListenerWrapper listener) {
- this.service = service;
this.operation = operation;
- this.receiver = receiver;
this.handlerProvider = handlerProvider;
- expectedUrl = service.path().split("/");
this.beanJsonConverter = beanJsonConverter;
- this.requestItemConstructor = requestItemConstructor;
this.listener = listener;
+ this.methodCaller = methodCaller;
}
public Future<?> execute(Map<String, String[]> parameters, Reader body,
@@ -378,17 +341,11 @@
if (body != null) {
parameters.put(operation.bodyParam(), new
String[]{IOUtils.toString(body)});
}
- RequestItem item = (RequestItem)
requestItemConstructor.newInstance(parameters, token,
- converter, beanJsonConverter);
+ RequestItem item = methodCaller.getRestRequestItem(parameters, token,
converter,
+ beanJsonConverter);
listener.executing(item);
- Object result = receiver.invoke(handlerProvider.get(), item);
- if (result instanceof Future) {
- return (Future<?>) result;
- }
- return ImmediateFuture.newInstance(result);
- } catch (InvocationTargetException ite) {
- // Unwrap these
- return ImmediateFuture.errorInstance(ite.getTargetException());
+
+ return methodCaller.call(handlerProvider.get(), item);
} catch (Exception e) {
return ImmediateFuture.errorInstance(e);
}
@@ -414,7 +371,95 @@
return handler.execute(pathParams, body, token, converter);
}
}
+
+ /**
+ * Calls methods annotated with {...@link Operation} and appropriately
translates
+ * RequestItem to the actual input class of the method.
+ */
+ private static class MethodCaller {
+ /** Type of object to create for this method, or null if takes no args */
+ private Class<?> inputClass;
+
+ /** Constructors for request item class that will be used */
+ private final Constructor<?> restRequestItemConstructor;
+ private final Constructor<?> rpcRequestItemConstructor;
+
+ /** The method */
+ private final Method method;
+
+ /**
+ * Create information needed to call a method
+ * @param method The method
+ * @param isRest True if REST method (affects which RequestItem
constructor to return)
+ * @throws NoSuchMethodException
+ */
+ public MethodCaller(Method method, boolean isRest) throws
NoSuchMethodException {
+ this.method = method;
+ inputClass = method.getParameterTypes().length > 0 ?
method.getParameterTypes()[0] : null;
+
+ // Methods that need RequestItem interface should automatically get a
BaseRequestItem
+ if (RequestItem.class.equals(inputClass)) {
+ inputClass = BaseRequestItem.class;
+ }
+ boolean inputIsRequestItem = (inputClass != null) &&
+ RequestItem.class.isAssignableFrom(inputClass);
+
+ Class<?> requestItemType = inputIsRequestItem ? inputClass :
BaseRequestItem.class;
+
+ Class<?> requestItemFirstParamClass = isRest ? Map.class :
JSONObject.class;
+ restRequestItemConstructor = requestItemType.getConstructor(Map.class,
+ SecurityToken.class, BeanConverter.class, BeanJsonConverter.class);
+ rpcRequestItemConstructor =
requestItemType.getConstructor(JSONObject.class,
+ SecurityToken.class, BeanConverter.class, BeanJsonConverter.class);
+ }
+
+ public RequestItem getRestRequestItem(Map<String, String[]> params,
SecurityToken token,
+ BeanConverter converter, BeanJsonConverter jsonConverter) {
+ return getRequestItem(params, token, converter, jsonConverter,
restRequestItemConstructor);
+ }
+
+ public RequestItem getRpcRequestItem(JSONObject params, SecurityToken
token, BeanJsonConverter converter) {
+ return getRequestItem(params, token, converter, converter,
rpcRequestItemConstructor);
+ }
+
+ private RequestItem getRequestItem(Object params, SecurityToken token,
BeanConverter converter,
+ BeanJsonConverter jsonConverter, Constructor<?> constructor) {
+ try {
+ return (RequestItem) constructor.newInstance(params, token, converter,
+ jsonConverter);
+ } catch (InstantiationException e) {
+ throw new RuntimeException(e);
+ } catch (IllegalAccessException e) {
+ throw new RuntimeException(e);
+ } catch (InvocationTargetException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public Future<?> call(Object handler, RequestItem item) {
+ try {
+ Object result;
+ if (inputClass == null) {
+ result = method.invoke(handler);
+ } else if (RequestItem.class.isAssignableFrom(inputClass)) {
+ result = method.invoke(handler, item);
+ } else {
+ result = method.invoke(handler, item.getTypedRequest(inputClass));
+ }
+
+ if (result instanceof Future) {
+ return (Future<?>) result;
+ }
+ return ImmediateFuture.newInstance(result);
+ } catch (IllegalAccessException e) {
+ return ImmediateFuture.errorInstance(e);
+ } catch (InvocationTargetException e) {
+ // Unwrap the internal exception
+ return ImmediateFuture.errorInstance(e.getTargetException());
+ }
+ }
+ }
/**
* Standard REST handler to wrap errors
Modified:
incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/protocol/DefaultHandlerRegistryTest.java
URL:
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/protocol/DefaultHandlerRegistryTest.java?rev=746079&r1=746078&r2=746079&view=diff
==============================================================================
---
incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/protocol/DefaultHandlerRegistryTest.java
(original)
+++
incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/protocol/DefaultHandlerRegistryTest.java
Fri Feb 20 00:54:35 2009
@@ -19,12 +19,15 @@
package org.apache.shindig.protocol;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
+import com.google.inject.Guice;
import junit.framework.Assert;
import junit.framework.TestCase;
+import org.apache.shindig.protocol.conversion.BeanJsonConverter;
import org.json.JSONObject;
import static org.junit.Assert.assertArrayEquals;
@@ -39,13 +42,15 @@
public class DefaultHandlerRegistryTest extends TestCase {
private DefaultHandlerRegistry registry;
+ private BeanJsonConverter converter;
@Override
@SuppressWarnings("unchecked")
protected void setUp() throws Exception {
super.setUp();
+ converter = new BeanJsonConverter(Guice.createInjector());
registry = new DefaultHandlerRegistry(null,
- Sets.<Object>newHashSet(new TestHandler()), null,
+ Sets.<Object>newHashSet(new TestHandler()), converter,
new HandlerExecutionListener.NoOpHandlerExecutionListener());
}
@@ -116,6 +121,27 @@
assertEquals(future.get(), TestHandler.CREATE_RESPONSE);
}
+ public void testRpcWithInputClassThatIsntRequestItem() throws Exception {
+ JSONObject rpc = new JSONObject("{ method : test.echo, params: {value:
'Bob' }}");
+ RpcHandler handler = registry.getRpcHandler(rpc);
+ Future<?> future = handler.execute(null, converter);
+ assertEquals(future.get(), TestHandler.ECHO_PREFIX + "Bob");
+ }
+
+ public void testRestWithInputClassThatIsntRequestItem() throws Exception {
+ RestHandler handler = registry.getRestHandler("/test/echo", "GET");
+ String[] value = {"Bob"};
+ Future<?> future = handler.execute(ImmutableMap.of("value", value), null,
null, converter);
+ assertEquals(future.get(), TestHandler.ECHO_PREFIX + "Bob");
+ }
+
+ public void testNoArgumentClass() throws Exception {
+ JSONObject rpc = new JSONObject("{ method : test.noArg }");
+ RpcHandler handler = registry.getRpcHandler(rpc);
+ Future<?> future = handler.execute(null, converter);
+ assertEquals(future.get(), TestHandler.NO_ARG_RESPONSE);
+ }
+
public void testNonFutureException() throws Exception {
// Test calling a handler method which does not return a future
JSONObject rpc = new JSONObject("{ method : test.exception }");
@@ -145,7 +171,7 @@
public void testSupportedRpcServices() throws Exception {
assertEquals(registry.getSupportedRpcServices(),
Sets.newHashSet("test.create", "test.get", "test.overridden",
"test.exception",
- "test.futureException", "test.override.rpcname"));
+ "test.futureException", "test.override.rpcname", "test.echo",
"test.noArg"));
}
public void testSupportedRestServices() throws Exception {
@@ -154,7 +180,8 @@
"PUT /test/{someParam}/{someOtherParam}",
"DELETE /test/{someParam}/{someOtherParam}",
"POST /test/{someParam}/{someOtherParam}",
- "GET /test/overridden/method"));
+ "GET /test/overridden/method",
+ "GET /test/echo"));
}
public void testAddNonService() {
Modified:
incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/protocol/TestHandler.java
URL:
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/protocol/TestHandler.java?rev=746079&r1=746078&r2=746079&view=diff
==============================================================================
---
incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/protocol/TestHandler.java
(original)
+++
incubator/shindig/trunk/java/common/src/test/java/org/apache/shindig/protocol/TestHandler.java
Fri Feb 20 00:54:35 2009
@@ -38,6 +38,8 @@
public static final String GET_RESPONSE = "GET_RESPONSE";
public static final String CREATE_RESPONSE = "CREATE_RESPONSE";
public static final String FAILURE_MESSAGE = "FAILURE_MESSAGE";
+ public static final String ECHO_PREFIX = "ECHO: ";
+ public static final String NO_ARG_RESPONSE = "No arguments from me!";
public static Map<String,String> REST_RESULTS = ImmutableMap.of(
"POST", CREATE_RESPONSE, "GET", GET_RESPONSE, "DELETE", FAILURE_MESSAGE);
@@ -100,4 +102,20 @@
throw new NullPointerException(FAILURE_MESSAGE);
}
}
+
+ @Operation(httpMethods = "GET", path = "/echo")
+ public String echo(Input input) {
+ return ECHO_PREFIX + input.value;
+ }
+
+ @Ignore
+ public static class Input {
+ public String value;
+ public void setValue(String value) { this.value = value; }
+ }
+
+ @Operation(httpMethods = "")
+ public String noArg() {
+ return NO_ARG_RESPONSE;
+ }
}