You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@shindig.apache.org by Cassie <do...@apache.org> on 2008/06/18 21:43:56 UTC

Re: svn commit: r669268 - in /incubator/shindig/trunk: features/opensocial-current/ java/social-api/src/main/java/org/apache/shindig/social/dataservice/ java/social-api/src/test/java/org/apache/shindig/social/dataservice/ java/social-api/src/test/jav

Hey Chris and other php guys - I just wanted to alert you to the json
batching stuff I wrote for the java restful server. This batching isn't in
the spec (the spec has some crazy multi part thing that is super atom pub
specific) so at this point it is just an extension so that the opensocial js
apis can work better (because not batching is a regression).

Lemme know what you think of the format - its pretty similar to what we had
for the old json wire format. Once we clean this up we can propose it to the
restful spec list as an alternative to the atomy batching.

All input welcome!

- Cassie


On Wed, Jun 18, 2008 at 12:39 PM, <do...@apache.org> wrote:

> Author: doll
> Date: Wed Jun 18 12:39:54 2008
> New Revision: 669268
>
> URL: http://svn.apache.org/viewvc?rev=669268&view=rev
> Log:
> The dataservice rest impl now supports a jsonBatch format. This batch
> format can now be optionally turned on in the restfulcontainer.js code.
>
> (As soon as php implements the batching stuff then we can remove the flag
> and always use batching)
>
>
>
> Added:
>
>  incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/dataservice/RequestItemTest.java
>
>  incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/dataservice/integration/RestfulBatchTest.java
> Modified:
>    incubator/shindig/trunk/features/opensocial-current/restfulcontainer.js
>
>  incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/dataservice/DataServiceServlet.java
>
>  incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/dataservice/RequestItem.java
>
>  incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/dataservice/DataServiceServletTest.java
>
>  incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/dataservice/integration/RestfulJsonPeopleTest.java
>
> Modified:
> incubator/shindig/trunk/features/opensocial-current/restfulcontainer.js
> URL:
> http://svn.apache.org/viewvc/incubator/shindig/trunk/features/opensocial-current/restfulcontainer.js?rev=669268&r1=669267&r2=669268&view=diff
>
> ==============================================================================
> --- incubator/shindig/trunk/features/opensocial-current/restfulcontainer.js
> (original)
> +++ incubator/shindig/trunk/features/opensocial-current/restfulcontainer.js
> Wed Jun 18 12:39:54 2008
> @@ -38,6 +38,8 @@
>   this.baseUrl_ = baseUrl;
>
>   this.securityToken_ = shindig.auth.getSecurityToken();
> +
> +  this.useBatching_ = false;
>  };
>  RestfulContainer.inherits(opensocial.Container);
>
> @@ -94,7 +96,6 @@
>     var url = requestObject.request.url;
>     var separator = url.indexOf("?") != -1 ? "&" : "?";
>
> -    // TODO: Use batching instead of doing this one by one
>     gadgets.io.makeNonProxiedRequest(
>         baseUrl + url + separator + "st=" + st,
>         function(result) {
> @@ -115,8 +116,61 @@
>   }
>
>   // may need multiple urls for one response but lets ignore that for now
> -  for (var i = 0; i < totalRequests; i++) {
> -    makeProxiedRequest(requestObjects[i], this.baseUrl_,
> this.securityToken_);
> +  // TODO: Once both the php and java code decide on and implement
> batching
> +  // the non-batching code should be deleted
> +  if (!this.useBatching_) {
> +    for (var i = 0; i < totalRequests; i++) {
> +      makeProxiedRequest(requestObjects[i], this.baseUrl_,
> this.securityToken_);
> +    }
> +
> +  } else {
> +    var jsonBatchData = {};
> +
> +    for (var j = 0; j < totalRequests; j++) {
> +      var requestObject = requestObjects[j];
> +
> +      jsonBatchData[requestObject.key] = {url : requestObject.request.url,
> +        method : requestObject.request.method};
> +      if (requestObject.request.postData) {
> +        jsonBatchData[requestObject.key].parameters =
> requestObject.request.postData;
> +      }
> +    }
> +
> +    // This is slightly different than jsonContainer
> +    var sendResponse = function(result) {
> +      result = result.data;
> +
> +      if (!result || result['error']) {
> +        callback(new opensocial.DataResponse({}, true));
> +        return;
> +      }
> +
> +      var responses = result['responses'] || [];
> +      var globalError = false;
> +
> +      var responseMap = {};
> +
> +      for (var k = 0; k < requestObjects.length; k++) {
> +        var request = requestObjects[k];
> +        var response = responses[request.key];
> +
> +        var rawData = response['response'];
> +        var error = response['error'];
> +        var errorMessage = response['errorMessage'];
> +
> +        var processedData = request.request.processResponse(
> +            request.request, rawData, error, errorMessage);
> +        globalError = globalError || processedData.hadError();
> +        responseMap[request.key] = processedData;
> +      }
> +
> +      var dataResponse = new opensocial.DataResponse(responseMap,
> globalError);
> +      callback(dataResponse);
> +    };
> +
> +    // TODO: get the jsonbatch url from the container config
> +    new BatchRequest(this.baseUrl_ + "/jsonBatch",
> gadgets.json.stringify(jsonBatchData),
> +        sendResponse, {'st' : shindig.auth.getSecurityToken()}).send();
>   }
>
>  };
>
> Modified:
> incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/dataservice/DataServiceServlet.java
> URL:
> http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/dataservice/DataServiceServlet.java?rev=669268&r1=669267&r2=669268&view=diff
>
> ==============================================================================
> ---
> incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/dataservice/DataServiceServlet.java
> (original)
> +++
> incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/dataservice/DataServiceServlet.java
> Wed Jun 18 12:39:54 2008
> @@ -17,28 +17,31 @@
>  */
>  package org.apache.shindig.social.dataservice;
>
> -import com.google.inject.Inject;
> -import com.google.inject.Injector;
> -
> -import org.apache.shindig.common.SecurityTokenDecoder;
>  import org.apache.shindig.common.SecurityToken;
> +import org.apache.shindig.common.SecurityTokenDecoder;
>  import org.apache.shindig.common.SecurityTokenException;
>  import org.apache.shindig.common.servlet.InjectedServlet;
> +import org.apache.shindig.social.ResponseItem;
>  import org.apache.shindig.social.opensocial.util.BeanConverter;
>  import org.apache.shindig.social.opensocial.util.BeanJsonConverter;
>  import org.apache.shindig.social.opensocial.util.BeanXmlConverter;
> -import org.apache.shindig.social.ResponseItem;
> +
> +import com.google.common.collect.Maps;
> +import com.google.inject.Inject;
> +import com.google.inject.Injector;
> +import org.apache.commons.lang.StringUtils;
> +import org.json.JSONException;
> +import org.json.JSONObject;
>
>  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.Iterator;
>  import java.util.Map;
>  import java.util.logging.Logger;
>
> -import org.apache.commons.lang.StringUtils;
> -
>  public class DataServiceServlet extends InjectedServlet {
>
>   protected static final String X_HTTP_METHOD_OVERRIDE =
> "X-HTTP-Method-Override";
> @@ -58,6 +61,7 @@
>   private Map<String, Class<? extends DataRequestHandler>> handlers;
>   private BeanJsonConverter jsonConverter;
>   private BeanXmlConverter xmlConverter;
> +  private static final String JSON_BATCH_ROUTE = "jsonBatch";
>
>   @Inject
>   public void setHandlers(HandlerProvider handlers) {
> @@ -65,8 +69,7 @@
>   }
>
>   @Inject
> -  public void setSecurityTokenDecoder(SecurityTokenDecoder
> -      securityTokenDecoder) {
> +  public void setSecurityTokenDecoder(SecurityTokenDecoder
> securityTokenDecoder) {
>     this.securityTokenDecoder = securityTokenDecoder;
>   }
>
> @@ -102,12 +105,67 @@
>   protected void doPost(HttpServletRequest servletRequest,
>       HttpServletResponse servletResponse)
>       throws ServletException, IOException {
> -    String path = servletRequest.getPathInfo();
> -    logger.finest("Handling restful request for " + path);
> +    logger.finest("Handling restful request for " +
> servletRequest.getPathInfo());
>
>     servletRequest.setCharacterEncoding("UTF-8");
> +    SecurityToken token = getSecurityToken(servletRequest);
> +    BeanConverter converter = getConverterForRequest(servletRequest);
>
> -    String route = getRouteFromParameter(path);
> +    if (isBatchUrl(servletRequest)) {
> +      try {
> +        handleBatchRequest(servletRequest, servletResponse, token,
> converter);
> +      } catch (JSONException e) {
> +        throw new RuntimeException("Bad batch format", e);
> +      }
> +    } else {
> +      handleSingleRequest(servletRequest, servletResponse, token,
> converter);
> +    }
> +  }
> +
> +  private void handleSingleRequest(HttpServletRequest servletRequest,
> +      HttpServletResponse servletResponse, SecurityToken token,
> +      BeanConverter converter) throws IOException {
> +    String method = getHttpMethodFromParameter(servletRequest.getMethod(),
> +        servletRequest.getParameter(X_HTTP_METHOD_OVERRIDE));
> +
> +    RequestItem requestItem = new RequestItem(servletRequest, token,
> method);
> +    ResponseItem responseItem = getResponseItem(converter, requestItem);
> +
> +    if (responseItem.getError() == null) {
> +      PrintWriter writer = servletResponse.getWriter();
> +      writer.write(converter.convertToString(responseItem.getResponse()));
> +    } else {
> +
>  servletResponse.sendError(responseItem.getError().getHttpErrorCode(),
> +          responseItem.getErrorMessage());
> +    }
> +  }
> +
> +  private void handleBatchRequest(HttpServletRequest servletRequest,
> +      HttpServletResponse servletResponse, SecurityToken token,
> +      BeanConverter converter) throws IOException, JSONException {
> +
> +    JSONObject requests = new
> JSONObject(servletRequest.getParameter("request"));
> +    Map<String, ResponseItem> responses = Maps.newHashMap();
> +
> +    Iterator keys = requests.keys();
> +    while (keys.hasNext()) {
> +      String key = (String) keys.next();
> +      String request = requests.getString(key);
> +
> +      RequestItem requestItem = converter.convertToObject(request,
> RequestItem.class);
> +      requestItem.parseUrlParamsIntoParameters();
> +      requestItem.setToken(token);
> +
> +      responses.put(key, getResponseItem(converter, requestItem));
> +    }
> +
> +    PrintWriter writer = servletResponse.getWriter();
> +    writer.write(converter.convertToString(
> +        Maps.immutableMap("error", false, "responses", responses)));
> +  }
> +
> +  ResponseItem getResponseItem(BeanConverter converter, RequestItem
> requestItem) {
> +    String route = getRouteFromParameter(requestItem.getUrl());
>     Class<? extends DataRequestHandler> handlerClass = handlers.get(route);
>
>     if (handlerClass == null) {
> @@ -115,13 +173,12 @@
>     }
>
>     DataRequestHandler handler = injector.getInstance(handlerClass);
> -    BeanConverter converter = getConverterForRequest(servletRequest);
> -    // TODO: Move all conversions out of the handler up into the servlet
> layer
>     handler.setConverter(converter);
>
> -    String method = getHttpMethodFromParameter(servletRequest.getMethod(),
> -        servletRequest.getParameter(X_HTTP_METHOD_OVERRIDE));
> +    return handler.handleMethod(requestItem);
> +  }
>
> +  SecurityToken getSecurityToken(HttpServletRequest servletRequest) {
>     SecurityToken token;
>     try {
>       token =
> securityTokenDecoder.createToken(servletRequest.getParameter(SECURITY_TOKEN_PARAM));
> @@ -129,20 +186,10 @@
>       throw new RuntimeException(
>           "Implement error return for bad security token.");
>     }
> -
> -    ResponseItem responseItem = handler.handleMethod(
> -        new RequestItem(servletRequest, token, method));
> -
> -    if (responseItem.getError() == null) {
> -      PrintWriter writer = servletResponse.getWriter();
> -      writer.write(converter.convertToString(responseItem.getResponse()));
> -    } else {
> -
>  servletResponse.sendError(responseItem.getError().getHttpErrorCode(),
> -          responseItem.getErrorMessage());
> -    }
> +    return token;
>   }
>
> -  /*package-protected*/ BeanConverter
> getConverterForRequest(HttpServletRequest servletRequest) {
> +  BeanConverter getConverterForRequest(HttpServletRequest servletRequest)
> {
>     String formatString = servletRequest.getParameter(FORMAT_PARAM);
>     if (!StringUtils.isBlank(formatString) &&
> formatString.equals(ATOM_FORMAT)) {
>       return xmlConverter;
> @@ -150,8 +197,7 @@
>     return jsonConverter;
>   }
>
> -  /*package-protected*/ String getHttpMethodFromParameter(String method,
> -      String overrideParameter) {
> +  String getHttpMethodFromParameter(String method, String
> overrideParameter) {
>     if (!StringUtils.isBlank(overrideParameter)) {
>       return overrideParameter;
>     } else {
> @@ -159,11 +205,15 @@
>     }
>   }
>
> -  /*package-protected*/ String getRouteFromParameter(String pathInfo) {
> +  String getRouteFromParameter(String pathInfo) {
>     pathInfo = pathInfo.substring(1);
>     int indexOfNextPathSeparator = pathInfo.indexOf("/");
>     return indexOfNextPathSeparator != -1 ?
>         pathInfo.substring(0, indexOfNextPathSeparator) :
>         pathInfo;
>   }
> +
> +  boolean isBatchUrl(HttpServletRequest servletRequest) {
> +    return servletRequest.getPathInfo().endsWith(JSON_BATCH_ROUTE);
> +  }
>  }
>
> Modified:
> incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/dataservice/RequestItem.java
> URL:
> http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/dataservice/RequestItem.java?rev=669268&r1=669267&r2=669268&view=diff
>
> ==============================================================================
> ---
> incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/dataservice/RequestItem.java
> (original)
> +++
> incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/dataservice/RequestItem.java
> Wed Jun 18 12:39:54 2008
> @@ -34,6 +34,8 @@
>   private Map<String, String> parameters;
>   private SecurityToken token;
>
> +  public RequestItem() { }
> +
>   public RequestItem(String url, Map<String, String> parameters,
> SecurityToken token,
>       String method) {
>     this.url = url;
> @@ -50,7 +52,7 @@
>     Map<String, String> parameters = Maps.newHashMap();
>
>     Enumeration names = servletRequest.getParameterNames();
> -    while(names.hasMoreElements()) {
> +    while (names.hasMoreElements()) {
>       String name = (String) names.nextElement();
>       parameters.put(name, servletRequest.getParameter(name));
>     }
> @@ -58,6 +60,30 @@
>     return parameters;
>   }
>
> +  /*
> +   * Takes any url params out of the url and puts them into the param map.
> +   * Usually the servlet request code does this for us but the batch
> request calls have to do it
> +   * by hand.
> +   */
> +  public void parseUrlParamsIntoParameters() {
> +    if (this.parameters == null) {
> +      this.parameters = Maps.newHashMap();
> +    }
> +
> +    String fullUrl = this.url;
> +    int queryParamIndex = fullUrl.indexOf("?");
> +
> +    if (queryParamIndex != -1) {
> +      this.url = fullUrl.substring(0, queryParamIndex);
> +
> +      String queryParams = fullUrl.substring(queryParamIndex + 1);
> +      for (String param : queryParams.split("&")) {
> +        String[] paramPieces = param.split("=", 2);
> +        this.parameters.put(paramPieces[0], paramPieces.length == 2 ?
> paramPieces[1] : "");
> +      }
> +    }
> +  }
> +
>   public String getUrl() {
>     return url;
>   }
>
> Modified:
> incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/dataservice/DataServiceServletTest.java
> URL:
> http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/dataservice/DataServiceServletTest.java?rev=669268&r1=669267&r2=669268&view=diff
>
> ==============================================================================
> ---
> incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/dataservice/DataServiceServletTest.java
> (original)
> +++
> incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/dataservice/DataServiceServletTest.java
> Wed Jun 18 12:39:54 2008
> @@ -147,37 +147,24 @@
>   }
>
>   public void testInvalidRoute() throws Exception {
> -    req.setCharacterEncoding("UTF-8");
> -    EasyMock.expect(req.getPathInfo()).andReturn("/ahhh!");
> -
> -    EasyMock.replay(req);
>     try {
> -      servlet.doPost(req, res);
> +      servlet.getResponseItem(null, new RequestItem("/ahhh!", null, null,
> null));
>       fail("The route should not have found a valid handler.");
>     } catch (RuntimeException e) {
>       // Yea!
>       assertEquals("No handler for route: ahhh!", e.getMessage());
>     }
> -    EasyMock.verify(req);
>   }
>
>   public void testSecurityTokenException() throws Exception {
> -    req.setCharacterEncoding("UTF-8");
> -    EasyMock.expect(req.getPathInfo()).andReturn("/" +
> DataServiceServlet.APPDATA_ROUTE);
> -    EasyMock.expect(req.getMethod()).andReturn("POST");
> -
>  EasyMock.expect(req.getParameter(DataServiceServlet.X_HTTP_METHOD_OVERRIDE)).andReturn("POST");
> -
>  EasyMock.expect(req.getParameter(DataServiceServlet.FORMAT_PARAM)).andReturn(null);
> -
>     String tokenString = "owner:viewer:app:container.com:foo:bar";
>
> EasyMock.expect(req.getParameter(DataServiceServlet.SECURITY_TOKEN_PARAM))
>         .andReturn(tokenString);
>     EasyMock.expect(tokenDecoder.createToken(tokenString)).andThrow(new
> SecurityTokenException(""));
>
> -    setupInjector();
> -
> -    EasyMock.replay(req, tokenDecoder, injector);
> +    EasyMock.replay(req, tokenDecoder);
>     try {
> -      servlet.doPost(req, res);
> +      servlet.getSecurityToken(req);
>       fail("The route should have thrown an exception due to the invalid
> security token.");
>     } catch (RuntimeException e) {
>       // Yea!
> @@ -185,7 +172,7 @@
>       // instead of just throwing an exception.
>       assertEquals("Implement error return for bad security token.",
> e.getMessage());
>     }
> -    EasyMock.verify(req, tokenDecoder, injector);
> +    EasyMock.verify(req, tokenDecoder);
>   }
>
>   public void testGetHttpMethodFromParameter() throws Exception {
> @@ -202,6 +189,20 @@
>     assertEquals("path", servlet.getRouteFromParameter("/path/fun/yes"));
>   }
>
> +  public void testIsBatchUrl() throws Exception {
> +    assertBatchUrl("/jsonBatch", true);
> +    assertBatchUrl("/path/to/the/jsonBatch", true);
> +    assertBatchUrl("/people/normalpath", false);
> +  }
> +
> +  private void assertBatchUrl(String url, boolean isBatch) {
> +    EasyMock.expect(req.getPathInfo()).andReturn(url);
> +    EasyMock.replay(req);
> +    assertEquals(isBatch, servlet.isBatchUrl(req));
> +    EasyMock.verify(req);
> +    EasyMock.reset(req);
> +  }
> +
>   public void testGetConverterForRequest() throws Exception {
>     BeanJsonConverter json = new BeanJsonConverter();
>     BeanXmlConverter xml = new BeanXmlConverter();
>
> Added:
> incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/dataservice/RequestItemTest.java
> URL:
> http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/dataservice/RequestItemTest.java?rev=669268&view=auto
>
> ==============================================================================
> ---
> incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/dataservice/RequestItemTest.java
> (added)
> +++
> incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/dataservice/RequestItemTest.java
> Wed Jun 18 12:39:54 2008
> @@ -0,0 +1,64 @@
> +/*
> + * 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.dataservice;
> +
> +import org.apache.shindig.common.SecurityToken;
> +
> +import junit.framework.TestCase;
> +
> +import java.util.Map;
> +
> +import com.google.common.collect.Maps;
> +
> +public class RequestItemTest extends TestCase {
> +
> +  public void testParseUrl() throws Exception {
> +    String path = "/people/john.doe/@self";
> +
> +    RequestItem request = new RequestItem();
> +    request.setUrl(path + "?fields=huey,dewey,louie");
> +
> +    request.parseUrlParamsIntoParameters();
> +
> +    assertEquals(path, request.getUrl());
> +    assertEquals("huey,dewey,louie",
> request.getParameters().get("fields"));
> +
> +    // Try it without any params
> +    request = new RequestItem();
> +    request.setUrl(path);
> +
> +    request.parseUrlParamsIntoParameters();
> +
> +    assertEquals(path, request.getUrl());
> +    assertEquals(null, request.getParameters().get("fields"));
> +  }
> +
> +  public void testBasicFunctions() throws Exception {
> +    String url = "url";
> +    Map<String, String> params = Maps.newHashMap();
> +    SecurityToken token = null;
> +    String method = "method";
> +    RequestItem request = new RequestItem(url, params, token, method);
> +
> +    assertEquals(url, request.getUrl());
> +    assertEquals(params, request.getParameters());
> +    assertEquals(token, request.getToken());
> +    assertEquals(method, request.getMethod());
> +  }
> +
> +}
> \ No newline at end of file
>
> Added:
> incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/dataservice/integration/RestfulBatchTest.java
> URL:
> http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/dataservice/integration/RestfulBatchTest.java?rev=669268&view=auto
>
> ==============================================================================
> ---
> incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/dataservice/integration/RestfulBatchTest.java
> (added)
> +++
> incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/dataservice/integration/RestfulBatchTest.java
> Wed Jun 18 12:39:54 2008
> @@ -0,0 +1,93 @@
> +/*
> + * Licensed to the Apache Software Foundation (ASF) under one or more
> + * contributor license agreements.  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.  For additional information regarding
> + * copyright in this work, please see the NOTICE file in the top level
> + * directory of this distribution.
> + */
> +package org.apache.shindig.social.dataservice.integration;
> +
> +import com.google.common.collect.Maps;
> +import org.json.JSONObject;
> +import org.junit.Test;
> +
> +import java.util.Map;
> +
> +public class RestfulBatchTest extends AbstractLargeRestfulTests {
> +
> +  /**
> +   * Batch format:
> +   *   POST to /jsonBatch
> +   *   {request :
> +   *     {friends : {url : /people/john.doe/@friends, method : GET}}
> +   *     {john : {url : /people/john.doe/@self, method : GET}}
> +   *     {updateData : {url : /appdata/john.doe/@self/appId, method :
> POST, postData : {count : 1}}}
> +   *   }
> +   *
> +   *
> +   * Expected response
> +   *
> +   *  {error : false,
> +   *   responses : {
> +   *     {friends : {response : {<friend collection>}}}
> +   *     {john : {response : {<john.doe>}}}
> +   *     {updateData : {response : {}}}
> +   *  }
> +   *
> +   * Each response can possibly have .error and .errorMessage properties
> as well.
> +   *
> +   * @throws Exception if test encounters an error
> +   */
> +  @Test
> +  public void testGetBatchRequest() throws Exception {
> +    Map<String, String> extraParams = Maps.newHashMap();
> +    extraParams.put("request", "{"
> +        + "friends : {url : '/people/john.doe/@friends', method : 'GET'},
> "
> +        + "john : {url : '/people/john.doe/@self', method : 'GET'}, "
> +        + "updateData : {url : '/appdata/john.doe/@self/a', method :
> 'POST', parameters : {entry : {count : 1}}}"
> +        + "}");
> +
> +    String resp = getJsonResponse("jsonBatch", "POST", extraParams);
> +    JSONObject result = getJson(resp);
> +
> +    assertEquals(false, result.getBoolean("error"));
> +
> +    JSONObject jsonResponses = result.getJSONObject("responses");
> +    assertEquals(3, jsonResponses.length());
> +
> +    // friends response
> +    JSONObject jsonFriends = jsonResponses.getJSONObject("friends");
> +    assertFalse(jsonFriends.has("error"));
> +    assertFalse(jsonFriends.has("errorMessage"));
> +
> +    JSONObject jsonFriendsResponse =
> jsonFriends.getJSONObject("response");
> +    assertEquals(2, jsonFriendsResponse.getInt("totalResults"));
> +    assertEquals(0, jsonFriendsResponse.getInt("startIndex"));
> +
> +    // john.doe response
> +    JSONObject jsonJohn = jsonResponses.getJSONObject("john");
> +    assertFalse(jsonJohn.has("error"));
> +    assertFalse(jsonJohn.has("errorMessage"));
> +
> +    JSONObject jsonJohnResponse = jsonJohn.getJSONObject("response");
> +    assertEquals("john.doe", jsonJohnResponse.getString("id"));
> +    assertEquals("John Doe",
> jsonJohnResponse.getJSONObject("name").getString("unstructured"));
> +
> +    // john.doe response
> +    JSONObject jsonData = jsonResponses.getJSONObject("updateData");
> +    assertFalse(jsonData.has("error"));
> +    assertFalse(jsonData.has("errorMessage"));
> +    assertTrue(jsonData.has("response"));
> +  }
> +
> +}
> \ No newline at end of file
>
> Modified:
> incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/dataservice/integration/RestfulJsonPeopleTest.java
> URL:
> http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/dataservice/integration/RestfulJsonPeopleTest.java?rev=669268&r1=669267&r2=669268&view=diff
>
> ==============================================================================
> ---
> incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/dataservice/integration/RestfulJsonPeopleTest.java
> (original)
> +++
> incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/dataservice/integration/RestfulJsonPeopleTest.java
> Wed Jun 18 12:39:54 2008
> @@ -29,7 +29,6 @@
>  import org.apache.shindig.social.opensocial.model.Url;
>
>  import com.google.common.collect.Maps;
> -import org.easymock.classextension.EasyMock;
>  import org.json.JSONArray;
>  import org.json.JSONException;
>  import org.json.JSONObject;
>
>
>

Re: svn commit: r669268 - in /incubator/shindig/trunk: features/opensocial-current/ java/social-api/src/main/java/org/apache/shindig/social/dataservice/ java/social-api/src/test/java/org/apache/shindig/social/dataservice/ java/social-api/src/test/jav

Posted by Cassie <do...@apache.org>.
On Wed, Jun 18, 2008 at 1:53 PM, Chris Chabot <ch...@xs4all.nl> wrote:

> Thanks for the heads up Cassie!
>
> I'm unavoidably tied up until mid friday, but i'll start banging on it as
> soon as possible. First impression is very good though, way of dealing with
> it looks sound and practical, and should be relatively trivial to add on the
> php side too.
>
> Good job!
>
> Ps, just a thought that occurred to me: Since the spec doesn't yet mention
> batching using the json format .. would we dare suggesting adding it to the
> restful api properly or do we hide it in the not-documented cellar and save
> the big coming out party for 0.9?


lol, dunno.
once we get both impls i figure we can throw it to the spec list and decide
through mass discussion over there :)


>
>
>        -- Chris
>
>
> On Jun 18, 2008, at 9:43 PM, Cassie wrote:
>
>  Hey Chris and other php guys - I just wanted to alert you to the json
>> batching stuff I wrote for the java restful server. This batching isn't in
>> the spec (the spec has some crazy multi part thing that is super atom pub
>> specific) so at this point it is just an extension so that the opensocial
>> js
>> apis can work better (because not batching is a regression).
>>
>> Lemme know what you think of the format - its pretty similar to what we
>> had
>> for the old json wire format. Once we clean this up we can propose it to
>> the
>> restful spec list as an alternative to the atomy batching.
>>
>> All input welcome!
>>
>> - Cassie
>>
>>
>> On Wed, Jun 18, 2008 at 12:39 PM, <do...@apache.org> wrote:
>>
>>  Author: doll
>>> Date: Wed Jun 18 12:39:54 2008
>>> New Revision: 669268
>>>
>>> URL: http://svn.apache.org/viewvc?rev=669268&view=rev
>>> Log:
>>> The dataservice rest impl now supports a jsonBatch format. This batch
>>> format can now be optionally turned on in the restfulcontainer.js code.
>>>
>>> (As soon as php implements the batching stuff then we can remove the flag
>>> and always use batching)
>>>
>>>
>>>
>>> Added:
>>>
>>>
>>> incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/dataservice/RequestItemTest.java
>>>
>>>
>>> incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/dataservice/integration/RestfulBatchTest.java
>>> Modified:
>>>  incubator/shindig/trunk/features/opensocial-current/restfulcontainer.js
>>>
>>>
>>> incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/dataservice/DataServiceServlet.java
>>>
>>>
>>> incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/dataservice/RequestItem.java
>>>
>>>
>>> incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/dataservice/DataServiceServletTest.java
>>>
>>>
>>> incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/dataservice/integration/RestfulJsonPeopleTest.java
>>>
>>> Modified:
>>> incubator/shindig/trunk/features/opensocial-current/restfulcontainer.js
>>> URL:
>>>
>>> http://svn.apache.org/viewvc/incubator/shindig/trunk/features/opensocial-current/restfulcontainer.js?rev=669268&r1=669267&r2=669268&view=diff
>>>
>>>
>>> ==============================================================================
>>> ---
>>> incubator/shindig/trunk/features/opensocial-current/restfulcontainer.js
>>> (original)
>>> +++
>>> incubator/shindig/trunk/features/opensocial-current/restfulcontainer.js
>>> Wed Jun 18 12:39:54 2008
>>> @@ -38,6 +38,8 @@
>>>  this.baseUrl_ = baseUrl;
>>>
>>>  this.securityToken_ = shindig.auth.getSecurityToken();
>>> +
>>> +  this.useBatching_ = false;
>>> };
>>> RestfulContainer.inherits(opensocial.Container);
>>>
>>> @@ -94,7 +96,6 @@
>>>   var url = requestObject.request.url;
>>>   var separator = url.indexOf("?") != -1 ? "&" : "?";
>>>
>>> -    // TODO: Use batching instead of doing this one by one
>>>   gadgets.io.makeNonProxiedRequest(
>>>       baseUrl + url + separator + "st=" + st,
>>>       function(result) {
>>> @@ -115,8 +116,61 @@
>>>  }
>>>
>>>  // may need multiple urls for one response but lets ignore that for now
>>> -  for (var i = 0; i < totalRequests; i++) {
>>> -    makeProxiedRequest(requestObjects[i], this.baseUrl_,
>>> this.securityToken_);
>>> +  // TODO: Once both the php and java code decide on and implement
>>> batching
>>> +  // the non-batching code should be deleted
>>> +  if (!this.useBatching_) {
>>> +    for (var i = 0; i < totalRequests; i++) {
>>> +      makeProxiedRequest(requestObjects[i], this.baseUrl_,
>>> this.securityToken_);
>>> +    }
>>> +
>>> +  } else {
>>> +    var jsonBatchData = {};
>>> +
>>> +    for (var j = 0; j < totalRequests; j++) {
>>> +      var requestObject = requestObjects[j];
>>> +
>>> +      jsonBatchData[requestObject.key] = {url :
>>> requestObject.request.url,
>>> +        method : requestObject.request.method};
>>> +      if (requestObject.request.postData) {
>>> +        jsonBatchData[requestObject.key].parameters =
>>> requestObject.request.postData;
>>> +      }
>>> +    }
>>> +
>>> +    // This is slightly different than jsonContainer
>>> +    var sendResponse = function(result) {
>>> +      result = result.data;
>>> +
>>> +      if (!result || result['error']) {
>>> +        callback(new opensocial.DataResponse({}, true));
>>> +        return;
>>> +      }
>>> +
>>> +      var responses = result['responses'] || [];
>>> +      var globalError = false;
>>> +
>>> +      var responseMap = {};
>>> +
>>> +      for (var k = 0; k < requestObjects.length; k++) {
>>> +        var request = requestObjects[k];
>>> +        var response = responses[request.key];
>>> +
>>> +        var rawData = response['response'];
>>> +        var error = response['error'];
>>> +        var errorMessage = response['errorMessage'];
>>> +
>>> +        var processedData = request.request.processResponse(
>>> +            request.request, rawData, error, errorMessage);
>>> +        globalError = globalError || processedData.hadError();
>>> +        responseMap[request.key] = processedData;
>>> +      }
>>> +
>>> +      var dataResponse = new opensocial.DataResponse(responseMap,
>>> globalError);
>>> +      callback(dataResponse);
>>> +    };
>>> +
>>> +    // TODO: get the jsonbatch url from the container config
>>> +    new BatchRequest(this.baseUrl_ + "/jsonBatch",
>>> gadgets.json.stringify(jsonBatchData),
>>> +        sendResponse, {'st' : shindig.auth.getSecurityToken()}).send();
>>>  }
>>>
>>> };
>>>
>>> Modified:
>>>
>>> incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/dataservice/DataServiceServlet.java
>>> URL:
>>>
>>> http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/dataservice/DataServiceServlet.java?rev=669268&r1=669267&r2=669268&view=diff
>>>
>>>
>>> ==============================================================================
>>> ---
>>>
>>> incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/dataservice/DataServiceServlet.java
>>> (original)
>>> +++
>>>
>>> incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/dataservice/DataServiceServlet.java
>>> Wed Jun 18 12:39:54 2008
>>> @@ -17,28 +17,31 @@
>>> */
>>> package org.apache.shindig.social.dataservice;
>>>
>>> -import com.google.inject.Inject;
>>> -import com.google.inject.Injector;
>>> -
>>> -import org.apache.shindig.common.SecurityTokenDecoder;
>>> import org.apache.shindig.common.SecurityToken;
>>> +import org.apache.shindig.common.SecurityTokenDecoder;
>>> import org.apache.shindig.common.SecurityTokenException;
>>> import org.apache.shindig.common.servlet.InjectedServlet;
>>> +import org.apache.shindig.social.ResponseItem;
>>> import org.apache.shindig.social.opensocial.util.BeanConverter;
>>> import org.apache.shindig.social.opensocial.util.BeanJsonConverter;
>>> import org.apache.shindig.social.opensocial.util.BeanXmlConverter;
>>> -import org.apache.shindig.social.ResponseItem;
>>> +
>>> +import com.google.common.collect.Maps;
>>> +import com.google.inject.Inject;
>>> +import com.google.inject.Injector;
>>> +import org.apache.commons.lang.StringUtils;
>>> +import org.json.JSONException;
>>> +import org.json.JSONObject;
>>>
>>> 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.Iterator;
>>> import java.util.Map;
>>> import java.util.logging.Logger;
>>>
>>> -import org.apache.commons.lang.StringUtils;
>>> -
>>> public class DataServiceServlet extends InjectedServlet {
>>>
>>>  protected static final String X_HTTP_METHOD_OVERRIDE =
>>> "X-HTTP-Method-Override";
>>> @@ -58,6 +61,7 @@
>>>  private Map<String, Class<? extends DataRequestHandler>> handlers;
>>>  private BeanJsonConverter jsonConverter;
>>>  private BeanXmlConverter xmlConverter;
>>> +  private static final String JSON_BATCH_ROUTE = "jsonBatch";
>>>
>>>  @Inject
>>>  public void setHandlers(HandlerProvider handlers) {
>>> @@ -65,8 +69,7 @@
>>>  }
>>>
>>>  @Inject
>>> -  public void setSecurityTokenDecoder(SecurityTokenDecoder
>>> -      securityTokenDecoder) {
>>> +  public void setSecurityTokenDecoder(SecurityTokenDecoder
>>> securityTokenDecoder) {
>>>   this.securityTokenDecoder = securityTokenDecoder;
>>>  }
>>>
>>> @@ -102,12 +105,67 @@
>>>  protected void doPost(HttpServletRequest servletRequest,
>>>     HttpServletResponse servletResponse)
>>>     throws ServletException, IOException {
>>> -    String path = servletRequest.getPathInfo();
>>> -    logger.finest("Handling restful request for " + path);
>>> +    logger.finest("Handling restful request for " +
>>> servletRequest.getPathInfo());
>>>
>>>   servletRequest.setCharacterEncoding("UTF-8");
>>> +    SecurityToken token = getSecurityToken(servletRequest);
>>> +    BeanConverter converter = getConverterForRequest(servletRequest);
>>>
>>> -    String route = getRouteFromParameter(path);
>>> +    if (isBatchUrl(servletRequest)) {
>>> +      try {
>>> +        handleBatchRequest(servletRequest, servletResponse, token,
>>> converter);
>>> +      } catch (JSONException e) {
>>> +        throw new RuntimeException("Bad batch format", e);
>>> +      }
>>> +    } else {
>>> +      handleSingleRequest(servletRequest, servletResponse, token,
>>> converter);
>>> +    }
>>> +  }
>>> +
>>> +  private void handleSingleRequest(HttpServletRequest servletRequest,
>>> +      HttpServletResponse servletResponse, SecurityToken token,
>>> +      BeanConverter converter) throws IOException {
>>> +    String method =
>>> getHttpMethodFromParameter(servletRequest.getMethod(),
>>> +        servletRequest.getParameter(X_HTTP_METHOD_OVERRIDE));
>>> +
>>> +    RequestItem requestItem = new RequestItem(servletRequest, token,
>>> method);
>>> +    ResponseItem responseItem = getResponseItem(converter, requestItem);
>>> +
>>> +    if (responseItem.getError() == null) {
>>> +      PrintWriter writer = servletResponse.getWriter();
>>> +
>>>  writer.write(converter.convertToString(responseItem.getResponse()));
>>> +    } else {
>>> +
>>> servletResponse.sendError(responseItem.getError().getHttpErrorCode(),
>>> +          responseItem.getErrorMessage());
>>> +    }
>>> +  }
>>> +
>>> +  private void handleBatchRequest(HttpServletRequest servletRequest,
>>> +      HttpServletResponse servletResponse, SecurityToken token,
>>> +      BeanConverter converter) throws IOException, JSONException {
>>> +
>>> +    JSONObject requests = new
>>> JSONObject(servletRequest.getParameter("request"));
>>> +    Map<String, ResponseItem> responses = Maps.newHashMap();
>>> +
>>> +    Iterator keys = requests.keys();
>>> +    while (keys.hasNext()) {
>>> +      String key = (String) keys.next();
>>> +      String request = requests.getString(key);
>>> +
>>> +      RequestItem requestItem = converter.convertToObject(request,
>>> RequestItem.class);
>>> +      requestItem.parseUrlParamsIntoParameters();
>>> +      requestItem.setToken(token);
>>> +
>>> +      responses.put(key, getResponseItem(converter, requestItem));
>>> +    }
>>> +
>>> +    PrintWriter writer = servletResponse.getWriter();
>>> +    writer.write(converter.convertToString(
>>> +        Maps.immutableMap("error", false, "responses", responses)));
>>> +  }
>>> +
>>> +  ResponseItem getResponseItem(BeanConverter converter, RequestItem
>>> requestItem) {
>>> +    String route = getRouteFromParameter(requestItem.getUrl());
>>>   Class<? extends DataRequestHandler> handlerClass = handlers.get(route);
>>>
>>>   if (handlerClass == null) {
>>> @@ -115,13 +173,12 @@
>>>   }
>>>
>>>   DataRequestHandler handler = injector.getInstance(handlerClass);
>>> -    BeanConverter converter = getConverterForRequest(servletRequest);
>>> -    // TODO: Move all conversions out of the handler up into the servlet
>>> layer
>>>   handler.setConverter(converter);
>>>
>>> -    String method =
>>> getHttpMethodFromParameter(servletRequest.getMethod(),
>>> -        servletRequest.getParameter(X_HTTP_METHOD_OVERRIDE));
>>> +    return handler.handleMethod(requestItem);
>>> +  }
>>>
>>> +  SecurityToken getSecurityToken(HttpServletRequest servletRequest) {
>>>   SecurityToken token;
>>>   try {
>>>     token =
>>>
>>> securityTokenDecoder.createToken(servletRequest.getParameter(SECURITY_TOKEN_PARAM));
>>> @@ -129,20 +186,10 @@
>>>     throw new RuntimeException(
>>>         "Implement error return for bad security token.");
>>>   }
>>> -
>>> -    ResponseItem responseItem = handler.handleMethod(
>>> -        new RequestItem(servletRequest, token, method));
>>> -
>>> -    if (responseItem.getError() == null) {
>>> -      PrintWriter writer = servletResponse.getWriter();
>>> -
>>>  writer.write(converter.convertToString(responseItem.getResponse()));
>>> -    } else {
>>> -
>>> servletResponse.sendError(responseItem.getError().getHttpErrorCode(),
>>> -          responseItem.getErrorMessage());
>>> -    }
>>> +    return token;
>>>  }
>>>
>>> -  /*package-protected*/ BeanConverter
>>> getConverterForRequest(HttpServletRequest servletRequest) {
>>> +  BeanConverter getConverterForRequest(HttpServletRequest
>>> servletRequest)
>>> {
>>>   String formatString = servletRequest.getParameter(FORMAT_PARAM);
>>>   if (!StringUtils.isBlank(formatString) &&
>>> formatString.equals(ATOM_FORMAT)) {
>>>     return xmlConverter;
>>> @@ -150,8 +197,7 @@
>>>   return jsonConverter;
>>>  }
>>>
>>> -  /*package-protected*/ String getHttpMethodFromParameter(String method,
>>> -      String overrideParameter) {
>>> +  String getHttpMethodFromParameter(String method, String
>>> overrideParameter) {
>>>   if (!StringUtils.isBlank(overrideParameter)) {
>>>     return overrideParameter;
>>>   } else {
>>> @@ -159,11 +205,15 @@
>>>   }
>>>  }
>>>
>>> -  /*package-protected*/ String getRouteFromParameter(String pathInfo) {
>>> +  String getRouteFromParameter(String pathInfo) {
>>>   pathInfo = pathInfo.substring(1);
>>>   int indexOfNextPathSeparator = pathInfo.indexOf("/");
>>>   return indexOfNextPathSeparator != -1 ?
>>>       pathInfo.substring(0, indexOfNextPathSeparator) :
>>>       pathInfo;
>>>  }
>>> +
>>> +  boolean isBatchUrl(HttpServletRequest servletRequest) {
>>> +    return servletRequest.getPathInfo().endsWith(JSON_BATCH_ROUTE);
>>> +  }
>>> }
>>>
>>> Modified:
>>>
>>> incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/dataservice/RequestItem.java
>>> URL:
>>>
>>> http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/dataservice/RequestItem.java?rev=669268&r1=669267&r2=669268&view=diff
>>>
>>>
>>> ==============================================================================
>>> ---
>>>
>>> incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/dataservice/RequestItem.java
>>> (original)
>>> +++
>>>
>>> incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/dataservice/RequestItem.java
>>> Wed Jun 18 12:39:54 2008
>>> @@ -34,6 +34,8 @@
>>>  private Map<String, String> parameters;
>>>  private SecurityToken token;
>>>
>>> +  public RequestItem() { }
>>> +
>>>  public RequestItem(String url, Map<String, String> parameters,
>>> SecurityToken token,
>>>     String method) {
>>>   this.url = url;
>>> @@ -50,7 +52,7 @@
>>>   Map<String, String> parameters = Maps.newHashMap();
>>>
>>>   Enumeration names = servletRequest.getParameterNames();
>>> -    while(names.hasMoreElements()) {
>>> +    while (names.hasMoreElements()) {
>>>     String name = (String) names.nextElement();
>>>     parameters.put(name, servletRequest.getParameter(name));
>>>   }
>>> @@ -58,6 +60,30 @@
>>>   return parameters;
>>>  }
>>>
>>> +  /*
>>> +   * Takes any url params out of the url and puts them into the param
>>> map.
>>> +   * Usually the servlet request code does this for us but the batch
>>> request calls have to do it
>>> +   * by hand.
>>> +   */
>>> +  public void parseUrlParamsIntoParameters() {
>>> +    if (this.parameters == null) {
>>> +      this.parameters = Maps.newHashMap();
>>> +    }
>>> +
>>> +    String fullUrl = this.url;
>>> +    int queryParamIndex = fullUrl.indexOf("?");
>>> +
>>> +    if (queryParamIndex != -1) {
>>> +      this.url = fullUrl.substring(0, queryParamIndex);
>>> +
>>> +      String queryParams = fullUrl.substring(queryParamIndex + 1);
>>> +      for (String param : queryParams.split("&")) {
>>> +        String[] paramPieces = param.split("=", 2);
>>> +        this.parameters.put(paramPieces[0], paramPieces.length == 2 ?
>>> paramPieces[1] : "");
>>> +      }
>>> +    }
>>> +  }
>>> +
>>>  public String getUrl() {
>>>   return url;
>>>  }
>>>
>>> Modified:
>>>
>>> incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/dataservice/DataServiceServletTest.java
>>> URL:
>>>
>>> http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/dataservice/DataServiceServletTest.java?rev=669268&r1=669267&r2=669268&view=diff
>>>
>>>
>>> ==============================================================================
>>> ---
>>>
>>> incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/dataservice/DataServiceServletTest.java
>>> (original)
>>> +++
>>>
>>> incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/dataservice/DataServiceServletTest.java
>>> Wed Jun 18 12:39:54 2008
>>> @@ -147,37 +147,24 @@
>>>  }
>>>
>>>  public void testInvalidRoute() throws Exception {
>>> -    req.setCharacterEncoding("UTF-8");
>>> -    EasyMock.expect(req.getPathInfo()).andReturn("/ahhh!");
>>> -
>>> -    EasyMock.replay(req);
>>>   try {
>>> -      servlet.doPost(req, res);
>>> +      servlet.getResponseItem(null, new RequestItem("/ahhh!", null,
>>> null,
>>> null));
>>>     fail("The route should not have found a valid handler.");
>>>   } catch (RuntimeException e) {
>>>     // Yea!
>>>     assertEquals("No handler for route: ahhh!", e.getMessage());
>>>   }
>>> -    EasyMock.verify(req);
>>>  }
>>>
>>>  public void testSecurityTokenException() throws Exception {
>>> -    req.setCharacterEncoding("UTF-8");
>>> -    EasyMock.expect(req.getPathInfo()).andReturn("/" +
>>> DataServiceServlet.APPDATA_ROUTE);
>>> -    EasyMock.expect(req.getMethod()).andReturn("POST");
>>> -
>>>
>>> EasyMock.expect(req.getParameter(DataServiceServlet.X_HTTP_METHOD_OVERRIDE)).andReturn("POST");
>>> -
>>>
>>> EasyMock.expect(req.getParameter(DataServiceServlet.FORMAT_PARAM)).andReturn(null);
>>> -
>>>   String tokenString = "owner:viewer:app:container.com:foo:bar";
>>>
>>>
>>> EasyMock.expect(req.getParameter(DataServiceServlet.SECURITY_TOKEN_PARAM))
>>>       .andReturn(tokenString);
>>>   EasyMock.expect(tokenDecoder.createToken(tokenString)).andThrow(new
>>> SecurityTokenException(""));
>>>
>>> -    setupInjector();
>>> -
>>> -    EasyMock.replay(req, tokenDecoder, injector);
>>> +    EasyMock.replay(req, tokenDecoder);
>>>   try {
>>> -      servlet.doPost(req, res);
>>> +      servlet.getSecurityToken(req);
>>>     fail("The route should have thrown an exception due to the invalid
>>> security token.");
>>>   } catch (RuntimeException e) {
>>>     // Yea!
>>> @@ -185,7 +172,7 @@
>>>     // instead of just throwing an exception.
>>>     assertEquals("Implement error return for bad security token.",
>>> e.getMessage());
>>>   }
>>> -    EasyMock.verify(req, tokenDecoder, injector);
>>> +    EasyMock.verify(req, tokenDecoder);
>>>  }
>>>
>>>  public void testGetHttpMethodFromParameter() throws Exception {
>>> @@ -202,6 +189,20 @@
>>>   assertEquals("path", servlet.getRouteFromParameter("/path/fun/yes"));
>>>  }
>>>
>>> +  public void testIsBatchUrl() throws Exception {
>>> +    assertBatchUrl("/jsonBatch", true);
>>> +    assertBatchUrl("/path/to/the/jsonBatch", true);
>>> +    assertBatchUrl("/people/normalpath", false);
>>> +  }
>>> +
>>> +  private void assertBatchUrl(String url, boolean isBatch) {
>>> +    EasyMock.expect(req.getPathInfo()).andReturn(url);
>>> +    EasyMock.replay(req);
>>> +    assertEquals(isBatch, servlet.isBatchUrl(req));
>>> +    EasyMock.verify(req);
>>> +    EasyMock.reset(req);
>>> +  }
>>> +
>>>  public void testGetConverterForRequest() throws Exception {
>>>   BeanJsonConverter json = new BeanJsonConverter();
>>>   BeanXmlConverter xml = new BeanXmlConverter();
>>>
>>> Added:
>>>
>>> incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/dataservice/RequestItemTest.java
>>> URL:
>>>
>>> http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/dataservice/RequestItemTest.java?rev=669268&view=auto
>>>
>>>
>>> ==============================================================================
>>> ---
>>>
>>> incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/dataservice/RequestItemTest.java
>>> (added)
>>> +++
>>>
>>> incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/dataservice/RequestItemTest.java
>>> Wed Jun 18 12:39:54 2008
>>> @@ -0,0 +1,64 @@
>>> +/*
>>> + * 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.dataservice;
>>> +
>>> +import org.apache.shindig.common.SecurityToken;
>>> +
>>> +import junit.framework.TestCase;
>>> +
>>> +import java.util.Map;
>>> +
>>> +import com.google.common.collect.Maps;
>>> +
>>> +public class RequestItemTest extends TestCase {
>>> +
>>> +  public void testParseUrl() throws Exception {
>>> +    String path = "/people/john.doe/@self";
>>> +
>>> +    RequestItem request = new RequestItem();
>>> +    request.setUrl(path + "?fields=huey,dewey,louie");
>>> +
>>> +    request.parseUrlParamsIntoParameters();
>>> +
>>> +    assertEquals(path, request.getUrl());
>>> +    assertEquals("huey,dewey,louie",
>>> request.getParameters().get("fields"));
>>> +
>>> +    // Try it without any params
>>> +    request = new RequestItem();
>>> +    request.setUrl(path);
>>> +
>>> +    request.parseUrlParamsIntoParameters();
>>> +
>>> +    assertEquals(path, request.getUrl());
>>> +    assertEquals(null, request.getParameters().get("fields"));
>>> +  }
>>> +
>>> +  public void testBasicFunctions() throws Exception {
>>> +    String url = "url";
>>> +    Map<String, String> params = Maps.newHashMap();
>>> +    SecurityToken token = null;
>>> +    String method = "method";
>>> +    RequestItem request = new RequestItem(url, params, token, method);
>>> +
>>> +    assertEquals(url, request.getUrl());
>>> +    assertEquals(params, request.getParameters());
>>> +    assertEquals(token, request.getToken());
>>> +    assertEquals(method, request.getMethod());
>>> +  }
>>> +
>>> +}
>>> \ No newline at end of file
>>>
>>> Added:
>>>
>>> incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/dataservice/integration/RestfulBatchTest.java
>>> URL:
>>>
>>> http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/dataservice/integration/RestfulBatchTest.java?rev=669268&view=auto
>>>
>>>
>>> ==============================================================================
>>> ---
>>>
>>> incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/dataservice/integration/RestfulBatchTest.java
>>> (added)
>>> +++
>>>
>>> incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/dataservice/integration/RestfulBatchTest.java
>>> Wed Jun 18 12:39:54 2008
>>> @@ -0,0 +1,93 @@
>>> +/*
>>> + * Licensed to the Apache Software Foundation (ASF) under one or more
>>> + * contributor license agreements.  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.  For additional information regarding
>>> + * copyright in this work, please see the NOTICE file in the top level
>>> + * directory of this distribution.
>>> + */
>>> +package org.apache.shindig.social.dataservice.integration;
>>> +
>>> +import com.google.common.collect.Maps;
>>> +import org.json.JSONObject;
>>> +import org.junit.Test;
>>> +
>>> +import java.util.Map;
>>> +
>>> +public class RestfulBatchTest extends AbstractLargeRestfulTests {
>>> +
>>> +  /**
>>> +   * Batch format:
>>> +   *   POST to /jsonBatch
>>> +   *   {request :
>>> +   *     {friends : {url : /people/john.doe/@friends, method : GET}}
>>> +   *     {john : {url : /people/john.doe/@self, method : GET}}
>>> +   *     {updateData : {url : /appdata/john.doe/@self/appId, method :
>>> POST, postData : {count : 1}}}
>>> +   *   }
>>> +   *
>>> +   *
>>> +   * Expected response
>>> +   *
>>> +   *  {error : false,
>>> +   *   responses : {
>>> +   *     {friends : {response : {<friend collection>}}}
>>> +   *     {john : {response : {<john.doe>}}}
>>> +   *     {updateData : {response : {}}}
>>> +   *  }
>>> +   *
>>> +   * Each response can possibly have .error and .errorMessage properties
>>> as well.
>>> +   *
>>> +   * @throws Exception if test encounters an error
>>> +   */
>>> +  @Test
>>> +  public void testGetBatchRequest() throws Exception {
>>> +    Map<String, String> extraParams = Maps.newHashMap();
>>> +    extraParams.put("request", "{"
>>> +        + "friends : {url : '/people/john.doe/@friends', method :
>>> 'GET'},
>>> "
>>> +        + "john : {url : '/people/john.doe/@self', method : 'GET'}, "
>>> +        + "updateData : {url : '/appdata/john.doe/@self/a', method :
>>> 'POST', parameters : {entry : {count : 1}}}"
>>> +        + "}");
>>> +
>>> +    String resp = getJsonResponse("jsonBatch", "POST", extraParams);
>>> +    JSONObject result = getJson(resp);
>>> +
>>> +    assertEquals(false, result.getBoolean("error"));
>>> +
>>> +    JSONObject jsonResponses = result.getJSONObject("responses");
>>> +    assertEquals(3, jsonResponses.length());
>>> +
>>> +    // friends response
>>> +    JSONObject jsonFriends = jsonResponses.getJSONObject("friends");
>>> +    assertFalse(jsonFriends.has("error"));
>>> +    assertFalse(jsonFriends.has("errorMessage"));
>>> +
>>> +    JSONObject jsonFriendsResponse =
>>> jsonFriends.getJSONObject("response");
>>> +    assertEquals(2, jsonFriendsResponse.getInt("totalResults"));
>>> +    assertEquals(0, jsonFriendsResponse.getInt("startIndex"));
>>> +
>>> +    // john.doe response
>>> +    JSONObject jsonJohn = jsonResponses.getJSONObject("john");
>>> +    assertFalse(jsonJohn.has("error"));
>>> +    assertFalse(jsonJohn.has("errorMessage"));
>>> +
>>> +    JSONObject jsonJohnResponse = jsonJohn.getJSONObject("response");
>>> +    assertEquals("john.doe", jsonJohnResponse.getString("id"));
>>> +    assertEquals("John Doe",
>>> jsonJohnResponse.getJSONObject("name").getString("unstructured"));
>>> +
>>> +    // john.doe response
>>> +    JSONObject jsonData = jsonResponses.getJSONObject("updateData");
>>> +    assertFalse(jsonData.has("error"));
>>> +    assertFalse(jsonData.has("errorMessage"));
>>> +    assertTrue(jsonData.has("response"));
>>> +  }
>>> +
>>> +}
>>> \ No newline at end of file
>>>
>>> Modified:
>>>
>>> incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/dataservice/integration/RestfulJsonPeopleTest.java
>>> URL:
>>>
>>> http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/dataservice/integration/RestfulJsonPeopleTest.java?rev=669268&r1=669267&r2=669268&view=diff
>>>
>>>
>>> ==============================================================================
>>> ---
>>>
>>> incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/dataservice/integration/RestfulJsonPeopleTest.java
>>> (original)
>>> +++
>>>
>>> incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/dataservice/integration/RestfulJsonPeopleTest.java
>>> Wed Jun 18 12:39:54 2008
>>> @@ -29,7 +29,6 @@
>>> import org.apache.shindig.social.opensocial.model.Url;
>>>
>>> import com.google.common.collect.Maps;
>>> -import org.easymock.classextension.EasyMock;
>>> import org.json.JSONArray;
>>> import org.json.JSONException;
>>> import org.json.JSONObject;
>>>
>>>
>>>
>>>
>

Re: svn commit: r669268 - in /incubator/shindig/trunk: features/opensocial-current/ java/social-api/src/main/java/org/apache/shindig/social/dataservice/ java/social-api/src/test/java/org/apache/shindig/social/dataservice/ java/social-api/src/test/jav

Posted by Chris Chabot <ch...@xs4all.nl>.
Thanks for the heads up Cassie!

I'm unavoidably tied up until mid friday, but i'll start banging on it  
as soon as possible. First impression is very good though, way of  
dealing with it looks sound and practical, and should be relatively  
trivial to add on the php side too.

Good job!

Ps, just a thought that occurred to me: Since the spec doesn't yet  
mention batching using the json format .. would we dare suggesting  
adding it to the restful api properly or do we hide it in the not- 
documented cellar and save the big coming out party for 0.9?

	-- Chris

On Jun 18, 2008, at 9:43 PM, Cassie wrote:

> Hey Chris and other php guys - I just wanted to alert you to the json
> batching stuff I wrote for the java restful server. This batching  
> isn't in
> the spec (the spec has some crazy multi part thing that is super  
> atom pub
> specific) so at this point it is just an extension so that the  
> opensocial js
> apis can work better (because not batching is a regression).
>
> Lemme know what you think of the format - its pretty similar to what  
> we had
> for the old json wire format. Once we clean this up we can propose  
> it to the
> restful spec list as an alternative to the atomy batching.
>
> All input welcome!
>
> - Cassie
>
>
> On Wed, Jun 18, 2008 at 12:39 PM, <do...@apache.org> wrote:
>
>> Author: doll
>> Date: Wed Jun 18 12:39:54 2008
>> New Revision: 669268
>>
>> URL: http://svn.apache.org/viewvc?rev=669268&view=rev
>> Log:
>> The dataservice rest impl now supports a jsonBatch format. This batch
>> format can now be optionally turned on in the restfulcontainer.js  
>> code.
>>
>> (As soon as php implements the batching stuff then we can remove  
>> the flag
>> and always use batching)
>>
>>
>>
>> Added:
>>
>> incubator/shindig/trunk/java/social-api/src/test/java/org/apache/ 
>> shindig/social/dataservice/RequestItemTest.java
>>
>> incubator/shindig/trunk/java/social-api/src/test/java/org/apache/ 
>> shindig/social/dataservice/integration/RestfulBatchTest.java
>> Modified:
>>   incubator/shindig/trunk/features/opensocial-current/ 
>> restfulcontainer.js
>>
>> incubator/shindig/trunk/java/social-api/src/main/java/org/apache/ 
>> shindig/social/dataservice/DataServiceServlet.java
>>
>> incubator/shindig/trunk/java/social-api/src/main/java/org/apache/ 
>> shindig/social/dataservice/RequestItem.java
>>
>> incubator/shindig/trunk/java/social-api/src/test/java/org/apache/ 
>> shindig/social/dataservice/DataServiceServletTest.java
>>
>> incubator/shindig/trunk/java/social-api/src/test/java/org/apache/ 
>> shindig/social/dataservice/integration/RestfulJsonPeopleTest.java
>>
>> Modified:
>> incubator/shindig/trunk/features/opensocial-current/ 
>> restfulcontainer.js
>> URL:
>> http://svn.apache.org/viewvc/incubator/shindig/trunk/features/opensocial-current/restfulcontainer.js?rev=669268&r1=669267&r2=669268&view=diff
>>
>> = 
>> = 
>> = 
>> = 
>> = 
>> = 
>> = 
>> = 
>> = 
>> =====================================================================
>> --- incubator/shindig/trunk/features/opensocial-current/ 
>> restfulcontainer.js
>> (original)
>> +++ incubator/shindig/trunk/features/opensocial-current/ 
>> restfulcontainer.js
>> Wed Jun 18 12:39:54 2008
>> @@ -38,6 +38,8 @@
>>  this.baseUrl_ = baseUrl;
>>
>>  this.securityToken_ = shindig.auth.getSecurityToken();
>> +
>> +  this.useBatching_ = false;
>> };
>> RestfulContainer.inherits(opensocial.Container);
>>
>> @@ -94,7 +96,6 @@
>>    var url = requestObject.request.url;
>>    var separator = url.indexOf("?") != -1 ? "&" : "?";
>>
>> -    // TODO: Use batching instead of doing this one by one
>>    gadgets.io.makeNonProxiedRequest(
>>        baseUrl + url + separator + "st=" + st,
>>        function(result) {
>> @@ -115,8 +116,61 @@
>>  }
>>
>>  // may need multiple urls for one response but lets ignore that  
>> for now
>> -  for (var i = 0; i < totalRequests; i++) {
>> -    makeProxiedRequest(requestObjects[i], this.baseUrl_,
>> this.securityToken_);
>> +  // TODO: Once both the php and java code decide on and implement
>> batching
>> +  // the non-batching code should be deleted
>> +  if (!this.useBatching_) {
>> +    for (var i = 0; i < totalRequests; i++) {
>> +      makeProxiedRequest(requestObjects[i], this.baseUrl_,
>> this.securityToken_);
>> +    }
>> +
>> +  } else {
>> +    var jsonBatchData = {};
>> +
>> +    for (var j = 0; j < totalRequests; j++) {
>> +      var requestObject = requestObjects[j];
>> +
>> +      jsonBatchData[requestObject.key] = {url :  
>> requestObject.request.url,
>> +        method : requestObject.request.method};
>> +      if (requestObject.request.postData) {
>> +        jsonBatchData[requestObject.key].parameters =
>> requestObject.request.postData;
>> +      }
>> +    }
>> +
>> +    // This is slightly different than jsonContainer
>> +    var sendResponse = function(result) {
>> +      result = result.data;
>> +
>> +      if (!result || result['error']) {
>> +        callback(new opensocial.DataResponse({}, true));
>> +        return;
>> +      }
>> +
>> +      var responses = result['responses'] || [];
>> +      var globalError = false;
>> +
>> +      var responseMap = {};
>> +
>> +      for (var k = 0; k < requestObjects.length; k++) {
>> +        var request = requestObjects[k];
>> +        var response = responses[request.key];
>> +
>> +        var rawData = response['response'];
>> +        var error = response['error'];
>> +        var errorMessage = response['errorMessage'];
>> +
>> +        var processedData = request.request.processResponse(
>> +            request.request, rawData, error, errorMessage);
>> +        globalError = globalError || processedData.hadError();
>> +        responseMap[request.key] = processedData;
>> +      }
>> +
>> +      var dataResponse = new opensocial.DataResponse(responseMap,
>> globalError);
>> +      callback(dataResponse);
>> +    };
>> +
>> +    // TODO: get the jsonbatch url from the container config
>> +    new BatchRequest(this.baseUrl_ + "/jsonBatch",
>> gadgets.json.stringify(jsonBatchData),
>> +        sendResponse, {'st' :  
>> shindig.auth.getSecurityToken()}).send();
>>  }
>>
>> };
>>
>> Modified:
>> incubator/shindig/trunk/java/social-api/src/main/java/org/apache/ 
>> shindig/social/dataservice/DataServiceServlet.java
>> URL:
>> http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/dataservice/DataServiceServlet.java?rev=669268&r1=669267&r2=669268&view=diff
>>
>> = 
>> = 
>> = 
>> = 
>> = 
>> = 
>> = 
>> = 
>> = 
>> =====================================================================
>> ---
>> incubator/shindig/trunk/java/social-api/src/main/java/org/apache/ 
>> shindig/social/dataservice/DataServiceServlet.java
>> (original)
>> +++
>> incubator/shindig/trunk/java/social-api/src/main/java/org/apache/ 
>> shindig/social/dataservice/DataServiceServlet.java
>> Wed Jun 18 12:39:54 2008
>> @@ -17,28 +17,31 @@
>> */
>> package org.apache.shindig.social.dataservice;
>>
>> -import com.google.inject.Inject;
>> -import com.google.inject.Injector;
>> -
>> -import org.apache.shindig.common.SecurityTokenDecoder;
>> import org.apache.shindig.common.SecurityToken;
>> +import org.apache.shindig.common.SecurityTokenDecoder;
>> import org.apache.shindig.common.SecurityTokenException;
>> import org.apache.shindig.common.servlet.InjectedServlet;
>> +import org.apache.shindig.social.ResponseItem;
>> import org.apache.shindig.social.opensocial.util.BeanConverter;
>> import org.apache.shindig.social.opensocial.util.BeanJsonConverter;
>> import org.apache.shindig.social.opensocial.util.BeanXmlConverter;
>> -import org.apache.shindig.social.ResponseItem;
>> +
>> +import com.google.common.collect.Maps;
>> +import com.google.inject.Inject;
>> +import com.google.inject.Injector;
>> +import org.apache.commons.lang.StringUtils;
>> +import org.json.JSONException;
>> +import org.json.JSONObject;
>>
>> 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.Iterator;
>> import java.util.Map;
>> import java.util.logging.Logger;
>>
>> -import org.apache.commons.lang.StringUtils;
>> -
>> public class DataServiceServlet extends InjectedServlet {
>>
>>  protected static final String X_HTTP_METHOD_OVERRIDE =
>> "X-HTTP-Method-Override";
>> @@ -58,6 +61,7 @@
>>  private Map<String, Class<? extends DataRequestHandler>> handlers;
>>  private BeanJsonConverter jsonConverter;
>>  private BeanXmlConverter xmlConverter;
>> +  private static final String JSON_BATCH_ROUTE = "jsonBatch";
>>
>>  @Inject
>>  public void setHandlers(HandlerProvider handlers) {
>> @@ -65,8 +69,7 @@
>>  }
>>
>>  @Inject
>> -  public void setSecurityTokenDecoder(SecurityTokenDecoder
>> -      securityTokenDecoder) {
>> +  public void setSecurityTokenDecoder(SecurityTokenDecoder
>> securityTokenDecoder) {
>>    this.securityTokenDecoder = securityTokenDecoder;
>>  }
>>
>> @@ -102,12 +105,67 @@
>>  protected void doPost(HttpServletRequest servletRequest,
>>      HttpServletResponse servletResponse)
>>      throws ServletException, IOException {
>> -    String path = servletRequest.getPathInfo();
>> -    logger.finest("Handling restful request for " + path);
>> +    logger.finest("Handling restful request for " +
>> servletRequest.getPathInfo());
>>
>>    servletRequest.setCharacterEncoding("UTF-8");
>> +    SecurityToken token = getSecurityToken(servletRequest);
>> +    BeanConverter converter =  
>> getConverterForRequest(servletRequest);
>>
>> -    String route = getRouteFromParameter(path);
>> +    if (isBatchUrl(servletRequest)) {
>> +      try {
>> +        handleBatchRequest(servletRequest, servletResponse, token,
>> converter);
>> +      } catch (JSONException e) {
>> +        throw new RuntimeException("Bad batch format", e);
>> +      }
>> +    } else {
>> +      handleSingleRequest(servletRequest, servletResponse, token,
>> converter);
>> +    }
>> +  }
>> +
>> +  private void handleSingleRequest(HttpServletRequest  
>> servletRequest,
>> +      HttpServletResponse servletResponse, SecurityToken token,
>> +      BeanConverter converter) throws IOException {
>> +    String method =  
>> getHttpMethodFromParameter(servletRequest.getMethod(),
>> +        servletRequest.getParameter(X_HTTP_METHOD_OVERRIDE));
>> +
>> +    RequestItem requestItem = new RequestItem(servletRequest, token,
>> method);
>> +    ResponseItem responseItem = getResponseItem(converter,  
>> requestItem);
>> +
>> +    if (responseItem.getError() == null) {
>> +      PrintWriter writer = servletResponse.getWriter();
>> +       
>> writer.write(converter.convertToString(responseItem.getResponse()));
>> +    } else {
>> +
>> servletResponse.sendError(responseItem.getError().getHttpErrorCode(),
>> +          responseItem.getErrorMessage());
>> +    }
>> +  }
>> +
>> +  private void handleBatchRequest(HttpServletRequest servletRequest,
>> +      HttpServletResponse servletResponse, SecurityToken token,
>> +      BeanConverter converter) throws IOException, JSONException {
>> +
>> +    JSONObject requests = new
>> JSONObject(servletRequest.getParameter("request"));
>> +    Map<String, ResponseItem> responses = Maps.newHashMap();
>> +
>> +    Iterator keys = requests.keys();
>> +    while (keys.hasNext()) {
>> +      String key = (String) keys.next();
>> +      String request = requests.getString(key);
>> +
>> +      RequestItem requestItem = converter.convertToObject(request,
>> RequestItem.class);
>> +      requestItem.parseUrlParamsIntoParameters();
>> +      requestItem.setToken(token);
>> +
>> +      responses.put(key, getResponseItem(converter, requestItem));
>> +    }
>> +
>> +    PrintWriter writer = servletResponse.getWriter();
>> +    writer.write(converter.convertToString(
>> +        Maps.immutableMap("error", false, "responses", responses)));
>> +  }
>> +
>> +  ResponseItem getResponseItem(BeanConverter converter, RequestItem
>> requestItem) {
>> +    String route = getRouteFromParameter(requestItem.getUrl());
>>    Class<? extends DataRequestHandler> handlerClass =  
>> handlers.get(route);
>>
>>    if (handlerClass == null) {
>> @@ -115,13 +173,12 @@
>>    }
>>
>>    DataRequestHandler handler = injector.getInstance(handlerClass);
>> -    BeanConverter converter =  
>> getConverterForRequest(servletRequest);
>> -    // TODO: Move all conversions out of the handler up into the  
>> servlet
>> layer
>>    handler.setConverter(converter);
>>
>> -    String method =  
>> getHttpMethodFromParameter(servletRequest.getMethod(),
>> -        servletRequest.getParameter(X_HTTP_METHOD_OVERRIDE));
>> +    return handler.handleMethod(requestItem);
>> +  }
>>
>> +  SecurityToken getSecurityToken(HttpServletRequest  
>> servletRequest) {
>>    SecurityToken token;
>>    try {
>>      token =
>> securityTokenDecoder 
>> .createToken(servletRequest.getParameter(SECURITY_TOKEN_PARAM));
>> @@ -129,20 +186,10 @@
>>      throw new RuntimeException(
>>          "Implement error return for bad security token.");
>>    }
>> -
>> -    ResponseItem responseItem = handler.handleMethod(
>> -        new RequestItem(servletRequest, token, method));
>> -
>> -    if (responseItem.getError() == null) {
>> -      PrintWriter writer = servletResponse.getWriter();
>> -       
>> writer.write(converter.convertToString(responseItem.getResponse()));
>> -    } else {
>> -
>> servletResponse.sendError(responseItem.getError().getHttpErrorCode(),
>> -          responseItem.getErrorMessage());
>> -    }
>> +    return token;
>>  }
>>
>> -  /*package-protected*/ BeanConverter
>> getConverterForRequest(HttpServletRequest servletRequest) {
>> +  BeanConverter getConverterForRequest(HttpServletRequest  
>> servletRequest)
>> {
>>    String formatString = servletRequest.getParameter(FORMAT_PARAM);
>>    if (!StringUtils.isBlank(formatString) &&
>> formatString.equals(ATOM_FORMAT)) {
>>      return xmlConverter;
>> @@ -150,8 +197,7 @@
>>    return jsonConverter;
>>  }
>>
>> -  /*package-protected*/ String getHttpMethodFromParameter(String  
>> method,
>> -      String overrideParameter) {
>> +  String getHttpMethodFromParameter(String method, String
>> overrideParameter) {
>>    if (!StringUtils.isBlank(overrideParameter)) {
>>      return overrideParameter;
>>    } else {
>> @@ -159,11 +205,15 @@
>>    }
>>  }
>>
>> -  /*package-protected*/ String getRouteFromParameter(String  
>> pathInfo) {
>> +  String getRouteFromParameter(String pathInfo) {
>>    pathInfo = pathInfo.substring(1);
>>    int indexOfNextPathSeparator = pathInfo.indexOf("/");
>>    return indexOfNextPathSeparator != -1 ?
>>        pathInfo.substring(0, indexOfNextPathSeparator) :
>>        pathInfo;
>>  }
>> +
>> +  boolean isBatchUrl(HttpServletRequest servletRequest) {
>> +    return servletRequest.getPathInfo().endsWith(JSON_BATCH_ROUTE);
>> +  }
>> }
>>
>> Modified:
>> incubator/shindig/trunk/java/social-api/src/main/java/org/apache/ 
>> shindig/social/dataservice/RequestItem.java
>> URL:
>> http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/dataservice/RequestItem.java?rev=669268&r1=669267&r2=669268&view=diff
>>
>> = 
>> = 
>> = 
>> = 
>> = 
>> = 
>> = 
>> = 
>> = 
>> =====================================================================
>> ---
>> incubator/shindig/trunk/java/social-api/src/main/java/org/apache/ 
>> shindig/social/dataservice/RequestItem.java
>> (original)
>> +++
>> incubator/shindig/trunk/java/social-api/src/main/java/org/apache/ 
>> shindig/social/dataservice/RequestItem.java
>> Wed Jun 18 12:39:54 2008
>> @@ -34,6 +34,8 @@
>>  private Map<String, String> parameters;
>>  private SecurityToken token;
>>
>> +  public RequestItem() { }
>> +
>>  public RequestItem(String url, Map<String, String> parameters,
>> SecurityToken token,
>>      String method) {
>>    this.url = url;
>> @@ -50,7 +52,7 @@
>>    Map<String, String> parameters = Maps.newHashMap();
>>
>>    Enumeration names = servletRequest.getParameterNames();
>> -    while(names.hasMoreElements()) {
>> +    while (names.hasMoreElements()) {
>>      String name = (String) names.nextElement();
>>      parameters.put(name, servletRequest.getParameter(name));
>>    }
>> @@ -58,6 +60,30 @@
>>    return parameters;
>>  }
>>
>> +  /*
>> +   * Takes any url params out of the url and puts them into the  
>> param map.
>> +   * Usually the servlet request code does this for us but the batch
>> request calls have to do it
>> +   * by hand.
>> +   */
>> +  public void parseUrlParamsIntoParameters() {
>> +    if (this.parameters == null) {
>> +      this.parameters = Maps.newHashMap();
>> +    }
>> +
>> +    String fullUrl = this.url;
>> +    int queryParamIndex = fullUrl.indexOf("?");
>> +
>> +    if (queryParamIndex != -1) {
>> +      this.url = fullUrl.substring(0, queryParamIndex);
>> +
>> +      String queryParams = fullUrl.substring(queryParamIndex + 1);
>> +      for (String param : queryParams.split("&")) {
>> +        String[] paramPieces = param.split("=", 2);
>> +        this.parameters.put(paramPieces[0], paramPieces.length ==  
>> 2 ?
>> paramPieces[1] : "");
>> +      }
>> +    }
>> +  }
>> +
>>  public String getUrl() {
>>    return url;
>>  }
>>
>> Modified:
>> incubator/shindig/trunk/java/social-api/src/test/java/org/apache/ 
>> shindig/social/dataservice/DataServiceServletTest.java
>> URL:
>> http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/dataservice/DataServiceServletTest.java?rev=669268&r1=669267&r2=669268&view=diff
>>
>> = 
>> = 
>> = 
>> = 
>> = 
>> = 
>> = 
>> = 
>> = 
>> =====================================================================
>> ---
>> incubator/shindig/trunk/java/social-api/src/test/java/org/apache/ 
>> shindig/social/dataservice/DataServiceServletTest.java
>> (original)
>> +++
>> incubator/shindig/trunk/java/social-api/src/test/java/org/apache/ 
>> shindig/social/dataservice/DataServiceServletTest.java
>> Wed Jun 18 12:39:54 2008
>> @@ -147,37 +147,24 @@
>>  }
>>
>>  public void testInvalidRoute() throws Exception {
>> -    req.setCharacterEncoding("UTF-8");
>> -    EasyMock.expect(req.getPathInfo()).andReturn("/ahhh!");
>> -
>> -    EasyMock.replay(req);
>>    try {
>> -      servlet.doPost(req, res);
>> +      servlet.getResponseItem(null, new RequestItem("/ahhh!",  
>> null, null,
>> null));
>>      fail("The route should not have found a valid handler.");
>>    } catch (RuntimeException e) {
>>      // Yea!
>>      assertEquals("No handler for route: ahhh!", e.getMessage());
>>    }
>> -    EasyMock.verify(req);
>>  }
>>
>>  public void testSecurityTokenException() throws Exception {
>> -    req.setCharacterEncoding("UTF-8");
>> -    EasyMock.expect(req.getPathInfo()).andReturn("/" +
>> DataServiceServlet.APPDATA_ROUTE);
>> -    EasyMock.expect(req.getMethod()).andReturn("POST");
>> -
>> EasyMock 
>> .expect 
>> (req 
>> .getParameter 
>> (DataServiceServlet.X_HTTP_METHOD_OVERRIDE)).andReturn("POST");
>> -
>> EasyMock 
>> .expect 
>> (req.getParameter(DataServiceServlet.FORMAT_PARAM)).andReturn(null);
>> -
>>    String tokenString = "owner:viewer:app:container.com:foo:bar";
>>
>> EasyMock 
>> .expect(req.getParameter(DataServiceServlet.SECURITY_TOKEN_PARAM))
>>        .andReturn(tokenString);
>>     
>> EasyMock.expect(tokenDecoder.createToken(tokenString)).andThrow(new
>> SecurityTokenException(""));
>>
>> -    setupInjector();
>> -
>> -    EasyMock.replay(req, tokenDecoder, injector);
>> +    EasyMock.replay(req, tokenDecoder);
>>    try {
>> -      servlet.doPost(req, res);
>> +      servlet.getSecurityToken(req);
>>      fail("The route should have thrown an exception due to the  
>> invalid
>> security token.");
>>    } catch (RuntimeException e) {
>>      // Yea!
>> @@ -185,7 +172,7 @@
>>      // instead of just throwing an exception.
>>      assertEquals("Implement error return for bad security token.",
>> e.getMessage());
>>    }
>> -    EasyMock.verify(req, tokenDecoder, injector);
>> +    EasyMock.verify(req, tokenDecoder);
>>  }
>>
>>  public void testGetHttpMethodFromParameter() throws Exception {
>> @@ -202,6 +189,20 @@
>>    assertEquals("path", servlet.getRouteFromParameter("/path/fun/ 
>> yes"));
>>  }
>>
>> +  public void testIsBatchUrl() throws Exception {
>> +    assertBatchUrl("/jsonBatch", true);
>> +    assertBatchUrl("/path/to/the/jsonBatch", true);
>> +    assertBatchUrl("/people/normalpath", false);
>> +  }
>> +
>> +  private void assertBatchUrl(String url, boolean isBatch) {
>> +    EasyMock.expect(req.getPathInfo()).andReturn(url);
>> +    EasyMock.replay(req);
>> +    assertEquals(isBatch, servlet.isBatchUrl(req));
>> +    EasyMock.verify(req);
>> +    EasyMock.reset(req);
>> +  }
>> +
>>  public void testGetConverterForRequest() throws Exception {
>>    BeanJsonConverter json = new BeanJsonConverter();
>>    BeanXmlConverter xml = new BeanXmlConverter();
>>
>> Added:
>> incubator/shindig/trunk/java/social-api/src/test/java/org/apache/ 
>> shindig/social/dataservice/RequestItemTest.java
>> URL:
>> http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/dataservice/RequestItemTest.java?rev=669268&view=auto
>>
>> = 
>> = 
>> = 
>> = 
>> = 
>> = 
>> = 
>> = 
>> = 
>> =====================================================================
>> ---
>> incubator/shindig/trunk/java/social-api/src/test/java/org/apache/ 
>> shindig/social/dataservice/RequestItemTest.java
>> (added)
>> +++
>> incubator/shindig/trunk/java/social-api/src/test/java/org/apache/ 
>> shindig/social/dataservice/RequestItemTest.java
>> Wed Jun 18 12:39:54 2008
>> @@ -0,0 +1,64 @@
>> +/*
>> + * 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.dataservice;
>> +
>> +import org.apache.shindig.common.SecurityToken;
>> +
>> +import junit.framework.TestCase;
>> +
>> +import java.util.Map;
>> +
>> +import com.google.common.collect.Maps;
>> +
>> +public class RequestItemTest extends TestCase {
>> +
>> +  public void testParseUrl() throws Exception {
>> +    String path = "/people/john.doe/@self";
>> +
>> +    RequestItem request = new RequestItem();
>> +    request.setUrl(path + "?fields=huey,dewey,louie");
>> +
>> +    request.parseUrlParamsIntoParameters();
>> +
>> +    assertEquals(path, request.getUrl());
>> +    assertEquals("huey,dewey,louie",
>> request.getParameters().get("fields"));
>> +
>> +    // Try it without any params
>> +    request = new RequestItem();
>> +    request.setUrl(path);
>> +
>> +    request.parseUrlParamsIntoParameters();
>> +
>> +    assertEquals(path, request.getUrl());
>> +    assertEquals(null, request.getParameters().get("fields"));
>> +  }
>> +
>> +  public void testBasicFunctions() throws Exception {
>> +    String url = "url";
>> +    Map<String, String> params = Maps.newHashMap();
>> +    SecurityToken token = null;
>> +    String method = "method";
>> +    RequestItem request = new RequestItem(url, params, token,  
>> method);
>> +
>> +    assertEquals(url, request.getUrl());
>> +    assertEquals(params, request.getParameters());
>> +    assertEquals(token, request.getToken());
>> +    assertEquals(method, request.getMethod());
>> +  }
>> +
>> +}
>> \ No newline at end of file
>>
>> Added:
>> incubator/shindig/trunk/java/social-api/src/test/java/org/apache/ 
>> shindig/social/dataservice/integration/RestfulBatchTest.java
>> URL:
>> http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/dataservice/integration/RestfulBatchTest.java?rev=669268&view=auto
>>
>> = 
>> = 
>> = 
>> = 
>> = 
>> = 
>> = 
>> = 
>> = 
>> =====================================================================
>> ---
>> incubator/shindig/trunk/java/social-api/src/test/java/org/apache/ 
>> shindig/social/dataservice/integration/RestfulBatchTest.java
>> (added)
>> +++
>> incubator/shindig/trunk/java/social-api/src/test/java/org/apache/ 
>> shindig/social/dataservice/integration/RestfulBatchTest.java
>> Wed Jun 18 12:39:54 2008
>> @@ -0,0 +1,93 @@
>> +/*
>> + * Licensed to the Apache Software Foundation (ASF) under one or  
>> more
>> + * contributor license agreements.  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.  For additional information  
>> regarding
>> + * copyright in this work, please see the NOTICE file in the top  
>> level
>> + * directory of this distribution.
>> + */
>> +package org.apache.shindig.social.dataservice.integration;
>> +
>> +import com.google.common.collect.Maps;
>> +import org.json.JSONObject;
>> +import org.junit.Test;
>> +
>> +import java.util.Map;
>> +
>> +public class RestfulBatchTest extends AbstractLargeRestfulTests {
>> +
>> +  /**
>> +   * Batch format:
>> +   *   POST to /jsonBatch
>> +   *   {request :
>> +   *     {friends : {url : /people/john.doe/@friends, method : GET}}
>> +   *     {john : {url : /people/john.doe/@self, method : GET}}
>> +   *     {updateData : {url : /appdata/john.doe/@self/appId,  
>> method :
>> POST, postData : {count : 1}}}
>> +   *   }
>> +   *
>> +   *
>> +   * Expected response
>> +   *
>> +   *  {error : false,
>> +   *   responses : {
>> +   *     {friends : {response : {<friend collection>}}}
>> +   *     {john : {response : {<john.doe>}}}
>> +   *     {updateData : {response : {}}}
>> +   *  }
>> +   *
>> +   * Each response can possibly have .error and .errorMessage  
>> properties
>> as well.
>> +   *
>> +   * @throws Exception if test encounters an error
>> +   */
>> +  @Test
>> +  public void testGetBatchRequest() throws Exception {
>> +    Map<String, String> extraParams = Maps.newHashMap();
>> +    extraParams.put("request", "{"
>> +        + "friends : {url : '/people/john.doe/@friends', method :  
>> 'GET'},
>> "
>> +        + "john : {url : '/people/john.doe/@self', method :  
>> 'GET'}, "
>> +        + "updateData : {url : '/appdata/john.doe/@self/a', method :
>> 'POST', parameters : {entry : {count : 1}}}"
>> +        + "}");
>> +
>> +    String resp = getJsonResponse("jsonBatch", "POST", extraParams);
>> +    JSONObject result = getJson(resp);
>> +
>> +    assertEquals(false, result.getBoolean("error"));
>> +
>> +    JSONObject jsonResponses = result.getJSONObject("responses");
>> +    assertEquals(3, jsonResponses.length());
>> +
>> +    // friends response
>> +    JSONObject jsonFriends = jsonResponses.getJSONObject("friends");
>> +    assertFalse(jsonFriends.has("error"));
>> +    assertFalse(jsonFriends.has("errorMessage"));
>> +
>> +    JSONObject jsonFriendsResponse =
>> jsonFriends.getJSONObject("response");
>> +    assertEquals(2, jsonFriendsResponse.getInt("totalResults"));
>> +    assertEquals(0, jsonFriendsResponse.getInt("startIndex"));
>> +
>> +    // john.doe response
>> +    JSONObject jsonJohn = jsonResponses.getJSONObject("john");
>> +    assertFalse(jsonJohn.has("error"));
>> +    assertFalse(jsonJohn.has("errorMessage"));
>> +
>> +    JSONObject jsonJohnResponse =  
>> jsonJohn.getJSONObject("response");
>> +    assertEquals("john.doe", jsonJohnResponse.getString("id"));
>> +    assertEquals("John Doe",
>> jsonJohnResponse.getJSONObject("name").getString("unstructured"));
>> +
>> +    // john.doe response
>> +    JSONObject jsonData = jsonResponses.getJSONObject("updateData");
>> +    assertFalse(jsonData.has("error"));
>> +    assertFalse(jsonData.has("errorMessage"));
>> +    assertTrue(jsonData.has("response"));
>> +  }
>> +
>> +}
>> \ No newline at end of file
>>
>> Modified:
>> incubator/shindig/trunk/java/social-api/src/test/java/org/apache/ 
>> shindig/social/dataservice/integration/RestfulJsonPeopleTest.java
>> URL:
>> http://svn.apache.org/viewvc/incubator/shindig/trunk/java/social-api/src/test/java/org/apache/shindig/social/dataservice/integration/RestfulJsonPeopleTest.java?rev=669268&r1=669267&r2=669268&view=diff
>>
>> = 
>> = 
>> = 
>> = 
>> = 
>> = 
>> = 
>> = 
>> = 
>> =====================================================================
>> ---
>> incubator/shindig/trunk/java/social-api/src/test/java/org/apache/ 
>> shindig/social/dataservice/integration/RestfulJsonPeopleTest.java
>> (original)
>> +++
>> incubator/shindig/trunk/java/social-api/src/test/java/org/apache/ 
>> shindig/social/dataservice/integration/RestfulJsonPeopleTest.java
>> Wed Jun 18 12:39:54 2008
>> @@ -29,7 +29,6 @@
>> import org.apache.shindig.social.opensocial.model.Url;
>>
>> import com.google.common.collect.Maps;
>> -import org.easymock.classextension.EasyMock;
>> import org.json.JSONArray;
>> import org.json.JSONException;
>> import org.json.JSONObject;
>>
>>
>>