1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.opencastproject.external.endpoint;
22
23 import static org.apache.commons.lang3.StringUtils.defaultString;
24 import static org.apache.commons.lang3.exception.ExceptionUtils.getMessage;
25 import static org.opencastproject.index.service.util.JSONUtils.arrayToJsonArray;
26 import static org.opencastproject.index.service.util.JSONUtils.safeString;
27 import static org.opencastproject.util.doc.rest.RestParameter.Type.BOOLEAN;
28 import static org.opencastproject.util.doc.rest.RestParameter.Type.INTEGER;
29 import static org.opencastproject.util.doc.rest.RestParameter.Type.STRING;
30 import static org.opencastproject.util.requests.SortCriterion.Order.Descending;
31
32 import org.opencastproject.external.common.ApiMediaType;
33 import org.opencastproject.external.common.ApiResponseBuilder;
34 import org.opencastproject.index.service.util.RestUtils;
35 import org.opencastproject.util.NotFoundException;
36 import org.opencastproject.util.RestUtil;
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 import org.opencastproject.util.requests.SortCriterion;
42 import org.opencastproject.workflow.api.RetryStrategy;
43 import org.opencastproject.workflow.api.WorkflowDatabaseException;
44 import org.opencastproject.workflow.api.WorkflowDefinition;
45 import org.opencastproject.workflow.api.WorkflowOperationDefinition;
46 import org.opencastproject.workflow.api.WorkflowService;
47
48 import com.google.gson.JsonArray;
49 import com.google.gson.JsonObject;
50
51 import org.apache.commons.collections4.comparators.ComparatorChain;
52 import org.apache.commons.lang3.ArrayUtils;
53 import org.apache.commons.lang3.StringUtils;
54 import org.osgi.service.component.ComponentContext;
55 import org.osgi.service.component.annotations.Activate;
56 import org.osgi.service.component.annotations.Component;
57 import org.osgi.service.component.annotations.Reference;
58 import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource;
59 import org.slf4j.Logger;
60 import org.slf4j.LoggerFactory;
61
62 import java.util.ArrayList;
63 import java.util.List;
64 import java.util.stream.Collectors;
65 import java.util.stream.Stream;
66
67 import javax.servlet.http.HttpServletResponse;
68 import javax.ws.rs.GET;
69 import javax.ws.rs.HeaderParam;
70 import javax.ws.rs.Path;
71 import javax.ws.rs.PathParam;
72 import javax.ws.rs.Produces;
73 import javax.ws.rs.QueryParam;
74 import javax.ws.rs.core.Response;
75
76 @Path("/api/workflow-definitions")
77 @Produces({ ApiMediaType.JSON, ApiMediaType.VERSION_1_1_0, ApiMediaType.VERSION_1_2_0, ApiMediaType.VERSION_1_3_0,
78 ApiMediaType.VERSION_1_4_0, ApiMediaType.VERSION_1_5_0, ApiMediaType.VERSION_1_6_0,
79 ApiMediaType.VERSION_1_7_0, ApiMediaType.VERSION_1_8_0,
80 ApiMediaType.VERSION_1_9_0, ApiMediaType.VERSION_1_10_0, ApiMediaType.VERSION_1_11_0 })
81 @RestService(
82 name = "externalapiworkflowdefinitions",
83 title = "External API Workflow Definitions Service",
84 notes = {},
85 abstractText = "Provides resources and operations related to the workflow definitions"
86 )
87 @Component(
88 immediate = true,
89 service = WorkflowDefinitionsEndpoint.class,
90 property = {
91 "service.description=External API - Workflow Definitions Endpoint",
92 "opencast.service.type=org.opencastproject.external.workflows.definitions",
93 "opencast.service.path=/api/workflow-definitions"
94 }
95 )
96 @JaxrsResource
97 public class WorkflowDefinitionsEndpoint {
98
99
100
101
102 private static final Logger logger = LoggerFactory.getLogger(WorkflowDefinitionsEndpoint.class);
103
104
105
106
107 private WorkflowService workflowService;
108
109
110
111
112 public WorkflowService getWorkflowService() {
113 return workflowService;
114 }
115
116
117
118
119 @Reference
120 public void setWorkflowService(WorkflowService workflowService) {
121 this.workflowService = workflowService;
122 }
123
124
125
126
127 @Activate
128 void activate(ComponentContext cc) {
129 logger.info("Activating External API - Workflow Definitions Endpoint");
130 }
131
132 @GET
133 @Path("/")
134 @RestQuery(
135 name = "getworkflowdefinitions",
136 description = "Returns a list of workflow definition.",
137 returnDescription = "",
138 restParameters = {
139 @RestParameter(name = "withoperations", description = "Whether the workflow operations should be included in "
140 + "the response", isRequired = false, type = BOOLEAN),
141 @RestParameter(name = "withconfigurationpanel", description = "Whether the workflow configuration panel "
142 + "should be included in the response", isRequired = false, type = BOOLEAN),
143 @RestParameter(name = "filter", description = "Usage [Filter Name]:[Value to Filter With]. Available filter: "
144 + "\"tag\"", isRequired = false, type = STRING),
145 @RestParameter(name = "sort", description = "Sort the results based upon a list of comma seperated sorting "
146 + "criteria. In the comma seperated list each type of sorting is specified as a pair such as: "
147 + "<Sort Name>:ASC or <Sort Name>:DESC. Adding the suffix ASC or DESC sets the order as ascending or "
148 + "descending order and is mandatory.", isRequired = false, type = STRING),
149 @RestParameter(name = "limit", description = "The maximum number of results to return for a single request.",
150 isRequired = false, type = INTEGER),
151 @RestParameter(name = "offset", description = "The index of the first result to return.",
152 isRequired = false, type = INTEGER) },
153 responses = {
154 @RestResponse(description = "A (potentially empty) list of workflow definitions is returned.",
155 responseCode = HttpServletResponse.SC_OK),
156 @RestResponse(description = "The request is invalid or inconsistent.",
157 responseCode = HttpServletResponse.SC_BAD_REQUEST)
158 })
159 public Response getWorkflowDefinitions(@HeaderParam("Accept") String acceptHeader,
160 @QueryParam("withoperations") boolean withOperations,
161 @QueryParam("withconfigurationpanel") boolean withConfigurationPanel,
162 @QueryParam("withconfigurationpaneljson") boolean withConfigurationPanelJson,
163 @QueryParam("filter") String filter,
164 @QueryParam("sort") String sort,
165 @QueryParam("offset") Integer offset,
166 @QueryParam("limit") Integer limit) {
167 Stream<WorkflowDefinition> workflowDefinitions;
168 try {
169 workflowDefinitions = workflowService.listAvailableWorkflowDefinitions().stream();
170 } catch (WorkflowDatabaseException e) {
171 logger.error("The workflow service was not able to get the workflow definitions:", e);
172 return ApiResponseBuilder.serverError("Could not retrieve workflow definitions, reason: '%s'", getMessage(e));
173 }
174
175
176 if (StringUtils.isNotBlank(filter)) {
177 for (String f : filter.split(",")) {
178 int sepIdx = f.indexOf(':');
179 if (sepIdx < 0 || sepIdx == f.length() - 1) {
180 logger.debug("No value for filter {} in filters list: {}", f, filter);
181 continue;
182 }
183 String name = f.substring(0, sepIdx);
184 String value = f.substring(sepIdx + 1);
185
186 if ("tag".equals(name)) {
187 workflowDefinitions = workflowDefinitions.filter(wd -> ArrayUtils.contains(wd.getTags(), value));
188 }
189 }
190 }
191
192
193
194 ComparatorChain<WorkflowDefinition> comparator = new ComparatorChain<>();
195 if (StringUtils.isNoneBlank(sort)) {
196 ArrayList<SortCriterion> sortCriteria = RestUtils.parseSortQueryParameter(sort);
197 for (SortCriterion criterion : sortCriteria) {
198 switch (criterion.getFieldName()) {
199 case "identifier":
200 comparator.addComparator((wd1, wd2) -> {
201 String s1 = defaultString(wd1.getId());
202 String s2 = defaultString(wd2.getId());
203 if (criterion.getOrder() == Descending) {
204 return s2.compareTo(s1);
205 }
206 return s1.compareTo(s2);
207 });
208 break;
209 case "title":
210 comparator.addComparator((wd1, wd2) -> {
211 String s1 = defaultString(wd1.getTitle());
212 String s2 = defaultString(wd2.getTitle());
213 if (criterion.getOrder() == Descending) {
214 return s2.compareTo(s1);
215 }
216 return s1.compareTo(s2);
217 });
218 break;
219 case "displayorder":
220 comparator.addComparator((wd1, wd2) -> {
221 if (criterion.getOrder() == Descending) {
222 return Integer.compare(wd2.getDisplayOrder(), wd1.getDisplayOrder());
223 }
224 return Integer.compare(wd1.getDisplayOrder(), wd2.getDisplayOrder());
225 });
226 break;
227 default:
228 return RestUtil.R.badRequest(
229 String.format("Unknown search criterion in request: %s", criterion.getFieldName()));
230 }
231 }
232 }
233 if (comparator.size() > 0) {
234 workflowDefinitions = workflowDefinitions.sorted(comparator);
235 }
236
237
238 if (offset != null && offset > 0) {
239 workflowDefinitions = workflowDefinitions.skip(offset);
240 }
241
242
243 if (limit != null && limit > 0) {
244 workflowDefinitions = workflowDefinitions.limit(limit);
245 }
246
247 List<JsonObject> jsonObjects = workflowDefinitions
248 .map(wd -> workflowDefinitionToJSON(wd, withOperations, withConfigurationPanel, withConfigurationPanelJson))
249 .collect(Collectors.toList());
250
251 JsonArray jsonArray = new JsonArray();
252 for (JsonObject obj : jsonObjects) {
253 jsonArray.add(obj);
254 }
255
256 return ApiResponseBuilder.Json.ok(acceptHeader, jsonArray);
257 }
258
259 @GET
260 @Path("{workflowDefinitionId}")
261 @RestQuery(
262 name = "getworkflowdefinition",
263 description = "Returns a single workflow definition.",
264 returnDescription = "",
265 pathParameters = {
266 @RestParameter(name = "workflowDefinitionId", description = "The workflow definition id", isRequired = true,
267 type = STRING)
268 },
269 restParameters = {
270 @RestParameter(name = "withoperations", description = "Whether the workflow operations should be included in "
271 + "the response", isRequired = false, type = BOOLEAN),
272 @RestParameter(name = "withconfigurationpaneljson", description = "Whether the workflow configuration panel "
273 + "in JSON should be included in the response", isRequired = false, type = BOOLEAN),
274 @RestParameter(name = "withconfigurationpanel", description = "Whether the workflow configuration panel "
275 + "should be included in the response", isRequired = false, type = BOOLEAN)
276 },
277 responses = {
278 @RestResponse(description = "The workflow definition is returned.",
279 responseCode = HttpServletResponse.SC_OK),
280 @RestResponse(description = "The specified workflow definition does not exist.",
281 responseCode = HttpServletResponse.SC_NOT_FOUND)
282 })
283 public Response getWorkflowDefinition(@HeaderParam("Accept") String acceptHeader,
284 @PathParam("workflowDefinitionId") String id, @QueryParam("withoperations") boolean withOperations,
285 @QueryParam("withconfigurationpanel") boolean withConfigurationPanel,
286 @QueryParam("withconfigurationpaneljson") boolean withConfigurationPanelJson) throws Exception {
287 WorkflowDefinition wd;
288 try {
289 wd = workflowService.getWorkflowDefinitionById(id);
290 } catch (NotFoundException e) {
291 return ApiResponseBuilder.notFound("Cannot find workflow definition with id '%s'.", id);
292 }
293
294 return ApiResponseBuilder.Json.ok(acceptHeader, workflowDefinitionToJSON(wd, withOperations,
295 withConfigurationPanel, withConfigurationPanelJson));
296 }
297
298 private JsonObject workflowDefinitionToJSON(WorkflowDefinition wd, boolean withOperations,
299 boolean withConfigurationPanel, boolean withConfigurationPanelJson) {
300 JsonObject json = new JsonObject();
301
302 json.addProperty("identifier", wd.getId());
303 json.addProperty("title", safeString(wd.getTitle()));
304 json.addProperty("description", safeString(wd.getDescription()));
305 json.add("tags", arrayToJsonArray(wd.getTags()));
306 if (withConfigurationPanel) {
307 json.addProperty("configuration_panel", safeString(wd.getConfigurationPanel()));
308 }
309 if (withConfigurationPanelJson) {
310 json.addProperty("configuration_panel_json", safeString(wd.getConfigurationPanelJson()));
311 }
312 if (withOperations) {
313 JsonArray operationsArray = new JsonArray();
314 for (WorkflowOperationDefinition op : wd.getOperations()) {
315 operationsArray.add(workflowOperationDefinitionToJSON(op));
316 }
317 json.add("operations", operationsArray);
318 }
319
320 return json;
321 }
322
323 private JsonObject workflowOperationDefinitionToJSON(WorkflowOperationDefinition wod) {
324 JsonObject json = new JsonObject();
325
326 json.addProperty("operation", wod.getId());
327 json.addProperty("description", safeString(wod.getDescription()));
328 JsonObject configJson = new JsonObject();
329 for (String key : wod.getConfigurationKeys()) {
330 String value = wod.getConfiguration(key);
331 configJson.addProperty(key, value);
332 }
333 json.add("configuration", configJson);
334 json.addProperty("if", safeString(wod.getExecutionCondition()));
335 json.addProperty("unless", safeString(wod.getSkipCondition()));
336 json.addProperty("fail_workflow_on_error", wod.isFailWorkflowOnException());
337 json.addProperty("error_handler_workflow", safeString(wod.getExceptionHandlingWorkflow()));
338 String retryStrategy = new RetryStrategy.Adapter().marshal(wod.getRetryStrategy());
339 json.addProperty("retry_strategy", safeString(retryStrategy));
340 json.addProperty("max_attempts", wod.getMaxAttempts());
341
342 return json;
343 }
344 }