/*************************************************************************
* Copyright 2009-2016 Eucalyptus Systems, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
* Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta
* CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need
* additional information or have any questions.
************************************************************************/
package com.eucalyptus.simpleworkflow;
import static com.eucalyptus.simpleworkflow.common.SimpleWorkflowMetadata.WorkflowExecutionMetadata;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
import javax.persistence.CascadeType;
import javax.persistence.CollectionTable;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.Lob;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OrderBy;
import javax.persistence.OrderColumn;
import javax.persistence.PersistenceContext;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import org.hibernate.annotations.Type;
import com.eucalyptus.component.ComponentIds;
import com.eucalyptus.entities.AuxiliaryDatabaseObject;
import com.eucalyptus.entities.AuxiliaryDatabaseObjects;
import com.eucalyptus.entities.UserMetadata;
import com.eucalyptus.simpleworkflow.common.SimpleWorkflow;
import com.eucalyptus.simpleworkflow.common.SimpleWorkflowMetadatas;
import com.eucalyptus.simpleworkflow.common.model.WorkflowEventAttributes;
import com.eucalyptus.util.CollectionUtils;
import com.eucalyptus.auth.principal.FullName;
import com.eucalyptus.auth.principal.OwnerFullName;
import com.google.common.base.Functions;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
/**
*
*/
@Entity
@PersistenceContext( name = "eucalyptus_simpleworkflow" )
@Table( name = "swf_workflow_execution" )
@AuxiliaryDatabaseObjects( {
@AuxiliaryDatabaseObject(
dialect = "org.hibernate.dialect.PostgreSQLDialect",
create = "create index swf_workflow_execution_open_pending_idx on ${schema}.swf_workflow_execution ( metadata_account_id, domain, task_list, metadata_state, decision_status ) where metadata_state = 'Open' and decision_status = 'Pending'",
drop = "drop index if exists ${schema}.swf_workflow_execution_open_pending_idx"
),
})
public class WorkflowExecution extends UserMetadata<WorkflowExecution.ExecutionStatus> implements WorkflowExecutionMetadata {
private static final long serialVersionUID = 1L;
public enum ExecutionStatus {
Open,
Closed,
;
public String toString( ) {
return name( ).toUpperCase( );
}
}
public enum CloseStatus {
Completed,
Failed,
Canceled,
Terminated,
Continued_As_New,
Timed_Out
;
public String toString( ) {
return name( ).toUpperCase( );
}
public static CloseStatus fromString( final String value ) {
return Iterables.tryFind(
Arrays.asList( values( ) ),
CollectionUtils.propertyPredicate( value, Functions.toStringFunction( ) ) ).or( CloseStatus.Completed );
}
}
public enum DecisionStatus {
Idle,
Pending,
Active,
;
}
@ManyToOne
@JoinColumn( name = "domain_id", nullable = false, updatable = false )
private Domain domain;
@ManyToOne( fetch = FetchType.LAZY )
@JoinColumn( name = "workflow_type_id", nullable = false, updatable = false )
private WorkflowType workflowType;
@Column( name = "workflow_id", length = 256, nullable = false, updatable = false )
private String workflowId;
@Column( name = "child_policy", nullable = false, updatable = false )
private String childPolicy;
@Column( name = "domain", length = 256, nullable = false, updatable = false )
private String domainName;
@Column( name = "domain_uuid", nullable = false, updatable = false )
private String domainUuid;
@Column( name = "task_list", length = 256, nullable = false, updatable = false )
private String taskList;
@Column( name = "exec_start_to_close_timeout", nullable = false, updatable = false )
private Integer executionStartToCloseTimeout;
@Column( name = "task_start_to_close_timeout", updatable = false )
private Integer taskStartToCloseTimeout;
@Column( name = "cancel_requested", nullable = false )
private Boolean cancelRequested;
@Column( name = "decision_status" )
@Enumerated( EnumType.STRING )
private DecisionStatus decisionStatus;
@Column( name = "decision_timestamp", nullable = false)
@Temporal( TemporalType.TIMESTAMP )
private Date decisionTimestamp;
@Column( name = "close_status" )
@Enumerated( EnumType.STRING )
private CloseStatus closeStatus;
@Column( name = "close_timestamp" )
@Temporal( TemporalType.TIMESTAMP )
private Date closeTimestamp;
@Column( name = "retention_timestamp" )
@Temporal( TemporalType.TIMESTAMP )
private Date retentionTimestamp;
@ElementCollection
@CollectionTable( name = "swf_workflow_execution_tags" )
@Column( name = "tag", length = 256 )
@OrderColumn( name = "tag_index")
private List<String> tagList;
@Column( name = "latest_activity_timestamp" )
@Temporal( TemporalType.TIMESTAMP )
private Date latestActivityTaskScheduled;
@Column( name = "latest_execution_context" )
@Lob
@Type(type="org.hibernate.type.StringClobType")
private String latestExecutionContext;
@Column( name = "timeout_timestamp" )
@Temporal( TemporalType.TIMESTAMP )
private Date timeoutTimestamp;
@OneToMany( fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST, CascadeType.REMOVE }, orphanRemoval = true, mappedBy = "workflowExecution" )
@OrderBy( "eventOrder" )
private List<WorkflowHistoryEvent> workflowHistory;
@OneToMany( fetch = FetchType.LAZY, cascade = { CascadeType.REMOVE }, orphanRemoval = true, mappedBy = "workflowExecution" )
@OrderColumn( name = "scheduled_event_id" )
private List<ActivityTask> activityTasks;
@OneToMany( fetch = FetchType.LAZY, cascade = { CascadeType.REMOVE }, orphanRemoval = true, mappedBy = "workflowExecution" )
@OrderColumn( name = "decision_task_comp_event_id" )
private List<Timer> timers;
protected WorkflowExecution( ) {
}
protected WorkflowExecution( final OwnerFullName owner, final String displayName ) {
super( owner, displayName );
}
public static WorkflowExecution create( final OwnerFullName owner,
final String name, /* runId */
final Domain domain,
final WorkflowType workflowType,
final String workflowId,
final String childPolicy,
final String taskList,
@Nullable final Integer executionStartToCloseTimeout,
@Nullable final Integer taskStartToCloseTimeout,
final List<String> tags,
final List<WorkflowEventAttributes> eventAttributes ) {
final WorkflowExecution workflowExecution = new WorkflowExecution( owner, name );
workflowExecution.setDomain( domain );
workflowExecution.setDomainName( domain.getDisplayName( ) );
workflowExecution.setDomainUuid( domain.getNaturalId( ) );
workflowExecution.setWorkflowType( workflowType );
workflowExecution.setWorkflowId( workflowId );
workflowExecution.setState( ExecutionStatus.Open );
workflowExecution.setChildPolicy( childPolicy );
workflowExecution.setTaskList( taskList );
workflowExecution.setExecutionStartToCloseTimeout( executionStartToCloseTimeout );
workflowExecution.setTaskStartToCloseTimeout( taskStartToCloseTimeout );
workflowExecution.setTagList( tags );
workflowExecution.setCancelRequested( false );
workflowExecution.setDecisionStatus( DecisionStatus.Pending );
workflowExecution.setDecisionTimestamp( new Date( ) );
workflowExecution.setWorkflowHistory( Lists.<WorkflowHistoryEvent>newArrayList( ) );
for ( final WorkflowEventAttributes attributes : eventAttributes ) {
workflowExecution.addHistoryEvent( WorkflowHistoryEvent.create( workflowExecution, attributes ) );
}
return workflowExecution;
}
public Date calculateNextTimeout( ) {
final Long timeout = CollectionUtils.reduce( Iterables.filter( Lists.newArrayList(
toTimeout( getCreationTimestamp( ), getExecutionStartToCloseTimeout( ) ),
toTimeout( getDecisionTimestamp( ), getDecisionStatus( ) == DecisionStatus.Active ? getTaskStartToCloseTimeout( ) : null )
), Predicates.notNull( ) ), Long.MAX_VALUE, CollectionUtils.lmin( ) );
return timeout == Long.MAX_VALUE ? null : new Date( timeout );
}
public boolean isWorkflowTimedOut( final long timestamp,
final long maximumDurationMillis ){
final Long timeout = toTimeout( getCreationTimestamp( ), getExecutionStartToCloseTimeout( ) );
return
( timeout != null && timeout < timestamp ) ||
( maximumDurationMillis > 0 && ( getCreationTimestamp( ).getTime( ) + maximumDurationMillis ) < timestamp );
}
private static Long toTimeout( final Date from, final Integer period ) {
return period == null ?
null :
from.getTime( ) + TimeUnit.SECONDS.toMillis( period );
}
public static WorkflowExecution exampleWithOwner( final OwnerFullName owner ) {
return new WorkflowExecution( owner, null );
}
public static WorkflowExecution exampleWithName( final OwnerFullName owner, final String name ) {
return new WorkflowExecution( owner, name );
}
public static WorkflowExecution exampleWithPendingDecision( final OwnerFullName owner,
final String domain,
final String taskList ) {
final WorkflowExecution workflowExecution = new WorkflowExecution( owner, null );
workflowExecution.setDomainName( domain );
workflowExecution.setTaskList( taskList );
workflowExecution.setDecisionStatus( DecisionStatus.Pending );
workflowExecution.setState( ExecutionStatus.Open );
workflowExecution.setStateChangeStack( null );
workflowExecution.setLastState( null );
return workflowExecution;
}
public static WorkflowExecution exampleWithUniqueName( final OwnerFullName owner,
final String domain,
final String runId ) {
final WorkflowExecution workflowExecution = new WorkflowExecution( owner, runId );
workflowExecution.setUniqueName( createUniqueName( owner.getAccountNumber(), domain, runId ) );
return workflowExecution;
}
public static WorkflowExecution exampleForOpenWorkflow( ) {
return exampleForOpenWorkflow( null, null, null );
}
public static WorkflowExecution exampleForOpenWorkflow( final OwnerFullName owner,
final String domain,
final String workflowId ) {
return exampleForOpenWorkflow( owner, domain, workflowId, null );
}
public static WorkflowExecution exampleForOpenWorkflow( final OwnerFullName owner,
final String domain,
final String workflowId,
final String runId ) {
final WorkflowExecution workflowExecution = new WorkflowExecution( owner, runId );
workflowExecution.setDomainName( domain );
workflowExecution.setWorkflowId( workflowId );
workflowExecution.setState( ExecutionStatus.Open );
workflowExecution.setStateChangeStack( null );
workflowExecution.setLastState( null );
return workflowExecution;
}
public static WorkflowExecution exampleForClosedWorkflow( ) {
return exampleForClosedWorkflow( null, null, null );
}
public static WorkflowExecution exampleForClosedWorkflow( final OwnerFullName owner,
final String domain,
final String workflowId ) {
final WorkflowExecution workflowExecution = new WorkflowExecution( owner, null );
workflowExecution.setDomainName( domain );
workflowExecution.setWorkflowId( workflowId );
workflowExecution.setState( ExecutionStatus.Closed );
workflowExecution.setStateChangeStack( null );
workflowExecution.setLastState( null );
return workflowExecution;
}
@Override
protected String createUniqueName( ) {
return createUniqueName( getOwnerAccountNumber(),
SimpleWorkflowMetadatas.toDisplayName().apply( getDomain() ),
getDisplayName() );
}
private static String createUniqueName( final String accountNumber,
final String domain,
final String runId ) {
return accountNumber + ":" + domain + ":" + runId;
}
public Long addHistoryEvent( final WorkflowHistoryEvent event ) throws WorkflowHistorySizeLimitException {
// Order would be filled in on save, but we may need the event
// identifier before the entity is stored
event.setEventOrder( (long) workflowHistory.size( ) );
workflowHistory.add( event );
if ( workflowHistory.size( ) > SimpleWorkflowProperties.getWorkflowExecutionHistorySize() ) {
throw new WorkflowHistorySizeLimitException( this );
}
updateTimeStamps( ); // ensure workflow version incremented
return event.getEventId();
}
public void closeWorkflow( final CloseStatus closeStatus,
final WorkflowHistoryEvent event ) {
setState( WorkflowExecution.ExecutionStatus.Closed );
setCloseStatus( closeStatus );
setCloseTimestamp( new Date( ) );
setRetentionTimestamp( new Date(
getCloseTimestamp( ).getTime( ) +
TimeUnit.DAYS.toMillis( getDomain( ).getWorkflowExecutionRetentionPeriodInDays( ) ) ) );
addHistoryEvent( event );
}
@Override
public String getPartition( ) {
return "eucalyptus";
}
@Override
public FullName getFullName( ) {
return FullName.create.vendor( "euca" )
.region( ComponentIds.lookup( SimpleWorkflow.class ).name() )
.namespace( this.getOwnerAccountNumber() )
.relativeId(
"domain", SimpleWorkflowMetadatas.toDisplayName( ).apply( getDomain() ),
"run-id", getDisplayName() );
}
public Domain getDomain() {
return domain;
}
public void setDomain( final Domain domain ) {
this.domain = domain;
}
public WorkflowType getWorkflowType( ) {
return workflowType;
}
public void setWorkflowType( final WorkflowType workflowType ) {
this.workflowType = workflowType;
}
public String getWorkflowId( ) {
return workflowId;
}
public void setWorkflowId( final String workflowId ) {
this.workflowId = workflowId;
}
public String getChildPolicy( ) {
return childPolicy;
}
public void setChildPolicy( final String childPolicy ) {
this.childPolicy = childPolicy;
}
public String getDomainName() {
return domainName;
}
public void setDomainName( final String domainName ) {
this.domainName = domainName;
}
public String getDomainUuid( ) {
return domainUuid;
}
public void setDomainUuid( final String domainUuid ) {
this.domainUuid = domainUuid;
}
public String getTaskList( ) {
return taskList;
}
public void setTaskList( final String taskList ) {
this.taskList = taskList;
}
public Integer getExecutionStartToCloseTimeout( ) {
return executionStartToCloseTimeout;
}
public void setExecutionStartToCloseTimeout( final Integer executionStartToCloseTimeout ) {
this.executionStartToCloseTimeout = executionStartToCloseTimeout;
}
public Integer getTaskStartToCloseTimeout( ) {
return taskStartToCloseTimeout;
}
public void setTaskStartToCloseTimeout( final Integer taskStartToCloseTimeout ) {
this.taskStartToCloseTimeout = taskStartToCloseTimeout;
}
public Boolean getCancelRequested( ) {
return cancelRequested;
}
public void setCancelRequested( final Boolean cancelRequested ) {
this.cancelRequested = cancelRequested;
}
public DecisionStatus getDecisionStatus( ) {
return decisionStatus;
}
public void setDecisionStatus( final DecisionStatus decisionStatus ) {
this.decisionStatus = decisionStatus;
}
public Date getDecisionTimestamp() {
return decisionTimestamp;
}
public void setDecisionTimestamp( final Date decisionTimestamp ) {
this.decisionTimestamp = decisionTimestamp;
}
public CloseStatus getCloseStatus( ) {
return closeStatus;
}
public void setCloseStatus( final CloseStatus closeStatus ) {
this.closeStatus = closeStatus;
}
public Date getCloseTimestamp( ) {
return closeTimestamp;
}
public void setCloseTimestamp( final Date closeTimestamp ) {
this.closeTimestamp = closeTimestamp;
}
public Date getRetentionTimestamp() {
return retentionTimestamp;
}
public void setRetentionTimestamp( final Date retentionTimestamp ) {
this.retentionTimestamp = retentionTimestamp;
}
public List<String> getTagList( ) {
return tagList;
}
public void setTagList( final List<String> tagList ) {
this.tagList = tagList;
}
public Date getLatestActivityTaskScheduled( ) {
return latestActivityTaskScheduled;
}
public void setLatestActivityTaskScheduled( final Date latestActivityTaskScheduled ) {
this.latestActivityTaskScheduled = latestActivityTaskScheduled;
}
public String getLatestExecutionContext( ) {
return latestExecutionContext;
}
public void setLatestExecutionContext( final String latestExecutionContext ) {
this.latestExecutionContext = latestExecutionContext;
}
public Date getTimeoutTimestamp() {
return timeoutTimestamp;
}
public void setTimeoutTimestamp( final Date timeoutTimestamp ) {
this.timeoutTimestamp = timeoutTimestamp;
}
public List<WorkflowHistoryEvent> getWorkflowHistory() {
return workflowHistory;
}
public void setWorkflowHistory( final List<WorkflowHistoryEvent> workflowHistory ) {
this.workflowHistory = workflowHistory;
}
@PreUpdate
@PrePersist
protected void updateTimeout( ) {
updateTimeStamps( );
setTimeoutTimestamp( calculateNextTimeout( ) );
}
public static final class WorkflowHistorySizeLimitException extends RuntimeException {
private static final long serialVersionUID = 1L;
private final String accountNumber;
private final String domain;
private final String runId;
private final String workflowId;
public WorkflowHistorySizeLimitException( final WorkflowExecution workflowExecution ) {
this(
workflowExecution.getOwnerAccountNumber( ),
workflowExecution.getDomainName( ),
workflowExecution.getDisplayName( ),
workflowExecution.getWorkflowId( )
);
}
public WorkflowHistorySizeLimitException( final String accountNumber,
final String domain,
final String runId,
final String workflowId ) {
this.accountNumber = accountNumber;
this.domain = domain;
this.runId = runId;
this.workflowId = workflowId;
}
public String getAccountNumber() {
return accountNumber;
}
public String getDomain() {
return domain;
}
public String getRunId() {
return runId;
}
public String getWorkflowId() {
return workflowId;
}
}
}