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