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.api;
23
24 import static java.lang.String.format;
25
26 import org.opencastproject.job.api.Job;
27 import org.opencastproject.job.api.JobBarrier;
28 import org.opencastproject.job.api.JobContext;
29 import org.opencastproject.mediapackage.MediaPackage;
30 import org.opencastproject.mediapackage.MediaPackageElement;
31 import org.opencastproject.mediapackage.MediaPackageElementFlavor;
32 import org.opencastproject.serviceregistry.api.ServiceRegistry;
33 import org.opencastproject.workflow.api.WorkflowOperationResult.Action;
34
35 import org.apache.commons.io.FilenameUtils;
36 import org.apache.commons.lang3.StringUtils;
37 import org.osgi.framework.Constants;
38 import org.osgi.service.component.ComponentContext;
39 import org.osgi.service.component.annotations.Activate;
40 import org.osgi.service.component.annotations.Reference;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43
44 import java.util.ArrayList;
45 import java.util.List;
46 import java.util.Map;
47 import java.util.Optional;
48
49
50
51
52
53 public abstract class AbstractWorkflowOperationHandler implements WorkflowOperationHandler {
54
55
56 private static final Logger logger = LoggerFactory.getLogger(AbstractWorkflowOperationHandler.class);
57
58
59 protected String id = null;
60
61
62 protected String description = null;
63
64
65 protected ServiceRegistry serviceRegistry = null;
66
67
68 private long jobBarrierPollingInterval = JobBarrier.DEFAULT_POLLING_INTERVAL;
69
70
71 protected enum Configuration { none, one, many };
72
73 public static final String TARGET_FLAVORS = "target-flavors";
74 public static final String TARGET_FLAVOR = "target-flavor";
75 public static final String TARGET_TAGS = "target-tags";
76 public static final String TARGET_TAG = "target-tag";
77 public static final String SOURCE_FLAVORS = "source-flavors";
78 public static final String SOURCE_FLAVOR = "source-flavor";
79 public static final String SOURCE_TAG = "source-tag";
80 public static final String SOURCE_TAGS = "source-tags";
81
82
83
84
85
86
87
88 @Activate
89 protected void activate(ComponentContext cc) {
90 this.id = (String) cc.getProperties().get(WorkflowService.WORKFLOW_OPERATION_PROPERTY);
91 this.description = (String) cc.getProperties().get(Constants.SERVICE_DESCRIPTION);
92 }
93
94
95
96
97
98
99
100 @Override
101 public abstract WorkflowOperationResult start(WorkflowInstance workflowInstance, JobContext context)
102 throws WorkflowOperationException;
103
104
105
106
107
108
109
110 @Override
111 public WorkflowOperationResult skip(WorkflowInstance workflowInstance, JobContext context)
112 throws WorkflowOperationException {
113 return createResult(Action.SKIP);
114 }
115
116
117
118
119
120
121
122 @Override
123 public void destroy(WorkflowInstance workflowInstance, JobContext context) throws WorkflowOperationException {
124 }
125
126
127
128
129
130
131
132
133
134 protected List<String> asList(String elements) {
135 elements = StringUtils.trimToEmpty(elements);
136 List<String> list = new ArrayList<>();
137 for (String s : StringUtils.split(elements, ",")) {
138 if (StringUtils.trimToNull(s) != null) {
139 list.add(s.trim());
140 }
141 }
142 return list;
143 }
144
145
146
147
148
149
150
151
152
153
154 protected String getFileNameFromElements(MediaPackageElement source, MediaPackageElement derived) {
155 String fileName = FilenameUtils.getBaseName(source.getURI().getPath());
156 String fileExtension = FilenameUtils.getExtension(derived.getURI().getPath());
157 return fileName + "." + fileExtension;
158 }
159
160
161
162
163
164
165 @Override
166 public String getId() {
167 return id;
168 }
169
170
171
172
173
174
175 @Override
176 public String getDescription() {
177 return description;
178 }
179
180
181
182
183
184
185
186
187 protected WorkflowOperationResult createResult(Action action) {
188 return createResult(null, null, action, 0);
189 }
190
191
192
193
194
195
196
197
198
199
200 protected WorkflowOperationResult createResult(MediaPackage mediaPackage, Action action) {
201 return createResult(mediaPackage, null, action, 0);
202 }
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218 protected WorkflowOperationResult createResult(MediaPackage mediaPackage, Action action, long timeInQueue) {
219 return createResult(mediaPackage, null, action, timeInQueue);
220 }
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238 protected WorkflowOperationResult createResult(MediaPackage mediaPackage, Map<String, String> properties,
239 Action action, long timeInQueue) {
240 return new WorkflowOperationResultImpl(mediaPackage, properties, action, timeInQueue);
241 }
242
243
244
245
246
247
248
249
250 @Reference
251 public void setServiceRegistry(ServiceRegistry serviceRegistry) {
252 this.serviceRegistry = serviceRegistry;
253 }
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272 protected JobBarrier.Result waitForStatus(Job... jobs) throws IllegalStateException, IllegalArgumentException {
273 return waitForStatus(0, jobs);
274 }
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295 protected JobBarrier.Result waitForStatus(long timeout, Job... jobs) throws IllegalStateException,
296 IllegalArgumentException {
297 if (serviceRegistry == null) {
298 throw new IllegalStateException("Can't wait for job status without providing a service registry first");
299 }
300 JobBarrier barrier = new JobBarrier(null, serviceRegistry, jobBarrierPollingInterval, jobs);
301 return barrier.waitForJobs(timeout);
302 }
303
304
305
306
307
308
309 protected Optional<String> getCfg(WorkflowInstance wi, String key) {
310 return Optional.ofNullable(wi.getCurrentOperation().getConfiguration(key));
311 }
312
313
314
315
316
317
318
319 protected String getConfig(WorkflowInstance wi, String key) throws WorkflowOperationException {
320 return getConfig(wi.getCurrentOperation(), key);
321 }
322
323
324
325
326
327
328
329
330
331
332
333 protected String getConfig(WorkflowInstance w, String key, String defaultValue) {
334 Optional<String> cfgOpt = getOptConfig(w.getCurrentOperation(), key);
335 if (cfgOpt.isPresent()) {
336 return cfgOpt.get();
337 }
338 return defaultValue;
339 }
340
341
342
343
344
345
346
347 protected String getConfig(WorkflowOperationInstance woi, String key) throws WorkflowOperationException {
348 Optional<String> cfgOpt = getOptConfig(woi, key);
349 if (cfgOpt.isPresent()) {
350 return cfgOpt.get();
351 }
352 throw new WorkflowOperationException(format("Configuration key '%s' is either missing or empty", key));
353 }
354
355
356
357
358 protected Optional<String> getOptConfig(WorkflowInstance wi, String key) {
359 return getOptConfig(wi.getCurrentOperation(), key);
360 }
361
362
363
364
365 protected Optional<String> getOptConfig(WorkflowOperationInstance woi, String key) {
366 return Optional.ofNullable(woi.getConfiguration(key))
367 .map(String::trim)
368 .filter(s -> !s.isEmpty());
369 }
370
371
372
373
374
375
376
377
378
379
380
381 protected ConfiguredTagsAndFlavors getTagsAndFlavors(WorkflowInstance workflow, Configuration srcTags, Configuration srcFlavors, Configuration targetTags, Configuration targetFlavors) throws WorkflowOperationException {
382 WorkflowOperationInstance operation = workflow.getCurrentOperation();
383 ConfiguredTagsAndFlavors tagsAndFlavors = new ConfiguredTagsAndFlavors();
384 MediaPackageElementFlavor flavor;
385
386 List<String> srcTagList = new ArrayList<>();
387 String srcTag;
388 switch(srcTags) {
389 case none:
390 break;
391 case one:
392 srcTag = StringUtils.trimToNull(operation.getConfiguration(SOURCE_TAG));
393 if (srcTag == null) {
394 throw new WorkflowOperationException("Configuration key '" + SOURCE_TAG + "' must be set");
395 }
396 srcTagList.add(srcTag);
397 break;
398 case many:
399 srcTagList = asList(StringUtils.trimToNull(operation.getConfiguration(SOURCE_TAGS)));
400 srcTag = StringUtils.trimToNull(operation.getConfiguration(SOURCE_TAG));
401 if (srcTagList.isEmpty() && srcTag != null) {
402 srcTagList.add(srcTag);
403 }
404 break;
405 default:
406 throw new WorkflowOperationException("Couldn't process srcTags configuration option!");
407 }
408 tagsAndFlavors.setSrcTags(srcTagList);
409
410 List<MediaPackageElementFlavor> srcFlavorList = new ArrayList<>();
411 String singleSourceFlavor;
412 switch(srcFlavors) {
413 case none:
414 break;
415 case one:
416 singleSourceFlavor = StringUtils.trimToNull(operation.getConfiguration(SOURCE_FLAVOR));
417 if (singleSourceFlavor == null) {
418 throw new WorkflowOperationException("Configuration key '" + SOURCE_FLAVOR + "' must be set");
419 }
420 try {
421 flavor = MediaPackageElementFlavor.parseFlavor(singleSourceFlavor);
422 } catch (IllegalArgumentException e) {
423 throw new WorkflowOperationException(singleSourceFlavor + " is not a valid flavor!");
424 }
425 srcFlavorList.add(flavor);
426 break;
427 case many:
428 List<String> srcFlavorString = asList(StringUtils.trimToNull(operation.getConfiguration(SOURCE_FLAVORS)));
429 singleSourceFlavor = StringUtils.trimToNull(operation.getConfiguration(SOURCE_FLAVOR));
430 if (srcFlavorString.isEmpty() && singleSourceFlavor != null) {
431 srcFlavorString.add(singleSourceFlavor);
432 }
433 for (String elem : srcFlavorString) {
434 try {
435 flavor = MediaPackageElementFlavor.parseFlavor(elem);
436 srcFlavorList.add(flavor);
437 } catch (IllegalArgumentException e) {
438 throw new WorkflowOperationException(elem + " is not a valid flavor!");
439 }
440 }
441 break;
442 default:
443 throw new WorkflowOperationException("Couldn't process srcFlavors configuration option!");
444 }
445 tagsAndFlavors.setSrcFlavors(srcFlavorList);
446
447 ConfiguredTagsAndFlavors.TargetTags targetTagMap = new ConfiguredTagsAndFlavors.TargetTags();
448 String targetTag;
449 switch(targetTags) {
450 case none:
451 break;
452 case one:
453 targetTag = StringUtils.trimToNull(operation.getConfiguration(TARGET_TAG));
454 if (targetTag == null) {
455 throw new WorkflowOperationException("Configuration key '" + TARGET_TAG + "' must be set");
456 }
457 targetTagMap = parseTargetTagsByType(List.of(targetTag));
458 break;
459 case many:
460 List<String> targetTagList = asList(StringUtils.trimToNull(operation.getConfiguration(TARGET_TAGS)));
461 targetTagMap = parseTargetTagsByType(targetTagList);
462 targetTag = StringUtils.trimToNull(operation.getConfiguration(TARGET_TAG));
463 if (targetTagList.isEmpty() && targetTag != null) {
464 targetTagMap = parseTargetTagsByType(List.of(targetTag));
465 }
466 break;
467 default:
468 throw new WorkflowOperationException("Couldn't process target-tag configuration option!");
469 }
470 tagsAndFlavors.setTargetTags(targetTagMap);
471
472 List<MediaPackageElementFlavor> targetFlavorList = new ArrayList<>();
473 String singleTargetFlavor;
474 switch(targetFlavors) {
475 case none:
476 break;
477 case one:
478 singleTargetFlavor = StringUtils.trimToNull(operation.getConfiguration(TARGET_FLAVOR));
479 if (singleTargetFlavor == null) {
480 throw new WorkflowOperationException("Configuration key '" + TARGET_FLAVOR + "' must be set");
481 }
482 try {
483 flavor = MediaPackageElementFlavor.parseFlavor(singleTargetFlavor);
484 } catch (IllegalArgumentException e) {
485 throw new WorkflowOperationException(singleTargetFlavor + " is not a valid flavor!");
486 }
487 targetFlavorList.add(flavor);
488 break;
489 case many:
490 List<String> targetFlavorString = asList(StringUtils.trimToNull(operation.getConfiguration(TARGET_FLAVORS)));
491 singleTargetFlavor = StringUtils.trimToNull(operation.getConfiguration(TARGET_FLAVOR));
492 if (targetFlavorString.isEmpty() && singleTargetFlavor != null) {
493 targetFlavorString.add(singleTargetFlavor);
494 }
495 for (String elem : targetFlavorString) {
496 try {
497 flavor = MediaPackageElementFlavor.parseFlavor(elem);
498 } catch (IllegalArgumentException e) {
499 throw new WorkflowOperationException(elem + " is not a valid flavor!");
500 }
501 targetFlavorList.add(flavor);
502 }
503 break;
504 default:
505 throw new WorkflowOperationException("Couldn't process targetFlavors configuration option!");
506 }
507 tagsAndFlavors.setTargetFlavors(targetFlavorList);
508 return tagsAndFlavors;
509 }
510
511 private ConfiguredTagsAndFlavors.TargetTags parseTargetTagsByType(List<String> tags) {
512 final String plus = "+";
513 final String minus = "-";
514 List<String> overrideTags = new ArrayList();
515 List<String> addTags = new ArrayList();
516 List<String> removeTags = new ArrayList();
517
518 for (String targetTag : tags) {
519 if (!StringUtils.startsWithAny(targetTag, plus, minus)) {
520 if (addTags.size() > 0
521 || removeTags.size() > 0) {
522 logger.warn("You may not mix override tags and tag changes. "
523 + "The list of override tags so far is {}. "
524 + "The tag {} is not prefixed with '{}' or '{}'.", overrideTags, targetTag, plus, minus);
525 }
526 overrideTags.add(targetTag);
527 } else if (StringUtils.startsWith(targetTag, plus)) {
528 addTags.add(StringUtils.substring(targetTag, 1));
529 } else if (StringUtils.startsWith(targetTag, minus)) {
530 removeTags.add(StringUtils.substring(targetTag, 1));
531 }
532 }
533
534 return new ConfiguredTagsAndFlavors.TargetTags(overrideTags, addTags, removeTags);
535 }
536
537
538
539
540
541
542
543 protected <T extends MediaPackageElement> T applyTargetTagsToElement(
544 ConfiguredTagsAndFlavors.TargetTags targetTags,
545 T element
546 ) {
547
548 List<String> overrideTags = targetTags.getOverrideTags();
549 List<String> addTags = targetTags.getAddTags();
550 List<String> removeTags = targetTags.getRemoveTags();
551 if (overrideTags.size() > 0) {
552 element.clearTags();
553 for (String tag : overrideTags) {
554 element.addTag(tag);
555 }
556 } else {
557 for (String tag : removeTags) {
558 element.removeTag(tag);
559 }
560 for (String tag : addTags) {
561 element.addTag(tag);
562 }
563 }
564
565 return element;
566 }
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583 public void setJobBarrierPollingInterval(long interval) {
584 this.jobBarrierPollingInterval = interval;
585 }
586
587
588
589
590
591
592 @Override
593 public int hashCode() {
594 return id != null ? id.hashCode() : super.hashCode();
595 }
596
597
598
599
600
601
602 @Override
603 public boolean equals(Object obj) {
604 if (obj instanceof WorkflowOperationHandler) {
605 if (id != null)
606 return id.equals(((WorkflowOperationHandler) obj).getId());
607 else
608 return this == obj;
609 }
610 return false;
611 }
612
613
614
615
616
617
618 @Override
619 public String toString() {
620 return getId();
621 }
622 }