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