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  package org.opencastproject.distribution.aws.s3.endpoint;
22  
23  import static javax.servlet.http.HttpServletResponse.SC_OK;
24  import static javax.ws.rs.core.Response.status;
25  
26  import org.opencastproject.distribution.aws.s3.api.AwsS3DistributionService;
27  import org.opencastproject.job.api.JaxbJob;
28  import org.opencastproject.job.api.Job;
29  import org.opencastproject.job.api.JobProducer;
30  import org.opencastproject.mediapackage.MediaPackage;
31  import org.opencastproject.mediapackage.MediaPackageElement;
32  import org.opencastproject.mediapackage.MediaPackageElementParser;
33  import org.opencastproject.mediapackage.MediaPackageParser;
34  import org.opencastproject.rest.AbstractJobProducerEndpoint;
35  import org.opencastproject.serviceregistry.api.ServiceRegistry;
36  import org.opencastproject.util.doc.rest.RestParameter;
37  import org.opencastproject.util.doc.rest.RestParameter.Type;
38  import org.opencastproject.util.doc.rest.RestQuery;
39  import org.opencastproject.util.doc.rest.RestResponse;
40  import org.opencastproject.util.doc.rest.RestService;
41  
42  import com.google.gson.Gson;
43  import com.google.gson.reflect.TypeToken;
44  
45  import org.osgi.service.component.ComponentContext;
46  import org.osgi.service.component.annotations.Activate;
47  import org.osgi.service.component.annotations.Component;
48  import org.osgi.service.component.annotations.Reference;
49  import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource;
50  import org.slf4j.Logger;
51  import org.slf4j.LoggerFactory;
52  
53  import java.util.List;
54  import java.util.Set;
55  
56  import javax.ws.rs.DefaultValue;
57  import javax.ws.rs.FormParam;
58  import javax.ws.rs.POST;
59  import javax.ws.rs.Path;
60  import javax.ws.rs.Produces;
61  import javax.ws.rs.core.MediaType;
62  import javax.ws.rs.core.Response;
63  import javax.ws.rs.core.Response.Status;
64  
65  /**
66   * Rest endpoint for distributing files to AWS S3.
67   */
68  @Path("/distribution/s3")
69  @RestService(
70      name = "awss3distributionservice",
71      title = "AWS S3 Distribution Service",
72      abstractText = "This service distributes media package elements to AWS S3.",
73      notes = {
74          "All paths above are relative to the REST endpoint base (something like http://your.server/files)",
75          "If the service is down or not working it will return a status 503, this means the the "
76              + "underlying service is not working and is either restarting or has failed",
77          "A status code 500 means a general failure has occurred which is not recoverable and was "
78              + "not anticipated. In other words, there is a bug! You should file an error report "
79              + "with your server logs from the time when the error occurred: "
80              + "<a href=\"https://github.com/opencast/opencast/issues\">Opencast Issue Tracker</a>"
81      }
82  )
83  @Component(
84      immediate = true,
85      service = AwsS3DistributionRestService.class,
86      property = {
87          "service.description=AWS S3 Distribution REST Endpoint",
88          "opencast.service.type=org.opencastproject.distribution.aws.s3",
89          "opencast.service.path=/distribution/s3",
90          "opencast.service.jobproducer=true"
91      }
92  )
93  @JaxrsResource
94  public class AwsS3DistributionRestService extends AbstractJobProducerEndpoint {
95  
96    /** The logger */
97    private static final Logger logger = LoggerFactory.getLogger(AwsS3DistributionRestService.class);
98  
99    /** The distribution service */
100   protected AwsS3DistributionService service;
101 
102   /** The service registry */
103   protected ServiceRegistry serviceRegistry = null;
104 
105   /**
106    * OSGi activation callback
107    *
108    * @param cc
109    *          this component's context
110    */
111   @Activate
112   public void activate(ComponentContext cc) {
113   }
114 
115   /**
116    * Callback from the OSGi declarative services to set the service registry.
117    *
118    * @param serviceRegistry
119    *          the service registry
120    */
121   @Reference
122   protected void setServiceRegistry(ServiceRegistry serviceRegistry) {
123     this.serviceRegistry = serviceRegistry;
124   }
125 
126   /**
127    * @param service
128    *          the service to set
129    */
130   @Reference(target = "(distribution.channel=aws.s3)")
131   public void setService(AwsS3DistributionService service) {
132     this.service = service;
133   }
134 
135   @POST
136   @Path("/")
137   @Produces(MediaType.TEXT_XML)
138   @RestQuery(
139       name = "distribute",
140       description = "Distribute a media package element to this distribution channel",
141       returnDescription = "The job that can be used to track the distribution",
142       restParameters = {
143           @RestParameter(
144               name = "mediapackage",
145               isRequired = true,
146               description = "The mediapackage",
147               type = Type.TEXT
148           ),
149           @RestParameter(
150               name = "channelId",
151               isRequired = true,
152               description = "The publication channel ID",
153               type = Type.TEXT
154           ),
155           @RestParameter(
156               name = "elementId",
157               isRequired = true,
158               description = "The element to distribute",
159               type = Type.STRING
160           ),
161           @RestParameter(
162               name = "checkAvailability",
163               isRequired = false,
164               description = "If the service should try to access the distributed element",
165               type = Type.BOOLEAN
166           )
167       },
168       responses = {
169           @RestResponse(responseCode = SC_OK, description = "An XML representation of the distribution job")
170       }
171   )
172   public Response distribute(
173       @FormParam("mediapackage") String mediaPackageXml,
174       @FormParam("channelId") String channelId,
175       @FormParam("elementId") String elementId,
176       @FormParam("checkAvailability") @DefaultValue("true") boolean checkAvailability
177   ) throws Exception {
178     Job job = null;
179     try {
180       Gson gson = new Gson();
181       Set<String> setElementIds = gson.fromJson(elementId, new TypeToken<Set<String>>() { }.getType());
182       MediaPackage mediapackage = MediaPackageParser.getFromXml(mediaPackageXml);
183       job = service.distribute(channelId, mediapackage, setElementIds, checkAvailability);
184     } catch (IllegalArgumentException e) {
185       logger.debug("Unable to distribute element: {}", e.getMessage());
186       return status(Status.BAD_REQUEST).build();
187     } catch (Exception e) {
188       logger.warn("Unable to distribute media package {}, element {} to aws s3 channel",
189           mediaPackageXml, elementId, e);
190       return Response.serverError().status(Status.INTERNAL_SERVER_ERROR).build();
191     }
192     return Response.ok(new JaxbJob(job)).build();
193   }
194 
195   @POST
196   @Path("/distributesync")
197   @Produces(MediaType.TEXT_XML)
198   @RestQuery(
199       name = "distributesync",
200       description = "Synchronously distribute a media package element to this distribution channel",
201       returnDescription = "The distribution",
202       restParameters = {
203           @RestParameter(
204               name = "mediapackage",
205               isRequired = true,
206               description = "The mediapackage",
207               type = Type.TEXT
208           ),
209           @RestParameter(
210               name = "channelId",
211               isRequired = true,
212               description = "The publication channel ID",
213               type = Type.TEXT
214           ),
215           @RestParameter(
216               name = "elementId",
217               isRequired = true,
218               description = "The element to distribute",
219               type = Type.STRING
220           ),
221           @RestParameter(
222               name = "checkAvailability",
223               isRequired = false,
224               description = "If the service should try to access the distributed element",
225               type = Type.BOOLEAN
226           )
227       },
228       responses = {
229           @RestResponse(responseCode = SC_OK, description = "An XML representation of the distribution")
230       }
231   )
232   public Response distributeSync(
233       @FormParam("mediapackage") String mediaPackageXml,
234       @FormParam("channelId") String channelId,
235       @FormParam("elementId") String elementId,
236       @FormParam("checkAvailability") @DefaultValue("true") boolean checkAvailability
237   ) throws Exception {
238     List<MediaPackageElement> result = null;
239     try {
240       Gson gson = new Gson();
241       Set<String> setElementIds = gson.fromJson(elementId, new TypeToken<Set<String>>() { }.getType());
242       MediaPackage mediapackage = MediaPackageParser.getFromXml(mediaPackageXml);
243       result = service.distributeSync(channelId, mediapackage, setElementIds, checkAvailability);
244     } catch (IllegalArgumentException e) {
245       logger.debug("Unable to distribute element: {}", e.getMessage());
246       return status(Status.BAD_REQUEST).build();
247     } catch (Exception e) {
248       logger.warn("Unable to distribute media package {}, element {} to aws s3 channel",
249           mediaPackageXml, elementId, e);
250       return Response.serverError().status(Status.INTERNAL_SERVER_ERROR).build();
251     }
252     return Response.ok(MediaPackageElementParser.getArrayAsXml(result)).build();
253   }
254 
255   @POST
256   @Path("/retract")
257   @Produces(MediaType.TEXT_XML)
258   @RestQuery(
259       name = "retract",
260       description = "Retract a media package element from this distribution channel",
261       returnDescription = "The job that can be used to track the retraction",
262       restParameters = {
263           @RestParameter(
264               name = "mediapackage",
265               isRequired = true,
266               description = "The mediapackage",
267               type = Type.TEXT
268           ),
269           @RestParameter(
270               name = "channelId",
271               isRequired = true,
272               description = "The publication channel ID",
273               type = Type.TEXT
274           ),
275           @RestParameter(
276               name = "elementId",
277               isRequired = true,
278               description = "The element to retract",
279               type = Type.STRING
280           )
281       },
282       responses = {
283           @RestResponse(responseCode = SC_OK, description = "An XML representation of the retraction job")
284       }
285   )
286   public Response retract(
287       @FormParam("mediapackage") String mediaPackageXml,
288       @FormParam("channelId") String channelId,
289       @FormParam("elementId") String elementId
290   ) throws Exception {
291     Job job = null;
292     try {
293       Gson gson = new Gson();
294       Set<String> setElementIds = gson.fromJson(elementId, new TypeToken<Set<String>>() { }.getType());
295       MediaPackage mediapackage = MediaPackageParser.getFromXml(mediaPackageXml);
296       job = service.retract(channelId, mediapackage, setElementIds);
297     } catch (IllegalArgumentException e) {
298       logger.debug("Unable to retract element: {}", e.getMessage());
299       return status(Status.BAD_REQUEST).build();
300     } catch (Exception e) {
301       logger.warn("Unable to retract media package {}, element {} from aws s3 channel",
302           mediaPackageXml, elementId, e);
303       return Response.serverError().status(Status.INTERNAL_SERVER_ERROR).build();
304     }
305     return Response.ok(new JaxbJob(job)).build();
306   }
307 
308   @POST
309   @Path("/retractsync")
310   @Produces(MediaType.TEXT_XML)
311   @RestQuery(
312       name = "retractsync",
313       description = "Synchronously retract a media package element from this distribution channel",
314       returnDescription = "The retraction",
315       restParameters = {
316           @RestParameter(
317               name = "mediapackage",
318               isRequired = true,
319               description = "The mediapackage",
320               type = Type.TEXT
321           ),
322           @RestParameter(
323               name = "channelId",
324               isRequired = true,
325               description = "The publication channel ID",
326               type = Type.TEXT
327           ),
328           @RestParameter(
329               name = "elementId",
330               isRequired = true,
331               description = "The element to retract",
332               type = Type.STRING
333           )
334       },
335       responses = {
336           @RestResponse(responseCode = SC_OK, description = "An XML representation of the retraction")
337       }
338   )
339   public Response retractSync(
340       @FormParam("mediapackage") String mediaPackageXml,
341       @FormParam("channelId") String channelId,
342       @FormParam("elementId") String elementId
343   ) throws Exception {
344     List<MediaPackageElement> result = null;
345     try {
346       Gson gson = new Gson();
347       Set<String> setElementIds = gson.fromJson(elementId, new TypeToken<Set<String>>() { }.getType());
348       MediaPackage mediapackage = MediaPackageParser.getFromXml(mediaPackageXml);
349       result = service.retractSync(channelId, mediapackage, setElementIds);
350     } catch (IllegalArgumentException e) {
351       logger.debug("Unable to retract element: {}", e.getMessage());
352       return status(Status.BAD_REQUEST).build();
353     } catch (Exception e) {
354       logger.warn("Unable to retract media package {}, element {} from aws s3 channel",
355           mediaPackageXml, elementId, e);
356       return Response.serverError().status(Status.INTERNAL_SERVER_ERROR).build();
357     }
358     return Response.ok(MediaPackageElementParser.getArrayAsXml(result)).build();
359   }
360 
361   /**
362    * {@inheritDoc}
363    *
364    * @see org.opencastproject.rest.AbstractJobProducerEndpoint#getService()
365    */
366   @Override
367   public JobProducer getService() {
368     if (service instanceof JobProducer) {
369       return (JobProducer) service;
370     } else {
371       return null;
372     }
373   }
374 
375   /**
376    * {@inheritDoc}
377    *
378    * @see org.opencastproject.rest.AbstractJobProducerEndpoint#getServiceRegistry()
379    */
380   @Override
381   public ServiceRegistry getServiceRegistry() {
382     return serviceRegistry;
383   }
384 
385 }