1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 private static final Comparator<Person> NAME_COMPARATOR = new Comparator<Person>() {
66 public int compare(Person person, Person person1) {
67 String name = person.getName().getUnstructured();
68 String name1 = person1.getName().getUnstructured();
69 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 @Named("shindig.bean.converter.json")BeanConverter converter) throws Exception {
106 String content = IOUtils.toString(ResourceLoader.openResource(jsonLocation), "UTF-8");
107 this.db = new JSONObject(content);
108 this.converter = converter;
109 }
110
111 public JSONObject getDb() {
112 return db;
113 }
114
115 public void setDb(JSONObject db) {
116 this.db = db;
117 }
118
119 public Future<RestfulCollection<Activity>> getActivities(Set<UserId> userIds,
120 GroupId groupId, String appId, Set<String> fields, SecurityToken token)
121 throws SocialSpiException {
122 List<Activity> result = Lists.newArrayList();
123 try {
124 Set<String> idSet = getIdSet(userIds, groupId, token);
125 for (String id : idSet) {
126 if (db.getJSONObject(ACTIVITIES_TABLE).has(id)) {
127 JSONArray activities = db.getJSONObject(ACTIVITIES_TABLE).getJSONArray(id);
128 for (int i = 0; i < activities.length(); i++) {
129 JSONObject activity = activities.getJSONObject(i);
130 if (appId == null || !activity.has(Activity.Field.APP_ID.toString())) {
131 result.add(convertToActivity(activity, fields));
132 } else if (activity.get(Activity.Field.APP_ID.toString()).equals(appId)) {
133 result.add(convertToActivity(activity, fields));
134 }
135 }
136 }
137 }
138 return ImmediateFuture.newInstance(new RestfulCollection<Activity>(result));
139 } catch (JSONException je) {
140 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 List<Activity> result = Lists.newArrayList();
148 try {
149 String user = userId.getUserId(token);
150 if (db.getJSONObject(ACTIVITIES_TABLE).has(user)) {
151 JSONArray activities = db.getJSONObject(ACTIVITIES_TABLE).getJSONArray(user);
152 for (int i = 0; i < activities.length(); i++) {
153 JSONObject activity = activities.getJSONObject(i);
154 if (activity.get(Activity.Field.USER_ID.toString()).equals(user)
155 && activityIds.contains(activity.getString(Activity.Field.ID.toString()))) {
156 result.add(convertToActivity(activity, fields));
157 }
158 }
159 }
160 return ImmediateFuture.newInstance(new RestfulCollection<Activity>(result));
161 } catch (JSONException je) {
162 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 String user = userId.getUserId(token);
171 if (db.getJSONObject(ACTIVITIES_TABLE).has(user)) {
172 JSONArray activities = db.getJSONObject(ACTIVITIES_TABLE).getJSONArray(user);
173 for (int i = 0; i < activities.length(); i++) {
174 JSONObject activity = activities.getJSONObject(i);
175 if (activity.get(Activity.Field.USER_ID.toString()).equals(user)
176 && activity.get(Activity.Field.ID.toString()).equals(activityId)) {
177 return ImmediateFuture.newInstance(convertToActivity(activity, fields));
178 }
179 }
180 }
181
182 throw new SocialSpiException(ResponseError.BAD_REQUEST, "Activity not found");
183 } catch (JSONException je) {
184 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 String user = userId.getUserId(token);
192 if (db.getJSONObject(ACTIVITIES_TABLE).has(user)) {
193 JSONArray activities = db.getJSONObject(ACTIVITIES_TABLE).getJSONArray(user);
194 if (activities != null) {
195 JSONArray newList = new JSONArray();
196 for (int i = 0; i < activities.length(); i++) {
197 JSONObject activity = activities.getJSONObject(i);
198 if (!activityIds.contains(activity.getString(Activity.Field.ID.toString()))) {
199 newList.put(activity);
200 }
201 }
202 db.getJSONObject(ACTIVITIES_TABLE).put(user, newList);
203
204
205
206
207 }
208 }
209
210 return ImmediateFuture.newInstance(null);
211 } catch (JSONException je) {
212 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
219 try {
220 JSONObject jsonObject = convertFromActivity(activity, fields);
221 if (!jsonObject.has(Activity.Field.ID.toString())) {
222 jsonObject.put(Activity.Field.ID.toString(), System.currentTimeMillis());
223 }
224 JSONArray jsonArray = db.getJSONObject(ACTIVITIES_TABLE)
225 .getJSONArray(userId.getUserId(token));
226 if (jsonArray == null) {
227 jsonArray = new JSONArray();
228 db.getJSONObject(ACTIVITIES_TABLE).put(userId.getUserId(token), jsonArray);
229 }
230 jsonArray.put(jsonObject);
231 return ImmediateFuture.newInstance(null);
232 } catch (JSONException je) {
233 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 List<Person> result = Lists.newArrayList();
241 try {
242 JSONArray people = db.getJSONArray(PEOPLE_TABLE);
243
244 Set<String> idSet = getIdSet(userIds, groupId, token);
245
246 for (int i = 0; i < people.length(); i++) {
247 JSONObject person = people.getJSONObject(i);
248 if (!idSet.contains(person.get(Person.Field.ID.toString()))) {
249 continue;
250 }
251
252 result.add(convertToPerson(person, fields));
253 }
254
255
256 if (options.getSortBy().equals(Person.Field.NAME.toString())) {
257 Collections.sort(result, NAME_COMPARATOR);
258 }
259
260 if (options.getSortOrder().equals(SortOrder.descending)) {
261 Collections.reverse(result);
262 }
263
264
265
266
267 int totalSize = result.size();
268 int last = options.getFirst() + options.getMax();
269 result = result.subList(options.getFirst(), Math.min(last, totalSize));
270
271 return ImmediateFuture.newInstance(new RestfulCollection<Person>(
272 result, options.getFirst(), totalSize));
273 } catch (JSONException je) {
274 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 JSONArray people = db.getJSONArray(PEOPLE_TABLE);
282
283 for (int i = 0; i < people.length(); i++) {
284 JSONObject person = people.getJSONObject(i);
285 if (id != null && person.get(Person.Field.ID.toString())
286 .equals(id.getUserId(token))) {
287 return ImmediateFuture.newInstance(convertToPerson(person, fields));
288 }
289 }
290 throw new SocialSpiException(ResponseError.BAD_REQUEST, "Person not found");
291 } catch (JSONException je) {
292 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 Map<String, Map<String, String>> idToData = Maps.newHashMap();
300 Set<String> idSet = getIdSet(userIds, groupId, token);
301 for (String id : idSet) {
302 JSONObject personData;
303 if (!db.getJSONObject(DATA_TABLE).has(id)) {
304 personData = new JSONObject();
305 } else {
306 if (!fields.isEmpty()) {
307 personData = new JSONObject(
308 db.getJSONObject(DATA_TABLE).getJSONObject(id),
309 fields.toArray(new String[fields.size()]));
310 } else {
311 personData = db.getJSONObject(DATA_TABLE).getJSONObject(id);
312 }
313 }
314
315
316 Iterator keys = personData.keys();
317 Map<String, String> data = Maps.newHashMap();
318 while (keys.hasNext()) {
319 String key = (String) keys.next();
320 data.put(key, personData.getString(key));
321 }
322 idToData.put(id, data);
323 }
324 return ImmediateFuture.newInstance(new DataCollection(idToData));
325 } catch (JSONException je) {
326 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 String user = userId.getUserId(token);
334 if (!db.getJSONObject(DATA_TABLE).has(user)) {
335 return null;
336 }
337 JSONObject newPersonData = new JSONObject();
338 JSONObject oldPersonData = db.getJSONObject(DATA_TABLE).getJSONObject(user);
339 Iterator keys = oldPersonData.keys();
340 while (keys.hasNext()) {
341 String key = (String) keys.next();
342 if (!fields.contains(key)) {
343 newPersonData.put(key, oldPersonData.getString(key));
344 }
345 }
346 db.getJSONObject(DATA_TABLE).put(user, newPersonData);
347 return ImmediateFuture.newInstance(null);
348 } catch (JSONException je) {
349 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
357
358
359
360 try {
361 JSONObject personData = db.getJSONObject(DATA_TABLE).getJSONObject(userId.getUserId(token));
362 if (personData == null) {
363 personData = new JSONObject();
364 db.getJSONObject(DATA_TABLE).put(userId.getUserId(token), personData);
365 }
366
367 for (Map.Entry<String, String> entry : values.entrySet()) {
368 personData.put(entry.getKey(), entry.getValue());
369 }
370 return ImmediateFuture.newInstance(null);
371 } catch (JSONException je) {
372 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 String userId = user.getUserId(token);
382
383 if (group == null) {
384 return ImmutableSortedSet.of(userId);
385 }
386
387 Set<String> returnVal = Sets.newLinkedHashSet();
388 switch (group.getType()) {
389 case all:
390 case friends:
391 case groupId:
392 if (db.getJSONObject(FRIEND_LINK_TABLE).has(userId)) {
393 JSONArray friends = db.getJSONObject(FRIEND_LINK_TABLE).getJSONArray(userId);
394 for (int i = 0; i < friends.length(); i++) {
395 returnVal.add(friends.getString(i));
396 }
397 }
398 break;
399 case self:
400 returnVal.add(userId);
401 break;
402 }
403 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 Set<String> ids = Sets.newLinkedHashSet();
412 for (UserId user : users) {
413 ids.addAll(getIdSet(user, group, token));
414 }
415 return ids;
416 }
417
418 private Activity convertToActivity(JSONObject object, Set<String> fields) throws JSONException {
419 if (!fields.isEmpty()) {
420
421 object = new JSONObject(object, fields.toArray(new String[fields.size()]));
422 }
423 return converter.convertToObject(object.toString(), Activity.class);
424 }
425
426 private JSONObject convertFromActivity(Activity activity, Set<String> fields)
427 throws JSONException {
428
429 return new JSONObject(converter.convertToString(activity));
430 }
431
432 private Person convertToPerson(JSONObject object, Set<String> fields) throws JSONException {
433 if (!fields.isEmpty()) {
434
435 object = new JSONObject(object, fields.toArray(new String[fields.size()]));
436 }
437 return converter.convertToObject(object.toString(), Person.class);
438 }
439 }