Coverage Report - org.apache.shindig.social.sample.spi.JsonDbOpensocialService
 
Classes in this File Line Coverage Branch Coverage Complexity
JsonDbOpensocialService
75%
124/165
70%
62/89
0
JsonDbOpensocialService$1
100%
4/4
N/A
0
JsonDbOpensocialService$2
100%
1/1
N/A
0
 
 1  
 /*
 2  
  * Licensed to the Apache Software Foundation (ASF) under one
 3  
  * or more contributor license agreements. See the NOTICE file
 4  
  * distributed with this work for additional information
 5  
  * regarding copyright ownership. The ASF licenses this file
 6  
  * to you under the Apache License, Version 2.0 (the
 7  
  * "License"); you may not use this file except in compliance
 8  
  * with the License. You may obtain a copy of the License at
 9  
  *
 10  
  *     http://www.apache.org/licenses/LICENSE-2.0
 11  
  *
 12  
  * Unless required by applicable law or agreed to in writing,
 13  
  * software distributed under the License is distributed on an
 14  
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 15  
  * KIND, either express or implied. See the License for the
 16  
  * specific language governing permissions and limitations under the License.
 17  
  */
 18  
 
 19  
 package org.apache.shindig.social.sample.spi;
 20  
 
 21  
 import org.apache.shindig.auth.SecurityToken;
 22  
 import org.apache.shindig.common.util.ImmediateFuture;
 23  
 import org.apache.shindig.common.util.ResourceLoader;
 24  
 import org.apache.shindig.social.ResponseError;
 25  
 import org.apache.shindig.social.opensocial.model.Activity;
 26  
 import org.apache.shindig.social.opensocial.model.Person;
 27  
 import org.apache.shindig.social.opensocial.service.BeanConverter;
 28  
 import org.apache.shindig.social.opensocial.spi.ActivityService;
 29  
 import org.apache.shindig.social.opensocial.spi.AppDataService;
 30  
 import org.apache.shindig.social.opensocial.spi.CollectionOptions;
 31  
 import org.apache.shindig.social.opensocial.spi.DataCollection;
 32  
 import org.apache.shindig.social.opensocial.spi.GroupId;
 33  
 import org.apache.shindig.social.opensocial.spi.PersonService;
 34  
 import org.apache.shindig.social.opensocial.spi.RestfulCollection;
 35  
 import org.apache.shindig.social.opensocial.spi.SocialSpiException;
 36  
 import org.apache.shindig.social.opensocial.spi.UserId;
 37  
 
 38  
 import com.google.common.collect.ImmutableSortedSet;
 39  
 import com.google.common.collect.Lists;
 40  
 import com.google.common.collect.Maps;
 41  
 import com.google.common.collect.Sets;
 42  
 import com.google.inject.Inject;
 43  
 import com.google.inject.Singleton;
 44  
 import com.google.inject.name.Named;
 45  
 
 46  
 import org.apache.commons.io.IOUtils;
 47  
 import org.json.JSONArray;
 48  
 import org.json.JSONException;
 49  
 import org.json.JSONObject;
 50  
 
 51  
 import java.util.Collections;
 52  
 import java.util.Comparator;
 53  
 import java.util.Iterator;
 54  
 import java.util.List;
 55  
 import java.util.Map;
 56  
 import java.util.Set;
 57  
 import java.util.concurrent.Future;
 58  
 
 59  
 /**
 60  
  * Implementation of supported services backed by a JSON DB.
 61  
  */
 62  
 @Singleton
 63  
 public class JsonDbOpensocialService implements ActivityService, PersonService, AppDataService {
 64  
 
 65  1
   private static final Comparator<Person> NAME_COMPARATOR = new Comparator<Person>() {
 66  13
     public int compare(Person person, Person person1) {
 67  12
       String name = person.getName().getUnstructured();
 68  12
       String name1 = person1.getName().getUnstructured();
 69  12
       return name.compareTo(name1);
 70  
     }
 71  
   };
 72  
 
 73  
   /**
 74  
    * The DB
 75  
    */
 76  
   private JSONObject db;
 77  
 
 78  
   /**
 79  
    * The JSON<->Bean converter
 80  
    */
 81  
   private BeanConverter converter;
 82  
 
 83  
   /**
 84  
    * db["activities"] -> Array<Person>
 85  
    */
 86  
   private static final String PEOPLE_TABLE = "people";
 87  
 
 88  
   /**
 89  
    * db["people"] -> Map<Person.Id, Array<Activity>>
 90  
    */
 91  
   private static final String ACTIVITIES_TABLE = "activities";
 92  
 
 93  
   /**
 94  
    * db["data"] -> Map<Person.Id, Map<String, String>>
 95  
    */
 96  
   private static final String DATA_TABLE = "data";
 97  
 
 98  
   /**
 99  
    * db["friendLinks"] -> Map<Person.Id, Array<Person.Id>>
 100  
    */
 101  
   private static final String FRIEND_LINK_TABLE = "friendLinks";
 102  
 
 103  
   @Inject
 104  
   public JsonDbOpensocialService(@Named("shindig.canonical.json.db")String jsonLocation,
 105  38
       @Named("shindig.bean.converter.json")BeanConverter converter) throws Exception {
 106  38
     String content = IOUtils.toString(ResourceLoader.openResource(jsonLocation), "UTF-8");
 107  38
     this.db = new JSONObject(content);
 108  38
     this.converter = converter;
 109  38
   }
 110  
 
 111  
   public JSONObject getDb() {
 112  0
     return db;
 113  
   }
 114  
 
 115  
   public void setDb(JSONObject db) {
 116  0
     this.db = db;
 117  0
   }
 118  
 
 119  
   public Future<RestfulCollection<Activity>> getActivities(Set<UserId> userIds,
 120  
       GroupId groupId, String appId, Set<String> fields, SecurityToken token)
 121  
       throws SocialSpiException  {
 122  8
     List<Activity> result = Lists.newArrayList();
 123  
     try {
 124  8
       Set<String> idSet = getIdSet(userIds, groupId, token);
 125  8
       for (String id : idSet) {
 126  13
         if (db.getJSONObject(ACTIVITIES_TABLE).has(id)) {
 127  9
           JSONArray activities = db.getJSONObject(ACTIVITIES_TABLE).getJSONArray(id);
 128  24
           for (int i = 0; i < activities.length(); i++) {
 129  15
             JSONObject activity = activities.getJSONObject(i);
 130  15
             if (appId == null || !activity.has(Activity.Field.APP_ID.toString())) {
 131  11
               result.add(convertToActivity(activity, fields));
 132  11
             } else if (activity.get(Activity.Field.APP_ID.toString()).equals(appId)) {
 133  4
               result.add(convertToActivity(activity, fields));
 134  
             }
 135  
           }
 136  
         }
 137  13
       }
 138  8
       return ImmediateFuture.newInstance(new RestfulCollection<Activity>(result));
 139  0
     } catch (JSONException je) {
 140  0
       throw new SocialSpiException(ResponseError.INTERNAL_ERROR, je.getMessage(), je);
 141  
     }
 142  
   }
 143  
 
 144  
   public Future<RestfulCollection<Activity>> getActivities(UserId userId,
 145  
       GroupId groupId, String appId, Set<String> fields,
 146  
       Set<String> activityIds, SecurityToken token) throws SocialSpiException {
 147  0
     List<Activity> result = Lists.newArrayList();
 148  
     try {
 149  0
       String user = userId.getUserId(token);
 150  0
       if (db.getJSONObject(ACTIVITIES_TABLE).has(user)) {
 151  0
         JSONArray activities = db.getJSONObject(ACTIVITIES_TABLE).getJSONArray(user);
 152  0
         for (int i = 0; i < activities.length(); i++) {
 153  0
           JSONObject activity = activities.getJSONObject(i);
 154  0
           if (activity.get(Activity.Field.USER_ID.toString()).equals(user)
 155  
               && activityIds.contains(activity.getString(Activity.Field.ID.toString()))) {
 156  0
             result.add(convertToActivity(activity, fields));
 157  
           }
 158  
         }
 159  
       }
 160  0
       return ImmediateFuture.newInstance(new RestfulCollection<Activity>(result));
 161  0
     } catch (JSONException je) {
 162  0
       throw new SocialSpiException(ResponseError.INTERNAL_ERROR, je.getMessage(), je);
 163  
     }
 164  
   }
 165  
 
 166  
   public Future<Activity> getActivity(UserId userId,
 167  
       GroupId groupId, String appId, Set<String> fields, String activityId, SecurityToken token)
 168  
       throws SocialSpiException {
 169  
     try {
 170  4
       String user = userId.getUserId(token);
 171  4
       if (db.getJSONObject(ACTIVITIES_TABLE).has(user)) {
 172  4
         JSONArray activities = db.getJSONObject(ACTIVITIES_TABLE).getJSONArray(user);
 173  4
         for (int i = 0; i < activities.length(); i++) {
 174  3
           JSONObject activity = activities.getJSONObject(i);
 175  3
           if (activity.get(Activity.Field.USER_ID.toString()).equals(user)
 176  
               && activity.get(Activity.Field.ID.toString()).equals(activityId)) {
 177  3
             return ImmediateFuture.newInstance(convertToActivity(activity, fields));
 178  
           }
 179  
         }
 180  
       }
 181  
 
 182  1
       throw new SocialSpiException(ResponseError.BAD_REQUEST, "Activity not found");
 183  0
     } catch (JSONException je) {
 184  0
       throw new SocialSpiException(ResponseError.INTERNAL_ERROR, je.getMessage(), je);
 185  
     }
 186  
   }
 187  
 
 188  
   public Future<Void> deleteActivities(UserId userId, GroupId groupId, String appId,
 189  
       Set<String> activityIds, SecurityToken token) throws SocialSpiException {
 190  
     try {
 191  1
       String user = userId.getUserId(token);
 192  1
       if (db.getJSONObject(ACTIVITIES_TABLE).has(user)) {
 193  1
         JSONArray activities = db.getJSONObject(ACTIVITIES_TABLE).getJSONArray(user);
 194  1
         if (activities != null) {
 195  1
           JSONArray newList = new JSONArray();
 196  3
           for (int i = 0; i < activities.length(); i++) {
 197  2
             JSONObject activity = activities.getJSONObject(i);
 198  2
             if (!activityIds.contains(activity.getString(Activity.Field.ID.toString()))) {
 199  0
               newList.put(activity);
 200  
             }
 201  
           }
 202  1
           db.getJSONObject(ACTIVITIES_TABLE).put(user, newList);
 203  
           // TODO. This seems very odd that we return no useful response in this case
 204  
           // There is no way to represent not-found
 205  
           // if (found) { ??
 206  
           //}
 207  
         }
 208  
       }
 209  
       // What is the appropriate response here??
 210  1
       return ImmediateFuture.newInstance(null);
 211  0
     } catch (JSONException je) {
 212  0
       throw new SocialSpiException(ResponseError.INTERNAL_ERROR, je.getMessage(), je);
 213  
     }
 214  
   }
 215  
 
 216  
   public Future<Void> createActivity(UserId userId, GroupId groupId, String appId,
 217  
       Set<String> fields, Activity activity, SecurityToken token) throws SocialSpiException {
 218  
     // Are fields really needed here?
 219  
     try {
 220  2
       JSONObject jsonObject = convertFromActivity(activity, fields);
 221  2
       if (!jsonObject.has(Activity.Field.ID.toString())) {
 222  2
         jsonObject.put(Activity.Field.ID.toString(), System.currentTimeMillis());
 223  
       }
 224  2
       JSONArray jsonArray = db.getJSONObject(ACTIVITIES_TABLE)
 225  
           .getJSONArray(userId.getUserId(token));
 226  2
       if (jsonArray == null) {
 227  0
         jsonArray = new JSONArray();
 228  0
         db.getJSONObject(ACTIVITIES_TABLE).put(userId.getUserId(token), jsonArray);
 229  
       }
 230  2
       jsonArray.put(jsonObject);
 231  2
       return ImmediateFuture.newInstance(null);
 232  0
     } catch (JSONException je) {
 233  0
       throw new SocialSpiException(ResponseError.INTERNAL_ERROR, je.getMessage(), je);
 234  
     }
 235  
   }
 236  
 
 237  
   public Future<RestfulCollection<Person>> getPeople(Set<UserId> userIds,
 238  
       GroupId groupId, CollectionOptions options, Set<String> fields, SecurityToken token)
 239  
       throws SocialSpiException {
 240  8
     List<Person> result = Lists.newArrayList();
 241  
     try {
 242  8
       JSONArray people = db.getJSONArray(PEOPLE_TABLE);
 243  
 
 244  8
       Set<String> idSet = getIdSet(userIds, groupId, token);
 245  
 
 246  56
       for (int i = 0; i < people.length(); i++) {
 247  48
         JSONObject person = people.getJSONObject(i);
 248  48
         if (!idSet.contains(person.get(Person.Field.ID.toString()))) {
 249  22
           continue;
 250  
         }
 251  
         // Add group support later
 252  26
         result.add(convertToPerson(person, fields));
 253  
       }
 254  
 
 255  
       // We can pretend that by default the people are in top friends order
 256  8
       if (options.getSortBy().equals(Person.Field.NAME.toString())) {
 257  6
         Collections.sort(result, NAME_COMPARATOR);
 258  
       }
 259  
 
 260  8
       if (options.getSortOrder().equals(SortOrder.descending)) {
 261  0
         Collections.reverse(result);
 262  
       }
 263  
 
 264  
       // TODO: The samplecontainer doesn't really have the concept of HAS_APP so
 265  
       // we can't support any filters yet. We should fix this.
 266  
 
 267  8
       int totalSize = result.size();
 268  8
       int last = options.getFirst() + options.getMax();
 269  8
       result = result.subList(options.getFirst(), Math.min(last, totalSize));
 270  
 
 271  8
       return ImmediateFuture.newInstance(new RestfulCollection<Person>(
 272  
           result, options.getFirst(), totalSize));
 273  0
     } catch (JSONException je) {
 274  0
       throw new SocialSpiException(ResponseError.INTERNAL_ERROR, je.getMessage(), je);
 275  
     }
 276  
   }
 277  
 
 278  
   public Future<Person> getPerson(UserId id, Set<String> fields,
 279  
       SecurityToken token) throws SocialSpiException {
 280  
     try {
 281  4
       JSONArray people = db.getJSONArray(PEOPLE_TABLE);
 282  
 
 283  4
       for (int i = 0; i < people.length(); i++) {
 284  4
         JSONObject person = people.getJSONObject(i);
 285  4
         if (id != null && person.get(Person.Field.ID.toString())
 286  
             .equals(id.getUserId(token))) {
 287  4
           return ImmediateFuture.newInstance(convertToPerson(person, fields));
 288  
         }
 289  
       }
 290  0
       throw new SocialSpiException(ResponseError.BAD_REQUEST, "Person not found");
 291  0
     } catch (JSONException je) {
 292  0
       throw new SocialSpiException(ResponseError.INTERNAL_ERROR, je.getMessage(), je);
 293  
     }
 294  
   }
 295  
 
 296  
   public Future<DataCollection> getPersonData(Set<UserId> userIds, GroupId groupId,
 297  
       String appId, Set<String> fields, SecurityToken token) throws SocialSpiException {
 298  
     try {
 299  22
       Map<String, Map<String, String>> idToData = Maps.newHashMap();
 300  22
       Set<String> idSet = getIdSet(userIds, groupId, token);
 301  22
       for (String id : idSet) {
 302  
         JSONObject personData;
 303  27
         if (!db.getJSONObject(DATA_TABLE).has(id)) {
 304  2
           personData = new JSONObject();
 305  2
         } else {
 306  25
           if (!fields.isEmpty()) {
 307  8
             personData = new JSONObject(
 308  
                 db.getJSONObject(DATA_TABLE).getJSONObject(id),
 309  
                 fields.toArray(new String[fields.size()]));
 310  8
           } else {
 311  17
             personData = db.getJSONObject(DATA_TABLE).getJSONObject(id);
 312  
           }
 313  
         }
 314  
 
 315  
         // TODO: We can use the converter here to do this for us
 316  27
         Iterator keys = personData.keys();
 317  27
         Map<String, String> data = Maps.newHashMap();
 318  52
         while (keys.hasNext()) {
 319  25
           String key = (String) keys.next();
 320  25
           data.put(key, personData.getString(key));
 321  25
         }
 322  27
         idToData.put(id, data);
 323  27
       }
 324  22
       return ImmediateFuture.newInstance(new DataCollection(idToData));
 325  0
     } catch (JSONException je) {
 326  0
       throw new SocialSpiException(ResponseError.INTERNAL_ERROR, je.getMessage(), je);
 327  
     }
 328  
   }
 329  
 
 330  
   public Future<Void> deletePersonData(UserId userId, GroupId groupId, String appId,
 331  
       Set<String> fields, SecurityToken token) throws SocialSpiException {
 332  
     try {
 333  5
       String user = userId.getUserId(token);
 334  5
       if (!db.getJSONObject(DATA_TABLE).has(user)) {
 335  0
         return null;
 336  
       }
 337  5
       JSONObject newPersonData = new JSONObject();
 338  5
       JSONObject oldPersonData = db.getJSONObject(DATA_TABLE).getJSONObject(user);
 339  5
       Iterator keys = oldPersonData.keys();
 340  11
       while (keys.hasNext()) {
 341  6
         String key = (String) keys.next();
 342  6
         if (!fields.contains(key)) {
 343  3
           newPersonData.put(key, oldPersonData.getString(key));
 344  
         }
 345  6
       }
 346  5
       db.getJSONObject(DATA_TABLE).put(user, newPersonData);
 347  5
       return ImmediateFuture.newInstance(null);
 348  0
     } catch (JSONException je) {
 349  0
       throw new SocialSpiException(ResponseError.INTERNAL_ERROR, je.getMessage(), je);
 350  
     }
 351  
   }
 352  
 
 353  
   public Future<Void> updatePersonData(UserId userId, GroupId groupId, String appId,
 354  
       Set<String> fields, Map<String, String> values, SecurityToken token)
 355  
       throws SocialSpiException {
 356  
     // TODO: this seems redundant. No need to pass both fields and a map of field->value
 357  
     // TODO: According to rest, yes there is. If a field is in the param list but not in the map
 358  
     // that means it is a delete
 359  
 
 360  
     try {
 361  3
       JSONObject personData = db.getJSONObject(DATA_TABLE).getJSONObject(userId.getUserId(token));
 362  3
       if (personData == null) {
 363  0
         personData = new JSONObject();
 364  0
         db.getJSONObject(DATA_TABLE).put(userId.getUserId(token), personData);
 365  
       }
 366  
 
 367  3
       for (Map.Entry<String, String> entry : values.entrySet()) {
 368  4
         personData.put(entry.getKey(), entry.getValue());
 369  4
       }
 370  3
       return ImmediateFuture.newInstance(null);
 371  0
     } catch (JSONException je) {
 372  0
       throw new SocialSpiException(ResponseError.INTERNAL_ERROR, je.getMessage(), je);
 373  
     }
 374  
   }
 375  
 
 376  
   /**
 377  
    * Get the set of user id's from a user and group
 378  
    */
 379  
   private Set<String> getIdSet(UserId user, GroupId group, SecurityToken token)
 380  
       throws JSONException {
 381  41
     String userId = user.getUserId(token);
 382  
 
 383  41
     if (group == null) {
 384  0
       return ImmutableSortedSet.of(userId);
 385  
     }
 386  
 
 387  41
     Set<String> returnVal = Sets.newLinkedHashSet();
 388  41
     switch (group.getType()) {
 389  
       case all:
 390  
       case friends:
 391  
       case groupId:
 392  13
         if (db.getJSONObject(FRIEND_LINK_TABLE).has(userId)) {
 393  13
           JSONArray friends = db.getJSONObject(FRIEND_LINK_TABLE).getJSONArray(userId);
 394  51
           for (int i = 0; i < friends.length(); i++) {
 395  38
             returnVal.add(friends.getString(i));
 396  
           }
 397  13
         }
 398  
         break;
 399  
       case self:
 400  28
         returnVal.add(userId);
 401  
         break;
 402  
     }
 403  41
     return returnVal;
 404  
   }
 405  
 
 406  
   /**
 407  
    * Get the set of user id's for a set of users and a group
 408  
    */
 409  
   private Set<String> getIdSet(Set<UserId> users, GroupId group, SecurityToken token)
 410  
       throws JSONException {
 411  38
     Set<String> ids = Sets.newLinkedHashSet();
 412  38
     for (UserId user : users) {
 413  41
       ids.addAll(getIdSet(user, group, token));
 414  41
     }
 415  38
     return ids;
 416  
   }
 417  
 
 418  
   private Activity convertToActivity(JSONObject object, Set<String> fields) throws JSONException {
 419  18
     if (!fields.isEmpty()) {
 420  
       // Create a copy with just the specified fields
 421  1
       object = new JSONObject(object, fields.toArray(new String[fields.size()]));
 422  
     }
 423  18
     return converter.convertToObject(object.toString(), Activity.class);
 424  
   }
 425  
 
 426  
   private JSONObject convertFromActivity(Activity activity, Set<String> fields)
 427  
       throws JSONException {
 428  
     // TODO Not using fields yet
 429  2
     return new JSONObject(converter.convertToString(activity));
 430  
   }
 431  
 
 432  
   private Person convertToPerson(JSONObject object, Set<String> fields) throws JSONException {
 433  30
     if (!fields.isEmpty()) {
 434  
       // Create a copy with just the specified fields
 435  22
       object = new JSONObject(object, fields.toArray(new String[fields.size()]));
 436  
     }
 437  30
     return converter.convertToObject(object.toString(), Person.class);
 438  
   }
 439  
 }