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