Author: chabotc
Date: Wed Jun 25 04:21:42 2008
New Revision: 671511

URL: http://svn.apache.org/viewvc?rev=671511&view=rev
Log:
SHINDIG-404 Support Batching in RESTful interface (in json format)

Modified:
    incubator/shindig/trunk/php/src/socialrest/ActivityHandler.php
    incubator/shindig/trunk/php/src/socialrest/AppDataHandler.php
    incubator/shindig/trunk/php/src/socialrest/DataRequestHandler.php
    incubator/shindig/trunk/php/src/socialrest/PersonHandler.php
    incubator/shindig/trunk/php/src/socialrest/http/RestServlet.php

Modified: incubator/shindig/trunk/php/src/socialrest/ActivityHandler.php
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/socialrest/ActivityHandler.php?rev=671511&r1=671510&r2=671511&view=diff
==============================================================================
--- incubator/shindig/trunk/php/src/socialrest/ActivityHandler.php (original)
+++ incubator/shindig/trunk/php/src/socialrest/ActivityHandler.php Wed Jun 25 
04:21:42 2008
@@ -19,6 +19,8 @@
 
 class ActivityHandler extends DataRequestHandler {
        private $service;
+       
+       private static $ACTIVITY_ID_PATH = 
"/activities/{userId}/{groupId}/{activityId}";
 
        public function __construct()
        {
@@ -26,7 +28,7 @@
                $this->service = new $service();
        }
 
-       public function handleDelete($params, $token)
+       public function handleDelete(RestRequestItem $requestItem)
        {
                return new ResponseItem(BAD_REQUEST, "You can't delete 
activities. ", null);
        }
@@ -39,20 +41,17 @@
         * /activities/john.doe/@self
         * /activities/john.doe/@friends
         */
-       public function handleGet($params, $token)
+       public function handleGet(RestRequestItem $requestItem)
        {
-               $userId = UserId::fromJson($params[1]);
-               $groupId = GroupId::fromJson($params[2]);
-               $optionalActivityId = null;
-               if (count($params) > 3) {
-                       $optionalActivityId = $params[3];
-               }
+               $requestItem->parseUrlWithTemplate(self::$ACTIVITY_ID_PATH);
+               $parameters = $requestItem->getParameters();
+               $optionalActivityId = in_array('activityId', $parameters) ? 
$parameters['activityId'] : null;
                // TODO: Filter by fields
                // TODO: do we need to add pagination and sorting support?
                if ($optionalActivityId != null) {
-                       return $this->service->getActivity($userId, $groupId, 
$optionalActivityId, $token);
+                       return 
$this->service->getActivity($requestItem->getUser(), $requestItem->getGroup(), 
$optionalActivityId, $requestItem->getToken());
                }
-               return $this->service->getActivities($userId, $groupId, $token);
+               return $this->service->getActivities($requestItem->getUser(), 
$requestItem->getGroup(), $requestItem->getToken());
        }
 
        /**
@@ -62,20 +61,10 @@
         * /activities/john.doe/@self
         * - postBody is an activity object
         */
-       public function handlePost($params, $token)
+       public function handlePost(RestRequestItem $requestItem)
        {
-               $userId = UserId::fromJson($params[1]);
-               $groupId = GroupId::fromJson($params[2]);
-               // TODO: Should we pass the groupId through to the service?
-               if (!isset($GLOBALS['HTTP_RAW_POST_DATA'])) {
-                       throw new Exception("Empty raw post data");
-               }
-               $jsonActivity = $GLOBALS['HTTP_RAW_POST_DATA'];
-               if (get_magic_quotes_gpc()) {
-                       $jsonActivity = stripslashes($jsonActivity);
-               }
-               $activity = $this->convertToObject($jsonActivity);
-       return $this->service->createActivity($userId, $activity, $token);
+               //TODO: do we need to add groups here?
+               return $this->service->createActivity($requestItem->getUser(), 
$requestItem->getPostData(), $requestItem->getToken());
        }
 
        /**
@@ -85,8 +74,8 @@
         * /activities/john.doe/@self
         * - postBody is an activity object
         */
-       public function handlePut($params, $token)
+       public function handlePut(RestRequestItem $requestItem)
        {
-               return $this->handlePost($token);
+               return $this->handlePost($requestItem);
        }
-}
\ No newline at end of file
+}

