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