WorkflowOperationInstance.java
/*
* Licensed to The Apereo Foundation under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
*
* The Apereo Foundation licenses this file to you under the Educational
* Community License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License
* at:
*
* http://opensource.org/licenses/ecl2.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*
*/
package org.opencastproject.workflow.api;
import java.util.Collections;
import java.util.Date;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.TreeMap;
import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.JoinColumn;
import javax.persistence.Lob;
import javax.persistence.ManyToOne;
import javax.persistence.MapKeyColumn;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
/**
* A workflow operation belonging to a workflow instance.
*/
@Entity(name = "WorkflowOperationInstance")
@Access(AccessType.FIELD)
@Table(name = "oc_workflow_operation", indexes = {
@Index(name = "IX_oc_workflow_operation_workflow_id", columnList = ("workflow_id"))})
public class WorkflowOperationInstance implements Configurable {
public enum OperationState {
INSTANTIATED, RUNNING, PAUSED, SUCCEEDED, FAILED, SKIPPED, RETRY
}
@Id
@GeneratedValue
@Column(name = "id")
private Long id;
@Column(name = "template")
protected String template;
@Column(name = "job")
protected Long jobId;
@Column(name = "state")
protected OperationState state;
@Column(name = "description")
@Lob
protected String description;
@ElementCollection
@CollectionTable(
name = "oc_workflow_operation_configuration",
joinColumns = @JoinColumn(name = "workflow_operation_id"),
indexes = {
@Index(name = "IX_oc_workflow_operation_configuration_workflow_operation_id", columnList = ("workflow_operation_id")),
}
)
@MapKeyColumn(name = "configuration_key", nullable = false)
@Lob
@Column(name = "configuration_value")
protected Map<String, String> configurations;
@Column(name = "fail_on_error")
protected boolean failOnError;
@Column(name = "if_condition")
@Lob
protected String executeCondition;
@Column(name = "exception_handler_workflow")
protected String exceptionHandlingWorkflow;
@Column(name = "abortable")
protected Boolean abortable;
@Column(name = "continuable")
protected Boolean continuable;
@Column(name = "started")
@Temporal(TemporalType.TIMESTAMP)
protected Date dateStarted;
@Column(name = "completed")
@Temporal(TemporalType.TIMESTAMP)
protected Date dateCompleted;
@Column(name = "time_in_queue")
protected Long timeInQueue;
@Column(name = "max_attempts")
protected int maxAttempts;
@Column(name = "failed_attempts")
protected int failedAttempts;
@Column(name = "execution_host")
protected String executionHost;
@Column(name = "retry_strategy", length = 128)
protected RetryStrategy retryStrategy;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "workflow_id", nullable = false)
private WorkflowInstance instance;
/**
* No-arg constructor needed for JAXB serialization
*/
public WorkflowOperationInstance() {
this.maxAttempts = 1;
this.retryStrategy = RetryStrategy.NONE;
}
/**
* Builds a new workflow operation instance based on another workflow operation.
*
* @param def
* the workflow definition
*/
public WorkflowOperationInstance(WorkflowOperationDefinition def) {
this();
setTemplate(def.getId());
setState(OperationState.INSTANTIATED);
setDescription(def.getDescription());
setMaxAttempts(def.getMaxAttempts());
setFailOnError(def.isFailWorkflowOnException());
setExceptionHandlingWorkflow(def.getExceptionHandlingWorkflow());
setExecutionCondition(def.getExecutionCondition());
setRetryStrategy(def.getRetryStrategy());
Set<String> defConfigs = def.getConfigurationKeys();
this.configurations = new TreeMap<>();
if (defConfigs != null) {
for (String key : defConfigs) {
configurations.put(key, def.getConfiguration(key));
}
}
if ((retryStrategy == RetryStrategy.RETRY || retryStrategy == RetryStrategy.HOLD) && maxAttempts < 2) {
maxAttempts = 2;
}
}
/**
* Constructs a new operation instance with the given id and initial state.
*
* @param id
* the operation id
* @param state
* the state
*/
public WorkflowOperationInstance(String id, OperationState state) {
this();
setTemplate(id);
setState(state);
}
public WorkflowOperationInstance(
String template,
Long jobId,
OperationState state,
String description,
Map<String, String> configurations,
boolean failOnError,
String executeCondition,
String exceptionHandlingWorkflow,
Boolean abortable,
Boolean continuable,
Date dateStarted,
Date dateCompleted,
Long timeInQueue,
int maxAttempts,
int failedAttempts,
String executionHost,
RetryStrategy retryStrategy) {
this.template = template;
this.jobId = jobId;
this.state = state;
this.description = description;
this.configurations = configurations;
this.failOnError = failOnError;
this.executeCondition = executeCondition;
this.exceptionHandlingWorkflow = exceptionHandlingWorkflow;
this.abortable = abortable;
this.continuable = continuable;
this.dateStarted = dateStarted;
this.dateCompleted = dateCompleted;
this.timeInQueue = timeInQueue;
this.maxAttempts = maxAttempts;
this.failedAttempts = failedAttempts;
this.executionHost = executionHost;
this.retryStrategy = retryStrategy;
}
/**
* Sets the template
*
* @param template
* the template
*/
public void setTemplate(String template) {
this.template = template;
}
/**
* Gets the operation type.
*
* @return the operation type
*/
public String getTemplate() {
return template;
}
/**
* Gets the unique identifier for this operation, or null.
*
* @return the identifier, or null if this operation has not yet run
*/
public Long getId() {
return jobId;
}
/**
* Sets the unique identifier for this operation.
*
* @param jobId
* the identifier
*/
public void setId(Long jobId) {
this.jobId = jobId;
}
/**
* Gets the operation description
*
* @return the description
*/
public String getDescription() {
return description;
}
/**
* Set the operation description.
*
* @param description The new description
*/
public void setDescription(String description) {
this.description = description;
}
/**
* The state of this operation.
*/
public OperationState getState() {
return state;
}
/**
* Sets the state of this operation
*
* @param state
* the state to set
*/
public void setState(OperationState state) {
Date now = new Date();
if (OperationState.RUNNING.equals(state)) {
this.dateStarted = now;
} else if (OperationState.FAILED.equals(state) || OperationState.SUCCEEDED.equals(state)) {
this.dateCompleted = now;
}
this.state = state;
}
/**
* Return configuration of this workflow operation as Map.
* Guaranteed to be not null
*
* @return Configuration map
*/
public Map<String, String> getConfigurations() {
return configurations;
}
/**
* {@inheritDoc}
*
* @see org.opencastproject.workflow.api.WorkflowInstance#getConfiguration(java.lang.String)
*/
@Override
public String getConfiguration(String key) {
if (key == null || configurations == null)
return null;
return configurations.get(key);
}
/**
* {@inheritDoc}
*
* @see org.opencastproject.workflow.api.WorkflowInstance#removeConfiguration(java.lang.String)
*/
@Override
public void removeConfiguration(String key) {
if (key == null || configurations == null)
return;
configurations.remove(key);
}
/**
* {@inheritDoc}
*
* @see org.opencastproject.workflow.api.WorkflowInstance#setConfiguration(java.lang.String, java.lang.String)
*/
@Override
public void setConfiguration(String key, String value) {
if (key == null)
return;
if (configurations == null)
configurations = new TreeMap<>();
configurations.put(key, value);
}
/**
* {@inheritDoc}
*
* @see org.opencastproject.workflow.api.WorkflowOperationInstance#getConfigurationKeys()
*/
@Override
public Set<String> getConfigurationKeys() {
if (configurations == null) {
return Collections.emptySet();
}
return configurations.keySet();
}
/** The workflow to run if an exception is thrown while this operation is running. */
public String getExceptionHandlingWorkflow() {
return exceptionHandlingWorkflow;
}
public void setExceptionHandlingWorkflow(String exceptionHandlingWorkflow) {
this.exceptionHandlingWorkflow = exceptionHandlingWorkflow;
}
/**
* If true, this workflow will be put into a failed (or failing, if getExceptionHandlingWorkflow() is not null) state
* when exceptions are thrown during an operation.
*/
public boolean isFailOnError() {
return failOnError;
}
public void setFailOnError(boolean failOnError) {
this.failOnError = failOnError;
}
/**
* The timestamp this operation started. If the job was queued, this can be significantly later than the date created.
*/
public Date getDateStarted() {
return dateStarted;
}
/** The number of milliseconds this operation waited in a service queue */
public Long getTimeInQueue() {
return timeInQueue;
}
public void setTimeInQueue(long timeInQueue) {
this.timeInQueue = timeInQueue;
}
/** The timestamp this operation completed */
public Date getDateCompleted() {
return dateCompleted;
}
/**
* Returns either <code>null</code> or <code>true</code> to have the operation executed. Any other value is
* interpreted as <code>false</code> and will skip the operation.
* <p>
* Usually, this will be a variable name such as <code>${foo}</code>, which will be replaced with its acutal value
* once the workflow is executed.
* <p>
* If both <code>getExecuteCondition()</code> and <code>getSkipCondition</code> return a non-null value, the execute
* condition takes precedence.
*
* @return the execution condition.
*/
public String getExecutionCondition() {
return executeCondition;
}
public void setExecutionCondition(String condition) {
this.executeCondition = condition;
}
/**
* Returns <code>true</code> if this operation can be continued by the user from an optional hold state. A return
* value of <code>null</code> indicates that this operation instance does not have a hold state.
*
* @return <code>true</code> if this operation instance is continuable
*/
public Boolean isContinuable() {
return continuable;
}
/**
* Defines whether this operation instance should be continuable from a hold state or whether it is resumed
* automatically.
*
* @param continuable
* <code>true</code> to allow the user to resume the operation
*/
public void setContinuable(Boolean continuable) {
this.continuable = continuable;
}
/**
* Returns <code>true</code> if this operation can be aborted by the user from an optional hold state. If a resumable
* operation is aborted from its hold state, the workflow is put into
* {@link org.opencastproject.workflow.api.WorkflowInstance.WorkflowState#STOPPED}. A return value of
* <code>null</code> indicates that this operation instance does not have a hold state.
*
* @return <code>true</code> if this operation instance is abortable
*/
public Boolean isAbortable() {
return abortable;
}
/**
* Defines whether this operation instance should be abortable from a hold state.
*
* @param abortable
* <code>true</code> to allow the user to cancel the operation
*/
public void setAbortable(Boolean abortable) {
this.abortable = abortable;
}
/**
* Return the strategy to use in case of operation failure
*
* @return a strategy from {@link org.opencastproject.workflow.api.RetryStrategy}.
*/
public RetryStrategy getRetryStrategy() {
return retryStrategy;
}
private void setRetryStrategy(RetryStrategy retryStrategy) {
this.retryStrategy = retryStrategy;
}
/**
* Returns the number of attempts the workflow service will make to execute this operation.
*
* @return the maximum number of retries before failing
*/
public int getMaxAttempts() {
return maxAttempts;
}
/**
* @param maxAttempts
* the maxAttempts to set
* @throws IllegalArgumentException
* if maxAttempts is less than one.
*/
private void setMaxAttempts(int maxAttempts) {
if (maxAttempts < 1) {
throw new IllegalArgumentException("maxAttempts must be >=1");
}
this.maxAttempts = maxAttempts;
}
/**
* Returns the number of failed executions that have previously been attempted.
*
* @return the number of previous attempts
*/
public int getFailedAttempts() {
return failedAttempts;
}
public void setFailedAttempts(int failedAttempts) {
this.failedAttempts = failedAttempts;
}
/**
* Returns the current execution host
*
* @return the execution host
*/
public String getExecutionHost() {
return executionHost;
}
/**
* Sets the current execution host
*
* @param executionHost
* the execution host
*/
public void setExecutionHost(String executionHost) {
this.executionHost = executionHost;
}
public WorkflowInstance getWorkflowInstance() {
return instance;
}
public void setWorkflowInstance(WorkflowInstance instance) {
this.instance = instance;
}
@Override
public int hashCode() {
return Long.valueOf(id).hashCode();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o instanceof WorkflowOperationInstance) {
WorkflowOperationInstance other = (WorkflowOperationInstance) o;
return other.getTemplate().equals(this.getTemplate()) && Objects.equals(other.id, this.id);
}
return false;
}
/**
* {@inheritDoc}
*
* @see java.lang.Object#toString()
*/
@Override
public String toString() {
return "operation:'" + template + ", state:'" + this.state + "'";
}
}