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.Lists;
23  import com.tinkerpop.blueprints.Vertex;
24  import com.tinkerpop.gremlin.java.GremlinPipeline;
25  import com.tinkerpop.pipes.PipeFunction;
26  import eu.ehri.extension.base.AbstractAccessibleResource;
27  import eu.ehri.extension.base.AbstractResource;
28  import eu.ehri.extension.base.DeleteResource;
29  import eu.ehri.extension.base.GetResource;
30  import eu.ehri.extension.base.ListResource;
31  import eu.ehri.extension.base.UpdateResource;
32  import eu.ehri.project.acl.AclManager;
33  import eu.ehri.project.core.Tx;
34  import eu.ehri.project.definitions.Entities;
35  import eu.ehri.project.exceptions.AccessDenied;
36  import eu.ehri.project.exceptions.DeserializationError;
37  import eu.ehri.project.exceptions.ItemNotFound;
38  import eu.ehri.project.exceptions.PermissionDenied;
39  import eu.ehri.project.exceptions.ValidationError;
40  import eu.ehri.project.models.DocumentaryUnit;
41  import eu.ehri.project.models.EntityClass;
42  import eu.ehri.project.models.UserProfile;
43  import eu.ehri.project.models.VirtualUnit;
44  import eu.ehri.project.models.base.Accessor;
45  import eu.ehri.project.persistence.Bundle;
46  import org.neo4j.graphdb.GraphDatabaseService;
47  
48  import javax.ws.rs.Consumes;
49  import javax.ws.rs.DELETE;
50  import javax.ws.rs.DefaultValue;
51  import javax.ws.rs.GET;
52  import javax.ws.rs.POST;
53  import javax.ws.rs.PUT;
54  import javax.ws.rs.Path;
55  import javax.ws.rs.PathParam;
56  import javax.ws.rs.Produces;
57  import javax.ws.rs.QueryParam;
58  import javax.ws.rs.core.Context;
59  import javax.ws.rs.core.MediaType;
60  import javax.ws.rs.core.Response;
61  import java.util.List;
62  
63  /**
64   * Provides a web service interface for the VirtualUnit type
65   */
66  @Path(AbstractResource.RESOURCE_ENDPOINT_PREFIX + "/" + Entities.VIRTUAL_UNIT)
67  public final class VirtualUnitResource extends
68          AbstractAccessibleResource<VirtualUnit>
69          implements GetResource, ListResource, UpdateResource, DeleteResource {
70  
71      public static final String INCLUDED = "includes";
72  
73      public VirtualUnitResource(@Context GraphDatabaseService database) {
74          super(database, VirtualUnit.class);
75      }
76  
77      @GET
78      @Produces(MediaType.APPLICATION_JSON)
79      @Path("{id:[^/]+}")
80      @Override
81      public Response get(@PathParam("id") String id) throws ItemNotFound {
82          return getItem(id);
83      }
84  
85      @GET
86      @Produces(MediaType.APPLICATION_JSON)
87      @Override
88      public Response list() {
89          return listItems();
90      }
91  
92      @GET
93      @Produces(MediaType.APPLICATION_JSON)
94      @Path("{id:[^/]+}/list")
95      public Response listChildVirtualUnits(
96              @PathParam("id") String id,
97              @QueryParam(ALL_PARAM) @DefaultValue("false") boolean all) throws ItemNotFound {
98          try (final Tx tx = beginTx()) {
99              VirtualUnit parent = manager.getEntity(id, VirtualUnit.class);
100             Response response = streamingPage(() -> {
101                 Iterable<VirtualUnit> units = all
102                         ? parent.getAllChildren()
103                         : parent.getChildren();
104                 return getQuery().page(units, cls);
105             });
106             tx.success();
107             return response;
108         }
109     }
110 
111     @GET
112     @Produces(MediaType.APPLICATION_JSON)
113     @Path("{id:[^/]+}/includes")
114     public Response listIncludedVirtualUnits(
115             @PathParam("id") String id) throws ItemNotFound {
116         try (final Tx tx = beginTx()) {
117             VirtualUnit parent = manager.getEntity(id, VirtualUnit.class);
118             Response response = streamingPage(() -> getQuery()
119                     .page(parent.getIncludedUnits(), DocumentaryUnit.class));
120             tx.success();
121             return response;
122         }
123     }
124 
125     @POST
126     @Path("{id:[^/]+}/includes")
127     public Response addIncludedVirtualUnits(
128             @PathParam("id") String id, @QueryParam(ID_PARAM) List<String> includedIds)
129             throws ItemNotFound, PermissionDenied {
130         try (final Tx tx = beginTx()) {
131             UserProfile currentUser = getCurrentUser();
132             VirtualUnit parent = api().detail(id, cls);
133             Response item = single(api().virtualUnits().addIncludedUnits(parent,
134                     getIncludedUnits(includedIds, currentUser)));
135             tx.success();
136             return item;
137         }
138     }
139 
140     @DELETE
141     @Path("{id:[^/]+}/includes")
142     public Response removeIncludedVirtualUnits(
143             @PathParam("id") String id, @QueryParam(ID_PARAM) List<String> includedIds)
144             throws ItemNotFound, PermissionDenied {
145         try (final Tx tx = beginTx()) {
146             UserProfile currentUser = getCurrentUser();
147             VirtualUnit parent = api().detail(id, cls);
148             Response item = single(api().virtualUnits().removeIncludedUnits(parent,
149                     getIncludedUnits(includedIds, currentUser)));
150             tx.success();
151             return item;
152         }
153     }
154 
155     @POST
156     @Path("{from:[^/]+}/includes/{to:[^/]+}")
157     public void moveIncludedVirtualUnits(
158             @PathParam("from") String fromId, @PathParam("to") String toId,
159             @QueryParam(ID_PARAM) List<String> includedIds)
160             throws ItemNotFound, PermissionDenied {
161         try (final Tx tx = beginTx()) {
162             UserProfile currentUser = getCurrentUser();
163             VirtualUnit fromVu = api().detail(fromId, cls);
164             VirtualUnit toVu = api().detail(toId, cls);
165             Iterable<DocumentaryUnit> units = getIncludedUnits(includedIds, currentUser);
166             api().virtualUnits().moveIncludedUnits(fromVu, toVu, units);
167             tx.success();
168         }
169     }
170 
171     @POST
172     @Consumes(MediaType.APPLICATION_JSON)
173     @Produces(MediaType.APPLICATION_JSON)
174     public Response createTopLevelVirtualUnit(Bundle bundle,
175             @QueryParam(ACCESSOR_PARAM) List<String> accessors,
176             @QueryParam(ID_PARAM) List<String> includedIds)
177             throws PermissionDenied, ValidationError,
178             DeserializationError, ItemNotFound {
179         try (final Tx tx = beginTx()) {
180             final Accessor currentUser = getCurrentUser();
181             final Iterable<DocumentaryUnit> includedUnits
182                     = getIncludedUnits(includedIds, currentUser);
183 
184             Response item = createItem(bundle, accessors, virtualUnit -> {
185                 virtualUnit.setAuthor(currentUser);
186                 for (DocumentaryUnit include : includedUnits) {
187                     virtualUnit.addIncludedUnit(include);
188                 }
189             });
190             tx.success();
191             return item;
192         }
193     }
194 
195     @PUT
196     @Consumes(MediaType.APPLICATION_JSON)
197     @Produces(MediaType.APPLICATION_JSON)
198     @Path("{id:[^/]+}")
199     @Override
200     public Response update(@PathParam("id") String id, Bundle bundle)
201             throws PermissionDenied, ValidationError,
202             DeserializationError, ItemNotFound {
203         try (final Tx tx = beginTx()) {
204             Response item = updateItem(id, bundle);
205             tx.success();
206             return item;
207         }
208     }
209 
210     @DELETE
211     @Path("{id:[^/]+}")
212     @Override
213     public void delete(@PathParam("id") String id)
214             throws PermissionDenied, ItemNotFound, ValidationError {
215         try (final Tx tx = beginTx()) {
216             deleteItem(id);
217             tx.success();
218         }
219     }
220 
221     @POST
222     @Consumes(MediaType.APPLICATION_JSON)
223     @Produces(MediaType.APPLICATION_JSON)
224     @Path("{id:[^/]+}")
225     public Response createChildVirtualUnit(@PathParam("id") String id,
226             Bundle bundle, @QueryParam(ACCESSOR_PARAM) List<String> accessors,
227             @QueryParam(ID_PARAM) List<String> includedIds)
228             throws AccessDenied, PermissionDenied, ValidationError,
229             DeserializationError, ItemNotFound {
230         try (final Tx tx = beginTx()) {
231             Accessor currentUser = getRequesterUserProfile();
232             final Iterable<DocumentaryUnit> includedUnits
233                     = getIncludedUnits(includedIds, currentUser);
234             final VirtualUnit parent = api().detail(id, cls);
235 
236             // NB: Unlike most other items created in another context, virtual
237             // units do not inherit the permission scope of their 'parent',
238             // because they make have many parents.
239             Response item = createItem(bundle, accessors, virtualUnit -> {
240                 parent.addChild(virtualUnit);
241                 for (DocumentaryUnit included : includedUnits) {
242                     virtualUnit.addIncludedUnit(included);
243                 }
244             });
245             tx.success();
246             return item;
247         }
248     }
249 
250     /**
251      * Fetch a set of document descriptions from a list of description IDs.
252      * We filter these for accessibility and content type (to ensure
253      * they actually are the right type.
254      */
255     private List<DocumentaryUnit> getIncludedUnits(
256             List<String> ids, Accessor accessor) throws ItemNotFound {
257         Iterable<Vertex> vertices = manager.getVertices(ids);
258 
259         PipeFunction<Vertex, Boolean> aclFilter = AclManager.getAclFilterFunction(accessor);
260 
261         PipeFunction<Vertex, Boolean> typeFilter = vertex -> {
262             EntityClass entityClass = manager.getEntityClass(vertex);
263             return EntityClass.DOCUMENTARY_UNIT.equals(entityClass);
264         };
265 
266         GremlinPipeline<Vertex, Vertex> units = new GremlinPipeline<Vertex, Vertex>(
267                 vertices).filter(typeFilter).filter(aclFilter);
268         return Lists.newArrayList(graph.frameVertices(units, DocumentaryUnit.class));
269     }
270 }