Author: lindner
Date: Fri Aug 27 12:00:52 2010
New Revision: 990115

URL: http://svn.apache.org/viewvc?rev=990115&view=rev
Log:
SHINDIG-1410 | Patch from Eric Woods | Album/MediaItem implementation

Added:
    
shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/AlbumHandler.java
    
shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/MediaItemHandler.java
    
shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/spi/AlbumService.java
    
shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/spi/MediaItemService.java
Modified:
    shindig/trunk/content/sampledata/canonicaldb.json
    
shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/core/config/SocialApiGuiceModule.java
    
shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/sample/SampleModule.java
    
shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/sample/spi/JsonDbOpensocialService.java

Modified: shindig/trunk/content/sampledata/canonicaldb.json
URL: 
http://svn.apache.org/viewvc/shindig/trunk/content/sampledata/canonicaldb.json?rev=990115&r1=990114&r2=990115&view=diff
==============================================================================
--- shindig/trunk/content/sampledata/canonicaldb.json (original)
+++ shindig/trunk/content/sampledata/canonicaldb.json Fri Aug 27 12:00:52 2010
@@ -369,6 +369,25 @@
                }
        }]
 },
+"albums" : {
+       "john.doe": [{
+               "id" : "album123",
+               "ownerId" : "john.doe",
+               "thumbnailUrl" : 
"http://pages.example.org/albums/4433221-tn.png";,
+               "title" : "Example Album",
+               "description" : "This is an example album, and this text is an 
example description",
+               "location" : { "latitude": 0, "longitude": 0 }
+       }]
+},
+"mediaItems" : {
+       "john.doe": [{
+               "id" : "mediaItem123",
+               "albumId" : "album123",
+               "mimeType" : "image/jpeg",
+               "type" : "image",
+               "url" : 
"http://animals.nationalgeographic.com/staticfiles/NGS/Shared/StaticFiles/animals/images/primary/black-spider-monkey.jpg";
+       }]
+},
 //
 // ----------------------------- Data ---------------------------------------
 //

Modified: 
shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/core/config/SocialApiGuiceModule.java
URL: 
http://svn.apache.org/viewvc/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/core/config/SocialApiGuiceModule.java?rev=990115&r1=990114&r2=990115&view=diff
==============================================================================
--- 
shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/core/config/SocialApiGuiceModule.java
 (original)
+++ 
shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/core/config/SocialApiGuiceModule.java
 Fri Aug 27 12:00:52 2010
@@ -18,6 +18,9 @@
 
 package org.apache.shindig.social.core.config;
 
+import java.util.List;
+import java.util.Set;
+
 import org.apache.shindig.auth.AnonymousAuthenticationHandler;
 import org.apache.shindig.auth.AuthenticationHandler;
 import org.apache.shindig.common.servlet.ParameterFetcher;
@@ -30,13 +33,12 @@ import org.apache.shindig.social.core.oa
 import org.apache.shindig.social.core.util.BeanXStreamAtomConverter;
 import org.apache.shindig.social.core.util.xstream.XStream081Configuration;
 import org.apache.shindig.social.opensocial.service.ActivityHandler;
+import org.apache.shindig.social.opensocial.service.AlbumHandler;
 import org.apache.shindig.social.opensocial.service.AppDataHandler;
+import org.apache.shindig.social.opensocial.service.MediaItemHandler;
 import org.apache.shindig.social.opensocial.service.MessageHandler;
 import org.apache.shindig.social.opensocial.service.PersonHandler;
 
-import java.util.List;
-import java.util.Set;
-
 import com.google.common.collect.ImmutableSet;
 import com.google.inject.AbstractModule;
 import com.google.inject.TypeLiteral;
@@ -83,6 +85,6 @@ public class SocialApiGuiceModule extend
    */
   protected Set<Class<?>> getHandlers() {
     return ImmutableSet.<Class<?>>of(ActivityHandler.class, 
AppDataHandler.class,
-        PersonHandler.class, MessageHandler.class);
+        PersonHandler.class, MessageHandler.class, AlbumHandler.class, 
MediaItemHandler.class);
   }
 }

Added: 
shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/AlbumHandler.java
URL: 
http://svn.apache.org/viewvc/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/AlbumHandler.java?rev=990115&view=auto
==============================================================================
--- 
shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/AlbumHandler.java
 (added)
+++ 
shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/AlbumHandler.java
 Fri Aug 27 12:00:52 2010
