1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package eu.ehri.extension.base;
21
22 import com.google.common.collect.Lists;
23 import com.google.common.collect.Sets;
24 import eu.ehri.project.acl.AclManager;
25 import eu.ehri.project.api.Api;
26 import eu.ehri.project.api.EventsApi;
27 import eu.ehri.project.core.Tx;
28 import eu.ehri.project.definitions.EventTypes;
29 import eu.ehri.project.exceptions.DeserializationError;
30 import eu.ehri.project.exceptions.ItemNotFound;
31 import eu.ehri.project.exceptions.PermissionDenied;
32 import eu.ehri.project.exceptions.SerializationError;
33 import eu.ehri.project.exceptions.ValidationError;
34 import eu.ehri.project.exporters.xml.XmlExporter;
35 import eu.ehri.project.models.EntityClass;
36 import eu.ehri.project.models.base.Accessible;
37 import eu.ehri.project.models.base.Accessor;
38 import eu.ehri.project.models.base.Entity;
39 import eu.ehri.project.persistence.ActionManager;
40 import eu.ehri.project.persistence.Bundle;
41 import eu.ehri.project.persistence.Mutation;
42 import eu.ehri.project.persistence.Serializer;
43 import org.joda.time.DateTime;
44 import org.neo4j.graphdb.GraphDatabaseService;
45
46 import javax.ws.rs.WebApplicationException;
47 import javax.ws.rs.core.Context;
48 import javax.ws.rs.core.Response;
49 import javax.ws.rs.core.StreamingOutput;
50 import javax.xml.transform.TransformerException;
51 import java.io.IOException;
52 import java.util.List;
53 import java.util.Set;
54 import java.util.zip.ZipEntry;
55 import java.util.zip.ZipOutputStream;
56
57
58
59
60
61
62
63
64
65 public class AbstractAccessibleResource<E extends Accessible> extends AbstractResource {
66
67 public final static String ITEM_TYPE_PARAM = "type";
68 public final static String ITEM_ID_PARAM = "item";
69 public final static String EVENT_TYPE_PARAM = "et";
70 public final static String USER_PARAM = "user";
71 public final static String FROM_PARAM = "from";
72 public final static String TO_PARAM = "to";
73 public final static String SHOW_PARAM = "show";
74 public final static String AGGREGATION_PARAM = "aggregation";
75
76 protected final AclManager aclManager;
77 protected final ActionManager actionManager;
78 protected final Class<E> cls;
79
80
81
82
83 public interface Handler<E extends Accessible> {
84 void process(E frame) throws PermissionDenied;
85 }
86
87
88
89
90
91
92 public static class NoOpHandler<E extends Accessible> implements Handler<E> {
93 @Override
94 public void process(E frame) {
95 }
96 }
97
98 private final Handler<E> noOpHandler = new NoOpHandler<>();
99
100
101
102
103
104
105
106 public AbstractAccessibleResource(
107 @Context GraphDatabaseService database, Class<E> cls) {
108 super(database);
109 this.cls = cls;
110 aclManager = new AclManager(graph);
111 actionManager = new ActionManager(graph);
112 }
113
114
115
116
117
118
119 public Response listItems() {
120 try (final Tx tx = beginTx()) {
121 Response response = streamingPage(() -> getQuery().page(cls));
122 tx.success();
123 return response;
124 }
125 }
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144 public <T extends Accessible> Response createItem(
145 Bundle entityBundle,
146 List<String> accessorIds,
147 Handler<T> handler,
148 Api scopedApi,
149 Class<T> otherCls)
150 throws PermissionDenied, ValidationError, DeserializationError {
151 Accessor user = getRequesterUserProfile();
152 T entity = scopedApi.create(entityBundle, otherCls, getLogMessage());
153 if (!accessorIds.isEmpty()) {
154 api().acl().setAccessors(entity, getAccessors(accessorIds, user));
155 }
156
157
158 handler.process(entity);
159 return creationResponse(entity);
160 }
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175 public Response createItem(Bundle entityBundle, List<String> accessorIds, Handler<E> handler)
176 throws PermissionDenied, ValidationError, DeserializationError {
177 return createItem(entityBundle, accessorIds, handler, api(), cls);
178 }
179
180 public Response createItem(Bundle entityBundle, List<String> accessorIds)
181 throws PermissionDenied, ValidationError, DeserializationError {
182 return createItem(entityBundle, accessorIds, noOpHandler);
183 }
184
185
186
187
188
189
190
191
192 public Response getItem(String id) throws ItemNotFound {
193 logger.debug("Fetched item: {}", id);
194 try (final Tx tx = beginTx()) {
195 E entity = api().detail(id, cls);
196 if (!manager.getEntityClass(entity).getJavaClass().equals(cls)) {
197 throw new ItemNotFound(id);
198 }
199 Response response = single(entity);
200 tx.success();
201 return response;
202 }
203 }
204
205
206
207
208
209
210
211 public Response updateItem(Bundle entityBundle)
212 throws PermissionDenied, ValidationError, ItemNotFound, DeserializationError {
213 Mutation<E> update = api().update(entityBundle, cls, getLogMessage());
214 return single(update.getNode());
215 }
216
217
218
219
220
221
222
223
224
225
226
227 public Response updateItem(String id, Bundle rawBundle)
228 throws PermissionDenied, ValidationError,
229 DeserializationError, ItemNotFound {
230 try {
231 E entity = api().detail(id, cls);
232 if (isPatch()) {
233 Serializer depSerializer = new Serializer.Builder(graph).dependentOnly().build();
234 Bundle existing = depSerializer.entityToBundle(entity);
235 return updateItem(existing.mergeDataWith(rawBundle));
236 } else {
237 return updateItem(rawBundle.withId(entity.getId()));
238 }
239 } catch (SerializationError serializationError) {
240 throw new RuntimeException(serializationError);
241 }
242 }
243
244
245
246
247
248
249
250
251 protected void deleteItem(String id, Handler<E> preProcess)
252 throws PermissionDenied, ItemNotFound, ValidationError {
253 try {
254 Api api = api();
255 preProcess.process(api.detail(id, cls));
256 api.delete(id, getLogMessage());
257 } catch (SerializationError serializationError) {
258 throw new RuntimeException(serializationError);
259 }
260 }
261
262
263
264
265
266
267 protected void deleteItem(String id)
268 throws PermissionDenied, ItemNotFound, ValidationError {
269 deleteItem(id, noOpHandler);
270 }
271
272
273
274 protected <T extends Entity> Response exportItemsAsZip(XmlExporter<T> exporter, Iterable<T> items, String lang)
275 throws IOException {
276 return Response.ok((StreamingOutput) outputStream -> {
277 try (final Tx tx = beginTx();
278 ZipOutputStream zos = new ZipOutputStream(outputStream)) {
279 for (T item : items) {
280 ZipEntry zipEntry = new ZipEntry(item.getId() + ".xml");
281 zipEntry.setComment("Exported from the EHRI portal at " + (DateTime.now()));
282 zos.putNextEntry(zipEntry);
283 exporter.export(item, zos, lang);
284 zos.closeEntry();
285 }
286 tx.success();
287 } catch (TransformerException e) {
288 throw new WebApplicationException(e);
289 }
290 }).type("application/zip").build();
291 }
292
293
294
295
296
297
298 protected EventsApi getEventsApi() {
299 List<EventTypes> eventTypes = Lists.transform(getStringListQueryParam(EVENT_TYPE_PARAM),
300 EventTypes::valueOf);
301 List<EntityClass> entityClasses = Lists.transform(getStringListQueryParam(ITEM_TYPE_PARAM),
302 EntityClass::withName);
303 List<EventsApi.ShowType> showTypes = Lists.transform(getStringListQueryParam(SHOW_PARAM),
304 EventsApi.ShowType::valueOf);
305 List<String> fromStrings = getStringListQueryParam(FROM_PARAM);
306 List<String> toStrings = getStringListQueryParam(TO_PARAM);
307 List<String> users = getStringListQueryParam(USER_PARAM);
308 List<String> ids = getStringListQueryParam(ITEM_ID_PARAM);
309 return api()
310 .events()
311 .withRange(getIntQueryParam(OFFSET_PARAM, 0),
312 getIntQueryParam(LIMIT_PARAM, DEFAULT_LIST_LIMIT))
313 .withEventTypes(eventTypes.toArray(new EventTypes[eventTypes.size()]))
314 .withEntityClasses(entityClasses.toArray(new EntityClass[entityClasses.size()]))
315 .from(fromStrings.isEmpty() ? null : fromStrings.get(0))
316 .to(toStrings.isEmpty() ? null : toStrings.get(0))
317 .withUsers(users.toArray(new String[users.size()]))
318 .withIds(ids.toArray(new String[ids.size()]))
319 .withShowType(showTypes.toArray(new EventsApi.ShowType[showTypes.size()]));
320 }
321
322
323
324
325
326
327
328
329 protected Set<Accessor> getAccessors(List<String> accessorIds, Accessor current) {
330
331 Set<Accessor> accessors = Sets.newHashSet();
332 for (String id : accessorIds) {
333 try {
334 Accessor av = manager.getEntity(id, Accessor.class);
335 accessors.add(av);
336 } catch (ItemNotFound e) {
337 logger.warn("Invalid accessor given: {}", id);
338 }
339 }
340
341
342 if (!accessors.isEmpty()) {
343 accessors.add(current);
344 }
345 return accessors;
346 }
347 }