You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@shindig.apache.org by lr...@apache.org on 2009/02/02 19:39:16 UTC

svn commit: r740067 [1/2] - in /incubator/shindig/trunk/java: server/src/test/java/org/apache/shindig/server/endtoend/ social-api/src/main/java/org/apache/shindig/social/core/config/ social-api/src/main/java/org/apache/shindig/social/opensocial/service...

Author: lryan
Date: Mon Feb  2 18:39:12 2009
New Revision: 740067

URL: http://svn.apache.org/viewvc?rev=740067&view=rev
Log:
Annotation based handler binding and dispatch

Added:
    incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/BaseRequestItem.java
    incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/DefaultHandlerRegistry.java   (contents, props changed)
      - copied, changed from r738302, incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/StandardHandlerDispatcher.java
    incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/HandlerPreconditions.java
    incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/HandlerRegistry.java   (contents, props changed)
      - copied, changed from r738302, incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/HandlerDispatcher.java
    incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/Operation.java
    incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/RestHandler.java
    incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/RpcHandler.java
    incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/Service.java
    incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/SystemHandler.java
    incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/opensocial/service/BaseRequestItemTest.java
    incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/opensocial/service/DefaultHandlerRegistryTest.java
    incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/opensocial/service/TestHandler.java
Removed:
    incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/DataRequestHandler.java
    incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/HandlerDispatcher.java
    incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/RestfulRequestItem.java
    incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/RpcRequestItem.java
    incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/StandardHandlerDispatcher.java
    incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/opensocial/service/RestfulRequestItemTest.java
    incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/opensocial/service/RpcRequestItemTest.java
    incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/opensocial/service/StandardHandlerDispatcherTest.java
    incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/opensocial/spi/DataRequestHandlerTest.java
Modified:
    incubator/shindig/trunk/java/server/src/test/java/org/apache/shindig/server/endtoend/EndToEndModule.java
    incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/core/config/SocialApiGuiceModule.java
    incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/ActivityHandler.java
    incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/ApiServlet.java
    incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/AppDataHandler.java
    incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/DataServiceServlet.java
    incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/JsonRpcServlet.java
    incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/PersonHandler.java
    incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/RequestItem.java
    incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/sample/service/SampleContainerHandler.java
    incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/SocialApiTestsGuiceModule.java
    incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/dataservice/integration/AbstractLargeRestfulTests.java
    incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/opensocial/service/ActivityHandlerTest.java
    incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/opensocial/service/AppDataHandlerTest.java
    incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/opensocial/service/DataServiceServletTest.java
    incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/opensocial/service/JsonRpcServletTest.java
    incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/opensocial/service/PersonHandlerTest.java

Modified: incubator/shindig/trunk/java/server/src/test/java/org/apache/shindig/server/endtoend/EndToEndModule.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/server/src/test/java/org/apache/shindig/server/endtoend/EndToEndModule.java?rev=740067&r1=740066&r2=740067&view=diff
==============================================================================
--- incubator/shindig/trunk/java/server/src/test/java/org/apache/shindig/server/endtoend/EndToEndModule.java (original)
+++ incubator/shindig/trunk/java/server/src/test/java/org/apache/shindig/server/endtoend/EndToEndModule.java Mon Feb  2 18:39:12 2009
@@ -20,13 +20,19 @@
 import org.apache.shindig.auth.AnonymousAuthenticationHandler;
 import org.apache.shindig.auth.AuthenticationHandler;
 import org.apache.shindig.common.servlet.ParameterFetcher;
+import org.apache.shindig.config.ContainerConfig;
+import org.apache.shindig.config.JsonContainerConfig;
 import org.apache.shindig.social.core.oauth.AuthenticationHandlerProvider;
 import org.apache.shindig.social.core.util.BeanJsonConverter;
 import org.apache.shindig.social.core.util.BeanXStreamAtomConverter;
 import org.apache.shindig.social.core.util.BeanXStreamConverter;
+import org.apache.shindig.social.opensocial.service.ActivityHandler;
+import org.apache.shindig.social.opensocial.service.AppDataHandler;
 import org.apache.shindig.social.opensocial.service.BeanConverter;
 import org.apache.shindig.social.opensocial.service.DataServiceServletFetcher;
+import org.apache.shindig.social.opensocial.service.PersonHandler;
 
+import com.google.common.collect.Lists;
 import com.google.inject.AbstractModule;
 import com.google.inject.TypeLiteral;
 import com.google.inject.name.Names;
@@ -58,5 +64,11 @@
 
     bind(new TypeLiteral<List<AuthenticationHandler>>(){}).toProvider(
         AuthenticationHandlerProvider.class);
+
+    bind(List.class).annotatedWith(Names.named("org.apache.shindig.handlers"))
+        .toInstance(Lists.immutableList(ActivityHandler.class, AppDataHandler.class,
+            PersonHandler.class));
+
+    bind(ContainerConfig.class).to(JsonContainerConfig.class);    
   }
 }

Modified: incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/core/config/SocialApiGuiceModule.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/core/config/SocialApiGuiceModule.java?rev=740067&r1=740066&r2=740067&view=diff
==============================================================================
--- incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/core/config/SocialApiGuiceModule.java (original)
+++ incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/core/config/SocialApiGuiceModule.java Mon Feb  2 18:39:12 2009
@@ -25,15 +25,17 @@
 import org.apache.shindig.social.core.util.BeanJsonConverter;
 import org.apache.shindig.social.core.util.BeanXStreamAtomConverter;
 import org.apache.shindig.social.core.util.BeanXStreamConverter;
+import org.apache.shindig.social.opensocial.service.ActivityHandler;
+import org.apache.shindig.social.opensocial.service.AppDataHandler;
 import org.apache.shindig.social.opensocial.service.BeanConverter;
 import org.apache.shindig.social.opensocial.service.DataServiceServletFetcher;
-import org.apache.shindig.social.opensocial.service.StandardHandlerDispatcher;
-import org.apache.shindig.social.opensocial.service.HandlerDispatcher;
+import org.apache.shindig.social.opensocial.service.DefaultHandlerRegistry;
+import org.apache.shindig.social.opensocial.service.HandlerRegistry;
+import org.apache.shindig.social.opensocial.service.PersonHandler;
 import org.apache.shindig.social.sample.service.SampleContainerHandler;
 
+import com.google.common.collect.Lists;
 import com.google.inject.AbstractModule;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
 import com.google.inject.TypeLiteral;
 import com.google.inject.name.Names;
 
@@ -50,7 +52,7 @@
   /** {@inheritDoc} */
   @Override
   protected void configure() {
-	bind(HandlerDispatcher.class).toProvider(HandlerDispatcherProvider.class);
+    bind(HandlerRegistry.class).to(DefaultHandlerRegistry.class);
 
     bind(ParameterFetcher.class).annotatedWith(Names.named("DataServiceServlet"))
         .to(DataServiceServletFetcher.class);
@@ -71,24 +73,9 @@
 
     bind(new TypeLiteral<List<AuthenticationHandler>>(){}).toProvider(
         AuthenticationHandlerProvider.class);
-  }
-
-  /**
-   * Provider for the HandlerDispatcher.  Adds the sample container handler
-   * at "samplecontainer".
-   */
-  static class HandlerDispatcherProvider implements Provider<HandlerDispatcher> {
-    private final HandlerDispatcher dispatcher;
-
-    @Inject
-    public HandlerDispatcherProvider(StandardHandlerDispatcher dispatcher,
-        Provider<SampleContainerHandler> sampleHandler) {
-      dispatcher.addHandler("samplecontainer", sampleHandler);
-      this.dispatcher = dispatcher;
-    }
-
-    public HandlerDispatcher get() {
-      return dispatcher;
-    }
+  
+    bind(List.class).annotatedWith(Names.named("org.apache.shindig.handlers"))
+        .toInstance(Lists.immutableList(ActivityHandler.class, AppDataHandler.class,
+            PersonHandler.class, SampleContainerHandler.class));
   }
 }

Modified: incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/ActivityHandler.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/ActivityHandler.java?rev=740067&r1=740066&r2=740067&view=diff
==============================================================================
--- incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/ActivityHandler.java (original)
+++ incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/ActivityHandler.java Mon Feb  2 18:39:12 2009
@@ -17,30 +17,35 @@
  */
 package org.apache.shindig.social.opensocial.service;
 
+import org.apache.shindig.config.ContainerConfig;
 import org.apache.shindig.social.opensocial.model.Activity;
 import org.apache.shindig.social.opensocial.spi.ActivityService;
+import org.apache.shindig.social.opensocial.spi.CollectionOptions;
 import org.apache.shindig.social.opensocial.spi.SocialSpiException;
 import org.apache.shindig.social.opensocial.spi.UserId;
-import org.apache.shindig.social.opensocial.spi.CollectionOptions;
 
-import com.google.common.collect.Sets;
-import com.google.common.collect.Iterables;
+import com.google.common.base.Objects;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
 import com.google.inject.Inject;
 
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.Future;
+import java.util.logging.Logger;
 
