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