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;
+  }
 }


Reply via email to