View Javadoc
1   /*
2    * Licensed to The Apereo Foundation under one or more contributor license
3    * agreements. See the NOTICE file distributed with this work for additional
4    * information regarding copyright ownership.
5    *
6    *
7    * The Apereo Foundation licenses this file to you under the Educational
8    * Community License, Version 2.0 (the "License"); you may not use this file
9    * except in compliance with the License. You may obtain a copy of the License
10   * at:
11   *
12   *   http://opensource.org/licenses/ecl2.txt
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
17   * License for the specific language governing permissions and limitations under
18   * the License.
19   *
20   */
21  
22  package org.opencastproject.workflow.api;
23  
24  import java.util.Collections;
25  import java.util.Date;
26  import java.util.Map;
27  import java.util.Objects;
28  import java.util.Set;
29  import java.util.TreeMap;
30  
31  import javax.persistence.Access;
32  import javax.persistence.AccessType;
33  import javax.persistence.CollectionTable;
34  import javax.persistence.Column;
35  import javax.persistence.ElementCollection;
36  import javax.persistence.Entity;
37  import javax.persistence.FetchType;
38  import javax.persistence.GeneratedValue;
39  import javax.persistence.Id;
40  import javax.persistence.Index;
41  import javax.persistence.JoinColumn;
42  import javax.persistence.Lob;
43  import javax.persistence.ManyToOne;
44  import javax.persistence.MapKeyColumn;
45  import javax.persistence.Table;
46  import javax.persistence.Temporal;
47  import javax.persistence.TemporalType;
48  
49  /**
50   * A workflow operation belonging to a workflow instance.
51   */
52  @Entity(name = "WorkflowOperationInstance")
53  @Access(AccessType.FIELD)
54  @Table(name = "oc_workflow_operation", indexes = {
55      @Index(name = "IX_oc_workflow_operation_workflow_id", columnList = ("workflow_id"))})
56  public class WorkflowOperationInstance implements Configurable {
57    public enum OperationState {
58      INSTANTIATED, RUNNING, PAUSED, SUCCEEDED, FAILED, SKIPPED, RETRY
59    }
60  
61    @Id
62    @GeneratedValue
63    @Column(name = "id")
64    private Long id;
65  
66    @Column(name = "template")
67    protected String template;
68  
69    @Column(name = "job")
70    protected Long jobId;
71  
72    @Column(name = "state")
73    protected OperationState state;
74  
75    @Column(name = "description")
76    @Lob
77    protected String description;
78  
79    @ElementCollection
80    @CollectionTable(
81            name = "oc_workflow_operation_configuration",
82            joinColumns = @JoinColumn(name = "workflow_operation_id"),
83            indexes = {
84                  @Index(name = "IX_oc_workflow_operation_configuration_workflow_operation_id",
85                      columnList = ("workflow_operation_id")),
86            }
87    )
88    @MapKeyColumn(name = "configuration_key", nullable = false)
89    @Lob
90    @Column(name = "configuration_value")
91    protected Map<String, String> configurations;
92  
93    @Column(name = "fail_on_error")
94    protected boolean failOnError;
95  
96    @Column(name = "if_condition")
97    @Lob
98    protected String executeCondition;
99  
100   @Column(name = "exception_handler_workflow")
101   protected String exceptionHandlingWorkflow;
102 
103   @Column(name = "abortable")
104   protected Boolean abortable;
105 
106   @Column(name = "continuable")
107   protected Boolean continuable;
108 
109   @Column(name = "started")
110   @Temporal(TemporalType.TIMESTAMP)
111   protected Date dateStarted;
112 
113   @Column(name = "completed")
114   @Temporal(TemporalType.TIMESTAMP)
115   protected Date dateCompleted;
116 
117   @Column(name = "time_in_queue")
118   protected Long timeInQueue;
119 
120   @Column(name = "max_attempts")
121   protected int maxAttempts;
122 
123   @Column(name = "failed_attempts")
124   protected int failedAttempts;
125 
126   @Column(name = "execution_host")
127   protected String executionHost;
128 
129   @Column(name = "retry_strategy", length = 128)
130   protected RetryStrategy retryStrategy;
131 
132   @ManyToOne(fetch = FetchType.LAZY)
133   @JoinColumn(name = "workflow_id", nullable = false)
134   private WorkflowInstance instance;
135 
136   /**
137    * No-arg constructor needed for JAXB serialization
138    */
139   public WorkflowOperationInstance() {
140     this.maxAttempts = 1;
141     this.retryStrategy = RetryStrategy.NONE;
142   }
143 
144   /**
145    * Builds a new workflow operation instance based on another workflow operation.
146    *
147    * @param def
148    *          the workflow definition
149    */
150   public WorkflowOperationInstance(WorkflowOperationDefinition def) {
151     this();
152     setTemplate(def.getId());
153     setState(OperationState.INSTANTIATED);
154     setDescription(def.getDescription());
155     setMaxAttempts(def.getMaxAttempts());
156     setFailOnError(def.isFailWorkflowOnException());
157     setExceptionHandlingWorkflow(def.getExceptionHandlingWorkflow());
158     setExecutionCondition(def.getExecutionCondition());
159     setRetryStrategy(def.getRetryStrategy());
160     Set<String> defConfigs = def.getConfigurationKeys();
161     this.configurations = new TreeMap<>();
162     if (defConfigs != null) {
163       for (String key : defConfigs) {
164         configurations.put(key, def.getConfiguration(key));
165       }
166     }
167 
168     if ((retryStrategy == RetryStrategy.RETRY || retryStrategy == RetryStrategy.HOLD) && maxAttempts < 2) {
169       maxAttempts = 2;
170     }
171   }
172 
173   /**
174    * Constructs a new operation instance with the given id and initial state.
175    *
176    * @param id
177    *          the operation id
178    * @param state
179    *          the state
180    */
181   public WorkflowOperationInstance(String id, OperationState state) {
182     this();
183     setTemplate(id);
184     setState(state);
185   }
186 
187   public WorkflowOperationInstance(
188           String template,
189           Long jobId,
190           OperationState state,
191           String description,
192           Map<String, String> configurations,
193           boolean failOnError,
194           String executeCondition,
195           String exceptionHandlingWorkflow,
196           Boolean abortable,
197           Boolean continuable,
198           Date dateStarted,
199           Date dateCompleted,
200           Long timeInQueue,
201           int maxAttempts,
202           int failedAttempts,
203           String executionHost,
204           RetryStrategy retryStrategy) {
205     this.template = template;
206     this.jobId = jobId;
207     this.state = state;
208     this.description = description;
209     this.configurations = configurations;
210     this.failOnError = failOnError;
211     this.executeCondition = executeCondition;
212     this.exceptionHandlingWorkflow = exceptionHandlingWorkflow;
213     this.abortable = abortable;
214     this.continuable = continuable;
215     this.dateStarted = dateStarted;
216     this.dateCompleted = dateCompleted;
217     this.timeInQueue = timeInQueue;
218     this.maxAttempts = maxAttempts;
219     this.failedAttempts = failedAttempts;
220     this.executionHost = executionHost;
221     this.retryStrategy = retryStrategy;
222   }
223 
224   /**
225    * Sets the template
226    *
227    * @param template
228    *          the template
229    */
230   public void setTemplate(String template) {
231     this.template = template;
232   }
233 
234   /**
235    * Gets the operation type.
236    *
237    * @return the operation type
238    */
239   public String getTemplate() {
240     return template;
241   }
242 
243   /**
244    * Gets the unique identifier for this operation, or null.
245    *
246    * @return the identifier, or null if this operation has not yet run
247    */
248   public Long getId() {
249     return jobId;
250   }
251 
252   /**
253    * Sets the unique identifier for this operation.
254    *
255    * @param jobId
256    *          the identifier
257    */
258   public void setId(Long jobId) {
259     this.jobId = jobId;
260   }
261 
262   /**
263    * Gets the operation description
264    *
265    * @return the description
266    */
267   public String getDescription() {
268     return description;
269   }
270 
271   /**
272    * Set the operation description.
273    *
274    * @param description The new description
275    */
276   public void setDescription(String description) {
277     this.description = description;
278   }
279 
280   /**
281    * The state of this operation.
282    */
283   public OperationState getState() {
284     return state;
285   }
286 
287   /**
288    * Sets the state of this operation
289    *
290    * @param state
291    *          the state to set
292    */
293   public void setState(OperationState state) {
294     Date now = new Date();
295     if (OperationState.RUNNING.equals(state)) {
296       this.dateStarted = now;
297     } else if (OperationState.FAILED.equals(state) || OperationState.SUCCEEDED.equals(state)) {
298       this.dateCompleted = now;
299     }
300     this.state = state;
301   }
302 
303   /**
304    * Return configuration of this workflow operation as Map.
305    * Guaranteed to be not null
306    *
307    * @return Configuration map
308    */
309   public Map<String, String> getConfigurations() {
310     return configurations;
311   }
312 
313   /**
314    * {@inheritDoc}
315    *
316    * @see org.opencastproject.workflow.api.WorkflowInstance#getConfiguration(java.lang.String)
317    */
318   @Override
319   public String getConfiguration(String key) {
320     if (key == null || configurations == null) {
321       return null;
322     }
323     return configurations.get(key);
324   }
325 
326   /**
327    * {@inheritDoc}
328    *
329    * @see org.opencastproject.workflow.api.WorkflowInstance#removeConfiguration(java.lang.String)
330    */
331   @Override
332   public void removeConfiguration(String key) {
333     if (key == null || configurations == null) {
334       return;
335     }
336     configurations.remove(key);
337   }
338 
339   /**
340    * {@inheritDoc}
341    *
342    * @see org.opencastproject.workflow.api.WorkflowInstance#setConfiguration(java.lang.String, java.lang.String)
343    */
344   @Override
345   public void setConfiguration(String key, String value) {
346     if (key == null) {
347       return;
348     }
349     if (configurations == null) {
350       configurations = new TreeMap<>();
351     }
352 
353     configurations.put(key, value);
354   }
355 
356   /**
357    * {@inheritDoc}
358    *
359    * @see org.opencastproject.workflow.api.WorkflowOperationInstance#getConfigurationKeys()
360    */
361   @Override
362   public Set<String> getConfigurationKeys() {
363     if (configurations == null) {
364       return Collections.emptySet();
365     }
366     return configurations.keySet();
367   }
368 
369   /** The workflow to run if an exception is thrown while this operation is running. */
370   public String getExceptionHandlingWorkflow() {
371     return exceptionHandlingWorkflow;
372   }
373 
374   public void setExceptionHandlingWorkflow(String exceptionHandlingWorkflow) {
375     this.exceptionHandlingWorkflow = exceptionHandlingWorkflow;
376   }
377 
378   /**
379    * If true, this workflow will be put into a failed (or failing, if getExceptionHandlingWorkflow() is not null) state
380    * when exceptions are thrown during an operation.
381    */
382   public boolean isFailOnError() {
383     return failOnError;
384   }
385 
386   public void setFailOnError(boolean failOnError) {
387     this.failOnError = failOnError;
388   }
389 
390   /**
391    * The timestamp this operation started. If the job was queued, this can be significantly later than the date created.
392    */
393   public Date getDateStarted() {
394     return dateStarted;
395   }
396 
397   /** The number of milliseconds this operation waited in a service queue */
398   public Long getTimeInQueue() {
399     return timeInQueue;
400   }
401 
402   public void setTimeInQueue(long timeInQueue) {
403     this.timeInQueue = timeInQueue;
404   }
405 
406   /** The timestamp this operation completed */
407   public Date getDateCompleted() {
408     return dateCompleted;
409   }
410 
411   /**
412    * Returns either <code>null</code> or <code>true</code> to have the operation executed. Any other value is
413    * interpreted as <code>false</code> and will skip the operation.
414    * <p>
415    * Usually, this will be a variable name such as <code>${foo}</code>, which will be replaced with its acutal value
416    * once the workflow is executed.
417    * <p>
418    * If both <code>getExecuteCondition()</code> and <code>getSkipCondition</code> return a non-null value, the execute
419    * condition takes precedence.
420    *
421    * @return the execution condition.
422    */
423   public String getExecutionCondition() {
424     return executeCondition;
425   }
426 
427   public void setExecutionCondition(String condition) {
428     this.executeCondition = condition;
429   }
430 
431   /**
432    * Returns <code>true</code> if this operation can be continued by the user from an optional hold state. A return
433    * value of <code>null</code> indicates that this operation instance does not have a hold state.
434    *
435    * @return <code>true</code> if this operation instance is continuable
436    */
437   public Boolean isContinuable() {
438     return continuable;
439   }
440 
441   /**
442    * Defines whether this operation instance should be continuable from a hold state or whether it is resumed
443    * automatically.
444    *
445    * @param continuable
446    *          <code>true</code> to allow the user to resume the operation
447    */
448   public void setContinuable(Boolean continuable) {
449     this.continuable = continuable;
450   }
451 
452   /**
453    * Returns <code>true</code> if this operation can be aborted by the user from an optional hold state. If a resumable
454    * operation is aborted from its hold state, the workflow is put into
455    * {@link org.opencastproject.workflow.api.WorkflowInstance.WorkflowState#STOPPED}. A return value of
456    * <code>null</code> indicates that this operation instance does not have a hold state.
457    *
458    * @return <code>true</code> if this operation instance is abortable
459    */
460   public Boolean isAbortable() {
461     return abortable;
462   }
463 
464   /**
465    * Defines whether this operation instance should be abortable from a hold state.
466    *
467    * @param abortable
468    *          <code>true</code> to allow the user to cancel the operation
469    */
470   public void setAbortable(Boolean abortable) {
471     this.abortable = abortable;
472   }
473 
474   /**
475    * Return the strategy to use in case of operation failure
476    *
477    * @return a strategy from {@link org.opencastproject.workflow.api.RetryStrategy}.
478    */
479   public RetryStrategy getRetryStrategy() {
480     return retryStrategy;
481   }
482 
483   private void setRetryStrategy(RetryStrategy retryStrategy) {
484     this.retryStrategy = retryStrategy;
485   }
486 
487   /**
488    * Returns the number of attempts the workflow service will make to execute this operation.
489    *
490    * @return the maximum number of retries before failing
491    */
492   public int getMaxAttempts() {
493     return maxAttempts;
494   }
495 
496   /**
497    * @param maxAttempts
498    *          the maxAttempts to set
499    * @throws IllegalArgumentException
500    *           if maxAttempts is less than one.
501    */
502   private void setMaxAttempts(int maxAttempts) {
503     if (maxAttempts < 1) {
504       throw new IllegalArgumentException("maxAttempts must be >=1");
505     }
506     this.maxAttempts = maxAttempts;
507   }
508 
509   /**
510    * Returns the number of failed executions that have previously been attempted.
511    *
512    * @return the number of previous attempts
513    */
514   public int getFailedAttempts() {
515     return failedAttempts;
516   }
517 
518   public void setFailedAttempts(int failedAttempts) {
519     this.failedAttempts = failedAttempts;
520   }
521 
522   /**
523    * Returns the current execution host
524    *
525    * @return the execution host
526    */
527   public String getExecutionHost() {
528     return executionHost;
529   }
530 
531   /**
532    * Sets the current execution host
533    *
534    * @param executionHost
535    *          the execution host
536    */
537   public void setExecutionHost(String executionHost) {
538     this.executionHost = executionHost;
539   }
540 
541   public WorkflowInstance getWorkflowInstance() {
542     return instance;
543   }
544 
545   public void setWorkflowInstance(WorkflowInstance instance) {
546     this.instance = instance;
547   }
548 
549   @Override
550   public int hashCode() {
551     return Long.valueOf(id).hashCode();
552   }
553 
554   @Override
555   public boolean equals(Object o) {
556     if (this == o) {
557       return true;
558     }
559     if (o instanceof WorkflowOperationInstance) {
560       WorkflowOperationInstance other = (WorkflowOperationInstance) o;
561       return other.getTemplate().equals(this.getTemplate()) && Objects.equals(other.id, this.id);
562     }
563     return false;
564   }
565 
566   /**
567    * {@inheritDoc}
568    *
569    * @see java.lang.Object#toString()
570    */
571   @Override
572   public String toString() {
573     return "operation:'" + template +  ", state:'" + this.state + "'";
574   }
575 }