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