View Javadoc

1   /*
2    * Copyright 2015 Data Archiving and Networked Services (an institute of
3    * Koninklijke Nederlandse Akademie van Wetenschappen), King's College London,
4    * Georg-August-Universitaet Goettingen Stiftung Oeffentlichen Rechts
5    *
6    * Licensed under the EUPL, Version 1.1 or – as soon they will be approved by
7    * the European Commission - subsequent versions of the EUPL (the "Licence");
8    * You may not use this work except in compliance with the Licence.
9    * You may obtain a copy of the Licence at:
10   *
11   * https://joinup.ec.europa.eu/software/page/eupl
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the Licence is distributed on an "AS IS" basis,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the Licence for the specific language governing
17   * permissions and limitations under the Licence.
18   */
19  
20  package eu.ehri.extension;
21  
22  import com.google.common.collect.Sets;
23  import eu.ehri.extension.base.AbstractAccessibleResource;
24  import eu.ehri.extension.base.AbstractResource;
25  import eu.ehri.extension.base.DeleteResource;
26  import eu.ehri.extension.base.GetResource;
27  import eu.ehri.extension.base.ListResource;
28  import eu.ehri.extension.base.UpdateResource;
29  import eu.ehri.project.api.Api;
30  import eu.ehri.project.api.EventsApi;
31  import eu.ehri.project.api.UserProfilesApi;
32  import eu.ehri.project.core.Tx;
33  import eu.ehri.project.definitions.Entities;
34  import eu.ehri.project.exceptions.DeserializationError;
35  import eu.ehri.project.exceptions.ItemNotFound;
36  import eu.ehri.project.exceptions.PermissionDenied;
37  import eu.ehri.project.exceptions.ValidationError;
38  import eu.ehri.project.models.Annotation;
39  import eu.ehri.project.models.Group;
40  import eu.ehri.project.models.Link;
41  import eu.ehri.project.models.UserProfile;
42  import eu.ehri.project.models.VirtualUnit;
43  import eu.ehri.project.models.base.Watchable;
44  import eu.ehri.project.persistence.Bundle;
45  import org.neo4j.graphdb.GraphDatabaseService;
46  
47  import javax.ws.rs.Consumes;
48  import javax.ws.rs.DELETE;
49  import javax.ws.rs.DefaultValue;
50  import javax.ws.rs.GET;
51  import javax.ws.rs.POST;
52  import javax.ws.rs.PUT;
53  import javax.ws.rs.Path;
54  import javax.ws.rs.PathParam;
55  import javax.ws.rs.Produces;
56  import javax.ws.rs.QueryParam;
57  import javax.ws.rs.core.Context;
58  import javax.ws.rs.core.MediaType;
59  import javax.ws.rs.core.Response;
60  import java.util.List;
61  import java.util.Set;
62  
63  /**
64   * Provides a web service interface for the UserProfile.
65   */
66  @Path(AbstractResource.RESOURCE_ENDPOINT_PREFIX + "/" + Entities.USER_PROFILE)
67  public class UserProfileResource extends AbstractAccessibleResource<UserProfile>
68          implements GetResource, ListResource, UpdateResource, DeleteResource {
69  
70  
71      public UserProfileResource(@Context GraphDatabaseService database) {
72          super(database, UserProfile.class);
73      }
74  
75      @GET
76      @Produces(MediaType.APPLICATION_JSON)
77      @Path("{id:[^/]+}")
78      @Override
79      public Response get(@PathParam("id") String id) throws ItemNotFound {
80          return getItem(id);
81      }
82  
83      @GET
84      @Produces(MediaType.APPLICATION_JSON)
85      @Override
86      public Response list() {
87          return listItems();
88      }
89  
90      @POST
91      @Consumes(MediaType.APPLICATION_JSON)
92      @Produces(MediaType.APPLICATION_JSON)
93      public Response createUserProfile(Bundle bundle,
94              @QueryParam(GROUP_PARAM) List<String> groupIds,
95              @QueryParam(ACCESSOR_PARAM) List<String> accessors) throws PermissionDenied,
96              ValidationError, DeserializationError,
97              ItemNotFound {
98          try (final Tx tx = beginTx()) {
99              final Api.Acl acl = api().acl();
100             final Set<Group> groups = Sets.newHashSet();
101             for (String groupId : groupIds) {
102                 groups.add(manager.getEntity(groupId, Group.class));
103             }
104             Response item = createItem(bundle, accessors, userProfile -> {
105                 for (Group group : groups) {
106                     acl.addAccessorToGroup(group, userProfile);
107                 }
108             });
109             tx.success();
110             return item;
111         } catch (ItemNotFound e) {
112             throw new DeserializationError("User or group given as accessor not found: " + e.getValue());
113         }
114     }
115 
116     @PUT
117     @Consumes(MediaType.APPLICATION_JSON)
118     @Produces(MediaType.APPLICATION_JSON)
119     @Path("{id:[^/]+}")
120     @Override
121     public Response update(@PathParam("id") String id, Bundle bundle)
122             throws PermissionDenied, ValidationError,
123             DeserializationError, ItemNotFound {
124         try (final Tx tx = beginTx()) {
125             Response item = updateItem(id, bundle);
126             tx.success();
127             return item;
128         }
129     }
130 
131     @DELETE
132     @Path("{id:[^/]+}")
133     @Override
134     public void delete(@PathParam("id") String id)
135             throws PermissionDenied, ItemNotFound, ValidationError {
136         try (final Tx tx = beginTx()) {
137             deleteItem(id);
138             tx.success();
139         }
140     }
141 
142     @GET
143     @Produces(MediaType.APPLICATION_JSON)
144     @Path("{id:[^/]+}/followers")
145     public Response listFollowers(@PathParam("id") String userId) throws ItemNotFound {
146         try (final Tx tx = beginTx()) {
147             UserProfile user = api().detail(userId, cls);
148             Response response = streamingPage(() -> getQuery()
149                     .page(user.getFollowers(), UserProfile.class));
150             tx.success();
151             return response;
152         }
153     }
154 
155     @GET
156     @Produces(MediaType.APPLICATION_JSON)
157     @Path("{id:[^/]+}/following")
158     public Response listFollowing(@PathParam("id") String userId) throws ItemNotFound {
159         try (final Tx tx = beginTx()) {
160             UserProfile user = api().detail(userId, cls);
161             Response response = streamingPage(() -> getQuery()
162                     .page(user.getFollowing(), UserProfile.class));
163             tx.success();
164             return response;
165         }
166     }
167 
168     @GET
169     @Produces(MediaType.APPLICATION_JSON)
170     @Path("{id:[^/]+}/is-following/{otherId:[^/]+}")
171     public boolean isFollowing(
172             @PathParam("id") String userId,
173             @PathParam("otherId") String otherId)
174             throws PermissionDenied, ItemNotFound {
175         try (final Tx tx = beginTx()) {
176             UserProfile user = api().detail(userId, cls);
177             boolean following = user.isFollowing(
178                     manager.getEntity(otherId, UserProfile.class));
179             tx.success();
180             return following;
181         }
182     }
183 
184     @GET
185     @Produces(MediaType.APPLICATION_JSON)
186     @Path("{id:[^/]+}/is-follower/{otherId:[^/]+}")
187     public boolean isFollower(
188             @PathParam("id") String userId,
189             @PathParam("otherId") String otherId)
190             throws PermissionDenied, ItemNotFound {
191         try (final Tx tx = beginTx()) {
192             UserProfile user = api().detail(userId, cls);
193             boolean follower = user.isFollower(manager.getEntity(otherId, UserProfile.class));
194             tx.success();
195             return follower;
196         }
197     }
198 
199     @POST
200     @Path("{id:[^/]+}/following")
201     public void followUserProfile(
202             @PathParam("id") String userId,
203             @QueryParam(ID_PARAM) List<String> otherIds)
204             throws PermissionDenied, ItemNotFound {
205         try (final Tx tx = beginTx()) {
206             userProfilesApi().addFollowers(userId, otherIds);
207             tx.success();
208         }
209     }
210 
211     @DELETE
212     @Path("{id:[^/]+}/following")
213     public void unfollowUserProfile(
214             @PathParam("id") String userId,
215             @QueryParam(ID_PARAM) List<String> otherIds)
216             throws PermissionDenied, ItemNotFound {
217         try (final Tx tx = beginTx()) {
218             userProfilesApi().removeFollowers(userId, otherIds);
219             tx.success();
220         }
221     }
222 
223     @GET
224     @Produces(MediaType.APPLICATION_JSON)
225     @Path("{id:[^/]+}/blocked")
226     public Response listBlocked(@PathParam("id") String userId) throws ItemNotFound {
227         try (final Tx tx = beginTx()) {
228             UserProfile user = api().detail(userId, cls);
229             Response response = streamingPage(() -> getQuery()
230                     .page(user.getBlocked(), UserProfile.class));
231             tx.success();
232             return response;
233         }
234     }
235 
236     @GET
237     @Produces(MediaType.APPLICATION_JSON)
238     @Path("{id:[^/]+}/is-blocking/{otherId:[^/]+}")
239     public boolean isBlocking(
240             @PathParam("id") String userId,
241             @PathParam("otherId") String otherId)
242             throws PermissionDenied, ItemNotFound {
243         try (final Tx tx = beginTx()) {
244             UserProfile user = api().detail(userId, cls);
245             boolean blocking = user.isBlocking(manager.getEntity(otherId, UserProfile.class));
246             tx.success();
247             return blocking;
248         }
249     }
250 
251     @POST
252     @Path("{id:[^/]+}/blocked")
253     public void blockUserProfile(
254             @PathParam("id") String userId,
255             @QueryParam(ID_PARAM) List<String> otherIds)
256             throws PermissionDenied, ItemNotFound {
257         try (final Tx tx = beginTx()) {
258             userProfilesApi().addBlocked(userId, otherIds);
259             tx.success();
260         }
261     }
262 
263     @DELETE
264     @Path("{id:[^/]+}/blocked")
265     public void unblockUserProfile(
266             @PathParam("id") String userId,
267             @QueryParam(ID_PARAM) List<String> otherIds)
268             throws PermissionDenied, ItemNotFound {
269         try (final Tx tx = beginTx()) {
270             userProfilesApi().removeBlocked(userId, otherIds);
271             tx.success();
272         }
273     }
274 
275     @GET
276     @Produces(MediaType.APPLICATION_JSON)
277     @Path("{id:[^/]+}/watching")
278     public Response listWatching(@PathParam("id") String userId) throws ItemNotFound {
279         try (final Tx tx = beginTx()) {
280             UserProfile user = api().detail(userId, cls);
281             Response response = streamingPage(() -> getQuery()
282                     .page(user.getWatching(), Watchable.class));
283             tx.success();
284             return response;
285         }
286     }
287 
288     @POST
289     @Path("{id:[^/]+}/watching")
290     public void watchItem(
291             @PathParam("id") String userId,
292             @QueryParam(ID_PARAM) List<String> otherIds)
293             throws PermissionDenied, ItemNotFound {
294         try (final Tx tx = beginTx()) {
295             userProfilesApi().addWatching(userId, otherIds);
296             tx.success();
297         }
298     }
299 
300     @DELETE
301     @Path("{id:[^/]+}/watching")
302     public void unwatchItem(
303             @PathParam("id") String userId,
304             @QueryParam(ID_PARAM) List<String> otherIds)
305             throws PermissionDenied, ItemNotFound {
306         try (final Tx tx = beginTx()) {
307             userProfilesApi().removeWatching(userId, otherIds);
308             tx.success();
309         }
310     }
311 
312     @GET
313     @Produces(MediaType.APPLICATION_JSON)
314     @Path("{id:[^/]+}/is-watching/{otherId:[^/]+}")
315     public boolean isWatching(
316             @PathParam("id") String userId,
317             @PathParam("otherId") String otherId)
318             throws PermissionDenied, ItemNotFound {
319         try (final Tx tx = beginTx()) {
320             UserProfile user = api().detail(userId, cls);
321             boolean watching = user.isWatching(manager.getEntity(otherId, Watchable.class));
322             tx.success();
323             return watching;
324         }
325     }
326 
327     @GET
328     @Produces(MediaType.APPLICATION_JSON)
329     @Path("{id:[^/]+}/annotations")
330     public Response listAnnotations(@PathParam("id") String userId) throws ItemNotFound {
331         try (final Tx tx = beginTx()) {
332             UserProfile user = api().detail(userId, cls);
333             Response response = streamingPage(() -> getQuery()
334                     .page(user.getAnnotations(), Annotation.class));
335             tx.success();
336             return response;
337         }
338     }
339 
340     @GET
341     @Produces(MediaType.APPLICATION_JSON)
342     @Path("{id:[^/]+}/links")
343     public Response pageLinks(@PathParam("id") String userId) throws ItemNotFound {
344         try (final Tx tx = beginTx()) {
345             UserProfile user = api().detail(userId, cls);
346             Response response = streamingPage(() -> getQuery()
347                     .page(user.getLinks(), Link.class));
348             tx.success();
349             return response;
350         }
351     }
352 
353     @GET
354     @Produces(MediaType.APPLICATION_JSON)
355     @Path("{id:[^/]+}/virtual-units")
356     public Response pageVirtualUnits(@PathParam("id") String userId) throws ItemNotFound {
357         try (final Tx tx = beginTx()) {
358             UserProfile user = api().detail(userId, cls);
359             Response response = streamingPage(() -> getQuery()
360                     .page(user.getVirtualUnits(), VirtualUnit.class));
361             tx.success();
362             return response;
363         }
364     }
365 
366     /**
367      * Fetch an aggregate list of a user's actions.
368      *
369      * @param userId      the user's ID
370      * @param aggregation the manner in which to aggregate the results, accepting
371      *                    "user", "strict" or "off" (no aggregation). Default is
372      *                    <b>strict</b>.
373      * @return a list of event ranges
374      */
375     @GET
376     @Produces(MediaType.APPLICATION_JSON)
377     @Path("{id:[^/]+}/actions")
378     public Response aggregateUserActions(
379             @PathParam("id") String userId,
380             @QueryParam(AGGREGATION_PARAM) @DefaultValue("strict") EventsApi.Aggregation aggregation)
381             throws ItemNotFound {
382         try (final Tx tx = beginTx()) {
383             UserProfile user = manager.getEntity(userId, UserProfile.class);
384             EventsApi eventsApi = getEventsApi()
385                     .withAggregation(aggregation);
386             Response response = streamingListOfLists(() -> eventsApi.aggregateActions(user));
387             tx.success();
388             return response;
389         }
390     }
391 
392     /**
393      * Aggregate actions that are relevant to a given user based on
394      * the other users that they follow and the items they watch.
395      *
396      * @param userId      the user's ID
397      * @param aggregation the manner in which to aggregate the results, accepting
398      *                    "user", "strict" or "off" (no aggregation). Default is
399      *                    <b>user</b>.
400      * @return a list of event ranges
401      */
402     @GET
403     @Produces(MediaType.APPLICATION_JSON)
404     @Path("{id:[^/]+}/events")
405     public Response aggregateEventsForUser(
406             @PathParam("id") String userId,
407             @QueryParam(AGGREGATION_PARAM) @DefaultValue("user") EventsApi.Aggregation aggregation)
408             throws ItemNotFound {
409         try (final Tx tx = beginTx()) {
410             UserProfile asUser = manager.getEntity(userId, UserProfile.class);
411             EventsApi eventsApi = getEventsApi()
412                     .withAggregation(aggregation);
413             Response response = streamingListOfLists(() -> eventsApi.aggregateAsUser(asUser));
414             tx.success();
415             return response;
416         }
417     }
418 
419     // Helpers
420 
421     private UserProfilesApi userProfilesApi() {
422         // Logging on these events is currently not enabled.
423         return api().enableLogging(false).userProfiles();
424     }
425 }