Modified: incubator/shindig/trunk/php/src/socialrest/AppDataHandler.php
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/socialrest/AppDataHandler.php?rev=671511&r1=671510&r2=671511&view=diff
==============================================================================
--- incubator/shindig/trunk/php/src/socialrest/AppDataHandler.php (original)
+++ incubator/shindig/trunk/php/src/socialrest/AppDataHandler.php Wed Jun 25 
04:21:42 2008
@@ -1,4 +1,5 @@
 <?php
+
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements. See the NOTICE file
@@ -18,6 +19,7 @@
  */
 
 class AppDataHandler extends DataRequestHandler {
+       private static $APP_DATA_PATH = "/people/{userId}/{groupId}/{appId}";
        private $service;
 
        public function __construct()
@@ -38,13 +40,10 @@
         * be pulled from the values and set on the person object. If there are 
no
         * fields vars then all of the data will be overridden.
         */
-       public function handleDelete($params, $token)
+       public function handleDelete(RestRequestItem $requestItem)
        {
-               $userId = UserId::fromJson($params[1]);
-               $groupId = GroupId::fromJson($params[2]);
-               $appId = $this->getAppId($params[3], $token);
-               $fields = isset($_GET['fields']) ? explode(',', 
$_GET['fields']) : null;
-               return $this->service->deletePersonData($userId, $groupId, 
$fields, $appId, $token);            
+               $requestItem->parseUrlWithTemplate(self::$APP_DATA_PATH);
+               return 
$this->service->deletePersonData($requestItem->getUser(), 
$requestItem->getGroup(), $requestItem->getFields(), $requestItem->getAppId(), 
$requestItem->getToken());
        }
 
        /**
@@ -55,13 +54,10 @@
         * /appdata/john.doe/@friends/app?fields=count
         * /appdata/john.doe/@self/app
         */
-       public function handleGet($params, $token)
+       public function handleGet(RestRequestItem $requestItem)
        {
-               $userId = UserId::fromJson($params[1]);
-               $groupId = GroupId::fromJson($params[2]);
-               $appId = $this->getAppId($params[3], $token);
-               $fields = isset($_GET['fields']) ? explode(',', 
$_GET['fields']) : null;
-               return $this->service->getPersonData($userId, $groupId, 
$fields, $appId, $token);
+               $requestItem->parseUrlWithTemplate(self::$APP_DATA_PATH);
+               return $this->service->getPersonData($requestItem->getUser(), 
$requestItem->getGroup(), $requestItem->getFields(), $requestItem->getAppId(), 
$requestItem->getToken());
        }
 
        /**
@@ -76,21 +72,10 @@
         * be pulled from the values and set on the person object. If there are 
no
         * fields vars then all of the data will be overridden.
         */
-       public function handlePost($params, $token)
+       public function handlePost(RestRequestItem $requestItem)
        {
-               $userId = UserId::fromJson($params[1]);
-               $groupId = GroupId::fromJson($params[2]);
-               $appId = $this->getAppId($params[3], $token);
-               $fields = isset($_GET['fields']) ? explode(',', 
$_GET['fields']) : null;
-               if (!isset($GLOBALS['HTTP_RAW_POST_DATA'])) {
-                       throw new Exception("Empty raw post data");
-               }
-               $jsonActivity = $GLOBALS['HTTP_RAW_POST_DATA'];
-               if (get_magic_quotes_gpc()) {
-                       $jsonActivity = stripslashes($jsonActivity);
-               }
-               $values = $this->convertToObject($jsonActivity);
-               return $this->service->updatePersonData($userId, $groupId, 
$fields, $values, $appId, $token);
+               $requestItem->parseUrlWithTemplate(self::$APP_DATA_PATH);
+               return 
$this->service->updatePersonData($requestItem->getUser(), 
$requestItem->getGroup(), $requestItem->getFields(), 
$requestItem->getPostData(), $requestItem->getAppId(), 
$requestItem->getToken());
        }
 
        /**
@@ -105,8 +90,8 @@
         * be pulled from the values and set on the person object. If there are 
no
         * fields vars then all of the data will be overridden.
         */
-       public function handlePut($params, $token)
+       public function handlePut(RestRequestItem $requestItem)
        {
-               return $this->handlePost($params, $token);
+               return $this->handlePost($requestItem);
        }
 }
