Package org.apache.shindig.social.sample.spi

Source Code of org.apache.shindig.social.sample.spi.JsonDbOpensocialService

/*
* 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.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.AnonymousSecurityToken;
import org.apache.shindig.auth.SecurityToken;
import org.apache.shindig.common.servlet.Authority;
import org.apache.shindig.common.util.ResourceLoader;
import org.apache.shindig.protocol.DataCollection;
import org.apache.shindig.protocol.ProtocolException;
import org.apache.shindig.protocol.RestfulCollection;
import org.apache.shindig.protocol.conversion.BeanConverter;
import org.apache.shindig.protocol.model.SortOrder;
import org.apache.shindig.social.core.model.NameImpl;
import org.apache.shindig.social.core.model.PersonImpl;
import org.apache.shindig.social.opensocial.model.Activity;
import org.apache.shindig.social.opensocial.model.ActivityEntry;
import org.apache.shindig.social.opensocial.model.Album;
import org.apache.shindig.social.opensocial.model.Group;
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.ActivityStreamService;
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.GroupService;
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;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.util.concurrent.Futures;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.google.inject.name.Named;

/**
* Implementation of supported services backed by a JSON DB.
*/
@Singleton
public class JsonDbOpensocialService implements ActivityService, PersonService, AppDataService,
    MessageService, AlbumService, MediaItemService, ActivityStreamService, GroupService {

  private static final Comparator<Person> NAME_COMPARATOR = new Comparator<Person>() {
    public int compare(Person person, Person person1) {
      String name = person.getName().getFormatted();
      String name1 = person1.getName().getFormatted();
      return name.compareTo(name1);
    }
  };

  /**
   * The DB
   */
  private JSONObject db;

  /**
   * The JSON<->Bean converter
   */
  private BeanConverter converter;

  /**
   * db["people"] -> Array<Person>
   */
  private static final String PEOPLE_TABLE = "people";

  /**
   * db["groups"] -> Array<Group>
   */
  private static final String GROUPS_TABLE = "groups";

  /**
   * db["groupMembers"] -> Array<Person>
   */
  private static final String GROUP_MEMBERS_TABLE = "groupMembers";

  /**
   * db["activities"] -> Map<Person.Id, Array<Activity>>
   */
  private static final String ACTIVITIES_TABLE = "activities";

  /**
   * db["albums"] -> Map<Person.Id, Array<Album>>
   */
  private static final String ALBUMS_TABLE = "albums";

  /**
   * db["mediaItems"] -> Map<Person.Id, Array<MediaItem>>
   */
  private static final String MEDIAITEMS_TABLE = "mediaItems";

  /**
   * db["data"] -> Map<Person.Id, Map<String, String>>
   */
  private static final String DATA_TABLE = "data";

  /**
   * db["friendLinks"] -> Map<Person.Id, Array<Person.Id>>
   */
  private static final String FRIEND_LINK_TABLE = "friendLinks";

  /**
   * db["messages"] -> Map<Person.Id, Array<Message>>
   */
  private static final String MESSAGE_TABLE = "messages";

  /**
   * db["passwords"] -> Map<Person.Id, String>
   */
  private static final String PASSWORDS_TABLE = "passwords";

  /**
   * db["activityEntries"] -> Map<Person.Id, Array<ActivityEntry>>
   */
  private static final String ACTIVITYSTREAMS_TABLE = "activityEntries";

  /**
   * Anonymous name.
   */
  private static final String ANONYMOUS_NAME = "Anonymous";

  private Authority authority;

  /**
   * Initializes the JsonDbOpensocialService using Guice
   *
   * @param jsonLocation location of the json data provided by the shindig.canonical.json.db parameter
   * @param converter an injected BeanConverter
   * @throws java.lang.Exception if any
   */
  @Inject
  public JsonDbOpensocialService(@Named("shindig.canonical.json.db")
  String jsonLocation, @Named("shindig.bean.converter.json")
  BeanConverter converter,
  @Named("shindig.contextroot") String contextroot) throws Exception {
    String content = IOUtils.toString(ResourceLoader.openResource(jsonLocation), "UTF-8");
    this.db = new JSONObject(content.replace("%contextroot%", contextroot));
    this.converter = converter;
  }

  /**
   * Allows access to the underlying json db.
   *
   * @return a reference to the json db
   */
  public JSONObject getDb() {
    return db;
  }

   /**
   * override the json database
   * @param db a {@link org.json.JSONObject}.
   */
  public void setDb(JSONObject db) {
    this.db = db;
  }

  @Inject(optional = true)
  public void setAuthority(Authority authority) {
    this.authority = authority;
  }

  /** {@inheritDoc} */
  public Future<RestfulCollection<Activity>> getActivities(Set<UserId> userIds, GroupId groupId,
      String appId, Set<String> fields, CollectionOptions options, SecurityToken token)
      throws ProtocolException {
    List<Activity> result = Lists.newArrayList();
    try {
      Set<String> idSet = getIdSet(userIds, groupId, token);
      for (String id : idSet) {
        if (db.getJSONObject(ACTIVITIES_TABLE).has(id)) {
          JSONArray activities = db.getJSONObject(ACTIVITIES_TABLE).getJSONArray(id);
          for (int i = 0; i < activities.length(); i++) {
            JSONObject activity = activities.getJSONObject(i);
            if (appId == null || !activity.has(Activity.Field.APP_ID.toString())) {
              result.add(filterFields(activity, fields, Activity.class));
            } else if (activity.get(Activity.Field.APP_ID.toString()).equals(appId)) {
              result.add(filterFields(activity, fields, Activity.class));
            }
          }
        }
      }
      return Futures.immediateFuture(new RestfulCollection<Activity>(result));
    } catch (JSONException je) {
      throw new ProtocolException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, je.getMessage(),
          je);
    }
  }

  /** {@inheritDoc} */
  public Future<RestfulCollection<Activity>> getActivities(UserId userId, GroupId groupId,
      String appId, Set<String> fields, CollectionOptions options, Set<String> activityIds,
      SecurityToken token) throws ProtocolException {
    List<Activity> result = Lists.newArrayList();
    try {
      String user = userId.getUserId(token);
      if (db.getJSONObject(ACTIVITIES_TABLE).has(user)) {
        JSONArray activities = db.getJSONObject(ACTIVITIES_TABLE).getJSONArray(user);
        for (int i = 0; i < activities.length(); i++) {
          JSONObject activity = activities.getJSONObject(i);
          if (activity.get(Activity.Field.USER_ID.toString()).equals(user)
              && activityIds.contains(activity.getString(Activity.Field.ID.toString()))) {
            result.add(filterFields(activity, fields, Activity.class));
          }
        }
      }
      return Futures.immediateFuture(new RestfulCollection<Activity>(result));
    } catch (JSONException je) {
      throw new ProtocolException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, je.getMessage(),
          je);
    }
  }

  /** {@inheritDoc} */
  public Future<Activity> getActivity(UserId userId, GroupId groupId, String appId,
      Set<String> fields, String activityId, SecurityToken token) throws ProtocolException {
    try {
      String user = userId.getUserId(token);
      if (db.getJSONObject(ACTIVITIES_TABLE).has(user)) {
        JSONArray activities = db.getJSONObject(ACTIVITIES_TABLE).getJSONArray(user);
        for (int i = 0; i < activities.length(); i++) {
          JSONObject activity = activities.getJSONObject(i);
          if (activity.get(Activity.Field.USER_ID.toString()).equals(user)
              && activity.get(Activity.Field.ID.toString()).equals(activityId)) {
            return Futures.immediateFuture(filterFields(activity, fields, Activity.class));
          }
        }
      }

      throw new ProtocolException(HttpServletResponse.SC_BAD_REQUEST, "Activity not found");
    } catch (JSONException je) {
      throw new ProtocolException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, je.getMessage(),
          je);
    }
  }

  /** {@inheritDoc} */
  public Future<Void> deleteActivities(UserId userId, GroupId groupId, String appId,
      Set<String> activityIds, SecurityToken token) throws ProtocolException {
    try {
      String user = userId.getUserId(token);
      if (db.getJSONObject(ACTIVITIES_TABLE).has(user)) {
        JSONArray activities = db.getJSONObject(ACTIVITIES_TABLE).getJSONArray(user);
        if (activities != null) {
          JSONArray newList = new JSONArray();
          for (int i = 0; i < activities.length(); i++) {
            JSONObject activity = activities.getJSONObject(i);
            if (!activityIds.contains(activity.getString(Activity.Field.ID.toString()))) {
              newList.put(activity);
            }
          }
          db.getJSONObject(ACTIVITIES_TABLE).put(user, newList);
          // TODO. This seems very odd that we return no useful response in this
          // case
          // There is no way to represent not-found
          // if (found) { ??
          // }
        }
      }
      // What is the appropriate response here??
      return Futures.immediateFuture(null);
    } catch (JSONException je) {
      throw new ProtocolException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, je.getMessage(),
          je);
    }
  }

  /** {@inheritDoc} */
  public Future<Void> createActivity(UserId userId, GroupId groupId, String appId,
      Set<String> fields, Activity activity, SecurityToken token) throws ProtocolException {
    // Are fields really needed here?
    try {
      JSONObject jsonObject = convertFromActivity(activity, fields);
      if (!jsonObject.has(Activity.Field.ID.toString())) {
        jsonObject.put(Activity.Field.ID.toString(), System.currentTimeMillis());
      }
      JSONArray jsonArray = db.getJSONObject(ACTIVITIES_TABLE)
          .getJSONArray(userId.getUserId(token));
      if (jsonArray == null) {
        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 Futures.immediateFuture(null);
    } catch (JSONException je) {
      throw new ProtocolException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, je.getMessage(),
          je);
    }
  }

  /** {@inheritDoc} */
  public Future<RestfulCollection<Person>> getPeople(Set<UserId> userIds, GroupId groupId,
      CollectionOptions options, Set<String> fields, SecurityToken token) throws ProtocolException {
    List<Person> result = Lists.newArrayList();
    try {
      JSONArray people = db.getJSONArray(PEOPLE_TABLE);

      Set<String> idSet = getIdSet(userIds, groupId, token);

      for (int i = 0; i < people.length(); i++) {
        JSONObject person = people.getJSONObject(i);
        if (!idSet.contains(person.get(Person.Field.ID.toString()))) {
          continue;
        }

        // Add group support later
        Person personObj = filterFields(person, fields, Person.class);
        Map<String, Object> appData = getPersonAppData(
            person.getString(Person.Field.ID.toString()), fields);
        personObj.setAppData(appData);

        result.add(personObj);
      }

      if (GroupId.Type.self == groupId.getType() && result.isEmpty()) {
        throw new ProtocolException(HttpServletResponse.SC_BAD_REQUEST, "People '" + idSet + "' not found");
      }

      // We can pretend that by default the people are in top friends order
      if (options.getSortBy().equals(Person.Field.NAME.toString())) {
        Collections.sort(result, NAME_COMPARATOR);

        if (options.getSortOrder() == SortOrder.descending) {
          Collections.reverse(result);
        }
      }

      // TODO: The samplecontainer doesn't really have the concept of HAS_APP so
      // we can't support any filters yet. We should fix this.

      int totalSize = result.size();
      int last = options.getFirst() + options.getMax();
      result = result.subList(options.getFirst(), Math.min(last, totalSize));

      return Futures.immediateFuture(new RestfulCollection<Person>(result, options.getFirst(), totalSize, options.getMax()));
    } catch (JSONException je) {
      throw new ProtocolException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, je.getMessage(),
          je);
    }
  }

  /** {@inheritDoc} */
  public Future<Person> getPerson(UserId id, Set<String> fields, SecurityToken token)
      throws ProtocolException {
    if (id != null && AnonymousSecurityToken.ANONYMOUS_ID.equals(id.getUserId())) {
      Person anonymous = new PersonImpl();
      anonymous.setId(AnonymousSecurityToken.ANONYMOUS_ID);
      anonymous.setName(new NameImpl(ANONYMOUS_NAME));
      anonymous.setNickname(ANONYMOUS_NAME);
      return Futures.immediateFuture(anonymous);
    }
    try {
      JSONArray people = db.getJSONArray(PEOPLE_TABLE);

      for (int i = 0; i < people.length(); i++) {
        JSONObject person = people.getJSONObject(i);
        if (id != null && person.get(Person.Field.ID.toString()).equals(id.getUserId(token))) {
          Person personObj = filterFields(person, fields, Person.class);
          Map<String, Object> appData = getPersonAppData(person.getString(Person.Field.ID
              .toString()), fields);
          personObj.setAppData(appData);

          return Futures.immediateFuture(personObj);
        }
      }
      throw new ProtocolException(HttpServletResponse.SC_BAD_REQUEST, "Person '" + id.getUserId(token) + "' not found");
    } catch (JSONException je) {
      throw new ProtocolException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, je.getMessage(),
          je);
    }
  }

  /** {@inheritDoc} */
  public Future<Person> updatePerson(UserId id, Person person, SecurityToken token)
      throws ProtocolException {
    try {
      String viewer = token.getViewerId(); // viewer
      String user = id.getUserId(token); // person to update

      if (!viewerCanUpdatePerson(viewer,user)) {
        throw new ProtocolException(HttpServletResponse.SC_FORBIDDEN, "User '" + viewer + "' does not have enough privileges to update person '"+user+"'");
      }

      JSONArray people = db.getJSONArray(PEOPLE_TABLE);

      for (int i = 0; i < people.length(); i++) {
        JSONObject curPerson = people.getJSONObject(i);

        if (user != null && curPerson.getString(Person.Field.ID.toString()).equals(user)) {
          // Convert user to JSON and set ID
          JSONObject jsonPerson = convertToJson(person);
          // go through all properties to update in the submitted person object
          // and change them in the current person object
          for (String key : JSONObject.getNames(jsonPerson)) {
            curPerson.put(key,jsonPerson.get(key));
          }

          people.put(i,curPerson);
          return Futures.immediateFuture(converter.convertToObject(curPerson.toString(), Person.class));
        }
      }

      // Error - no album found to update with given ID
      throw new ProtocolException(HttpServletResponse.SC_BAD_REQUEST, "User ID " + user + " does not exist");
    } catch (JSONException je) {
      throw new ProtocolException(
          HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
          je.getMessage(), je);
    }

  }

  /** Check if a viewer is allowed to update the given person record. **/
  protected boolean viewerCanUpdatePerson(String viewer, String person) {
    // A person can only update his own personal data (by default)
    // if you wish to allow other people to update the personal data of the user
    // you should change the current function
    return viewer.equals(person) ? true : false;
  }

  private Map<String, Object> getPersonAppData(String id, Set<String> fields) {
    try {
      Map<String, Object> appData = null;
      JSONObject personData = db.getJSONObject(DATA_TABLE).optJSONObject(id);
      if (personData != null) {
        if (fields.contains(Person.Field.APP_DATA.toString())) {
          appData = Maps.newHashMap();
          @SuppressWarnings("unchecked")
          Iterator<String> keys = personData.keys();
          while (keys.hasNext()) {
            String key = keys.next();
            appData.put(key, personData.get(key));
          }
        } else {
          String appDataPrefix = Person.Field.APP_DATA.toString() + '.';
          for (String field : fields) {
            if (field.startsWith(appDataPrefix)) {
              if (appData == null) {
                appData = Maps.newHashMap();
              }

              String appDataField = field.substring(appDataPrefix.length());
              if (personData.has(appDataField)) {
                appData.put(appDataField, personData.get(appDataField));
              }
            }
          }
        }
      }

      return appData;
    } catch (JSONException je) {
      throw new ProtocolException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, je.getMessage(),
          je);
    }
  }

  /** {@inheritDoc} */
  public Future<DataCollection> getPersonData(Set<UserId> userIds, GroupId groupId, String appId,
      Set<String> fields, SecurityToken token) throws ProtocolException {
    try {
      Map<String, Map<String, String>> idToData = Maps.newHashMap();
      Set<String> idSet = getIdSet(userIds, groupId, token);
      for (String id : idSet) {
        JSONObject personData;
        if (!db.getJSONObject(DATA_TABLE).has(id)) {
          personData = new JSONObject();
        } else {
          if (!fields.isEmpty()) {
            personData = new JSONObject(db.getJSONObject(DATA_TABLE).getJSONObject(id), fields
                .toArray(new String[fields.size()]));
          } else {
            personData = db.getJSONObject(DATA_TABLE).getJSONObject(id);
          }
        }

        // TODO: We can use the converter here to do this for us

        // JSONObject keys are always strings
        @SuppressWarnings("unchecked")
        Iterator<String> keys = personData.keys();
        Map<String, String> data = Maps.newHashMap();
        while (keys.hasNext()) {
          String key = keys.next();
          data.put(key, personData.getString(key));
        }
        idToData.put(id, data);
      }
      return Futures.immediateFuture(new DataCollection(idToData));
    } catch (JSONException je) {
      throw new ProtocolException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, je.getMessage(),
          je);
    }
  }

  /** {@inheritDoc} */
  public Future<Void> deletePersonData(UserId userId, GroupId groupId, String appId,
      Set<String> fields, SecurityToken token) throws ProtocolException {
    try {
      String user = userId.getUserId(token);
      if (!db.getJSONObject(DATA_TABLE).has(user)) {
        return null;
      }
      JSONObject newPersonData = new JSONObject();
      JSONObject oldPersonData = db.getJSONObject(DATA_TABLE).getJSONObject(user);

      // JSONObject keys are always strings
      @SuppressWarnings("unchecked")
      Iterator<String> keys = oldPersonData.keys();
      while (keys.hasNext()) {
        String key = keys.next();
        if (!fields.contains(key)) {
          newPersonData.put(key, oldPersonData.getString(key));
        }
      }
      db.getJSONObject(DATA_TABLE).put(user, newPersonData);
      return Futures.immediateFuture(null);
    } catch (JSONException je) {
      throw new ProtocolException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, je.getMessage(),
          je);
    }
  }

  /** {@inheritDoc} */
  public Future<Void> updatePersonData(UserId userId, GroupId groupId, String appId,
      Set<String> fields, Map<String, Object> values, SecurityToken token)
      throws ProtocolException {
    // TODO: this seems redundant. No need to pass both fields and a map of
    // field->value
    // TODO: According to rest, yes there is. If a field is in the param list
    // but not in the map
    // that means it is a delete

    try {
      JSONObject personData = db.getJSONObject(DATA_TABLE).getJSONObject(userId.getUserId(token));
      if (personData == null) {
        personData = new JSONObject();
        db.getJSONObject(DATA_TABLE).put(userId.getUserId(token), personData);
      }

      for (Map.Entry<String, Object> entry : values.entrySet()) {
        personData.put(entry.getKey(), entry.getValue());
      }
      return Futures.immediateFuture(null);
    } catch (JSONException je) {
      throw new ProtocolException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, je.getMessage(),
          je);
    }
  }

  /** {@inheritDoc} */
  public Future<RestfulCollection<Group>> getGroups(UserId userId,
    CollectionOptions options, Set<String> fields, SecurityToken token)
    throws ProtocolException {
    List<Group> result = Lists.newArrayList();
    String user = userId.getUserId(token);
    try {
      JSONArray groups = db.getJSONObject(GROUPS_TABLE).getJSONArray(user);

      for (int i = 0; i < groups.length(); i++) {
        JSONObject group = groups.getJSONObject(i);

        Group groupObj = filterFields(group, fields, Group.class);
        result.add(groupObj);
      }
    } catch (JSONException je) {
      throw new ProtocolException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, je.getMessage(), je);
    }

    return Futures.immediateFuture(new RestfulCollection<Group>(result));
  }

  /**
   * {@inheritDoc}
   *
   * Post a message for a set of users.
   */
  public Future<Void> createMessage(UserId userId, String appId, String msgCollId, Message message,
      SecurityToken token) throws ProtocolException {
    for (String recipient : message.getRecipients()) {
      try {
        JSONArray outbox = db.getJSONObject(MESSAGE_TABLE).getJSONArray(recipient);
        if (outbox == null) {
          outbox = new JSONArray();
          db.getJSONObject(MESSAGE_TABLE).put(recipient, outbox);
        }

        outbox.put(message);
      } catch (JSONException je) {
        throw new ProtocolException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, je.getMessage(),
            je);
      }
    }

    return Futures.immediateFuture(null);
  }

  /** {@inheritDoc} */
  public Future<RestfulCollection<MessageCollection>> getMessageCollections(UserId userId,
      Set<String> fields, CollectionOptions options, SecurityToken token) throws ProtocolException {
    try {
      List<MessageCollection> result = Lists.newArrayList();
      JSONObject messageCollections = db.getJSONObject(MESSAGE_TABLE).getJSONObject(
          userId.getUserId(token));
      for (String msgCollId : JSONObject.getNames(messageCollections)) {
        JSONObject msgColl = messageCollections.getJSONObject(msgCollId);
        msgColl.put("id", msgCollId);
        JSONArray messages = msgColl.getJSONArray("messages");
        int numMessages = (messages == null) ? 0 : messages.length();
        msgColl.put("total", String.valueOf(numMessages));
        msgColl.put("unread", String.valueOf(numMessages));

        result.add(filterFields(msgColl, fields, MessageCollection.class));
      }
      return Futures.immediateFuture(new RestfulCollection<MessageCollection>(result));
    } catch (JSONException je) {
      throw new ProtocolException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, je.getMessage(),
          je);
    }
  }

  /** {@inheritDoc} */
  public Future<Void> deleteMessages(UserId userId, String msgCollId, List<String> ids,
      SecurityToken token) throws ProtocolException {
    throw new ProtocolException(HttpServletResponse.SC_NOT_IMPLEMENTED,
        "this functionality is not yet available");
  }

  /**
   * {@inheritDoc}
   *
   * Gets the messsages in an user's queue.
   */
  public Future<RestfulCollection<Message>> getMessages(UserId userId, String msgCollId,
      Set<String> fields, List<String> msgIds, CollectionOptions options, SecurityToken token)
      throws ProtocolException {
    try {
      List<Message> result = Lists.newArrayList();
      JSONArray messages = db.getJSONObject(MESSAGE_TABLE).getJSONObject(userId.getUserId(token))
          .getJSONObject(msgCollId).getJSONArray("messages");

      // TODO: special case @all

      if (messages == null) {
        throw new ProtocolException(HttpServletResponse.SC_BAD_REQUEST, "message collection"
            + msgCollId + " not found");
      }

      // TODO: filter and sort outbox.
      for (int i = 0; i < messages.length(); i++) {
        JSONObject msg = messages.getJSONObject(i);
        result.add(filterFields(msg, fields, Message.class));
      }

      return Futures.immediateFuture(new RestfulCollection<Message>(result));

    } catch (JSONException je) {
      throw new ProtocolException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, je.getMessage(),
          je);
    }
  }

  /** {@inheritDoc} */
  public Future<MessageCollection> createMessageCollection(UserId userId,
      MessageCollection msgCollection, SecurityToken token) throws ProtocolException {
    throw new ProtocolException(HttpServletResponse.SC_NOT_IMPLEMENTED,
        "this functionality is not yet available");
  }

  /** {@inheritDoc} */
  public Future<Void> modifyMessage(UserId userId, String msgCollId, String messageId,
      Message message, SecurityToken token) throws ProtocolException {
    throw new ProtocolException(HttpServletResponse.SC_NOT_IMPLEMENTED,
        "this functionality is not yet available");
  }

  /** {@inheritDoc} */
  public Future<Void> modifyMessageCollection(UserId userId, MessageCollection msgCollection,
      SecurityToken token) throws ProtocolException {
    throw new ProtocolException(HttpServletResponse.SC_NOT_IMPLEMENTED,
        "this functionality is not yet available");
  }

  /** {@inheritDoc} */
  public Future<Void> deleteMessageCollection(UserId userId, String msgCollId, SecurityToken token)
      throws ProtocolException {
    throw new ProtocolException(HttpServletResponse.SC_NOT_IMPLEMENTED,
        "this functionality is not yet available");
  }

  /**
   * Public methods for use with Authentication Classes
   *
   * @param username a {@link java.lang.String} object.
   * @return a {@link java.lang.String} object.
   */
  public String getPassword(String username) {
    try {
      return db.getJSONObject(PASSWORDS_TABLE).getString(username);
    } catch (JSONException e) {
      return null;
    }
  }

  private Set<String> getIdSet(UserId user, GroupId group, SecurityToken token)
      throws JSONException {
    String userId = user.getUserId(token);

    if (group == null) {
      return ImmutableSortedSet.of(userId);
    }

    Set<String> returnVal = Sets.newLinkedHashSet();
    switch (group.getType()) {
    case all:
    case friends:
      if (db.getJSONObject(FRIEND_LINK_TABLE).has(userId)) {
        JSONArray friends = db.getJSONObject(FRIEND_LINK_TABLE).getJSONArray(userId);
        for (int i = 0; i < friends.length(); i++) {
          returnVal.add(friends.getString(i));
        }
      }
      break;
    case objectId:
      if (db.getJSONObject(GROUP_MEMBERS_TABLE).has(group.toString())) {
        JSONArray groupMembers = db.getJSONObject(GROUP_MEMBERS_TABLE).getJSONArray(group.toString());
        for (int i = 0; i < groupMembers.length(); i++) {
          returnVal.add(groupMembers.getString(i));
        }
      }
      break;
    case self:
      returnVal.add(userId);
      break;
    }
    return returnVal;
  }

  /**
   * Get the set of user id's for a set of users and a group
   *
   * @param users set of UserIds
   * @param group the group
   * @param token a token
   * @return set of Id strings
   * @throws org.json.JSONException if errors in Json
   */
  public Set<String> getIdSet(Set<UserId> users, GroupId group, SecurityToken token)
      throws JSONException {
    Set<String> ids = Sets.newLinkedHashSet();
    for (UserId user : users) {
      ids.addAll(getIdSet(user, group, token));
    }
    return ids;
  }

  // TODO: not using appId

  /** {@inheritDoc} */
  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 Futures.immediateFuture(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

  /** {@inheritDoc} */
  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 Futures.immediateFuture(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

  /** {@inheritDoc} */
  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 Futures.immediateFuture(new RestfulCollection<Album>(result));
    } catch (JSONException je) {
      throw new ProtocolException(
          HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
          je.getMessage(), je);
    }
  }

  // TODO: not using appId

  /** {@inheritDoc} */
  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 Futures.immediateFuture(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

  /** {@inheritDoc} */
  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 Futures.immediateFuture(null);
    } catch (JSONException je) {
      throw new ProtocolException(
          HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
          je.getMessage(), je);
    }
  }

  // TODO: not using appId

  /** {@inheritDoc} */
  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
        for (int i = 0; i < userAlbums.length(); i++) {
          JSONObject curAlbum = userAlbums.getJSONObject(i);
          if (curAlbum.getString(Album.Field.ID.toString()).equals(albumId)) {
            userAlbums.put(i, jsonAlbum);
            return Futures.immediateFuture(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

  /** {@inheritDoc} */
  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 Futures.immediateFuture(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

  /** {@inheritDoc} */
  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 Futures.immediateFuture(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

  /** {@inheritDoc} */
  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 Futures.immediateFuture(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

  /** {@inheritDoc} */
  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 Futures.immediateFuture(new RestfulCollection<MediaItem>(result));
    } catch (JSONException je) {
      throw new ProtocolException(
          HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
          je.getMessage(), je);
    }
  }

  // TODO: not using appId

  /** {@inheritDoc} */
  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 Futures.immediateFuture(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

  /** {@inheritDoc} */
  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 Futures.immediateFuture(null);
    } catch (JSONException je) {
      throw new ProtocolException(
          HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
          je.getMessage(), je);
    }
  }

  // TODO: not using appId

  /** {@inheritDoc} */
  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
        for (int i = 0; i < userMediaItems.length(); i++) {
          JSONObject 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 Futures.immediateFuture(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);
    }
  }

  // Are fields really needed here?
  /** {@inheritDoc} */
  public Future<ActivityEntry> updateActivityEntry(UserId userId, GroupId groupId, String appId,
        Set<String> fields, ActivityEntry activityEntry, String activityId, SecurityToken token) throws ProtocolException {
    try {
      JSONObject jsonEntry = convertFromActivityEntry(activityEntry, fields);
      if (!jsonEntry.has(ActivityEntry.Field.ID.toString())) {
        if (activityId != null) {
          jsonEntry.put(ActivityEntry.Field.ID.toString(), activityId);
        } else {
          jsonEntry.put(ActivityEntry.Field.ID.toString(), System.currentTimeMillis());
        }
      }
      activityId = jsonEntry.getString(ActivityEntry.Field.ID.toString());

      JSONArray jsonArray;
      if (db.getJSONObject(ACTIVITYSTREAMS_TABLE).has(userId.getUserId(token))) {
        jsonArray = db.getJSONObject(ACTIVITYSTREAMS_TABLE).getJSONArray(userId.getUserId(token));
      } else {
        jsonArray = new JSONArray();
        db.getJSONObject(ACTIVITYSTREAMS_TABLE).put(userId.getUserId(token), jsonArray);
      }

      // Find & replace activity
      for (int i = 0; i < jsonArray.length(); i++) {
        JSONObject entry = jsonArray.getJSONObject(i);
        if (entry.getString(ActivityEntry.Field.ID.toString()).equals(activityId)) {
          jsonArray.put(i, jsonEntry);
          return Futures.immediateFuture(filterFields(jsonEntry, fields, ActivityEntry.class));
        }
      }
      throw new ProtocolException(HttpServletResponse.SC_BAD_REQUEST, "Activity not found: " + activityId);
    } catch (JSONException je) {
      throw new ProtocolException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, je.getMessage(), je);
    }
  }

  // Are fields really needed here?
  /** {@inheritDoc} */
  public Future<ActivityEntry> createActivityEntry(UserId userId, GroupId groupId, String appId,
        Set<String> fields, ActivityEntry activityEntry, SecurityToken token) throws ProtocolException {
    try {
      JSONObject jsonEntry = convertFromActivityEntry(activityEntry, fields);
      if (!jsonEntry.has(ActivityEntry.Field.ID.toString())) {
        jsonEntry.put(ActivityEntry.Field.ID.toString(), System.currentTimeMillis());
      }
      String activityId = jsonEntry.getString(ActivityEntry.Field.ID.toString());

      JSONArray jsonArray;
      if (db.getJSONObject(ACTIVITYSTREAMS_TABLE).has(userId.getUserId(token))) {
        jsonArray = db.getJSONObject(ACTIVITYSTREAMS_TABLE).getJSONArray(userId.getUserId(token));
      } else {
        jsonArray = new JSONArray();
        db.getJSONObject(ACTIVITYSTREAMS_TABLE).put(userId.getUserId(token), jsonArray);
      }

      // Ensure activity does not already exist
      for (int i = 0; i < jsonArray.length(); i++) {
        JSONObject entry = jsonArray.getJSONObject(i);
        if (entry.getString(ActivityEntry.Field.ID.toString()).equals(activityId)) {
          throw new ProtocolException(HttpServletResponse.SC_BAD_REQUEST, "Activity already exists: " + activityId);
        }
      }
      jsonArray.put(jsonEntry);
      return Futures.immediateFuture(filterFields(jsonEntry, fields, ActivityEntry.class));
    } catch (JSONException je) {
      throw new ProtocolException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, je.getMessage(), je);
    }
  }

  /** {@inheritDoc} */
  public Future<Void> deleteActivityEntries(UserId userId, GroupId groupId,
      String appId, Set<String> activityIds, SecurityToken token) throws ProtocolException {
    try {
      String user = userId.getUserId(token);

      if (db.getJSONObject(ACTIVITYSTREAMS_TABLE).has(user)) {
        JSONArray activityEntries = db.getJSONObject(ACTIVITYSTREAMS_TABLE).getJSONArray(user);

        if (activityEntries != null) {
          JSONArray newList = new JSONArray();
          for (int i = 0; i < activityEntries.length(); i++) {
            JSONObject activityEntry = activityEntries.getJSONObject(i);
            if (!activityIds.contains(activityEntry.getString(ActivityEntry.Field.ID.toString()))) {
              newList.put(activityEntry);
            }
          }
          db.getJSONObject(ACTIVITYSTREAMS_TABLE).put(user, newList);
        }
      }
      return Futures.immediateFuture(null);
    } catch (JSONException je) {
      throw new ProtocolException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, je.getMessage(), je);
    }
  }

  /** {@inheritDoc} */
  public Future<ActivityEntry> getActivityEntry(UserId userId, GroupId groupId,
      String appId, Set<String> fields, String activityId, SecurityToken token)
      throws ProtocolException {
    try {
      String user = userId.getUserId(token);
      if (db.getJSONObject(ACTIVITYSTREAMS_TABLE).has(user)) {
        JSONArray activityEntries = db.getJSONObject(ACTIVITYSTREAMS_TABLE).getJSONArray(user);
        for (int i = 0; i < activityEntries.length(); i++) {
          JSONObject activityEntry = activityEntries.getJSONObject(i);
          if (activityEntry.getString(ActivityEntry.Field.ID.toString()).equals(activityId)) {
            return Futures.immediateFuture(filterFields(activityEntry, fields, ActivityEntry.class));
          }
        }
      }
      throw new ProtocolException(HttpServletResponse.SC_BAD_REQUEST, "Activity not found: " + activityId);
    } catch (JSONException je) {
      throw new ProtocolException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, je.getMessage(), je);
    }
  }


