1 package eu.fbk.knowledgestore.server.http.jaxrs;
2
3 import java.io.InputStream;
4 import java.util.Date;
5
6 import javax.ws.rs.Consumes;
7 import javax.ws.rs.DELETE;
8 import javax.ws.rs.DefaultValue;
9 import javax.ws.rs.GET;
10 import javax.ws.rs.HeaderParam;
11 import javax.ws.rs.POST;
12 import javax.ws.rs.PUT;
13 import javax.ws.rs.Path;
14 import javax.ws.rs.Produces;
15 import javax.ws.rs.QueryParam;
16 import javax.ws.rs.core.HttpHeaders;
17 import javax.ws.rs.core.MediaType;
18 import javax.ws.rs.core.Response;
19 import javax.ws.rs.core.Response.Status;
20
21 import org.codehaus.enunciate.jaxrs.ResponseCode;
22 import org.codehaus.enunciate.jaxrs.ResponseHeader;
23 import org.codehaus.enunciate.jaxrs.ResponseHeaders;
24 import org.codehaus.enunciate.jaxrs.StatusCodes;
25 import org.codehaus.enunciate.jaxrs.TypeHint;
26 import org.glassfish.jersey.media.multipart.BodyPart;
27 import org.glassfish.jersey.media.multipart.ContentDisposition;
28 import org.glassfish.jersey.media.multipart.FormDataBodyPart;
29 import org.glassfish.jersey.media.multipart.FormDataMultiPart;
30 import org.openrdf.model.URI;
31 import org.openrdf.model.Value;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34
35 import eu.fbk.knowledgestore.Operation;
36 import eu.fbk.knowledgestore.Outcome;
37 import eu.fbk.knowledgestore.data.Data;
38 import eu.fbk.knowledgestore.data.Record;
39 import eu.fbk.knowledgestore.data.Representation;
40 import eu.fbk.knowledgestore.data.Stream;
41 import eu.fbk.knowledgestore.internal.jaxrs.Protocol;
42 import eu.fbk.knowledgestore.vocabulary.NFO;
43 import eu.fbk.knowledgestore.vocabulary.NIE;
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68 @Path("/" + Protocol.PATH_REPRESENTATIONS)
69 public class Files extends Resource {
70
71 private static final Logger LOGGER = LoggerFactory.getLogger(Files.class);
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95 @GET
96 @Produces("*/*")
97 @TypeHint(InputStream.class)
98 @StatusCodes({
99 @ResponseCode(code = 200, condition = "if the file is found and its representation "
100 + "is returned"),
101 @ResponseCode(code = 404, condition = "if the requested file does not exist (the "
102 + "associated resource may exist or not)") })
103 @ResponseHeaders({
104 @ResponseHeader(name = "Content-Language", description = "the 2-letters ISO 639 "
105 + "language code for file representation, if known"),
106 @ResponseHeader(name = "Content-Disposition", description = "a content disposition "
107 + "directive for browsers, including the suggested file name and date for "
108 + "saving the file"),
109 @ResponseHeader(name = "Content-MD5", description = "the MD5 hash of the file "
110 + "representation") })
111 public Response get(@QueryParam(Protocol.PARAMETER_ID) final URI id,
112 @HeaderParam(HttpHeaders.ACCEPT) @DefaultValue(MediaType.WILDCARD) final String accept)
113 throws Exception {
114
115
116 checkNotNull(id, Outcome.Status.ERROR_INVALID_INPUT, "Missing 'id' query parameter");
117
118
119 final Representation representation = getSession()
120 .download(id)
121 .timeout(getTimeout())
122 .accept(accept.split(","))
123 .caching(isCachingEnabled())
124 .exec();
125
126
127 checkNotNull(representation, Outcome.Status.ERROR_OBJECT_NOT_FOUND,
128 "Specified file does not exist");
129 closeOnCompletion(representation);
130
131
132 final Record metadata = representation.getMetadata();
133 final Long fileSize = metadata.getUnique(NFO.FILE_SIZE, Long.class, null);
134 final String fileName = metadata.getUnique(NFO.FILE_NAME, String.class, null);
135 final String mimeType = metadata.getUnique(NIE.MIME_TYPE, String.class, null);
136 final Date lastModified = extractLastModified(representation);
137 final String tag = extractMD5(representation);
138 final ContentDisposition disposition = ContentDisposition.type("attachment")
139 .fileName(fileName)
140 .modificationDate(lastModified)
141 .size(fileSize != null ? fileSize : -1)
142 .build();
143
144
145 init(false, mimeType, lastModified, tag);
146
147
148
149 return newResponseBuilder(Status.OK, representation, null).header(
150 HttpHeaders.CONTENT_DISPOSITION, disposition).build();
151 }
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176 @PUT
177 @Consumes(MediaType.WILDCARD)
178 @Produces(Protocol.MIME_TYPES_RDF)
179 @TypeHint(Stream.class)
180 @StatusCodes({ @ResponseCode(code = 200, condition = "if the file has been updated"),
181 @ResponseCode(code = 201, condition = "if the file has been created") })
182 public Response put(@QueryParam(Protocol.PARAMETER_ID) final URI id,
183 final Representation representation) throws Exception {
184
185
186 closeOnCompletion(representation);
187
188
189 checkNotNull(id, Outcome.Status.ERROR_INVALID_INPUT, "Missing 'id' query parameter");
190
191
192 final Operation.Upload operation;
193 try {
194 operation = getSession().upload(id).timeout(getTimeout())
195 .representation(representation);
196 } catch (final RuntimeException ex) {
197 throw newException(Outcome.Status.ERROR_INVALID_INPUT, ex, null);
198 }
199
200
201 Representation oldRepresentation = null;
202 try {
203 oldRepresentation = getSession().download(id).timeout(getTimeout()).exec();
204 closeOnCompletion(oldRepresentation);
205 } catch (final Throwable ex) {
206 LOGGER.error("Error retrieving current files associated to resource " + id, ex);
207 }
208
209
210 if (oldRepresentation == null) {
211
212 init(true, null);
213
214 } else {
215
216 final Date getLastModified = extractLastModified(oldRepresentation);
217 final String getTag = extractMD5(oldRepresentation);
218 oldRepresentation.close();
219 init(true, null, getLastModified, getTag);
220 }
221
222
223 final Outcome outcome = operation.exec();
224
225
226 final int httpStatus = outcome.getStatus().getHTTPStatus();
227 final Stream<Outcome> entity = Stream.create(outcome);
228
229
230 return newResponseBuilder(httpStatus, entity, Protocol.STREAM_OF_OUTCOMES).build();
231 }
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256 @POST
257 @Consumes(MediaType.MULTIPART_FORM_DATA)
258 @Produces(Protocol.MIME_TYPES_RDF)
259 @TypeHint(Stream.class)
260 @StatusCodes({
261 @ResponseCode(code = 200, condition = "if the file has been updated or deleted"),
262 @ResponseCode(code = 201, condition = "if the file has been created") })
263 @ResponseHeaders({ @ResponseHeader(name = "Location", description = "the URI of "
264 + "the created file") })
265 public Response post(final FormDataMultiPart formData) throws Exception {
266
267
268
269 init(true, null);
270
271
272 URI id = null;
273 Representation representation = null;
274 final Record record = Record.create();
275 for (final BodyPart bodyPart : formData.getBodyParts()) {
276
277
278 final FormDataBodyPart part = (FormDataBodyPart) bodyPart;
279 final String name = part.getName();
280 if ("id".equals(name)) {
281
282 id = Data.getValueFactory().createURI(name);
283
284 } else if ("file".equals(name)) {
285
286 representation = closeOnCompletion(part.getEntityAs(Representation.class));
287 final ContentDisposition disposition = checkNotNull(part.getContentDisposition(),
288 Outcome.Status.ERROR_INVALID_INPUT,
289 "Missing Content-Disposition header for body part " + part.getName());
290 final Record metadata = representation.getMetadata();
291 metadata.set(NFO.FILE_NAME, disposition.getFileName());
292 metadata.set(NFO.FILE_LAST_MODIFIED, disposition.getModificationDate());
293
294 } else {
295
296 final URI property = (URI) Data.parseValue(name, Data.getNamespaceMap());
297 final Value value = Data.parseValue(part.getEntityAs(String.class),
298 Data.getNamespaceMap());
299 record.add(property, value);
300 }
301 }
302
303
304 checkNotNull(id, Outcome.Status.ERROR_INVALID_INPUT, "Missing 'id' form parameter");
305 assert id != null;
306
307
308 if (representation != null) {
309
310 final Record metadata = representation.getMetadata();
311 for (final URI property : record.getProperties()) {
312 metadata.set(property, record.get(property));
313 }
314 }
315
316
317 final Outcome outcome = getSession().upload(id).timeout(getTimeout())
318 .representation(representation).exec();
319
320
321 final int httpStatus = outcome.getStatus().getHTTPStatus();
322 final Stream<Outcome> entity = Stream.create(outcome);
323
324
325 return newResponseBuilder(httpStatus, entity, Protocol.STREAM_OF_OUTCOMES).build();
326 }
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341 @DELETE
342 @Produces(Protocol.MIME_TYPES_RDF)
343 @TypeHint(Outcome.class)
344 @StatusCodes({
345 @ResponseCode(code = 200, condition = "if the file has been deleted"),
346 @ResponseCode(code = 404, condition = "if the file does not exist (the associated "
347 + "resource may exist or not)") })
348 public Response delete(@QueryParam(Protocol.PARAMETER_ID) final URI id) throws Exception {
349
350
351 checkNotNull(id, Outcome.Status.ERROR_INVALID_INPUT, "Missing 'id' query parameter");
352
353
354 final Representation oldRepresentation = getSession().download(id).timeout(getTimeout())
355 .exec();
356 closeOnCompletion(oldRepresentation);
357 checkNotNull(oldRepresentation, Outcome.Status.ERROR_OBJECT_NOT_FOUND,
358 "Specified file does not exist.");
359
360
361 final Date getLastModified = extractLastModified(oldRepresentation);
362 final String getTag = extractMD5(oldRepresentation);
363 oldRepresentation.close();
364
365
366 final Operation.Upload operation;
367 try {
368 operation = getSession().upload(id).timeout(getTimeout()).representation(null);
369 } catch (final RuntimeException ex) {
370 throw newException(Outcome.Status.ERROR_INVALID_INPUT, ex, null);
371 }
372
373
374 init(true, null, getLastModified, getTag);
375
376
377 final Outcome outcome = operation.exec();
378
379
380 final int httpStatus = outcome.getStatus().getHTTPStatus();
381 final Stream<Outcome> entity = Stream.create(outcome);
382
383
384 return newResponseBuilder(httpStatus, entity, Protocol.STREAM_OF_OUTCOMES).build();
385 }
386
387 private static Date extractLastModified(final Representation representation) {
388 final Record metadata = representation.getMetadata();
389 return metadata.getUnique(NFO.FILE_LAST_MODIFIED, Date.class, null);
390 }
391
392 private static String extractMD5(final Representation representation) {
393 final Record metadata = representation.getMetadata();
394 final Record hash = metadata.getUnique(NFO.HAS_HASH, Record.class, null);
395 if (hash != null && "MD5".equals(hash.getUnique(NFO.HASH_ALGORITHM, String.class, null))) {
396 return hash.getUnique(NFO.HASH_VALUE, String.class, null);
397 }
398 return null;
399 }
400
401 }