Now that the OpenSocial REST API and the Shindig implementation is stabilizing, we can finalize the SocialSite REST API – which is OpenSocial plus our extensions that support friending, groups, enchanced search and gadget management. This proposal presents a way to do that.
Currently, our SocialSite extensions are implemented in an adhoc fashion: a simple Servlet that parses the incoming URL and uses a large and unweildy if-else statement to decide which POJOs to create, retrieve, update or delete and to return JSON data. Only JSON is supported, unlike OpenSocial APIs which support JSON, XML and Atom. On the client-side, the SocialSite JavaScript API is different from the OpenSocial web services in many ways and does not use the Shindig infrastructure for supporting batching of calls.
By abandoning our adhoc implementation and instead implementing our web services via Shindig ~DataRequestHandlers on the server-side and using standard OpenSocial request facilities the server-side we get these benefits:
The rest of this document presents a design for our new web services in terms of URIs and HTTP actions/verbs POST, GET, PUT and DELETE as well as notes on how to implement these new web services using the framework provided by Shindig.
The proposal is based on these specifications and codebases:
(incubating) - trunk rev. 694454
- John Panzer's notes on RESTful approach to friending
On the server-side, each URI root such as '/people' or '/activities' is handled by a ~DataRequestHandler with methods for POST, GET, PUT and DELETE. These methods accept and return Java beans and Shindig specific collection classes that hold Java beans. Shindig takes care of converting POJOS to and from XML, Atom and JSON formats.
Note that, though we are talking about REST and URIs and HTTP verbs, these same handlers are used for JSON-RPC implementation and by implementing them we get support for both REST and JSON-RPC interaction -- all taken care of by Shinding.
This section lays out the SocialSite extensions to OpenSocial by explaining each of the handlers that we will implement or modify to add the URIs and actions we need.
We will extend the Shindig PersonHandlerImpl, which supports the '/people' APIs discussed in the. We will add support for the new URIs and actions below.
POJO type: returns and accepts Profiles
/people/@all [?search={searchString}]
GET - get all people
/people/{userId}/{groupId}
GET - members of a group
/people/{userId}/@union/{groupId1}/{groupId2}
GET - union of members in two groups
To create a friend connection, you post a Profile representation of the person you wish to friend to your friends collection:
/people/{yourUserId}/@friends
To get a list of people who have invited you to be a friend, do a get on:
/people/{yourUserId}/@requests
To accept a friendship, post a Profile representation of the requesting person to your friend collection:
/people/{yourUserId}/@friends
To ignore a friendship, do a delete on:
/people/{yourUserId}/@requests/{userId}
POJO type: returns GroupDefinition
/groupdef GET - get group definition
POJO type: Returns and accepts Groups
Types of groups:
NOTE that we will implement only public groups for October milestone
/groups/@public [?search={searchString}]
GET - get list of all public groups defined in system
POST - create a new public group
/groups/@public/{groupId}
GET - get public group
PUT - update a public group that you are the admin of
DELETE - delete a public group that you are the admin of
/groups/{userId}
GET - get list all groups (public, private and closed) associated with a user
/groups/{userId}/@friends
GET - get list of groups (public, private and closed) that friends belong to
POJO type: returns and accepts Profiles
Do this inside the people handler!
/members/{groupId}
GET - get list of members in group
POST - add a new member to the group
/members/{groupId}/{userId}
PUT - update a member in group
DELETE - delete a member in group
To ask to join a group, post a sufficient Profile representation of yourself to:
/members/{groupId}
To get a list of incoming group membership requests:
/members/@requestsTo accept an incoming group request, post sufficient Group representation to:
/members/{groupId}
To reject an incoming group request, do a delete on the request:
/members/{groupId}/@requests/{userId}
POJO type: Returns and accepts MessageContents
/messages/{userId}/outbox
POST - create a new message, recipient(s) should be specified in content
GET - get sent messages for user [added by Bobby]
/messages/{userId}/inbox
GET - get messages for user
/messages/{userId}/inbox/{messageId}
GET - get individual message
PUT - update message for user (i.e. mark it as read)
DELETE - delete a message
POJO type: Returns SearchResults
/search/{searchString}
GET - get combined search results
POJO type: Returns and accepts InstalledGadgets
/gadgets/@all [?search={searchString}]
GET - all gadgets known to system, with optional search string
/gadgets/@user/{userId} - [?destination={destinationId}]
GET - all user's installed gadgets
/gadgets/@user/{userId}
POST - install a gadget for user (destination specified in content)
/gadgets/@group/{groupId} - [?destination={destinationId}]
GET - all group's installed gadgets
/gadgets/@group/{groupId}
POST - install a gadget for group (destination specified in content)
For each handler you implement:
Define a simple POJO to represent that type and provide a way to serialize and de-serialize the POJO to XML and JSON. Shindig expects us to return POJO objects, which Shindig will automatically convert to either JSON or XML, depending on what was requested.
In some cases, we can use our existing POJOs for this -- but I expect that some of our POJOs expose more information than we want to return to the client. In those cases, we might have to develop simple wrapper POJOs that expose just the things we want.
Implement the get(), post(), put() and delete() methods as needed. Define a URI parsing template that can be applied to incoming RequestItems, use the RequestItem to get data from the request in the form of POJOs. See our existing handler implementationsfor examples.
Guice is used to inject handlers into the Shindig Servlet, so you will need to add your handlers to our CustomHandlerProvider.
Define a JavaScript object to match your Java POJO and code thatcan construct this from JSON data. See sectionprivacy.js for an example of how to do this.
Define JavaScript methods to create needed requests and process the responses. If you support full CRUD for your data type then you'll need four methods like so (see socialsite.js for examples):
Modify your existing SocialSite Gadgets to use your new methods instead of the old deprecated JS APIs.
Delete your old and no longer needed code from the SocialSiteGadgetDataServlet and socialsite.js.
This section lists URI patterns from the old REST API and, in highlights, explains which parts of the new REST API to use instead.
/profiles
getOnProfilesUrl
DONE
/profiles/{ownerId}
getOnProfilesAndOwnerUrl
putOnProfileUrl
DONE
/allprofiles
getOnAllProfilesUrl
Use PersonHandlerImpl /people/@all instead
/profiles/{ownerId}/friendsgroups
getOnFriendsGroupsUrl - returns - Collection of Groups POJOs in JSON form
Use GroupHandler /groups/@friends insead
/profiles/{ownerId}/request/{friendId}
putOnRequestFriendUrl
use POST to /people/{userId}/@friends
/profiles/{ownerId}/accept/{friendId}
putOnAcceptFriendUrl
use POST to /people/{userId}/@friends instead
/profiles/{ownerId}/reject/{friendId}
See notes above
/groups
getOnGroupsUrl - returns - GroupDefinition in JSON form
Use GroupDefinitionHandler /groupdef instead
/groups/{groupHandle}
getOnGroupsAndGroupIdUrl - returns - Group POJO in JSON form
putOnGroupUrl - updates - Group POJO from JSON data
Use GroupHandler and /groups/@public and /groups/@public/{groupId} instead
/groups/{groupHandle}/members
getOnGroupMembersGroupsUrl - returns - Group POJOs
Use PersonHandlerImpl /people/{userId}/{groupId} instead
/groups/{groupHandle1}/{groupHandle2}
getOnCommonGroupMembersUrl - Group POJOs
Use PersonHandlerImpl /people/{userId}/@union/{groupId1}/{groupId2} instead
/groups/{groupHandle}/activities
getOnGroupActivities - Activity POJOs
Use standard OpenSocial /activities/{userId}/{groupId} instead
/groups/{groupHandle}/myfriends/{ownerId}
getMyFriendsInGroupUrl - returns - Profile POJOs
Use standard OpenSocial /people/{userId}/{groupId} instead
/groups/{groupHandle}/admin/{ownerId}
getOnGroupAdminsUrl - returns - type value of MEMBER or ADMIN
Instead, use a field in the group to indicate user's standing
/currentgroup
getOnCurrentGroupUrl - Group POJO for current group
Instead, passing groupId to gadget and get group by ID
/mygroups
getOnMyGroupsUrl - returns - Group POJOs
Use standard OpenSocial API instead /groups/{userId}
/mygroups/add/{ownerId}
putOnAddRemoveGroupUrl
Instead, use POST to /members/{groupId}
/mygroups/remove/{ownerId}
putOnAddRemoveGroupUrl
Instead, use DELETE on /members/{groupId}/{userId}
/allgroups
getOnAllGroupsUrl
Use /groups/@public instead
/groups/{groupHandle}/create/{ownerId}
putOnCreateGroup
Use POST to /groups/@public instead
/groups/{groupHandle}/invite/{ownerId}
putOnInviteFriendToGroupUrl
Use POST to /members/{groupId} instead
/groups/{groupHande}/{ownerId}/accept/{memberId}
putOnAcceptGroupMemberUrl
See notes above on joining groups
/groups/{groupHande}/{ownerId}/reject/{memberId}
putOnRejectGroupMemberUrl
See notes above on joining groups
/notifications/{ownerId}
getOnNotificationsAndOwnerUrl
Use GET on /messages/{userId}/inbox instead
/notifications/id/{notficationId}
getOnNotificationId
Use GET on /message/{userId}/inbox/{messageId} instead
/notifications/person/{fromId}/{toId}/{subject}
putOnMessageToPersonUrl
Use POST to /messages/{userId}/outbox with person recipient in data instead
/notifications/group/{fromId}/{toId}/{subject}
putOnMessageToGroupUrl
Use POST to /messages/{userId}/outbox with group recipient in data instead
/notifications/id
deleteOnNotificationUrl
Use GET on /message/{userId}/inbox/{messageId} instead
/search/{searchString}
getOnSearch
Use SearchHandler instead with same URL
/search/profile/{searchString}
getOnProfileSearch
Use PersonHandler and /people/@all [?search={searchString}] instead
/search/group/{searchString}
getOnGroupSearch
Use GroupHandler and /groups/@public [?search={searchString}] instead
/search/gadget/{searchString}
getOnGadgetSearch
Use GadgetHandler and /gadgets [?search={searchString}] instead
/installedgadgets/user/{ownerId}/{destination}
putOnCreateInstalledGadgetForUser
Use POST to /gadgets/@user/{userId} instead
/installedgadgets/group/{groupId}/{destination}
putOnCreateInstalledGadgetForGroup
Use POST /gadgets/@group/{groupId} instead
/installedgadgets/id
deleteOnInstalledGadgetUrl
Use DELETE on either /gadgets/@user/{userId}/{gadgetId} or the group equivalent