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  
23  package org.opencastproject.execute.impl.endpoint;
24  
25  import org.opencastproject.execute.api.ExecuteException;
26  import org.opencastproject.execute.api.ExecuteService;
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.MediaPackageException;
34  import org.opencastproject.mediapackage.MediaPackageParser;
35  import org.opencastproject.rest.AbstractJobProducerEndpoint;
36  import org.opencastproject.serviceregistry.api.ServiceRegistry;
37  import org.opencastproject.util.doc.rest.RestParameter;
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 org.apache.commons.lang3.StringUtils;
43  import org.osgi.service.component.annotations.Component;
44  import org.osgi.service.component.annotations.Reference;
45  import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource;
46  import org.slf4j.Logger;
47  import org.slf4j.LoggerFactory;
48  
49  import javax.servlet.http.HttpServletResponse;
50  import javax.ws.rs.FormParam;
51  import javax.ws.rs.POST;
52  import javax.ws.rs.Path;
53  import javax.ws.rs.Produces;
54  import javax.ws.rs.core.MediaType;
55  import javax.ws.rs.core.Response;
56  
57  /**
58   * The REST endpoint for {@link ExecuteService}s
59   */
60  @Path("/execute")
61  // Endpoint to the execute service, that runs CLI commands using MediaPackageElement's as parameters
62  @RestService(name = "execute", title = "Execute Service", notes = {"" }, abstractText = "Runs CLI commands with MediaPackageElement's as parameters")
63  @Component(
64      immediate = true,
65      service = ExecuteRestEndpoint.class,
66      property = {
67          "service.description=Execute REST Endpoint",
68          "opencast.service.type=org.opencastproject.execute",
69          "opencast.service.path=/execute",
70          "opencast.service.jobproducer=true"
71      }
72  )
73  @JaxrsResource
74  public class ExecuteRestEndpoint extends AbstractJobProducerEndpoint {
75  
76    /** The logging facility */
77    private static final Logger logger = LoggerFactory.getLogger(ExecuteRestEndpoint.class);
78  
79    /** The service registry */
80    protected ServiceRegistry serviceRegistry = null;
81  
82    /** The execute service */
83    protected ExecuteService service;
84  
85    @POST
86    @Produces(MediaType.TEXT_XML)
87    @Path(ExecuteService.ENDPOINT_NAME)
88    @RestQuery(name = "name", description = "Executes the given command", restParameters = {
89            @RestParameter(description = "The command to execute", isRequired = true, name = ExecuteService.EXEC_FORM_PARAM, type = RestParameter.Type.STRING),
90            @RestParameter(description = "The arguments to the command", isRequired = true, name = ExecuteService.PARAMS_FORM_PARAM, type = RestParameter.Type.STRING),
91            @RestParameter(description = "The estimated load placed on the system by this command", isRequired = false, name = ExecuteService.LOAD_FORM_PARAM, type = RestParameter.Type.FLOAT),
92            @RestParameter(description = "The mediapackage to apply the command to. Either this or " + ExecuteService.INPUT_ELEM_FORM_PARAM + " are required",
93            isRequired = false, name = ExecuteService.INPUT_MP_FORM_PARAM, type = RestParameter.Type.TEXT),
94            @RestParameter(description = "The mediapackage element to apply the command to. Either this or " + ExecuteService.INPUT_MP_FORM_PARAM + " are required",
95            isRequired = false, name = ExecuteService.INPUT_ELEM_FORM_PARAM, type = RestParameter.Type.TEXT),
96            @RestParameter(description = "The mediapackage element produced by the command", isRequired = false, name = ExecuteService.OUTPUT_NAME_FORM_PARAMETER,
97            type = RestParameter.Type.STRING),
98            @RestParameter(description = "The type of the returned element", isRequired = false, name = ExecuteService.TYPE_FORM_PARAMETER, type = RestParameter.Type.STRING) },
99            responses = {
100           @RestResponse(description = "XML-encoded Job is returned.", responseCode = HttpServletResponse.SC_NO_CONTENT),
101           @RestResponse(description = "Service unavailabe or not currently present", responseCode = HttpServletResponse.SC_SERVICE_UNAVAILABLE),
102           @RestResponse(description = "Incorrect parameters", responseCode = HttpServletResponse.SC_BAD_REQUEST),
103           @RestResponse(description = "Problem executing the command or serializing the arguments/results", responseCode = HttpServletResponse.SC_INTERNAL_SERVER_ERROR)
104           },
105           returnDescription = "")
106   public Response execute(@FormParam(ExecuteService.EXEC_FORM_PARAM) String exec,
107           @FormParam(ExecuteService.PARAMS_FORM_PARAM) String params,
108           @FormParam(ExecuteService.LOAD_FORM_PARAM) Float loadParam,
109           @FormParam(ExecuteService.INPUT_ELEM_FORM_PARAM) String inputElementStr,
110           @FormParam(ExecuteService.INPUT_MP_FORM_PARAM) String inputMpStr,
111           @FormParam(ExecuteService.OUTPUT_NAME_FORM_PARAMETER) String outputFileName,
112           @FormParam(ExecuteService.TYPE_FORM_PARAMETER) String elementTypeStr) {
113 
114     checkNotNull(service);
115     try {
116 
117       MediaPackageElement.Type expectedType = null;
118       if (StringUtils.isNotBlank(elementTypeStr)) {
119         for (MediaPackageElement.Type candidateType : MediaPackageElement.Type.values())
120           if (candidateType.toString().equalsIgnoreCase(elementTypeStr)) {
121             expectedType = candidateType;
122             break;
123           }
124         if (expectedType == null) {
125           logger.error("Wrong element type specified: {}", elementTypeStr);
126           return Response.status(Response.Status.BAD_REQUEST).build();
127         }
128       }
129 
130       float load = 1.0f;
131       if (loadParam != null) {
132         load = loadParam;
133       }
134 
135       Job retJob = null;
136       if (StringUtils.isNotBlank(inputElementStr) && StringUtils.isNotBlank(inputMpStr)) {
137         logger.error("Only one input MediaPackage OR input MediaPackageElement can be set at the same time");
138         return Response.status(Response.Status.BAD_REQUEST).build();
139       } else if (StringUtils.isNotBlank(inputElementStr)) {
140         MediaPackageElement inputElement = MediaPackageElementParser.getFromXml(inputElementStr);
141         retJob = service.execute(exec, params, inputElement, outputFileName, expectedType, load);
142       } else if (StringUtils.isNotBlank(inputMpStr)) {
143         MediaPackage inputMp = MediaPackageParser.getFromXml(inputMpStr);
144         retJob = service.execute(exec, params, inputMp, outputFileName, expectedType, load);
145       } else {
146         logger.error("A MediaPackage OR MediaPackageElement must be provided");
147         return Response.status(Response.Status.BAD_REQUEST).build();
148       }
149 
150       return Response.ok(new JaxbJob(retJob)).build();
151 
152     } catch (IllegalArgumentException e) {
153       logger.error("The expected element type is required if an output filename is specified");
154       return Response.status(Response.Status.BAD_REQUEST).build();
155     } catch (MediaPackageException e) {
156       logger.error("Received excepcion: {}", e.getMessage());
157       return Response.serverError().build();
158     } catch (ExecuteException e) {
159       logger.error("Received error from the execute service: {}", e.getMessage());
160       return Response.serverError().build();
161     }
162   }
163 
164 
165   /**
166    * Sets the service
167    *
168    * @param service
169    */
170   @Reference
171   public void setExecuteService(ExecuteService service) {
172     this.service = service;
173   }
174 
175   /**
176    * {@inheritDoc}
177    * 
178    * @see org.opencastproject.rest.AbstractJobProducerEndpoint#getService()
179    */
180   @Override
181   public JobProducer getService() {
182     if (service instanceof JobProducer)
183       return (JobProducer) service;
184     else
185       return null;
186   }
187 
188   /**
189    * Checks if the service or services are available, if not it handles it by returning a 503 with a message
190    * 
191    * @param services
192    *          an array of services to check
193    */
194   protected void checkNotNull(Object... services) {
195     if (services != null) {
196       for (Object object : services) {
197         if (object == null) {
198           throw new javax.ws.rs.WebApplicationException(javax.ws.rs.core.Response.Status.SERVICE_UNAVAILABLE);
199         }
200       }
201     }
202   }
203 
204   /**
205    * Callback from the OSGi declarative services to set the service registry.
206    *
207    * @param serviceRegistry
208    *          the service registry
209    */
210   @Reference
211   protected void setServiceRegistry(ServiceRegistry serviceRegistry) {
212     this.serviceRegistry = serviceRegistry;
213   }
214 
215   /**
216    * {@inheritDoc}
217    *
218    * @see org.opencastproject.rest.AbstractJobProducerEndpoint#getServiceRegistry()
219    */
220   @Override
221   public ServiceRegistry getServiceRegistry() {
222     return serviceRegistry;
223   }
224 }