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.series.endpoint;
23
24 import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
25 import static javax.servlet.http.HttpServletResponse.SC_CREATED;
26 import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
27 import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
28 import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
29 import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
30 import static javax.servlet.http.HttpServletResponse.SC_OK;
31 import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
32 import static javax.ws.rs.core.Response.Status.BAD_REQUEST;
33 import static javax.ws.rs.core.Response.Status.CREATED;
34 import static javax.ws.rs.core.Response.Status.INTERNAL_SERVER_ERROR;
35 import static javax.ws.rs.core.Response.Status.NOT_FOUND;
36 import static javax.ws.rs.core.Response.Status.NO_CONTENT;
37 import static org.opencastproject.metadata.dublincore.DublinCore.PROPERTY_IDENTIFIER;
38 import static org.opencastproject.util.doc.rest.RestParameter.Type.STRING;
39 import static org.opencastproject.util.doc.rest.RestParameter.Type.TEXT;
40
41 import org.opencastproject.mediapackage.EName;
42 import org.opencastproject.metadata.dublincore.DublinCore;
43 import org.opencastproject.metadata.dublincore.DublinCoreCatalog;
44 import org.opencastproject.metadata.dublincore.DublinCoreCatalogService;
45 import org.opencastproject.metadata.dublincore.DublinCores;
46 import org.opencastproject.rest.RestConstants;
47 import org.opencastproject.security.api.AccessControlList;
48 import org.opencastproject.security.api.AccessControlParser;
49 import org.opencastproject.security.api.SecurityService;
50 import org.opencastproject.security.api.UnauthorizedException;
51 import org.opencastproject.series.api.Series;
52 import org.opencastproject.series.api.SeriesException;
53 import org.opencastproject.series.api.SeriesService;
54 import org.opencastproject.systems.OpencastConstants;
55 import org.opencastproject.util.NotFoundException;
56 import org.opencastproject.util.RestUtil.R;
57 import org.opencastproject.util.UrlSupport;
58 import org.opencastproject.util.doc.rest.RestParameter;
59 import org.opencastproject.util.doc.rest.RestParameter.Type;
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
64 import com.google.gson.Gson;
65 import com.google.gson.JsonArray;
66 import com.google.gson.JsonPrimitive;
67
68 import org.apache.commons.io.IOUtils;
69 import org.apache.commons.lang3.StringUtils;
70 import org.json.simple.JSONArray;
71 import org.json.simple.JSONObject;
72 import org.osgi.service.component.ComponentContext;
73 import org.osgi.service.component.annotations.Activate;
74 import org.osgi.service.component.annotations.Component;
75 import org.osgi.service.component.annotations.Reference;
76 import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource;
77 import org.slf4j.Logger;
78 import org.slf4j.LoggerFactory;
79
80 import java.io.ByteArrayInputStream;
81 import java.io.IOException;
82 import java.io.InputStream;
83 import java.io.UnsupportedEncodingException;
84 import java.net.URI;
85 import java.nio.charset.StandardCharsets;
86 import java.util.Date;
87 import java.util.List;
88 import java.util.Map;
89 import java.util.Optional;
90
91 import javax.servlet.http.HttpServletRequest;
92 import javax.ws.rs.DELETE;
93 import javax.ws.rs.DefaultValue;
94 import javax.ws.rs.FormParam;
95 import javax.ws.rs.GET;
96 import javax.ws.rs.POST;
97 import javax.ws.rs.PUT;
98 import javax.ws.rs.Path;
99 import javax.ws.rs.PathParam;
100 import javax.ws.rs.Produces;
101 import javax.ws.rs.WebApplicationException;
102 import javax.ws.rs.core.Context;
103 import javax.ws.rs.core.MediaType;
104 import javax.ws.rs.core.Response;
105
106
107
108
109
110 @Path("/series")
111 @RestService(
112 name = "seriesservice",
113 title = "Series Service",
114 abstractText = "This service creates, edits and retrieves and helps managing series.",
115 notes = {
116 "All paths above are relative to the REST endpoint base (something like http://your.server/files)",
117 "If the service is down or not working it will return a status 503, this means the the "
118 + "underlying service is not working and is either restarting or has failed",
119 "A status code 500 means a general failure has occurred which is not recoverable and was "
120 + "not anticipated. In other words, there is a bug! You should file an error report "
121 + "with your server logs from the time when the error occurred: "
122 + "<a href=\"https://github.com/opencast/opencast/issues\">Opencast Issue Tracker</a>"
123 }
124 )
125 @Component(
126 immediate = true,
127 service = SeriesRestService.class,
128 property = {
129 "service.description=Series REST Endpoint",
130 "opencast.service.type=org.opencastproject.series",
131 "opencast.service.path=/series"
132 }
133 )
134 @JaxrsResource
135 public class SeriesRestService {
136
137 private static final String SERIES_ELEMENT_CONTENT_TYPE_PREFIX = "series/";
138
139 private static final Gson gson = new Gson();
140
141
142 private static final Logger logger = LoggerFactory.getLogger(SeriesRestService.class);
143
144
145 private SeriesService seriesService;
146
147
148 private DublinCoreCatalogService dcService;
149
150
151 private SecurityService securityService;
152
153
154 protected String serverUrl = "http://localhost:8080";
155
156
157 protected String serviceUrl = null;
158
159
160 private static final int DEFAULT_LIMIT = 20;
161
162
163 public static final String DESCENDING_SUFFIX = "_DESC";
164
165 private static final String SAMPLE_DUBLIN_CORE = "<?xml version=\"1.0\"?>\n"
166 + "<dublincore xmlns=\"http://www.opencastproject.org/xsd/1.0/dublincore/\" "
167 + " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n"
168 + " xsi:schemaLocation=\"http://www.opencastproject.org http://www.opencastproject.org/schema.xsd\" "
169 + " xmlns:dc=\"http://purl.org/dc/elements/1.1/\"\n"
170 + " xmlns:dcterms=\"http://purl.org/dc/terms/\" "
171 + " xmlns:oc=\"http://www.opencastproject.org/matterhorn/\">\n\n"
172 + " <dcterms:title xml:lang=\"en\">\n"
173 + " Land and Vegetation: Key players on the Climate Scene\n"
174 + " </dcterms:title>\n"
175 + " <dcterms:subject>"
176 + " climate, land, vegetation\n"
177 + " </dcterms:subject>\n"
178 + " <dcterms:description xml:lang=\"en\">\n"
179 + " Introduction lecture from the Institute for\n"
180 + " Atmospheric and Climate Science.\n"
181 + " </dcterms:description>\n"
182 + " <dcterms:publisher>\n"
183 + " ETH Zurich, Switzerland\n"
184 + " </dcterms:publisher>\n"
185 + " <dcterms:identifier>\n"
186 + " 10.0000/5819\n"
187 + " </dcterms:identifier>\n"
188 + " <dcterms:modified xsi:type=\"dcterms:W3CDTF\">\n"
189 + " 2007-12-05\n"
190 + " </dcterms:modified>\n"
191 + " <dcterms:format xsi:type=\"dcterms:IMT\">\n"
192 + " video/x-dv\n"
193 + " </dcterms:format>\n"
194 + "</dublincore>";
195
196 private static final String SAMPLE_ACCESS_CONTROL_LIST =
197 "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n"
198 + "<acl xmlns=\"http://org.opencastproject.security\">\n"
199 + " <ace>\n"
200 + " <role>admin</role>\n"
201 + " <action>delete</action>\n"
202 + " <allow>true</allow>\n"
203 + " </ace>\n"
204 + "</acl>";
205
206
207
208
209
210
211 @Reference
212 public void setService(SeriesService seriesService) {
213 this.seriesService = seriesService;
214 }
215
216
217
218
219
220
221 @Reference
222 public void setDublinCoreService(DublinCoreCatalogService dcService) {
223 this.dcService = dcService;
224 }
225
226
227 @Reference
228 public void setSecurityService(SecurityService securityService) {
229 this.securityService = securityService;
230 }
231
232
233
234
235
236
237
238
239 @Activate
240 public void activate(ComponentContext cc) {
241 if (cc == null) {
242 this.serverUrl = "http://localhost:8080";
243 } else {
244 String ccServerUrl = cc.getBundleContext().getProperty(OpencastConstants.SERVER_URL_PROPERTY);
245 logger.debug("Configured server url is {}", ccServerUrl);
246 if (ccServerUrl == null) {
247 this.serverUrl = "http://localhost:8080";
248 } else {
249 this.serverUrl = ccServerUrl;
250 }
251 }
252 serviceUrl = (String) cc.getProperties().get(RestConstants.SERVICE_PATH_PROPERTY);
253 }
254
255 public String getSeriesXmlUrl(String seriesId) {
256 return UrlSupport.concat(serverUrl, serviceUrl, seriesId + ".xml");
257 }
258
259 public String getSeriesJsonUrl(String seriesId) {
260 return UrlSupport.concat(serverUrl, serviceUrl, seriesId + ".json");
261 }
262
263 @GET
264 @Produces(MediaType.TEXT_XML)
265 @Path("{seriesID:.+}.xml")
266 @RestQuery(
267 name = "getAsXml",
268 description = "Returns the series with the given identifier",
269 returnDescription = "Returns the series dublin core XML document",
270 pathParameters = {
271 @RestParameter(name = "seriesID", isRequired = true, description = "The series identifier", type = STRING)
272 },
273 responses = {
274 @RestResponse(responseCode = SC_OK, description = "The series dublin core."),
275 @RestResponse(responseCode = SC_NOT_FOUND, description = "No series with this identifier was found."),
276 @RestResponse(responseCode = SC_FORBIDDEN, description = "You do not have permission to view this series."),
277 @RestResponse(
278 responseCode = SC_UNAUTHORIZED,
279 description = "You do not have permission to view this series. Maybe you need to authenticate."
280 )
281 }
282 )
283 public Response getSeriesXml(@PathParam("seriesID") String seriesID) {
284 logger.debug("Series Lookup: {}", seriesID);
285 try {
286 DublinCoreCatalog dc = this.seriesService.getSeries(seriesID);
287 return Response.ok(dc.toXmlString()).build();
288 } catch (NotFoundException e) {
289 return Response.status(Response.Status.NOT_FOUND).build();
290 } catch (UnauthorizedException e) {
291 logger.warn("permission exception retrieving series");
292
293 throw new WebApplicationException(Response.Status.UNAUTHORIZED);
294 } catch (Exception e) {
295 logger.error("Could not retrieve series: {}", e.getMessage());
296 throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
297 }
298 }
299
300 @GET
301 @Produces(MediaType.APPLICATION_JSON)
302 @Path("{seriesID:.+}.json")
303 @RestQuery(
304 name = "getAsJson",
305 description = "Returns the series with the given identifier",
306 returnDescription = "Returns the series dublin core JSON document",
307 pathParameters = {
308 @RestParameter(name = "seriesID", isRequired = true, description = "The series identifier", type = STRING)
309 },
310 responses = {
311 @RestResponse(responseCode = SC_OK, description = "The series dublin core."),
312 @RestResponse(responseCode = SC_NOT_FOUND, description = "No series with this identifier was found."),
313 @RestResponse(
314 responseCode = SC_UNAUTHORIZED,
315 description = "You do not have permission to view this series. Maybe you need to authenticate."
316 )
317 }
318 )
319 public Response getSeriesJSON(@PathParam("seriesID") String seriesID) {
320 logger.debug("Series Lookup: {}", seriesID);
321 try {
322 DublinCoreCatalog dc = this.seriesService.getSeries(seriesID);
323 return Response.ok(dc.toJson()).build();
324 } catch (NotFoundException e) {
325 return Response.status(Response.Status.NOT_FOUND).build();
326 } catch (UnauthorizedException e) {
327 logger.warn("permission exception retrieving series");
328
329 throw new WebApplicationException(Response.Status.UNAUTHORIZED);
330 } catch (Exception e) {
331 logger.error("Could not retrieve series: {}", e.getMessage());
332 throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
333 }
334 }
335
336 @GET
337 @Produces(MediaType.TEXT_XML)
338 @Path("/{seriesID:.+}/acl.xml")
339 @RestQuery(
340 name = "getAclAsXml",
341 description = "Returns the access control list for the series with the given identifier",
342 returnDescription = "Returns the series ACL as XML",
343 pathParameters = {
344 @RestParameter(name = "seriesID", isRequired = true, description = "The series identifier", type = STRING)
345 },
346 responses = {
347 @RestResponse(responseCode = SC_OK, description = "The access control list."),
348 @RestResponse(responseCode = SC_NOT_FOUND, description = "No series with this identifier was found.")
349 }
350 )
351 public Response getSeriesAccessControlListXml(@PathParam("seriesID") String seriesID) {
352 return getSeriesAccessControlList(seriesID);
353 }
354
355 @GET
356 @Produces(MediaType.APPLICATION_JSON)
357 @Path("/{seriesID:.+}/acl.json")
358 @RestQuery(
359 name = "getAclAsJson",
360 description = "Returns the access control list for the series with the given identifier",
361 returnDescription = "Returns the series ACL as JSON",
362 pathParameters = {
363 @RestParameter(name = "seriesID", isRequired = true, description = "The series identifier", type = STRING)
364 },
365 responses = {
366 @RestResponse(responseCode = SC_OK, description = "The access control list."),
367 @RestResponse(responseCode = SC_NOT_FOUND, description = "No series with this identifier was found.")
368 }
369 )
370 public Response getSeriesAccessControlListJson(@PathParam("seriesID") String seriesID) {
371 return getSeriesAccessControlList(seriesID);
372 }
373
374 @GET
375 @Produces(MediaType.APPLICATION_JSON)
376 @Path("/allInRangeAdministrative.json")
377 @RestQuery(
378 name = "allInRangeAdministrative",
379 description = "Internal API! Returns all series (included deleted ones!) in the given "
380 + "range 'from' (inclusive) .. 'to' (exclusive). Returns at most 'limit' many series. "
381 + "Can only be used as administrator!",
382 returnDescription = "Series in the range",
383 restParameters = {
384 @RestParameter(
385 name = "from",
386 isRequired = true,
387 description = "Start of date range (inclusive) in milliseconds "
388 + "since 1970-01-01T00:00:00Z. Has to be >=0.",
389 type = Type.INTEGER
390 ),
391 @RestParameter(
392 name = "to",
393 isRequired = false,
394
395 description = "End of date range (exclusive) in milliseconds "
396 + "since 1970-01-01T00:00:00Z. Has to be > 'from'.",
397 type = Type.INTEGER
398 ),
399 @RestParameter(
400 name = "limit",
401 isRequired = true,
402 description = "Maximum number of series to be returned. Has to be >0.",
403 type = Type.INTEGER
404 ),
405 },
406 responses = {
407 @RestResponse(responseCode = SC_OK, description = "All series in the range"),
408 @RestResponse(responseCode = SC_BAD_REQUEST, description = "if the given parameters are invalid"),
409 @RestResponse(responseCode = SC_UNAUTHORIZED, description = "If the user is not an administrator"),
410 }
411 )
412 public Response getAllInRangeAdministrative(
413 @FormParam("from") Long from,
414 @FormParam("to") Long to,
415 @FormParam("limit") Integer limit
416 ) throws UnauthorizedException {
417
418 if (from == null) {
419 return badRequestAllInRange("Required parameter 'from' not specified");
420 }
421 if (limit == null) {
422 return badRequestAllInRange("Required parameter 'limit' not specified");
423 }
424 if (from < 0) {
425 return badRequestAllInRange("Parameter 'from' < 0, but it has to be >= 0");
426 }
427 if (to != null && to <= from) {
428 return badRequestAllInRange("Parameter 'to' <= 'from', but that is not allowed");
429 }
430 if (limit <= 0) {
431 return badRequestAllInRange("Parameter 'limit' <= 0, but it has to be > 0");
432 }
433
434 try {
435 final List<Series> series = seriesService.getAllForAdministrativeRead(
436 new Date(from),
437 Optional.ofNullable(to).map(millis -> new Date(millis)),
438 limit);
439
440 return Response.ok(gson.toJson(series)).build();
441 } catch (SeriesException e) {
442 logger.error("Unexpected exception in getAllInRangeAdministrative", e);
443 return Response.status(INTERNAL_SERVER_ERROR)
444 .entity("internal server error")
445 .build();
446 }
447 }
448
449
450
451
452
453 private static Response badRequestAllInRange(String msg) {
454 logger.debug("Bad request to /series/allInRangeAdministrative: {}", msg);
455 return Response.status(BAD_REQUEST).entity(msg).build();
456 }
457
458
459
460
461
462
463
464
465 private Response getSeriesAccessControlList(String seriesID) {
466 logger.debug("Series ACL lookup: {}", seriesID);
467 try {
468 AccessControlList acl = seriesService.getSeriesAccessControl(seriesID);
469 return Response.ok(acl).build();
470 } catch (NotFoundException e) {
471 return Response.status(NOT_FOUND).build();
472 } catch (SeriesException e) {
473 logger.error("Could not retrieve series ACL: {}", e.getMessage());
474 }
475 throw new WebApplicationException(INTERNAL_SERVER_ERROR);
476 }
477
478 private void addDcData(final DublinCoreCatalog dc, final String field, final String value) {
479 if (StringUtils.isNotBlank(value)) {
480 EName en = new EName(DublinCore.TERMS_NS_URI, field);
481 dc.add(en, value);
482 }
483 }
484
485 @POST
486 @Path("/")
487 @RestQuery(
488 name = "updateSeries",
489 description = "Updates a series",
490 returnDescription = "No content.",
491 restParameters = {
492 @RestParameter(
493 name = "series",
494 isRequired = false,
495 defaultValue = SAMPLE_DUBLIN_CORE,
496 description = "The series document. Will take precedence over metadata fields",
497 type = TEXT
498 ),
499 @RestParameter(
500 name = "acl",
501 isRequired = false,
502 defaultValue = SAMPLE_ACCESS_CONTROL_LIST,
503 description = "The access control list for the series",
504 type = TEXT
505 ),
506 @RestParameter(
507 description = "Series metadata value",
508 isRequired = false,
509 name = "accessRights",
510 type = RestParameter.Type.STRING
511 ),
512 @RestParameter(
513 description = "Series metadata value",
514 isRequired = false,
515 name = "available",
516 type = RestParameter.Type.STRING
517 ),
518 @RestParameter(
519 description = "Series metadata value",
520 isRequired = false,
521 name = "contributor",
522 type = RestParameter.Type.STRING
523 ),
524 @RestParameter(
525 description = "Series metadata value",
526 isRequired = false,
527 name = "coverage",
528 type = RestParameter.Type.STRING
529 ),
530 @RestParameter(
531 description = "Series metadata value",
532 isRequired = false,
533 name = "created",
534 type = RestParameter.Type.STRING
535 ),
536 @RestParameter(
537 description = "Series metadata value",
538 isRequired = false,
539 name = "creator",
540 type = RestParameter.Type.STRING
541 ),
542 @RestParameter(
543 description = "Series metadata value",
544 isRequired = false,
545 name = "date",
546 type = RestParameter.Type.STRING
547 ),
548 @RestParameter(
549 description = "Series metadata value",
550 isRequired = false,
551 name = "description",
552 type = RestParameter.Type.STRING
553 ),
554 @RestParameter(
555 description = "Series metadata value",
556 isRequired = false,
557 name = "extent",
558 type = RestParameter.Type.STRING
559 ),
560 @RestParameter(
561 description = "Series metadata value",
562 isRequired = false,
563 name = "format",
564 type = RestParameter.Type.STRING
565 ),
566 @RestParameter(
567 description = "Series metadata value",
568 isRequired = false,
569 name = "identifier",
570 type = RestParameter.Type.STRING
571 ),
572 @RestParameter(
573 description = "Series metadata value",
574 isRequired = false,
575 name = "isPartOf",
576 type = RestParameter.Type.STRING
577 ),
578 @RestParameter(
579 description = "Series metadata value",
580 isRequired = false,
581 name = "isReferencedBy",
582 type = RestParameter.Type.STRING
583 ),
584 @RestParameter(
585 description = "Series metadata value",
586 isRequired = false,
587 name = "isReplacedBy",
588 type = RestParameter.Type.STRING
589 ),
590 @RestParameter(
591 description = "Series metadata value",
592 isRequired = false,
593 name = "language",
594 type = RestParameter.Type.STRING
595 ),
596 @RestParameter(
597 description = "Series metadata value",
598 isRequired = false,
599 name = "license",
600 type = RestParameter.Type.STRING
601 ),
602 @RestParameter(
603 description = "Series metadata value",
604 isRequired = false,
605 name = "publisher",
606 type = RestParameter.Type.STRING
607 ),
608 @RestParameter(
609 description = "Series metadata value",
610 isRequired = false,
611 name = "relation",
612 type = RestParameter.Type.STRING
613 ),
614 @RestParameter(
615 description = "Series metadata value",
616 isRequired = false,
617 name = "replaces",
618 type = RestParameter.Type.STRING
619 ),
620 @RestParameter(
621 description = "Series metadata value",
622 isRequired = false,
623 name = "rights",
624 type = RestParameter.Type.STRING
625 ),
626 @RestParameter(
627 description = "Series metadata value",
628 isRequired = false,
629 name = "rightsHolder",
630 type = RestParameter.Type.STRING
631 ),
632 @RestParameter(
633 description = "Series metadata value",
634 isRequired = false,
635 name = "source",
636 type = RestParameter.Type.STRING
637 ),
638 @RestParameter(
639 description = "Series metadata value",
640 isRequired = false,
641 name = "spatial",
642 type = RestParameter.Type.STRING
643 ),
644 @RestParameter(
645 description = "Series metadata value",
646 isRequired = false,
647 name = "subject",
648 type = RestParameter.Type.STRING
649 ),
650 @RestParameter(
651 description = "Series metadata value",
652 isRequired = false,
653 name = "temporal",
654 type = RestParameter.Type.STRING
655 ),
656 @RestParameter(
657 description = "Series metadata value",
658 isRequired = false,
659 name = "title",
660 type = RestParameter.Type.STRING
661 ),
662 @RestParameter(
663 description = "Series metadata value",
664 isRequired = false,
665 name = "type",
666 type = RestParameter.Type.STRING
667 ),
668 @RestParameter(
669 name = "override",
670 isRequired = false,
671 defaultValue = "false",
672 description = "If true the series ACL will take precedence over any existing episode ACL",
673 type = STRING
674 )
675 },
676 responses = {
677 @RestResponse(
678 responseCode = SC_BAD_REQUEST,
679 description = "The required form params were missing in the request."
680 ),
681 @RestResponse(
682 responseCode = SC_NO_CONTENT,
683 description = "The access control list has been updated."
684 ),
685 @RestResponse(
686 responseCode = SC_UNAUTHORIZED,
687 description = "If the current user is not authorized to perform this action"
688 ),
689 @RestResponse(
690 responseCode = SC_CREATED,
691 description = "The access control list has been created."
692 )
693 }
694 )
695 public Response addOrUpdateSeries(
696 @FormParam("series") String series,
697 @FormParam("acl") String accessControl,
698 @FormParam("accessRights") String dcAccessRights,
699 @FormParam("available") String dcAvailable,
700 @FormParam("contributor") String dcContributor,
701 @FormParam("coverage") String dcCoverage,
702 @FormParam("created") String dcCreated,
703 @FormParam("creator") String dcCreator,
704 @FormParam("date") String dcDate,
705 @FormParam("description") String dcDescription,
706 @FormParam("extent") String dcExtent,
707 @FormParam("format") String dcFormat,
708 @FormParam("identifier") String dcIdentifier,
709 @FormParam("isPartOf") String dcIsPartOf,
710 @FormParam("isReferencedBy") String dcIsReferencedBy,
711 @FormParam("isReplacedBy") String dcIsReplacedBy,
712 @FormParam("language") String dcLanguage,
713 @FormParam("license") String dcLicense,
714 @FormParam("publisher") String dcPublisher,
715 @FormParam("relation") String dcRelation,
716 @FormParam("replaces") String dcReplaces,
717 @FormParam("rights") String dcRights,
718 @FormParam("rightsHolder") String dcRightsHolder,
719 @FormParam("source") String dcSource,
720 @FormParam("spatial") String dcSpatial,
721 @FormParam("subject") String dcSubject,
722 @FormParam("temporal") String dcTemporal,
723 @FormParam("title") String dcTitle,
724 @FormParam("type") String dcType,
725 @DefaultValue("false") @FormParam("override") boolean override
726 ) throws UnauthorizedException {
727 DublinCoreCatalog dc;
728 if (StringUtils.isNotBlank(series)) {
729 try {
730 dc = this.dcService.load(new ByteArrayInputStream(series.getBytes(StandardCharsets.UTF_8)));
731 } catch (UnsupportedEncodingException e1) {
732 logger.error("Could not deserialize dublin core catalog", e1);
733 throw new WebApplicationException(INTERNAL_SERVER_ERROR);
734 } catch (IOException e1) {
735 logger.warn("Could not deserialize dublin core catalog", e1);
736 return Response.status(BAD_REQUEST).build();
737 }
738 } else if (StringUtils.isNotBlank(dcTitle)) {
739 dc = DublinCores.mkOpencastSeries().getCatalog();
740 addDcData(dc, "accessRights", dcAccessRights);
741 addDcData(dc, "available", dcAvailable);
742 addDcData(dc, "contributor", dcContributor);
743 addDcData(dc, "coverage", dcCoverage);
744 addDcData(dc, "created", dcCreated);
745 addDcData(dc, "creator", dcCreator);
746 addDcData(dc, "date", dcDate);
747 addDcData(dc, "description", dcDescription);
748 addDcData(dc, "extent", dcExtent);
749 addDcData(dc, "format", dcFormat);
750 addDcData(dc, "identifier", dcIdentifier);
751 addDcData(dc, "isPartOf", dcIsPartOf);
752 addDcData(dc, "isReferencedBy", dcIsReferencedBy);
753 addDcData(dc, "isReplacedBy", dcIsReplacedBy);
754 addDcData(dc, "language", dcLanguage);
755 addDcData(dc, "license", dcLicense);
756 addDcData(dc, "publisher", dcPublisher);
757 addDcData(dc, "relation", dcRelation);
758 addDcData(dc, "replaces", dcReplaces);
759 addDcData(dc, "rights", dcRights);
760 addDcData(dc, "rightsHolder", dcRightsHolder);
761 addDcData(dc, "source", dcSource);
762 addDcData(dc, "spatial", dcSpatial);
763 addDcData(dc, "subject", dcSubject);
764 addDcData(dc, "temporal", dcTemporal);
765 addDcData(dc, "title", dcTitle);
766 addDcData(dc, "type", dcType);
767 } else {
768 return Response.status(BAD_REQUEST).entity("Required series metadata not provided").build();
769 }
770 AccessControlList acl = null;
771 if (StringUtils.isNotBlank(accessControl)) {
772 try {
773 acl = AccessControlParser.parseAcl(accessControl);
774 } catch (Exception e) {
775 logger.debug("Could not parse ACL", e);
776 return Response.status(BAD_REQUEST).entity("Could not parse ACL").build();
777 }
778 }
779
780 try {
781 DublinCoreCatalog newSeries = seriesService.updateSeries(dc);
782 if (acl != null) {
783 seriesService.updateAccessControl(dc.getFirst(PROPERTY_IDENTIFIER), acl, override);
784 }
785 if (newSeries == null) {
786 logger.debug("Updated series {} ", dc.getFirst(PROPERTY_IDENTIFIER));
787 return Response.status(NO_CONTENT).build();
788 }
789 String id = newSeries.getFirst(PROPERTY_IDENTIFIER);
790 logger.debug("Created series {} ", id);
791 return Response.status(CREATED)
792 .header("Location", getSeriesXmlUrl(id))
793 .header("Location", getSeriesJsonUrl(id))
794 .entity(newSeries.toXmlString())
795 .build();
796 } catch (UnauthorizedException e) {
797 throw e;
798 } catch (Exception e) {
799 logger.error("Could not add/update series", e);
800 }
801 return Response.serverError().build();
802 }
803
804 @POST
805 @Path("/{seriesID:.+}/accesscontrol")
806 @RestQuery(
807 name = "updateAcl",
808 description = "Updates the access control list for a series",
809 returnDescription = "No content.",
810 restParameters = {
811 @RestParameter(
812 name = "acl",
813 isRequired = true,
814 defaultValue = SAMPLE_ACCESS_CONTROL_LIST,
815 description = "The access control list for the series",
816 type = TEXT
817 ),
818 @RestParameter(
819 name = "override",
820 isRequired = false,
821 defaultValue = "false",
822 description = "If true the series ACL will take precedence over any existing episode ACL",
823 type = STRING
824 )
825 },
826 pathParameters = {
827 @RestParameter(name = "seriesID", isRequired = true, description = "The series identifier", type = STRING)
828 },
829 responses = {
830 @RestResponse(
831 responseCode = SC_NOT_FOUND,
832 description = "No series with this identifier was found."
833 ),
834 @RestResponse(
835 responseCode = SC_NO_CONTENT,
836 description = "The access control list has been updated."
837 ),
838 @RestResponse(
839 responseCode = SC_CREATED,
840 description = "The access control list has been created."
841 ),
842 @RestResponse(
843 responseCode = SC_UNAUTHORIZED,
844 description = "If the current user is not authorized to perform this action"
845 ),
846 @RestResponse(
847 responseCode = SC_BAD_REQUEST,
848 description = "The required path or form params were missing in the request."
849 )
850 }
851 )
852 public Response updateAccessControl(
853 @PathParam("seriesID") String seriesID,
854 @FormParam("acl") String accessControl,
855 @DefaultValue("false") @FormParam("override") boolean override
856 ) throws UnauthorizedException {
857 if (accessControl == null) {
858 logger.warn("Access control parameter is null.");
859 return Response.status(BAD_REQUEST).build();
860 }
861 AccessControlList acl;
862 try {
863 acl = AccessControlParser.parseAcl(accessControl);
864 } catch (Exception e) {
865 logger.warn("Could not parse ACL: {}", e.getMessage());
866 return Response.status(BAD_REQUEST).build();
867 }
868 try {
869 boolean updated = seriesService.updateAccessControl(seriesID, acl, override);
870 if (updated) {
871 return Response.status(NO_CONTENT).build();
872 }
873 return Response.status(CREATED).build();
874 } catch (NotFoundException e) {
875 return Response.status(NOT_FOUND).build();
876 } catch (SeriesException e) {
877 logger.warn("Could not update ACL for {}: {}", seriesID, e.getMessage());
878 }
879 throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
880 }
881
882 @DELETE
883 @Path("/{seriesID:.+}")
884 @RestQuery(
885 name = "delete",
886 description = "Delete a series",
887 returnDescription = "No content.",
888 pathParameters = {
889 @RestParameter(name = "seriesID", isRequired = true, description = "The series identifier", type = STRING)
890 },
891 responses = {
892 @RestResponse(
893 responseCode = SC_NOT_FOUND,
894 description = "No series with this identifier was found."
895 ),
896 @RestResponse(
897 responseCode = SC_UNAUTHORIZED,
898 description = "If the current user is not authorized to perform this action"
899 ),
900 @RestResponse(
901 responseCode = SC_NO_CONTENT,
902 description = "The series was deleted."
903 )
904 }
905 )
906 public Response deleteSeries(@PathParam("seriesID") String seriesID) throws UnauthorizedException {
907 try {
908 this.seriesService.deleteSeries(seriesID);
909 return Response.ok().build();
910 } catch (NotFoundException e) {
911 return Response.status(Response.Status.NOT_FOUND).build();
912 } catch (SeriesException se) {
913 logger.warn("Could not delete series {}: {}", seriesID, se.getMessage());
914 }
915 throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
916 }
917
918 @GET
919 @Produces(MediaType.TEXT_PLAIN)
920 @Path("/count")
921 @RestQuery(
922 name = "count",
923 description = "Returns the number of series",
924 returnDescription = "Returns the number of series",
925 responses = {
926 @RestResponse(responseCode = SC_OK, description = "The number of series")
927 }
928 )
929 public Response getCount() throws UnauthorizedException {
930 try {
931 int count = seriesService.getSeriesCount();
932 return Response.ok(count).build();
933 } catch (SeriesException se) {
934 logger.warn("Could not count series: {}", se.getMessage());
935 throw new WebApplicationException(se);
936 }
937 }
938
939 @SuppressWarnings("unchecked")
940 @GET
941 @Produces(MediaType.APPLICATION_JSON)
942 @Path("{id}/properties.json")
943 @RestQuery(
944 name = "getSeriesProperties",
945 description = "Returns the series properties",
946 returnDescription = "Returns the series properties as JSON",
947 pathParameters = {
948 @RestParameter(name = "id", description = "ID of series", isRequired = true, type = Type.STRING)
949 },
950 responses = {
951 @RestResponse(
952 responseCode = SC_OK,
953 description = "The access control list."
954 ),
955 @RestResponse(
956 responseCode = SC_UNAUTHORIZED,
957 description = "If the current user is not authorized to perform this action"
958 )
959 }
960 )
961 public Response getSeriesPropertiesAsJson(@PathParam("id") String seriesId)
962 throws UnauthorizedException, NotFoundException {
963 if (StringUtils.isBlank(seriesId)) {
964 logger.warn("Series id parameter is blank '{}'.", seriesId);
965 return Response.status(BAD_REQUEST).build();
966 }
967 try {
968 Map<String, String> properties = seriesService.getSeriesProperties(seriesId);
969 JSONArray jsonProperties = new JSONArray();
970 for (String name : properties.keySet()) {
971 JSONObject property = new JSONObject();
972 property.put(name, properties.get(name));
973 jsonProperties.add(property);
974 }
975 return Response.ok(jsonProperties.toString()).build();
976 } catch (UnauthorizedException e) {
977 throw e;
978 } catch (NotFoundException e) {
979 throw e;
980 } catch (Exception e) {
981 logger.warn("Could not perform search query: {}", e.getMessage());
982 }
983 throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
984 }
985
986 @GET
987 @Produces(MediaType.APPLICATION_JSON)
988 @Path("{seriesId}/property/{propertyName}.json")
989 @RestQuery(
990 name = "getSeriesProperty",
991 description = "Returns a series property value",
992 returnDescription = "Returns the series property value",
993 pathParameters = {
994 @RestParameter(
995 name = "seriesId",
996 description = "ID of series",
997 isRequired = true,
998 type = Type.STRING
999 ),
1000 @RestParameter(
1001 name = "propertyName",
1002 description = "Name of series property",
1003 isRequired = true,
1004 type = Type.STRING
1005 )
1006 },
1007 responses = {
1008 @RestResponse(
1009 responseCode = SC_OK,
1010 description = "The access control list."
1011 ),
1012 @RestResponse(
1013 responseCode = SC_UNAUTHORIZED,
1014 description = "If the current user is not authorized to perform this action"
1015 )
1016 }
1017 )
1018 public Response getSeriesProperty(@PathParam("seriesId") String seriesId,
1019 @PathParam("propertyName") String propertyName) throws UnauthorizedException, NotFoundException {
1020 if (StringUtils.isBlank(seriesId)) {
1021 logger.warn("Series id parameter is blank '{}'.", seriesId);
1022 return Response.status(BAD_REQUEST).build();
1023 }
1024 if (StringUtils.isBlank(propertyName)) {
1025 logger.warn("Series property name parameter is blank '{}'.", propertyName);
1026 return Response.status(BAD_REQUEST).build();
1027 }
1028 try {
1029 String propertyValue = seriesService.getSeriesProperty(seriesId, propertyName);
1030 return Response.ok(propertyValue).build();
1031 } catch (UnauthorizedException e) {
1032 throw e;
1033 } catch (NotFoundException e) {
1034 throw e;
1035 } catch (Exception e) {
1036 logger.warn("Could not perform search query", e);
1037 }
1038 throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
1039 }
1040
1041 @POST
1042 @Path("/{seriesId}/property")
1043 @RestQuery(
1044 name = "updateSeriesProperty",
1045 description = "Updates a series property",
1046 returnDescription = "No content.",
1047 restParameters = {
1048 @RestParameter(name = "name", isRequired = true, description = "The property's name", type = TEXT),
1049 @RestParameter(name = "value", isRequired = true, description = "The property's value", type = TEXT)
1050 },
1051 pathParameters = {
1052 @RestParameter(name = "seriesId", isRequired = true, description = "The series identifier", type = STRING)
1053 },
1054 responses = {
1055 @RestResponse(
1056 responseCode = SC_NOT_FOUND,
1057 description = "No series with this identifier was found."
1058 ),
1059 @RestResponse(
1060 responseCode = SC_NO_CONTENT,
1061 description = "The access control list has been updated."
1062 ),
1063 @RestResponse(
1064 responseCode = SC_UNAUTHORIZED,
1065 description = "If the current user is not authorized to perform this action"
1066 ),
1067 @RestResponse(
1068 responseCode = SC_BAD_REQUEST,
1069 description = "The required path or form params were missing in the request."
1070 )
1071 }
1072 )
1073 public Response updateSeriesProperty(
1074 @PathParam("seriesId") String seriesId,
1075 @FormParam("name") String name,
1076 @FormParam("value") String value
1077 ) throws UnauthorizedException {
1078 if (StringUtils.isBlank(seriesId)) {
1079 logger.warn("Series id parameter is blank '{}'.", seriesId);
1080 return Response.status(BAD_REQUEST).build();
1081 }
1082 if (StringUtils.isBlank(name)) {
1083 logger.warn("Name parameter is blank '{}'.", name);
1084 return Response.status(BAD_REQUEST).build();
1085 }
1086 if (StringUtils.isBlank(value)) {
1087 logger.warn("Series id parameter is blank '{}'.", value);
1088 return Response.status(BAD_REQUEST).build();
1089 }
1090 try {
1091 seriesService.updateSeriesProperty(seriesId, name, value);
1092 return Response.status(NO_CONTENT).build();
1093 } catch (NotFoundException e) {
1094 return Response.status(NOT_FOUND).build();
1095 } catch (SeriesException e) {
1096 logger.warn("Could not update series property for series {} property {}:{} :", seriesId, name, value, e);
1097 }
1098 throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
1099 }
1100
1101 @DELETE
1102 @Path("{seriesId}/property/{propertyName}")
1103 @RestQuery(
1104 name = "deleteSeriesProperty",
1105 description = "Deletes a series property",
1106 returnDescription = "No Content",
1107 pathParameters = {
1108 @RestParameter(
1109 name = "seriesId",
1110 description = "ID of series",
1111 isRequired = true,
1112 type = Type.STRING
1113 ),
1114 @RestParameter(
1115 name = "propertyName",
1116 description = "Name of series property",
1117 isRequired = true,
1118 type = Type.STRING
1119 )
1120 },
1121 responses = {
1122 @RestResponse(
1123 responseCode = SC_NO_CONTENT,
1124 description = "The series property has been deleted."
1125 ),
1126 @RestResponse(
1127 responseCode = SC_NOT_FOUND,
1128 description = "The series or property has not been found."
1129 ),
1130 @RestResponse(
1131 responseCode = SC_UNAUTHORIZED,
1132 description = "If the current user is not authorized to perform this action"
1133 )
1134 }
1135 )
1136 public Response deleteSeriesProperty(
1137 @PathParam("seriesId") String seriesId,
1138 @PathParam("propertyName") String propertyName
1139 ) throws UnauthorizedException, NotFoundException {
1140 if (StringUtils.isBlank(seriesId)) {
1141 logger.warn("Series id parameter is blank '{}'.", seriesId);
1142 return Response.status(BAD_REQUEST).build();
1143 }
1144 if (StringUtils.isBlank(propertyName)) {
1145 logger.warn("Series property name parameter is blank '{}'.", propertyName);
1146 return Response.status(BAD_REQUEST).build();
1147 }
1148 try {
1149 seriesService.deleteSeriesProperty(seriesId, propertyName);
1150 return Response.status(NO_CONTENT).build();
1151 } catch (UnauthorizedException e) {
1152 throw e;
1153 } catch (NotFoundException e) {
1154 throw e;
1155 } catch (Exception e) {
1156 logger.warn("Could not delete series '{}' property '{}' query:", seriesId, propertyName, e);
1157 }
1158 throw new WebApplicationException(Response.Status.INTERNAL_SERVER_ERROR);
1159 }
1160
1161 @GET
1162 @Path("{seriesId}/elements.json")
1163 @Produces(MediaType.APPLICATION_JSON)
1164 @RestQuery(
1165 name = "getSeriesElements",
1166 description = "Returns all the element types of a series",
1167 returnDescription = "Returns a JSON array with all the types of elements of the given series.",
1168 pathParameters = {
1169 @RestParameter(name = "seriesId", description = "The series identifier", type = STRING, isRequired = true)
1170 },
1171 responses = {
1172 @RestResponse(responseCode = SC_OK, description = "Series found"),
1173 @RestResponse(responseCode = SC_NOT_FOUND, description = "Series not found"),
1174 @RestResponse(responseCode = SC_INTERNAL_SERVER_ERROR, description = "Error while processing the request")
1175 }
1176 )
1177 public Response getSeriesElements(@PathParam("seriesId") String seriesId) {
1178 try {
1179 Optional<Map<String, byte[]>> optSeriesElements = seriesService.getSeriesElements(seriesId);
1180 if (optSeriesElements.isPresent()) {
1181 Map<String, byte[]> seriesElements = optSeriesElements.get();
1182 JsonArray jsonArray = new JsonArray();
1183 for (String key : seriesElements.keySet()) {
1184 jsonArray.add(new JsonPrimitive(key));
1185 }
1186 String json = new Gson().toJson(jsonArray);
1187 return Response.ok(json).build();
1188 } else {
1189 return R.notFound();
1190 }
1191 } catch (SeriesException e) {
1192 logger.warn("Error while retrieving elements for sieres '{}'", seriesId, e);
1193 return R.serverError();
1194 }
1195 }
1196
1197 @GET
1198 @Path("{seriesId}/elements/{elementType}")
1199 @RestQuery(
1200 name = "getSeriesElement",
1201 description = "Returns the series element",
1202 returnDescription = "The data of the series element",
1203 pathParameters = {
1204 @RestParameter(
1205 name = "seriesId",
1206 description = "The series identifier",
1207 type = STRING,
1208 isRequired = true
1209 ),
1210 @RestParameter(
1211 name = "elementType",
1212 description = "The element type. This is equal to the subtype of the media type of "
1213 + "this element: series/<elementtype>",
1214 type = STRING,
1215 isRequired = true
1216 )
1217 },
1218 responses = {
1219 @RestResponse(responseCode = SC_OK, description = "Series element found"),
1220 @RestResponse(responseCode = SC_NOT_FOUND, description = "Series element not found"),
1221 @RestResponse(responseCode = SC_INTERNAL_SERVER_ERROR, description = "Error while processing the request")
1222 }
1223 )
1224 public Response getSeriesElement(
1225 @PathParam("seriesId") String seriesId,
1226 @PathParam("elementType") String elementType
1227 ) {
1228 try {
1229 Optional<byte[]> data = seriesService.getSeriesElementData(seriesId, elementType);
1230 if (data.isPresent()) {
1231 return Response.ok().entity(new ByteArrayInputStream(data.get()))
1232 .type(SERIES_ELEMENT_CONTENT_TYPE_PREFIX + elementType).build();
1233 } else {
1234 return R.notFound();
1235 }
1236 } catch (SeriesException e) {
1237 logger.warn("Error while returning element '{}' of series '{}':", elementType, seriesId, e);
1238 return R.serverError();
1239 }
1240 }
1241
1242 @PUT
1243 @Path("{seriesId}/extendedMetadata/{type}")
1244 @RestQuery(
1245 name = "updateExtendedMetadata",
1246 description = "Updates extended metadata of a series",
1247 returnDescription = "An empty response",
1248 pathParameters = {
1249 @RestParameter(name = "seriesId", description = "The series identifier", type = STRING,
1250 isRequired = true),
1251 @RestParameter(name = "type", description = "The type of the catalog flavor", type = STRING,
1252 isRequired = true)
1253 },
1254 restParameters = {
1255 @RestParameter(name = "dc", description = "The catalog with extended metadata.", type = TEXT,
1256 isRequired = true, defaultValue = SAMPLE_DUBLIN_CORE
1257 )
1258 },
1259 responses = {
1260 @RestResponse(responseCode = SC_NO_CONTENT, description = "Extended metadata updated"),
1261 @RestResponse(responseCode = SC_CREATED, description = "Extended metadata created"),
1262 @RestResponse(responseCode = SC_INTERNAL_SERVER_ERROR,
1263 description = "Error while processing the request")
1264 }
1265 )
1266 public Response putSeriesExtendedMetadata(
1267 @PathParam("seriesId") String seriesId,
1268 @PathParam("type") String type,
1269 @FormParam("dc") String dcString
1270 ) {
1271 try {
1272 DublinCoreCatalog dc = dcService.load(new ByteArrayInputStream(dcString.getBytes(StandardCharsets.UTF_8)));
1273 boolean elementExists = seriesService.getSeriesElementData(seriesId, type).isPresent();
1274 if (seriesService.updateExtendedMetadata(seriesId, type, dc)) {
1275 if (elementExists) {
1276 return R.noContent();
1277 } else {
1278 return R.created(URI.create(UrlSupport.concat(serverUrl, serviceUrl, seriesId, "elements", type)));
1279 }
1280 } else {
1281 return R.serverError();
1282 }
1283 } catch (IOException e) {
1284 logger.warn("Could not deserialize dublin core catalog", e);
1285 return Response.status(BAD_REQUEST).build();
1286 } catch (SeriesException e) {
1287 logger.warn("Error while updating extended metadata of series '{}'", seriesId, e);
1288 return R.serverError();
1289 }
1290 }
1291
1292
1293 @PUT
1294 @Path("{seriesId}/elements/{elementType}")
1295 @RestQuery(
1296 name = "updateSeriesElement",
1297 description = "Updates an existing series element",
1298 returnDescription = "An empty response",
1299 pathParameters = {
1300 @RestParameter(name = "seriesId", description = "The series identifier", type = STRING, isRequired = true),
1301 @RestParameter(name = "elementType", description = "The element type", type = STRING, isRequired = true)
1302 },
1303 responses = {
1304 @RestResponse(responseCode = SC_NO_CONTENT, description = "Series element updated"),
1305 @RestResponse(responseCode = SC_CREATED, description = "Series element created"),
1306 @RestResponse(responseCode = SC_INTERNAL_SERVER_ERROR, description = "Error while processing the request")
1307 }
1308 )
1309 public Response putSeriesElement(
1310 @Context HttpServletRequest request,
1311 @PathParam("seriesId") String seriesId,
1312 @PathParam("elementType") String elementType
1313 ) {
1314 InputStream is = null;
1315 try {
1316 is = request.getInputStream();
1317 final byte[] data = IOUtils.toByteArray(is);
1318 boolean elementExists = seriesService.getSeriesElementData(seriesId, elementType).isPresent();
1319 if (seriesService.updateSeriesElement(seriesId, elementType, data)) {
1320 if (elementExists) {
1321 return R.noContent();
1322 } else {
1323 return R.created(URI.create(UrlSupport.concat(serverUrl, serviceUrl, seriesId, "elements", elementType)));
1324 }
1325 } else {
1326 return R.serverError();
1327 }
1328 } catch (IOException e) {
1329 logger.error("Error while trying to read from request", e);
1330 return R.serverError();
1331 } catch (SeriesException e) {
1332 logger.warn("Error while adding element to series '{}'", seriesId, e);
1333 return R.serverError();
1334 } finally {
1335 IOUtils.closeQuietly(is);
1336 }
1337 }
1338
1339 @DELETE
1340 @Path("{seriesId}/elements/{elementType}")
1341 @RestQuery(
1342 name = "deleteSeriesElement",
1343 description = "Deletes a series element",
1344 returnDescription = "An empty response",
1345 pathParameters = {
1346 @RestParameter(name = "seriesId", description = "The series identifier", type = STRING, isRequired = true),
1347 @RestParameter(name = "elementType", description = "The element type", type = STRING, isRequired = true)
1348 },
1349 responses = {
1350 @RestResponse(responseCode = SC_NO_CONTENT, description = "Series element deleted"),
1351 @RestResponse(responseCode = SC_NOT_FOUND, description = "Series element not found"),
1352 @RestResponse(responseCode = SC_INTERNAL_SERVER_ERROR, description = "Error while processing the request")
1353 }
1354 )
1355 public Response deleteSeriesElement(
1356 @PathParam("seriesId") String seriesId,
1357 @PathParam("elementType") String elementType
1358 ) {
1359 try {
1360 if (seriesService.deleteSeriesElement(seriesId, elementType)) {
1361 return R.noContent();
1362 } else {
1363 return R.notFound();
1364 }
1365 } catch (SeriesException e) {
1366 return R.serverError();
1367 }
1368 }
1369
1370 }