@@ -0,0 +1,185 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.shindig.social.opensocial.service;
+
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Future;
+
+import org.apache.shindig.config.ContainerConfig;
+import org.apache.shindig.protocol.HandlerPreconditions;
+import org.apache.shindig.protocol.Operation;
+import org.apache.shindig.protocol.ProtocolException;
+import org.apache.shindig.protocol.RequestItem;
+import org.apache.shindig.protocol.Service;
+import org.apache.shindig.social.opensocial.model.Album;
+import org.apache.shindig.social.opensocial.spi.AlbumService;
+import org.apache.shindig.social.opensocial.spi.CollectionOptions;
+import org.apache.shindig.social.opensocial.spi.UserId;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.inject.Inject;
+
+/*
+ * Receives and delegates requests to the OpenSocial Album service.
+ * 
+ * TODO: test cases
+ */
+...@service(name = "albums", path = "/{userId}+/{groupId}/{albumId}+")
+public class AlbumHandler {
+
+       private final AlbumService service;
+       private final ContainerConfig config;
+
+       @Inject
+       public AlbumHandler(AlbumService service, ContainerConfig config) {
+               this.service = service;
+               this.config = config;
+       }
+
+       /*
+        * Handles create operations.
+        * 
+        * Allowed end-points: /albums/{userId}/@self
+        * 
+        * Examples: /albums/john.doe/@self
+        */
+       @Operation(httpMethods = "POST", bodyParam = "album")
+       public Future<?> create(SocialRequestItem request) throws 
ProtocolException {
+               // Retrieve userIds and albumIds
+               Set<UserId> userIds = request.getUsers();
+               List<String> albumIds = request.getListParameter("albumId");
+
+               // Preconditions - exactly one userId specified, no albumIds 
specified
+               HandlerPreconditions.requireNotEmpty(userIds, "No userId 
specified");
+               HandlerPreconditions.requireSingular(userIds, "Multiple userIds 
not supported");
+               HandlerPreconditions.requireEmpty(albumIds, "Cannot specify 
albumId in create");
+
+               return service.createAlbum(Iterables.getOnlyElement(userIds),
+                               request.getAppId(),
+                               request.getTypedParameter("album", Album.class),
+                               request.getToken());
+       }
+
+       /*
+        * Handles retrieve operations.
+        * 
+        * Allowed end-points: /albums/{userId}+/{groupId}/{albumId}+
+        * 
+        * Examples: /albums/@me/@self /albums/john.doe/@self/1,2
+        * /albums/john.doe,jane.doe/@friends
+        */
+       @Operation(httpMethods = "GET")
+       public Future<?> get(SocialRequestItem request) throws 
ProtocolException {
+               // Get user, group, and album IDs
+               Set<UserId> userIds = request.getUsers();
+               Set<String> optionalAlbumIds = ImmutableSet.copyOf(request
+                               .getListParameter("albumId"));
+
+               // At least one userId must be specified
+               HandlerPreconditions.requireNotEmpty(userIds, "No userId 
specified");
+
+               // If multiple userIds specified, albumIds must not be specified
+               if (userIds.size() > 1 && !optionalAlbumIds.isEmpty()) {
+                       throw new IllegalArgumentException("Cannot fetch same 
albumIds for multiple userIds");
+               }
+
+               // Retrieve albums by ID
+               if (!optionalAlbumIds.isEmpty()) {
+                       if (optionalAlbumIds.size() == 1) {
+                               return 
service.getAlbum(Iterables.getOnlyElement(userIds),
+                                               request.getAppId(), 
request.getFields(),
+                                               
optionalAlbumIds.iterator().next(), request.getToken());
+                       } else {
+                               return 
service.getAlbums(Iterables.getOnlyElement(userIds),
+                                               request.getAppId(), 
request.getFields(),
+                                               new CollectionOptions(request), 
optionalAlbumIds,
+                                               request.getToken());
+                       }
+               }
+
+               // Retrieve albums by group
+               return service.getAlbums(userIds, request.getGroup(), request
+                               .getAppId(), request.getFields(),
+                               new CollectionOptions(request), 
request.getToken());
+       }
+
+       /*
+        * Handles update operations.
+        * 
+        * Allowed end-points: /albums/{userId}/@self/{albumId}
+        * 
+        * Examples: /albums/john.doe/@self/1
+        */
+       @Operation(httpMethods = "PUT", bodyParam = "album")
+       public Future<?> update(SocialRequestItem request) throws 
ProtocolException {
+               // Retrieve userIds and albumIds
+               Set<UserId> userIds = request.getUsers();
+               List<String> albumIds = request.getListParameter("albumId");
+
+               // Enforce preconditions - exactly one user and one album 
specified
+               HandlerPreconditions.requireNotEmpty(userIds, "No userId 
specified");
+               HandlerPreconditions.requireSingular(userIds, "Multiple userIds 
not supported");
+               HandlerPreconditions.requireNotEmpty(albumIds, "No albumId 
specified");
+               HandlerPreconditions.requireSingular(albumIds, "Multiple 
albumIds not supported");
+
+               return service.updateAlbum(Iterables.getOnlyElement(userIds),
+                               request.getAppId(),
+                               request.getTypedParameter("album", Album.class),
+                               Iterables.getOnlyElement(albumIds), 
request.getToken());
+       }
+
+       /*
+        * Handles delete operations.
+        * 
+        * Allowed end-points: /albums/{userId}/@self/{albumId}
+        * 
+        * Examples: /albums/john.doe/@self/1
+        */
+       @Operation(httpMethods = "DELETE")
+       public Future<?> delete(SocialRequestItem request) throws 
ProtocolException {
+               // Get user and album ID
+               Set<UserId> userIds = request.getUsers();
+               String albumId = request.getParameter("albumId");
+
+               // Enforce preconditions - userIds must contain exactly one 
element
+               HandlerPreconditions.requireNotEmpty(userIds, "No userId 
specified");
+               HandlerPreconditions.requireSingular(userIds, "Multiple userIds 
not supported");
+
+               // Service request
+               return service.deleteAlbum(Iterables.getOnlyElement(userIds),
+                               request.getAppId(), albumId, 
request.getToken());
+       }
+       
+       /*
+        * Retrieves supported fields for the albums service.
+        */
+       @Operation(httpMethods = "GET", path = "/@supportedFields")
+       public List<Object> supportedFields(RequestItem request) {
+               String container = 
firstNonNull(request.getToken().getContainer(),
+                               ContainerConfig.DEFAULT_CONTAINER);
+               return config.getList(container,
+                               
"${Cur['gadgets.features'].opensocial.supportedFields.album}");
+       }
+
+       private static <T> T firstNonNull(T first, T second) {
+               return first != null ? first : 
Preconditions.checkNotNull(second);
+       }
+}
\ No newline at end of file

Added: 
shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/MediaItemHandler.java
URL: 
http://svn.apache.org/viewvc/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/MediaItemHandler.java?rev=990115&view=auto
==============================================================================
--- 
shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/MediaItemHandler.java
 (added)
+++ 
shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/service/MediaItemHandler.java
 Fri Aug 27 12:00:52 2010
@@ -0,0 +1,202 @@
+package org.apache.shindig.social.opensocial.service;
+
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.Future;
+
+import org.apache.shindig.config.ContainerConfig;
+import org.apache.shindig.protocol.HandlerPreconditions;
+import org.apache.shindig.protocol.Operation;
+import org.apache.shindig.protocol.ProtocolException;
+import org.apache.shindig.protocol.RequestItem;
+import org.apache.shindig.protocol.Service;
+import org.apache.shindig.social.opensocial.model.MediaItem;
+import org.apache.shindig.social.opensocial.spi.CollectionOptions;
+import org.apache.shindig.social.opensocial.spi.MediaItemService;
+import org.apache.shindig.social.opensocial.spi.UserId;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import com.google.inject.Inject;
+
+/*
+ * Receives and delegates requests to the OpenSocial MediaItems service.
+ * 
+ * TODO: test cases
+ */
+...@service(name = "mediaItems", path = 
"/{userId}+/{groupId}/{albumId}/{mediaItemId}+")
+public class MediaItemHandler {
+
+       
+       private final MediaItemService service;
+       private final ContainerConfig config;
+       
+       @Inject
+       public MediaItemHandler(MediaItemService service, ContainerConfig 
config) {
+               this.service = service;
+               this.config = config;
+       }
+       
+       /*
+        * Handles GET operations.
+        * 
+        * Allowed end-points: 
/mediaItems/{userId}+/{groupId}/{albumId}/{mediaItemId}+
+        * 
+        * Examples:    /mediaItems/john.doe/@self
+        *                              /mediaItems/john.doe,jane.doe/@self
+        *                              /mediaItems/john.doe/@self/album123
+        *                              
/mediaItems/john.doe/@self/album123/1,2,3       
+        */
+       @Operation(httpMethods = "GET")
+       public Future<?> get(SocialRequestItem request) throws 
ProtocolException {
+               // Get user, group, album IDs, and MediaItem IDs
+               Set<UserId> userIds = request.getUsers();
+               Set<String> optionalAlbumIds = 
ImmutableSet.copyOf(request.getListParameter("albumId"));
+               Set<String> optionalMediaItemIds = 
ImmutableSet.copyOf(request.getListParameter("mediaItemId"));
+               
+               // At least one userId must be specified
+               HandlerPreconditions.requireNotEmpty(userIds, "No user ID 
specified");
+               
+               // Get Album ID; null if not provided
+               String albumId = null;
+               if (optionalAlbumIds.size() == 1) {
+                       albumId = Iterables.getOnlyElement(optionalAlbumIds);
+               } else if (optionalAlbumIds.size() > 1) {
+                       throw new IllegalArgumentException("Multiple Album IDs 
not supported");
+               }
+               
+               // Cannot retrieve by ID if album ID not provided
+               if (albumId == null && !optionalMediaItemIds.isEmpty()) {
+                       throw new IllegalArgumentException("Cannot fetch by 
MediaItem ID without Album ID");
+               }
+               
+               // Cannot retrieve by ID or album if multiple user's given
+               if (userIds.size() > 1) {
+                       if (!optionalMediaItemIds.isEmpty()) {
+                               throw new IllegalArgumentException("Cannot 
fetch MediaItem by ID for multiple users");
+                       } else if (albumId != null) {
+                               throw new IllegalArgumentException("Cannot 
fetch MediaItem by Album for multiple users");
+                       } 
+               }
+               
+               // Retrieve by ID(s)
+               if (!optionalMediaItemIds.isEmpty()) {
+                       if (optionalMediaItemIds.size() == 1) {
+                               return 
service.getMediaItem(Iterables.getOnlyElement(userIds),
+                                               request.getAppId(), albumId,
+                                               
Iterables.getOnlyElement(optionalMediaItemIds),
+                                               request.getFields(), 
request.getToken());
+                       } else {
+                               return 
service.getMediaItems(Iterables.getOnlyElement(userIds),
+                                               request.getAppId(), albumId, 
optionalMediaItemIds,
+                                               request.getFields(), new 
CollectionOptions(request),
+                                               request.getToken());
+                       }
+               }
+               
+               // Retrieve by Album
+               if (albumId != null) {
+                       return 
service.getMediaItems(Iterables.getOnlyElement(userIds),
+                                       request.getAppId(), albumId, 
request.getFields(),
+                                       new CollectionOptions(request), 
request.getToken());
+               }
+               
+               // Retrieve by users and groups
+               return service.getMediaItems(userIds, request.getGroup(), 
request
+                               .getAppId(), request.getFields(),
+                               new CollectionOptions(request), 
request.getToken());
+       }
+       
+       /*
+        * Handles DELETE operations.
+        * 
+        * Allowed end-points: /mediaItem/{userId}/@self/{albumId}/{mediaItemId}
+        * 
+        * Examples: /mediaItems/john.doe/@self/1/2
+        */
+       @Operation(httpMethods = "DELETE")
+       public Future<?> delete(SocialRequestItem request) throws 
ProtocolException {
+               // Get users, Album ID, and MediaItem ID
+               Set<UserId> userIds = request.getUsers();
+               Set<String> albumIds = 
ImmutableSet.copyOf(request.getListParameter("albumId"));
+               Set<String> mediaItemIds = 
ImmutableSet.copyOf(request.getListParameter("mediaItemId"));
+               
+               // Exactly one user, Album, and MediaItem must be specified
+               HandlerPreconditions.requireNotEmpty(userIds, "No userId 
specified");
+               HandlerPreconditions.requireSingular(userIds, "Exactly one user 
ID must be specified");
+               HandlerPreconditions.requireSingular(albumIds, "Exactly one 
Album ID must be specified");
+               HandlerPreconditions.requireSingular(mediaItemIds, "Exactly one 
MediaItem ID must be specified");
+               
+               // Service request
+               return 
service.deleteMediaItem(Iterables.getOnlyElement(userIds),
+                               request.getAppId(), 
Iterables.getOnlyElement(albumIds),
+                               Iterables.getOnlyElement(mediaItemIds), 
request.getToken());
+       }
+       
+       /*
+        * Handles POST operations.
+        * 
+        * Allowed end-points: /mediaItems/{userId}/@self/{albumId}
+        * 
+        * Examples: /mediaItems/john.doe/@self/1
+        */
+       @Operation(httpMethods = "POST", bodyParam = "mediaItem")
+       public Future<?> create(SocialRequestItem request) throws 
ProtocolException {
+               // Retrieve userIds and albumIds
+               Set<UserId> userIds = request.getUsers();
+               Set<String> albumIds = 
ImmutableSet.copyOf(request.getListParameter("albumId"));
+               
+               // Exactly one user and Album must be specified
+               HandlerPreconditions.requireNotEmpty(userIds, "No userId 
specified");
+               HandlerPreconditions.requireSingular(userIds, "Exactly one user 
ID must be specified");
+               HandlerPreconditions.requireSingular(albumIds, "Exactly one 
Album ID must be specified");
+               
+               // Service request
+               return 
service.createMediaItem(Iterables.getOnlyElement(userIds),
+                               request.getAppId(), 
Iterables.getOnlyElement(albumIds),
+                               request.getTypedParameter("mediaItem", 
MediaItem.class),
+                               request.getToken());
+       }
+       
+       /*
+        * Handles PUT operations.
+        * 
+        * Allowed end-points: 
/mediaItems/{userId}/@self/{albumId}/{mediaItemId}
+        * 
+        * Examples: /mediaItems/john.doe/@self/1/2
+        */
+       @Operation(httpMethods = "PUT", bodyParam = "mediaItem")
+       public Future<?> update(SocialRequestItem request) throws 
ProtocolException {
+               // Retrieve userIds, albumIds, and mediaItemIds
+               Set<UserId> userIds = request.getUsers();
+               Set<String> albumIds = 
ImmutableSet.copyOf(request.getListParameter("albumId"));
+               Set<String> mediaItemIds = 
ImmutableSet.copyOf(request.getListParameter("mediaItemIds"));
+               
+               // Exactly one user, Album, and MediaItem must be specified
+               HandlerPreconditions.requireNotEmpty(userIds, "No userId 
specified");
+               HandlerPreconditions.requireSingular(userIds, "Exactly one user 
ID must be specified");
+               HandlerPreconditions.requireSingular(albumIds, "Exactly one 
Album ID must be specified");
+               HandlerPreconditions.requireSingular(mediaItemIds, "Exactly one 
MediaItem ID must be specified");
+               
+               // Service request
+               return 
service.updateMediaItem(Iterables.getOnlyElement(userIds),
+                               request.getAppId(), 
Iterables.getOnlyElement(albumIds),
+                               Iterables.getOnlyElement(mediaItemIds),
+                               request.getTypedParameter("mediaItem", 
MediaItem.class),
+                               request.getToken());
+       }
+
+       @Operation(httpMethods = "GET", path = "/@supportedFields")
+       public List<Object> supportedFields(RequestItem request) {
+               // TODO: Would be nice if name in config matched name of 
service.
+               String container = 
firstNonNull(request.getToken().getContainer(),
+                               ContainerConfig.DEFAULT_CONTAINER);
+               return config.getList(container,
+                       
"${Cur['gadgets.features'].opensocial.supportedFields.mediaItem}");
+       }
+
+       private static <T> T firstNonNull(T first, T second) {
+               return first != null ? first : 
Preconditions.checkNotNull(second);
+       }
+}

Added: 
shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/spi/AlbumService.java
URL: 
http://svn.apache.org/viewvc/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/spi/AlbumService.java?rev=990115&view=auto
==============================================================================
--- 
shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/spi/AlbumService.java
 (added)
+++ 
shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/spi/AlbumService.java
 Fri Aug 27 12:00:52 2010
@@ -0,0 +1,121 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.shindig.social.opensocial.spi;
+
+import java.util.Set;
+import java.util.concurrent.Future;
+
+import org.apache.shindig.auth.SecurityToken;
+import org.apache.shindig.protocol.ProtocolException;
+import org.apache.shindig.protocol.RestfulCollection;
+import org.apache.shindig.social.opensocial.model.Album;
+
+/*
+ * The AlbumService interface defines the service provider interface for
+ * creating, retrieving, updating, and deleting OpenSocial albums.
+ */
+public interface AlbumService {
+
+       /*
+        * Retrieves a single album for the given user with the given album ID.
+        * 
+        * @param userId        Identifies the person to retrieve the album from
+        * @param appId         Identifies the application to retrieve the 
album from
+        * @param fields        Indicates the fields to return.  Empty set 
implies all
+        * @param albumId       Identifies the album to retrieve
+        * @param token         A valid SecurityToken
+        * 
+        * @return a response item with the requested album
+        */
+       Future<Album> getAlbum(UserId userId, String appId, Set<String> fields,
+                       String albumId, SecurityToken token) throws 
ProtocolException;
+
+       /*
+        * Retrieves albums for the given user with the given album IDs.
+        * 
+        * @param userId        Identifies the person to retrieve albums for
+        * @param appId         Identifies the application to retrieve albums 
from
+        * @param fields        The fields to return; empty set implies all
+        * @param options       The sorting/filtering/pagination options
+        * @param albumIds      The set of album ids to fetch
+        * @param token         A valid SecurityToken
+        * 
+        * @return a response item with requested albums
+        */
+       Future<RestfulCollection<Album>> getAlbums(UserId userId, String appId,
+                       Set<String> fields, CollectionOptions options,
+                       Set<String> albumIds, SecurityToken token) throws 
ProtocolException;
+
+       /*
+        * Retrieves albums for the given user and group.
+        * 
+        * @param userIds       Identifies the users to retrieve albums from
+        * @param groupId       Identifies the group to retrieve albums from
+        * @param appId         Identifies the application to retrieve albums 
from
+        * @param fields        The fields to return.  Empty set implies all
+        * @param options       The sorting/filtering/pagination options
+        * @param token         A valid SecurityToken
+        * 
+        * @return a response item with the requested albums
+        */
+       Future<RestfulCollection<Album>> getAlbums(Set<UserId> userIds,
+                       GroupId groupId, String appId, Set<String> fields,
+                       CollectionOptions options, SecurityToken token)
+                       throws ProtocolException;
+       
+       /*
+        * Deletes a single album for the given user with the given album ID.
+        * 
+        * @param userId        Identifies the user to delete the album from
+        * @param appId         Identifies the application to delete the album 
from
+        * @param albumId       Identifies the album to delete
+        * @param token         A valid SecurityToken
+        * 
+        * @return a response item containing any errors
+        */
+       Future<Void> deleteAlbum(UserId userId, String appId, String albumId,
+                       SecurityToken token) throws ProtocolException;
+       
+       /*
+        * Creates an album for the given user.
+        * 
+        * @param userId        Identifies the user to create the album for
+        * @param appId         Identifies the application to create the album 
in
+        * @param album         The album to create
+        * @param token         A valid SecurityToken
+        * 
+        * @return a response containing any errors
+        */
+       Future<Void> createAlbum(UserId userId, String appId, Album album,
+                       SecurityToken token) throws ProtocolException;
+       
+       /*
+        * Updates an album for the given user.  The album ID specified in the 
REST
+        * end-point is used, even if the album also defines an ID.
+        * 
+        * @param userId        Identifies the user to update the album for
+        * @param appId         Identifies the application to update the album 
in
+        * @param album         Defines the updated album
+        * @param albumId       Identifies the ID of the album to update
+        * @param token         A valid SecurityToken
+        * 
+        * @return a response containing any errors
+        */
+       Future<Void> updateAlbum(UserId userId, String appId, Album album,
+                       String albumId, SecurityToken token) throws 
ProtocolException;
+}
\ No newline at end of file

Added: 
shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/spi/MediaItemService.java
URL: 
http://svn.apache.org/viewvc/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/spi/MediaItemService.java?rev=990115&view=auto
==============================================================================
--- 
shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/spi/MediaItemService.java
 (added)
+++ 
shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/opensocial/spi/MediaItemService.java
 Fri Aug 27 12:00:52 2010
@@ -0,0 +1,129 @@
+package org.apache.shindig.social.opensocial.spi;
+
+import java.util.Set;
+import java.util.concurrent.Future;
+
+import org.apache.shindig.auth.SecurityToken;
+import org.apache.shindig.protocol.ProtocolException;
+import org.apache.shindig.protocol.RestfulCollection;
+import org.apache.shindig.social.opensocial.model.MediaItem;
+
+/*
+ * The MediaItemService interface defines the service provider interface for
+ * creating, retrieving, updating, and deleting OpenSocial MediaItems.
+ */
+public interface MediaItemService {
+       
+       /*
+        * Retrieves a MediaItem by ID.
+        * 
+        * @param userId                Identifies the owner of the MediaItem 
to retrieve
+        * @param appId                 Identifies the application of the 
MeiaItem to retrieve
+        * @param albumId               Identifies the album containing the 
MediaItem
+        * @param mediaItemId   Identifies the MediaItem to retrieve
+        * @param fields                Indicates fields to be returned; empty 
set implies all
+        * @param token                 A valid SecurityToken
+        * 
+        * @return a response item with the requested MediaItem
+        */
+       Future<MediaItem> getMediaItem(UserId userId, String appId, String 
albumId,
+                       String mediaItemId, Set<String> fields, SecurityToken 
token)
+                       throws ProtocolException;
+       
+       /*
+        * Retrieves MediaItems by IDs.
+        * 
+        * @param userId                Identifies the owner of the MediaItems
+        * @param appId                 Identifies the application of the 
MediaItems
+        * @param albumId               Identifies the album containing the 
MediaItems
+        * @param mediaItemIds  Identifies the MediaItems to retrieve
+        * @param fields                Specifies the fields to return; empty 
set implies all
+        * @param options               Sorting/filtering/pagination options
+        * @param token                 A valid SecurityToken
+        * 
+        * @return a response item with the requested MediaItems
+        */
+       Future<RestfulCollection<MediaItem>> getMediaItems(UserId userId,
+                       String appId, String albumId, Set<String> mediaItemIds,
+                       Set<String> fields, CollectionOptions options, 
SecurityToken token)
+                       throws ProtocolException;
+       
+       /*
+        * Retrieves MediaItems by Album.
+        * 
+        * @param userId        Identifies the owner of the MediaItems
+        * @param appId         Identifies the application of the MediaItems
+        * @param albumId       Identifies the Album containing the MediaItems
+        * @param fields        Specifies the fields to return; empty set 
implies all
+        * @param options       Sorting/filtering/pagination options
+        * @param token         A valid SecurityToken
+        * 
+        * @return a response item with the requested MediaItems
+        */
+       Future<RestfulCollection<MediaItem>> getMediaItems(UserId userId,
+                       String appId, String albumId, Set<String> fields,
+                       CollectionOptions options, SecurityToken token)
+                       throws ProtocolException;
+       
+       /*
+        * Retrieves MediaItems by users and groups.
+        * 
+        * @param userIds       Identifies the users that this request is 
relative to
+        * @param groupId       Identifies the users' groups to retrieve 
MediaItems from
+        * @param appId         Identifies the application to retrieve 
MediaItems from
+        * @param fields        The fields to return; empty set implies all
+        * @param options       Sorting/filtering/pagination options
+        * @param token         A valid SecurityToken
+        * 
+        * @return a response item with the requested MediaItems
+        */
+       Future<RestfulCollection<MediaItem>> getMediaItems(Set<UserId> userIds,
+                       GroupId groupId, String appId, Set<String> fields,
+                       CollectionOptions options, SecurityToken token)
+                       throws ProtocolException;
+       
+       /*
+        * Deletes a MediaItem by ID.
+        * 
+        * @param userId                Identifies the owner of the MediaItem 
to delete
+        * @param appId                 Identifies the application hosting the 
MediaItem
+        * @param albumId               Identifies the parent album of the 
MediaItem
+        * @param mediaItemId   Identifies the MediaItem to delete
+        * @param token                 A valid SecurityToken
+        * 
+        * @return a response item containing any errors
+        */
+       Future<Void> deleteMediaItem(UserId userId, String appId, String 
albumId,
+                       String mediaItemId, SecurityToken token) throws 
ProtocolException;
+       
+       /*
+        * Create a MediaItem in the given album for the given user.
+        * 
+        * @param userId                Identifies the owner of the MediaItem 
to create
+        * @param appId                 Identifies the application hosting the 
MediaItem
+        * @param albumId               Identifies the album to contain the 
MediaItem
+        * @param mediaItem             The MediaItem to create
+        * @param token                 A valid SecurityToken
+        * 
+        * @return a response containing any errors
+        */
+       Future<Void> createMediaItem(UserId userId, String appId, String 
albumId,
+                       MediaItem mediaItem, SecurityToken token) throws 
ProtocolException;
+       
+       /*
+        * Updates a MediaItem for the given user.  The MediaItem ID specified 
in
+        * the REST end-point is used, even if the MediaItem also defines an ID.
+        * 
+        * @param userId                Identifies the owner of the MediaItem 
to update
+        * @param appId                 Identifies the application hosting the 
MediaItem
+        * @param albumId               Identifies the album containing the 
MediaItem
+        * @param mediaItemId   Identifies the MediaItem to update
+        * @param mediaItem             The updated MediaItem to persist
+        * @param token                 A valid SecurityToken
+        * 
+        * @return a response containing any errors
+        */
+       Future<Void> updateMediaItem(UserId userId, String appId, String 
albumId,
+                       String mediaItemId, MediaItem mediaItem, SecurityToken 
token)
+                       throws ProtocolException;
+}

Modified: 
shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/sample/SampleModule.java
URL: 
http://svn.apache.org/viewvc/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/sample/SampleModule.java?rev=990115&r1=990114&r2=990115&view=diff
==============================================================================
--- 
shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/sample/SampleModule.java
 (original)
+++ 
shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/sample/SampleModule.java
 Fri Aug 27 12:00:52 2010
@@ -19,7 +19,9 @@ package org.apache.shindig.social.sample
 
 import org.apache.shindig.social.opensocial.oauth.OAuthDataStore;
 import org.apache.shindig.social.opensocial.spi.ActivityService;
+import org.apache.shindig.social.opensocial.spi.AlbumService;
 import org.apache.shindig.social.opensocial.spi.AppDataService;
+import org.apache.shindig.social.opensocial.spi.MediaItemService;
 import org.apache.shindig.social.opensocial.spi.MessageService;
 import org.apache.shindig.social.opensocial.spi.PersonService;
 import org.apache.shindig.social.sample.oauth.SampleOAuthDataStore;
@@ -41,10 +43,11 @@ public class SampleModule extends Abstra
     bind(String.class).annotatedWith(Names.named("shindig.canonical.json.db"))
         .toInstance("sampledata/canonicaldb.json");
     bind(ActivityService.class).to(JsonDbOpensocialService.class);
+    bind(AlbumService.class).to(JsonDbOpensocialService.class);
+    bind(MediaItemService.class).to(JsonDbOpensocialService.class);
     bind(AppDataService.class).to(JsonDbOpensocialService.class);
     bind(PersonService.class).to(JsonDbOpensocialService.class);
     bind(MessageService.class).to(JsonDbOpensocialService.class);
-    
     bind(OAuthDataStore.class).to(SampleOAuthDataStore.class);
   }
 }

Modified: 
shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/sample/spi/JsonDbOpensocialService.java
URL: 
http://svn.apache.org/viewvc/shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/sample/spi/JsonDbOpensocialService.java?rev=990115&r1=990114&r2=990115&view=diff
==============================================================================
--- 
shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/sample/spi/JsonDbOpensocialService.java
 (original)
+++ 
shindig/trunk/java/social-api/src/main/java/org/apache/shindig/social/sample/spi/JsonDbOpensocialService.java
 Fri Aug 27 12:00:52 2010
@@ -18,6 +18,16 @@
 
 package org.apache.shindig.social.sample.spi;
 
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Future;
+
+import javax.servlet.http.HttpServletResponse;
+
 import org.apache.commons.io.IOUtils;
 import org.apache.shindig.auth.SecurityToken;
 import org.apache.shindig.common.util.ImmediateFuture;
@@ -28,13 +38,17 @@ import org.apache.shindig.protocol.Restf
 import org.apache.shindig.protocol.conversion.BeanConverter;
 import org.apache.shindig.protocol.model.SortOrder;
 import org.apache.shindig.social.opensocial.model.Activity;
+import org.apache.shindig.social.opensocial.model.Album;
+import org.apache.shindig.social.opensocial.model.MediaItem;
 import org.apache.shindig.social.opensocial.model.Message;
 import org.apache.shindig.social.opensocial.model.MessageCollection;
 import org.apache.shindig.social.opensocial.model.Person;
 import org.apache.shindig.social.opensocial.spi.ActivityService;
+import org.apache.shindig.social.opensocial.spi.AlbumService;
 import org.apache.shindig.social.opensocial.spi.AppDataService;
 import org.apache.shindig.social.opensocial.spi.CollectionOptions;
 import org.apache.shindig.social.opensocial.spi.GroupId;
+import org.apache.shindig.social.opensocial.spi.MediaItemService;
 import org.apache.shindig.social.opensocial.spi.MessageService;
 import org.apache.shindig.social.opensocial.spi.PersonService;
 import org.apache.shindig.social.opensocial.spi.UserId;
@@ -42,16 +56,6 @@ import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
-import java.util.Collections;
-import java.util.Comparator;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.Future;
-
-import javax.servlet.http.HttpServletResponse;
-
 import com.google.common.collect.ImmutableSortedSet;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
@@ -65,7 +69,7 @@ import com.google.inject.name.Named;
  */
 @Singleton
 public class JsonDbOpensocialService implements ActivityService, 
PersonService, AppDataService,
-    MessageService {
+    MessageService, AlbumService, MediaItemService {
 
   private static final Comparator<Person> NAME_COMPARATOR = new 
Comparator<Person>() {
     public int compare(Person person, Person person1) {
@@ -94,6 +98,16 @@ public class JsonDbOpensocialService imp
    * db["people"] -> Map<Person.Id, Array<Activity>>
    */
   private static final String ACTIVITIES_TABLE = "activities";
+  
+  /**
+   * db["people"] -> Map<Person.Id, Array<Album>>
+   */
+  private static final String ALBUMS_TABLE = "albums";
+  
+  /**
+   * db["people"] -> Map<Person.Id, Array<MediaItem>>
+   */
+  private static final String MEDIAITEMS_TABLE = "mediaItems";
 
   /**
    * db["data"] -> Map<Person.Id, Map<String, String>>
@@ -247,6 +261,7 @@ public class JsonDbOpensocialService imp
         jsonArray = new JSONArray();
         db.getJSONObject(ACTIVITIES_TABLE).put(userId.getUserId(token), 
jsonArray);
       }
+      // TODO (woodser): if used with PUT, duplicate activity would be created?
       jsonArray.put(jsonObject);
       return ImmediateFuture.newInstance(null);
     } catch (JSONException je) {
@@ -623,19 +638,490 @@ public class JsonDbOpensocialService imp
     }
     return ids;
   }
-
-  private JSONObject convertFromActivity(Activity activity, Set<String> fields)
-      throws JSONException {
-    // TODO Not using fields yet
-    return new JSONObject(converter.convertToString(activity));
-  }
-
-  public <T> T filterFields(JSONObject object, Set<String> fields, Class<T> 
clz)
-      throws JSONException {
-    if (!fields.isEmpty()) {
-      // Create a copy with just the specified fields
-      object = new JSONObject(object, fields.toArray(new 
String[fields.size()]));
-    }
-    return converter.convertToObject(object.toString(), clz);
-  }
+  
+       // TODO: not using appId
+       public Future<Album> getAlbum(UserId userId, String appId, Set<String> 
fields,
+                       String albumId, SecurityToken token) throws 
ProtocolException {
+               try {
+                       // First ensure user has a table
+                       String user = userId.getUserId((token));
+                       if (db.getJSONObject(ALBUMS_TABLE).has(user)) {
+                               // Retrieve user's albums
+                               JSONArray userAlbums = 
db.getJSONObject(ALBUMS_TABLE).getJSONArray(user);
+                               
+                               // Search albums for given ID and owner
+                               JSONObject album;
+                               for (int i = 0; i < userAlbums.length(); i++) {
+                                       album = userAlbums.getJSONObject(i);
+                                       if 
(album.getString(Album.Field.ID.toString()).equals(albumId) &&
+                                               
album.getString(Album.Field.OWNER_ID.toString()).equals(user)) {
+                                               return 
ImmediateFuture.newInstance(filterFields(album, fields, Album.class));
+                                       }
+                               }
+                       }
+
+                       // Album wasn't found
+                       throw new 
ProtocolException(HttpServletResponse.SC_BAD_REQUEST, "Album ID " + albumId + " 
does not exist");
+               } catch (JSONException je) {
+                       throw new ProtocolException(
+                                       
HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+                                       je.getMessage(), je);
+               }
+       }
+
+       // TODO: not using appId
+       public Future<RestfulCollection<Album>> getAlbums(UserId userId, String 
appId,
+                       Set<String> fields, CollectionOptions options, 
Set<String> albumIds,
+                       SecurityToken token) throws ProtocolException {
+               try {
+                       // Ensure user has a table
+                       String user = userId.getUserId(token);
+                       if (db.getJSONObject(ALBUMS_TABLE).has(user)) {
+                               // Get user's albums
+                               JSONArray userAlbums = 
db.getJSONObject(ALBUMS_TABLE).getJSONArray(user);
+                               
+                               // Stores target albums
+                               List<Album> result = Lists.newArrayList();
+                               
+                               // Search for every albumId
+                               boolean found;
+                               JSONObject curAlbum;
+                               for (String albumId : albumIds) {
+                                       // Search albums for this albumId
+                                       found = false;
+                                       for (int i = 0; i < 
userAlbums.length(); i++) {
+                                               curAlbum = 
userAlbums.getJSONObject(i);
+                                               if 
(curAlbum.getString(Album.Field.ID.toString()).equals(albumId) &&
+                                                       
curAlbum.getString(Album.Field.OWNER_ID.toString()).equals(user)) {
+                                                       
result.add(filterFields(curAlbum, fields, Album.class));
+                                                       found = true;
+                                                       break;
+                                               }
+                                       }
+                                       
+                                       // Error - albumId not found
+                                       if (!found) {
+                                               throw new 
ProtocolException(HttpServletResponse.SC_BAD_REQUEST, "Album ID " + albumId + " 
does not exist");
+                                       }
+                               }
+                               
+                               // Return found albums
+                               return ImmediateFuture.newInstance(new 
RestfulCollection<Album>(result));
+                       }
+                       
+                       // Album table doesn't exist for user
+                       throw new 
ProtocolException(HttpServletResponse.SC_BAD_REQUEST, "User '" + user + "' has 
no albums");
+               } catch (JSONException je) {
+                       throw new ProtocolException(
+                                       
HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+                                       je.getMessage(), je);
+               }
+       }
+       
+       // TODO: not using appId
+       public Future<RestfulCollection<Album>> getAlbums(Set<UserId> userIds,
+                       GroupId groupId, String appId, Set<String> fields,
+                       CollectionOptions options, SecurityToken token)
+                       throws ProtocolException {
+               try {
+                       List<Album> result = Lists.newArrayList();
+                       Set<String> idSet = getIdSet(userIds, groupId, token);
+                       
+                       // Gather albums for all user IDs
+                       for (String id : idSet) {
+                               if (db.getJSONObject(ALBUMS_TABLE).has(id)) {
+                                       JSONArray userAlbums = 
db.getJSONObject(ALBUMS_TABLE).getJSONArray(id);
+                                       for (int i = 0; i < 
userAlbums.length(); i++) {
+                                               JSONObject album = 
userAlbums.getJSONObject(i);
+                                               if 
(album.getString(Album.Field.OWNER_ID.toString()).equals(id)) {
+                                                       
result.add(filterFields(album, fields, Album.class));
+                                               }
+                                       }
+                               }
+                       }
+                       return ImmediateFuture.newInstance(new 
RestfulCollection<Album>(result));
+               } catch (JSONException je) {
+                       throw new ProtocolException(
+                                       
HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+                                       je.getMessage(), je);
+               }
+       }
+       
+       // TODO: not using appId
+       public Future<Void> deleteAlbum(UserId userId, String appId, String 
albumId,
+                       SecurityToken token) throws ProtocolException {
+               try {
+                       boolean targetFound = false;                    // 
indicates if target album is found
+                       JSONArray newAlbums = new JSONArray();  // list of 
albums minus target
+                       String user = userId.getUserId(token);  // retrieve 
user id
+                       
+                       // First ensure user has a table
+                       if (db.getJSONObject(ALBUMS_TABLE).has(user)) {
+                               // Get user's albums
+                               JSONArray userAlbums = 
db.getJSONObject(ALBUMS_TABLE).getJSONArray(user);
+                               
+                               // Compose new list of albums excluding album 
to be deleted
+                               JSONObject curAlbum;
+                               for (int i = 0; i < userAlbums.length(); i++) {
+                                       curAlbum = userAlbums.getJSONObject(i);
+                                       if 
(curAlbum.getString(Album.Field.ID.toString()).equals(albumId)) {
+                                               targetFound = true;
+                                       } else {
+                                               newAlbums.put(curAlbum);
+                                       }
+                               }
+                       }
+                       
+                       // Overwrite user's albums with updated list if album 
found
+                       if (targetFound) {
+                               db.getJSONObject(ALBUMS_TABLE).put(user, 
newAlbums);
+                               return ImmediateFuture.newInstance(null);
+                       } else {
+                               throw new 
ProtocolException(HttpServletResponse.SC_BAD_REQUEST, "Album ID " + albumId + " 
does not exist");
+                       }
+               } catch (JSONException je) {
+                       throw new ProtocolException(
+                                       
HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+                                       je.getMessage(), je);
+               }
+       }
+       
+       // TODO: userId and album's ownerId don't have to match - potential 
problem
+       // TODO: not using appId
+       public Future<Void> createAlbum(UserId userId, String appId, Album 
album,
+                       SecurityToken token) throws ProtocolException {
+               try {                   
+                       // Get table of user's albums
+                       String user = userId.getUserId(token);
+                       JSONArray userAlbums = 
db.getJSONObject(ALBUMS_TABLE).getJSONArray(user);
+                       if (userAlbums == null) {
+                               userAlbums = new JSONArray();
+                               db.getJSONObject(ALBUMS_TABLE).put(user, 
userAlbums);
+                       }
+                       
+                       // Convert album to JSON and set ID & owner
+                       JSONObject jsonAlbum = convertToJson(album);
+                       if (!jsonAlbum.has(Album.Field.ID.toString())) {
+                               jsonAlbum.put(Album.Field.ID.toString(), 
System.currentTimeMillis());
+                       }
+                       if (!jsonAlbum.has(Album.Field.OWNER_ID.toString())) {
+                               jsonAlbum.put(Album.Field.OWNER_ID.toString(), 
user);
+                       }
+                       
+                       // Insert new album into table
+                       userAlbums.put(jsonAlbum);
+                       return ImmediateFuture.newInstance(null);
+               } catch (JSONException je) {
+                       throw new ProtocolException(
+                                       
HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+                                       je.getMessage(), je);
+               }
+       }
+
+       // TODO: not using appId
+       public Future<Void> updateAlbum(UserId userId, String appId, Album 
album,
+                       String albumId, SecurityToken token) throws 
ProtocolException {
+               try {
+                       // First ensure user has a table
+                       String user = userId.getUserId(token);
+                       if (db.getJSONObject(ALBUMS_TABLE).has(user)) {
+                               // Retrieve user's albums
+                               JSONArray userAlbums = 
db.getJSONObject(ALBUMS_TABLE).getJSONArray(user);
+                               
+                               // Convert album to JSON and set ID
+                               JSONObject jsonAlbum = convertToJson(album);
+                               jsonAlbum.put(Album.Field.ID.toString(), 
albumId);
+                               
+                               // Iterate through albums to identify album to 
update
+                               JSONObject curAlbum = null;
+                               for (int i = 0; i < userAlbums.length(); i++) {
+                                       curAlbum = userAlbums.getJSONObject(i);
+                                       if 
(curAlbum.getString(Album.Field.ID.toString()).equals(albumId)) {
+                                               userAlbums.put(i, jsonAlbum);
+                                               return 
ImmediateFuture.newInstance(null);
+                                       }
+                               }
+                       }
+
+                       // Error - no album found to update with given ID
+                       throw new 
ProtocolException(HttpServletResponse.SC_BAD_REQUEST, "Album ID " + albumId + " 
does not exist");
+               } catch (JSONException je) {
+                       throw new ProtocolException(
+                                       
HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+                                       je.getMessage(), je);
+               }
+       }
+
+       // TODO: not using appId
+       public Future<MediaItem> getMediaItem(UserId userId, String appId,
+                       String albumId, String mediaItemId, Set<String> fields,
+                       SecurityToken token) throws ProtocolException {
+               try {
+                       // First ensure user has a table
+                       String user = userId.getUserId((token));
+                       if (db.getJSONObject(MEDIAITEMS_TABLE).has(user)) {
+                               // Retrieve user's MediaItems
+                               JSONArray userMediaItems = 
db.getJSONObject(MEDIAITEMS_TABLE).getJSONArray(user);
+                               
+                               // Search user's MediaItems for given ID and 
album
+                               JSONObject mediaItem;
+                               for (int i = 0; i < userMediaItems.length(); 
i++) {
+                                       mediaItem = 
userMediaItems.getJSONObject(i);
+                                       if 
(mediaItem.getString(MediaItem.Field.ID.toString()).equals(mediaItemId) &&
+                                               
mediaItem.getString(MediaItem.Field.ALBUM_ID.toString()).equals(albumId)) {
+                                               return 
ImmediateFuture.newInstance(filterFields(mediaItem, fields, MediaItem.class));
+                                       }
+                               }
+                       }
+
+                       // MediaItem wasn't found
+                       throw new 
ProtocolException(HttpServletResponse.SC_BAD_REQUEST, "MediaItem ID '" + 
mediaItemId + "' does not exist within Album '" + albumId + "'");
+               } catch (JSONException je) {
+                       throw new ProtocolException(
+                                       
HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+                                       je.getMessage(), je);
+               }
+       }
+
+       // TODO: not using appId
+       public Future<RestfulCollection<MediaItem>> getMediaItems(UserId userId,
+                       String appId, String albumId, Set<String> mediaItemIds,
+                       Set<String> fields, CollectionOptions options, 
SecurityToken token)
+                       throws ProtocolException {
+               try {
+                       // Ensure user has a table
+                       String user = userId.getUserId(token);
+                       if (db.getJSONObject(MEDIAITEMS_TABLE).has(user)) {
+                               // Get user's MediaItems
+                               JSONArray userMediaItems = 
db.getJSONObject(MEDIAITEMS_TABLE).getJSONArray(user);
+                               
+                               // Stores found MediaItems
+                               List<MediaItem> result = Lists.newArrayList();
+                               
+                               // Search for every MediaItem ID target
+                               boolean found;
+                               JSONObject curMediaItem;
+                               for (String mediaItemId : mediaItemIds) {
+                                       // Search existing MediaItems for this 
MediaItem ID
+                                       found = false;
+                                       for (int i = 0; i < 
userMediaItems.length(); i++) {
+                                               curMediaItem = 
userMediaItems.getJSONObject(i);
+                                               if 
(curMediaItem.getString(MediaItem.Field.ID.toString()).equals(albumId) &&
+                                                       
curMediaItem.getString(MediaItem.Field.ALBUM_ID.toString()).equals(albumId)) {
+                                                       
result.add(filterFields(curMediaItem, fields, MediaItem.class));
+                                                       found = true;
+                                                       break;
+                                               }
+                                       }
+                                       
+                                       // Error - MediaItem ID not found
+                                       if (!found) {
+                                               throw new 
ProtocolException(HttpServletResponse.SC_BAD_REQUEST, "MediaItem ID " + 
mediaItemId + " does not exist within Album " + albumId);
+                                       }
+                               }
+                       
+                               // Return found MediaItems
+                               return ImmediateFuture.newInstance(new 
RestfulCollection<MediaItem>(result));
+                       }
+                       
+                       // Table doesn't exist for user
+                       throw new 
ProtocolException(HttpServletResponse.SC_BAD_REQUEST, "MediaItem table not 
found for user " + user);
+               } catch (JSONException je) {
+                       throw new ProtocolException(
+                                       
HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+                                       je.getMessage(), je);
+               }
+       }
+       
+       // TODO: not using appId
+       public Future<RestfulCollection<MediaItem>> getMediaItems(UserId userId,
+                       String appId, String albumId, Set<String> fields,
+                       CollectionOptions options, SecurityToken token)
+                       throws ProtocolException {
+               try {
+                       // First ensure user has a table
+                       String user = userId.getUserId((token));
+                       if (db.getJSONObject(MEDIAITEMS_TABLE).has(user)) {
+                               // Retrieve user's MediaItems
+                               JSONArray userMediaItems = 
db.getJSONObject(MEDIAITEMS_TABLE).getJSONArray(user);
+                               
+                               // Stores target MediaItems
+                               List<MediaItem> result = Lists.newArrayList();
+                               
+                               // Search user's MediaItems for given album
+                               JSONObject curMediaItem;
+                               for (int i = 0; i < userMediaItems.length(); 
i++) {
+                                       curMediaItem = 
userMediaItems.getJSONObject(i);
+                                       if 
(curMediaItem.getString(MediaItem.Field.ALBUM_ID.toString()).equals(albumId)) {
+                                               
result.add(filterFields(curMediaItem, fields, MediaItem.class));
+                                       }
+                               }
+                               
+                               // Return found MediaItems
+                               return ImmediateFuture.newInstance(new 
RestfulCollection<MediaItem>(result));
+                       }
+
+                       // Album wasn't found
+                       throw new 
ProtocolException(HttpServletResponse.SC_BAD_REQUEST, "Album ID " + albumId + " 
does not exist");
+               } catch (JSONException je) {
+                       throw new ProtocolException(
+                                       
HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+                                       je.getMessage(), je);
+               }
+       }
+
+       // TODO: not using appId
+       public Future<RestfulCollection<MediaItem>> getMediaItems(
+                       Set<UserId> userIds, GroupId groupId, String appId,
+                       Set<String> fields, CollectionOptions options, 
SecurityToken token)
+                       throws ProtocolException {
+               try {
+                       List<MediaItem> result = Lists.newArrayList();
+                       Set<String> idSet = getIdSet(userIds, groupId, token);
+                       
+                       // Gather MediaItems for all user IDs
+                       for (String id : idSet) {
+                               if (db.getJSONObject(MEDIAITEMS_TABLE).has(id)) 
{
+                                       JSONArray userMediaItems = 
db.getJSONObject(MEDIAITEMS_TABLE).getJSONArray(id);
+                                       for (int i = 0; i < 
userMediaItems.length(); i++) {
+                                               
result.add(filterFields(userMediaItems.getJSONObject(i), fields, 
MediaItem.class));
+                                       }
+                               }
+                       }
+                       return ImmediateFuture.newInstance(new 
RestfulCollection<MediaItem>(result));
+               } catch (JSONException je) {
+                       throw new ProtocolException(
+                                       
HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+                                       je.getMessage(), je);
+               }
+       }
+
+       // TODO: not using appId
+       public Future<Void> deleteMediaItem(UserId userId, String appId,
+                       String albumId, String mediaItemId, SecurityToken token)
+                       throws ProtocolException {
+               try {
+                       boolean targetFound = false;                            
// indicates if target MediaItem is found
+                       JSONArray newMediaItems = new JSONArray();      // list 
of MediaItems minus target
+                       String user = userId.getUserId(token);          // 
retrieve user id
+                       
+                       // First ensure user has a table
+                       if (db.getJSONObject(MEDIAITEMS_TABLE).has(user)) {
+                               // Get user's MediaItems
+                               JSONArray userMediaItems = 
db.getJSONObject(MEDIAITEMS_TABLE).getJSONArray(user);
+                               
+                               // Compose new list of MediaItems excluding 
item to be deleted
+                               JSONObject curMediaItem;
+                               for (int i = 0; i < userMediaItems.length(); 
i++) {
+                                       curMediaItem = 
userMediaItems.getJSONObject(i);
+                                       if 
(curMediaItem.getString(MediaItem.Field.ID.toString()).equals(mediaItemId) &&
+                                               
curMediaItem.getString(MediaItem.Field.ALBUM_ID.toString()).equals(albumId)) {
+                                               targetFound = true;
+                                       } else {
+                                               newMediaItems.put(curMediaItem);
+                                       }
+                               }
+                       }
+                       
+                       // Overwrite user's MediaItems with updated list if 
target found
+                       if (targetFound) {
+                               db.getJSONObject(MEDIAITEMS_TABLE).put(user, 
newMediaItems);
+                               return ImmediateFuture.newInstance(null);
+                       } else {
+                               throw new 
ProtocolException(HttpServletResponse.SC_BAD_REQUEST, "MediaItem ID " + 
mediaItemId + " does not exist existin within Album " + albumId);
+                       }
+               } catch (JSONException je) {
+                       throw new ProtocolException(
+                                       
HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+                                       je.getMessage(), je);
+               }
+       }
+
+       // TODO: not using appId
+       public Future<Void> createMediaItem(UserId userId, String appId,
+                       String albumId, MediaItem mediaItem, SecurityToken 
token)
+                       throws ProtocolException {
+               try {
+                       // Get table of user's MediaItems
+                       JSONArray userMediaItems = 
db.getJSONObject(MEDIAITEMS_TABLE).getJSONArray(userId.getUserId(token));
+                       if (userMediaItems == null) {
+                               userMediaItems = new JSONArray();
+                               
db.getJSONObject(MEDIAITEMS_TABLE).put(userId.getUserId(token), userMediaItems);
+                       }
+
+                       // Convert MediaItem to JSON and set ID & Album ID
+                       JSONObject jsonMediaItem = convertToJson(mediaItem);
+                       jsonMediaItem.put(MediaItem.Field.ALBUM_ID.toString(), 
albumId);
+                       if (!jsonMediaItem.has(MediaItem.Field.ID.toString())) {
+                               
jsonMediaItem.put(MediaItem.Field.ID.toString(), System.currentTimeMillis());
+                       }
+
+                       // Insert new MediaItem into table
+                       userMediaItems.put(jsonMediaItem);
+                       return ImmediateFuture.newInstance(null);
+               } catch (JSONException je) {
+                       throw new ProtocolException(
+                                       
HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+                                       je.getMessage(), je);
+               }
+       }
+
+       // TODO: not using appId
+       public Future<Void> updateMediaItem(UserId userId, String appId,
+                       String albumId, String mediaItemId, MediaItem mediaItem,
+                       SecurityToken token) throws ProtocolException {
+               try {
+                       // First ensure user has a table
+                       String user = userId.getUserId(token);
+                       if (db.getJSONObject(MEDIAITEMS_TABLE).has(user)) {
+                               // Retrieve user's MediaItems
+                               JSONArray userMediaItems = 
db.getJSONObject(MEDIAITEMS_TABLE).getJSONArray(user);
+                               
+                               // Convert MediaItem to JSON and set ID & Album 
ID
+                               JSONObject jsonMediaItem = 
convertToJson(mediaItem);
+                               
jsonMediaItem.put(MediaItem.Field.ID.toString(), mediaItemId);
+                               
jsonMediaItem.put(MediaItem.Field.ALBUM_ID.toString(), albumId);
+                               
+                               // Iterate through MediaItems to identify item 
to update
+                               JSONObject curMediaItem = null;
+                               for (int i = 0; i < userMediaItems.length(); 
i++) {
+                                       curMediaItem = 
userMediaItems.getJSONObject(i);
+                                       if 
(curMediaItem.getString(MediaItem.Field.ID.toString()).equals(mediaItemId) &&
+                                               
curMediaItem.getString(MediaItem.Field.ALBUM_ID.toString()).equals(albumId)) {
+                                               userMediaItems.put(i, 
jsonMediaItem);
+                                               return 
ImmediateFuture.newInstance(null);
+                                       }
+                               }
+                       }
+
+                       // Error - no MediaItem found with given ID and Album ID
+                       throw new 
ProtocolException(HttpServletResponse.SC_BAD_REQUEST, "MediaItem ID " + 
mediaItemId + " does not exist existin within Album " + albumId);
+               } catch (JSONException je) {
+                       throw new ProtocolException(
+                                       
HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
+                                       je.getMessage(), je);
+               }
+       }
+
+       // TODO Why specifically handle Activity instead of generic POJO 
(below)?
+       private JSONObject convertFromActivity(Activity activity, Set<String> 
fields)
+                       throws JSONException {
+               // TODO Not using fields yet
+               return new JSONObject(converter.convertToString(activity));
+       }
+
+       private JSONObject convertToJson(Object object) throws JSONException {
+               // TODO not using fields yet
+               return new JSONObject(converter.convertToString(object));
+       }
+
+       public <T> T filterFields(JSONObject object, Set<String> fields,
+                       Class<T> clz) throws JSONException {
+               if (!fields.isEmpty()) {
+                       // Create a copy with just the specified fields
+                       object = new JSONObject(object, fields.toArray(new 
String[fields
+                                       .size()]));
+               }
+               return converter.convertToObject(object.toString(), clz);
+       }
 }


Reply via email to