-public class ActivityHandler extends DataRequestHandler {
-  private final ActivityService service;
+@Service(name = "activities", path="/{userId}+/{groupId}/{appId}/{activityId}+")
+public class ActivityHandler  {
 
-  private static final String ACTIVITY_ID_PATH
-      = "/activities/{userId}+/{groupId}/{appId}/{activityId}+";
+  private static final Logger logger = Logger.getLogger(ActivityHandler.class.getName());
+
+  private final ActivityService service;
+  private final ContainerConfig config;
 
   @Inject
-  public ActivityHandler(ActivityService service) {
+  public ActivityHandler(ActivityService service, ContainerConfig config) {
     this.service = service;
+    this.config = config;
   }
 
   /**
@@ -48,16 +53,15 @@
    *
    * examples: /activities/john.doe/@self/1
    */
-  @Override
-  protected Future<?> handleDelete(RequestItem request)
+  @Operation(httpMethods="DELETE")
+  public Future<?> delete(RequestItem request)
       throws SocialSpiException {
-    request.applyUrlTemplate(ACTIVITY_ID_PATH);
 
     Set<UserId> userIds = request.getUsers();
     Set<String> activityIds = ImmutableSet.copyOf(request.getListParameter("activityId"));
 
-    Preconditions.requireNotEmpty(userIds, "No userId specified");
-    Preconditions.requireSingular(userIds, "Multiple userIds not supported");
+    HandlerPreconditions.requireNotEmpty(userIds, "No userId specified");
+    HandlerPreconditions.requireSingular(userIds, "Multiple userIds not supported");
     // Throws exceptions if userIds contains more than one element or zero elements
     return service.deleteActivities(Iterables.getOnlyElement(userIds), request.getGroup(),
         request.getAppId(), activityIds, request.getToken());
@@ -68,9 +72,9 @@
    *
    * examples: /activities/john.doe/@self - postBody is an activity object
    */
-  @Override
-  protected Future<?> handlePut(RequestItem request) throws SocialSpiException {
-    return handlePost(request);
+  @Operation(httpMethods="PUT", bodyParam = "activity")
+  public Future<?> update(RequestItem request) throws SocialSpiException {
+    return create(request);
   }
 
   /**
@@ -78,17 +82,16 @@
    *
    * examples: /activities/john.doe/@self - postBody is an activity object
    */
-  @Override
-  protected Future<?> handlePost(RequestItem request) throws SocialSpiException {
-    request.applyUrlTemplate(ACTIVITY_ID_PATH);
+  @Operation(httpMethods="POST", bodyParam = "activity")
+  public Future<?> create(RequestItem request) throws SocialSpiException {
 
     Set<UserId> userIds = request.getUsers();
     List<String> activityIds = request.getListParameter("activityId");
 
-    Preconditions.requireNotEmpty(userIds, "No userId specified");
-    Preconditions.requireSingular(userIds, "Multiple userIds not supported");
+    HandlerPreconditions.requireNotEmpty(userIds, "No userId specified");
+    HandlerPreconditions.requireSingular(userIds, "Multiple userIds not supported");
     // TODO(lryan) This seems reasonable to allow on PUT but we don't have an update verb.
-    Preconditions.requireEmpty(activityIds, "Cannot specify activityId in create");
+    HandlerPreconditions.requireEmpty(activityIds, "Cannot specify activityId in create");
 
     return service.createActivity(Iterables.getOnlyElement(userIds), request.getGroup(),
         request.getAppId(), request.getFields(),
@@ -103,18 +106,16 @@
    * examples: /activities/john.doe/@self/1 /activities/john.doe/@self
    * /activities/john.doe,jane.doe/@friends
    */
-  @Override
-  protected Future<?> handleGet(RequestItem request)
+  @Operation(httpMethods="GET")
+  public Future<?> get(RequestItem request)
       throws SocialSpiException {
-    request.applyUrlTemplate(ACTIVITY_ID_PATH);
-
     Set<UserId> userIds = request.getUsers();
     Set<String> optionalActivityIds = ImmutableSet.copyOf(request.getListParameter("activityId"));
 
     CollectionOptions options = new CollectionOptions(request);
 
     // Preconditions
-    Preconditions.requireNotEmpty(userIds, "No userId specified");
+    HandlerPreconditions.requireNotEmpty(userIds, "No userId specified");
     if (userIds.size() > 1 && !optionalActivityIds.isEmpty()) {
       throw new IllegalArgumentException("Cannot fetch same activityIds for multiple userIds");
     }
@@ -136,4 +137,13 @@
         // getSortBy(params), getFilterBy(params), getStartIndex(params), getCount(params),
         request.getFields(), options, request.getToken());
   }
+
+  @Operation(httpMethods = "GET", path="/@supportedFields")
+  public List supportedFields(RequestItem request) {
+    // TODO: Would be nice if name in config matched name of service.
+    String container = Objects.firstNonNull(request.getToken().getContainer(),
+        ContainerConfig.DEFAULT_CONTAINER);
+    return config.getList(container,
+        "${gadgets\\.features.opensocial-0\\.8.supportedFields.activity}");
+  }
 }

Modified: incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/ApiServlet.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/ApiServlet.java?rev=740067&r1=740066&r2=740067&view=diff
==============================================================================
--- incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/ApiServlet.java (original)
+++ incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/ApiServlet.java Mon Feb  2 18:39:12 2009
@@ -20,7 +20,6 @@
 import org.apache.shindig.auth.AuthInfo;
 import org.apache.shindig.auth.SecurityToken;
 import org.apache.shindig.common.servlet.InjectedServlet;
-import org.apache.shindig.common.util.ImmediateFuture;
 import org.apache.shindig.social.ResponseError;
 import org.apache.shindig.social.core.util.BeanJsonConverter;
 import org.apache.shindig.social.opensocial.spi.SocialSpiException;
@@ -28,12 +27,12 @@
 import com.google.inject.Inject;
 import com.google.inject.name.Named;
 
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 import java.util.Collections;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Future;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
 
 /**
  * Common base class for API servlets.
@@ -41,13 +40,13 @@
 public abstract class ApiServlet extends InjectedServlet {
   protected static final String DEFAULT_ENCODING = "UTF-8";
 
-  private HandlerDispatcher dispatcher;
+  protected HandlerRegistry dispatcher;
   protected BeanJsonConverter jsonConverter;
   protected BeanConverter xmlConverter;
   protected BeanConverter atomConverter;
 
   @Inject
-  public void setHandlerDispatcher(HandlerDispatcher dispatcher) {
+  public void setHandlerRegistry(HandlerRegistry dispatcher) {
     this.dispatcher = dispatcher;
   }
 
@@ -75,21 +74,6 @@
             + "requests are not allowed"));
   }
 
-  /**
-   * Delivers a request item to the appropriate DataRequestHandler.
-   */
-  protected Future<?> handleRequestItem(RequestItem requestItem,
-      HttpServletRequest servletRequest) {
-    DataRequestHandler handler = dispatcher.getHandler(requestItem.getService());
-
-    if (handler == null) {
-      return ImmediateFuture.errorInstance(new SocialSpiException(ResponseError.NOT_IMPLEMENTED,
-          "The service " + requestItem.getService() + " is not implemented"));
-    }
-
-    return handler.handleItem(requestItem);
-  }
-
   protected ResponseItem getResponseItem(Future<?> future) {
     ResponseItem response;
     try {

Modified: incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/AppDataHandler.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/AppDataHandler.java?rev=740067&r1=740066&r2=740067&view=diff
==============================================================================
--- incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/AppDataHandler.java (original)
+++ incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/AppDataHandler.java Mon Feb  2 18:39:12 2009
@@ -29,12 +29,11 @@
 import java.util.Set;
 import java.util.concurrent.Future;
 
-public class AppDataHandler extends DataRequestHandler {
+@Service(name = "appdata", path = "/{userId}+/{groupId}/{appId}")
+public class AppDataHandler {
 
   private final AppDataService service;
 
-  private static final String APP_DATA_PATH = "/appdata/{userId}+/{groupId}/{appId}";
-
   @Inject
   public AppDataHandler(AppDataService service) {
     this.service = service;
@@ -49,15 +48,14 @@
    * values and set on the person object. If there are no fields vars then all of the data will be
    * overridden.
    */
-  @Override
-  protected Future<?> handleDelete(RequestItem request)
+  @Operation(httpMethods = "DELETE")
+  public Future<?> delete(RequestItem request)
       throws SocialSpiException {
-    request.applyUrlTemplate(APP_DATA_PATH);
 
     Set<UserId> userIds = request.getUsers();
 
-    Preconditions.requireNotEmpty(userIds, "No userId specified");
-    Preconditions.requireSingular(userIds, "Multiple userIds not supported");
+    HandlerPreconditions.requireNotEmpty(userIds, "No userId specified");
+    HandlerPreconditions.requireSingular(userIds, "Multiple userIds not supported");
 
     return service.deletePersonData(userIds.iterator().next(), request.getGroup(),
         request.getAppId(), request.getFields(), request.getToken());
@@ -72,9 +70,9 @@
    * values and set on the person object. If there are no fields vars then all of the data will be
    * overridden.
    */
-  @Override
-  protected Future<?> handlePut(RequestItem request) throws SocialSpiException {
-    return handlePost(request);
+  @Operation(httpMethods = "PUT", bodyParam = "data")
+  public Future<?> update(RequestItem request) throws SocialSpiException {
+    return create(request);
   }
 
   /**
@@ -85,15 +83,12 @@
    * The post data should be a regular json object. All of the fields vars will be pulled from the
    * values and set. If there are no fields vars then all of the data will be overridden.
    */
-  @SuppressWarnings("unchecked")
-  @Override
-  protected Future<?> handlePost(RequestItem request) throws SocialSpiException {
-    request.applyUrlTemplate(APP_DATA_PATH);
-
+  @Operation(httpMethods = "POST", bodyParam = "data")
+  public Future<?> create(RequestItem request) throws SocialSpiException {
     Set<UserId> userIds = request.getUsers();
 
-    Preconditions.requireNotEmpty(userIds, "No userId specified");
-    Preconditions.requireSingular(userIds, "Multiple userIds not supported");
+    HandlerPreconditions.requireNotEmpty(userIds, "No userId specified");
+    HandlerPreconditions.requireSingular(userIds, "Multiple userIds not supported");
 
     Map<String, String> values = request.getTypedParameter("data", HashMap.class);
     for (String key : values.keySet()) {
@@ -112,14 +107,12 @@
    *
    * examples: /appdata/john.doe/@friends/app?fields=count /appdata/john.doe/@self/app
    */
-  @Override
-  protected Future<?> handleGet(RequestItem request) throws SocialSpiException {
-    request.applyUrlTemplate(APP_DATA_PATH);
-
+  @Operation(httpMethods = "GET")
+  public Future<?> get(RequestItem request) throws SocialSpiException {
     Set<UserId> userIds = request.getUsers();
 
     // Preconditions
-    Preconditions.requireNotEmpty(userIds, "No userId specified");
+    HandlerPreconditions.requireNotEmpty(userIds, "No userId specified");
 
     return service.getPersonData(userIds, request.getGroup(),
         request.getAppId(), request.getFields(), request.getToken());

Added: incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/BaseRequestItem.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/BaseRequestItem.java?rev=740067&view=auto
==============================================================================
--- incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/BaseRequestItem.java (added)
+++ incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/BaseRequestItem.java Mon Feb  2 18:39:12 2009
@@ -0,0 +1,280 @@
+/*
+ * 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 org.apache.shindig.social.opensocial.service;
+
+import org.apache.shindig.auth.SecurityToken;
+import org.apache.shindig.social.ResponseError;
+import org.apache.shindig.social.opensocial.spi.GroupId;
+import org.apache.shindig.social.opensocial.spi.PersonService;
+import org.apache.shindig.social.opensocial.spi.SocialSpiException;
+import org.apache.shindig.social.opensocial.spi.UserId;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
+import org.joda.time.DateTime;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Default implementation of RequestItem
+ */
+public class BaseRequestItem implements RequestItem {
+
+  final SecurityToken token;
+  final BeanConverter converter;
+  final Map<String,Object> parameters;
+
+  public BaseRequestItem(Map<String, String[]> parameters,
+      SecurityToken token,
+      BeanConverter converter) {
+    this.token = token;
+    this.converter = converter;
+    this.parameters = Maps.newHashMap();
+    for (Map.Entry<String, String[]> entry : parameters.entrySet()) {
+      if  (entry.getValue() == null) {
+        setParameter(entry.getKey(), null);
+      } else if (entry.getValue().length == 1) {
+        setParameter(entry.getKey(), entry.getValue()[0]);
+      } else {
+        setParameter(entry.getKey(), Lists.newArrayList(entry.getValue()));
+      }
+    }
+  }
+
+  public BaseRequestItem(JSONObject parameters,
+      SecurityToken token,
+      BeanConverter converter) {
+    try {
+      this.parameters = Maps.newHashMap();
+      Iterator keys = parameters.keys();
+      while (keys.hasNext()) {
+        String key = (String)keys.next();
+        this.parameters.put(key, parameters.get(key));
+      }
+      this.token = token;
+      this.converter = converter;
+    } catch (JSONException je) {
+      throw new SocialSpiException(ResponseError.INTERNAL_ERROR, je.getMessage(), je);
+    }
+  }
+
+  public String getAppId() {
+    String appId = getParameter(APP_ID);
+    if (appId != null && appId.equals(APP_SUBSTITUTION_TOKEN)) {
+      return token.getAppId();
+    } else {
+      return appId;
+    }
+  }
+
+  public Date getUpdatedSince() {
+    String updatedSince = getParameter("updatedSince");
+    if (updatedSince == null)
+      return null;
+
+    DateTime date = new DateTime(updatedSince);
+
+    return date.toDate();
+  }
+
+  public Set<UserId> getUsers() {
+    List<String> ids = getListParameter(USER_ID);
+    if (ids.isEmpty()) {
+      if (token.getViewerId() != null) {
+        // Assume @me
+        ids = Lists.newArrayList("@me");
+      } else {
+        throw new IllegalArgumentException("No userId provided and viewer not available");
+      }
+    }
+    Set<UserId> userIds = Sets.newLinkedHashSet();
+    for (String id : ids) {
+      userIds.add(UserId.fromJson(id));
+    }
+    return userIds;
+  }
+
+
+  public GroupId getGroup() {
+    return GroupId.fromJson(getParameter(GROUP_ID, "@self"));
+  }
+
+  public int getStartIndex() {
+    String startIndex = getParameter(START_INDEX);
+    try {
+      return startIndex == null ? DEFAULT_START_INDEX
+          : Integer.valueOf(startIndex);
+    } catch (NumberFormatException nfe) {
+      throw new SocialSpiException(ResponseError.BAD_REQUEST,
+          "Parameter " + START_INDEX + " (" + startIndex + ") is not a number.");
+    }
+  }
+
+  public int getCount() {
+    String count = getParameter(COUNT);
+    try {
+      return count == null ? DEFAULT_COUNT : Integer.valueOf(count);
+    } catch (NumberFormatException nfe) {
+      throw new SocialSpiException(ResponseError.BAD_REQUEST,
+           "Parameter " + COUNT + " (" + count + ") is not a number.");
+    }
+  }
+
+  public String getSortBy() {
+    String sortBy = getParameter(SORT_BY);
+    return sortBy == null ? PersonService.TOP_FRIENDS_SORT : sortBy;
+  }
+
+  public PersonService.SortOrder getSortOrder() {
+    String sortOrder = getParameter(SORT_ORDER);
+    try {
+      return sortOrder == null
+            ? PersonService.SortOrder.ascending
+            : PersonService.SortOrder.valueOf(sortOrder);
+    } catch (IllegalArgumentException iae) {
+      throw new SocialSpiException(ResponseError.BAD_REQUEST,
+           "Parameter " + SORT_ORDER + " (" + sortOrder + ") is not valid.");
+    }
+  }
+
+  public String getFilterBy() {
+    return getParameter(FILTER_BY);
+  }
+
+  public PersonService.FilterOperation getFilterOperation() {
+    String filterOp = getParameter(FILTER_OPERATION);
+    try {
+      return filterOp == null
+          ? PersonService.FilterOperation.contains
+          : PersonService.FilterOperation.valueOf(filterOp);
+    } catch (IllegalArgumentException iae) {
+      throw new SocialSpiException(ResponseError.BAD_REQUEST,
+           "Parameter " + FILTER_OPERATION + " (" + filterOp + ") is not valid.");
+    }
+  }
+
+  public String getFilterValue() {
+    String filterValue = getParameter(FILTER_VALUE);
+    return filterValue == null ? "" : filterValue;
+  }
+
+  public Set<String> getFields() {
+    return getFields(Collections.<String>emptySet());
+  }
+
+  public Set<String> getFields(Set<String> defaultValue) {
+    Set<String> result = ImmutableSet.copyOf(getListParameter(FIELDS));
+    if (result.isEmpty()) {
+      return defaultValue;
+    }
+    return result;
+  }
+
+
+  public SecurityToken getToken() {
+    return token;
+  }
+
+  public <T> T getTypedParameter(String parameterName, Class<T> dataTypeClass) {
+    return converter.convertToObject(getParameter(parameterName), dataTypeClass);
+  }
+
+  public String getParameter(String paramName) {
+    Object param = this.parameters.get(paramName);
+    if (param instanceof List) {
+      if (((List)param).isEmpty()) {
+        return null;
+      } else {
+        param = ((List)param).get(0);
+      }
+    }
+    if (param == null) {
+      return null;
+    }
+    return param.toString();
+  }
+
+  public String getParameter(String paramName, String defaultValue) {
+    String param = getParameter(paramName);
+    if (param == null) {
+      return defaultValue;
+    }
+    return param;
+  }
+
+  public List<String> getListParameter(String paramName) {
+    Object param = this.parameters.get(paramName);
+    if (param == null) {
+      return Collections.emptyList();
+    }
+    if (param instanceof String && ((String)param).indexOf(',') != -1) {
+      List<String> listParam = Arrays.asList(((String)param).split(","));
+      this.parameters.put(paramName, listParam);
+      return listParam;
+    }
+    else if (param instanceof List) {
+      List<String> listParam = (List<String>)param;
+      return listParam;
+    } else if (param instanceof JSONArray) {
+      try {
+        JSONArray jsonArray = (JSONArray)param;
+        List<String> returnVal = Lists.newArrayListWithExpectedSize(jsonArray.length());
+        for (int i = 0; i < jsonArray.length(); i++) {
+          returnVal.add(jsonArray.getString(i));
+        }
+        return returnVal;
+      } catch (JSONException je) {
+        throw new SocialSpiException(ResponseError.BAD_REQUEST, je.getMessage(), je);
+      }
+    } else {
+      // Allow up-conversion of non-array to array params.
+      return Lists.newArrayList(param.toString());
+    }
+  }
+
+
+  // Exposed for testing only
+  void setParameter(String paramName, Object paramValue) {
+    if (paramValue instanceof String[]) {
+      String[] arr = (String[])paramValue;
+      if (arr.length == 1) {
+        this.parameters.put(paramName, arr[0]);
+      } else {
+        this.parameters.put(paramName, Lists.newArrayList(arr));
+      }
+    } else if (paramValue instanceof String) {
+      String stringValue = (String)paramValue;
+      if (stringValue.length() > 0) {
+        this.parameters.put(paramName, stringValue);
+      }
+    } else {
+      this.parameters.put(paramName, paramValue);
+    }
+  }
+}

Modified: incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/DataServiceServlet.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/DataServiceServlet.java?rev=740067&r1=740066&r2=740067&view=diff
==============================================================================
--- incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/DataServiceServlet.java (original)
+++ incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/DataServiceServlet.java Mon Feb  2 18:39:12 2009
@@ -18,46 +18,32 @@
 package org.apache.shindig.social.opensocial.service;
 
 import org.apache.shindig.auth.SecurityToken;
-import org.apache.shindig.config.ContainerConfig;
-import org.apache.shindig.social.ResponseError;
 import org.apache.shindig.social.opensocial.spi.DataCollection;
 import org.apache.shindig.social.opensocial.spi.RestfulCollection;
 
-import com.google.common.base.Objects;
 import com.google.common.collect.ImmutableMap;
-import com.google.inject.Inject;
 
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
 import java.io.IOException;
 import java.io.PrintWriter;
-import java.util.List;
-import java.util.Map;
+import java.io.Reader;
+import java.util.concurrent.Future;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-
 public class DataServiceServlet extends ApiServlet {
 
   protected static final String FORMAT_PARAM = "format";
   protected static final String ATOM_FORMAT = "atom";
   protected static final String XML_FORMAT = "xml";
 
-  public static final String PEOPLE_ROUTE = "people";
-  public static final String ACTIVITY_ROUTE = "activities";
-  public static final String APPDATA_ROUTE = "appdata";
-
   public static final String CONTENT_TYPE = "CONTENT_TYPE";
 
   private static final Logger logger = Logger.getLogger("org.apache.shindig.social.opensocial.spi");
 
-  /** Map from service name to the property name in the container config */
-  private static final Map<String, String> SERVICE_TO_SUPPORTED_FIELD_MAP =
-    ImmutableMap.of("people", "person", "activities", "activity");
-
-
-  private ContainerConfig config;
+  protected static final String X_HTTP_METHOD_OVERRIDE = "X-HTTP-Method-Override";
 
   @Override
   protected void doGet(HttpServletRequest servletRequest,
@@ -106,26 +92,37 @@
         responseItem.getErrorMessage());
   }
 
-  @Inject
-  public void setContainerConfig(ContainerConfig config) {
-    this.config = config;
-  }
-
   /**
    * Handler for non-batch requests.
    */
   private void handleSingleRequest(HttpServletRequest servletRequest,
       HttpServletResponse servletResponse, SecurityToken token,
       BeanConverter converter) throws IOException {
-    RestfulRequestItem requestItem = new RestfulRequestItem(servletRequest, token, converter);
-    ResponseItem responseItem;
 
-    if (requestItem.getUrl().endsWith("/@supportedFields")) {
-      responseItem = getSupportedFields(requestItem);
-    } else {
-      responseItem = getResponseItem(handleRequestItem(requestItem, servletRequest));
+    // TODO Rework to allow sub-services
+    String path = servletRequest.getPathInfo();
+
+    // TODO - This shouldnt be on BaseRequestItem
+    String method = servletRequest.getParameter(X_HTTP_METHOD_OVERRIDE);
+    if (method == null) {
+      method = servletRequest.getMethod();
+    }
+
+    // Always returns a non-null handler.
+    RestHandler handler = dispatcher.getRestHandler(path, method.toUpperCase());
+
+    Reader bodyReader = null;
+    if (!servletRequest.getMethod().equals("GET") &&
+        !servletRequest.getMethod().equals("HEAD")) {
+      bodyReader = servletRequest.getReader();
     }
 
+    // Execute the request
+    Future future = handler.execute(path, servletRequest.getParameterMap(),
+        bodyReader, token, converter);
+
+    ResponseItem responseItem = getResponseItem(future);
+
     servletResponse.setContentType(converter.getContentType());
     if (responseItem.getError() == null) {
       PrintWriter writer = servletResponse.getWriter();
@@ -141,25 +138,6 @@
     }
   }
 
-  private ResponseItem getSupportedFields(RequestItem requestItem) {
-    String service = requestItem.getService();
-    String configProperty = SERVICE_TO_SUPPORTED_FIELD_MAP.get(service);
-    if (configProperty == null) {
-      configProperty = service;
-    }
-
-    String container = Objects.firstNonNull(requestItem.getToken().getContainer(), "default");
-    // TODO: hardcoding opensocial-0.8 is brittle
-    List<Object> fields = config.getList(container,
-        "${gadgets\\.features.opensocial-0\\.8.supportedFields." + configProperty + '}');
-
-    if (fields.size() == 0) {
-      return new ResponseItem(ResponseError.NOT_IMPLEMENTED,"Supported fields not available for" +
-      		" service \"" + service + '\"');
-    }
-
-    return new ResponseItem(fields);
-  }
 
   BeanConverter getConverterForRequest(HttpServletRequest servletRequest) {
     String formatString = null;

Copied: incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/DefaultHandlerRegistry.java (from r738302, incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/StandardHandlerDispatcher.java)
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/DefaultHandlerRegistry.java?p2=incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/DefaultHandlerRegistry.java&p1=incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/StandardHandlerDispatcher.java&r1=738302&r2=740067&rev=740067&view=diff
==============================================================================
--- incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/StandardHandlerDispatcher.java (original)
+++ incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/DefaultHandlerRegistry.java Mon Feb  2 18:39:12 2009
@@ -18,61 +18,291 @@
  */
 package org.apache.shindig.social.opensocial.service;
 
-import com.google.inject.Inject;
-import com.google.inject.Provider;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.shindig.auth.SecurityToken;
+import org.apache.shindig.common.util.ImmediateFuture;
+import org.apache.shindig.social.ResponseError;
+import org.apache.shindig.social.opensocial.spi.SocialSpiException;
 
-import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.name.Named;
+import org.json.JSONException;
+import org.json.JSONObject;
 
+import java.io.Reader;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Future;
 
 /**
- * Default implementation of HandlerDispatcher.  Provides default
- * bindings for the person, activity, and app data handlers.
+ * Default implementation of HandlerRegistry. Bind to appropriately
+ * annotated handlers.
  */
-public class StandardHandlerDispatcher implements HandlerDispatcher {
-  private final Map<String, Provider<? extends DataRequestHandler>> handlers;
+public class DefaultHandlerRegistry implements HandlerRegistry {
+
+  private final Map<String, RestHandler> restOperations = Maps.newHashMap();
+  private final Map<String, RpcHandler> rpcOperations = Maps.newHashMap();
+
+  private final Injector injector;
 
   /**
-   * Creates a dispatcher with the standard handlers.
-   * @param personHandlerProvider provider for the person handler
-   * @param activityHandlerProvider provider for the activity handler
-   * @param appDataHandlerProvider provider for the app data handler
+   * Creates a dispatcher with the specified handler classes
+   * @param injector Used to create instance if handler is a Class
+   * @param handlers List of handler instances or classes.
    */
   @Inject
-  public StandardHandlerDispatcher(Provider<PersonHandler> personHandlerProvider,
-      Provider<ActivityHandler> activityHandlerProvider,
-      Provider<AppDataHandler> appDataHandlerProvider) {
-    this(ImmutableMap.of(
-        DataServiceServlet.PEOPLE_ROUTE, personHandlerProvider,
-        DataServiceServlet.ACTIVITY_ROUTE, activityHandlerProvider,
-        DataServiceServlet.APPDATA_ROUTE, appDataHandlerProvider));
+  public DefaultHandlerRegistry(Injector injector,
+      @Named("org.apache.shindig.handlers") List handlers) {
+    this.injector = injector;
+    addHandlers(handlers.toArray());
   }
 
   /**
-   * Creates a dispatcher with a custom list of handlers.
-   * @param handlers a map of handlers by service name
+   * Get an RPC handler
    */
-  public StandardHandlerDispatcher(Map<String,Provider<? extends DataRequestHandler>> handlers) {
-    this.handlers = Maps.newHashMap(handlers);
+  public RpcHandler getRpcHandler(JSONObject rpc) {
+    try {
+      String key = rpc.getString("method");
+      RpcHandler rpcHandler = rpcOperations.get(key);
+      if (rpcHandler == null) {
+        return new ErrorRpcHandler(new SocialSpiException(ResponseError.NOT_IMPLEMENTED,
+            "The method " + key + " is not implemented"));
+      }
+      return rpcHandler;
+    } catch (JSONException je) {
+      return new ErrorRpcHandler(new SocialSpiException(ResponseError.BAD_REQUEST,
+            "No method requested in RPC"));
+    }
   }
 
   /**
-   * Gets a handler by service name.
+   * Get a REST request handler
    */
-  public DataRequestHandler getHandler(String service) {
-    Provider<? extends DataRequestHandler> provider = handlers.get(service);
-    if (provider == null) {
-      return null;
+  public RestHandler getRestHandler(String path, String method) {
+    method = method.toUpperCase();
+    RestHandler restHandler = null;
+    String matchPath = path;
+    int separatorIndex = matchPath.lastIndexOf("/");
+    while (restHandler == null && separatorIndex != -1) {
+      restHandler = restOperations.get(method + " " + matchPath);
+      matchPath = matchPath.substring(0, separatorIndex);
+      separatorIndex = matchPath.lastIndexOf("/");
+    }
+    if (restHandler == null) {
+      return new ErrorRestHandler(new SocialSpiException(ResponseError.NOT_IMPLEMENTED,
+            "No service defined for path " + path));
     }
+    return restHandler;
+  }
 
-    return provider.get();
+  public Set<String> getSupportedRestServices() {
+    return Collections.unmodifiableSet(restOperations.keySet());
+  }
+
+  public Set<String> getSupportedRpcServices() {
+    return Collections.unmodifiableSet(rpcOperations.keySet());
   }
 
   /**
    * Adds a custom handler.
    */
-  public void addHandler(String service, Provider<? extends DataRequestHandler> handler) {
-    handlers.put(service, handler);
+  public void addHandlers(Object... handlers) {
+    for (Object handler : handlers) {
+      Class handlerType;
+      if (handler instanceof Class) {
+        handlerType = (Class) handler;
+        handler = injector.getInstance(handlerType);
+      } else {
+        handlerType = handler.getClass();
+      }
+      if (!handlerType.isAnnotationPresent(Service.class)) {
+        throw new IllegalStateException("Attempt to bind unannotated service implementation " +
+            handlerType.getName());
+      }
+      Service service = (Service) handlerType.getAnnotation(Service.class);
+      String serviceName = service.name();
+
+      for (Method m : handlerType.getMethods()) {
+        if (m.isAnnotationPresent(Operation.class)) {
+          Operation op = m.getAnnotation(Operation.class);
+          createRpcHandler(handler, service, m);
+          createRestHandler(handler, service, op, m);
+        }
+      }
+    }
+  }
+
+  private void createRestHandler(Object handler, Service service, Operation op, Method m) {
+    RestHandler restHandler = new RestInvocationHandler(service, op, m, handler);
+    String serviceName = service.name();
+
+    for (String httpMethod : op.httpMethods()) {
+      if (!StringUtils.isEmpty(httpMethod)) {
+        if (StringUtils.isEmpty(op.path())) {
+          // Use the standard service name as the key
+          restOperations.put(httpMethod.toUpperCase() + " /" + serviceName, restHandler);
+        } else {
+          // Use the standard service name and constant prefix as the key
+          String prefix = op.path().split("\\{")[0];
+          restOperations.put(httpMethod.toUpperCase() + " /" + serviceName +
+              prefix, restHandler);
+        }
+      }
+    }
+  }
+
+  private void createRpcHandler(Object handler, Service service, Method m) {
+    RpcHandler rpcHandler = new RpcInvocationHandler(m, handler);
+    String defaultName = service.name() + "." + m.getName();
+    rpcOperations.put(defaultName, rpcHandler);
+  }
+
+  /**
+   * Proxy binding for an RPC operation. We allow binding to methods that
+   * return non-Future types by wrapping them in ImmediateFuture.
+   */
+  private static final class RpcInvocationHandler implements RpcHandler {
+    private Method receiver;
+    Object handlerInstance;
+
+    private RpcInvocationHandler(Method receiver, Object handlerInstance) {
+      this.receiver = receiver;
+      this.handlerInstance =  handlerInstance;
+    }
+
+    public Future execute(JSONObject rpc, SecurityToken token, BeanConverter converter) {
+      try {
+        RequestItem item;
+        if (rpc.has("params")) {
+          item = new BaseRequestItem((JSONObject)rpc.get("params"), token, converter);
+        } else {
+          item = new BaseRequestItem(new JSONObject(), token, converter);
+        }
+        Object result = receiver.invoke(handlerInstance, item);
+        if (result instanceof Future) {
+          return (Future)result;
+        }
+        return ImmediateFuture.newInstance(result);
+      } catch (InvocationTargetException ite) {
+        // Unwrap these
+        return ImmediateFuture.errorInstance(ite.getTargetException());
+      } catch (Exception e) {
+        return ImmediateFuture.errorInstance(e);
+      }
+    }
+  }
+
+  /**
+   * Proxy binding for a REST operation. We allow binding to methods that
+   * return non-Future types by wrapping them in ImmediateFuture.
+   */
+  static final class RestInvocationHandler implements RestHandler {
+    Method receiver;
+    Object handlerInstance;
+    Service service;
+    Operation operation;
+    final String[] expectedUrl;
+
+    private RestInvocationHandler(Service service, Operation operation,
+                                  Method receiver, Object handlerInstance) {
+      this.service = service;
+      this.operation = operation;
+      this.receiver = receiver;
+      this.handlerInstance =  handlerInstance;
+      expectedUrl = service.path().split("/");
+    }
+
+    public Future<?> execute(String path, Map<String, String[]> parameters, Reader body,
+                          SecurityToken token, BeanConverter converter) {
+      // Create a mutable copy.
+      parameters = new HashMap(parameters);
+      try {
+        // bind the body contents if available
+        if (body != null) {
+          parameters.put(operation.bodyParam(), new String[]{IOUtils.toString(body)});
+        }
+
+        extractPathParameters(parameters, path);
+
+        RequestItem item = new BaseRequestItem(parameters, token, converter);
+        Object result = receiver.invoke(handlerInstance, item);
+        if (result instanceof Future) {
+          return (Future) result;
+        }
+        return ImmediateFuture.newInstance(result);
+      } catch (InvocationTargetException ite) {
+        // Unwrap these
+        return ImmediateFuture.errorInstance(ite.getTargetException());
+      } catch (Exception e) {
+        return ImmediateFuture.errorInstance(e);
+      }
+    }
+
+    private void extractPathParameters(Map<String, String[]> parameters, String path) {
+      String[] actualUrl = path.split("/");
+
+      for (int i = 1; i < actualUrl.length; i++) {
+        String actualPart = actualUrl[i];
+        String expectedPart = expectedUrl[i - 1];
+        // Extract named parameters from the path
+        if (expectedPart.startsWith("{")) {
+          if (expectedPart.endsWith("}+")) {
+            // The param can be a repeated field. Use ',' as default separator
+            parameters.put(expectedPart.substring(1, expectedPart.length() - 2),
+                actualPart.split(","));
+          } else {
+            if (actualPart.indexOf(',') != -1) {
+              throw new SocialSpiException(ResponseError.BAD_REQUEST,
+                  "Cannot expect plural value " + actualPart
+                      + " for singular field " + expectedPart + " in " + service.path());
+            }
+            parameters.put(expectedPart.substring(1, expectedPart.length() - 1),
+                new String[]{actualPart});
+          }
+        }
+      }
+    }
+  }
+
+
+  /**
+   * Standard REST handler to wrap errors
+   */
+  private static final class ErrorRestHandler implements RestHandler {
+
+    private SocialSpiException error;
+
+    public ErrorRestHandler(SocialSpiException error) {
+      this.error = error;
+    }
+
+    public Future execute(String path, Map parameters, Reader body,
+                          SecurityToken token, BeanConverter converter) {
+      return ImmediateFuture.errorInstance(error);
+    }
+  }
+
+  /**
+   * Standard RPC handler to wrap errors
+   */
+  private static final class ErrorRpcHandler implements RpcHandler {
+
+    private SocialSpiException error;
+
+    public ErrorRpcHandler(SocialSpiException error) {
+      this.error = error;
+    }
+
+    public Future execute(JSONObject rpc, SecurityToken token, BeanConverter converter) {
+      return ImmediateFuture.errorInstance(error);
+    }
   }
 }

Propchange: incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/DefaultHandlerRegistry.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/HandlerPreconditions.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/HandlerPreconditions.java?rev=740067&view=auto
==============================================================================
--- incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/HandlerPreconditions.java (added)
+++ incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/HandlerPreconditions.java Mon Feb  2 18:39:12 2009
@@ -0,0 +1,58 @@
+/*
+ * 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 org.apache.shindig.social.opensocial.service;
+
+import org.apache.shindig.social.ResponseError;
+import org.apache.shindig.social.opensocial.spi.SocialSpiException;
+
+import java.util.Collection;
+
+/**
+ * Utility class for common API call preconditions
+ */
+public class HandlerPreconditions {
+
+  private HandlerPreconditions() {}
+
+  public static void requireNotEmpty(Collection<?> coll, String message)
+      throws SocialSpiException {
+    if (coll.isEmpty()) {
+      throw new SocialSpiException(ResponseError.BAD_REQUEST, message);
+    }
+  }
+
+  public static void requireEmpty(Collection<?> coll, String message) throws SocialSpiException {
+    if (!coll.isEmpty()) {
+      throw new SocialSpiException(ResponseError.BAD_REQUEST, message);
+    }
+  }
+
+  public static void requireSingular(Collection<?> coll, String message)
+      throws SocialSpiException {
+    if (coll.size() != 1) {
+      throw new SocialSpiException(ResponseError.BAD_REQUEST, message);
+    }
+  }
+
+  public static void requirePlural(Collection<?> coll, String message) throws SocialSpiException {
+    if (coll.size() <= 1) {
+      throw new SocialSpiException(ResponseError.BAD_REQUEST, message);
+    }
+  }
+}

Copied: incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/HandlerRegistry.java (from r738302, incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/HandlerDispatcher.java)
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/HandlerRegistry.java?p2=incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/HandlerRegistry.java&p1=incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/HandlerDispatcher.java&r1=738302&r2=740067&rev=740067&view=diff
==============================================================================
--- incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/HandlerDispatcher.java (original)
+++ incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/HandlerRegistry.java Mon Feb  2 18:39:12 2009
@@ -19,19 +19,36 @@
 package org.apache.shindig.social.opensocial.service;
 
 import com.google.inject.ImplementedBy;
+import org.json.JSONObject;
+
+import java.util.Set;
 
 /**
- * Dispatcher for DataRequestHandler requests.  The default implementation
- * registers the three standard Shindig handlers.
- * <p>
- * Add a custom binding of this interface to customize request handling.
+ * Registry of REST and RPC handlers for the set of available services
  */
-@ImplementedBy(StandardHandlerDispatcher.class)
-public interface HandlerDispatcher {
+@ImplementedBy(DefaultHandlerRegistry.class)
+public interface HandlerRegistry {
+
+  /**
+   * @param rpc The rpc to dispatch
+   * @return the handler
+   */
+  RpcHandler getRpcHandler(JSONObject rpc);
+
+  /**
+   * @param path Path of the service
+   * @param method The HTTP method
+   * @return the handler
+   */
+  RestHandler getRestHandler(String path, String method);
+  
+  /**
+   * @return The list of available services
+   */
+  public Set<String> getSupportedRestServices();
 
   /**
-   * @param service a service name
-   * @return the handler, or null if no handler is registered for that service
+   * @return The list of available services
    */
-  DataRequestHandler getHandler(String service);
+  public Set<String> getSupportedRpcServices();
 }

Propchange: incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/HandlerRegistry.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/JsonRpcServlet.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/JsonRpcServlet.java?rev=740067&r1=740066&r2=740067&view=diff
==============================================================================
--- incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/JsonRpcServlet.java (original)
+++ incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/JsonRpcServlet.java Mon Feb  2 18:39:12 2009
@@ -17,6 +17,8 @@
  */
 package org.apache.shindig.social.opensocial.service;
 
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang.StringUtils;
 import org.apache.shindig.auth.SecurityToken;
 import org.apache.shindig.common.util.JsonConversionUtil;
 import org.apache.shindig.social.ResponseError;
@@ -24,18 +26,16 @@
 import org.apache.shindig.social.opensocial.spi.RestfulCollection;
 
 import com.google.common.collect.Lists;
-import org.apache.commons.io.IOUtils;
-import org.apache.commons.lang.StringUtils;
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
-import java.io.IOException;
-import java.util.List;
-import java.util.concurrent.Future;
 import javax.servlet.ServletException;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
+import java.util.concurrent.Future;
 
 /**
  * JSON-RPC handler servlet.
@@ -101,8 +101,7 @@
     // into single requests.
     for (int i = 0; i < batch.length(); i++) {
       JSONObject batchObj = batch.getJSONObject(i);
-      RpcRequestItem requestItem = new RpcRequestItem(batchObj, token, jsonConverter);
-      responses.add(handleRequestItem(requestItem, servletRequest));
+      responses.add(dispatcher.getRpcHandler(batchObj).execute(batchObj, token, jsonConverter));
     }
 
     // Resolve each Future into a response.
@@ -125,11 +124,13 @@
     if (request.has("id")) {
       key = request.getString("id");
     }
-    RpcRequestItem requestItem = new RpcRequestItem(request, token, jsonConverter);
+
+    // getRpcHandler never returns null
+    Future future = dispatcher.getRpcHandler(request).execute(request, token, jsonConverter);
 
     // Resolve each Future into a response.
     // TODO: should use shared deadline across each request
-    ResponseItem response = getResponseItem(handleRequestItem(requestItem, servletRequest));
+    ResponseItem response = getResponseItem(future);
     JSONObject result = getJSONResponse(key, response);
     servletResponse.getWriter().write(result.toString());
   }
@@ -143,15 +144,15 @@
       result.put("error", getErrorJson(responseItem));
     } else {
       Object response = responseItem.getResponse();
-      JSONObject converted = (JSONObject) jsonConverter.convertToJson(response);
+      Object converted = jsonConverter.convertToJson(response);
 
       if (response instanceof RestfulCollection) {
         // FIXME this is a little hacky because of the field names in the RestfulCollection
-        converted.put("list", converted.remove("entry"));
+        ((JSONObject)converted).put("list", ((JSONObject)converted).remove("entry"));
         result.put("data", converted);
       } else if (response instanceof DataCollection) {
-        if (converted.has("entry")) {
-          result.put("data", converted.get("entry"));
+        if (((JSONObject)converted).has("entry")) {
+          result.put("data", ((JSONObject)converted).get("entry"));
         }
       } else {
         result.put("data", converted);

Added: incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/Operation.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/Operation.java?rev=740067&view=auto
==============================================================================
--- incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/Operation.java (added)
+++ incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/Operation.java Mon Feb  2 18:39:12 2009
@@ -0,0 +1,51 @@
+/*
+ * 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 org.apache.shindig.social.opensocial.service;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotates a method on a ServiceHandler which expose a REST/RPC operation
+ * The name of the annotated method is the literal name of the method for JSON-RPC
+ *
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface Operation {
+  /**
+   * The HTTP methods to bind this operation to.
+   */
+  String[] httpMethods();
+
+  /**
+   * The parameter name to bind the body content to in the RequestItem
+   * passed to the REST/RPC handler.
+   */
+  String bodyParam() default "body";
+
+  /**
+   * The path to match for the operation to override the service
+   * path matching and parameter binding. This is useful for situations
+   * such as /<service>/@supportedFields where the path determines the
+   * operation rather than the HTTP method in REST
+   */
+  String path() default "";
+}

Modified: incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/PersonHandler.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/PersonHandler.java?rev=740067&r1=740066&r2=740067&view=diff
==============================================================================
--- incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/PersonHandler.java (original)
+++ incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/PersonHandler.java Mon Feb  2 18:39:12 2009
@@ -17,7 +17,7 @@
  */
 package org.apache.shindig.social.opensocial.service;
 
-import org.apache.shindig.social.ResponseError;
+import org.apache.shindig.config.ContainerConfig;
 import org.apache.shindig.social.opensocial.model.Person;
 import org.apache.shindig.social.opensocial.spi.CollectionOptions;
 import org.apache.shindig.social.opensocial.spi.GroupId;
@@ -25,35 +25,26 @@
 import org.apache.shindig.social.opensocial.spi.SocialSpiException;
 import org.apache.shindig.social.opensocial.spi.UserId;
 
-import com.google.common.collect.Sets;
+import com.google.common.base.Objects;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
 import com.google.inject.Inject;
+
+import java.util.List;
 import java.util.Set;
 import java.util.concurrent.Future;
+import java.util.logging.Logger;
 
-public class PersonHandler extends DataRequestHandler {
+@Service(name = "people", path = "/{userId}+/{groupId}/{personId}+")
+public class PersonHandler {
+  private final static Logger logger = Logger.getLogger(PersonHandler.class.getName());
   private final PersonService personService;
-
-  private static final String PEOPLE_PATH = "/people/{userId}+/{groupId}/{personId}+";
+  private final ContainerConfig config;
 
   @Inject
-  public PersonHandler(PersonService personService) {
+  public PersonHandler(PersonService personService, ContainerConfig config) {
     this.personService = personService;
-  }
-
-  @Override
-  protected Future<?> handleDelete(RequestItem request) throws SocialSpiException {
-    throw new SocialSpiException(ResponseError.BAD_REQUEST, "You can't delete people.");
-  }
-
-  @Override
-  protected Future<?> handlePut(RequestItem request) throws SocialSpiException {
-    throw new SocialSpiException(ResponseError.NOT_IMPLEMENTED, "You can't update right now.");
-  }
-
-  @Override
-  protected Future<?> handlePost(RequestItem request) throws SocialSpiException {
-    throw new SocialSpiException(ResponseError.NOT_IMPLEMENTED, "You can't add people right now.");
+    this.config = config;
   }
 
   /**
@@ -61,16 +52,15 @@
    *
    * examples: /people/john.doe/@all /people/john.doe/@friends /people/john.doe/@self
    */
-  @Override
-  protected Future<?> handleGet(RequestItem request) throws SocialSpiException {
-    request.applyUrlTemplate(PEOPLE_PATH);
+  @Operation(httpMethods = "GET")
+  public Future<?> get(RequestItem request) throws SocialSpiException {
     GroupId groupId = request.getGroup();
     Set<String> optionalPersonId = ImmutableSet.copyOf(request.getListParameter("personId"));
     Set<String> fields = request.getFields(Person.Field.DEFAULT_FIELDS);
     Set<UserId> userIds = request.getUsers();
 
     // Preconditions
-    Preconditions.requireNotEmpty(userIds, "No userId specified");
+    HandlerPreconditions.requireNotEmpty(userIds, "No userId specified");
     if (userIds.size() > 1 && !optionalPersonId.isEmpty()) {
       throw new IllegalArgumentException("Cannot fetch personIds for multiple userIds");
     }
@@ -103,4 +93,12 @@
     // Every other case is a collection response.
     return personService.getPeople(userIds, groupId, options, fields, request.getToken());
   }
+
+  @Operation(httpMethods = "GET", path="/@supportedFields")
+  public List supportedFields(RequestItem request) {
+    // TODO: Would be nice if name in config matched name of service.
+    String container = Objects.firstNonNull(request.getToken().getContainer(), "default");
+    return config.getList(container,
+        "${gadgets\\.features.opensocial-0\\.8.supportedFields.person}");
+  }
 }

Modified: incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/RequestItem.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/RequestItem.java?rev=740067&r1=740066&r2=740067&view=diff
==============================================================================
--- incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/RequestItem.java (original)
+++ incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/RequestItem.java Mon Feb  2 18:39:12 2009
@@ -18,205 +18,68 @@
 package org.apache.shindig.social.opensocial.service;
 
 import org.apache.shindig.auth.SecurityToken;
-import org.apache.shindig.social.ResponseError;
 import org.apache.shindig.social.opensocial.spi.GroupId;
 import org.apache.shindig.social.opensocial.spi.PersonService;
-import org.apache.shindig.social.opensocial.spi.SocialSpiException;
 import org.apache.shindig.social.opensocial.spi.UserId;
 
-import org.joda.time.DateTime;
-
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-
-import java.util.Collections;
 import java.util.Date;
 import java.util.List;
 import java.util.Set;
 
 /**
- * Abstract base type for social API requests.
+ * A request to pass to a bound service handler
  */
-public abstract class RequestItem {
+public interface RequestItem {
 
   // Common OpenSocial API fields
-  public static final String APP_ID = "appId";
+  String APP_ID = "appId";
+  String USER_ID = "userId";
+  String GROUP_ID = "groupId";
+  String START_INDEX = "startIndex";
+  String COUNT = "count";
+  String SORT_BY = "sortBy";
+  String SORT_ORDER = "sortOrder";
+  String FILTER_BY = "filterBy";
+  String FILTER_OPERATION = "filterOp";
+  String FILTER_VALUE = "filterValue";
+  String FIELDS = "fields";// Opensocial defaults
+  int DEFAULT_START_INDEX = 0;
+  int DEFAULT_COUNT = 20;
+  String APP_SUBSTITUTION_TOKEN = "@app";
+
+  String getAppId();
+
+  Date getUpdatedSince();
+
+  Set<UserId> getUsers();
+
+  GroupId getGroup();
+
+  int getStartIndex();
+
+  int getCount();
+
+  String getSortBy();
+
+  PersonService.SortOrder getSortOrder();
+
+  String getFilterBy();
+
+  PersonService.FilterOperation getFilterOperation();
 
-  public static final String USER_ID = "userId";
+  String getFilterValue();
 
-  public static final String GROUP_ID = "groupId";
+  Set<String> getFields();
 
-  public static final String START_INDEX = "startIndex";
+  Set<String> getFields(Set<String> defaultValue);
 
-  public static final String COUNT = "count";
-
-  public static final String SORT_BY = "sortBy";
-  public static final String SORT_ORDER = "sortOrder";
-
-  public static final String FILTER_BY = "filterBy";
-  public static final String FILTER_OPERATION = "filterOp";
-  public static final String FILTER_VALUE = "filterValue";
-
-  public static final String FIELDS = "fields";
-
-  // Opensocial defaults
-  public static final int DEFAULT_START_INDEX = 0;
-
-  public static final int DEFAULT_COUNT = 20;
-
-  public static final String APP_SUBSTITUTION_TOKEN = "@app";
-
-  private final SecurityToken token;
-
-  protected final BeanConverter converter;
-
-  private final String operation;
-
-  private final String service;
-
-  public RequestItem(String service, String operation, SecurityToken token,
-      BeanConverter converter) {
-    this.service = service;
-    this.operation = operation;
-    this.token = token;
-    this.converter = converter;
-  }
-
-  public String getAppId() {
-    String appId = getParameter(APP_ID);
-    if (appId != null && appId.equals(APP_SUBSTITUTION_TOKEN)) {
-      return token.getAppId();
-    } else {
-      return appId;
-    }
-  }
-
-  public Date getUpdatedSince() {
-    String updatedSince = getParameter("updatedSince");
-    if (updatedSince == null)
-      return null;
-
-    DateTime date = new DateTime(updatedSince);
-    if (date == null) return null;
-
-    return date.toDate();
-  }
-
-  public Set<UserId> getUsers() {
-    List<String> ids = getListParameter(USER_ID);
-    if (ids.isEmpty()) {
-      if (token.getViewerId() != null) {
-        // Assume @me
-        ids = Lists.newArrayList("@me");
-      } else {
-        throw new IllegalArgumentException("No userId provided and viewer not available");
-      }
-    }
-    Set<UserId> userIds = Sets.newLinkedHashSet();
-    for (String id : ids) {
-      userIds.add(UserId.fromJson(id));
-    }
-    return userIds;
-  }
-
-
-  public GroupId getGroup() {
-    return GroupId.fromJson(getParameter(GROUP_ID, "@self"));
-  }
-
-  public int getStartIndex() {
-    String startIndex = getParameter(START_INDEX);
-    try {
-      return startIndex == null ? DEFAULT_START_INDEX
-          : Integer.valueOf(startIndex);
-    } catch (NumberFormatException nfe) {
-      throw new SocialSpiException(ResponseError.BAD_REQUEST,
-          "Parameter " + START_INDEX + " (" + startIndex + ") is not a number.");
-    }
-  }
-
-  public int getCount() {
-    String count = getParameter(COUNT);
-    try {
-      return count == null ? DEFAULT_COUNT : Integer.valueOf(count);
-    } catch (NumberFormatException nfe) {
-      throw new SocialSpiException(ResponseError.BAD_REQUEST,
-           "Parameter " + COUNT + " (" + count + ") is not a number.");
-    }
-  }
-
-  public String getSortBy() {
-    String sortBy = getParameter(SORT_BY);
-    return sortBy == null ? PersonService.TOP_FRIENDS_SORT : sortBy;
-  }
-
-  public PersonService.SortOrder getSortOrder() {
-    String sortOrder = getParameter(SORT_ORDER);
-    try {
-      return sortOrder == null
-            ? PersonService.SortOrder.ascending
-            : PersonService.SortOrder.valueOf(sortOrder);
-    } catch (IllegalArgumentException iae) {
-      throw new SocialSpiException(ResponseError.BAD_REQUEST,
-           "Parameter " + SORT_ORDER + " (" + sortOrder + ") is not valid.");
-    }
-  }
-
-  public String getFilterBy() {
-    return getParameter(FILTER_BY);
-  }
-
-  public PersonService.FilterOperation getFilterOperation() {
-    String filterOp = getParameter(FILTER_OPERATION);
-    try {
-      return filterOp == null
-          ? PersonService.FilterOperation.contains
-          : PersonService.FilterOperation.valueOf(filterOp);
-    } catch (IllegalArgumentException iae) {
-      throw new SocialSpiException(ResponseError.BAD_REQUEST,
-           "Parameter " + FILTER_OPERATION + " (" + filterOp + ") is not valid.");
-    }
-  }
-
-  public String getFilterValue() {
-    String filterValue = getParameter(FILTER_VALUE);
-    return filterValue == null ? "" : filterValue;
-  }
-
-  public Set<String> getFields() {
-    return getFields(Collections.<String>emptySet());
-  }
-
-  public Set<String> getFields(Set<String> defaultValue) {
-    Set<String> result = ImmutableSet.copyOf(getListParameter(FIELDS));
-    if (result.isEmpty()) {
-      return defaultValue;
-    }
-    return result;
-  }
-
-  public String getOperation() {
-    return operation;
-  }
-
-  public String getService() {
-    return service;
-  }
-
-  public SecurityToken getToken() {
-    return token;
-  }
-
-  public abstract <T> T getTypedParameter(String parameterName, Class<T> dataTypeClass);
-
-  public abstract <T> T getTypedParameters(Class<T> dataTypeClass);
+  SecurityToken getToken();
 
-  public abstract void applyUrlTemplate(String urlTemplate);
+  <T> T getTypedParameter(String parameterName, Class<T> dataTypeClass);
 
-  public abstract String getParameter(String paramName);
+  String getParameter(String paramName);
 
-  public abstract String getParameter(String paramName, String defaultValue);
+  String getParameter(String paramName, String defaultValue);
 
-  public abstract List<String> getListParameter(String paramName);
+  List<String> getListParameter(String paramName);
 }

Added: incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/RestHandler.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/RestHandler.java?rev=740067&view=auto
==============================================================================
--- incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/RestHandler.java (added)
+++ incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/RestHandler.java Mon Feb  2 18:39:12 2009
@@ -0,0 +1,38 @@
+/*
+ * 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 org.apache.shindig.social.opensocial.service;
+
+import org.apache.shindig.auth.SecurityToken;
+
+import java.io.Reader;
+import java.util.Map;
+import java.util.concurrent.Future;
+
+/**
+ * Interface exposed by a REST handler
+ */
+public interface RestHandler {
+
+  /**
+   * Handle the request and return a Future from which the response object
+   * can be retrieved
+   */
+  Future<?> execute(String path, Map<String, String[]> parameters, Reader body,
+                    SecurityToken token, BeanConverter converter);
+}

Added: incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/RpcHandler.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/RpcHandler.java?rev=740067&view=auto
==============================================================================
--- incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/RpcHandler.java (added)
+++ incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/RpcHandler.java Mon Feb  2 18:39:12 2009
@@ -0,0 +1,37 @@
+/*
+ * 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 org.apache.shindig.social.opensocial.service;
+
+import org.apache.shindig.auth.SecurityToken;
+
+import org.json.JSONObject;
+
+import java.util.concurrent.Future;
+
+/**
+ * Interface exposed by an RPC handler
+ */
+public interface RpcHandler {
+
+  /**
+   * Handle the request and return a Future from which the response object
+   * can be retrieved
+   */
+  Future<?> execute(JSONObject rpc, SecurityToken st, BeanConverter converter);
+}

Added: incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/Service.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/Service.java?rev=740067&view=auto
==============================================================================
--- incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/Service.java (added)
+++ incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/Service.java Mon Feb  2 18:39:12 2009
@@ -0,0 +1,57 @@
+/*
+ * 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 org.apache.shindig.social.opensocial.service;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates the base path for REST calls or the RPC service name a RequestHandler
+ * can dispatch to. Define parameter binding for REST path variables
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface Service {
+  /**
+   * The name of the service this handler exports. This is also the name of the
+   * root path element of the REST endpoint. E.g. The "activities" service
+   * consumes all paths under /activities/...
+   */
+  String name();
+
+  /**
+   * The structure of the REST paths used to address this service. Paths
+   * can contain placeholders delimited by {...} that bind a named parameter or
+   * set of parameters to the request. A plural parameter is denoted by appending '+'
+   * after the named parameter. Parameters are bound in order left-to-right and
+   * missing path segments are bound to null or empty sets
+   *
+   * E.g.
+   *
+   * /{userId}+/{group}/{personId}+ will parameterize the following URLs
+   * /1/@self            => { userId : [1], group : @self, personId : []}
+   * /1/@self            => { userId : [1], group : @self, personId : []}
+   * /1,2/@friends       => { userId : [1,2], group : @friends, personId : []}
+   * /1,2/@friends/2,3   => { userId : [1,2], group : @friends, personId : [2,3]}
+   *
+   */
+  String path() default "";
+}

Added: incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/SystemHandler.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/SystemHandler.java?rev=740067&view=auto
==============================================================================
--- incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/SystemHandler.java (added)
+++ incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/SystemHandler.java Mon Feb  2 18:39:12 2009
@@ -0,0 +1,50 @@
+/*
+ * 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 org.apache.shindig.social.opensocial.service;
+
+import org.apache.shindig.social.opensocial.spi.PersonService;
+
+import com.google.inject.Inject;
+
+import java.util.Set;
+
+
+/**
+ * Implements the 'system' service operations for JSON/XML RPC
+ */
+@Service(name = "system")
+public class SystemHandler {
+
+  HandlerRegistry registry;
+
+  @Inject
+  public SystemHandler(HandlerRegistry registry) {
+    this.registry = registry;
+  }
+
+  @Operation(httpMethods = "GET")
+  public Set<String> listMethods(RequestItem request) {
+    if ("protocol".equalsIgnoreCase(request.getFilterBy()) &&
+        PersonService.FilterOperation.equals == request.getFilterOperation() &&
+      "REST".equalsIgnoreCase(request.getFilterValue())) {
+      return registry.getSupportedRestServices();
+    }
+    return registry.getSupportedRpcServices();
+  }
+}

Modified: incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/sample/service/SampleContainerHandler.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/sample/service/SampleContainerHandler.java?rev=740067&r1=740066&r2=740067&view=diff
==============================================================================
--- incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/sample/service/SampleContainerHandler.java (original)
+++ incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/sample/service/SampleContainerHandler.java Mon Feb  2 18:39:12 2009
@@ -18,57 +18,48 @@
 
 package org.apache.shindig.social.sample.service;
 
-import com.google.inject.Inject;
-
 import org.apache.commons.httpclient.HttpClient;
 import org.apache.commons.httpclient.HttpMethod;
 import org.apache.commons.httpclient.methods.GetMethod;
 import org.apache.shindig.common.util.ImmediateFuture;
 import org.apache.shindig.social.ResponseError;
-import org.apache.shindig.social.opensocial.service.DataRequestHandler;
+import org.apache.shindig.social.opensocial.service.Operation;
 import org.apache.shindig.social.opensocial.service.RequestItem;
+import org.apache.shindig.social.opensocial.service.Service;
 import org.apache.shindig.social.opensocial.spi.SocialSpiException;
 import org.apache.shindig.social.sample.spi.JsonDbOpensocialService;
+
+import com.google.inject.Inject;
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import java.io.IOException;
 import java.util.concurrent.Future;
 
-public class SampleContainerHandler extends DataRequestHandler {
+@Service(name = "samplecontainer", path = "/samplecontainer/{type}/{doevil}")
+public class SampleContainerHandler {
 
   private final JsonDbOpensocialService service;
 
-  private static final String POST_PATH = "/samplecontainer/{type}/{doevil}";
-
   @Inject
   public SampleContainerHandler(JsonDbOpensocialService dbService) {
     this.service = dbService;
   }
 
   /**
-   * We don't support any delete methods right now.
-   */
-  @Override
-  protected Future<?> handleDelete(RequestItem request) throws SocialSpiException {
-    throw new SocialSpiException(ResponseError.NOT_IMPLEMENTED, null);
-  }
-
-  /**
    * We don't distinguish between put and post for these urls.
    */
-  @Override
-  protected Future<?> handlePut(RequestItem request) throws SocialSpiException {
-    return handlePost(request);
+  @Operation(httpMethods = "PUT")
+  public Future<?> update(RequestItem request) throws SocialSpiException {
+    return create(request);
   }
 
   /**
    * Handles /samplecontainer/setstate and /samplecontainer/setevilness/{doevil}. TODO(doll): These
    * urls aren't very resty. Consider changing the samplecontainer.html calls post.
    */
-  @Override
-  protected Future<?> handlePost(RequestItem request) throws SocialSpiException {
-    request.applyUrlTemplate(POST_PATH);
+  @Operation(httpMethods = "POST")
+  public Future<?> create(RequestItem request) throws SocialSpiException {
     String type = request.getParameter("type");
     if (type.equals("setstate")) {
       try {
@@ -89,8 +80,8 @@
   /**
    * Handles /samplecontainer/dumpstate
    */
-  @Override
-  protected Future<?> handleGet(RequestItem request) {
+  @Operation(httpMethods = "GET")
+  public Future<?> get(RequestItem request) {
     return ImmediateFuture.newInstance(service.getDb());
   }