View Javadoc
1   /*
2    * Licensed to The Apereo Foundation under one or more contributor license
3    * agreements. See the NOTICE file distributed with this work for additional
4    * information regarding copyright ownership.
5    *
6    *
7    * The Apereo Foundation licenses this file to you under the Educational
8    * Community License, Version 2.0 (the "License"); you may not use this file
9    * except in compliance with the License. You may obtain a copy of the License
10   * at:
11   *
12   *   http://opensource.org/licenses/ecl2.txt
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
17   * License for the specific language governing permissions and limitations under
18   * the License.
19   *
20   */
21  
22  package org.opencastproject.annotation.impl;
23  
24  import static javax.servlet.http.HttpServletResponse.SC_CREATED;
25  import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
26  import static javax.servlet.http.HttpServletResponse.SC_OK;
27  
28  import org.opencastproject.annotation.api.Annotation;
29  import org.opencastproject.annotation.api.AnnotationService;
30  import org.opencastproject.rest.RestConstants;
31  import org.opencastproject.util.NotFoundException;
32  import org.opencastproject.util.UrlSupport;
33  import org.opencastproject.util.doc.rest.RestParameter;
34  import org.opencastproject.util.doc.rest.RestParameter.Type;
35  import org.opencastproject.util.doc.rest.RestQuery;
36  import org.opencastproject.util.doc.rest.RestResponse;
37  import org.opencastproject.util.doc.rest.RestService;
38  
39  import org.apache.commons.lang3.StringUtils;
40  import org.osgi.service.component.ComponentContext;
41  import org.osgi.service.component.annotations.Activate;
42  import org.osgi.service.component.annotations.Component;
43  import org.osgi.service.component.annotations.Reference;
44  import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource;
45  import org.slf4j.Logger;
46  import org.slf4j.LoggerFactory;
47  
48  import java.net.URI;
49  import java.net.URISyntaxException;
50  
51  import javax.servlet.http.HttpServletRequest;
52  import javax.ws.rs.DELETE;
53  import javax.ws.rs.FormParam;
54  import javax.ws.rs.GET;
55  import javax.ws.rs.PUT;
56  import javax.ws.rs.Path;
57  import javax.ws.rs.PathParam;
58  import javax.ws.rs.Produces;
59  import javax.ws.rs.QueryParam;
60  import javax.ws.rs.WebApplicationException;
61  import javax.ws.rs.core.Context;
62  import javax.ws.rs.core.MediaType;
63  import javax.ws.rs.core.Response;
64  import javax.ws.rs.core.Response.Status;
65  
66  /**
67   * The REST endpoint for the annotation service.
68   */
69  @Path("/annotation")
70  @RestService(name = "annotation", title = "Annotation Service",
71      abstractText = "This service is used for managing user generated annotations.",
72      notes = {
73          "<strong>Deprecated:</strong> "
74          + "<em>This module is deprecated. It may be removed at any time. Planned removal is the Opencast release in "
75          + "December 2017. Please do not use it for new development.</em>",
76          "All paths above are relative to the REST endpoint base (something like http://your.server/files)",
77          "If the service is down or not working it will return a status 503, this means the the underlying service is "
78          + "not working and is either restarting or has failed",
79          "A status code 500 means a general failure has occurred which is not recoverable and was not anticipated. In "
80          + "other words, there is a bug! You should file an error report with your server logs from the time when the "
81          + "error occurred: <a href=\"https://github.com/opencast/opencast/issues\">Opencast Issue Tracker</a>" })
82  @Component(
83      immediate = true,
84      service = AnnotationRestService.class,
85      property = {
86          "service.description=Annotation REST Endpoint",
87          "opencast.service.type=org.opencastproject.annotation",
88          "opencast.service.path=/annotation"
89      }
90  )
91  @JaxrsResource
92  public class AnnotationRestService {
93  
94    /** The logger */
95    private static final Logger logger = LoggerFactory.getLogger(AnnotationRestService.class);
96  
97    /** The annotation service */
98    private AnnotationService annotationService;
99  
100   /** This server's base URL */
101   protected String serverUrl = UrlSupport.DEFAULT_BASE_URL;
102 
103   /** The REST endpoint's base URL */
104   // this is the default value, which may be overridden in the OSGI service registration
105   protected String serviceUrl = "/annotation";
106 
107   /**
108    * Method to set the service this REST endpoint uses
109    *
110    * @param service
111    *          the annotation service implementation
112    */
113   @Reference
114   public void setService(AnnotationService service) {
115     this.annotationService = service;
116   }
117 
118   /**
119    * The method that is called, when the service is activated
120    *
121    * @param cc
122    *          The ComponentContext of this service
123    */
124   @Activate
125   public void activate(ComponentContext cc) {
126     // Get the configured server URL
127     if (cc == null) {
128       serverUrl = UrlSupport.DEFAULT_BASE_URL;
129     } else {
130       String ccServerUrl = cc.getBundleContext().getProperty("org.opencastproject.server.url");
131       logger.info("configured server url is {}", ccServerUrl);
132       if (ccServerUrl == null) {
133         serverUrl = UrlSupport.DEFAULT_BASE_URL;
134       } else {
135         serverUrl = ccServerUrl;
136       }
137       serviceUrl = (String) cc.getProperties().get(RestConstants.SERVICE_PATH_PROPERTY);
138     }
139   }
140 
141   /**
142    * @return XML with all footprints
143    */
144   @GET
145   @Produces(MediaType.TEXT_XML)
146   @Path("annotations.xml")
147   @RestQuery(
148       name = "annotationsasxml",
149       description = "Get annotations by key and day",
150       returnDescription = "The user annotations.",
151       restParameters = {
152           @RestParameter(
153               name = "episode",
154               description = "The episode identifier",
155               isRequired = false,
156               type = Type.STRING
157           ),
158           @RestParameter(
159               name = "type",
160               description = "The type of annotation",
161               isRequired = false,
162               type = Type.STRING
163           ),
164           @RestParameter(
165               name = "day",
166               description = "The day of creation (format: YYYYMMDD)",
167               isRequired = false,
168               type = Type.STRING
169           ),
170           @RestParameter(
171               name = "limit",
172               description = "The maximum number of items to return per page",
173               isRequired = false,
174               type = Type.INTEGER
175           ),
176           @RestParameter(
177               name = "offset",
178               description = "The page number",
179               isRequired = false,
180               type = Type.INTEGER
181           ),
182       },
183       responses = {
184           @RestResponse(responseCode = SC_OK, description = "An XML representation of the user annotations"),
185       }
186   )
187   public Response getAnnotationsAsXml(@QueryParam("episode") String id, @QueryParam("type") String type,
188           @QueryParam("day") String day, @QueryParam("limit") int limit, @QueryParam("offset") int offset) {
189 
190     // Are the values of offset and limit valid?
191     if (offset < 0 || limit < 0) {
192       return Response.status(Status.BAD_REQUEST).build();
193     }
194 
195     // Set default value of limit (max result value)
196     if (limit == 0) {
197       limit = 10;
198     }
199 
200     if (!StringUtils.isEmpty(id) && !StringUtils.isEmpty(type)) {
201       return Response.ok(annotationService.getAnnotationsByTypeAndMediapackageId(type, id, offset, limit)).build();
202     } else if (!StringUtils.isEmpty(id)) {
203       return Response.ok(annotationService.getAnnotationsByMediapackageId(id, offset, limit)).build();
204     } else if (!StringUtils.isEmpty(type) && !StringUtils.isEmpty(day)) {
205       return Response.ok(annotationService.getAnnotationsByTypeAndDay(type, day, offset, limit)).build();
206     } else if (!StringUtils.isEmpty(type)) {
207       return Response.ok(annotationService.getAnnotationsByType(type, offset, limit)).build();
208     } else if (!StringUtils.isEmpty(day)) {
209       return Response.ok(annotationService.getAnnotationsByDay(day, offset, limit)).build();
210     } else {
211       return Response.ok(annotationService.getAnnotations(offset, limit)).build();
212     }
213   }
214 
215   /**
216    * @return JSON with all footprints
217    */
218   @GET
219   @Produces(MediaType.APPLICATION_JSON)
220   @Path("annotations.json")
221   @RestQuery(
222       name = "annotationsasjson",
223       description = "Get annotations by key and day",
224       returnDescription = "The user annotations.",
225       restParameters = {
226           @RestParameter(
227               name = "episode",
228               description = "The episode identifier",
229               isRequired = false,
230               type = Type.STRING
231           ),
232           @RestParameter(
233               name = "type",
234               description = "The type of annotation",
235               isRequired = false,
236               type = Type.STRING
237           ),
238           @RestParameter(
239               name = "day",
240               description = "The day of creation (format: YYYYMMDD)",
241               isRequired = false,
242               type = Type.STRING
243           ),
244           @RestParameter(
245               name = "limit",
246               description = "The maximum number of items to return per page",
247               isRequired = false,
248               type = Type.INTEGER
249           ),
250           @RestParameter(
251               name = "offset",
252               description = "The page number",
253               isRequired = false,
254               type = Type.INTEGER
255           ),
256       },
257       responses = {
258           @RestResponse(responseCode = SC_OK, description = "A JSON representation of the user annotations"),
259       }
260   )
261   public Response getAnnotationsAsJson(
262       @QueryParam("episode") String id,
263       @QueryParam("type") String type,
264       @QueryParam("day") String day,
265       @QueryParam("limit") int limit,
266       @QueryParam("offset") int offset
267   ) {
268     return getAnnotationsAsXml(id, type, day, limit, offset); // same logic, different @Produces annotation
269   }
270 
271   @PUT
272   @Path("")
273   @Produces(MediaType.TEXT_XML)
274   @RestQuery(
275       name = "add",
276       description = "Add an annotation on an episode",
277       returnDescription = "The user annotation.",
278       restParameters = {
279           @RestParameter(
280               name = "episode",
281               description = "The episode identifier",
282               isRequired = true,
283               type = Type.STRING
284           ),
285           @RestParameter(
286               name = "type",
287               description = "The type of annotation",
288               isRequired = true,
289               type = Type.STRING
290           ),
291           @RestParameter(
292               name = "value",
293               description = "The value of the annotation",
294               isRequired = true,
295               type = Type.TEXT
296           ),
297           @RestParameter(
298               name = "in",
299               description = "The time, or inpoint, of the annotation",
300               isRequired = true,
301               type = Type.STRING
302           ),
303           @RestParameter(
304               name = "out",
305               description = "The optional outpoint of the annotation",
306               isRequired = false,
307               type = Type.STRING
308           ),
309           @RestParameter(
310               name = "isPrivate",
311               description = "True if the annotation is private",
312               isRequired = false,
313               type = Type.BOOLEAN
314           ),
315       },
316       responses = {
317           @RestResponse(
318               responseCode = SC_CREATED,
319               description = "The URL to this annotation is returned in the Location header, and an "
320                   + "XML representation of the annotation itelf is returned in the response body."
321           ),
322       }
323   )
324   public Response add(
325       @FormParam("episode") String mediapackageId,
326       @FormParam("in") int inpoint,
327       @FormParam("out") int outpoint,
328       @FormParam("type") String type,
329       @FormParam("value") String value,
330       @FormParam("isPrivate") boolean isPrivate,
331       @Context HttpServletRequest request
332   ) {
333     String sessionId = request.getSession().getId();
334     Annotation a = new AnnotationImpl();
335     a.setMediapackageId(mediapackageId);
336     a.setSessionId(sessionId);
337     a.setInpoint(inpoint);
338     a.setOutpoint(outpoint);
339     a.setType(type);
340     a.setValue(value);
341     a.setPrivate(isPrivate);
342     a = annotationService.addAnnotation(a);
343     URI uri;
344     try {
345       uri = new URI(
346               UrlSupport.concat(new String[] { serverUrl, serviceUrl, Long.toString(a.getAnnotationId()), ".xml" }));
347     } catch (URISyntaxException e) {
348       throw new WebApplicationException(e);
349     }
350     return Response.created(uri).entity(a).build();
351   }
352 
353   @PUT
354   @Path("{id}")
355   @Produces(MediaType.TEXT_XML)
356   @RestQuery(
357       name = "change",
358       description = "Changes the value of an annotation specified by its identifier ",
359       returnDescription = "The user annotation.",
360       pathParameters = {
361           @RestParameter(
362               name = "id",
363               description = "The annotation identifier",
364               isRequired = true,
365               type = Type.STRING
366           ),
367       },
368       restParameters = {
369           @RestParameter(
370               name = "value",
371               description = "The value of the annotation",
372               isRequired = true,
373               type = Type.TEXT
374           ),
375       },
376       responses = {
377           @RestResponse(
378               responseCode = SC_CREATED,
379               description = "The URL to this annotation is returned in the Location header, and an "
380                   + "XML representation of the annotation itelf is returned in the response body."
381           ),
382       }
383   )
384   public Response change(
385       @PathParam("id") String idAsString,
386       @FormParam("value") String value,
387       @Context HttpServletRequest request
388   ) throws NotFoundException {
389     Long id = null;
390     try {
391       id = Long.parseLong(idAsString);
392     } catch (NumberFormatException e) {
393       return Response.status(Status.INTERNAL_SERVER_ERROR).build();
394     }
395     Annotation a = annotationService.getAnnotation(id);
396     a.setValue(value);
397     a = annotationService.changeAnnotation(a);
398     URI uri;
399     try {
400       uri = new URI(
401               UrlSupport.concat(new String[] { serverUrl, serviceUrl, Long.toString(a.getAnnotationId()), ".xml" }));
402     } catch (URISyntaxException e) {
403       throw new WebApplicationException(e);
404     }
405     return Response.created(uri).entity(a).build();
406   }
407 
408   @GET
409   @Produces(MediaType.TEXT_XML)
410   @Path("{id}.xml")
411   @RestQuery(
412       name = "annotationasxml",
413       description = "Gets an annotation by its identifier",
414       returnDescription = "An XML representation of the user annotation.",
415       pathParameters = {
416           @RestParameter(name = "id", description = "The episode identifier", isRequired = false, type = Type.STRING),
417       },
418       responses = {
419           @RestResponse(responseCode = SC_OK, description = "An XML representation of the user annotation"),
420       }
421   )
422   public AnnotationImpl getAnnotationAsXml(@PathParam("id") String idAsString) throws NotFoundException {
423     Long id = null;
424     try {
425       id = Long.parseLong(idAsString);
426     } catch (NumberFormatException e) {
427       throw new WebApplicationException(e, Status.BAD_REQUEST);
428     }
429     return (AnnotationImpl) annotationService.getAnnotation(id);
430   }
431 
432   @GET
433   @Produces(MediaType.APPLICATION_JSON)
434   @Path("{id}.json")
435   @RestQuery(
436       name = "annotationasjson",
437       description = "Gets an annotation by its identifier",
438       returnDescription = "A JSON representation of the user annotation.",
439       pathParameters = {
440           @RestParameter(name = "id", description = "The episode identifier", isRequired = false, type = Type.STRING),
441       },
442       responses = {
443           @RestResponse(responseCode = SC_OK, description = "A JSON representation of the user annotation"),
444       }
445   )
446   public AnnotationImpl getAnnotationAsJson(@PathParam("id") String idAsString) throws NotFoundException {
447     return getAnnotationAsXml(idAsString);
448   }
449 
450   @DELETE
451   @Path("{id}")
452   @RestQuery(
453       name = "remove",
454       description = "Remove an annotation",
455       returnDescription = "Return status code",
456       pathParameters = {
457           @RestParameter(
458               name = "id",
459               description = "The annotation identifier",
460               isRequired = false,
461               type = Type.STRING
462           ),
463       },
464       responses = {
465           @RestResponse(responseCode = SC_OK, description = "Annotation deleted."),
466           @RestResponse(responseCode = SC_NO_CONTENT, description = "Annotation not found."),
467       }
468   )
469   public Response removeAnnotation(@PathParam("id") String idAsString) throws NotFoundException {
470     Long id = null;
471     Annotation a;
472     try {
473       id = Long.parseLong(idAsString);
474     } catch (NumberFormatException e) {
475       return Response.status(Status.INTERNAL_SERVER_ERROR).build();
476     }
477     a = (AnnotationImpl) annotationService.getAnnotation(id);
478     boolean removed = annotationService.removeAnnotation(a);
479     if (removed) {
480       return Response.status(Status.OK).build();
481     }
482     else {
483       return Response.status(Status.NO_CONTENT).build();
484     }
485   }
486 
487 }