\ No newline at end of file

Modified: incubator/shindig/trunk/php/src/socialrest/DataRequestHandler.php
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/socialrest/DataRequestHandler.php?rev=671511&r1=671510&r2=671511&view=diff
==============================================================================
--- incubator/shindig/trunk/php/src/socialrest/DataRequestHandler.php (original)
+++ incubator/shindig/trunk/php/src/socialrest/DataRequestHandler.php Wed Jun 
25 04:21:42 2008
@@ -18,32 +18,32 @@
  */
 
 abstract class DataRequestHandler {
-       
-       public function handleMethod($method, $params, $token)
+
+       public function handleMethod(RestRequestItem $requestItem)
        {
-               if ($method == 'POST') {
-                       $response = $this->handlePost($params, $token);
-               } elseif ($method == 'GET') {
-                       $response = $this->handleGet($params, $token);
-               } elseif ($method == 'DELETE') {
-                       $response = $this->handleDelete($params, $token);
-               } elseif ($method == 'PUT') {
-                       $response = $this->handlePut($params, $token);
+               if ($requestItem->getMethod() == 'POST') {
+                       $response = $this->handlePost($requestItem);
+               } elseif ($requestItem->getMethod() == 'GET') {
+                       $response = $this->handleGet($requestItem);
+               } elseif ($requestItem->getMethod() == 'DELETE') {
+                       $response = $this->handleDelete($requestItem);
+               } elseif ($requestItem->getMethod() == 'PUT') {
+                       $response = $this->handlePut($requestItem);
                } else {
                        $response = new ResponseItem(BAD_REQUEST, "Unserviced 
Http method type", null);
                }
                return $response;
        }
-       
+
        static public function getAppId($appId, SecurityToken $token)
        {
-           if ($appId == '@app') {
+               if ($appId == '@app') {
                        return $token->getAppId();
                } else {
                        return $appId;
                }
        }
-       
+
        static public function convertToObject($string)
        {
                //TODO should detect if it's atom/xml or json here really. 
assuming json for now
@@ -54,11 +54,8 @@
                return $decoded;
        }
 
-       abstract public function handleDelete($params, $token);
-
-       abstract public function handleGet($params, $token);
-
-       abstract public function handlePost($params, $token);
-
-       abstract public function handlePut($params, $token);
-}
\ No newline at end of file
+       abstract public function handleDelete(RestRequestItem $requestItem);
+       abstract public function handleGet(RestRequestItem $requestItem);
+       abstract public function handlePost(RestRequestItem $requestItem);
+       abstract public function handlePut(RestRequestItem $requestItem);
+}

Modified: incubator/shindig/trunk/php/src/socialrest/PersonHandler.php
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/socialrest/PersonHandler.php?rev=671511&r1=671510&r2=671511&view=diff
==============================================================================
--- incubator/shindig/trunk/php/src/socialrest/PersonHandler.php (original)
+++ incubator/shindig/trunk/php/src/socialrest/PersonHandler.php Wed Jun 25 
04:21:42 2008
@@ -19,6 +19,8 @@
 
 class PersonHandler extends DataRequestHandler {
        private $service;
+       private static $PEOPLE_PATH = "/people/{userId}/{groupId}/{personId}";
+       protected static $DEFAULT_PERSON_FIELDS = array("id", "name", 
"thumbnailUrl");
 
        public function __construct()
        {
@@ -34,38 +36,31 @@
         * /people/john.doe/@friends
         * /people/john.doe/@self
         */
-       public function handleGet($params, $token)
+       public function handleGet(RestRequestItem $requestItem)
        {
-               $userId = UserId::fromJson($params[1]);
-               $groupId = GroupId::fromJson($params[2]);
-               $optionalPersonId = false;
-               if (count($params) > 3) {
-                       $optionalPersonId = $params[3];
-               }
-               $fields = !empty($_GET['fields']) ? explode(',', 
$_GET['fields']) : null;
-               if ($optionalPersonId || $groupId->getType() == 'self') {
+               $requestItem->parseUrlWithTemplate(self::$PEOPLE_PATH);
+               $parameters = $requestItem->getParameters();
+               $optionalPersonId = in_array('personId', $parameters) ? 
$parameters['personId'] : null;
+               $fields = 
$requestItem->getFieldsWithDefaultValue(self::$DEFAULT_PERSON_FIELDS);
+               if ($optionalPersonId || $requestItem->getGroup()->getType() == 
'self') {
                        //FIXME same logic as the java code here, but doesn't 
seem to do much with the optionalPersonId which seems odd
-                       return $this->service->getPerson($userId, $groupId, 
$fields, $token);
+                       return 
$this->service->getPerson($requestItem->getUser(), $requestItem->getGroup(), 
$fields, $requestItem->getToken());
                }
-               $sort = !empty($_GET['orderBy']) && in_array($_GET['orderBy'], 
PeopleService::$sortOrder) ? $_GET['orderBy'] : '';
-               $filter = !empty($_GET['filterBy']) && 
in_array($_GET['filterBy'], PeopleService::$filterType) ? $_GET['filterBy'] : 
'';
-               $first = !empty($_GET['startIndex']) && 
is_numeric($_GET['startIndex']) ? $_GET['startIndex'] : 0;
-               $max = !empty($_GET['count']) && is_numeric($_GET['count']) ? 
$_GET['count'] : 0;
-               return $this->service->getPeople($userId, $groupId, $sort, 
$filter, $first, $max, $fields, $token);
+               return $this->service->getPeople($requestItem->getUser(), 
$requestItem->getGroup(), $requestItem->getOrderBy(), 
$requestItem->getFilterBy(), $requestItem->getStartIndex(), 
$requestItem->getCount(), $fields, $requestItem->getToken());
        }
 
-       public function handleDelete($params, $token)
+       public function handleDelete(RestRequestItem $requestItem)
        {
-           return new ResponseItem(BAD_REQUEST, "You can't delete people.", 
null);
+               return new ResponseItem(BAD_REQUEST, "You can't delete 
people.", null);
        }
-       
-       public function handlePost($params, $token)
+
+       public function handlePost(RestRequestItem $requestItem)
        {
-           return new ResponseItem(NOT_IMPLEMENTED, "You can't edit people 
right now.", null);
+               return new ResponseItem(NOT_IMPLEMENTED, "You can't edit people 
right now.", null);
        }
 
-       public function handlePut($params, $token)
+       public function handlePut(RestRequestItem $requestItem)
        {
-           return new ResponseItem(NOT_IMPLEMENTED, "You can't add people 
right now.", null); 
+               return new ResponseItem(NOT_IMPLEMENTED, "You can't add people 
right now.", null);
        }
 }
\ No newline at end of file

Modified: incubator/shindig/trunk/php/src/socialrest/http/RestServlet.php
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/php/src/socialrest/http/RestServlet.php?rev=671511&r1=671510&r2=671511&view=diff
==============================================================================
--- incubator/shindig/trunk/php/src/socialrest/http/RestServlet.php (original)
+++ incubator/shindig/trunk/php/src/socialrest/http/RestServlet.php Wed Jun 25 
04:21:42 2008
@@ -39,19 +39,21 @@
 require 'src/socialrest/UserId.php';
 require 'src/socialrest/ResponseItem.php';
 require 'src/socialrest/RestfulCollection.php';
+require 'src/socialrest/http/RestRequestItem.php';
 
 /*
  * See:
  * 
http://sites.google.com/a/opensocial.org/opensocial/Technical-Resources/opensocial-specification----implementation-version-08/restful-api-specification
- * OpenSocial uses standard HTTP methods: GET to retrieve, PUT to update in 
place, POST to create new, and DELETE to remove. 
+ * OpenSocial uses standard HTTP methods: GET to retrieve, PUT to update in 
place, POST to create new, and DELETE to remove.
  * POST is special; it operates on collections and creates new activities, 
persons, or app data within those collections,
  * and returns the base URI for the created resource in the Location: header, 
per AtomPub semantics.
- * 
+ *
  * Error status is returned by HTTP error code, with the error message in the 
html's body
  */
 
 //NOTE TO SELF: delete should respond with a 204 No Content to indicate 
success?
 
+
 /*
  * Internal error code representations, these get translated into http codes 
in the outputError() function
  */
@@ -65,51 +67,109 @@
 class RestException extends Exception {}
 
 class RestServlet extends HttpServlet {
+       
+       private static $JSON_BATCH_ROUTE = "jsonBatch";
 
        public function doPost($method = 'POST')
        {
                $this->setNoCache(true);
                $this->noHeaders = true;
-               try {
-                       // use security token, for now this is required
-                       // (later oauth should also be a way to specify this 
info)
-                       $token = $this->getSecurityToken();
-                       $params = $this->getListParams();
-                       switch ($params[0]) {
-                               case 'people':
-                                       $class = 'PersonHandler';
-                                       break;
-                               case 'activities':
-                                       $class = 'ActivityHandler';
-                                       break;
-                               case 'appdata':
-                                       $class = 'AppDataHandler';
-                                       break;
-                               //TODO add 'groups' here
-                               default:
-                                       $response = new 
ResponseItem(NOT_IMPLEMENTED, "{$params[0]} is not implemented");
-                                       break;
-                       }
-                       if (class_exists($class, true)) {
-                               $class = new $class(null);
-                               $response = $class->handleMethod($method, 
$params, $token);
+               // use security token, for now this is required
+               // (later oauth should also be a way to specify this info)
+               $token = $this->getSecurityToken();
+               $req = null;
+               if ($this->isBatchUrl()) {
+                       $req = $this->handleBatchRequest($token);
+                       echo json_encode(array("responses" => $req, "error" => 
false));
+               } else {
+                       $responseItem = $this->handleSingleRequest($token, 
$method);
+                       echo json_encode($responseItem);
+               }
+       }
+
+       private function handleSingleRequest($token, $method)
+       {
+               $params = $this->getListParams();
+               $requestItem = new RestRequestItem();
+               $url = $this->getUrl();
+               $requestParam = $this->getRequestParams();
+               $requestItem->createRequestItem($url, $token, $method, $params, 
$requestParam);
+               $responseItem = $this->getResponseItem($requestItem);
+               return $responseItem;
+       }
+
+       private function getRouteFromParameter($pathInfo)
+       {
+               $pathInfo = substr($pathInfo, 1);
+               $indexOfNextPathSeparator = strpos($pathInfo, "/");
+               return $indexOfNextPathSeparator != - 1 ? substr($pathInfo, 0, 
$indexOfNextPathSeparator) : $pathInfo;
+       }
+
+       private function getResponseItem(RestRequestItem $requestItem)
+       {
+               $path = $this->getRouteFromParameter($requestItem->getUrl());
+               switch ($path) {
+                       case 'people':
+                               $class = 'PersonHandler';
+                               break;
+                       case 'activities':
+                               $class = 'ActivityHandler';
+                               break;
+                       case 'appdata':
+                               $class = 'AppDataHandler';
+                               break;
+                       //TODO add 'groups' here
+                       default:
+                               $response = new ResponseItem(NOT_IMPLEMENTED, 
"{$path} is not implemented");
+                               break;
+               }
+               if (class_exists($class, true)) {
+                       $class = new $class(null);
+                       $response = $class->handleMethod($requestItem);
+               }
+               if ($this->getOutputFormat() == 'json') {
+                       if ($this->isBatchUrl()) {
+                               return $response;
+                       } else {
+                               //If the method exists, its an ResponseItem 
Error
+                               if ($response->getError() != null) {
+                                       $this->outputError($response);
+                               }
+                               return $response->getResponse();
                        }
-               } catch (Exception $e) {
-                       $response = new ResponseItem(INTERNAL_ERROR, 
$e->getMessage(), null);
+               } else {        // output atom format
+               }
+       }
+
+       private function getRequestParams()
+       {
+               $post = in_array('request', $_POST) != null ? $_POST['request'] 
: null;
+               $requestParam = isset($GLOBALS['HTTP_RAW_POST_DATA']) ? 
$GLOBALS['HTTP_RAW_POST_DATA'] : $post;
+               if (get_magic_quotes_gpc()) {
+                       $requestParam = stripslashes($requestParam);
                }
-               if (!is_object($response) || $response->getError()) {
-                       // error responses are the same for json and atom (ie: 
http error codes + message in body)
-                       if (!$response instanceof ResponseItem) {
-                               // just incase ..
-                               $response = new ResponseItem(INTERNAL_ERROR, 
"Internal Server Error, return value is not a response item");
+               $requests = json_decode($requestParam);
+               if ($requests == (isset($GLOBALS['HTTP_RAW_POST_DATA']) ? 
$GLOBALS['HTTP_RAW_POST_DATA'] : $post)) {
+                       return new ResponseItem(BAD_REQUEST, "Malformed json 
string");
+               }
+               return $requests;
+       }
+
+       private function handleBatchRequest($token)
+       {
+               // we support both a raw http post (without 
application/x-www-form-urlencoded headers) like java does
+               // and a more php / curl safe version of a form post with 
'request' as the post field that holds the request json data
+               if (isset($GLOBALS['HTTP_RAW_POST_DATA']) || 
isset($_POST['request'])) {
+                       $requests = $this->getRequestParams();
+                       $responses = array();
+                       foreach ($requests as $key => $value) {
+                               $requestItem = new RestRequestItem();
+                               
$requestItem->createRequestItemWithRequest($value, $token);
+                               $responses[$key] = 
$this->getResponseItem($requestItem);
                        }
-                       $this->outputError($response);
+                       return $responses;
                } else {
-                       if ($this->getOutputFormat() == 'json') {
-                                       echo 
json_encode($response->getResponse());
-                       } else {        
-                               // output atom format
-                       }
+                       throw new Exception("No post data set");
                }
        }
 
@@ -127,7 +187,7 @@
        {
                $this->doPost('DELETE');
        }
-       
+
        private function outputError(ResponseItem $response)
        {
                $errorMessage = $response->getErrorMessage();
@@ -151,7 +211,7 @@
                        default:
                                $code = '500 Internal Server Error';
                                break;
-                               
+               
                }
                header("HTTP/1.0 $code", true);
                echo "$code - $errorMessage";
@@ -174,7 +234,7 @@
 
        private function getOutputFormat()
        {
-               return isset($_GET['format']) && $_GET['format'] == 'atom' ? 
'atom' : 'json';
+               return isset($_POST['format']) && $_POST['format'] == 'atom' ? 
'atom' : 'json';
        }
 
        private function getListParams()
@@ -187,4 +247,14 @@
                $restParams = explode('/', $uri);
                return $restParams;
        }
+
+       private function getUrl()
+       {
+               return substr($_SERVER["REQUEST_URI"], 
strlen(Config::get('web_prefix') . '/social/rest'));
+       }
+
+       public function isBatchUrl()
+       {
+               return strrpos($_SERVER["REQUEST_URI"], 
RestServlet::$JSON_BATCH_ROUTE) > 0;
+       }
 }


Reply via email to