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",
111 title = "Workflow Service",
112 abstractText = "This service lists available workflows and starts, stops, suspends and resumes workflow instances.",
113 notes = {
114 "All paths above are relative to the REST endpoint base (something like http://your.server/files)",
115 "If the service is down or not working it will return a status 503, this means the the underlying service is "
116 + "not working and is either restarting or has failed",
117 "A status code 500 means a general failure has occurred which is not recoverable and was not anticipated. In "
118 + "other words, there is a bug! You should file an error report with your server logs from the time "
119 + "when the error occurred: "
120 + "<a href=\"https://github.com/opencast/opencast/issues\">Opencast Issue Tracker</a>"
121 }
122 )
123 @Component(
124 immediate = true,
125 service = WorkflowRestService.class,
126 property = {
127 "service.description=Workflow REST Endpoint",
128 "opencast.service.type=org.opencastproject.workflow",
129 "opencast.service.path=/workflow",
130 "opencast.service.jobproducer=true"
131 }
132 )
133 @JaxrsResource
134 public class WorkflowRestService extends AbstractJobProducerEndpoint {
135
136
137 private static final int DEFAULT_LIMIT = 20;
138
139 public static final String NEGATE_PREFIX = "-";
140
141 public static final String DESCENDING_SUFFIX = "_DESC";
142
143 private static final Logger logger = LoggerFactory.getLogger(WorkflowRestService.class);
144
145
146 protected String serverUrl = UrlSupport.DEFAULT_BASE_URL;
147
148 protected String serviceUrl = serverUrl + "/workflow";
149
150 private WorkflowService service;
151
152 protected ServiceRegistry serviceRegistry = null;
153
154 private Workspace workspace;
155
156
157 private final Striped<Lock> lock = Striped.lazyWeakLock(1024);
158
159
160
161
162
163
164
165 @Reference
166 protected void setServiceRegistry(ServiceRegistry serviceRegistry) {
167 this.serviceRegistry = serviceRegistry;
168 }
169
170
171
172
173
174
175
176 @Reference
177 public void setService(WorkflowService service) {
178 this.service = service;
179 }
180
181
182
183
184
185
186
187 @Reference
188 public void setWorkspace(Workspace workspace) {
189 this.workspace = workspace;
190 }
191
192
193
194
195
196
197
198 @Activate
199 public void activate(ComponentContext cc) {
200
201 if (cc == null) {
202 serverUrl = UrlSupport.DEFAULT_BASE_URL;
203 } else {
204 String ccServerUrl = cc.getBundleContext().getProperty(OpencastConstants.SERVER_URL_PROPERTY);
205 logger.info("configured server url is {}", ccServerUrl);
206 if (ccServerUrl == null) {
207 serverUrl = UrlSupport.DEFAULT_BASE_URL;
208 } else {
209 serverUrl = ccServerUrl;
210 }
211 serviceUrl = (String) cc.getProperties().get(RestConstants.SERVICE_PATH_PROPERTY);
212 }
213 }
214
215 @GET
216 @Produces(MediaType.TEXT_PLAIN)
217 @Path("/count")
218 @RestQuery(name = "count",
219 description = "Returns the number of workflow instances in a specific state",
220 returnDescription = "Returns the number of workflow instances in a specific state",
221 restParameters = {
222 @RestParameter(name = "state", isRequired = false, description = "The workflow state", type = STRING)
223 },
224 responses = {
225 @RestResponse(responseCode = SC_OK, description = "The number of workflow instances.")
226 }
227 )
228 public Response getCount(@QueryParam("state") WorkflowInstance.WorkflowState state,
229 @QueryParam("operation") String operation) {
230 try {
231 Long count = service.countWorkflowInstances(state);
232 return Response.ok(count).build();
233 } catch (WorkflowDatabaseException e) {
234 throw new WebApplicationException(e);
235 }
236 }
237
238 @GET
239 @Path("definitions.json")
240 @Produces(MediaType.APPLICATION_JSON)
241 @RestQuery(name = "definitions",
242 description = "List all available workflow definitions as JSON",
243 returnDescription = "Returns the workflow definitions as JSON",
244 responses = {
245 @RestResponse(responseCode = SC_OK, description = "The workflow definitions.")
246 }
247 )
248 public WorkflowDefinitionSet getWorkflowDefinitionsAsJson() throws Exception {
249 return getWorkflowDefinitionsAsXml();
250 }
251
252 @GET
253 @Path("definitions.xml")
254 @Produces(MediaType.APPLICATION_XML)
255 @RestQuery(name = "definitions",
256 description = "List all available workflow definitions as XML",
257 returnDescription = "Returns the workflow definitions as XML",
258 responses = {
259 @RestResponse(responseCode = SC_OK, description = "The workflow definitions.")
260 }
261 )
262 public WorkflowDefinitionSet getWorkflowDefinitionsAsXml() throws Exception {
263 List<WorkflowDefinition> list = service.listAvailableWorkflowDefinitions();
264 return new WorkflowDefinitionSet(list);
265 }
266
267 @GET
268 @Produces(MediaType.APPLICATION_JSON)
269 @Path("definition/{id}.json")
270 @RestQuery(name = "definitionasjson",
271 description = "Returns a single workflow definition",
272 returnDescription = "Returns a JSON representation of the workflow definition with the specified identifier",
273 pathParameters = {
274 @RestParameter(name = "id", isRequired = true, description = "The workflow definition identifier",
275 type = STRING)
276 },
277 responses = {
278 @RestResponse(responseCode = SC_OK, description = "The workflow definition."),
279 @RestResponse(responseCode = SC_NOT_FOUND, description = "Workflow definition not found.")
280 }
281 )
282 public Response getWorkflowDefinitionAsJson(@PathParam("id") String workflowDefinitionId)
283 throws NotFoundException {
284 WorkflowDefinition def;
285 try {
286 def = service.getWorkflowDefinitionById(workflowDefinitionId);
287 } catch (WorkflowDatabaseException e) {
288 throw new WebApplicationException(e);
289 }
290 return Response.ok(def).build();
291 }
292
293 @GET
294 @Produces(MediaType.TEXT_XML)
295 @Path("definition/{id}.xml")
296 @RestQuery(name = "definitionasxml",
297 description = "Returns a single workflow definition",
298 returnDescription = "Returns an XML representation of the workflow definition with the specified identifier",
299 pathParameters = {
300 @RestParameter(name = "id", isRequired = true, description = "The workflow definition identifier",
301 type = STRING)
302 }, responses = {
303 @RestResponse(responseCode = SC_OK, description = "The workflow definition."),
304 @RestResponse(responseCode = SC_NOT_FOUND, description = "Workflow definition not found.")
305 }
306 )
307 public Response getWorkflowDefinitionAsXml(@PathParam("id") String workflowDefinitionId)
308 throws NotFoundException {
309 return getWorkflowDefinitionAsJson(workflowDefinitionId);
310 }
311
312
313
314
315
316
317
318 @GET
319 @Produces(MediaType.TEXT_HTML)
320 @Path("configurationPanel")
321 @RestQuery(name = "configpanel",
322 description = "Get the configuration panel for a specific workflow",
323 returnDescription = "The HTML workflow configuration panel",
324 restParameters = {
325 @RestParameter(name = "definitionId", isRequired = false, description = "The workflow definition identifier",
326 type = STRING)
327 },
328 responses = {
329 @RestResponse(responseCode = SC_OK, description = "The workflow configuration panel.")
330 }
331 )
332 public Response getConfigurationPanel(@QueryParam("definitionId") String definitionId)
333 throws NotFoundException {
334 try {
335 final WorkflowDefinition def = service.getWorkflowDefinitionById(definitionId);
336 final String out = def.getConfigurationPanel();
337 return Response.ok(out).build();
338 } catch (WorkflowDatabaseException e) {
339 throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
340 }
341 }
342
343 @GET
344 @Produces(MediaType.APPLICATION_JSON)
345 @Path("mediaPackage/{id}/hasActiveWorkflows")
346 @RestQuery(name = "hasactiveworkflows",
347 description = "Returns if a media package has active workflows",
348 returnDescription = "Returns wether the media package has active workflows as a boolean.",
349 pathParameters = {
350 @RestParameter(name = "id", isRequired = true, description = "The media package identifier", type = STRING)
351 },
352 responses = {
353 @RestResponse(responseCode = SC_OK, description = "Whether the media package has active workflows.")
354 }
355 )
356 public Response mediaPackageHasActiveWorkflows(@PathParam("id") String mediaPackageId) {
357 try {
358 return Response.ok(Boolean.toString(service.mediaPackageHasActiveWorkflows(mediaPackageId))).build();
359
360 } catch (WorkflowDatabaseException e) {
361 throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
362 }
363 }
364
365 @GET
366 @Produces(MediaType.APPLICATION_JSON)
367 @Path("mediaPackage/{id}/instances.json")
368 @RestQuery(name = "workflowsofmediapackage",
369 description = "Returns the workflows for a media package",
370 returnDescription = "Returns the workflows that are associated with the media package as JSON.",
371 pathParameters = {
372 @RestParameter(name = "id", isRequired = true, description = "The media package identifier", type = STRING)
373 },
374 responses = {
375 @RestResponse(responseCode = SC_OK, description = "Returns the workflows for a media package.")
376 }
377 )
378 public Response getWorkflowsOfMediaPackage(@PathParam("id") String mediaPackageId) {
379 try {
380 return Response.ok(new WorkflowSetImpl(service.getWorkflowInstancesByMediaPackage(mediaPackageId))).build();
381 } catch (UnauthorizedException e) {
382 return Response.status(Status.FORBIDDEN).build();
383 } catch (WorkflowDatabaseException e) {
384 throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
385 }
386 }
387
388 @GET
389 @Produces(MediaType.APPLICATION_JSON)
390 @Path("mediaPackage/{id}/currentInstance.json")
391 @RestQuery(name = "currentworkflowofmediapackage",
392 description = "Returns the current workflow for a media package",
393 returnDescription = "Returns the currentworkflow that are associated with the media package as JSON.",
394 pathParameters = {
395 @RestParameter(name = "id", isRequired = true, description = "The media package identifier", type = STRING) },
396 responses = {
397 @RestResponse(responseCode = SC_OK, description = "Returns the workflows for a media package."),
398 @RestResponse(responseCode = SC_NOT_FOUND, description = "Current workflow not found.")
399 }
400 )
401 public Response getRunningWorkflowOfMediaPackage(@PathParam("id") String mediaPackageId) {
402 try {
403 Optional<WorkflowInstance> optWorkflowInstance = service.
404 getRunningWorkflowInstanceByMediaPackage(mediaPackageId, Permissions.Action.READ.toString());
405 if (optWorkflowInstance.isPresent()) {
406 return Response.ok(new JaxbWorkflowInstance(optWorkflowInstance.get())).build();
407 } else {
408 return Response.status(Response.Status.NOT_FOUND).build();
409 }
410
411 } catch (WorkflowException | UnauthorizedException e) {
412 throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
413 }
414 }
415
416 @GET
417 @Produces(MediaType.APPLICATION_JSON)
418 @Path("user/{id}/hasActiveWorkflows")
419 @RestQuery(name = "userhasactiveworkflows",
420 description = "Returns if there are currently workflow(s) running that were started by the given user",
421 returnDescription = "Returns if there are currently workflow(s) running that were started by the given user "
422 + "as a boolean.",
423 pathParameters = {
424 @RestParameter(name = "id", isRequired = true, description = "The user identifier", type = STRING)
425 },
426 responses = {
427 @RestResponse(responseCode = SC_OK, description = "Whether there are active workflow for the user.")
428 }
429 )
430 public Response userHasActiveWorkflows(@PathParam("id") String userId) {
431 try {
432 return Response.ok(Boolean.toString(service.userHasActiveWorkflows(userId))).build();
433
434 } catch (WorkflowDatabaseException e) {
435 throw new WebApplicationException(Status.INTERNAL_SERVER_ERROR);
436 }
437 }
438
439 @GET
440 @Produces(MediaType.TEXT_XML)
441 @Path("instance/{id}.xml")
442 @RestQuery(name = "workflowasxml",
443 description = "Get a specific workflow instance.",
444 returnDescription = "An XML representation of a workflow instance",
445 pathParameters = {
446 @RestParameter(name = "id", isRequired = true, description = "The workflow instance identifier",
447 type = STRING)
448 },
449 responses = {
450 @RestResponse(responseCode = SC_OK, description = "An XML representation of the workflow instance."),
451 @RestResponse(responseCode = SC_NOT_FOUND, description = "No workflow instance with that identifier exists.")
452 }
453 )
454 public JaxbWorkflowInstance getWorkflowAsXml(@PathParam("id") long id) throws WorkflowDatabaseException,
455 NotFoundException, UnauthorizedException {
456 return new JaxbWorkflowInstance(service.getWorkflowById(id));
457 }
458
459 @GET
460 @Produces(MediaType.APPLICATION_JSON)
461 @Path("instance/{id}.json")
462 @RestQuery(name = "workflowasjson",
463 description = "Get a specific workflow instance.",
464 returnDescription = "A JSON representation of a workflow instance",
465 pathParameters = {
466 @RestParameter(name = "id", isRequired = true, description = "The workflow instance identifier",
467 type = STRING)
468 },
469 responses = {
470 @RestResponse(responseCode = SC_OK, description = "A JSON representation of the workflow instance."),
471 @RestResponse(responseCode = SC_NOT_FOUND, description = "No workflow instance with that identifier exists.")
472 }
473 )
474 public JaxbWorkflowInstance getWorkflowAsJson(@PathParam("id") long id) throws WorkflowDatabaseException,
475 NotFoundException, UnauthorizedException {
476 return getWorkflowAsXml(id);
477 }
478
479 @POST
480 @Path("start")
481 @Produces(MediaType.TEXT_XML)
482 @RestQuery(name = "start",
483 description = "Start a new workflow instance.",
484 returnDescription = "An XML representation of the new workflow instance",
485 restParameters = {
486 @RestParameter(name = "definition", isRequired = true, description = "The workflow definition ID or an XML "
487 + "representation of a workflow definition", type = TEXT, jaxbClass = WorkflowDefinitionImpl.class),
488 @RestParameter(name = "mediapackage", isRequired = true, description = "The XML representation of a "
489 + "mediapackage", type = TEXT, jaxbClass = MediaPackageImpl.class),
490 @RestParameter(name = "parent", isRequired = false, description = "An optional parent workflow instance "
491 + "identifier", type = STRING),
492 @RestParameter(name = "properties", isRequired = false, description = "An optional set of key=value\\n "
493 + "properties", type = TEXT) }, responses = {
494 @RestResponse(responseCode = SC_OK, description = "An XML representation of the new workflow instance."),
495 @RestResponse(responseCode = SC_UNAUTHORIZED, description = "You do not have permission to resume. Maybe you "
496 + "need to authenticate."),
497 @RestResponse(responseCode = SC_NOT_FOUND, description = "If the parent workflow does not exist")
498 }
499 )
500 public JaxbWorkflowInstance start(@FormParam("definition") String workflowDefinitionXmlOrId,
501 @FormParam("mediapackage") MediaPackageImpl mp, @FormParam("parent") String parentWorkflowId,
502 @FormParam("properties") LocalHashMap localMap) {
503 if (mp == null || StringUtils.isBlank(workflowDefinitionXmlOrId)) {
504 throw new WebApplicationException(Status.BAD_REQUEST);
505 }
506
507 WorkflowDefinition workflowDefinition;
508 try {
509 workflowDefinition = service.getWorkflowDefinitionById(workflowDefinitionXmlOrId);
510 } catch (Exception e) {
511
512 try {
513 workflowDefinition = XmlWorkflowParser.parseWorkflowDefinition(workflowDefinitionXmlOrId);
514 } catch (WorkflowParsingException wpe) {
515 throw new WebApplicationException(wpe, Status.BAD_REQUEST);
516 }
517 }
518
519 WorkflowInstance instance = null;
520 try {
521 instance = startWorkflow(workflowDefinition, mp, parentWorkflowId, localMap);
522 } catch (UnauthorizedException e) {
523 throw new WebApplicationException(e, Status.UNAUTHORIZED);
524 }
525 return new JaxbWorkflowInstance(instance);
526 }
527
528 private WorkflowInstance startWorkflow(WorkflowDefinition workflowDefinition, MediaPackageImpl mp,
529 String parentWorkflowId, LocalHashMap localMap) throws UnauthorizedException {
530 Map<String, String> properties = new HashMap<String, String>();
531 if (localMap != null) {
532 properties = localMap.getMap();
533 }
534
535 Long parentIdAsLong = null;
536 if (StringUtils.isNotEmpty(parentWorkflowId)) {
537 try {
538 parentIdAsLong = Long.parseLong(parentWorkflowId);
539 } catch (NumberFormatException e) {
540 throw new WebApplicationException(e, Status.BAD_REQUEST);
541 }
542 }
543
544 try {
545 return (WorkflowInstance) service.start(workflowDefinition, mp, parentIdAsLong, properties);
546 } catch (WorkflowException e) {
547 throw new WebApplicationException(e);
548 } catch (NotFoundException e) {
549 throw new WebApplicationException(e);
550 }
551 }
552
553 @POST
554 @Path("stop")
555 @Produces(MediaType.TEXT_XML)
556 @RestQuery(name = "stop",
557 description = "Stops a workflow instance.",
558 returnDescription = "An XML representation of the stopped workflow instance",
559 restParameters = {
560 @RestParameter(name = "id", isRequired = true, description = "The workflow instance identifier",
561 type = STRING)
562 },
563 responses = {
564 @RestResponse(responseCode = SC_OK, description = "An XML representation of the stopped workflow instance."),
565 @RestResponse(responseCode = SC_NOT_FOUND, description = "No running workflow instance with that identifier "
566 + "exists.")
567 }
568 )
569 public JaxbWorkflowInstance stop(@FormParam("id") long workflowInstanceId)
570 throws WorkflowException, NotFoundException, UnauthorizedException {
571 WorkflowInstance instance = service.stop(workflowInstanceId);
572 return new JaxbWorkflowInstance(instance);
573 }
574
575 @DELETE
576 @Path("remove/{id}")
577 @Produces(MediaType.TEXT_PLAIN)
578 @RestQuery(name = "remove",
579 description = "Danger! Permenantly removes a workflow instance including all its child jobs. In most "
580 + "circumstances, /stop is what you should use.",
581 returnDescription = "HTTP 204 No Content",
582 pathParameters = {
583 @RestParameter(name = "id", isRequired = true, description = "The workflow instance identifier",
584 type = STRING)
585 },
586 restParameters = {
587 @RestParameter(name = "force", isRequired = false, description = "If the workflow status should be ignored "
588 + "and the workflow removed anyway", type = Type.BOOLEAN, defaultValue = "false")
589 },
590 responses = {
591 @RestResponse(responseCode = HttpServletResponse.SC_NO_CONTENT, description = "If workflow instance could be "
592 + "removed successfully, no content is returned"),
593 @RestResponse(responseCode = SC_NOT_FOUND, description = "No workflow instance with that identifier exists."),
594 @RestResponse(responseCode = SC_FORBIDDEN, description = "It's not allowed to remove other workflow instance "
595 + "statues than STOPPED, SUCCEEDED and FAILED (use force parameter to override AT YOUR OWN RISK).")
596 }
597 )
598 public Response remove(@PathParam("id") long workflowInstanceId, @QueryParam("force") boolean force)
599 throws WorkflowException, NotFoundException, UnauthorizedException {
600 try {
601 service.remove(workflowInstanceId, force);
602 } catch (WorkflowStateException e) {
603 return Response.status(Status.FORBIDDEN).build();
604 }
605 return Response.noContent().build();
606 }
607
608 @POST
609 @Path("suspend")
610 @Produces(MediaType.TEXT_XML)
611 @RestQuery(name = "suspend",
612 description = "Suspends a workflow instance.",
613 returnDescription = "An XML representation of the suspended workflow instance",
614 restParameters = {
615 @RestParameter(name = "id", isRequired = true, description = "The workflow instance identifier",
616 type = STRING)
617 },
618 responses = {
619 @RestResponse(responseCode = SC_OK, description = "An XML representation of the suspended workflow "
620 + "instance."),
621 @RestResponse(responseCode = SC_NOT_FOUND, description = "No running workflow instance with that identifier "
622 + "exists.")
623 }
624 )
625 public Response suspend(@FormParam("id") long workflowInstanceId) throws NotFoundException, UnauthorizedException {
626 try {
627 WorkflowInstance workflow = service.suspend(workflowInstanceId);
628 return Response.ok(new JaxbWorkflowInstance(workflow)).build();
629 } catch (WorkflowException e) {
630 throw new WebApplicationException(e);
631 }
632 }
633
634 @POST
635 @Path("resume")
636 @Produces(MediaType.TEXT_XML)
637 @RestQuery(name = "resume",
638 description = "Resumes a suspended workflow instance.",
639 returnDescription = "An XML representation of the resumed workflow instance",
640 restParameters = {
641 @RestParameter(name = "id", isRequired = true, description = "The workflow instance identifier",
642 type = STRING)
643 },
644 responses = {
645 @RestResponse(responseCode = SC_OK, description = "An XML representation of the resumed workflow instance."),
646 @RestResponse(responseCode = SC_CONFLICT, description = "Can not resume workflow not in paused state"),
647 @RestResponse(responseCode = SC_NOT_FOUND, description = "No suspended workflow instance with that "
648 + "identifier exists."),
649 @RestResponse(responseCode = SC_UNAUTHORIZED, description = "You do not have permission to resume. "
650 + "Maybe you need to authenticate.")
651 }
652 )
653 public Response resume(@FormParam("id") long workflowInstanceId, @FormParam("properties") LocalHashMap properties)
654 throws NotFoundException, UnauthorizedException {
655 return resume(workflowInstanceId, null, properties);
656 }
657
658 @POST
659 @Path("replaceAndresume")
660 @Produces(MediaType.TEXT_XML)
661 @RestQuery(name = "replaceAndresume",
662 description = "Replaces a suspended workflow instance with an updated version, and resumes the workflow.",
663 returnDescription = "An XML representation of the updated and resumed workflow instance",
664 restParameters = {
665 @RestParameter(name = "id", isRequired = true, description = "The workflow instance identifier",
666 type = STRING),
667 @RestParameter(name = "mediapackage", isRequired = false, description = "The new Mediapackage", type = TEXT),
668 @RestParameter(name = "properties", isRequired = false, description = "Properties", type = TEXT) },
669 responses = {
670 @RestResponse(responseCode = SC_OK, description = "An XML representation of the updated and resumed "
671 + "workflow instance."),
672 @RestResponse(responseCode = SC_CONFLICT, description = "Can not resume workflow not in paused state"),
673 @RestResponse(responseCode = SC_NOT_FOUND, description = "No suspended workflow instance with that "
674 + "identifier exists."),
675 @RestResponse(responseCode = SC_UNAUTHORIZED, description = "You do not have permission to resume. "
676 + "Maybe you need to authenticate.")
677 }
678 )
679 public Response resume(@FormParam("id") long workflowInstanceId,
680 @FormParam("mediapackage") final String mediaPackage, @FormParam("properties") LocalHashMap properties)
681 throws NotFoundException, UnauthorizedException {
682 final Map<String, String> map;
683 if (properties == null) {
684 map = new HashMap<String, String>();
685 } else {
686 map = properties.getMap();
687 }
688 final Lock lock = this.lock.get(workflowInstanceId);
689 lock.lock();
690 try {
691 WorkflowInstance workflow = service.getWorkflowById(workflowInstanceId);
692 if (!WorkflowState.PAUSED.equals(workflow.getState())) {
693 logger.warn("Can not resume workflow '{}', not in state paused but {}", workflow, workflow.getState());
694 return Response.status(Status.CONFLICT).build();
695 }
696
697 if (mediaPackage != null) {
698 MediaPackage newMp = MediaPackageParser.getFromXml(mediaPackage);
699 MediaPackage oldMp = workflow.getMediaPackage();
700
701
702 for (MediaPackageElement elem : oldMp.getElements()) {
703 if (MediaPackageSupport.contains(elem.getIdentifier(), newMp)) {
704 continue;
705 }
706 try {
707 workspace.delete(elem.getURI());
708 logger.info("Deleted removed mediapackge element {}", elem);
709 } catch (NotFoundException e) {
710 logger.info("Removed mediapackage element {} is already deleted", elem);
711 }
712 }
713
714 workflow.setMediaPackage(newMp);
715 service.update(workflow);
716 }
717 workflow = service.resume(workflowInstanceId, map);
718 return Response.ok(new JaxbWorkflowInstance(workflow)).build();
719 } catch (NotFoundException e) {
720 return Response.status(Status.NOT_FOUND).build();
721 } catch (UnauthorizedException e) {
722 return Response.status(Status.UNAUTHORIZED).build();
723 } catch (IllegalStateException e) {
724 logger.warn(ExceptionUtils.getMessage(e));
725 return Response.status(Status.CONFLICT).build();
726 } catch (WorkflowException e) {
727 logger.error(ExceptionUtils.getMessage(e), e);
728 return Response.serverError().build();
729 } catch (Exception e) {
730 logger.error(ExceptionUtils.getMessage(e), e);
731 return Response.serverError().build();
732 }
733 finally {
734 lock.unlock();
735 }
736 }
737
738 @POST
739 @Path("update")
740 @RestQuery(name = "update",
741 description = "Updates a workflow instance.",
742 returnDescription = "No content.",
743 restParameters = {
744 @RestParameter(name = "workflow", isRequired = true, description = "The XML representation of the "
745 + "workflow instance.", type = TEXT)
746 },
747 responses = {
748 @RestResponse(responseCode = SC_NO_CONTENT, description = "Workflow instance updated.")
749 }
750 )
751 public Response update(@FormParam("workflow") String workflowInstance)
752 throws NotFoundException, UnauthorizedException {
753 try {
754 WorkflowInstance instance = XmlWorkflowParser.parseWorkflowInstance(workflowInstance);
755 service.update(instance);
756 return Response.noContent().build();
757 } catch (WorkflowException e) {
758 throw new WebApplicationException(e);
759 }
760 }
761
762 @GET
763 @Path("handlers.json")
764 @SuppressWarnings("unchecked")
765 @RestQuery(name = "handlers",
766 description = "List all registered workflow operation handlers (implementations).",
767 returnDescription = "A JSON representation of the registered workflow operation handlers.",
768 responses = {
769 @RestResponse(responseCode = SC_OK, description = "A JSON representation of the registered workflow "
770 + "operation handlers")
771 }
772 )
773 public Response getOperationHandlers() {
774 JSONArray jsonArray = new JSONArray();
775 for (HandlerRegistration reg : ((WorkflowServiceImpl) service).getRegisteredHandlers()) {
776 WorkflowOperationHandler handler = reg.getHandler();
777 JSONObject jsonHandler = new JSONObject();
778 jsonHandler.put("id", handler.getId());
779 jsonHandler.put("description", handler.getDescription());
780 jsonArray.add(jsonHandler);
781 }
782 return Response.ok(jsonArray.toJSONString()).header("Content-Type", MediaType.APPLICATION_JSON).build();
783 }
784
785 @GET
786 @Path("statemappings.json")
787 @SuppressWarnings("unchecked")
788 @RestQuery(name = "statemappings",
789 description = "Get all workflow state mappings",
790 returnDescription = "A JSON representation of the workflow state mappings.",
791 responses = {
792 @RestResponse(responseCode = SC_OK, description = "A JSON representation of the workflow state mappings")
793 }
794 )
795 public Response getStateMappings() {
796 return Response.ok(new JSONObject(service.getWorkflowStateMappings()).toJSONString())
797 .header("Content-Type", MediaType.APPLICATION_JSON).build();
798 }
799
800 @Path("/cleanup")
801 @RestQuery(name = "cleanup",
802 description = "Cleans up workflow instances",
803 returnDescription = "No return value",
804 responses = {
805 @RestResponse(responseCode = SC_OK, description = "Cleanup OK"),
806 @RestResponse(responseCode = SC_BAD_REQUEST, description = "Couldn't parse given state"),
807 @RestResponse(responseCode = SC_UNAUTHORIZED, description = "You do not have permission to cleanup. "
808 + "Maybe you need to authenticate."),
809 @RestResponse(responseCode = SC_FORBIDDEN, description = "It's not allowed to delete other workflow "
810 + "instance statues than STOPPED, SUCCEEDED and FAILED")
811 }, restParameters = {
812 @RestParameter(name = "buffer", type = Type.INTEGER, defaultValue = "30", isRequired = true,
813 description = "Lifetime (buffer) in days a workflow instance should live"),
814 @RestParameter(name = "state", type = Type.STRING, isRequired = true,
815 description = "Workflow instance state, only STOPPED, SUCCEEDED and FAILED are allowed values here")
816 }
817 )
818 public Response cleanup(@FormParam("buffer") int buffer, @FormParam("state") String stateParam)
819 throws UnauthorizedException {
820
821 WorkflowInstance.WorkflowState state;
822 try {
823 state = WorkflowInstance.WorkflowState.valueOf(stateParam);
824 } catch (IllegalArgumentException e) {
825 return Response.status(Status.BAD_REQUEST).build();
826 }
827
828 if (state != WorkflowInstance.WorkflowState.SUCCEEDED && state != WorkflowInstance.WorkflowState.FAILED
829 && state != WorkflowInstance.WorkflowState.STOPPED) {
830 return Response.status(Status.FORBIDDEN).build();
831 }
832
833 try {
834 service.cleanupWorkflowInstances(buffer, state);
835 return Response.ok().build();
836 } catch (WorkflowDatabaseException e) {
837 throw new WebApplicationException(e);
838 }
839 }
840
841
842
843
844
845
846 @Override
847 public JobProducer getService() {
848 if (service instanceof JobProducer) {
849 return (JobProducer) service;
850 } else {
851 return null;
852 }
853 }
854
855
856
857
858
859
860 @Override
861 public ServiceRegistry getServiceRegistry() {
862 return serviceRegistry;
863 }
864 }