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.workflow.endpoint;
23
24 import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
25 import static javax.servlet.http.HttpServletResponse.SC_CONFLICT;
26 import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
27 import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
28 import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT;
29 import static javax.servlet.http.HttpServletResponse.SC_OK;
30 import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
31 import static org.opencastproject.util.doc.rest.RestParameter.Type.STRING;
32 import static org.opencastproject.util.doc.rest.RestParameter.Type.TEXT;
33
34 import org.opencastproject.job.api.JobProducer;
35 import org.opencastproject.mediapackage.MediaPackage;
36 import org.opencastproject.mediapackage.MediaPackageElement;
37 import org.opencastproject.mediapackage.MediaPackageImpl;
38 import org.opencastproject.mediapackage.MediaPackageParser;
39 import org.opencastproject.mediapackage.MediaPackageSupport;
40 import org.opencastproject.rest.AbstractJobProducerEndpoint;
41 import org.opencastproject.rest.RestConstants;
42 import org.opencastproject.security.api.Permissions;
43 import org.opencastproject.security.api.UnauthorizedException;
44 import org.opencastproject.serviceregistry.api.ServiceRegistry;
45 import org.opencastproject.systems.OpencastConstants;
46 import org.opencastproject.util.LocalHashMap;
47 import org.opencastproject.util.NotFoundException;
48 import org.opencastproject.util.UrlSupport;
49 import org.opencastproject.util.doc.rest.RestParameter;
50 import org.opencastproject.util.doc.rest.RestParameter.Type;
51 import org.opencastproject.util.doc.rest.RestQuery;
52 import org.opencastproject.util.doc.rest.RestResponse;
53 import org.opencastproject.util.doc.rest.RestService;
54 import org.opencastproject.workflow.api.JaxbWorkflowInstance;
55 import org.opencastproject.workflow.api.WorkflowDatabaseException;
56 import org.opencastproject.workflow.api.WorkflowDefinition;
57 import org.opencastproject.workflow.api.WorkflowDefinitionImpl;
58 import org.opencastproject.workflow.api.WorkflowDefinitionSet;
59 import org.opencastproject.workflow.api.WorkflowException;
60 import org.opencastproject.workflow.api.WorkflowInstance;
61 import org.opencastproject.workflow.api.WorkflowInstance.WorkflowState;
62 import org.opencastproject.workflow.api.WorkflowOperationHandler;
63 import org.opencastproject.workflow.api.WorkflowParsingException;
64 import org.opencastproject.workflow.api.WorkflowService;
65 import org.opencastproject.workflow.api.WorkflowSetImpl;
66 import org.opencastproject.workflow.api.WorkflowStateException;
67 import org.opencastproject.workflow.api.XmlWorkflowParser;
68 import org.opencastproject.workflow.impl.WorkflowServiceImpl;
69 import org.opencastproject.workflow.impl.WorkflowServiceImpl.HandlerRegistration;
70 import org.opencastproject.workspace.api.Workspace;
71
72 import com.google.common.util.concurrent.Striped;
73
74 import org.apache.commons.lang3.StringUtils;
75 import org.apache.commons.lang3.exception.ExceptionUtils;
76 import org.json.simple.JSONArray;
77 import org.json.simple.JSONObject;
78 import org.osgi.service.component.ComponentContext;
79 import org.osgi.service.component.annotations.Activate;
80 import org.osgi.service.component.annotations.Component;
81 import org.osgi.service.component.annotations.Reference;
82 import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource;
83 import org.slf4j.Logger;
84 import org.slf4j.LoggerFactory;
85
86 import java.util.HashMap;
87 import java.util.List;
88 import java.util.Map;
89 import java.util.Optional;
90 import java.util.concurrent.locks.Lock;
91
92 import javax.servlet.http.HttpServletResponse;
93 import javax.ws.rs.DELETE;
94 import javax.ws.rs.FormParam;
95 import javax.ws.rs.GET;
96 import javax.ws.rs.POST;
97 import javax.ws.rs.Path;
98 import javax.ws.rs.PathParam;
99 import javax.ws.rs.Produces;
100 import javax.ws.rs.QueryParam;
101 import javax.ws.rs.WebApplicationException;
102 import javax.ws.rs.core.MediaType;
103 import javax.ws.rs.core.Response;
104 import javax.ws.rs.core.Response.Status;
105
106
107
108
109 @Path("/workflow")
110 @RestService(name = "workflowservice", title = "Workflow Service", abstractText = "This service lists available workflows and starts, stops, suspends and resumes workflow instances.", notes = {
111 "All paths above are relative to the REST endpoint base (something like http://your.server/files)",
112 "If the service is down or not working it will return a status 503, this means the the underlying service is "
113 + "not working and is either restarting or has failed",
114 "A status code 500 means a general failure has occurred which is not recoverable and was not anticipated. In "
115 + "other words, there is a bug! You should file an error report with your server logs from the time when the "
116 + "error occurred: <a href=\"https://github.com/opencast/opencast/issues\">Opencast Issue Tracker</a>" })
117 @Component(
118 immediate = true,
119 service = WorkflowRestService.class,
120 property = {
121 "service.description=Workflow REST Endpoint",
122 "opencast.service.type=org.opencastproject.workflow",
123 "opencast.service.path=/workflow",
124 "opencast.service.jobproducer=true"
125 }
126 )
127 @JaxrsResource
128 public class WorkflowRestService extends AbstractJobProducerEndpoint {
129
130
131 private static final int DEFAULT_LIMIT = 20;
132
133 public static final String NEGATE_PREFIX = "-";
134
135 public static final String DESCENDING_SUFFIX = "_DESC";
136
137 private static final Logger logger = LoggerFactory.getLogger(WorkflowRestService.class);
138
139
140 protected String serverUrl = UrlSupport.DEFAULT_BASE_URL;
141
142 protected String serviceUrl = serverUrl + "/workflow";
143
144 private WorkflowService service;
145
146 protected ServiceRegistry serviceRegistry = null;
147
148 private Workspace workspace;
149
150
151 private final Striped<Lock> lock = Striped.lazyWeakLock(1024);
152
153
154
155
156
157
158
159 @Reference
160 protected void setServiceRegistry(ServiceRegistry serviceRegistry) {
161 this.serviceRegistry = serviceRegistry;
162 }
163
164
165
166
167
168
169
170 @Reference
171 public void setService(WorkflowService service) {
172 this.service = service;
173 }
174
175
176
177
178
179
180
181 @Reference
182 public void setWorkspace(Workspace workspace) {
183 this.workspace = workspace;
184 }
185
186
187
188
189
190
191
192 @Activate
193 public void activate(ComponentContext cc) {
194
195 if (cc == null) {
196 serverUrl = UrlSupport.DEFAULT_BASE_URL;
197 } else {
198 String ccServerUrl = cc.getBundleContext().getProperty(OpencastConstants.SERVER_URL_PROPERTY);
199 logger.info("configured server url is {}", ccServerUrl);
200 if (ccServerUrl == null) {
201 serverUrl = UrlSupport.DEFAULT_BASE_URL;
202 } else {
203 serverUrl = ccServerUrl;
204 }
205 serviceUrl = (String) cc.getProperties().get(RestConstants.SERVICE_PATH_PROPERTY);
206 }
207 }
208
209 @GET
210 @Produces(MediaType.TEXT_PLAIN)
211 @Path("/count")
212 @RestQuery(name = "count", description = "Returns the number of workflow instances in a specific state", returnDescription = "Returns the number of workflow instances in a specific state", restParameters = {
213 @RestParameter(name = "state", isRequired = false, description = "The workflow state", type = STRING)},
214 responses = { @RestResponse(responseCode = SC_OK, description = "The number of workflow instances.") })
215 public Response getCount(@QueryParam("state") WorkflowInstance.WorkflowState state,
216 @QueryParam("operation") String operation) {
217 try {
218 Long count = service.countWorkflowInstances(state);
219 return Response.ok(count).build();
220 } catch (WorkflowDatabaseException e) {
221 throw new WebApplicationException(e);
222 }
223 }
224
225 @GET
226 @Path("definitions.json")
227 @Produces(MediaType.APPLICATION_JSON)
228 @RestQuery(name = "definitions", description = "List all available workflow definitions as JSON", returnDescription = "Returns the workflow definitions as JSON", responses = { @RestResponse(responseCode = SC_OK, description = "The workflow definitions.") })
229 public WorkflowDefinitionSet getWorkflowDefinitionsAsJson() throws Exception {
230 return getWorkflowDefinitionsAsXml();
231 }
232
233 @GET
234 @Path("definitions.xml")
235 @Produces(MediaType.APPLICATION_XML)
236 @RestQuery(name = "definitions", description = "List all available workflow definitions as XML", returnDescription = "Returns the workflow definitions as XML", responses = { @RestResponse(responseCode = SC_OK, description = "The workflow definitions.") })
237 public WorkflowDefinitionSet getWorkflowDefinitionsAsXml() throws Exception {
238 List<WorkflowDefinition> list = service.listAvailableWorkflowDefinitions();
239 return new WorkflowDefinitionSet(list);
240 }
241
242 @GET
243 @Produces(MediaType.APPLICATION_JSON)
244 @Path("definition/{id}.json")
245 @RestQuery(name = "definitionasjson", description = "Returns a single workflow definition", returnDescription = "Returns a JSON representation of the workflow definition with the specified identifier", pathParameters = { @RestParameter(name = "id", isRequired = true, description = "The workflow definition identifier", type = STRING) }, responses = {
246 @RestResponse(responseCode = SC_OK, description = "The workflow definition."),
247 @RestResponse(responseCode = SC_NOT_FOUND, description = "Workflow definition not found.") })
248 public Response getWorkflowDefinitionAsJson(@PathParam("id") String workflowDefinitionId)
249 throws NotFoundException {
250 WorkflowDefinition def;
251 try {
252 def = service.getWorkflowDefinitionById(workflowDefinitionId);
253 } catch (WorkflowDatabaseException e) {
254 throw new WebApplicationException(e);
255 }
256 return Response.ok(def).build();
257 }
258
259 @GET
260 @Produces(MediaType.TEXT_XML)
261 @Path("definition/{id}.xml")
262 @RestQuery(name = "definitionasxml", description = "Returns a single workflow definition", returnDescription = "Returns an XML representation of the workflow definition with the specified identifier", pathParameters = { @RestParameter(name = "id", isRequired = true, description = "The workflow definition identifier", type = STRING) }, responses = {
263 @RestResponse(responseCode = SC_OK, description = "The workflow definition."),
264 @RestResponse(responseCode = SC_NOT_FOUND, description = "Workflow definition not found.") })
265 public Response getWorkflowDefinitionAsXml(@PathParam("id") String workflowDefinitionId)
266 throws NotFoundException {
267 return getWorkflowDefinitionAsJson(workflowDefinitionId);
268 }
269
270
271
272
273
274
275
276 @GET
277 @Produces(MediaType.TEXT_HTML)
278 @Path("configurationPanel")
279 @RestQuery(name = "configpanel", description = "Get the configuration panel for a specific workflow", returnDescription = "The HTML workflow configuration panel", restParameters = { @RestParameter(name = "definitionId", isRequired = false, description = "The workflow definition identifier", type = STRING) }, responses = { @RestResponse(responseCode = SC_OK, description = "The workflow configuration panel.") })
280 public Response getConfigurationPanel(@QueryParam("definitionId") String definitionId)
281 throws NotFoundException {
282 try {
283 final WorkflowDefinition def = service.getWorkflowDefinitionById(definitionId);
284 final String out = def.getConfigurationPanel();
285 return Response.ok(out).build();
286 } catch (WorkflowDatabaseException e) {
287 throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
288 }
289 }
290
291 @GET
292 @Produces(MediaType.APPLICATION_JSON)
293 @Path("mediaPackage/{id}/hasActiveWorkflows")
294 @RestQuery(name = "hasactiveworkflows", description = "Returns if a media package has active workflows",
295 returnDescription = "Returns wether the media package has active workflows as a boolean.",
296 pathParameters = {
297 @RestParameter(name = "id", isRequired = true, description = "The media package identifier", type = STRING) },
298 responses = {
299 @RestResponse(responseCode = SC_OK, description = "Whether the media package has active workflows.")})
300 public Response mediaPackageHasActiveWorkflows(@PathParam("id") String mediaPackageId) {
301 try {
302 return Response.ok(Boolean.toString(service.mediaPackageHasActiveWorkflows(mediaPackageId))).build();
303
304 } catch (WorkflowDatabaseException e) {
305 throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
306 }
307 }
308
309 @GET
310 @Produces(MediaType.APPLICATION_JSON)
311 @Path("mediaPackage/{id}/instances.json")
312 @RestQuery(name = "workflowsofmediapackage", description = "Returns the workflows for a media package",
313 returnDescription = "Returns the workflows that are associated with the media package as JSON.",
314 pathParameters = {
315 @RestParameter(name = "id", isRequired = true, description = "The media package identifier", type = STRING) },
316 responses = {
317 @RestResponse(responseCode = SC_OK, description = "Returns the workflows for a media package.")})
318 public Response getWorkflowsOfMediaPackage(@PathParam("id") String mediaPackageId) {
319 try {
320 return Response.ok(new WorkflowSetImpl(service.getWorkflowInstancesByMediaPackage(mediaPackageId))).build();
321 } catch (UnauthorizedException e) {
322 return Response.status(Status.FORBIDDEN).build();
323 } catch (WorkflowDatabaseException e) {
324 throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
325 }
326 }
327
328 @GET
329 @Produces(MediaType.APPLICATION_JSON)
330 @Path("mediaPackage/{id}/currentInstance.json")
331 @RestQuery(name = "currentworkflowofmediapackage", description = "Returns the current workflow for a media package",
332 returnDescription = "Returns the currentworkflow that are associated with the media package as JSON.",
333 pathParameters = {
334 @RestParameter(name = "id", isRequired = true, description = "The media package identifier", type = STRING) },
335 responses = {
336 @RestResponse(responseCode = SC_OK, description = "Returns the workflows for a media package."),
337 @RestResponse(responseCode = SC_NOT_FOUND, description = "Current workflow not found.") })
338 public Response getRunningWorkflowOfMediaPackage(@PathParam("id") String mediaPackageId) {
339 try {
340 Optional<WorkflowInstance> optWorkflowInstance = service.
341 getRunningWorkflowInstanceByMediaPackage(mediaPackageId, Permissions.Action.READ.toString());
342 if (optWorkflowInstance.isPresent()) {
343 return Response.ok(new JaxbWorkflowInstance(optWorkflowInstance.get())).build();
344 } else {
345 return Response.status(Response.Status.NOT_FOUND).build();
346 }
347
348 } catch (WorkflowException | UnauthorizedException e) {
349 throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
350 }
351 }
352
353 @GET
354 @Produces(MediaType.APPLICATION_JSON)
355 @Path("user/{id}/hasActiveWorkflows")
356 @RestQuery(name = "userhasactiveworkflows", description = "Returns if there are currently workflow(s) running that"
357 + "were started by the given user",
358 returnDescription = "Returns if there are currently workflow(s) running that were started by the given user "
359 + "as a boolean.",
360 pathParameters = {
361 @RestParameter(name = "id", isRequired = true, description = "The user identifier", type = STRING) },
362 responses = {
363 @RestResponse(responseCode = SC_OK, description = "Whether there are active workflow for the user.")})
364 public Response userHasActiveWorkflows(@PathParam("id") String userId) {
365 try {
366 return Response.ok(Boolean.toString(service.userHasActiveWorkflows(userId))).build();
367
368 } catch (WorkflowDatabaseException e) {
369 throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
370 }
371 }
372
373 @GET
374 @Produces(MediaType.TEXT_XML)
375 @Path("instance/{id}.xml")
376 @RestQuery(name = "workflowasxml", description = "Get a specific workflow instance.", returnDescription = "An XML representation of a workflow instance", pathParameters = { @RestParameter(name = "id", isRequired = true, description = "The workflow instance identifier", type = STRING) }, responses = {
377 @RestResponse(responseCode = SC_OK, description = "An XML representation of the workflow instance."),
378 @RestResponse(responseCode = SC_NOT_FOUND, description = "No workflow instance with that identifier exists.") })
379 public JaxbWorkflowInstance getWorkflowAsXml(@PathParam("id") long id) throws WorkflowDatabaseException,
380 NotFoundException, UnauthorizedException {
381 return new JaxbWorkflowInstance(service.getWorkflowById(id));
382 }
383
384 @GET
385 @Produces(MediaType.APPLICATION_JSON)
386 @Path("instance/{id}.json")
387 @RestQuery(name = "workflowasjson", description = "Get a specific workflow instance.", returnDescription = "A JSON representation of a workflow instance", pathParameters = { @RestParameter(name = "id", isRequired = true, description = "The workflow instance identifier", type = STRING) }, responses = {
388 @RestResponse(responseCode = SC_OK, description = "A JSON representation of the workflow instance."),
389 @RestResponse(responseCode = SC_NOT_FOUND, description = "No workflow instance with that identifier exists.") })
390 public JaxbWorkflowInstance getWorkflowAsJson(@PathParam("id") long id) throws WorkflowDatabaseException,
391 NotFoundException, UnauthorizedException {
392 return getWorkflowAsXml(id);
393 }
394
395 @POST
396 @Path("start")
397 @Produces(MediaType.TEXT_XML)
398 @RestQuery(name = "start", description = "Start a new workflow instance.", returnDescription = "An XML representation of the new workflow instance", restParameters = {
399 @RestParameter(name = "definition", isRequired = true, description = "The workflow definition ID or an XML representation of a workflow definition", type = TEXT, jaxbClass = WorkflowDefinitionImpl.class),
400 @RestParameter(name = "mediapackage", isRequired = true, description = "The XML representation of a mediapackage", type = TEXT, jaxbClass = MediaPackageImpl.class),
401 @RestParameter(name = "parent", isRequired = false, description = "An optional parent workflow instance identifier", type = STRING),
402 @RestParameter(name = "properties", isRequired = false, description = "An optional set of key=value\\n properties", type = TEXT) }, responses = {
403 @RestResponse(responseCode = SC_OK, description = "An XML representation of the new workflow instance."),
404 @RestResponse(responseCode = SC_UNAUTHORIZED, description = "You do not have permission to resume. Maybe you need to authenticate."),
405 @RestResponse(responseCode = SC_NOT_FOUND, description = "If the parent workflow does not exist") })
406 public JaxbWorkflowInstance start(@FormParam("definition") String workflowDefinitionXmlOrId,
407 @FormParam("mediapackage") MediaPackageImpl mp, @FormParam("parent") String parentWorkflowId,
408 @FormParam("properties") LocalHashMap localMap) {
409 if (mp == null || StringUtils.isBlank(workflowDefinitionXmlOrId))
410 throw new WebApplicationException(Status.BAD_REQUEST);
411
412 WorkflowDefinition workflowDefinition;
413 try {
414 workflowDefinition = service.getWorkflowDefinitionById(workflowDefinitionXmlOrId);
415 } catch (Exception e) {
416
417 try {
418 workflowDefinition = XmlWorkflowParser.parseWorkflowDefinition(workflowDefinitionXmlOrId);
419 } catch (WorkflowParsingException wpe) {
420 throw new WebApplicationException(wpe, Status.BAD_REQUEST);
421 }
422 }
423
424 WorkflowInstance instance = null;
425 try {
426 instance = startWorkflow(workflowDefinition, mp, parentWorkflowId, localMap);
427 } catch (UnauthorizedException e) {
428 throw new WebApplicationException(e, Status.UNAUTHORIZED);
429 }
430 return new JaxbWorkflowInstance(instance);
431 }
432
433 private WorkflowInstance startWorkflow(WorkflowDefinition workflowDefinition, MediaPackageImpl mp,
434 String parentWorkflowId, LocalHashMap localMap) throws UnauthorizedException {
435 Map<String, String> properties = new HashMap<String, String>();
436 if (localMap != null)
437 properties = localMap.getMap();
438
439 Long parentIdAsLong = null;
440 if (StringUtils.isNotEmpty(parentWorkflowId)) {
441 try {
442 parentIdAsLong = Long.parseLong(parentWorkflowId);
443 } catch (NumberFormatException e) {
444 throw new WebApplicationException(e, Status.BAD_REQUEST);
445 }
446 }
447
448 try {
449 return (WorkflowInstance) service.start(workflowDefinition, mp, parentIdAsLong, properties);
450 } catch (WorkflowException e) {
451 throw new WebApplicationException(e);
452 } catch (NotFoundException e) {
453 throw new WebApplicationException(e);
454 }
455 }
456
457 @POST
458 @Path("stop")
459 @Produces(MediaType.TEXT_XML)
460 @RestQuery(name = "stop", description = "Stops a workflow instance.", returnDescription = "An XML representation of the stopped workflow instance", restParameters = { @RestParameter(name = "id", isRequired = true, description = "The workflow instance identifier", type = STRING) }, responses = {
461 @RestResponse(responseCode = SC_OK, description = "An XML representation of the stopped workflow instance."),
462 @RestResponse(responseCode = SC_NOT_FOUND, description = "No running workflow instance with that identifier exists.") })
463 public JaxbWorkflowInstance stop(@FormParam("id") long workflowInstanceId) throws WorkflowException, NotFoundException,
464 UnauthorizedException {
465 WorkflowInstance instance = service.stop(workflowInstanceId);
466 return new JaxbWorkflowInstance(instance);
467 }
468
469 @DELETE
470 @Path("remove/{id}")
471 @Produces(MediaType.TEXT_PLAIN)
472 @RestQuery(name = "remove", description = "Danger! Permenantly removes a workflow instance including all its child jobs. In most circumstances, /stop is what you should use.", returnDescription = "HTTP 204 No Content", pathParameters = {
473 @RestParameter(name = "id", isRequired = true, description = "The workflow instance identifier", type = STRING)}, restParameters = {
474 @RestParameter(name = "force", isRequired = false, description = "If the workflow status should be ignored and the workflow removed anyway", type = Type.BOOLEAN, defaultValue = "false")}, responses = {
475 @RestResponse(responseCode = HttpServletResponse.SC_NO_CONTENT, description = "If workflow instance could be removed successfully, no content is returned"),
476 @RestResponse(responseCode = SC_NOT_FOUND, description = "No workflow instance with that identifier exists."),
477 @RestResponse(responseCode = SC_FORBIDDEN, description = "It's not allowed to remove other workflow instance statues than STOPPED, SUCCEEDED and FAILED (use force parameter to override AT YOUR OWN RISK).") })
478 public Response remove(@PathParam("id") long workflowInstanceId, @QueryParam("force") boolean force) throws WorkflowException, NotFoundException,
479 UnauthorizedException {
480 try {
481 service.remove(workflowInstanceId, force);
482 } catch (WorkflowStateException e) {
483 return Response.status(Status.FORBIDDEN).build();
484 }
485 return Response.noContent().build();
486 }
487
488 @POST
489 @Path("suspend")
490 @Produces(MediaType.TEXT_XML)
491 @RestQuery(name = "suspend", description = "Suspends a workflow instance.", returnDescription = "An XML representation of the suspended workflow instance", restParameters = { @RestParameter(name = "id", isRequired = true, description = "The workflow instance identifier", type = STRING) }, responses = {
492 @RestResponse(responseCode = SC_OK, description = "An XML representation of the suspended workflow instance."),
493 @RestResponse(responseCode = SC_NOT_FOUND, description = "No running workflow instance with that identifier exists.") })
494 public Response suspend(@FormParam("id") long workflowInstanceId) throws NotFoundException, UnauthorizedException {
495 try {
496 WorkflowInstance workflow = service.suspend(workflowInstanceId);
497 return Response.ok(new JaxbWorkflowInstance(workflow)).build();
498 } catch (WorkflowException e) {
499 throw new WebApplicationException(e);
500 }
501 }
502
503 @POST
504 @Path("resume")
505 @Produces(MediaType.TEXT_XML)
506 @RestQuery(name = "resume", description = "Resumes a suspended workflow instance.", returnDescription = "An XML representation of the resumed workflow instance", restParameters = { @RestParameter(name = "id", isRequired = true, description = "The workflow instance identifier", type = STRING) }, responses = {
507 @RestResponse(responseCode = SC_OK, description = "An XML representation of the resumed workflow instance."),
508 @RestResponse(responseCode = SC_CONFLICT, description = "Can not resume workflow not in paused state"),
509 @RestResponse(responseCode = SC_NOT_FOUND, description = "No suspended workflow instance with that identifier exists."),
510 @RestResponse(responseCode = SC_UNAUTHORIZED, description = "You do not have permission to resume. Maybe you need to authenticate.") })
511 public Response resume(@FormParam("id") long workflowInstanceId, @FormParam("properties") LocalHashMap properties)
512 throws NotFoundException, UnauthorizedException {
513 return resume(workflowInstanceId, null, properties);
514 }
515
516 @POST
517 @Path("replaceAndresume")
518 @Produces(MediaType.TEXT_XML)
519 @RestQuery(name = "replaceAndresume", description = "Replaces a suspended workflow instance with an updated version, and resumes the workflow.", returnDescription = "An XML representation of the updated and resumed workflow instance", restParameters = {
520 @RestParameter(name = "id", isRequired = true, description = "The workflow instance identifier", type = STRING),
521 @RestParameter(name = "mediapackage", isRequired = false, description = "The new Mediapackage", type = TEXT),
522 @RestParameter(name = "properties", isRequired = false, description = "Properties", type = TEXT) }, responses = {
523 @RestResponse(responseCode = SC_OK, description = "An XML representation of the updated and resumed workflow instance."),
524 @RestResponse(responseCode = SC_CONFLICT, description = "Can not resume workflow not in paused state"),
525 @RestResponse(responseCode = SC_NOT_FOUND, description = "No suspended workflow instance with that identifier exists."),
526 @RestResponse(responseCode = SC_UNAUTHORIZED, description = "You do not have permission to resume. Maybe you need to authenticate.") })
527 public Response resume(@FormParam("id") long workflowInstanceId,
528 @FormParam("mediapackage") final String mediaPackage, @FormParam("properties") LocalHashMap properties)
529 throws NotFoundException, UnauthorizedException {
530 final Map<String, String> map;
531 if (properties == null) {
532 map = new HashMap<String, String>();
533 } else {
534 map = properties.getMap();
535 }
536 final Lock lock = this.lock.get(workflowInstanceId);
537 lock.lock();
538 try {
539 WorkflowInstance workflow = service.getWorkflowById(workflowInstanceId);
540 if (!WorkflowState.PAUSED.equals(workflow.getState())) {
541 logger.warn("Can not resume workflow '{}', not in state paused but {}", workflow, workflow.getState());
542 return Response.status(Status.CONFLICT).build();
543 }
544
545 if (mediaPackage != null) {
546 MediaPackage newMp = MediaPackageParser.getFromXml(mediaPackage);
547 MediaPackage oldMp = workflow.getMediaPackage();
548
549
550 for (MediaPackageElement elem : oldMp.getElements()) {
551 if (MediaPackageSupport.contains(elem.getIdentifier(), newMp))
552 continue;
553 try {
554 workspace.delete(elem.getURI());
555 logger.info("Deleted removed mediapackge element {}", elem);
556 } catch (NotFoundException e) {
557 logger.info("Removed mediapackage element {} is already deleted", elem);
558 }
559 }
560
561 workflow.setMediaPackage(newMp);
562 service.update(workflow);
563 }
564 workflow = service.resume(workflowInstanceId, map);
565 return Response.ok(new JaxbWorkflowInstance(workflow)).build();
566 } catch (NotFoundException e) {
567 return Response.status(Status.NOT_FOUND).build();
568 } catch (UnauthorizedException e) {
569 return Response.status(Status.UNAUTHORIZED).build();
570 } catch (IllegalStateException e) {
571 logger.warn(ExceptionUtils.getMessage(e));
572 return Response.status(Status.CONFLICT).build();
573 } catch (WorkflowException e) {
574 logger.error(ExceptionUtils.getMessage(e), e);
575 return Response.serverError().build();
576 } catch (Exception e) {
577 logger.error(ExceptionUtils.getMessage(e), e);
578 return Response.serverError().build();
579 }
580 finally {
581 lock.unlock();
582 }
583 }
584
585 @POST
586 @Path("update")
587 @RestQuery(name = "update", description = "Updates a workflow instance.", returnDescription = "No content.", restParameters = { @RestParameter(name = "workflow", isRequired = true, description = "The XML representation of the workflow instance.", type = TEXT) }, responses = { @RestResponse(responseCode = SC_NO_CONTENT, description = "Workflow instance updated.") })
588 public Response update(@FormParam("workflow") String workflowInstance) throws NotFoundException,
589 UnauthorizedException {
590 try {
591 WorkflowInstance instance = XmlWorkflowParser.parseWorkflowInstance(workflowInstance);
592 service.update(instance);
593 return Response.noContent().build();
594 } catch (WorkflowException e) {
595 throw new WebApplicationException(e);
596 }
597 }
598
599 @GET
600 @Path("handlers.json")
601 @SuppressWarnings("unchecked")
602 @RestQuery(name = "handlers", description = "List all registered workflow operation handlers (implementations).", returnDescription = "A JSON representation of the registered workflow operation handlers.", responses = { @RestResponse(responseCode = SC_OK, description = "A JSON representation of the registered workflow operation handlers") })
603 public Response getOperationHandlers() {
604 JSONArray jsonArray = new JSONArray();
605 for (HandlerRegistration reg : ((WorkflowServiceImpl) service).getRegisteredHandlers()) {
606 WorkflowOperationHandler handler = reg.getHandler();
607 JSONObject jsonHandler = new JSONObject();
608 jsonHandler.put("id", handler.getId());
609 jsonHandler.put("description", handler.getDescription());
610 jsonArray.add(jsonHandler);
611 }
612 return Response.ok(jsonArray.toJSONString()).header("Content-Type", MediaType.APPLICATION_JSON).build();
613 }
614
615 @GET
616 @Path("statemappings.json")
617 @SuppressWarnings("unchecked")
618 @RestQuery(name = "statemappings", description = "Get all workflow state mappings",
619 returnDescription = "A JSON representation of the workflow state mappings.",
620 responses = { @RestResponse(responseCode = SC_OK, description = "A JSON representation of the workflow state mappings") })
621 public Response getStateMappings() {
622 return Response.ok(new JSONObject(service.getWorkflowStateMappings()).toJSONString())
623 .header("Content-Type", MediaType.APPLICATION_JSON).build();
624 }
625
626 @Path("/cleanup")
627 @RestQuery(name = "cleanup", description = "Cleans up workflow instances", returnDescription = "No return value", responses = {
628 @RestResponse(responseCode = SC_OK, description = "Cleanup OK"),
629 @RestResponse(responseCode = SC_BAD_REQUEST, description = "Couldn't parse given state"),
630 @RestResponse(responseCode = SC_UNAUTHORIZED, description = "You do not have permission to cleanup. Maybe you need to authenticate."),
631 @RestResponse(responseCode = SC_FORBIDDEN, description = "It's not allowed to delete other workflow instance statues than STOPPED, SUCCEEDED and FAILED") }, restParameters = {
632 @RestParameter(name = "buffer", type = Type.INTEGER, defaultValue = "30", isRequired = true, description = "Lifetime (buffer) in days a workflow instance should live"),
633 @RestParameter(name = "state", type = Type.STRING, isRequired = true, description = "Workflow instance state, only STOPPED, SUCCEEDED and FAILED are allowed values here") })
634 public Response cleanup(@FormParam("buffer") int buffer, @FormParam("state") String stateParam)
635 throws UnauthorizedException {
636
637 WorkflowInstance.WorkflowState state;
638 try {
639 state = WorkflowInstance.WorkflowState.valueOf(stateParam);
640 } catch (IllegalArgumentException e) {
641 return Response.status(Status.BAD_REQUEST).build();
642 }
643
644 if (state != WorkflowInstance.WorkflowState.SUCCEEDED && state != WorkflowInstance.WorkflowState.FAILED
645 && state != WorkflowInstance.WorkflowState.STOPPED)
646 return Response.status(Status.FORBIDDEN).build();
647
648 try {
649 service.cleanupWorkflowInstances(buffer, state);
650 return Response.ok().build();
651 } catch (WorkflowDatabaseException e) {
652 throw new WebApplicationException(e);
653 }
654 }
655
656
657
658
659
660
661 @Override
662 public JobProducer getService() {
663 if (service instanceof JobProducer) {
664 return (JobProducer) service;
665 } else {
666 return null;
667 }
668 }
669
670
671
672
673
674
675 @Override
676 public ServiceRegistry getServiceRegistry() {
677 return serviceRegistry;
678 }
679 }