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.publication.configurable.endpoint;
22  
23  import org.opencastproject.job.api.JaxbJob;
24  import org.opencastproject.job.api.Job;
25  import org.opencastproject.job.api.JobProducer;
26  import org.opencastproject.mediapackage.MediaPackage;
27  import org.opencastproject.mediapackage.MediaPackageElement;
28  import org.opencastproject.mediapackage.MediaPackageElementParser;
29  import org.opencastproject.mediapackage.MediaPackageParser;
30  import org.opencastproject.mediapackage.Publication;
31  import org.opencastproject.publication.api.ConfigurablePublicationService;
32  import org.opencastproject.rest.AbstractJobProducerEndpoint;
33  import org.opencastproject.serviceregistry.api.ServiceRegistry;
34  import org.opencastproject.util.doc.rest.RestParameter;
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 com.google.gson.Gson;
40  import com.google.gson.reflect.TypeToken;
41  
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.util.Collection;
49  import java.util.HashSet;
50  import java.util.Set;
51  
52  import javax.servlet.http.HttpServletResponse;
53  import javax.ws.rs.FormParam;
54  import javax.ws.rs.POST;
55  import javax.ws.rs.Path;
56  import javax.ws.rs.Produces;
57  import javax.ws.rs.core.MediaType;
58  import javax.ws.rs.core.Response;
59  
60  /**
61   * Rest endpoint, mainly for publishing media to a configurable channel
62   */
63  @Path("/publication/api")
64  @RestService(
65      name = "configurablepublicationservice",
66      title = "Configurable Publication Service",
67      abstractText = "This service publishes and retracts media package elements to a configurable channel",
68      notes = {
69          "All paths above are relative to the REST endpoint base (something like http://your.server/files). "
70              + "If the service is down or not working it will return a status 503, this means the "
71              + "underlying service is not working and is either restarting or has failed. A status "
72              + "code 500 means a general failure has occurred which is not recoverable and was not "
73              + "anticipated. In other words, there is a bug!"
74      }
75  )
76  @Component(
77      immediate = true,
78      service = ConfigurablePublicationRestService.class,
79      property = {
80          "service.description=Configurable Publication REST Endpoint",
81          "opencast.service.type=org.opencastproject.publication.configurable",
82          "opencast.service.path=/publication/api",
83          "opencast.service.jobproducer=true"
84      }
85  )
86  @JaxrsResource
87  public class ConfigurablePublicationRestService extends AbstractJobProducerEndpoint {
88  
89    private static final Logger logger = LoggerFactory.getLogger(ConfigurablePublicationRestService.class);
90  
91    /* Gson is thread-safe so we use a single instance */
92    private Gson gson = new Gson();
93  
94    private ConfigurablePublicationService service;
95    private ServiceRegistry serviceRegistry;
96  
97    @Reference
98    public void setService(final ConfigurablePublicationService service) {
99      this.service = service;
100   }
101 
102   @Reference
103   public void setServiceRegistry(final ServiceRegistry serviceRegistry) {
104     this.serviceRegistry = serviceRegistry;
105   }
106 
107   @Override
108   public JobProducer getService() {
109     // The implementation is, of course, resolved by OSGi, so to be "clean", we hold a reference to just
110     // the interface in this class, but at _this_ point, we assume it at least implements JobProducer.
111     return (JobProducer) this.service;
112   }
113 
114   @Override
115   public ServiceRegistry getServiceRegistry() {
116     return this.serviceRegistry;
117   }
118 
119   @POST
120   @Path("/replace")
121   @Produces(MediaType.TEXT_XML)
122   @RestQuery(
123       name = "replace",
124       description = "Replace a media package in this publication channel",
125       returnDescription = "The job that can be used to track the publication",
126       restParameters = {
127           @RestParameter(
128               name = "mediapackage",
129               isRequired = true,
130               description = "The media package",
131               type = RestParameter.Type.TEXT
132           ),
133           @RestParameter(
134               name = "channel",
135               isRequired = true,
136               description = "The channel name",
137               type = RestParameter.Type.STRING
138           ),
139           @RestParameter(
140               name = "addElements",
141               isRequired = true,
142               description = "The media package elements to published",
143               type = RestParameter.Type.STRING
144           ),
145           @RestParameter(
146               name = "retractElements",
147               isRequired = true,
148               description = "The identifiers of the media package elements to be retracted from the media package",
149               type = RestParameter.Type.STRING
150           )
151       },
152       responses = {
153           @RestResponse(
154               responseCode = HttpServletResponse.SC_OK,
155               description = "An XML representation of the publication job"
156           )
157       }
158   )
159   public Response replace(
160       @FormParam("mediapackage") final String mediaPackageXml,
161       @FormParam("channel") final String channel,
162       @FormParam("addElements") final String addElementsXml,
163       @FormParam("retractElements") final String retractElements
164   ) {
165     Response response;
166     final Job job;
167     try {
168       final MediaPackage mediaPackage = MediaPackageParser.getFromXml(mediaPackageXml);
169       final Collection<? extends MediaPackageElement> addElements = new HashSet<>(
170               MediaPackageElementParser.getArrayFromXml(addElementsXml));
171       Set<String> retractElementsIds = gson.fromJson(retractElements, new TypeToken<Set<String>>() { }.getType());
172       job = service.replace(mediaPackage, channel, addElements, retractElementsIds);
173       response = Response.ok(new JaxbJob(job)).build();
174     } catch (IllegalArgumentException e) {
175       logger.warn("Unable to create a publication job", e);
176       response = Response.status(Response.Status.BAD_REQUEST).build();
177     } catch (Exception e) {
178       logger.warn("Error publishing or retracting element", e);
179       response = Response.serverError().build();
180     }
181     return response;
182   }
183 
184   @POST
185   @Path("/replacesync")
186   @Produces(MediaType.TEXT_XML)
187   @RestQuery(
188       name = "replacesync",
189       description = "Synchronously replace a media package in this publication channel",
190       returnDescription = "The publication",
191       restParameters = {
192           @RestParameter(
193               name = "mediapackage",
194               isRequired = true,
195               description = "The media package",
196               type = RestParameter.Type.TEXT
197           ),
198           @RestParameter(
199               name = "channel",
200               isRequired = true,
201               description = "The channel name",
202               type = RestParameter.Type.STRING
203           ),
204           @RestParameter(
205               name = "addElements",
206               isRequired = true,
207               description = "The media package elements to published",
208               type = RestParameter.Type.STRING
209           ),
210           @RestParameter(
211               name = "retractElements",
212               isRequired = true,
213               description = "The identifiers of the media package elements to be retracted from the media package",
214               type = RestParameter.Type.STRING
215           )
216       },
217       responses = {
218           @RestResponse(
219               responseCode = HttpServletResponse.SC_OK,
220               description = "An XML representation of the publication"
221           )
222       }
223   )
224   public Response replaceSync(
225       @FormParam("mediapackage") final String mediaPackageXml,
226       @FormParam("channel") final String channel,
227       @FormParam("addElements") final String addElementsXml,
228       @FormParam("retractElements") final String retractElements
229   ) {
230     Response response;
231     final Publication publication;
232     try {
233       final MediaPackage mediaPackage = MediaPackageParser.getFromXml(mediaPackageXml);
234       final Collection<? extends MediaPackageElement> addElements = new HashSet<>(
235           MediaPackageElementParser.getArrayFromXml(addElementsXml));
236       Set<String> retractElementsIds = gson.fromJson(retractElements, new TypeToken<Set<String>>() { }.getType());
237       publication = service.replaceSync(mediaPackage, channel, addElements, retractElementsIds);
238       response = Response.ok(MediaPackageElementParser.getAsXml(publication)).build();
239     } catch (Exception e) {
240       logger.warn("Error publishing or retracting element", e);
241       response = Response.serverError().build();
242     }
243     return response;
244   }
245 
246 }