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(name = "externalapiworkflowdefinitions", title = "External API Workflow Definitions Service", notes = {},
82 abstractText = "Provides resources and operations related to the workflow definitions")
83 @Component(
84 immediate = true,
85 service = WorkflowDefinitionsEndpoint.class,
86 property = {
87 "service.description=External API - Workflow Definitions Endpoint",
88 "opencast.service.type=org.opencastproject.external.workflows.definitions",
89 "opencast.service.path=/api/workflow-definitions"
90 }
91 )
92 @JaxrsResource
93 public class WorkflowDefinitionsEndpoint {
94
95
96
97
98 private static final Logger logger = LoggerFactory.getLogger(WorkflowDefinitionsEndpoint.class);
99
100
101
102
103 private WorkflowService workflowService;
104
105
106
107
108 public WorkflowService getWorkflowService() {
109 return workflowService;
110 }
111
112
113
114
115 @Reference
116 public void setWorkflowService(WorkflowService workflowService) {
117 this.workflowService = workflowService;
118 }
119
120
121
122
123 @Activate
124 void activate(ComponentContext cc) {
125 logger.info("Activating External API - Workflow Definitions Endpoint");
126 }
127
128 @GET
129 @Path("/")
130 @RestQuery(name = "getworkflowdefinitions", description = "Returns a list of workflow definition.", returnDescription = "", restParameters = {
131 @RestParameter(name = "withoperations", description = "Whether the workflow operations should be included in the response", isRequired = false, type = BOOLEAN),
132 @RestParameter(name = "withconfigurationpanel", description = "Whether the workflow configuration panel should be included in the response", isRequired = false, type = BOOLEAN),
133 @RestParameter(name = "filter", description = "Usage [Filter Name]:[Value to Filter With]. Available filter: \"tag\"", isRequired = false, type = STRING),
134 @RestParameter(name = "sort", description = "Sort the results based upon a list of comma seperated sorting criteria. In the comma seperated list each type of sorting is specified as a pair such as: <Sort Name>:ASC or <Sort Name>:DESC. Adding the suffix ASC or DESC sets the order as ascending or descending order and is mandatory.", isRequired = false, type = STRING),
135 @RestParameter(name = "limit", description = "The maximum number of results to return for a single request.", isRequired = false, type = INTEGER),
136 @RestParameter(name = "offset", description = "The index of the first result to return.", isRequired = false, type = INTEGER) }, responses = {
137 @RestResponse(description = "A (potentially empty) list of workflow definitions is returned.", responseCode = HttpServletResponse.SC_OK),
138 @RestResponse(description = "The request is invalid or inconsistent.", responseCode = HttpServletResponse.SC_BAD_REQUEST) })
139 public Response getWorkflowDefinitions(@HeaderParam("Accept") String acceptHeader,
140 @QueryParam("withoperations") boolean withOperations,
141 @QueryParam("withconfigurationpanel") boolean withConfigurationPanel,
142 @QueryParam("withconfigurationpaneljson") boolean withConfigurationPanelJson, @QueryParam("filter") String filter,
143 @QueryParam("sort") String sort, @QueryParam("offset") Integer offset, @QueryParam("limit") Integer limit) {
144 Stream<WorkflowDefinition> workflowDefinitions;
145 try {
146 workflowDefinitions = workflowService.listAvailableWorkflowDefinitions().stream();
147 } catch (WorkflowDatabaseException e) {
148 logger.error("The workflow service was not able to get the workflow definitions:", e);
149 return ApiResponseBuilder.serverError("Could not retrieve workflow definitions, reason: '%s'", getMessage(e));
150 }
151
152
153 if (StringUtils.isNotBlank(filter)) {
154 for (String f : filter.split(",")) {
155 int sepIdx = f.indexOf(':');
156 if (sepIdx < 0 || sepIdx == f.length() - 1) {
157 logger.debug("No value for filter {} in filters list: {}", f, filter);
158 continue;
159 }
160 String name = f.substring(0, sepIdx);
161 String value = f.substring(sepIdx + 1);
162
163 if ("tag".equals(name))
164 workflowDefinitions = workflowDefinitions.filter(wd -> ArrayUtils.contains(wd.getTags(), value));
165 }
166 }
167
168
169
170 ComparatorChain<WorkflowDefinition> comparator = new ComparatorChain<>();
171 if (StringUtils.isNoneBlank(sort)) {
172 ArrayList<SortCriterion> sortCriteria = RestUtils.parseSortQueryParameter(sort);
173 for (SortCriterion criterion : sortCriteria) {
174 switch (criterion.getFieldName()) {
175 case "identifier":
176 comparator.addComparator((wd1, wd2) -> {
177 String s1 = defaultString(wd1.getId());
178 String s2 = defaultString(wd2.getId());
179 if (criterion.getOrder() == Descending)
180 return s2.compareTo(s1);
181 return s1.compareTo(s2);
182 });
183 break;
184 case "title":
185 comparator.addComparator((wd1, wd2) -> {
186 String s1 = defaultString(wd1.getTitle());
187 String s2 = defaultString(wd2.getTitle());
188 if (criterion.getOrder() == Descending)
189 return s2.compareTo(s1);
190 return s1.compareTo(s2);
191 });
192 break;
193 case "displayorder":
194 comparator.addComparator((wd1, wd2) -> {
195 if (criterion.getOrder() == Descending)
196 return Integer.compare(wd2.getDisplayOrder(), wd1.getDisplayOrder());
197 return Integer.compare(wd1.getDisplayOrder(), wd2.getDisplayOrder());
198 });
199 break;
200 default:
201 return RestUtil.R.badRequest(
202 String.format("Unknown search criterion in request: %s", criterion.getFieldName()));
203 }
204 }
205 }
206 if (comparator.size() > 0) {
207 workflowDefinitions = workflowDefinitions.sorted(comparator);
208 }
209
210
211 if (offset != null && offset > 0) {
212 workflowDefinitions = workflowDefinitions.skip(offset);
213 }
214
215
216 if (limit != null && limit > 0) {
217 workflowDefinitions = workflowDefinitions.limit(limit);
218 }
219
220 List<JsonObject> jsonObjects = workflowDefinitions
221 .map(wd -> workflowDefinitionToJSON(wd, withOperations, withConfigurationPanel, withConfigurationPanelJson))
222 .collect(Collectors.toList());
223
224 JsonArray jsonArray = new JsonArray();
225 for (JsonObject obj : jsonObjects) {
226 jsonArray.add(obj);
227 }
228
229 return ApiResponseBuilder.Json.ok(acceptHeader, jsonArray);
230 }
231
232 @GET
233 @Path("{workflowDefinitionId}")
234 @RestQuery(name = "getworkflowdefinition", description = "Returns a single workflow definition.", returnDescription = "", pathParameters = {
235 @RestParameter(name = "workflowDefinitionId", description = "The workflow definition id", isRequired = true, type = STRING) }, restParameters = {
236 @RestParameter(name = "withoperations", description = "Whether the workflow operations should be included in the response", isRequired = false, type = BOOLEAN),
237 @RestParameter(name = "withconfigurationpaneljson", description = "Whether the workflow configuration panel in JSON should be included in the response", isRequired = false, type = BOOLEAN),
238 @RestParameter(name = "withconfigurationpanel", description = "Whether the workflow configuration panel should be included in the response", isRequired = false, type = BOOLEAN) }, responses = {
239 @RestResponse(description = "The workflow definition is returned.", responseCode = HttpServletResponse.SC_OK),
240 @RestResponse(description = "The specified workflow definition does not exist.", responseCode = HttpServletResponse.SC_NOT_FOUND) })
241 public Response getWorkflowDefinition(@HeaderParam("Accept") String acceptHeader,
242 @PathParam("workflowDefinitionId") String id, @QueryParam("withoperations") boolean withOperations,
243 @QueryParam("withconfigurationpanel") boolean withConfigurationPanel,
244 @QueryParam("withconfigurationpaneljson") boolean withConfigurationPanelJson) throws Exception {
245 WorkflowDefinition wd;
246 try {
247 wd = workflowService.getWorkflowDefinitionById(id);
248 } catch (NotFoundException e) {
249 return ApiResponseBuilder.notFound("Cannot find workflow definition with id '%s'.", id);
250 }
251
252 return ApiResponseBuilder.Json.ok(acceptHeader, workflowDefinitionToJSON(wd, withOperations, withConfigurationPanel, withConfigurationPanelJson));
253 }
254
255 private JsonObject workflowDefinitionToJSON(WorkflowDefinition wd, boolean withOperations,
256 boolean withConfigurationPanel, boolean withConfigurationPanelJson) {
257 JsonObject json = new JsonObject();
258
259 json.addProperty("identifier", wd.getId());
260 json.addProperty("title", safeString(wd.getTitle()));
261 json.addProperty("description", safeString(wd.getDescription()));
262 json.add("tags", arrayToJsonArray(wd.getTags()));
263 if (withConfigurationPanel) {
264 json.addProperty("configuration_panel", safeString(wd.getConfigurationPanel()));
265 }
266 if (withConfigurationPanelJson) {
267 json.addProperty("configuration_panel_json", safeString(wd.getConfigurationPanelJson()));
268 }
269 if (withOperations) {
270 JsonArray operationsArray = new JsonArray();
271 for (WorkflowOperationDefinition op : wd.getOperations()) {
272 operationsArray.add(workflowOperationDefinitionToJSON(op));
273 }
274 json.add("operations", operationsArray);
275 }
276
277 return json;
278 }
279
280 private JsonObject workflowOperationDefinitionToJSON(WorkflowOperationDefinition wod) {
281 JsonObject json = new JsonObject();
282
283 json.addProperty("operation", wod.getId());
284 json.addProperty("description", safeString(wod.getDescription()));
285 JsonObject configJson = new JsonObject();
286 for (String key : wod.getConfigurationKeys()) {
287 String value = wod.getConfiguration(key);
288 configJson.addProperty(key, value);
289 }
290 json.add("configuration", configJson);
291 json.addProperty("if", safeString(wod.getExecutionCondition()));
292 json.addProperty("unless", safeString(wod.getSkipCondition()));
293 json.addProperty("fail_workflow_on_error", wod.isFailWorkflowOnException());
294 json.addProperty("error_handler_workflow", safeString(wod.getExceptionHandlingWorkflow()));
295 String retryStrategy = new RetryStrategy.Adapter().marshal(wod.getRetryStrategy());
296 json.addProperty("retry_strategy", safeString(retryStrategy));
297 json.addProperty("max_attempts", wod.getMaxAttempts());
298
299 return json;
300 }
301 }