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