1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package org.opencastproject.ingest.endpoint;
23
24 import static org.apache.commons.lang3.StringUtils.trimToNull;
25 import static org.opencastproject.mediapackage.MediaPackageElements.XACML_POLICY_EPISODE;
26
27 import org.opencastproject.authorization.xacml.XACMLUtils;
28 import org.opencastproject.capture.CaptureParameters;
29 import org.opencastproject.ingest.api.IngestException;
30 import org.opencastproject.ingest.api.IngestService;
31 import org.opencastproject.ingest.impl.IngestServiceImpl;
32 import org.opencastproject.job.api.JobProducer;
33 import org.opencastproject.mediapackage.EName;
34 import org.opencastproject.mediapackage.MediaPackage;
35 import org.opencastproject.mediapackage.MediaPackageBuilderFactory;
36 import org.opencastproject.mediapackage.MediaPackageElement;
37 import org.opencastproject.mediapackage.MediaPackageElementFlavor;
38 import org.opencastproject.mediapackage.MediaPackageElements;
39 import org.opencastproject.mediapackage.MediaPackageException;
40 import org.opencastproject.mediapackage.MediaPackageParser;
41 import org.opencastproject.mediapackage.MediaPackageSupport;
42 import org.opencastproject.mediapackage.identifier.IdImpl;
43 import org.opencastproject.metadata.dublincore.DublinCore;
44 import org.opencastproject.metadata.dublincore.DublinCoreCatalog;
45 import org.opencastproject.metadata.dublincore.DublinCoreCatalogService;
46 import org.opencastproject.metadata.dublincore.DublinCoreXmlFormat;
47 import org.opencastproject.metadata.dublincore.DublinCores;
48 import org.opencastproject.rest.AbstractJobProducerEndpoint;
49 import org.opencastproject.scheduler.api.SchedulerConflictException;
50 import org.opencastproject.scheduler.api.SchedulerException;
51 import org.opencastproject.security.api.AccessControlList;
52 import org.opencastproject.security.api.AccessControlParser;
53 import org.opencastproject.security.api.AccessControlParsingException;
54 import org.opencastproject.security.api.TrustedHttpClient;
55 import org.opencastproject.security.api.UnauthorizedException;
56 import org.opencastproject.serviceregistry.api.ServiceRegistry;
57 import org.opencastproject.util.NotFoundException;
58 import org.opencastproject.util.data.Function0.X;
59 import org.opencastproject.util.doc.rest.RestParameter;
60 import org.opencastproject.util.doc.rest.RestQuery;
61 import org.opencastproject.util.doc.rest.RestResponse;
62 import org.opencastproject.util.doc.rest.RestService;
63 import org.opencastproject.workflow.api.JaxbWorkflowInstance;
64 import org.opencastproject.workflow.api.WorkflowInstance;
65 import org.opencastproject.workflow.api.XmlWorkflowParser;
66
67 import com.google.common.cache.Cache;
68 import com.google.common.cache.CacheBuilder;
69
70 import org.apache.commons.fileupload.FileItemIterator;
71 import org.apache.commons.fileupload.FileItemStream;
72 import org.apache.commons.fileupload.FileUploadException;
73 import org.apache.commons.fileupload.servlet.ServletFileUpload;
74 import org.apache.commons.fileupload.util.Streams;
75 import org.apache.commons.io.IOUtils;
76 import org.apache.commons.lang3.StringUtils;
77 import org.apache.commons.lang3.exception.ExceptionUtils;
78 import org.apache.http.HttpResponse;
79 import org.apache.http.client.methods.HttpGet;
80 import org.osgi.service.component.ComponentContext;
81 import org.osgi.service.component.annotations.Activate;
82 import org.osgi.service.component.annotations.Component;
83 import org.osgi.service.component.annotations.Reference;
84 import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource;
85 import org.slf4j.Logger;
86 import org.slf4j.LoggerFactory;
87
88 import java.io.ByteArrayInputStream;
89 import java.io.ByteArrayOutputStream;
90 import java.io.IOException;
91 import java.io.InputStream;
92 import java.net.URI;
93 import java.nio.charset.StandardCharsets;
94 import java.text.DateFormat;
95 import java.text.SimpleDateFormat;
96 import java.util.ArrayList;
97 import java.util.Arrays;
98 import java.util.Date;
99 import java.util.HashMap;
100 import java.util.List;
101 import java.util.Map;
102 import java.util.concurrent.TimeUnit;
103
104 import javax.servlet.http.HttpServletRequest;
105 import javax.servlet.http.HttpServletResponse;
106 import javax.ws.rs.Consumes;
107 import javax.ws.rs.FormParam;
108 import javax.ws.rs.GET;
109 import javax.ws.rs.POST;
110 import javax.ws.rs.PUT;
111 import javax.ws.rs.Path;
112 import javax.ws.rs.PathParam;
113 import javax.ws.rs.Produces;
114 import javax.ws.rs.QueryParam;
115 import javax.ws.rs.core.Context;
116 import javax.ws.rs.core.MediaType;
117 import javax.ws.rs.core.MultivaluedHashMap;
118 import javax.ws.rs.core.MultivaluedMap;
119 import javax.ws.rs.core.Response;
120 import javax.ws.rs.core.Response.Status;
121
122
123
124
125 @Path("/ingest")
126 @RestService(name = "ingestservice", title = "Ingest Service", abstractText = "This service creates and augments Opencast media packages that include media tracks, metadata "
127 + "catalogs and attachments.", notes = {
128 "All paths above are relative to the REST endpoint base (something like http://your.server/files)",
129 "If the service is down or not working it will return a status 503, this means the the underlying service is "
130 + "not working and is either restarting or has failed",
131 "A status code 500 means a general failure has occurred which is not recoverable and was not anticipated. In "
132 + "other words, there is a bug! You should file an error report with your server logs from the time when the "
133 + "error occurred: <a href=\"https://github.com/opencast/opencast/issues\">Opencast Issue Tracker</a>" })
134 @Component(
135 immediate = true,
136 service = IngestRestService.class,
137 property = {
138 "service.description=Ingest REST Endpoint",
139 "opencast.service.type=org.opencastproject.ingest",
140 "opencast.service.path=/ingest",
141 "opencast.service.jobproducer=true"
142 }
143 )
144 @JaxrsResource
145 public class IngestRestService extends AbstractJobProducerEndpoint {
146
147 private static final Logger logger = LoggerFactory.getLogger(IngestRestService.class);
148
149
150 protected static final String DEFAULT_WORKFLOW_DEFINITION = "org.opencastproject.workflow.default.definition";
151
152
153 protected static final String MAX_INGESTS_KEY = "org.opencastproject.ingest.max.concurrent";
154
155
156 protected static final String WORKFLOW_INSTANCE_ID_PARAM = "workflowInstanceId";
157
158
159 protected static final String WORKFLOW_DEFINITION_ID_PARAM = "workflowDefinitionId";
160
161
162 private String defaultWorkflowDefinitionId = null;
163
164
165 private TrustedHttpClient httpClient;
166
167
168
169
170
171
172
173
174
175
176
177
178 private static final DateFormat DATE_FORMAT = new SimpleDateFormat(IngestService.UTC_DATE_FORMAT);
179
180
181 private static final MediaPackageBuilderFactory MP_FACTORY = MediaPackageBuilderFactory.newInstance();
182
183 private IngestService ingestService = null;
184 private ServiceRegistry serviceRegistry = null;
185 private DublinCoreCatalogService dublinCoreService;
186
187 private int ingestLimit = -1;
188
189 private final Cache<String, Date> startCache = CacheBuilder.newBuilder().expireAfterAccess(1, TimeUnit.DAYS).build();
190
191
192
193
194
195
196
197 protected synchronized int getIngestLimit() {
198 return ingestLimit;
199 }
200
201
202
203
204
205
206
207 private synchronized void setIngestLimit(int ingestLimit) {
208 this.ingestLimit = ingestLimit;
209 }
210
211
212
213
214
215
216 protected synchronized boolean isIngestLimitEnabled() {
217 return ingestLimit >= 0;
218 }
219
220
221
222
223 @Activate
224 public void activate(ComponentContext cc) {
225 if (cc != null) {
226 defaultWorkflowDefinitionId = trimToNull(cc.getBundleContext().getProperty(DEFAULT_WORKFLOW_DEFINITION));
227 if (defaultWorkflowDefinitionId == null) {
228 defaultWorkflowDefinitionId = "schedule-and-upload";
229 }
230 if (cc.getBundleContext().getProperty(MAX_INGESTS_KEY) != null) {
231 try {
232 ingestLimit = Integer.parseInt(trimToNull(cc.getBundleContext().getProperty(MAX_INGESTS_KEY)));
233 if (ingestLimit == 0) {
234 ingestLimit = -1;
235 }
236 } catch (NumberFormatException e) {
237 logger.warn("Max ingest property with key " + MAX_INGESTS_KEY
238 + " isn't defined so no ingest limit will be used.");
239 ingestLimit = -1;
240 }
241 }
242 }
243 }
244
245 @PUT
246 @Produces(MediaType.TEXT_XML)
247 @Path("createMediaPackageWithID/{id}")
248 @RestQuery(name = "createMediaPackageWithID", description = "Create an empty media package with ID /n Overrides Existing Mediapackage ", pathParameters = {
249 @RestParameter(description = "The Id for the new Mediapackage", isRequired = true, name = "id", type = RestParameter.Type.STRING) }, responses = {
250 @RestResponse(description = "Returns media package", responseCode = HttpServletResponse.SC_OK),
251 @RestResponse(description = "", responseCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR) }, returnDescription = "")
252 public Response createMediaPackage(@PathParam("id") String mediaPackageId) {
253 MediaPackage mp;
254 try {
255 mp = ingestService.createMediaPackage(mediaPackageId);
256
257 startCache.put(mp.getIdentifier().toString(), new Date());
258 return Response.ok(mp).build();
259 } catch (Exception e) {
260 logger.warn("Unable to create mediapackage", e);
261 return Response.serverError().status(Status.INTERNAL_SERVER_ERROR).build();
262 }
263 }
264
265 @GET
266 @Produces(MediaType.TEXT_XML)
267 @Path("createMediaPackage")
268 @RestQuery(name = "createMediaPackage", description = "Create an empty media package", restParameters = {
269 }, responses = {
270 @RestResponse(description = "Returns media package", responseCode = HttpServletResponse.SC_OK),
271 @RestResponse(description = "", responseCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR) }, returnDescription = "")
272 public Response createMediaPackage() {
273 MediaPackage mp;
274 try {
275 mp = ingestService.createMediaPackage();
276 startCache.put(mp.getIdentifier().toString(), new Date());
277 return Response.ok(mp).build();
278 } catch (Exception e) {
279 logger.warn("Unable to create empty mediapackage", e);
280 return Response.serverError().status(Status.INTERNAL_SERVER_ERROR).build();
281 }
282 }
283
284 @POST
285 @Path("discardMediaPackage")
286 @RestQuery(name = "discardMediaPackage", description = "Discard a media package", restParameters = { @RestParameter(description = "Given media package to be destroyed", isRequired = true, name = "mediaPackage", type = RestParameter.Type.TEXT) }, responses = {
287 @RestResponse(description = "", responseCode = HttpServletResponse.SC_OK),
288 @RestResponse(description = "", responseCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR) }, returnDescription = "")
289 public Response discardMediaPackage(@FormParam("mediaPackage") String mpx) {
290 logger.debug("discardMediaPackage(MediaPackage): {}", mpx);
291 try {
292 MediaPackage mp = MP_FACTORY.newMediaPackageBuilder().loadFromXml(mpx);
293 ingestService.discardMediaPackage(mp);
294 return Response.ok().build();
295 } catch (Exception e) {
296 logger.warn("Unable to discard mediapackage {}", mpx, e);
297 return Response.serverError().status(Status.INTERNAL_SERVER_ERROR).build();
298 }
299 }
300
301 @POST
302 @Produces(MediaType.TEXT_XML)
303 @Path("addTrack")
304 @RestQuery(name = "addTrackURL", description = "Add a media track to a given media package using an URL", restParameters = {
305 @RestParameter(description = "The location of the media", isRequired = true, name = "url", type = RestParameter.Type.STRING),
306 @RestParameter(description = "The kind of media", isRequired = true, name = "flavor", type = RestParameter.Type.STRING),
307 @RestParameter(description = "The tags of the media track", isRequired = false, name = "tags", type = RestParameter.Type.STRING),
308 @RestParameter(description = "The media package as XML", isRequired = true, name = "mediaPackage", type = RestParameter.Type.TEXT) }, responses = {
309 @RestResponse(description = "Returns augmented media package", responseCode = HttpServletResponse.SC_OK),
310 @RestResponse(description = "Media package not valid", responseCode = HttpServletResponse.SC_BAD_REQUEST),
311 @RestResponse(description = "", responseCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR) }, returnDescription = "")
312 public Response addMediaPackageTrack(@FormParam("url") String url, @FormParam("flavor") String flavor, @FormParam("tags") String tags,
313 @FormParam("mediaPackage") String mpx) {
314 logger.trace("add media package from url: {} flavor: {} tags: {} mediaPackage: {}", url, flavor, tags, mpx);
315 try {
316 MediaPackage mp = MP_FACTORY.newMediaPackageBuilder().loadFromXml(mpx);
317 if (MediaPackageSupport.sanityCheck(mp).isSome())
318 return Response.serverError().status(Status.BAD_REQUEST).build();
319 String[] tagsArray = null;
320 if (tags != null) {
321 tagsArray = tags.split(",");
322 }
323 mp = ingestService.addTrack(new URI(url), MediaPackageElementFlavor.parseFlavor(flavor), tagsArray, mp);
324 return Response.ok(mp).build();
325 } catch (Exception e) {
326 logger.warn("Unable to add mediapackage track", e);
327 return Response.serverError().status(Status.INTERNAL_SERVER_ERROR).build();
328 }
329 }
330
331 @POST
332 @Produces(MediaType.TEXT_XML)
333 @Consumes(MediaType.MULTIPART_FORM_DATA)
334 @Path("addTrack")
335 @RestQuery(
336 name = "addTrackInputStream",
337 description = "Add a media track to a given media package using an input stream",
338 restParameters = {
339 @RestParameter(description = "The kind of media track", isRequired = true, name = "flavor", type = RestParameter.Type.STRING),
340 @RestParameter(description = "The tags of the media track", isRequired = false, name = "tags", type = RestParameter.Type.STRING),
341 @RestParameter(description = "The media package as XML", isRequired = true, name = "mediaPackage", type = RestParameter.Type.TEXT) },
342 bodyParameter = @RestParameter(description = "The media track file", isRequired = true, name = "BODY", type = RestParameter.Type.FILE),
343 responses = {
344 @RestResponse(description = "Returns augmented media package", responseCode = HttpServletResponse.SC_OK),
345 @RestResponse(description = "Media package not valid", responseCode = HttpServletResponse.SC_BAD_REQUEST),
346 @RestResponse(description = "", responseCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR) },
347 returnDescription = "")
348 public Response addMediaPackageTrack(@Context HttpServletRequest request) {
349 logger.trace("add track as multipart-form-data");
350 return addMediaPackageElement(request, MediaPackageElement.Type.Track);
351 }
352
353 @POST
354 @Produces(MediaType.TEXT_XML)
355 @Path("addPartialTrack")
356 @RestQuery(name = "addPartialTrackURL", description = "Add a partial media track to a given media package using an URL", restParameters = {
357 @RestParameter(description = "The location of the media", isRequired = true, name = "url", type = RestParameter.Type.STRING),
358 @RestParameter(description = "The kind of media", isRequired = true, name = "flavor", type = RestParameter.Type.STRING),
359 @RestParameter(description = "The start time in milliseconds", isRequired = true, name = "startTime", type = RestParameter.Type.INTEGER),
360 @RestParameter(description = "The media package as XML", isRequired = true, name = "mediaPackage", type = RestParameter.Type.TEXT) }, responses = {
361 @RestResponse(description = "Returns augmented media package", responseCode = HttpServletResponse.SC_OK),
362 @RestResponse(description = "Media package not valid", responseCode = HttpServletResponse.SC_BAD_REQUEST),
363 @RestResponse(description = "", responseCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR) }, returnDescription = "")
364 public Response addMediaPackagePartialTrack(@FormParam("url") String url, @FormParam("flavor") String flavor,
365 @FormParam("startTime") Long startTime, @FormParam("mediaPackage") String mpx) {
366 logger.trace("add partial track with url: {} flavor: {} startTime: {} mediaPackage: {}",
367 url, flavor, startTime, mpx);
368 try {
369 MediaPackage mp = MP_FACTORY.newMediaPackageBuilder().loadFromXml(mpx);
370 if (MediaPackageSupport.sanityCheck(mp).isSome())
371 return Response.serverError().status(Status.BAD_REQUEST).build();
372
373 mp = ingestService.addPartialTrack(new URI(url), MediaPackageElementFlavor.parseFlavor(flavor), startTime, mp);
374 return Response.ok(mp).build();
375 } catch (Exception e) {
376 logger.warn("Unable to add partial track", e);
377 return Response.serverError().status(Status.INTERNAL_SERVER_ERROR).build();
378 }
379 }
380
381 @POST
382 @Produces(MediaType.TEXT_XML)
383 @Consumes(MediaType.MULTIPART_FORM_DATA)
384 @Path("addPartialTrack")
385 @RestQuery(name = "addPartialTrackInputStream", description = "Add a partial media track to a given media package using an input stream", restParameters = {
386 @RestParameter(description = "The kind of media track", isRequired = true, name = "flavor", type = RestParameter.Type.STRING),
387 @RestParameter(description = "The start time in milliseconds", isRequired = true, name = "startTime", type = RestParameter.Type.INTEGER),
388 @RestParameter(description = "The media package as XML", isRequired = true, name = "mediaPackage", type = RestParameter.Type.TEXT) }, bodyParameter = @RestParameter(description = "The media track file", isRequired = true, name = "BODY", type = RestParameter.Type.FILE), responses = {
389 @RestResponse(description = "Returns augmented media package", responseCode = HttpServletResponse.SC_OK),
390 @RestResponse(description = "Media package not valid", responseCode = HttpServletResponse.SC_BAD_REQUEST),
391 @RestResponse(description = "", responseCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR) }, returnDescription = "")
392 public Response addMediaPackagePartialTrack(@Context HttpServletRequest request) {
393 logger.trace("add partial track as multipart-form-data");
394 return addMediaPackageElement(request, MediaPackageElement.Type.Track);
395 }
396
397 @POST
398 @Produces(MediaType.TEXT_XML)
399 @Path("addCatalog")
400 @RestQuery(name = "addCatalogURL", description = "Add a metadata catalog to a given media package using an URL", restParameters = {
401 @RestParameter(description = "The location of the catalog", isRequired = true, name = "url", type = RestParameter.Type.STRING),
402 @RestParameter(description = "The kind of catalog", isRequired = true, name = "flavor", type = RestParameter.Type.STRING),
403 @RestParameter(description = "The tags of the catalog", isRequired = false, name = "tags", type = RestParameter.Type.STRING),
404 @RestParameter(description = "The media package as XML", isRequired = true, name = "mediaPackage", type = RestParameter.Type.TEXT) }, responses = {
405 @RestResponse(description = "Returns augmented media package", responseCode = HttpServletResponse.SC_OK),
406 @RestResponse(description = "Media package not valid", responseCode = HttpServletResponse.SC_BAD_REQUEST),
407 @RestResponse(description = "", responseCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR) }, returnDescription = "")
408 public Response addMediaPackageCatalog(@FormParam("url") String url, @FormParam("flavor") String flavor,
409 @FormParam("tags") String tags, @FormParam("mediaPackage") String mpx) {
410 logger.trace("add catalog with url: {} flavor: {} tags: {} mediaPackage: {}", url, flavor, tags, mpx);
411 try {
412 MediaPackage mp = MP_FACTORY.newMediaPackageBuilder().loadFromXml(mpx);
413 if (MediaPackageSupport.sanityCheck(mp).isSome())
414 return Response.serverError().status(Status.BAD_REQUEST).build();
415 String[] tagsArray = null;
416 if (tags != null) {
417 tagsArray = tags.split(",");
418 }
419 MediaPackage resultingMediaPackage = ingestService.addCatalog(new URI(url),
420 MediaPackageElementFlavor.parseFlavor(flavor), tagsArray, mp);
421 return Response.ok(resultingMediaPackage).build();
422 } catch (Exception e) {
423 logger.warn("Unable to add catalog", e);
424 return Response.serverError().status(Status.INTERNAL_SERVER_ERROR).build();
425 }
426 }
427
428 @POST
429 @Produces(MediaType.TEXT_XML)
430 @Consumes(MediaType.MULTIPART_FORM_DATA)
431 @Path("addCatalog")
432 @RestQuery(name = "addCatalogInputStream", description = "Add a metadata catalog to a given media package using an input stream", restParameters = {
433 @RestParameter(description = "The kind of media catalog", isRequired = true, name = "flavor", type = RestParameter.Type.STRING),
434 @RestParameter(description = "The tags of the attachment", isRequired = false, name = "tags", type = RestParameter.Type.STRING),
435 @RestParameter(description = "The media package as XML", isRequired = true, name = "mediaPackage", type = RestParameter.Type.TEXT) }, bodyParameter = @RestParameter(description = "The metadata catalog file", isRequired = true, name = "BODY", type = RestParameter.Type.FILE), responses = {
436 @RestResponse(description = "Returns augmented media package", responseCode = HttpServletResponse.SC_OK),
437 @RestResponse(description = "Media package not valid", responseCode = HttpServletResponse.SC_BAD_REQUEST),
438 @RestResponse(description = "", responseCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR) }, returnDescription = "")
439 public Response addMediaPackageCatalog(@Context HttpServletRequest request) {
440 logger.trace("add catalog as multipart-form-data");
441 return addMediaPackageElement(request, MediaPackageElement.Type.Catalog);
442 }
443
444 @POST
445 @Produces(MediaType.TEXT_XML)
446 @Path("addAttachment")
447 @RestQuery(name = "addAttachmentURL", description = "Add an attachment to a given media package using an URL", restParameters = {
448 @RestParameter(description = "The location of the attachment", isRequired = true, name = "url", type = RestParameter.Type.STRING),
449 @RestParameter(description = "The kind of attachment", isRequired = true, name = "flavor", type = RestParameter.Type.STRING),
450 @RestParameter(description = "The tags of the attachment", isRequired = false, name = "tags", type = RestParameter.Type.STRING),
451 @RestParameter(description = "The media package as XML", isRequired = true, name = "mediaPackage", type = RestParameter.Type.TEXT) }, responses = {
452 @RestResponse(description = "Returns augmented media package", responseCode = HttpServletResponse.SC_OK),
453 @RestResponse(description = "Media package not valid", responseCode = HttpServletResponse.SC_BAD_REQUEST),
454 @RestResponse(description = "", responseCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR) }, returnDescription = "")
455 public Response addMediaPackageAttachment(@FormParam("url") String url, @FormParam("flavor") String flavor,
456 @FormParam("tags") String tags, @FormParam("mediaPackage") String mpx) {
457 logger.trace("add attachment with url: {} flavor: {} mediaPackage: {}", url, flavor, mpx);
458 try {
459 MediaPackage mp = MP_FACTORY.newMediaPackageBuilder().loadFromXml(mpx);
460 if (MediaPackageSupport.sanityCheck(mp).isSome())
461 return Response.serverError().status(Status.BAD_REQUEST).build();
462 String[] tagsArray = null;
463 if (tags != null) {
464 tagsArray = tags.split(",");
465 }
466 mp = ingestService.addAttachment(new URI(url), MediaPackageElementFlavor.parseFlavor(flavor), tagsArray, mp);
467 return Response.ok(mp).build();
468 } catch (Exception e) {
469 logger.warn("Unable to add attachment", e);
470 return Response.serverError().status(Status.INTERNAL_SERVER_ERROR).build();
471 }
472 }
473
474 @POST
475 @Produces(MediaType.TEXT_XML)
476 @Consumes(MediaType.MULTIPART_FORM_DATA)
477 @Path("addAttachment")
478 @RestQuery(name = "addAttachmentInputStream", description = "Add an attachment to a given media package using an input stream", restParameters = {
479 @RestParameter(description = "The kind of attachment", isRequired = true, name = "flavor", type = RestParameter.Type.STRING),
480 @RestParameter(description = "The tags of the attachment", isRequired = false, name = "tags", type = RestParameter.Type.STRING),
481 @RestParameter(description = "The media package as XML", isRequired = true, name = "mediaPackage", type = RestParameter.Type.TEXT) }, bodyParameter = @RestParameter(description = "The attachment file", isRequired = true, name = "BODY", type = RestParameter.Type.FILE), responses = {
482 @RestResponse(description = "Returns augmented media package", responseCode = HttpServletResponse.SC_OK),
483 @RestResponse(description = "Media package not valid", responseCode = HttpServletResponse.SC_BAD_REQUEST),
484 @RestResponse(description = "", responseCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR) }, returnDescription = "")
485 public Response addMediaPackageAttachment(@Context HttpServletRequest request) {
486 logger.trace("add attachment as multipart-form-data");
487 return addMediaPackageElement(request, MediaPackageElement.Type.Attachment);
488 }
489
490 protected Response addMediaPackageElement(HttpServletRequest request, MediaPackageElement.Type type) {
491 MediaPackageElementFlavor flavor = null;
492 InputStream in = null;
493 try {
494 String fileName = null;
495 MediaPackage mp = null;
496 Long startTime = null;
497 String[] tags = null;
498
499 if (!ServletFileUpload.isMultipartContent(request)) {
500 logger.trace("request isn't multipart-form-data");
501 return Response.serverError().status(Status.BAD_REQUEST).build();
502 }
503 boolean isDone = false;
504 for (FileItemIterator iter = new ServletFileUpload().getItemIterator(request); iter.hasNext();) {
505 FileItemStream item = iter.next();
506 String fieldName = item.getFieldName();
507 if (item.isFormField()) {
508 if ("flavor".equals(fieldName)) {
509 String flavorString = Streams.asString(item.openStream(), "UTF-8");
510 logger.trace("flavor: {}", flavorString);
511 if (flavorString != null) {
512 try {
513 flavor = MediaPackageElementFlavor.parseFlavor(flavorString);
514 } catch (IllegalArgumentException e) {
515 String error = String.format("Could not parse flavor '%s'", flavorString);
516 logger.debug(error, e);
517 return Response.status(Status.BAD_REQUEST).entity(error).build();
518 }
519 }
520 } else if ("tags".equals(fieldName)) {
521 String tagsString = Streams.asString(item.openStream(), "UTF-8");
522 logger.trace("tags: {}", tagsString);
523 tags = tagsString.split(",");
524 } else if ("mediaPackage".equals(fieldName)) {
525 try {
526 String mediaPackageString = Streams.asString(item.openStream(), "UTF-8");
527 logger.trace("mediaPackage: {}", mediaPackageString);
528 mp = MP_FACTORY.newMediaPackageBuilder().loadFromXml(mediaPackageString);
529 } catch (MediaPackageException e) {
530 logger.debug("Unable to parse the 'mediaPackage' parameter: {}", ExceptionUtils.getMessage(e));
531 return Response.serverError().status(Status.BAD_REQUEST).build();
532 }
533 } else if ("startTime".equals(fieldName) && "/ingest/addPartialTrack".equals(request.getPathInfo())) {
534 String startTimeString = Streams.asString(item.openStream(), "UTF-8");
535 logger.trace("startTime: {}", startTime);
536 try {
537 startTime = Long.parseLong(startTimeString);
538 } catch (Exception e) {
539 logger.debug("Unable to parse the 'startTime' parameter: {}", ExceptionUtils.getMessage(e));
540 return Response.serverError().status(Status.BAD_REQUEST).build();
541 }
542 }
543 } else {
544 if (flavor == null) {
545
546 logger.debug("A flavor has to be specified in the request prior to the content BODY");
547 return Response.serverError().status(Status.BAD_REQUEST).build();
548 }
549 fileName = item.getName();
550 in = item.openStream();
551 isDone = true;
552 }
553 if (isDone) {
554 break;
555 }
556 }
557
558
559
560
561 if (in == null || mp == null || MediaPackageSupport.sanityCheck(mp).isSome()) {
562 return Response.serverError().status(Status.BAD_REQUEST).build();
563 }
564 switch (type) {
565 case Attachment:
566 mp = ingestService.addAttachment(in, fileName, flavor, tags, mp);
567 break;
568 case Catalog:
569 try {
570 mp = ingestService.addCatalog(in, fileName, flavor, tags, mp);
571 } catch (IllegalArgumentException e) {
572 logger.debug("Invalid catalog data", e);
573 return Response.serverError().status(Status.BAD_REQUEST).build();
574 }
575 break;
576 case Track:
577 if (startTime == null) {
578 mp = ingestService.addTrack(in, fileName, flavor, tags, mp);
579 } else {
580 mp = ingestService.addPartialTrack(in, fileName, flavor, startTime, mp);
581 }
582 break;
583 default:
584 throw new IllegalStateException("Type must be one of track, catalog, or attachment");
585 }
586 return Response.ok(MediaPackageParser.getAsXml(mp)).build();
587 } catch (Exception e) {
588 logger.warn("Unable to add mediapackage element", e);
589 return Response.serverError().status(Status.INTERNAL_SERVER_ERROR).build();
590 } finally {
591 IOUtils.closeQuietly(in);
592 }
593 }
594
595 @POST
596 @Produces(MediaType.TEXT_XML)
597 @Consumes(MediaType.MULTIPART_FORM_DATA)
598 @Path("addMediaPackage")
599 @RestQuery(name = "addMediaPackage",
600 description = "<p>Create and ingest media package from media tracks with additional Dublin Core metadata. It is "
601 + "mandatory to set a title for the recording. This can be done with the 'title' form field or by supplying a DC "
602 + "catalog with a title included. The identifier of the newly created media package will be taken from the "
603 + "<em>identifier</em> field or the episode DublinCore catalog (deprecated<sup>*</sup>). If no identifier is "
604 + "set, a new random UUIDv4 will be generated. This endpoint is not meant to be used by capture agents for "
605 + "scheduled recordings. Its primary use is for manual ingests with command line tools like cURL.</p> "
606 + "<p>Multiple tracks can be ingested by using multiple form fields. It is important to always set the "
607 + "flavor of the next media file <em>before</em> sending the media file itself.</p>"
608 + "<b>(*)</b> The special treatment of the identifier field is deprecated and may be removed in future versions "
609 + "without further notice in favor of a random UUID generation to ensure uniqueness of identifiers. "
610 + "<h3>Example cURL command:</h3>"
611 + "<p>Ingest one video file:</p>"
612 + "<p><pre>\n"
613 + "curl -i -u admin:opencast http://localhost:8080/ingest/addMediaPackage \\\n"
614 + " -F creator='John Doe' -F title='Test Recording' \\\n"
615 + " -F 'flavor=presentation/source' -F 'BODY=@test-recording.mp4' \n"
616 + "</pre></p>"
617 + "<p>Ingest two video files:</p>"
618 + "<p><pre>\n"
619 + "curl -i -u admin:opencast http://localhost:8080/ingest/addMediaPackage \\\n"
620 + " -F creator='John Doe' -F title='Test Recording' \\\n"
621 + " -F 'flavor=presentation/source' -F 'BODY=@test-recording-vga.mp4' \\\n"
622 + " -F 'flavor=presenter/source' -F 'BODY=@test-recording-camera.mp4' \n"
623 + "</pre></p>",
624 restParameters = {
625 @RestParameter(description = "The kind of media track. This has to be specified prior to each media track", isRequired = true, name = "flavor", type = RestParameter.Type.STRING),
626 @RestParameter(description = "Episode metadata value", isRequired = false, name = "abstract", type = RestParameter.Type.STRING),
627 @RestParameter(description = "Episode metadata value", isRequired = false, name = "accessRights", type = RestParameter.Type.STRING),
628 @RestParameter(description = "Episode metadata value", isRequired = false, name = "available", type = RestParameter.Type.STRING),
629 @RestParameter(description = "Episode metadata value", isRequired = false, name = "contributor", type = RestParameter.Type.STRING),
630 @RestParameter(description = "Episode metadata value", isRequired = false, name = "coverage", type = RestParameter.Type.STRING),
631 @RestParameter(description = "Episode metadata value", isRequired = false, name = "created", type = RestParameter.Type.STRING),
632 @RestParameter(description = "Episode metadata value", isRequired = false, name = "creator", type = RestParameter.Type.STRING),
633 @RestParameter(description = "Episode metadata value", isRequired = false, name = "date", type = RestParameter.Type.STRING),
634 @RestParameter(description = "Episode metadata value", isRequired = false, name = "description", type = RestParameter.Type.STRING),
635 @RestParameter(description = "Episode metadata value", isRequired = false, name = "extent", type = RestParameter.Type.STRING),
636 @RestParameter(description = "Episode metadata value", isRequired = false, name = "format", type = RestParameter.Type.STRING),
637 @RestParameter(description = "Episode metadata value", isRequired = false, name = "identifier", type = RestParameter.Type.STRING),
638 @RestParameter(description = "Episode metadata value", isRequired = false, name = "isPartOf", type = RestParameter.Type.STRING),
639 @RestParameter(description = "Episode metadata value", isRequired = false, name = "isReferencedBy", type = RestParameter.Type.STRING),
640 @RestParameter(description = "Episode metadata value", isRequired = false, name = "isReplacedBy", type = RestParameter.Type.STRING),
641 @RestParameter(description = "Episode metadata value", isRequired = false, name = "language", type = RestParameter.Type.STRING),
642 @RestParameter(description = "Episode metadata value", isRequired = false, name = "license", type = RestParameter.Type.STRING),
643 @RestParameter(description = "Episode metadata value", isRequired = false, name = "publisher", type = RestParameter.Type.STRING),
644 @RestParameter(description = "Episode metadata value", isRequired = false, name = "relation", type = RestParameter.Type.STRING),
645 @RestParameter(description = "Episode metadata value", isRequired = false, name = "replaces", type = RestParameter.Type.STRING),
646 @RestParameter(description = "Episode metadata value", isRequired = false, name = "rights", type = RestParameter.Type.STRING),
647 @RestParameter(description = "Episode metadata value", isRequired = false, name = "rightsHolder", type = RestParameter.Type.STRING),
648 @RestParameter(description = "Episode metadata value", isRequired = false, name = "source", type = RestParameter.Type.STRING),
649 @RestParameter(description = "Episode metadata value", isRequired = false, name = "spatial", type = RestParameter.Type.STRING),
650 @RestParameter(description = "Episode metadata value", isRequired = false, name = "subject", type = RestParameter.Type.STRING),
651 @RestParameter(description = "Episode metadata value", isRequired = false, name = "temporal", type = RestParameter.Type.STRING),
652 @RestParameter(description = "Episode metadata value", isRequired = false, name = "title", type = RestParameter.Type.STRING),
653 @RestParameter(description = "Episode metadata value", isRequired = false, name = "type", type = RestParameter.Type.STRING),
654 @RestParameter(description = "URL of episode DublinCore Catalog", isRequired = false, name = "episodeDCCatalogUri", type = RestParameter.Type.STRING),
655 @RestParameter(description = "Episode DublinCore Catalog", isRequired = false, name = "episodeDCCatalog", type = RestParameter.Type.STRING),
656 @RestParameter(description = "URL of series DublinCore Catalog", isRequired = false, name = "seriesDCCatalogUri", type = RestParameter.Type.STRING),
657 @RestParameter(description = "Series DublinCore Catalog", isRequired = false, name = "seriesDCCatalog", type = RestParameter.Type.STRING),
658 @RestParameter(description = "Access control list in XACML or JSON form", isRequired = false, name = "acl", type = RestParameter.Type.STRING),
659 @RestParameter(description = "Tag of the next media file", isRequired = false, name = "tag", type = RestParameter.Type.STRING),
660 @RestParameter(description = "URL of a media track file", isRequired = false, name = "mediaUri", type = RestParameter.Type.STRING) },
661 bodyParameter = @RestParameter(description = "The media track file", isRequired = true, name = "BODY", type = RestParameter.Type.FILE),
662 responses = {
663 @RestResponse(description = "Ingest successful. Returns workflow instance as xml", responseCode = HttpServletResponse.SC_OK),
664 @RestResponse(description = "Ingest failed due to invalid requests.", responseCode = HttpServletResponse.SC_BAD_REQUEST),
665 @RestResponse(description = "Ingest failed. Something went wrong internally. Please have a look at the log files",
666 responseCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR) },
667 returnDescription = "")
668 public Response addMediaPackage(@Context HttpServletRequest request) {
669 logger.trace("add mediapackage as multipart-form-data");
670 return addMediaPackage(request, null);
671 }
672
673 @POST
674 @Produces(MediaType.TEXT_XML)
675 @Consumes(MediaType.MULTIPART_FORM_DATA)
676 @Path("addMediaPackage/{wdID}")
677 @RestQuery(name = "addMediaPackage",
678 description = "<p>Create and ingest media package from media tracks with additional Dublin Core metadata. It is "
679 + "mandatory to set a title for the recording. This can be done with the 'title' form field or by supplying a DC "
680 + "catalog with a title included. The identifier of the newly created media package will be taken from the "
681 + "<em>identifier</em> field or the episode DublinCore catalog (deprecated<sup>*</sup>). If no identifier is "
682 + "set, a newa randumm UUIDv4 will be generated. This endpoint is not meant to be used by capture agents for "
683 + "scheduled recordings. It's primary use is for manual ingests with command line tools like cURL.</p> "
684 + "<p>Multiple tracks can be ingested by using multiple form fields. It's important, however, to always set the "
685 + "flavor of the next media file <em>before</em> sending the media file itself.</p>"
686 + "<b>(*)</b> The special treatment of the identifier field is deprecated any may be removed in future versions "
687 + "without further notice in favor of a random UUID generation to ensure uniqueness of identifiers. "
688 + "<h3>Example cURL command:</h3>"
689 + "<p>Ingest one video file:</p>"
690 + "<p><pre>\n"
691 + "curl -i -u admin:opencast http://localhost:8080/ingest/addMediaPackage/fast \\\n"
692 + " -F creator='John Doe' -F title='Test Recording' \\\n"
693 + " -F 'flavor=presentation/source' -F 'BODY=@test-recording.mp4' \n"
694 + "</pre></p>"
695 + "<p>Ingest two video files:</p>"
696 + "<p><pre>\n"
697 + "curl -i -u admin:opencast http://localhost:8080/ingest/addMediaPackage/fast \\\n"
698 + " -F creator='John Doe' -F title='Test Recording' \\\n"
699 + " -F 'flavor=presentation/source' -F 'BODY=@test-recording-vga.mp4' \\\n"
700 + " -F 'flavor=presenter/source' -F 'BODY=@test-recording-camera.mp4' \n"
701 + "</pre></p>",
702 pathParameters = {
703 @RestParameter(description = "Workflow definition id", isRequired = true, name = "wdID", type = RestParameter.Type.STRING) },
704 restParameters = {
705 @RestParameter(description = "The kind of media track. This has to be specified prior to each media track", isRequired = true, name = "flavor", type = RestParameter.Type.STRING),
706 @RestParameter(description = "Episode metadata value", isRequired = false, name = "abstract", type = RestParameter.Type.STRING),
707 @RestParameter(description = "Episode metadata value", isRequired = false, name = "accessRights", type = RestParameter.Type.STRING),
708 @RestParameter(description = "Episode metadata value", isRequired = false, name = "available", type = RestParameter.Type.STRING),
709 @RestParameter(description = "Episode metadata value", isRequired = false, name = "contributor", type = RestParameter.Type.STRING),
710 @RestParameter(description = "Episode metadata value", isRequired = false, name = "coverage", type = RestParameter.Type.STRING),
711 @RestParameter(description = "Episode metadata value", isRequired = false, name = "created", type = RestParameter.Type.STRING),
712 @RestParameter(description = "Episode metadata value", isRequired = false, name = "creator", type = RestParameter.Type.STRING),
713 @RestParameter(description = "Episode metadata value", isRequired = false, name = "date", type = RestParameter.Type.STRING),
714 @RestParameter(description = "Episode metadata value", isRequired = false, name = "description", type = RestParameter.Type.STRING),
715 @RestParameter(description = "Episode metadata value", isRequired = false, name = "extent", type = RestParameter.Type.STRING),
716 @RestParameter(description = "Episode metadata value", isRequired = false, name = "format", type = RestParameter.Type.STRING),
717 @RestParameter(description = "Episode metadata value", isRequired = false, name = "identifier", type = RestParameter.Type.STRING),
718 @RestParameter(description = "Episode metadata value", isRequired = false, name = "isPartOf", type = RestParameter.Type.STRING),
719 @RestParameter(description = "Episode metadata value", isRequired = false, name = "isReferencedBy", type = RestParameter.Type.STRING),
720 @RestParameter(description = "Episode metadata value", isRequired = false, name = "isReplacedBy", type = RestParameter.Type.STRING),
721 @RestParameter(description = "Episode metadata value", isRequired = false, name = "language", type = RestParameter.Type.STRING),
722 @RestParameter(description = "Episode metadata value", isRequired = false, name = "license", type = RestParameter.Type.STRING),
723 @RestParameter(description = "Episode metadata value", isRequired = false, name = "publisher", type = RestParameter.Type.STRING),
724 @RestParameter(description = "Episode metadata value", isRequired = false, name = "relation", type = RestParameter.Type.STRING),
725 @RestParameter(description = "Episode metadata value", isRequired = false, name = "replaces", type = RestParameter.Type.STRING),
726 @RestParameter(description = "Episode metadata value", isRequired = false, name = "rights", type = RestParameter.Type.STRING),
727 @RestParameter(description = "Episode metadata value", isRequired = false, name = "rightsHolder", type = RestParameter.Type.STRING),
728 @RestParameter(description = "Episode metadata value", isRequired = false, name = "source", type = RestParameter.Type.STRING),
729 @RestParameter(description = "Episode metadata value", isRequired = false, name = "spatial", type = RestParameter.Type.STRING),
730 @RestParameter(description = "Episode metadata value", isRequired = false, name = "subject", type = RestParameter.Type.STRING),
731 @RestParameter(description = "Episode metadata value", isRequired = false, name = "temporal", type = RestParameter.Type.STRING),
732 @RestParameter(description = "Episode metadata value", isRequired = false, name = "title", type = RestParameter.Type.STRING),
733 @RestParameter(description = "Episode metadata value", isRequired = false, name = "type", type = RestParameter.Type.STRING),
734 @RestParameter(description = "URL of episode DublinCore Catalog", isRequired = false, name = "episodeDCCatalogUri", type = RestParameter.Type.STRING),
735 @RestParameter(description = "Episode DublinCore Catalog", isRequired = false, name = "episodeDCCatalog", type = RestParameter.Type.STRING),
736 @RestParameter(description = "URL of series DublinCore Catalog", isRequired = false, name = "seriesDCCatalogUri", type = RestParameter.Type.STRING),
737 @RestParameter(description = "Series DublinCore Catalog", isRequired = false, name = "seriesDCCatalog", type = RestParameter.Type.STRING),
738 @RestParameter(description = "Access control list in XACML or JSON form", isRequired = false, name = "acl", type = RestParameter.Type.STRING),
739 @RestParameter(description = "Tag of the next media file", isRequired = false, name = "tag", type = RestParameter.Type.STRING),
740 @RestParameter(description = "URL of a media track file", isRequired = false, name = "mediaUri", type = RestParameter.Type.STRING) },
741 bodyParameter = @RestParameter(description = "The media track file", isRequired = true, name = "BODY", type = RestParameter.Type.FILE),
742 responses = {
743 @RestResponse(description = "Ingest successful. Returns workflow instance as XML", responseCode = HttpServletResponse.SC_OK),
744 @RestResponse(description = "Ingest failed due to invalid requests.", responseCode = HttpServletResponse.SC_BAD_REQUEST),
745 @RestResponse(description = "Ingest failed. A workflow is currently active on the media package", responseCode = HttpServletResponse.SC_CONFLICT),
746 @RestResponse(description = "Ingest failed. Something went wrong internally. Please have a look at the log files",
747 responseCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR) },
748 returnDescription = "")
749 public Response addMediaPackage(@Context HttpServletRequest request, @PathParam("wdID") String wdID) {
750 logger.trace("add mediapackage as multipart-form-data with workflow definition id: {}", wdID);
751
752
753 boolean flavorAlreadyUsed = false;
754 MediaPackageElementFlavor flavor = null;
755 List<String> tags = new ArrayList<>();
756 try {
757 MediaPackage mp = ingestService.createMediaPackage();
758 DublinCoreCatalog dcc = null;
759 Map<String, String> workflowProperties = new HashMap<>();
760 int seriesDCCatalogNumber = 0;
761 int episodeDCCatalogNumber = 0;
762 boolean hasMedia = false;
763 if (ServletFileUpload.isMultipartContent(request)) {
764 for (FileItemIterator iter = new ServletFileUpload().getItemIterator(request); iter.hasNext();) {
765 FileItemStream item = iter.next();
766 if (item.isFormField()) {
767 String fieldName = item.getFieldName();
768 String value = Streams.asString(item.openStream(), "UTF-8");
769 logger.trace("form field {}: {}", fieldName, value);
770
771 if ("".equals(value)) {
772 continue;
773 }
774
775
776 if ("flavor".equals(fieldName)) {
777 try {
778 flavor = MediaPackageElementFlavor.parseFlavor(value);
779 flavorAlreadyUsed = false;
780 } catch (IllegalArgumentException e) {
781 return badRequest(String.format("Could not parse flavor '%s'", value), e);
782 }
783
784 } else if ("tag".equals(fieldName)) {
785 tags.add(value);
786
787 } else if (dcterms.contains(fieldName)) {
788 if ("identifier".equals(fieldName)) {
789
790 mp.setIdentifier(new IdImpl(value));
791 }
792 if (dcc == null) {
793 dcc = dublinCoreService.newInstance();
794 }
795 dcc.add(new EName(DublinCore.TERMS_NS_URI, fieldName), value);
796
797
798 } else if ("episodeDCCatalogUri".equals(fieldName)) {
799 try {
800 URI dcUrl = new URI(value);
801 ingestService.addCatalog(dcUrl, MediaPackageElements.EPISODE, null, mp);
802 updateMediaPackageID(mp, dcUrl);
803 episodeDCCatalogNumber += 1;
804 } catch (java.net.URISyntaxException e) {
805 return badRequest(String.format("Invalid URI %s for episodeDCCatalogUri", value), e);
806 } catch (Exception e) {
807 return badRequest("Could not parse XML Dublin Core catalog", e);
808 }
809
810
811 } else if ("episodeDCCatalog".equals(fieldName)) {
812 try (InputStream is = new ByteArrayInputStream(value.getBytes(StandardCharsets.UTF_8))) {
813 final String fileName = "episode-" + episodeDCCatalogNumber + ".xml";
814 ingestService.addCatalog(is, fileName, MediaPackageElements.EPISODE, mp);
815 episodeDCCatalogNumber += 1;
816 is.reset();
817 updateMediaPackageID(mp, is);
818 } catch (Exception e) {
819 return badRequest("Could not parse XML Dublin Core catalog", e);
820 }
821
822
823 } else if ("seriesDCCatalogUri".equals(fieldName)) {
824 try {
825 URI dcUrl = new URI(value);
826 ingestService.addCatalog(dcUrl, MediaPackageElements.SERIES, null, mp);
827 } catch (java.net.URISyntaxException e) {
828 return badRequest(String.format("Invalid URI %s for episodeDCCatalogUri", value), e);
829 } catch (Exception e) {
830 return badRequest("Could not parse XML Dublin Core catalog", e);
831 }
832
833
834 } else if ("seriesDCCatalog".equals(fieldName)) {
835 final String fileName = "series-" + seriesDCCatalogNumber + ".xml";
836 seriesDCCatalogNumber += 1;
837 try (InputStream is = new ByteArrayInputStream(value.getBytes(StandardCharsets.UTF_8))) {
838 ingestService.addCatalog(is, fileName, MediaPackageElements.SERIES, mp);
839 } catch (Exception e) {
840 return badRequest("Could not parse XML Dublin Core catalog", e);
841 }
842
843
844 } else if ("acl".equals(fieldName)) {
845 InputStream inputStream = new ByteArrayInputStream(value.getBytes(StandardCharsets.UTF_8));
846 AccessControlList acl;
847 try {
848 acl = AccessControlParser.parseAcl(inputStream);
849 inputStream = new ByteArrayInputStream(XACMLUtils.getXacml(mp, acl).getBytes(StandardCharsets.UTF_8));
850 } catch (AccessControlParsingException e) {
851
852 logger.debug("Unable to parse ACL, guessing that this is already XACML");
853 inputStream.reset();
854 }
855 ingestService.addAttachment(inputStream, "episode-security.xml", XACML_POLICY_EPISODE, mp);
856
857
858 } else if ("mediaUri".equals(fieldName)) {
859 if (flavor == null) {
860 return badRequest("A flavor has to be specified in the request prior to the media file", null);
861 }
862 URI mediaUrl;
863 try {
864 mediaUrl = new URI(value);
865 } catch (java.net.URISyntaxException e) {
866 return badRequest(String.format("Invalid URI %s for media", value), e);
867 }
868 warnIfFlavorAlreadyUsed(flavorAlreadyUsed);
869 ingestService.addTrack(mediaUrl, flavor, tags.toArray(new String[0]), mp);
870 flavorAlreadyUsed = true;
871 tags.clear();
872 hasMedia = true;
873
874 } else {
875
876 workflowProperties.put(fieldName, value);
877 }
878
879
880 } else {
881 if (flavor == null) {
882
883 return badRequest("A flavor has to be specified in the request prior to the content BODY", null);
884 }
885 warnIfFlavorAlreadyUsed(flavorAlreadyUsed);
886 ingestService.addTrack(item.openStream(), item.getName(), flavor, tags.toArray(new String[0]), mp);
887 flavorAlreadyUsed = true;
888 tags.clear();
889 hasMedia = true;
890 }
891 }
892
893
894 if (!hasMedia) {
895 return badRequest("Rejected ingest without actual media.", null);
896 }
897
898
899 if (dcc != null) {
900 ByteArrayOutputStream out = new ByteArrayOutputStream();
901 try {
902 dcc.toXml(out, true);
903 try (InputStream in = new ByteArrayInputStream(out.toByteArray())) {
904 ingestService.addCatalog(in, "dublincore.xml", MediaPackageElements.EPISODE, mp);
905 }
906 } catch (Exception e) {
907 return badRequest("Could not create XML from ingested metadata", e);
908 }
909
910
911 } else if (episodeDCCatalogNumber == 0) {
912 return badRequest("Rejected ingest without episode metadata. At least provide a title.", null);
913 }
914
915 WorkflowInstance workflow = (wdID == null)
916 ? ingestService.ingest(mp)
917 : ingestService.ingest(mp, wdID, workflowProperties);
918 return Response.ok(new JaxbWorkflowInstance(workflow)).build();
919 }
920 return Response.serverError().status(Status.BAD_REQUEST).build();
921 } catch (IllegalArgumentException e) {
922 return badRequest(e.getMessage(), e);
923 } catch (IllegalStateException e) {
924 return Response.status(Status.CONFLICT).entity(e.getMessage()).build();
925 } catch (Exception e) {
926 logger.warn("Unable to add mediapackage", e);
927 return Response.serverError().status(Status.INTERNAL_SERVER_ERROR).build();
928 }
929 }
930
931 @Deprecated
932 private void warnIfFlavorAlreadyUsed(final boolean used) {
933 if (used) {
934 logger.warn("\n"
935 + "********************************************\n"
936 + "* Warning: Re-use of flavors during ingest *\n"
937 + "* is deprecated and will be *\n"
938 + "* removed soon! Declare a flavor *\n"
939 + "* for each media file or create *\n"
940 + "* an issue if you need this. *\n"
941 + "********************************************"
942 );
943 }
944 }
945
946
947
948
949
950
951
952
953
954 private void updateMediaPackageID(MediaPackage mp, InputStream is) throws IOException {
955 DublinCoreCatalog dc = DublinCores.read(is);
956 EName en = new EName(DublinCore.TERMS_NS_URI, "identifier");
957 String id = dc.getFirst(en);
958 if (id != null) {
959 mp.setIdentifier(new IdImpl(id));
960 }
961 }
962
963
964
965
966
967
968
969
970
971 private void updateMediaPackageID(MediaPackage mp, URI uri) throws IOException {
972 InputStream in = null;
973 HttpResponse response = null;
974 try {
975 if (uri.toString().startsWith("http")) {
976 HttpGet get = new HttpGet(uri);
977 response = httpClient.execute(get);
978 int httpStatusCode = response.getStatusLine().getStatusCode();
979 if (httpStatusCode != 200) {
980 throw new IOException(uri + " returns http " + httpStatusCode);
981 }
982 in = response.getEntity().getContent();
983 } else {
984 in = uri.toURL().openStream();
985 }
986 updateMediaPackageID(mp, in);
987 in.close();
988 } finally {
989 IOUtils.closeQuietly(in);
990 httpClient.close(response);
991 }
992 }
993
994 @POST
995 @Path("addZippedMediaPackage/{workflowDefinitionId}")
996 @Produces(MediaType.TEXT_XML)
997 @RestQuery(name = "addZippedMediaPackage", description = "Create media package from a compressed file containing a manifest.xml document and all media tracks, metadata catalogs and attachments", pathParameters = { @RestParameter(description = "Workflow definition id", isRequired = true, name = WORKFLOW_DEFINITION_ID_PARAM, type = RestParameter.Type.STRING) }, restParameters = { @RestParameter(description = "The workflow instance ID to associate with this zipped mediapackage", isRequired = false, name = WORKFLOW_INSTANCE_ID_PARAM, type = RestParameter.Type.STRING) }, bodyParameter = @RestParameter(description = "The compressed (application/zip) media package file", isRequired = true, name = "BODY", type = RestParameter.Type.FILE), responses = {
998 @RestResponse(description = "", responseCode = HttpServletResponse.SC_OK),
999 @RestResponse(description = "", responseCode = HttpServletResponse.SC_BAD_REQUEST),
1000 @RestResponse(description = "", responseCode = HttpServletResponse.SC_NOT_FOUND),
1001 @RestResponse(description = "", responseCode = HttpServletResponse.SC_SERVICE_UNAVAILABLE) }, returnDescription = "")
1002 public Response addZippedMediaPackage(@Context HttpServletRequest request,
1003 @PathParam("workflowDefinitionId") String wdID, @QueryParam("id") String wiID) {
1004 logger.trace("add zipped media package with workflow definition id: {} and workflow instance id: {}", wdID, wiID);
1005 if (!isIngestLimitEnabled() || getIngestLimit() > 0) {
1006 return ingestZippedMediaPackage(request, wdID, wiID);
1007 } else {
1008 logger.warn("Delaying ingest because we have exceeded the maximum number of ingests this server is setup to do concurrently.");
1009 return Response.status(Status.SERVICE_UNAVAILABLE).build();
1010 }
1011 }
1012
1013 @POST
1014 @Path("addZippedMediaPackage")
1015 @Produces(MediaType.TEXT_XML)
1016 @RestQuery(name = "addZippedMediaPackage", description = "Create media package from a compressed file containing a manifest.xml document and all media tracks, metadata catalogs and attachments", restParameters = {
1017 @RestParameter(description = "The workflow definition ID to run on this mediapackage. "
1018 + "This parameter has to be set in the request prior to the zipped mediapackage "
1019 + "(This parameter is deprecated. Please use /addZippedMediaPackage/{workflowDefinitionId} instead)", isRequired = false, name = WORKFLOW_DEFINITION_ID_PARAM, type = RestParameter.Type.STRING),
1020 @RestParameter(description = "The workflow instance ID to associate with this zipped mediapackage. "
1021 + "This parameter has to be set in the request prior to the zipped mediapackage "
1022 + "(This parameter is deprecated. Please use /addZippedMediaPackage/{workflowDefinitionId} with a path parameter instead)", isRequired = false, name = WORKFLOW_INSTANCE_ID_PARAM, type = RestParameter.Type.STRING) }, bodyParameter = @RestParameter(description = "The compressed (application/zip) media package file", isRequired = true, name = "BODY", type = RestParameter.Type.FILE), responses = {
1023 @RestResponse(description = "", responseCode = HttpServletResponse.SC_OK),
1024 @RestResponse(description = "", responseCode = HttpServletResponse.SC_BAD_REQUEST),
1025 @RestResponse(description = "", responseCode = HttpServletResponse.SC_NOT_FOUND),
1026 @RestResponse(description = "", responseCode = HttpServletResponse.SC_SERVICE_UNAVAILABLE) }, returnDescription = "")
1027 public Response addZippedMediaPackage(@Context HttpServletRequest request) {
1028 logger.trace("add zipped media package");
1029 if (!isIngestLimitEnabled() || getIngestLimit() > 0) {
1030 return ingestZippedMediaPackage(request, null, null);
1031 } else {
1032 logger.warn("Delaying ingest because we have exceeded the maximum number of ingests this server is setup to do concurrently.");
1033 return Response.status(Status.SERVICE_UNAVAILABLE).build();
1034 }
1035 }
1036
1037 private Response ingestZippedMediaPackage(HttpServletRequest request, String wdID, String wiID) {
1038 if (isIngestLimitEnabled()) {
1039 setIngestLimit(getIngestLimit() - 1);
1040 logger.debug("An ingest has started so remaining ingest limit is " + getIngestLimit());
1041 }
1042 InputStream in = null;
1043 Date started = new Date();
1044
1045 logger.info("Received new request from {} to ingest a zipped mediapackage", request.getRemoteHost());
1046
1047 try {
1048 String workflowDefinitionId = wdID;
1049 String workflowIdAsString = wiID;
1050 Long workflowInstanceIdAsLong = null;
1051 Map<String, String> workflowConfig = new HashMap<>();
1052 if (ServletFileUpload.isMultipartContent(request)) {
1053 boolean isDone = false;
1054 for (FileItemIterator iter = new ServletFileUpload().getItemIterator(request); iter.hasNext();) {
1055 FileItemStream item = iter.next();
1056 if (item.isFormField()) {
1057 String fieldName = item.getFieldName();
1058 String value = Streams.asString(item.openStream(), "UTF-8");
1059 logger.trace("{}: {}", fieldName, value);
1060 if (WORKFLOW_INSTANCE_ID_PARAM.equals(fieldName)) {
1061 workflowIdAsString = value;
1062 continue;
1063 } else if (WORKFLOW_DEFINITION_ID_PARAM.equals(fieldName)) {
1064 workflowDefinitionId = value;
1065 continue;
1066 } else {
1067 logger.debug("Processing form field: " + fieldName);
1068 workflowConfig.put(fieldName, value);
1069 }
1070 } else {
1071 logger.debug("Processing file item");
1072
1073
1074 in = item.openStream();
1075 isDone = true;
1076 }
1077 if (isDone)
1078 break;
1079 }
1080 } else {
1081 logger.debug("Processing file item");
1082 in = request.getInputStream();
1083 }
1084
1085
1086 DateFormat formatter = new SimpleDateFormat(IngestService.UTC_DATE_FORMAT);
1087 workflowConfig.put(IngestService.START_DATE_KEY, formatter.format(started));
1088
1089
1090 if (!StringUtils.isBlank(workflowIdAsString)) {
1091 try {
1092 workflowInstanceIdAsLong = Long.parseLong(workflowIdAsString);
1093 } catch (NumberFormatException e) {
1094
1095 workflowConfig.put(IngestServiceImpl.LEGACY_MEDIAPACKAGE_ID_KEY, workflowIdAsString);
1096 }
1097 }
1098 if (StringUtils.isBlank(workflowDefinitionId)) {
1099 workflowDefinitionId = defaultWorkflowDefinitionId;
1100 }
1101
1102 WorkflowInstance workflow;
1103 if (workflowInstanceIdAsLong != null) {
1104 workflow = ingestService.addZippedMediaPackage(in, workflowDefinitionId, workflowConfig,
1105 workflowInstanceIdAsLong);
1106 } else {
1107 workflow = ingestService.addZippedMediaPackage(in, workflowDefinitionId, workflowConfig);
1108 }
1109 return Response.ok(XmlWorkflowParser.toXml(workflow)).build();
1110 } catch (NotFoundException e) {
1111 logger.info("Not found: {}", e.getMessage());
1112 return Response.status(Status.NOT_FOUND).build();
1113 } catch (MediaPackageException e) {
1114 logger.warn("Unable to ingest mediapackage: {}", e.getMessage());
1115 return Response.serverError().status(Status.BAD_REQUEST).build();
1116 } catch (Exception e) {
1117 logger.warn("Unable to ingest mediapackage", e);
1118 return Response.serverError().status(Status.INTERNAL_SERVER_ERROR).build();
1119 } finally {
1120 IOUtils.closeQuietly(in);
1121 if (isIngestLimitEnabled()) {
1122 setIngestLimit(getIngestLimit() + 1);
1123 logger.debug("An ingest has finished so increased ingest limit to " + getIngestLimit());
1124 }
1125 }
1126 }
1127
1128 @POST
1129 @Produces(MediaType.TEXT_XML)
1130 @Path("ingest/{wdID}")
1131 @RestQuery(name = "ingest",
1132 description = "<p>Ingest the completed media package into the system and start a specified workflow.</p>"
1133 + "<p>In addition to the documented form parameters, workflow parameters are accepted as well.</p>",
1134 pathParameters = {
1135 @RestParameter(description = "Workflow definition id", isRequired = true, name = "wdID", type = RestParameter.Type.STRING) },
1136 restParameters = {
1137 @RestParameter(description = "The media package as XML", isRequired = true, name = "mediaPackage", type = RestParameter.Type.TEXT) },
1138 responses = {
1139 @RestResponse(description = "Returns the workflow instance", responseCode = HttpServletResponse.SC_OK),
1140 @RestResponse(description = "Media package not valid", responseCode = HttpServletResponse.SC_BAD_REQUEST) },
1141 returnDescription = "")
1142 public Response ingest(@Context HttpServletRequest request, @PathParam("wdID") String wdID) {
1143 logger.trace("ingest media package with workflow definition id: {}", wdID);
1144 if (StringUtils.isBlank(wdID)) {
1145 return Response.status(Response.Status.BAD_REQUEST).build();
1146 }
1147 return ingest(wdID, request);
1148 }
1149
1150 @POST
1151 @Produces(MediaType.TEXT_XML)
1152 @Path("ingest")
1153 @RestQuery(name = "ingest",
1154 description = "<p>Ingest the completed media package into the system</p>"
1155 + "<p>In addition to the documented form parameters, workflow parameters are accepted as well.</p>",
1156 restParameters = {
1157 @RestParameter(description = "The media package", isRequired = true, name = "mediaPackage", type = RestParameter.Type.TEXT),
1158 @RestParameter(description = "Workflow definition id", isRequired = false, name = WORKFLOW_DEFINITION_ID_PARAM, type = RestParameter.Type.STRING),
1159 @RestParameter(description = "The workflow instance ID to associate this ingest with scheduled events.", isRequired = false, name = WORKFLOW_INSTANCE_ID_PARAM, type = RestParameter.Type.STRING) },
1160 responses = {
1161 @RestResponse(description = "Returns the workflow instance", responseCode = HttpServletResponse.SC_OK),
1162 @RestResponse(description = "Media package not valid", responseCode = HttpServletResponse.SC_BAD_REQUEST) },
1163 returnDescription = "")
1164 public Response ingest(@Context HttpServletRequest request) {
1165 return ingest(null, request);
1166 }
1167
1168 private Map<String, String> getWorkflowConfig(MultivaluedMap<String, String> formData) {
1169 Map<String, String> wfConfig = new HashMap<>();
1170 for (String key : formData.keySet()) {
1171 if (!"mediaPackage".equals(key)) {
1172 wfConfig.put(key, formData.getFirst(key));
1173 }
1174 }
1175 return wfConfig;
1176 }
1177
1178 private Response ingest(final String wdID, final HttpServletRequest request) {
1179
1180
1181 final MultivaluedMap<String, String> formData = new MultivaluedHashMap<>();
1182 if (ServletFileUpload.isMultipartContent(request)) {
1183
1184 try {
1185 for (FileItemIterator iter = new ServletFileUpload().getItemIterator(request); iter.hasNext();) {
1186 FileItemStream item = iter.next();
1187 if (item.isFormField()) {
1188 final String value = Streams.asString(item.openStream(), "UTF-8");
1189 formData.putSingle(item.getFieldName(), value);
1190 }
1191 }
1192 } catch (FileUploadException | IOException e) {
1193 return Response.status(Response.Status.BAD_REQUEST).build();
1194 }
1195 } else {
1196 request.getParameterMap().forEach((key, value) -> formData.put(key, Arrays.asList(value)));
1197 }
1198
1199 final Map<String, String> wfConfig = getWorkflowConfig(formData);
1200 if (StringUtils.isNotBlank(wdID))
1201 wfConfig.put(WORKFLOW_DEFINITION_ID_PARAM, wdID);
1202
1203 final MediaPackage mp;
1204 try {
1205 mp = MP_FACTORY.newMediaPackageBuilder().loadFromXml(formData.getFirst("mediaPackage"));
1206 if (MediaPackageSupport.sanityCheck(mp).isSome()) {
1207 logger.warn("Rejected ingest with invalid mediapackage {}", mp);
1208 return Response.status(Status.BAD_REQUEST).build();
1209 }
1210 } catch (Exception e) {
1211 logger.warn("Rejected ingest without mediapackage");
1212 return Response.status(Status.BAD_REQUEST).build();
1213 }
1214
1215 final String workflowInstance = wfConfig.get(WORKFLOW_INSTANCE_ID_PARAM);
1216 final String workflowDefinition = wfConfig.get(WORKFLOW_DEFINITION_ID_PARAM);
1217
1218
1219 final Date ingestDate = startCache.getIfPresent(mp.getIdentifier().toString());
1220 wfConfig.put(IngestService.START_DATE_KEY, DATE_FORMAT.format(ingestDate != null ? ingestDate : new Date()));
1221
1222 final X<WorkflowInstance> ingest = new X<WorkflowInstance>() {
1223 @Override
1224 public WorkflowInstance xapply() throws Exception {
1225
1226 Long workflowInstanceId = null;
1227 if (StringUtils.isNotBlank(workflowInstance)) {
1228 try {
1229 workflowInstanceId = Long.parseLong(workflowInstance);
1230 } catch (NumberFormatException e) {
1231
1232 wfConfig.put(IngestServiceImpl.LEGACY_MEDIAPACKAGE_ID_KEY, workflowInstance);
1233 }
1234 }
1235
1236 if (workflowInstanceId != null) {
1237 return ingestService.ingest(mp, trimToNull(workflowDefinition), wfConfig, workflowInstanceId);
1238 } else {
1239 return ingestService.ingest(mp, trimToNull(workflowDefinition), wfConfig);
1240 }
1241 }
1242 };
1243
1244 try {
1245 WorkflowInstance workflow = ingest.apply();
1246 startCache.asMap().remove(mp.getIdentifier().toString());
1247 return Response.ok(XmlWorkflowParser.toXml(workflow)).build();
1248 } catch (Exception e) {
1249 Throwable cause = e.getCause();
1250 if (cause instanceof NotFoundException) {
1251 return badRequest("Could not retrieve all media package elements", e);
1252 }
1253 logger.warn("Unable to ingest mediapackage", e);
1254 return Response.serverError().status(Status.INTERNAL_SERVER_ERROR).build();
1255 }
1256 }
1257
1258 @POST
1259 @Path("schedule")
1260 @RestQuery(name = "schedule", description = "Schedule an event based on the given media package",
1261 restParameters = {
1262 @RestParameter(description = "The media package", isRequired = true, name = "mediaPackage", type = RestParameter.Type.TEXT) },
1263 responses = {
1264 @RestResponse(description = "Event scheduled", responseCode = HttpServletResponse.SC_CREATED),
1265 @RestResponse(description = "Media package not valid", responseCode = HttpServletResponse.SC_BAD_REQUEST) },
1266 returnDescription = "")
1267 public Response schedule(MultivaluedMap<String, String> formData) {
1268 logger.trace("pass schedule with default workflow definition id {}", defaultWorkflowDefinitionId);
1269 return this.schedule(defaultWorkflowDefinitionId, formData);
1270 }
1271
1272 @POST
1273 @Path("schedule/{wdID}")
1274 @RestQuery(name = "schedule", description = "Schedule an event based on the given media package",
1275 pathParameters = {
1276 @RestParameter(description = "Workflow definition id", isRequired = true, name = "wdID", type = RestParameter.Type.STRING) },
1277 restParameters = {
1278 @RestParameter(description = "The media package", isRequired = true, name = "mediaPackage", type = RestParameter.Type.TEXT) },
1279 responses = {
1280 @RestResponse(description = "Event scheduled", responseCode = HttpServletResponse.SC_CREATED),
1281 @RestResponse(description = "Media package not valid", responseCode = HttpServletResponse.SC_BAD_REQUEST) },
1282 returnDescription = "")
1283 public Response schedule(@PathParam("wdID") String wdID, MultivaluedMap<String, String> formData) {
1284 if (StringUtils.isBlank(wdID)) {
1285 logger.trace("workflow definition id is not specified");
1286 return Response.status(Response.Status.BAD_REQUEST).build();
1287 }
1288
1289 Map<String, String> wfConfig = getWorkflowConfig(formData);
1290 if (StringUtils.isNotBlank(wdID)) {
1291 wfConfig.put(CaptureParameters.INGEST_WORKFLOW_DEFINITION, wdID);
1292 }
1293 logger.debug("Schedule with workflow definition '{}'", wfConfig.get(WORKFLOW_DEFINITION_ID_PARAM));
1294
1295 String mediaPackageXml = formData.getFirst("mediaPackage");
1296 if (StringUtils.isBlank(mediaPackageXml)) {
1297 logger.debug("Rejected schedule without media package");
1298 return Response.status(Status.BAD_REQUEST).build();
1299 }
1300
1301 MediaPackage mp = null;
1302 try {
1303 mp = MP_FACTORY.newMediaPackageBuilder().loadFromXml(mediaPackageXml);
1304 if (MediaPackageSupport.sanityCheck(mp).isSome()) {
1305 throw new MediaPackageException("Insane media package");
1306 }
1307 } catch (MediaPackageException e) {
1308 logger.debug("Rejected ingest with invalid media package {}", mp);
1309 return Response.status(Status.BAD_REQUEST).build();
1310 }
1311
1312 MediaPackageElement[] mediaPackageElements = mp.getElementsByFlavor(MediaPackageElements.EPISODE);
1313 if (mediaPackageElements.length != 1) {
1314 logger.debug("There can be only one (and exactly one) episode dublin core catalog: https://youtu.be/_J3VeogFUOs");
1315 return Response.status(Status.BAD_REQUEST).build();
1316 }
1317
1318 try {
1319 ingestService.schedule(mp, wdID, wfConfig);
1320 return Response.status(Status.CREATED).build();
1321 } catch (IngestException e) {
1322 return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
1323 } catch (SchedulerConflictException e) {
1324 return Response.status(Status.CONFLICT).entity(e.getMessage()).build();
1325 } catch (NotFoundException | UnauthorizedException | SchedulerException e) {
1326 return Response.serverError().build();
1327 }
1328 }
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340 @POST
1341 @Produces(MediaType.TEXT_XML)
1342 @Path("addDCCatalog")
1343 @RestQuery(name = "addDCCatalog", description = "Add a dublincore episode catalog to a given media package using an url", restParameters = {
1344 @RestParameter(description = "The media package as XML", isRequired = true, name = "mediaPackage", type = RestParameter.Type.TEXT),
1345 @RestParameter(description = "DublinCore catalog as XML", isRequired = true, name = "dublinCore", type = RestParameter.Type.TEXT),
1346 @RestParameter(defaultValue = "dublincore/episode", description = "DublinCore Flavor", isRequired = false, name = "flavor", type = RestParameter.Type.STRING) }, responses = {
1347 @RestResponse(description = "Returns augmented media package", responseCode = HttpServletResponse.SC_OK),
1348 @RestResponse(description = "Media package not valid", responseCode = HttpServletResponse.SC_BAD_REQUEST),
1349 @RestResponse(description = "", responseCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR) }, returnDescription = "")
1350 public Response addDCCatalog(@FormParam("mediaPackage") String mp, @FormParam("dublinCore") String dc,
1351 @FormParam("flavor") String flavor) {
1352 logger.trace("add DC catalog: {} with flavor: {} to media package: {}", dc, flavor, mp);
1353 MediaPackageElementFlavor dcFlavor = MediaPackageElements.EPISODE;
1354 if (flavor != null) {
1355 try {
1356 dcFlavor = MediaPackageElementFlavor.parseFlavor(flavor);
1357 } catch (IllegalArgumentException e) {
1358 return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
1359 }
1360 }
1361 MediaPackage mediaPackage;
1362
1363 try {
1364 mediaPackage = MediaPackageBuilderFactory.newInstance().newMediaPackageBuilder().loadFromXml(mp);
1365 } catch (MediaPackageException e) {
1366 return Response.serverError().status(Status.BAD_REQUEST).build();
1367 }
1368 if (MediaPackageSupport.sanityCheck(mediaPackage).isSome()) {
1369 return Response.status(Status.BAD_REQUEST).build();
1370 }
1371
1372
1373 try {
1374 DublinCoreXmlFormat.read(dc);
1375 } catch (Exception e) {
1376 return Response.status(Status.BAD_REQUEST).entity(e.getMessage()).build();
1377 }
1378
1379 try (InputStream in = IOUtils.toInputStream(dc, "UTF-8")) {
1380 mediaPackage = ingestService.addCatalog(in, "dublincore.xml", dcFlavor, mediaPackage);
1381 } catch (MediaPackageException e) {
1382 return Response.serverError().status(Status.BAD_REQUEST).entity(e.getMessage()).build();
1383 } catch (IOException e) {
1384 logger.error("Could not write catalog to disk", e);
1385 return Response.serverError().build();
1386 } catch (Exception e) {
1387 logger.error("Unable to add catalog", e);
1388 return Response.serverError().build();
1389 }
1390 return Response.ok(mediaPackage).build();
1391 }
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402 private Response badRequest(final String message, final Exception e) {
1403 logger.debug(message, e == null && logger.isDebugEnabled() ? new IngestException(message) : e);
1404 return Response.status(Status.BAD_REQUEST)
1405 .entity(message)
1406 .build();
1407 }
1408
1409 @Override
1410 public JobProducer getService() {
1411 return ingestService;
1412 }
1413
1414 @Override
1415 public ServiceRegistry getServiceRegistry() {
1416 return serviceRegistry;
1417 }
1418
1419
1420
1421
1422
1423
1424
1425 @Reference
1426 void setIngestService(IngestService ingestService) {
1427 this.ingestService = ingestService;
1428 }
1429
1430
1431
1432
1433
1434
1435
1436 @Reference
1437 void setServiceRegistry(ServiceRegistry serviceRegistry) {
1438 this.serviceRegistry = serviceRegistry;
1439 }
1440
1441
1442
1443
1444
1445
1446
1447 @Reference
1448 void setDublinCoreService(DublinCoreCatalogService dcService) {
1449 this.dublinCoreService = dcService;
1450 }
1451
1452
1453
1454
1455
1456
1457
1458 @Reference
1459 public void setHttpClient(TrustedHttpClient httpClient) {
1460 this.httpClient = httpClient;
1461 }
1462
1463 }