/** {@inheritDoc} */
  public Future<RestfulCollection<ActivityEntry>> getActivityEntries(
      Set<UserId> userIds, GroupId groupId, String appId, Set<String> fields,
      CollectionOptions options, SecurityToken token)
      throws ProtocolException {
      List<ActivityEntry> result = Lists.newArrayList();
    try {
      Set<String> idSet = getIdSet(userIds, groupId, token);
      for (String id : idSet) {
        if (db.getJSONObject(ACTIVITYSTREAMS_TABLE).has(id)) {
          JSONArray activityEntries = db.getJSONObject(ACTIVITYSTREAMS_TABLE).getJSONArray(id);
          for (int i = 0; i < activityEntries.length(); i++) {
            JSONObject activityEntry = activityEntries.getJSONObject(i);
            result.add(filterFields(activityEntry, fields, ActivityEntry.class));
            // TODO: ActivityStreams don't have appIds
          }
        }
      }
      Collections.sort(result, Collections.reverseOrder());
      return Futures.immediateFuture(new RestfulCollection<ActivityEntry>(result));
    } catch (JSONException je) {
      throw new ProtocolException(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, je.getMessage(), je);
    }
  }

  /** {@inheritDoc} */
  public Future<RestfulCollection<ActivityEntry>> getActivityEntries(
      UserId userId, GroupId groupId, String appId, Set<String> fields,
      CollectionOptions options, Set<String> activityIds, SecurityToken token)
      throws ProtocolException {
    List<ActivityEntry> result = Lists.newArrayList();
    try {
      String user = userId.getUserId(token);
      if (db.getJSONObject(ACTIVITYSTREAMS_TABLE).has(user)) {
        JSONArray activityEntries = db.getJSONObject(ACTIVITYSTREAMS_TABLE).getJSONArray(user);
        for(String activityId : activityIds) {
          boolean found = false;
          for (int i = 0; i < activityEntries.length(); i++) {
            JSONObject activityEntry = activityEntries.getJSONObject(i);
            if (activityEntry.getString(ActivityEntry.Field.ID.toString()).equals(activityId)) {
              result.add(filterFields(activityEntry, fields, ActivityEntry.class));
              found = true;
              break;
            }
          }
          if (!found) {
            throw new ProtocolException(HttpServletResponse.SC_NOT_FOUND, "Activity not found: " + activityId);
          }
        }
      }
      Collections.sort(result, Collections.reverseOrder());
      return Futures.immediateFuture(new RestfulCollection<ActivityEntry>(result));
    } 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 convertFromActivityEntry(ActivityEntry activityEntry, Set<String> fields) throws JSONException {
    // TODO Not using fields yet
    return new JSONObject(converter.convertToString(activityEntry));
  }

  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()]));
    }
    String objectVal = object.toString();
    if (authority != null) {
      objectVal = objectVal.replace("%origin%", authority.getOrigin());
    } else {
      //provide default for junit tests
      objectVal = objectVal.replace("%origin%", "http://localhost:8080");
    }
    return converter.convertToObject(objectVal, clz);
  }
}
TOP

Related Classes of org.apache.shindig.social.sample.spi.JsonDbOpensocialService

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.