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 org.opencastproject.workflow.api.WorkflowOperationInstance.OperationState.INSTANTIATED;
25 import static org.opencastproject.workflow.api.WorkflowOperationInstance.OperationState.PAUSED;
26 import static org.opencastproject.workflow.api.WorkflowOperationInstance.OperationState.RETRY;
27 import static org.opencastproject.workflow.api.WorkflowOperationInstance.OperationState.RUNNING;
28
29 import org.opencastproject.mediapackage.MediaPackage;
30 import org.opencastproject.mediapackage.MediaPackageException;
31 import org.opencastproject.mediapackage.MediaPackageParser;
32 import org.opencastproject.security.api.Organization;
33 import org.opencastproject.security.api.User;
34
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
37
38 import java.util.ArrayList;
39 import java.util.Collections;
40 import java.util.Date;
41 import java.util.HashMap;
42 import java.util.List;
43 import java.util.Map;
44 import java.util.Set;
45 import java.util.TreeMap;
46
47 import javax.persistence.Access;
48 import javax.persistence.AccessType;
49 import javax.persistence.CascadeType;
50 import javax.persistence.CollectionTable;
51 import javax.persistence.Column;
52 import javax.persistence.ElementCollection;
53 import javax.persistence.Entity;
54 import javax.persistence.FetchType;
55 import javax.persistence.Id;
56 import javax.persistence.Index;
57 import javax.persistence.JoinColumn;
58 import javax.persistence.Lob;
59 import javax.persistence.MapKeyColumn;
60 import javax.persistence.NamedQueries;
61 import javax.persistence.NamedQuery;
62 import javax.persistence.OneToMany;
63 import javax.persistence.OrderColumn;
64 import javax.persistence.Table;
65 import javax.persistence.Temporal;
66 import javax.persistence.TemporalType;
67 import javax.persistence.Transient;
68 import javax.xml.bind.annotation.adapters.XmlAdapter;
69
70
71
72
73
74
75
76 @Entity(name = "WorkflowInstance")
77 @Access(AccessType.FIELD)
78 @Table(name = "oc_workflow", indexes = {
79 @Index(name = "IX_oc_workflow_mediapackage_id", columnList = ("mediapackage_id")),
80 @Index(name = "IX_oc_workflow_series_id", columnList = ("series_id")), })
81 @NamedQueries({
82 @NamedQuery(
83 name = "Workflow.findAll",
84 query = "select w from WorkflowInstance w where w.organizationId=:organizationId order by w.dateCreated"
85 ),
86 @NamedQuery(
87 name = "Workflow.countLatest",
88 query = "SELECT COUNT(DISTINCT w.mediaPackageId) FROM WorkflowInstance w"
89 ),
90 @NamedQuery(
91 name = "Workflow.findAllOrganizationIndependent",
92 query = "select w from WorkflowInstance w"
93 ),
94 @NamedQuery(
95 name = "Workflow.workflowById",
96 query = "SELECT w FROM WorkflowInstance as w where w.workflowId=:workflowId and w.organizationId=:organizationId"
97 ),
98 @NamedQuery(
99 name = "Workflow.workflowByIdOrganizationIndependent",
100 query = "SELECT w FROM WorkflowInstance as w where w.workflowId=:workflowId"
101 ),
102 @NamedQuery(
103 name = "Workflow.getCount",
104 query = "select COUNT(w) from WorkflowInstance w where w.organizationId=:organizationId "
105 + "and (:state is null or w.state = :state) "
106 ),
107 @NamedQuery(
108 name = "Workflow.toCleanup",
109 query = "SELECT w FROM WorkflowInstance w where w.state = :state "
110 + "and w.dateCreated < :dateCreated and w.organizationId = :organizationId"
111 ),
112
113
114 @NamedQuery(name = "Workflow.byMediaPackage", query = "SELECT w FROM WorkflowInstance w where "
115 + "w.mediaPackageId = :mediaPackageId and w.organizationId = :organizationId order by w.dateCreated"),
116 @NamedQuery(name = "Workflow.countActiveByMediaPackage", query = "SELECT COUNT(w) FROM WorkflowInstance w where "
117 + "w.mediaPackageId = :mediaPackageId and w.organizationId = :organizationId and "
118 + "(w.state = :stateInstantiated or w.state = :statePaused or w.state = :stateRunning "
119 + "or w.state = :stateFailing)"),
120 @NamedQuery(name = "Workflow.byMediaPackageAndActive", query = "SELECT w FROM WorkflowInstance w where "
121 + "w.mediaPackageId = :mediaPackageId and w.organizationId = :organizationId and "
122 + "(w.state = :stateInstantiated or w.state = :statePaused or w.state = :stateRunning "
123 + "or w.state = :stateFailing) order by w.dateCreated"),
124
125
126 @NamedQuery(name = "Workflow.countActiveByUser", query = "SELECT COUNT(w) FROM WorkflowInstance w where "
127 + "w.creatorName = :userId and w.organizationId = :organizationId and "
128 + "(w.state = :stateInstantiated or w.state = :statePaused or w.state = :stateRunning "
129 + "or w.state = :stateFailing)"),
130 })
131 public class WorkflowInstance {
132
133
134
135
136 @Id
137 @Column(name = "id")
138 private long workflowId;
139
140 @Column(name = "state", length = 128)
141 private WorkflowState state;
142
143 @Column(name = "template")
144 private String template;
145
146 @Column(name = "title")
147 private String title;
148
149 @Column(name = "description")
150 @Lob
151 private String description;
152
153 @Column(name = "creator_id")
154 private String creatorName;
155
156 @Column(name = "organization_id")
157 private String organizationId;
158
159 @Column(name = "date_created")
160 @Temporal(TemporalType.TIMESTAMP)
161 private Date dateCreated = null;
162
163 @Column(name = "date_completed")
164 @Temporal(TemporalType.TIMESTAMP)
165 private Date dateCompleted = null;
166
167 @Lob
168 @Column(name = "mediapackage", length = 16777215)
169 private String mediaPackage;
170
171 @Transient
172 private MediaPackage mediaPackageObj;
173
174 @OneToMany(
175 mappedBy = "instance",
176 cascade = CascadeType.ALL,
177 orphanRemoval = true,
178 fetch = FetchType.LAZY
179 )
180 @OrderColumn(name = "position")
181 protected List<WorkflowOperationInstance> operations;
182
183 @ElementCollection
184 @CollectionTable(
185 name = "oc_workflow_configuration",
186 joinColumns = @JoinColumn(name = "workflow_id"),
187 indexes = {
188 @Index(name = "IX_oc_workflow_configuration_workflow_id", columnList = ("workflow_id")),
189 }
190 )
191 @MapKeyColumn(name = "configuration_key")
192 @Lob
193 @Column(name = "configuration_value")
194 protected Map<String, String> configurations;
195
196 @Column(name = "mediapackage_id", length = 128)
197 protected String mediaPackageId;
198
199 @Column(name = "series_id", length = 128)
200 protected String seriesId;
201
202 public enum WorkflowState {
203 INSTANTIATED, RUNNING, STOPPED, PAUSED, SUCCEEDED, FAILED, FAILING;
204
205 public boolean isTerminated() {
206 switch (this) {
207 case STOPPED:
208 case SUCCEEDED:
209 case FAILED:
210 return true;
211 default:
212 return false;
213 }
214 }
215 public static class Adapter extends XmlAdapter<String, WorkflowState> {
216
217 @Override
218 public String marshal(WorkflowState workflowState) {
219 return workflowState == null ? null : workflowState.toString().toLowerCase();
220 }
221
222 @Override
223 public WorkflowState unmarshal(String val) {
224 return val == null ? null : WorkflowState.valueOf(val.toUpperCase());
225 }
226
227 }
228 }
229
230
231 private static final Logger logger = LoggerFactory.getLogger(WorkflowInstance.class);
232
233
234
235
236 public WorkflowInstance() {
237
238 }
239
240
241
242
243
244 public WorkflowInstance(WorkflowDefinition def, MediaPackage mediaPackage, User creator,
245 Organization organization, Map<String, String> configuration) {
246 this.workflowId = -1;
247 this.title = def.getTitle();
248 this.template = def.getId();
249 this.description = def.getDescription();
250 this.creatorName = creator != null ? creator.getUsername() : null;
251 this.organizationId = organization != null ? organization.getId() : null;
252 this.state = WorkflowState.INSTANTIATED;
253 this.dateCreated = new Date();
254 this.mediaPackageObj = mediaPackage;
255 this.mediaPackage = mediaPackage == null ? null : MediaPackageParser.getAsXml(mediaPackage);
256 this.mediaPackageId = mediaPackage == null ? null : mediaPackage.getIdentifier().toString();
257 this.seriesId = mediaPackage == null ? null : mediaPackage.getSeries();
258
259 this.operations = new ArrayList<>();
260 extend(def);
261
262 this.configurations = new HashMap<>();
263 if (configuration != null) {
264 this.configurations.putAll(configuration);
265 }
266 }
267
268 public WorkflowInstance(
269 long id,
270 WorkflowState state,
271 String template,
272 String title,
273 String description,
274 String creatorName,
275 String organizationId,
276 Date dateCreated,
277 Date dateCompleted,
278 MediaPackage mediaPackage,
279 List<WorkflowOperationInstance> operations,
280 Map<String, String> configurations,
281 String mediaPackageId,
282 String seriesId) {
283 this.workflowId = id;
284 this.state = state;
285 this.template = template;
286 this.title = title;
287 this.description = description;
288 this.creatorName = creatorName;
289 this.organizationId = organizationId;
290 this.dateCreated = dateCreated;
291 this.dateCompleted = dateCompleted;
292 this.mediaPackageObj = mediaPackage;
293 this.mediaPackage = mediaPackage == null ? null : MediaPackageParser.getAsXml(mediaPackage);
294 this.operations = operations;
295 this.configurations = configurations;
296 this.mediaPackageId = mediaPackageId;
297 this.seriesId = seriesId;
298 }
299
300 public long getId() {
301 return workflowId;
302 }
303
304 public void setId(long workflowId) {
305 this.workflowId = workflowId;
306 }
307
308 public WorkflowState getState() {
309 return state;
310 }
311
312 public void setState(WorkflowState state) {
313 if (dateCompleted == null && state.isTerminated()) {
314 dateCompleted = new Date();
315 }
316
317 this.state = state;
318 }
319
320 public String getTemplate() {
321 return template;
322 }
323
324 public void setTemplate(String template) {
325 this.template = template;
326 }
327
328 public String getTitle() {
329 return title;
330 }
331
332 public void setTitle(String title) {
333 this.title = title;
334 }
335
336 public String getDescription() {
337 return description;
338 }
339
340 public void setDescription(String description) {
341 this.description = description;
342 }
343
344 public String getCreatorName() {
345 return creatorName;
346 }
347
348 public void setCreatorName(String creatorName) {
349 this.creatorName = creatorName;
350 }
351
352 public String getOrganizationId() {
353 return organizationId;
354 }
355
356 public void setOrganizationId(String organizationId) {
357 this.organizationId = organizationId;
358 }
359
360 public Date getDateCreated() {
361 return dateCreated;
362 }
363
364 public void setDateCreated(Date dateCreated) {
365 this.dateCreated = dateCreated;
366 }
367
368 public Date getDateCompleted() {
369 return dateCompleted;
370 }
371
372 public void setDateCompleted(Date dateCompleted) {
373 this.dateCompleted = dateCompleted;
374 }
375
376 public MediaPackage getMediaPackage() {
377 try {
378 if (mediaPackageObj != null) {
379 return mediaPackageObj;
380 }
381 if (mediaPackage != null) {
382 mediaPackageObj = MediaPackageParser.getFromXml(mediaPackage);
383 return mediaPackageObj;
384 }
385 } catch (MediaPackageException e) {
386 logger.error("Error parsing media package in workflow instance", e);
387 }
388 return null;
389 }
390
391 public void setMediaPackage(MediaPackage mediaPackage) {
392 this.mediaPackageObj = mediaPackage;
393 this.mediaPackage = mediaPackage == null ? null : MediaPackageParser.getAsXml(mediaPackage);
394 this.mediaPackageId = mediaPackage == null ? null : mediaPackage.getIdentifier().toString();
395 this.seriesId = mediaPackage == null ? null : mediaPackage.getSeries();
396 }
397
398 public boolean isActive() {
399 return !getState().isTerminated();
400 }
401
402
403
404
405
406
407 public List<WorkflowOperationInstance> getOperations() {
408 if (operations == null) {
409 operations = new ArrayList<>();
410 }
411 return operations;
412 }
413
414
415
416
417
418
419 public final void setOperations(List<WorkflowOperationInstance> workflowOperationInstanceList) {
420 for (var workflowOperationInstance : workflowOperationInstanceList) {
421 workflowOperationInstance.setWorkflowInstance(this);
422 }
423 this.operations = workflowOperationInstanceList;
424 }
425
426
427
428
429
430
431 public WorkflowOperationInstance getCurrentOperation() {
432 logger.debug("operations: {}", operations);
433 if (operations == null) {
434 return null;
435 }
436
437
438
439
440 var currentStates = List.of(RUNNING, PAUSED, RETRY, INSTANTIATED);
441 for (var operation : operations) {
442 if (currentStates.contains(operation.getState())) {
443 logger.debug("current operation: {}", operation);
444 return operation;
445 }
446 }
447 return null;
448 }
449
450 public Map<String, String> getConfigurations() {
451 if (configurations == null) {
452 return Collections.emptyMap();
453 }
454 return configurations;
455 }
456
457
458
459
460
461
462 public String getConfiguration(String key) {
463 if (key == null || configurations == null)
464 return null;
465 return configurations.get(key);
466 }
467
468
469
470
471
472
473 public Set<String> getConfigurationKeys() {
474 if (configurations == null) {
475 return Collections.emptySet();
476 }
477 return configurations.keySet();
478 }
479
480
481
482
483
484
485 public void removeConfiguration(String key) {
486 if (key == null || configurations == null)
487 return;
488 configurations.remove(key);
489 }
490
491
492
493
494
495
496 public void setConfiguration(String key, String value) {
497 if (key == null)
498 return;
499 if (configurations == null)
500 configurations = new TreeMap<>();
501
502
503 configurations.put(key, value);
504 }
505
506 @Override
507 public int hashCode() {
508 return Long.valueOf(workflowId).hashCode();
509 }
510
511 @Override
512 public boolean equals(Object obj) {
513 if (obj instanceof WorkflowInstance) {
514 WorkflowInstance other = (WorkflowInstance) obj;
515 return workflowId == other.getId();
516 }
517 return false;
518 }
519
520 @Override
521 public String toString() {
522 return "Workflow {" + workflowId + "}";
523 }
524
525
526 public void extend(WorkflowDefinition workflowDefinition) {
527 for (var operation : workflowDefinition.getOperations()) {
528 var operationInstance = new WorkflowOperationInstance(operation);
529 operationInstance.setWorkflowInstance(this);
530 operations.add(operationInstance);
531 }
532 setTemplate(workflowDefinition.getId());
533 }
534
535 public void insert(WorkflowDefinition workflowDefinition, WorkflowOperationInstance after) {
536 var index = operations.indexOf(after) + 1;
537 for (var operation : workflowDefinition.getOperations()) {
538 var operationInstance = new WorkflowOperationInstance(operation);
539 operationInstance.setWorkflowInstance(this);
540 operations.add(index, operationInstance);
541 index++;
542 }
543 }
544 }