/************************************************************************* * 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.Domain.Status.Registered; import static com.eucalyptus.simpleworkflow.WorkflowExecution.DecisionStatus.*; import static com.eucalyptus.simpleworkflow.WorkflowExecution.WorkflowHistorySizeLimitException; import static com.eucalyptus.simpleworkflow.WorkflowExecutions.WorkflowHistoryEventStringFunctions.EVENT_TYPE; import static com.eucalyptus.simpleworkflow.common.model.ScheduleActivityTaskFailedCause.*; import java.lang.System; import java.sql.SQLException; import java.util.Collections; import java.util.Date; import java.util.Deque; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentLinkedDeque; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; import javax.annotation.Nullable; import javax.inject.Inject; import org.apache.log4j.Logger; import org.hibernate.StaleObjectStateException; import org.hibernate.criterion.Conjunction; import org.hibernate.criterion.Restrictions; import org.hibernate.exception.ConstraintViolationException; import com.eucalyptus.auth.AuthQuotaException; import com.eucalyptus.auth.principal.AccountFullName; import com.eucalyptus.auth.principal.UserFullName; import com.eucalyptus.bootstrap.OrderedShutdown; import com.eucalyptus.component.annotation.ComponentNamed; import com.eucalyptus.context.Context; import com.eucalyptus.context.Contexts; import com.eucalyptus.entities.AbstractPersistent; import com.eucalyptus.entities.AbstractPersistentSupport; import com.eucalyptus.entities.Entities; import com.eucalyptus.entities.PersistenceExceptions; import com.eucalyptus.event.ClockTick; import com.eucalyptus.event.EventListener; import com.eucalyptus.event.Listeners; import com.eucalyptus.simpleworkflow.NotifyClient.NotifyTaskList; import com.eucalyptus.simpleworkflow.common.SimpleWorkflowMetadatas; import com.eucalyptus.simpleworkflow.common.model.*; import com.eucalyptus.simpleworkflow.tokens.TaskToken; import com.eucalyptus.simpleworkflow.tokens.TaskTokenException; import com.eucalyptus.simpleworkflow.tokens.TaskTokenManager; import com.eucalyptus.util.CollectionUtils; import com.eucalyptus.util.Exceptions; import com.eucalyptus.util.Pair; import com.eucalyptus.auth.type.RestrictedType; import com.eucalyptus.util.RestrictedTypes; import com.eucalyptus.util.TypeMappers; import com.eucalyptus.ws.Role; import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.base.Joiner; import com.google.common.base.MoreObjects; import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.google.common.collect.Collections2; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Ordering; import com.google.common.collect.Sets; /** * */ @SuppressWarnings( { "UnusedDeclaration", "Guava", "StaticPseudoFunctionalStyleMethod", "ThrowableResultOfMethodCallIgnored", "Convert2Lambda" } ) @ComponentNamed public class SimpleWorkflowService { private static final Logger logger = Logger.getLogger( SimpleWorkflowService.class ); private static final ConcurrentMap<NotifyTaskList, Long> taskListActivity = Maps.newConcurrentMap( ); private static final Deque<Pair<Long,Consumer<Boolean>>> timestampedPollers = new ConcurrentLinkedDeque<>( ); static { OrderedShutdown.registerPreShutdownHook( new PollerShutdown( ) ); } private final Domains domains; private final ActivityTasks activityTasks; private final ActivityTypes activityTypes; private final WorkflowTypes workflowTypes; private final WorkflowExecutions workflowExecutions; private final TaskTokenManager taskTokenManager; private final Timers timers; @Inject public SimpleWorkflowService( final Domains domains, final ActivityTasks activityTasks, final ActivityTypes activityTypes, final WorkflowTypes workflowTypes, final WorkflowExecutions workflowExecutions, final TaskTokenManager taskTokenManager, final Timers timers ) { this.domains = domains; this.activityTasks = activityTasks; this.activityTypes = activityTypes; this.workflowTypes = workflowTypes; this.workflowExecutions = workflowExecutions; this.taskTokenManager = taskTokenManager; this.timers = timers; } public SimpleWorkflowMessage registerDomain( final RegisterDomainRequest request ) throws SimpleWorkflowException { final Context ctx = Contexts.lookup( ); final UserFullName userFullName = ctx.getUserFullName( ); allocate( new Supplier<Domain>( ) { @Override public Domain get( ) { try { final Domain domain = Domain.create( userFullName, request.getName( ), request.getDescription( ), MoreObjects.firstNonNull( parsePeriod( request.getWorkflowExecutionRetentionPeriodInDays( ), 0 ), 0 ) ); return domains.save( domain ); } catch ( Exception ex ) { throw new RuntimeException( ex ); } } }, Domain.class, request.getName( ) ); return request.reply( new SimpleWorkflowMessage( ) ); } public SimpleWorkflowMessage deprecateDomain( final DeprecateDomainRequest request ) throws SimpleWorkflowException { final Context ctx = Contexts.lookup( ); final AccountFullName accountFullName = ctx.getUserFullName( ).asAccountFullName( ); final Predicate<? super Domain> accessible = SimpleWorkflowMetadatas.filteringFor( Domain.class ).byPrivileges( ) .buildPredicate( ); try { domains.withRetries( ).updateByExample( Domain.exampleWithName( accountFullName, request.getName( ) ), accountFullName, request.getName( ), domain -> { if ( accessible.apply( domain ) ) try { if ( domain.getState( ) == Domain.Status.Deprecated ) { throw upClient( "DomainDeprecatedFault", "Domain already deprecated: " + request.getName() ); } domain.setState( Domain.Status.Deprecated ); activityTypes.list( // transform modifies state accountFullName, CollectionUtils.propertyPredicate( domain.getDisplayName( ), ActivityTypes.StringFunctions.DOMAIN ), ActivityType.Status.Deprecated.set( ) ); workflowTypes.list( // transform modifies state accountFullName, CollectionUtils.propertyPredicate( domain.getDisplayName( ), WorkflowTypes.StringFunctions.DOMAIN ), WorkflowType.Status.Deprecated.set( ) ); } catch ( final Exception e ) { throw up( e ); } } ); } catch ( SwfMetadataNotFoundException e ) { throw new SimpleWorkflowClientException( "UnknownResourceFault", "Domain not found: " + request.getName( ) ); } catch ( Exception e ) { throw handleException( e ); } return request.reply( new SimpleWorkflowMessage( ) ); } public DomainInfos listDomains( final ListDomainsRequest request ) throws SimpleWorkflowException { final DomainInfos domainInfos = new DomainInfos( ); final Context ctx = Contexts.lookup( ); final AccountFullName accountFullName = ctx.getUserFullName( ).asAccountFullName( ); final Predicate<? super Domain> requestedAndAccessible = SimpleWorkflowMetadatas.filteringFor( Domain.class ) .byProperty( Optional.fromNullable( request.getRegistrationStatus( ) ).asSet( ), Domains.StringFunctions.REGISTRATION_STATUS ) .byPrivileges( ) .buildPredicate( ); try { domainInfos.getDomainInfos( ).addAll( domains.list( accountFullName, Restrictions.conjunction( ), Collections.emptyMap( ), requestedAndAccessible, TypeMappers.lookup( Domain.class, DomainInfo.class ) ) ); } catch ( Exception e ) { throw handleException( e ); } return request.reply( domainInfos ); } public DomainDetail describeDomain( final DescribeDomainRequest request ) throws SimpleWorkflowException { final Context ctx = Contexts.lookup( ); final AccountFullName accountFullName = ctx.getUserFullName( ).asAccountFullName( ); final Predicate<? super Domain> requestedAndAccessible = SimpleWorkflowMetadatas.filteringFor( Domain.class ) .byId( Lists.newArrayList( request.getName( ) ) ) .byPrivileges( ) .buildPredicate( ); try { return request.reply( domains.lookupByExample( Domain.exampleWithName( accountFullName, request.getName( ) ), accountFullName, request.getName( ), requestedAndAccessible, TypeMappers.lookup( Domain.class, DomainDetail.class ) ) ); } catch ( SwfMetadataNotFoundException e ) { throw new SimpleWorkflowClientException( "UnknownResourceFault", "Domain not found: " + request.getName( ) ); } catch ( Exception e ) { throw handleException( e ); } } public SimpleWorkflowMessage registerActivityType( final RegisterActivityTypeRequest request ) throws SimpleWorkflowException { final Context ctx = Contexts.lookup( ); final UserFullName userFullName = ctx.getUserFullName( ); final AccountFullName accountFullName = userFullName.asAccountFullName( ); allocate( new Supplier<ActivityType>( ) { @Override public ActivityType get( ) { try { final Domain domain = domains.lookupByName( accountFullName, request.getDomain( ), Registered, Functions.identity( ) ); if ( activityTypes.countByDomain( accountFullName, domain.getDisplayName( ) ) >= SimpleWorkflowProperties.getActivityTypesPerDomain() ) { throw upClient( "LimitExceededFault", "Request would exceed limit for type: activity-type" ); } final ActivityType activityType = ActivityType.create( userFullName, request.getName( ), request.getVersion( ), domain, request.getDescription( ), request.getDefaultTaskList( ) == null ? null : request.getDefaultTaskList( ).getName( ), parsePeriod( request.getDefaultTaskHeartbeatTimeout( ), -1 ), parsePeriod( request.getDefaultTaskScheduleToCloseTimeout( ), -1 ), parsePeriod( request.getDefaultTaskScheduleToStartTimeout( ), -1 ), parsePeriod( request.getDefaultTaskStartToCloseTimeout( ), -1 ), parsePeriod( request.getDefaultTaskPriority( ), 0) ); return activityTypes.save( activityType ); } catch ( Exception ex ) { throw up( ex ); } } }, ActivityType.class, request.getName( ) ); return request.reply( new SimpleWorkflowMessage( ) ); } public SimpleWorkflowMessage deprecateActivityType( final DeprecateActivityTypeRequest request ) throws SimpleWorkflowException { final Context ctx = Contexts.lookup( ); final AccountFullName accountFullName = ctx.getUserFullName( ).asAccountFullName( ); final Predicate<? super ActivityType> accessible = SimpleWorkflowMetadatas.filteringFor( ActivityType.class ).byPrivileges( ) .buildPredicate( ); try { activityTypes.updateByExample( ActivityType.exampleWithUniqueName( accountFullName, request.getDomain( ), request.getActivityType( ).getName( ), request.getActivityType( ).getVersion( ) ), accountFullName, request.getActivityType( ).getName( ), activityType -> { if ( accessible.apply( activityType ) ) { if ( activityType.getState( ) == ActivityType.Status.Deprecated ) { throw upClient( "TypeDeprecatedFault", "Activity type already deprecated: " + request.getActivityType().getName() ); } activityType.setState( ActivityType.Status.Deprecated ); activityType.setDeprecationTimestamp( new Date( ) ); } } ); } catch ( SwfMetadataNotFoundException e ) { throw new SimpleWorkflowClientException( "UnknownResourceFault", "Activity type not found: " + request.getActivityType( ).getName( ) ); } catch ( Exception e ) { throw handleException( e ); } return request.reply( new SimpleWorkflowMessage( ) ); } public ActivityTypeInfos listActivityTypes( final ListActivityTypesRequest request ) throws SimpleWorkflowException { final ActivityTypeInfos activityTypeInfos = new ActivityTypeInfos( ); final Context ctx = Contexts.lookup( ); final AccountFullName accountFullName = ctx.getUserFullName( ).asAccountFullName( ); final Predicate<? super ActivityType> requestedAndAccessible = SimpleWorkflowMetadatas.filteringFor( ActivityType.class ) .byProperty( Optional.fromNullable( request.getDomain( ) ).asSet( ), ActivityTypes.StringFunctions.DOMAIN ) .byProperty( Optional.fromNullable( request.getRegistrationStatus( ) ).asSet( ), ActivityTypes.StringFunctions.REGISTRATION_STATUS ) .byId( Optional.fromNullable( request.getName( ) ).asSet( ) ) .byPrivileges( ) .buildPredicate( ); try { activityTypeInfos.getTypeInfos( ).addAll( activityTypes.list( accountFullName, requestedAndAccessible, TypeMappers.lookup( ActivityType.class, ActivityTypeInfo.class ) ) ); final Ordering<ActivityTypeInfo> ordering = Ordering.natural( ).onResultOf( ActivityTypes.ActivityTypeInfoStringFunctions.NAME ); Collections.sort( activityTypeInfos.getTypeInfos( ), MoreObjects.firstNonNull( request.getReverseOrder( ), Boolean.FALSE ) ? ordering.reverse() : ordering ); } catch ( Exception e ) { throw handleException( e ); } return request.reply( activityTypeInfos ); } public ActivityTypeDetail describeActivityType( final DescribeActivityTypeRequest request ) throws SimpleWorkflowException { final Context ctx = Contexts.lookup( ); final AccountFullName accountFullName = ctx.getUserFullName( ).asAccountFullName( ); final Predicate<? super ActivityType> accessible = SimpleWorkflowMetadatas.filteringFor( ActivityType.class ).byPrivileges().buildPredicate(); try { return request.reply( activityTypes.lookupByExample( ActivityType.exampleWithUniqueName( accountFullName, request.getDomain(), request.getActivityType().getName(), request.getActivityType().getVersion() ), accountFullName, request.getActivityType( ).getName(), accessible, TypeMappers.lookup( ActivityType.class, ActivityTypeDetail.class ) ) ); } catch ( SwfMetadataNotFoundException e ) { throw new SimpleWorkflowClientException( "UnknownResourceFault", "Activity type not found: " + request.getActivityType( ).getName() ); } catch ( Exception e ) { throw handleException( e ); } } public SimpleWorkflowMessage registerWorkflowType( final RegisterWorkflowTypeRequest request ) throws SimpleWorkflowException { final Context ctx = Contexts.lookup( ); final UserFullName userFullName = ctx.getUserFullName( ); final AccountFullName accountFullName = userFullName.asAccountFullName( ); allocate( new Supplier<WorkflowType>( ) { @Override public WorkflowType get( ) { try { final Domain domain = domains.lookupByName( accountFullName, request.getDomain(), Registered, Functions.identity() ); if ( workflowTypes.countByDomain( accountFullName, domain.getDisplayName() ) >= SimpleWorkflowProperties.getWorkflowTypesPerDomain() ) { throw upClient( "LimitExceededFault", "Request would exceed limit for type: workflow-type" ); } final WorkflowType workflowType = WorkflowType.create( userFullName, request.getName(), request.getVersion(), domain, request.getDescription(), request.getDefaultTaskList() == null ? null : request.getDefaultTaskList().getName(), request.getDefaultChildPolicy(), parsePeriod( request.getDefaultExecutionStartToCloseTimeout(), -1 ), parsePeriod( request.getDefaultTaskStartToCloseTimeout(), -1 ) ); return workflowTypes.save( workflowType ); } catch ( SwfMetadataNotFoundException e ) { throw upClient( "UnknownResourceFault", "Unknown domain: " + request.getDomain( ) ); } catch ( Exception ex ) { throw new RuntimeException( ex ); } } }, WorkflowType.class, request.getName( ) ); return request.reply( new SimpleWorkflowMessage( ) ); } public SimpleWorkflowMessage deprecateWorkflowType( final DeprecateWorkflowTypeRequest request ) throws SimpleWorkflowException { final Context ctx = Contexts.lookup( ); final AccountFullName accountFullName = ctx.getUserFullName( ).asAccountFullName( ); final Predicate<? super WorkflowType> accessible = SimpleWorkflowMetadatas.filteringFor( WorkflowType.class ).byPrivileges( ) .buildPredicate( ); try { workflowTypes.updateByExample( WorkflowType.exampleWithUniqueName( accountFullName, request.getDomain( ), request.getWorkflowType( ).getName( ), request.getWorkflowType( ).getVersion( ) ), accountFullName, request.getWorkflowType( ).getName( ), workflowType -> { if ( accessible.apply( workflowType ) ) { if ( workflowType.getState( ) == WorkflowType.Status.Deprecated ) { throw upClient( "TypeDeprecatedFault", "Workflow type already deprecated: " + request.getWorkflowType().getName() ); } workflowType.setState( WorkflowType.Status.Deprecated ); workflowType.setDeprecationTimestamp( new Date( ) ); } } ); } catch ( SwfMetadataNotFoundException e ) { throw new SimpleWorkflowClientException( "UnknownResourceFault", "Workflow type not found: " + request.getWorkflowType( ).getName( ) ); } catch ( Exception e ) { throw handleException( e ); } return request.reply( new SimpleWorkflowMessage( ) ); } public WorkflowTypeInfos listWorkflowTypes( final ListWorkflowTypesRequest request ) throws SimpleWorkflowException { final WorkflowTypeInfos workflowTypeInfos = new WorkflowTypeInfos( ); final Context ctx = Contexts.lookup( ); final AccountFullName accountFullName = ctx.getUserFullName( ).asAccountFullName( ); final Predicate<? super WorkflowType> requestedAndAccessible = SimpleWorkflowMetadatas.filteringFor( WorkflowType.class ) .byProperty( Optional.fromNullable( request.getDomain( ) ).asSet( ), WorkflowTypes.StringFunctions.DOMAIN ) .byProperty( Optional.fromNullable( request.getRegistrationStatus( ) ).asSet( ), WorkflowTypes.StringFunctions.REGISTRATION_STATUS ) .byId( Optional.fromNullable( request.getName( ) ).asSet( ) ) .byPrivileges( ) .buildPredicate( ); try { workflowTypeInfos.getTypeInfos( ).addAll( workflowTypes.list( accountFullName, requestedAndAccessible, TypeMappers.lookup( WorkflowType.class, WorkflowTypeInfo.class ) ) ); final Ordering<WorkflowTypeInfo> ordering = Ordering.natural( ).onResultOf( WorkflowTypes.WorkflowTypeInfoStringFunctions.NAME ); Collections.sort( workflowTypeInfos.getTypeInfos( ), MoreObjects.firstNonNull( request.getReverseOrder( ), Boolean.FALSE ) ? ordering.reverse() : ordering ); } catch ( Exception e ) { throw handleException( e ); } return request.reply( workflowTypeInfos ); } public WorkflowTypeDetail describeWorkflowType( final DescribeWorkflowTypeRequest request ) throws SimpleWorkflowException { final Context ctx = Contexts.lookup( ); final AccountFullName accountFullName = ctx.getUserFullName( ).asAccountFullName( ); final Predicate<? super WorkflowType> accessible = SimpleWorkflowMetadatas.filteringFor( WorkflowType.class ).byPrivileges( ).buildPredicate( ); try { return request.reply( workflowTypes.lookupByExample( WorkflowType.exampleWithUniqueName( accountFullName, request.getDomain( ), request.getWorkflowType( ).getName( ), request.getWorkflowType( ).getVersion( ) ), accountFullName, request.getWorkflowType( ).getName( ), accessible, TypeMappers.lookup( WorkflowType.class, WorkflowTypeDetail.class ) ) ); } catch ( SwfMetadataNotFoundException e ) { throw new SimpleWorkflowClientException( "UnknownResourceFault", "Workflow type not found: " + request.getWorkflowType( ).getName( ) ); } catch ( Exception e ) { throw handleException( e ); } } public WorkflowExecutionDetail describeWorkflowExecution( final DescribeWorkflowExecutionRequest request ) throws SimpleWorkflowException { final Context ctx = Contexts.lookup( ); final UserFullName userFullName = ctx.getUserFullName( ); final AccountFullName accountFullName = userFullName.asAccountFullName( ); final Predicate<? super WorkflowExecution> accessible = SimpleWorkflowMetadatas.filteringFor( WorkflowExecution.class ).byPrivileges( ).buildPredicate( ); final WorkflowExecutionDetail workflowExecutionDetail; try { workflowExecutionDetail = workflowExecutions.lookupByExample( WorkflowExecution.exampleWithName( accountFullName, request.getExecution( ).getRunId( ) ), accountFullName, request.getExecution( ).getRunId( ), accessible, workflowExecution -> { final WorkflowExecutionDetail detail = TypeMappers.transform( workflowExecution, WorkflowExecutionDetail.class ); final Iterable<WorkflowHistoryEvent> events = workflowExecution.getWorkflowHistory(); final int openActivities = CollectionUtils.reduce( events, 0, CollectionUtils.count( CollectionUtils.propertyPredicate( "ActivityTaskScheduled", EVENT_TYPE ) ) ) - CollectionUtils.reduce( events, 0, CollectionUtils.count( CollectionUtils.propertyPredicate( WorkflowExecutions.ACTIVITY_CLOSE_EVENT_TYPES, EVENT_TYPE ) ) ); final int openTimers = CollectionUtils.reduce( events, 0, CollectionUtils.count( CollectionUtils.propertyPredicate( "TimerStarted", EVENT_TYPE ) ) ) - CollectionUtils.reduce( events, 0, CollectionUtils.count( CollectionUtils.propertyPredicate( WorkflowExecutions.TIMER_CLOSE_EVENT_TYPES, EVENT_TYPE ) ) ); detail.withOpenCounts( new WorkflowExecutionOpenCounts( ) .withOpenActivityTasks( openActivities ) .withOpenChildWorkflowExecutions( 0 ) .withOpenDecisionTasks( workflowExecution.getDecisionStatus( ) != Idle ? 1 : 0 ) .withOpenTimers( openTimers ) .withOpenLambdaFunctions( 0 ) ); return detail; } ); } catch ( SwfMetadataNotFoundException e ) { throw new SimpleWorkflowClientException( "UnknownResourceFault", "Unknown execution, runId = " + request.getExecution().getRunId( ) ); } catch ( Exception e ) { throw handleException( e ); } return request.reply( workflowExecutionDetail ); } public WorkflowExecutionCount countClosedWorkflowExecutions( final CountClosedWorkflowExecutionsRequest request ) throws SimpleWorkflowException { final Context ctx = Contexts.lookup( ); final UserFullName userFullName = ctx.getUserFullName( ); final AccountFullName accountFullName = userFullName.asAccountFullName( ); final Predicate<? super Domain> accessible = SimpleWorkflowMetadatas.filteringFor( Domain.class ).byPrivileges( ).buildPredicate( ); final WorkflowExecutionCount workflowExecutionCount; try { workflowExecutionCount = domains.lookupByExample( Domain.exampleWithName( accountFullName, request.getDomain( ) ), accountFullName, request.getDomain( ), accessible, domain -> { final Conjunction filter = Restrictions.conjunction( ); final Map<String,String> aliases = Maps.newHashMap( ); buildFilters( request, filter, aliases ); //noinspection deprecation return new WorkflowExecutionCount( ) .withCount( (int) Entities.count( WorkflowExecution.exampleForClosedWorkflow( accountFullName, request.getDomain( ), null ), filter, aliases ) ) .withTruncated( false ); } ); } catch ( SwfMetadataNotFoundException e ) { throw new SimpleWorkflowClientException( "UnknownResourceFault", "Unknown domain, name = " + request.getDomain( ) ); } catch ( Exception e ) { throw handleException( e ); } return request.reply( workflowExecutionCount ); } public WorkflowExecutionCount countOpenWorkflowExecutions( final CountOpenWorkflowExecutionsRequest request ) throws SimpleWorkflowException { final Context ctx = Contexts.lookup( ); final UserFullName userFullName = ctx.getUserFullName( ); final AccountFullName accountFullName = userFullName.asAccountFullName( ); final Predicate<? super Domain> accessible = SimpleWorkflowMetadatas.filteringFor( Domain.class ).byPrivileges( ).buildPredicate( ); final WorkflowExecutionCount workflowExecutionCount; try { workflowExecutionCount = domains.lookupByExample( Domain.exampleWithName( accountFullName, request.getDomain( ) ), accountFullName, request.getDomain( ), accessible, domain -> { final Conjunction filter = Restrictions.conjunction( ); final Map<String,String> aliases = Maps.newHashMap( ); buildFilters( request, filter, aliases ); //noinspection deprecation return new WorkflowExecutionCount( ) .withCount( (int) Entities.count( WorkflowExecution.exampleForOpenWorkflow( accountFullName, request.getDomain(), null ), filter, aliases ) ) .withTruncated( false ); } ); } catch ( SwfMetadataNotFoundException e ) { throw new SimpleWorkflowClientException( "UnknownResourceFault", "Unknown domain, name = " + request.getDomain( ) ); } catch ( Exception e ) { throw handleException( e ); } return request.reply( workflowExecutionCount ); } public PendingTaskCount countPendingActivityTasks( final CountPendingActivityTasksRequest request ) throws SimpleWorkflowException { final Context ctx = Contexts.lookup( ); final UserFullName userFullName = ctx.getUserFullName( ); final AccountFullName accountFullName = userFullName.asAccountFullName( ); final Predicate<? super Domain> accessible = SimpleWorkflowMetadatas.filteringFor( Domain.class ).byPrivileges( ).buildPredicate( ); final PendingTaskCount pendingTaskCount; try { //noinspection deprecation pendingTaskCount = domains.lookupByExample( Domain.exampleWithName( accountFullName, request.getDomain( ) ), accountFullName, request.getDomain(), accessible, domain -> new PendingTaskCount( ) .withCount( (int) Entities.count( ActivityTask.examplePending( accountFullName, request.getDomain( ), request.getTaskList( ).getName( ) ) ) ) .withTruncated( false ) ); } catch ( SwfMetadataNotFoundException e ) { throw new SimpleWorkflowClientException( "UnknownResourceFault", "Unknown domain, name = " + request.getDomain( ) ); } catch ( Exception e ) { throw handleException( e ); } return request.reply( pendingTaskCount ); } public PendingTaskCount countPendingDecisionTasks( final CountPendingDecisionTasksRequest request ) throws SimpleWorkflowException { final Context ctx = Contexts.lookup( ); final UserFullName userFullName = ctx.getUserFullName( ); final AccountFullName accountFullName = userFullName.asAccountFullName( ); final Predicate<? super Domain> accessible = SimpleWorkflowMetadatas.filteringFor( Domain.class ).byPrivileges( ).buildPredicate( ); final PendingTaskCount pendingTaskCount; try { //noinspection deprecation pendingTaskCount = domains.lookupByExample( Domain.exampleWithName( accountFullName, request.getDomain( ) ), accountFullName, request.getDomain( ), accessible, domain -> new PendingTaskCount( ) .withCount( (int) Entities.count( WorkflowExecution.exampleWithPendingDecision( accountFullName, request.getDomain(), request.getTaskList().getName() ) ) ) .withTruncated( false ) ); } catch ( SwfMetadataNotFoundException e ) { throw new SimpleWorkflowClientException( "UnknownResourceFault", "Unknown domain, name = " + request.getDomain( ) ); } catch ( Exception e ) { throw handleException( e ); } return request.reply( pendingTaskCount ); } public WorkflowExecutionInfos listClosedWorkflowExecutions( final ListClosedWorkflowExecutionsRequest request ) throws SimpleWorkflowException { final Context ctx = Contexts.lookup( ); final UserFullName userFullName = ctx.getUserFullName( ); final AccountFullName accountFullName = userFullName.asAccountFullName( ); final Predicate<? super WorkflowExecution> accessible = SimpleWorkflowMetadatas.filteringFor( WorkflowExecution.class ).byPrivileges( ).buildPredicate( ); final WorkflowExecutionInfos workflowExecutionInfos = new WorkflowExecutionInfos( ); try { final Conjunction filter = Restrictions.conjunction( ); final Map<String,String> aliases = Maps.newHashMap( ); buildFilters( request, filter, aliases ); workflowExecutionInfos.getExecutionInfos( ).addAll( workflowExecutions.listByExample( WorkflowExecution.exampleForClosedWorkflow( accountFullName, request.getDomain( ), null ), accessible, filter, aliases, TypeMappers.lookup( WorkflowExecution.class, WorkflowExecutionInfo.class ) ) ); final Ordering<WorkflowExecutionInfo> ordering = Ordering.natural( ).onResultOf( WorkflowExecutions.WorkflowExecutionInfoDateFunctions.START_TIMESTAMP ); Collections.sort( workflowExecutionInfos.getExecutionInfos( ), MoreObjects.firstNonNull( request.getReverseOrder( ), Boolean.FALSE ) ? ordering.reverse() : ordering ); } catch ( Exception e ) { throw handleException( e ); } return request.reply( workflowExecutionInfos ); } public WorkflowExecutionInfos listOpenWorkflowExecutions( final ListOpenWorkflowExecutionsRequest request ) throws SimpleWorkflowException { final Context ctx = Contexts.lookup( ); final UserFullName userFullName = ctx.getUserFullName( ); final AccountFullName accountFullName = userFullName.asAccountFullName( ); final Predicate<? super WorkflowExecution> accessible = SimpleWorkflowMetadatas.filteringFor( WorkflowExecution.class ).byPrivileges( ).buildPredicate( ); final WorkflowExecutionInfos workflowExecutionInfos = new WorkflowExecutionInfos( ); try { final Conjunction filter = Restrictions.conjunction( ); final Map<String,String> aliases = Maps.newHashMap( ); buildFilters( request, filter, aliases ); workflowExecutionInfos.getExecutionInfos( ).addAll( workflowExecutions.listByExample( WorkflowExecution.exampleForOpenWorkflow( accountFullName, request.getDomain(), null ), accessible, filter, aliases, TypeMappers.lookup( WorkflowExecution.class, WorkflowExecutionInfo.class ) ) ); final Ordering<WorkflowExecutionInfo> ordering = Ordering.natural( ).onResultOf( WorkflowExecutions.WorkflowExecutionInfoDateFunctions.START_TIMESTAMP ); Collections.sort( workflowExecutionInfos.getExecutionInfos( ), MoreObjects.firstNonNull( request.getReverseOrder( ), Boolean.FALSE ) ? ordering.reverse() : ordering ); } catch ( Exception e ) { throw handleException( e ); } return request.reply( workflowExecutionInfos ); } public Run startWorkflowExecution( final StartWorkflowExecutionRequest request ) throws SimpleWorkflowException { final Context ctx = Contexts.lookup( ); final UserFullName userFullName = ctx.getUserFullName( ); final AccountFullName accountFullName = userFullName.asAccountFullName( ); final Predicate<? super WorkflowType> accessible = SimpleWorkflowMetadatas.filteringFor( WorkflowType.class ).byPrivileges( ).buildPredicate( ); final WorkflowExecution workflowExecution = allocate( new Supplier<WorkflowExecution>( ) { @Override public WorkflowExecution get( ) { try { if ( !workflowExecutions.listByExample( WorkflowExecution.exampleForOpenWorkflow( accountFullName, request.getDomain( ), request.getWorkflowId( ) ), Predicates.alwaysTrue( ), Functions.identity( ) ).isEmpty( ) ) { throw new SimpleWorkflowClientException( "WorkflowExecutionAlreadyStartedFault", "Workflow open with ID " + request.getWorkflowId( ) ); } final Domain domain; try { domain = domains.lookupByName( accountFullName, request.getDomain( ), Registered, Functions.identity( ) ); } catch ( SwfMetadataNotFoundException e ) { throw upClient( "UnknownResourceFault", "Unknown domain: " + request.getDomain() ); } if ( workflowExecutions.countOpenByDomain( accountFullName, domain.getDisplayName( ) ) >= SimpleWorkflowProperties.getOpenWorkflowExecutionsPerDomain() ) { throw upClient( "LimitExceededFault", "Request would exceed limit for open workflow executions" ); } final WorkflowType workflowType; try { workflowType = workflowTypes.lookupByExample( WorkflowType.exampleWithUniqueName( accountFullName, request.getDomain(), request.getWorkflowType().getName(), request.getWorkflowType().getVersion() ), accountFullName, request.getWorkflowType().getName(), Predicates.and( accessible, WorkflowType.Status.Registered ), Functions.<WorkflowType>identity() ); } catch ( SwfMetadataNotFoundException e ) { throw upClient( "UnknownResourceFault", "Unknown workflow type: " + request.getWorkflowType().getName() ); } if ( request.getChildPolicy( ) == null && workflowType.getDefaultChildPolicy( ) == null ) { throw upClient( "DefaultUndefinedFault", "Default child policy undefined" ); } if ( request.getTaskList( ) == null && workflowType.getDefaultTaskList( ) == null ) { throw upClient( "DefaultUndefinedFault", "Default task list undefined" ); } final String childPolicy = MoreObjects.firstNonNull( request.getChildPolicy( ), workflowType.getDefaultChildPolicy( ) ); final String taskList = request.getTaskList( ) == null ? workflowType.getDefaultTaskList( ): request.getTaskList( ).getName( ); final Integer executionStartToCloseTimeout = requireDefault( parsePeriod( request.getExecutionStartToCloseTimeout(), -1 ), workflowType.getDefaultExecutionStartToCloseTimeout(), "ExecutionStartToCloseTimeout" ); final Integer taskStartToCloseTimeout = requireDefault( parsePeriod( request.getTaskStartToCloseTimeout(), -1 ), workflowType.getDefaultTaskStartToCloseTimeout(), "TaskStartToCloseTimeout" ); final String taskStartToCloseTimeoutStr = taskStartToCloseTimeout < 0 ? "NONE" : String.valueOf( taskStartToCloseTimeout ); final WorkflowExecution workflowExecution = WorkflowExecution.create( userFullName, UUID.randomUUID( ).toString( ), domain, workflowType, request.getWorkflowId( ), childPolicy, taskList, executionStartToCloseTimeout, taskStartToCloseTimeout < 0 ? null : taskStartToCloseTimeout, request.getTagList( ), Lists.newArrayList( new WorkflowExecutionStartedEventAttributes( ) .withChildPolicy( childPolicy ) .withExecutionStartToCloseTimeout( String.valueOf( executionStartToCloseTimeout ) ) .withInput( request.getInput( ) ) .withParentInitiatedEventId( 0L ) .withTaskList( new TaskList( ).withName( taskList ) ) .withTagList( request.getTagList( ) ) .withTaskStartToCloseTimeout( taskStartToCloseTimeoutStr ) .withWorkflowType( request.getWorkflowType( ) ), new DecisionTaskScheduledEventAttributes( ) .withStartToCloseTimeout( taskStartToCloseTimeoutStr ) .withTaskList( new TaskList( ).withName( taskList ) ) ) ); return workflowExecutions.save( workflowExecution ); } catch ( Exception ex ) { throw new RuntimeException( ex ); } } }, WorkflowExecution.class, request.getWorkflowId( ) ); notifyTaskList( accountFullName, workflowExecution.getDomainName( ), "decision", workflowExecution.getTaskList( ) ); final Run run = new Run( ); run.setRunId( workflowExecution.getDisplayName() ); return request.reply( run ); } public com.eucalyptus.simpleworkflow.common.model.ActivityTask pollForActivityTask( final PollForActivityTaskRequest request ) throws SimpleWorkflowException { final Context ctx = Contexts.lookup( ); final UserFullName userFullName = ctx.getUserFullName( ); final AccountFullName accountFullName = userFullName.asAccountFullName( ); final Predicate<? super ActivityTask> accessible = SimpleWorkflowMetadatas.filteringFor( ActivityTask.class ).byPrivileges( ).buildPredicate( ); final String domain = request.getDomain( ); final String taskList = request.getTaskList( ).getName( ); final Callable<com.eucalyptus.simpleworkflow.common.model.ActivityTask> taskCallable = () -> { com.eucalyptus.simpleworkflow.common.model.ActivityTask activityTask = null; final List<ActivityTask> pending = activityTasks.listByExample( ActivityTask.examplePending( accountFullName, domain, taskList ), accessible, Functions.identity( ) ); Collections.sort( pending, Ordering.natural( ).onResultOf( AbstractPersistentSupport.creation( ) ) ); for ( final List<ActivityTask> pendingSegment : shufflePartitions( pending ) ) { for ( final ActivityTask pendingTask : pendingSegment ) { if ( activityTask != null ) break; try ( final WorkflowLock lock = WorkflowLock.tryLock( accountFullName, pendingTask.getDomainUuid( ), pendingTask.getWorkflowRunId() ) ) { if ( !lock.isHeldByCurrentThread( ) ) { continue; } activityTask = activityTasks.updateByExample( pendingTask, accountFullName, pendingTask.getDisplayName(), new Function<ActivityTask,com.eucalyptus.simpleworkflow.common.model.ActivityTask>(){ @Nullable @Override public com.eucalyptus.simpleworkflow.common.model.ActivityTask apply( final ActivityTask activityTask ) { if ( activityTask.getState( ) == ActivityTask.State.Pending ) { final WorkflowExecution workflowExecution = activityTask.getWorkflowExecution( ); final Long startedId = workflowExecution.addHistoryEvent( WorkflowHistoryEvent.create( workflowExecution, new ActivityTaskStartedEventAttributes( ) .withIdentity( request.getIdentity( ) ) .withScheduledEventId( activityTask.getScheduledEventId( ) ) ) ); activityTask.setState( ActivityTask.State.Active ); activityTask.setStartedEventId( startedId ); return new com.eucalyptus.simpleworkflow.common.model.ActivityTask( ) .withStartedEventId( startedId ) .withInput( activityTask.getInput() ) .withTaskToken( taskTokenManager.encryptTaskToken( new TaskToken( accountFullName.getAccountNumber(), workflowExecution.getDomain().getNaturalId(), workflowExecution.getDisplayName(), activityTask.getScheduledEventId(), startedId, System.currentTimeMillis(), System.currentTimeMillis() ) ) ) .withActivityId( activityTask.getDisplayName() ) .withActivityType( new com.eucalyptus.simpleworkflow.common.model.ActivityType() .withName( activityTask.getActivityType() ) .withVersion( activityTask.getActivityVersion() ) ) .withWorkflowExecution( new com.eucalyptus.simpleworkflow.common.model.WorkflowExecution() .withRunId( workflowExecution.getDisplayName() ) .withWorkflowId( workflowExecution.getWorkflowId() ) ); } return null; } }); } catch ( SwfMetadataException e ) { logger.info( "Activity task for domain " + domain + ", list " + taskList + " not found" ); } catch ( Exception e ) { if ( PersistenceExceptions.isStaleUpdate( e ) ) { logger.info( "Activity task for domain " + domain + ", list " + taskList + " already taken" ); } else if ( PersistenceExceptions.isLockError( e ) ) { logger.info( "Activity task for domain " + domain + ", list " + taskList + " locking error" ); } else { logger.error( "Error taking activity task for domain " + domain + ", list " + taskList, e ); } } } } return activityTask; }; try { return handleTaskPolling( accountFullName, domain, "activity", taskList, request.getCorrelationId( ), new com.eucalyptus.simpleworkflow.common.model.ActivityTask( ), taskCallable ); } catch ( Exception e ) { throw handleException( e ); } } public ActivityTaskStatus recordActivityTaskHeartbeat( final RecordActivityTaskHeartbeatRequest request ) throws SimpleWorkflowException { final Context ctx = Contexts.lookup( ); final UserFullName userFullName = ctx.getUserFullName( ); final AccountFullName accountFullName = userFullName.asAccountFullName( ); final Predicate<? super ActivityTask> accessible = SimpleWorkflowMetadatas.filteringFor( ActivityTask.class ).byPrivileges( ).buildPredicate( ); final ActivityTaskStatus status = new ActivityTaskStatus( ); status.setCancelRequested( false ); try { final TaskToken token = taskTokenManager.decryptTaskToken( accountFullName.getAccountNumber( ), request.getTaskToken( ) ); activityTasks.withRetries( ).updateByExample( ActivityTask.exampleWithUniqueName( accountFullName, token.getRunId(), token.getScheduledEventId() ), accountFullName, token.getRunId( ) + "/" + token.getScheduledEventId( ), activityTask -> { if ( accessible.apply( activityTask ) ) { activityTask.setHeartbeatDetails( request.getDetails( ) ); activityTask.updateTimeStamps( ); status.setCancelRequested( activityTask.getCancelRequestedEventId( ) != null ); } return activityTask; } ); } catch ( SwfMetadataNotFoundException e ) { throw new SimpleWorkflowClientException( "UnknownResourceFault", "Unknown activity task, token = " + request.getTaskToken( ) ); } catch( Exception e ) { throw handleException( e ); } return request.reply( status ); } public SimpleWorkflowMessage respondActivityTaskCanceled( final RespondActivityTaskCanceledRequest request ) throws SimpleWorkflowException { final Context ctx = Contexts.lookup( ); final UserFullName userFullName = ctx.getUserFullName( ); final AccountFullName accountFullName = userFullName.asAccountFullName( ); final Predicate<? super ActivityTask> accessible = SimpleWorkflowMetadatas.filteringFor( ActivityTask.class ).byPrivileges( ).buildPredicate( ); final ActivityTaskStatus status = new ActivityTaskStatus( ); status.setCancelRequested( false ); try { final TaskToken token = taskTokenManager.decryptTaskToken( accountFullName.getAccountNumber( ), request.getTaskToken( ) ); final Pair<String,String> domainTaskListPair; try ( final WorkflowLock lock = WorkflowLock.lock( accountFullName, token.getDomainUuid( ), token.getRunId( ) ) ) { domainTaskListPair = activityTasks.withRetries().updateByExample( ActivityTask.exampleWithUniqueName( accountFullName, token.getRunId(), token.getScheduledEventId() ), accountFullName, token.getRunId() + "/" + token.getScheduledEventId(), activityTask -> { if ( accessible.apply( activityTask ) ) { final WorkflowExecution workflowExecution = activityTask.getWorkflowExecution(); workflowExecution.addHistoryEvent( WorkflowHistoryEvent.create( workflowExecution, new ActivityTaskCanceledEventAttributes() .withDetails( request.getDetails() ) .withLatestCancelRequestedEventId( activityTask.getCancelRequestedEventId() ) .withScheduledEventId( activityTask.getScheduledEventId() ) .withStartedEventId( activityTask.getStartedEventId() ) ) ); if ( workflowExecution.getDecisionStatus() != Pending ) { workflowExecution.addHistoryEvent( WorkflowHistoryEvent.create( workflowExecution, new DecisionTaskScheduledEventAttributes() .withTaskList( new TaskList().withName( workflowExecution.getTaskList() ) ) .withStartToCloseTimeout( String.valueOf( workflowExecution.getTaskStartToCloseTimeout() ) ) ) ); if ( workflowExecution.getDecisionStatus() == Idle ) { workflowExecution.setDecisionStatus( Pending ); workflowExecution.setDecisionTimestamp( new Date() ); } } Entities.delete( activityTask ); return workflowExecution.getDecisionStatus() == Pending ? Pair.pair( workflowExecution.getDomainName(), workflowExecution.getTaskList() ) : null; } return null; } ); } if ( domainTaskListPair != null ) { notifyTaskList( accountFullName, domainTaskListPair.getLeft(), "decision", domainTaskListPair.getRight() ); } } catch ( SwfMetadataNotFoundException e ) { throw new SimpleWorkflowClientException( "UnknownResourceFault", "Unknown activity task, token = " + request.getTaskToken( ) ); } catch( Exception e ) { throw handleException( e ); } return request.reply( new SimpleWorkflowMessage( ) ); } public SimpleWorkflowMessage respondActivityTaskCompleted( final RespondActivityTaskCompletedRequest request ) throws SimpleWorkflowException { final Context ctx = Contexts.lookup( ); final UserFullName userFullName = ctx.getUserFullName( ); final AccountFullName accountFullName = userFullName.asAccountFullName( ); final Predicate<? super WorkflowExecution> accessible = SimpleWorkflowMetadatas.filteringFor( WorkflowExecution.class ).byPrivileges( ).buildPredicate( ); try { final TaskToken token = taskTokenManager.decryptTaskToken( accountFullName.getAccountNumber( ), request.getTaskToken( ) ); final Domain domain = domains.lookupByExample( Domain.exampleWithUuid( accountFullName, token.getDomainUuid( ) ), accountFullName, token.getDomainUuid( ), Predicates.alwaysTrue( ), Functions.identity( ) ); final WorkflowExecution workflowExecution; try ( final WorkflowLock lock = WorkflowLock.lock( accountFullName, domain, token.getRunId( ) ) ) { workflowExecution = workflowExecutions.withRetries().updateByExample( WorkflowExecution.exampleWithUniqueName( accountFullName, domain.getDisplayName(), token.getRunId() ), accountFullName, token.getRunId(), new Function<WorkflowExecution, WorkflowExecution>() { @Nullable @Override public WorkflowExecution apply( final WorkflowExecution workflowExecution ) { if ( accessible.apply( workflowExecution ) ) { try { activityTasks.deleteByExample( ActivityTask.exampleWithUniqueName( accountFullName, token.getRunId(), token.getScheduledEventId() ) ); } catch ( SwfMetadataException e ) { throw up( e ); } // TODO:STEVE: verify token valid (no reuse, etc) workflowExecution.addHistoryEvent( WorkflowHistoryEvent.create( workflowExecution, new ActivityTaskCompletedEventAttributes() .withResult( request.getResult() ) .withScheduledEventId( token.getScheduledEventId() ) .withStartedEventId( token.getStartedEventId() ) ) ); if ( workflowExecution.getDecisionStatus() != Pending ) { workflowExecution.addHistoryEvent( WorkflowHistoryEvent.create( workflowExecution, new DecisionTaskScheduledEventAttributes() .withTaskList( new TaskList().withName( workflowExecution.getTaskList() ) ) .withStartToCloseTimeout( String.valueOf( workflowExecution.getTaskStartToCloseTimeout() ) ) ) ); if ( workflowExecution.getDecisionStatus() == Idle ) { workflowExecution.setDecisionStatus( Pending ); workflowExecution.setDecisionTimestamp( new Date() ); } } } return workflowExecution; } } ); } if ( workflowExecution.getDecisionStatus() == Pending ) { notifyTaskList( accountFullName, workflowExecution.getDomainName(), "decision", workflowExecution.getTaskList() ); } } catch( Exception e ) { throw handleException( e ); } return request.reply( new SimpleWorkflowMessage( ) ); } public SimpleWorkflowMessage respondActivityTaskFailed( final RespondActivityTaskFailedRequest request ) throws SimpleWorkflowException { final Context ctx = Contexts.lookup( ); final UserFullName userFullName = ctx.getUserFullName( ); final AccountFullName accountFullName = userFullName.asAccountFullName( ); final Predicate<? super WorkflowExecution> accessible = SimpleWorkflowMetadatas.filteringFor( WorkflowExecution.class ).byPrivileges( ).buildPredicate( ); try { final TaskToken token = taskTokenManager.decryptTaskToken( accountFullName.getAccountNumber( ), request.getTaskToken( ) ); final Domain domain = domains.lookupByExample( Domain.exampleWithUuid( accountFullName, token.getDomainUuid( ) ), accountFullName, token.getDomainUuid( ), Predicates.alwaysTrue( ), Functions.identity( ) ); final WorkflowExecution workflowExecution; try ( final WorkflowLock lock = WorkflowLock.lock( accountFullName, domain, token.getRunId( ) ) ) { workflowExecution = workflowExecutions.withRetries().updateByExample( WorkflowExecution.exampleWithUniqueName( accountFullName, domain.getDisplayName(), token.getRunId() ), accountFullName, token.getRunId(), new Function<WorkflowExecution, WorkflowExecution>() { @Nullable @Override public WorkflowExecution apply( final WorkflowExecution workflowExecution ) { if ( accessible.apply( workflowExecution ) ) { try { activityTasks.deleteByExample( ActivityTask.exampleWithUniqueName( accountFullName, token.getRunId(), token.getScheduledEventId() ) ); } catch ( SwfMetadataException e ) { throw up( e ); } // TODO:STEVE: verify token valid (no reuse, etc) workflowExecution.addHistoryEvent( WorkflowHistoryEvent.create( workflowExecution, new ActivityTaskFailedEventAttributes() .withDetails( request.getDetails() ) .withReason( request.getReason() ) .withScheduledEventId( token.getScheduledEventId() ) .withStartedEventId( token.getStartedEventId() ) ) ); if ( workflowExecution.getDecisionStatus() != Pending ) { workflowExecution.addHistoryEvent( WorkflowHistoryEvent.create( workflowExecution, new DecisionTaskScheduledEventAttributes() .withTaskList( new TaskList().withName( workflowExecution.getTaskList() ) ) .withStartToCloseTimeout( String.valueOf( workflowExecution.getTaskStartToCloseTimeout() ) ) ) ); if ( workflowExecution.getDecisionStatus() == Idle ) { workflowExecution.setDecisionStatus( Pending ); workflowExecution.setDecisionTimestamp( new Date() ); } } } return workflowExecution; } } ); } if ( workflowExecution.getDecisionStatus( ) == Pending ) { notifyTaskList( accountFullName, workflowExecution.getDomainName(), "decision", workflowExecution.getTaskList() ); } } catch( Exception e ) { throw handleException( e ); } return request.reply( new SimpleWorkflowMessage() ); } public DecisionTask pollForDecisionTask( final PollForDecisionTaskRequest request ) throws SimpleWorkflowException { final Context ctx = Contexts.lookup( ); final UserFullName userFullName = ctx.getUserFullName( ); final AccountFullName accountFullName = userFullName.asAccountFullName( ); final Predicate<? super WorkflowExecution> accessible = SimpleWorkflowMetadatas.filteringFor( WorkflowExecution.class ).byPrivileges( ).buildPredicate( ); final String domain = request.getDomain( ); final String taskList = request.getTaskList( ).getName( ); final Callable<DecisionTask> taskCallable = () -> { final List<WorkflowExecution> pending = workflowExecutions.listByExample( WorkflowExecution.exampleWithPendingDecision( accountFullName, domain, taskList ), accessible, Functions.identity( ) ); Collections.sort( pending, Ordering.natural( ).onResultOf( AbstractPersistentSupport.creation( ) ) ); DecisionTask decisionTask = null; for ( final List<WorkflowExecution> pendingSegment : shufflePartitions( pending ) ) { for ( final WorkflowExecution execution : pendingSegment ) { if ( decisionTask != null ) break; try ( final WorkflowLock lock = WorkflowLock.tryLock( accountFullName, execution.getDomainUuid( ), execution.getDisplayName( ) ) ) { if ( !lock.isHeldByCurrentThread( ) ) { continue; } decisionTask = workflowExecutions.updateByExample( WorkflowExecution.exampleWithUniqueName( accountFullName, execution.getDomainName( ), execution.getDisplayName( ) ), accountFullName, execution.getDisplayName(), new Function<WorkflowExecution,DecisionTask>( ) { @Nullable @Override public DecisionTask apply( final WorkflowExecution workflowExecution ) { if ( workflowExecution.getDecisionStatus( ) == Pending ) { final List<WorkflowHistoryEvent> events = workflowExecution.getWorkflowHistory(); final List<WorkflowHistoryEvent> reverseEvents = Lists.reverse( events ); final WorkflowHistoryEvent scheduled = Iterables.find( reverseEvents, CollectionUtils.propertyPredicate( "DecisionTaskScheduled", EVENT_TYPE ) ); final Optional<WorkflowHistoryEvent> previousStarted = Iterables.tryFind( reverseEvents, CollectionUtils.propertyPredicate( "DecisionTaskStarted", EVENT_TYPE ) ); workflowExecution.setDecisionStatus( Active ); workflowExecution.setDecisionTimestamp( new Date( ) ); final WorkflowHistoryEvent started = WorkflowHistoryEvent.create( workflowExecution, new DecisionTaskStartedEventAttributes() .withIdentity( request.getIdentity() ) .withScheduledEventId( scheduled.getEventId() ) ); workflowExecution.addHistoryEvent( started ); return new DecisionTask( ) .withWorkflowExecution( new com.eucalyptus.simpleworkflow.common.model.WorkflowExecution( ) .withWorkflowId( workflowExecution.getWorkflowId( ) ) .withRunId( workflowExecution.getDisplayName( ) ) ) .withWorkflowType( new com.eucalyptus.simpleworkflow.common.model.WorkflowType() .withName( workflowExecution.getWorkflowType( ).getDisplayName( ) ) .withVersion( workflowExecution.getWorkflowType( ).getWorkflowVersion( ) ) ) .withTaskToken( taskTokenManager.encryptTaskToken( new TaskToken( accountFullName.getAccountNumber( ), workflowExecution.getDomain( ).getNaturalId( ), workflowExecution.getDisplayName( ), scheduled.getEventId( ), started.getEventId( ), System.currentTimeMillis( ), System.currentTimeMillis( ) ) ) ) //TODO:STEVE: token expiry date .withStartedEventId( started.getEventId() ) .withPreviousStartedEventId( previousStarted.transform( WorkflowExecutions.WorkflowHistoryEventLongFunctions.EVENT_ID ).or( 0L ) ) .withEvents( Collections2.transform( MoreObjects.firstNonNull( request.isReverseOrder( ), Boolean.FALSE ) ? reverseEvents : events, TypeMappers.lookup( WorkflowHistoryEvent.class, HistoryEvent.class ) ) ); } return null; } } ); } catch ( Exception e ) { final StaleObjectStateException stale = Exceptions.findCause( e, StaleObjectStateException.class ); if ( stale != null ) try { Entities.evictCache( Class.forName( stale.getEntityName( ) ) ); } catch ( ClassNotFoundException ce ) { /* eviction failure */ } if ( PersistenceExceptions.isStaleUpdate( e ) ) { logger.info( "Decision task for workflow " + execution.getDisplayName() + " already taken" ); } else if ( PersistenceExceptions.isLockError( e ) ) { logger.info( "Decision task for workflow " + execution.getDisplayName() + " locking error" ); } else { logger.error( "Error taking decision task for workflow " + execution.getDisplayName( ), e ); } } } } return decisionTask; }; try { return handleTaskPolling( accountFullName, domain, "decision", taskList, request.getCorrelationId(), new DecisionTask(), taskCallable ); } catch ( Exception e ) { throw handleException( e ); } } public SimpleWorkflowMessage respondDecisionTaskCompleted( final RespondDecisionTaskCompletedRequest request ) throws SimpleWorkflowException { final Context ctx = Contexts.lookup( ); final UserFullName userFullName = ctx.getUserFullName( ); final AccountFullName accountFullName = userFullName.asAccountFullName( ); final Predicate<? super WorkflowExecution> accessible = SimpleWorkflowMetadatas.filteringFor( WorkflowExecution.class ).byPrivileges( ).buildPredicate( ); try { final TaskToken token = taskTokenManager.decryptTaskToken( accountFullName.getAccountNumber(), request.getTaskToken() ); final Domain domain = domains.lookupByExample( Domain.exampleWithUuid( accountFullName, token.getDomainUuid( ) ), accountFullName, token.getDomainUuid( ), Predicates.alwaysTrue( ), Functions.identity( ) ); final Set<Pair<String,String>> notificationTypeListPairs = Sets.newHashSet( ); try ( final WorkflowLock lock = WorkflowLock.lock( accountFullName, domain, token.getRunId() ) ) { workflowExecutions.withRetries( ).updateByExample( WorkflowExecution.exampleWithUniqueName( accountFullName, domain.getDisplayName( ), token.getRunId( ) ), accountFullName, token.getRunId( ), new Function<WorkflowExecution, WorkflowExecution>() { @Nullable @Override public WorkflowExecution apply( final WorkflowExecution workflowExecution ) { if ( accessible.apply( workflowExecution ) ) { // clear pending notifications in case of retries notificationTypeListPairs.clear( ); // verify token is valid final List<WorkflowHistoryEvent> events = workflowExecution.getWorkflowHistory(); final List<WorkflowHistoryEvent> reverseEvents = Lists.reverse( events ); final WorkflowHistoryEvent started = Iterables.find( reverseEvents, CollectionUtils.propertyPredicate( "DecisionTaskStarted", EVENT_TYPE ) ); if ( !started.getEventId( ).equals( token.getStartedEventId( ) ) ) { throw upClient( "ValidationError", "Bad token" ); } final WorkflowHistoryEvent scheduled = Iterables.find( reverseEvents, CollectionUtils.propertyPredicate( "DecisionTaskScheduled", EVENT_TYPE ) ); if ( scheduled.getEventId( ) < started.getEventId() ) { workflowExecution.setDecisionStatus( Idle ); workflowExecution.setDecisionTimestamp( new Date( ) ); } else { workflowExecution.setDecisionStatus( Pending ); workflowExecution.setDecisionTimestamp( new Date( ) ); notificationTypeListPairs.add( Pair.pair( "decision", workflowExecution.getTaskList( ) ) ); } // setup activity count supplier int activityTaskScheduledCount = 0; final Supplier<Long> activityTaskCounter = Suppliers.memoize( () -> { try { return activityTasks.countByWorkflowExecution( accountFullName, domain.getDisplayName( ), workflowExecution.getDisplayName( ) ); } catch ( SwfMetadataException e ) { throw up( e ); } } ); // process decision task response workflowExecution.setLatestExecutionContext( request.getExecutionContext( ) ); final Long completedId = workflowExecution.addHistoryEvent( WorkflowHistoryEvent.create( workflowExecution, new DecisionTaskCompletedEventAttributes( ) .withExecutionContext( request.getExecutionContext( ) ) .withScheduledEventId( token.getScheduledEventId( ) ) .withStartedEventId( token.getStartedEventId( ) ) ) ); boolean scheduleDecisionTask = false; if ( request.getDecisions( ) != null ) for ( final Decision decision : request.getDecisions() ) { switch ( decision.getDecisionType( ) ) { case "CancelTimer": final CancelTimerDecisionAttributes cancelTimer = decision.getCancelTimerDecisionAttributes( ); try { final List<Timer> timerList = timers.listByExample( Timer.exampleWithTimerId( accountFullName, workflowExecution.getDomainName( ), workflowExecution.getDisplayName( ), cancelTimer.getTimerId( ) ), Predicates.alwaysTrue( ), Functions.identity( ) ); if ( !timerList.isEmpty( ) ) { final Timer timer = Iterables.getOnlyElement( timerList ); timers.deleteByExample( timer ); workflowExecution.addHistoryEvent( WorkflowHistoryEvent.create( workflowExecution, new TimerCanceledEventAttributes( ) .withDecisionTaskCompletedEventId( completedId ) .withStartedEventId( timer.getStartedEventId( ) ) .withTimerId( cancelTimer.getTimerId( ) ) ) ); } else { workflowExecution.addHistoryEvent( WorkflowHistoryEvent.create( workflowExecution, new CancelTimerFailedEventAttributes() .withCause( CancelTimerFailedCause.TIMER_ID_UNKNOWN ) .withDecisionTaskCompletedEventId( completedId ) .withTimerId( cancelTimer.getTimerId( ) ) ) ); scheduleDecisionTask = true; } } catch ( SwfMetadataException e ) { throw up( e ); } break; case "CancelWorkflowExecution": final CancelWorkflowExecutionDecisionAttributes cancelWorkflowExecution = decision.getCancelWorkflowExecutionDecisionAttributes(); workflowExecution.closeWorkflow( WorkflowExecution.CloseStatus.Canceled, WorkflowHistoryEvent.create( workflowExecution, new WorkflowExecutionCanceledEventAttributes( ) .withDecisionTaskCompletedEventId( completedId ) .withDetails( cancelWorkflowExecution.getDetails() ) ) ); deleteActivities( activityTasks, accountFullName, workflowExecution ); break; case "CompleteWorkflowExecution": final CompleteWorkflowExecutionDecisionAttributes completed = decision.getCompleteWorkflowExecutionDecisionAttributes( ); workflowExecution.closeWorkflow( WorkflowExecution.CloseStatus.Completed, WorkflowHistoryEvent.create( workflowExecution, new WorkflowExecutionCompletedEventAttributes( ) .withDecisionTaskCompletedEventId( completedId ) .withResult( completed.getResult( ) ) ) ); break; case "ContinueAsNewWorkflowExecution": final ContinueAsNewWorkflowExecutionDecisionAttributes continueAsNew = decision.getContinueAsNewWorkflowExecutionDecisionAttributes( ); ContinueAsNewWorkflowExecutionFailedCause failedCause = null; if ( Pending == workflowExecution.getDecisionStatus( ) ) { failedCause = ContinueAsNewWorkflowExecutionFailedCause.UNHANDLED_DECISION; } WorkflowType workflowType = null; if ( failedCause == null ) try { workflowType = workflowTypes.lookupByExample( WorkflowType.exampleWithUniqueName( accountFullName, workflowExecution.getDomainName( ), workflowExecution.getWorkflowType( ).getName( ), MoreObjects.firstNonNull( continueAsNew.getWorkflowTypeVersion( ), workflowExecution.getWorkflowType( ).getWorkflowVersion( ) ) ), accountFullName, workflowExecution.getWorkflowType( ).getName( ), Predicates.alwaysTrue( ), Functions.identity( ) ); if ( !WorkflowType.Status.Registered.apply( workflowType ) ) { failedCause = ContinueAsNewWorkflowExecutionFailedCause.WORKFLOW_TYPE_DEPRECATED; } } catch ( SwfMetadataNotFoundException e ) { failedCause = ContinueAsNewWorkflowExecutionFailedCause.WORKFLOW_TYPE_DOES_NOT_EXIST; } catch ( SwfMetadataException e ) { throw up( e ); } if ( failedCause == null ) { final Integer requestedExecutionStartToCloseTimeout = parsePeriod( continueAsNew.getExecutionStartToCloseTimeout( ), -1 ); final Integer requestedTaskStartToCloseTimeout = parsePeriod( continueAsNew.getTaskStartToCloseTimeout( ), -1 ); if ( continueAsNew.getChildPolicy( ) == null && workflowType.getDefaultChildPolicy( ) == null ) { failedCause = ContinueAsNewWorkflowExecutionFailedCause.DEFAULT_CHILD_POLICY_UNDEFINED; } else if ( requestedExecutionStartToCloseTimeout == null && workflowType.getDefaultExecutionStartToCloseTimeout( ) == null ) { failedCause = ContinueAsNewWorkflowExecutionFailedCause.DEFAULT_EXECUTION_START_TO_CLOSE_TIMEOUT_UNDEFINED; } else if ( continueAsNew.getTaskList( ) == null && workflowType.getDefaultTaskList( ) == null ) { failedCause = ContinueAsNewWorkflowExecutionFailedCause.DEFAULT_TASK_LIST_UNDEFINED; } else if ( requestedTaskStartToCloseTimeout == null && workflowType.getDefaultTaskStartToCloseTimeout( ) == null ) { failedCause = ContinueAsNewWorkflowExecutionFailedCause.DEFAULT_TASK_START_TO_CLOSE_TIMEOUT_UNDEFINED; } else { final String childPolicy = MoreObjects.firstNonNull( continueAsNew.getChildPolicy( ), workflowType.getDefaultChildPolicy( ) ); final String taskList = continueAsNew.getTaskList( ) == null ? workflowType.getDefaultTaskList( ): continueAsNew.getTaskList( ).getName( ); final Integer executionStartToCloseTimeout = MoreObjects.firstNonNull( requestedExecutionStartToCloseTimeout, workflowType.getDefaultExecutionStartToCloseTimeout( ) ); final Integer taskStartToCloseTimeout = MoreObjects.firstNonNull( requestedTaskStartToCloseTimeout, workflowType.getDefaultTaskStartToCloseTimeout( ) ); final String taskStartToCloseTimeoutStr = taskStartToCloseTimeout < 0 ? "NONE" : String.valueOf( taskStartToCloseTimeout ); final WorkflowExecution workflowExecutionContinued = WorkflowExecution.create( userFullName, UUID.randomUUID( ).toString( ), domain, workflowType, workflowExecution.getWorkflowId( ), childPolicy, taskList, executionStartToCloseTimeout, taskStartToCloseTimeout < 0 ? null : taskStartToCloseTimeout, continueAsNew.getTagList( ), Lists.newArrayList( new WorkflowExecutionStartedEventAttributes( ) .withChildPolicy( childPolicy ) .withContinuedExecutionRunId( workflowExecution.getDisplayName( ) ) .withExecutionStartToCloseTimeout( String.valueOf( executionStartToCloseTimeout ) ) .withInput( continueAsNew.getInput( ) ) .withParentInitiatedEventId( 0L ) .withTaskList( new TaskList( ).withName( taskList ) ) .withTagList( continueAsNew.getTagList( ) ) .withTaskStartToCloseTimeout( taskStartToCloseTimeoutStr ) .withWorkflowType( new com.eucalyptus.simpleworkflow.common.model.WorkflowType( ) .withName( workflowType.getDisplayName( ) ) .withVersion( workflowType.getWorkflowVersion( ) ) ), new DecisionTaskScheduledEventAttributes( ) .withStartToCloseTimeout( taskStartToCloseTimeoutStr ) .withTaskList( new TaskList( ).withName( taskList ) ) ) ); try { workflowExecutions.save( workflowExecutionContinued ); } catch ( SwfMetadataException e ) { throw up( e ); } workflowExecution.closeWorkflow( WorkflowExecution.CloseStatus.Continued_As_New, WorkflowHistoryEvent.create( workflowExecution, new WorkflowExecutionCompletedEventAttributes( ) .withDecisionTaskCompletedEventId( completedId ) ) ); deleteActivities( activityTasks, accountFullName, workflowExecution ); notificationTypeListPairs.add( Pair.pair( "decision", taskList ) ); } } if ( failedCause != null ) { workflowExecution.addHistoryEvent( WorkflowHistoryEvent.create( workflowExecution, new ContinueAsNewWorkflowExecutionFailedEventAttributes( ) .withCause( ContinueAsNewWorkflowExecutionFailedCause.OPERATION_NOT_PERMITTED ) .withDecisionTaskCompletedEventId( completedId ) ) ); scheduleDecisionTask = true; } break; case "FailWorkflowExecution": final FailWorkflowExecutionDecisionAttributes failed = decision.getFailWorkflowExecutionDecisionAttributes(); workflowExecution.closeWorkflow( WorkflowExecution.CloseStatus.Failed, WorkflowHistoryEvent.create( workflowExecution, new WorkflowExecutionFailedEventAttributes( ) .withDecisionTaskCompletedEventId( completedId ) .withDetails( failed.getDetails( ) ) .withReason( failed.getReason( ) ) ) ); deleteActivities( activityTasks, accountFullName, workflowExecution ); break; case "RecordMarker": final RecordMarkerDecisionAttributes mark = decision.getRecordMarkerDecisionAttributes( ); workflowExecution.addHistoryEvent( WorkflowHistoryEvent.create( workflowExecution, new MarkerRecordedEventAttributes( ) .withDetails( mark.getDetails( ) ) .withDecisionTaskCompletedEventId( completedId ) .withMarkerName( mark.getMarkerName( ) ) ) ); break; case "RequestCancelActivityTask": final RequestCancelActivityTaskDecisionAttributes cancelActivity = decision.getRequestCancelActivityTaskDecisionAttributes(); try { activityTasks.updateByExample( ActivityTask.exampleWithActivityId( accountFullName, workflowExecution.getDomainName( ), workflowExecution.getDisplayName( ), cancelActivity.getActivityId( ) ), accountFullName, cancelActivity.getActivityId( ), (Function<ActivityTask, Void>) activityTask -> { final Long cancelRequestedId = workflowExecution.addHistoryEvent( WorkflowHistoryEvent.create( workflowExecution, new ActivityTaskCancelRequestedEventAttributes() .withDecisionTaskCompletedEventId( completedId ) .withActivityId( cancelActivity.getActivityId() ) ) ); if ( activityTask.getState( ) == ActivityTask.State.Active ) { activityTask.setCancelRequestedEventId( cancelRequestedId ); } else { workflowExecution.addHistoryEvent( WorkflowHistoryEvent.create( workflowExecution, new ActivityTaskCanceledEventAttributes() .withLatestCancelRequestedEventId( cancelRequestedId ) .withScheduledEventId( activityTask.getScheduledEventId() ) .withStartedEventId( activityTask.getStartedEventId() ) ) ); Entities.delete( activityTask ); } return null; } ); } catch ( SwfMetadataNotFoundException e ) { workflowExecution.addHistoryEvent( WorkflowHistoryEvent.create( workflowExecution, new RequestCancelActivityTaskFailedEventAttributes( ) .withCause( RequestCancelActivityTaskFailedCause.ACTIVITY_ID_UNKNOWN ) .withDecisionTaskCompletedEventId( completedId ) .withActivityId( cancelActivity.getActivityId( ) ) ) ); } catch ( SwfMetadataException e ) { throw up( e ); } scheduleDecisionTask = true; break; case "RequestCancelExternalWorkflowExecution": final RequestCancelExternalWorkflowExecutionDecisionAttributes cancelExternalWorkflow = decision.getRequestCancelExternalWorkflowExecutionDecisionAttributes(); workflowExecution.addHistoryEvent( WorkflowHistoryEvent.create( workflowExecution, new RequestCancelExternalWorkflowExecutionFailedEventAttributes() .withCause( RequestCancelExternalWorkflowExecutionFailedCause.UNKNOWN_EXTERNAL_WORKFLOW_EXECUTION ) .withControl( cancelExternalWorkflow.getControl() ) .withDecisionTaskCompletedEventId( completedId ) .withRunId( cancelExternalWorkflow.getRunId() ) .withWorkflowId( cancelExternalWorkflow.getWorkflowId() ) ) ); scheduleDecisionTask = true; break; case "ScheduleActivityTask": workflowExecution.setLatestActivityTaskScheduled( new Date( ) ); final ScheduleActivityTaskDecisionAttributes scheduleActivity = decision.getScheduleActivityTaskDecisionAttributes(); final Long scheduledId = workflowExecution.addHistoryEvent( WorkflowHistoryEvent.create( workflowExecution, new ActivityTaskScheduledEventAttributes( ) .withDecisionTaskCompletedEventId( completedId ) .withActivityId( scheduleActivity.getActivityId( ) ) .withActivityType( scheduleActivity.getActivityType( ) ) .withControl( scheduleActivity.getControl( ) ) .withHeartbeatTimeout( scheduleActivity.getHeartbeatTimeout( ) ) .withInput( scheduleActivity.getInput( ) ) .withScheduleToCloseTimeout( scheduleActivity.getScheduleToCloseTimeout( ) ) .withScheduleToStartTimeout( scheduleActivity.getScheduleToStartTimeout( ) ) .withStartToCloseTimeout( scheduleActivity.getStartToCloseTimeout( ) ) .withTaskList( scheduleActivity.getTaskList( ) ) ) ); try { final ActivityType activityType; try { activityType = activityTypes.lookupByExample( ActivityType.exampleWithUniqueName( accountFullName, domain.getDisplayName(), scheduleActivity.getActivityType().getName(), scheduleActivity.getActivityType().getVersion() ), accountFullName, scheduleActivity.getActivityType().getName(), Predicates.alwaysTrue(), Functions.identity() ); } catch ( final SwfMetadataNotFoundException e ) { throw new ScheduleActivityTaskException( ACTIVITY_TYPE_DOES_NOT_EXIST ); } if ( ActivityType.Status.Deprecated.apply( activityType ) ) { throw new ScheduleActivityTaskException( ACTIVITY_TYPE_DEPRECATED ); } final String list = scheduleActivity.getTaskList( ) == null ? activityType.getDefaultTaskList( ) : scheduleActivity.getTaskList( ).getName( ); if ( list == null ) { throw new ScheduleActivityTaskException( DEFAULT_TASK_LIST_UNDEFINED ); } if ( activityTaskCounter.get( ) + activityTaskScheduledCount >= SimpleWorkflowProperties.getOpenActivityTasksPerWorkflowExecution() ) { throw new ScheduleActivityTaskException( OPEN_ACTIVITIES_LIMIT_EXCEEDED ); } activityTasks.save( com.eucalyptus.simpleworkflow.ActivityTask.create( userFullName, workflowExecution, domain.getDisplayName(), domain.getNaturalId( ), scheduleActivity.getActivityId(), scheduleActivity.getActivityType().getName(), scheduleActivity.getActivityType().getVersion(), scheduleActivity.getInput(), scheduledId, list, parseActivityPeriod( scheduleActivity.getScheduleToCloseTimeout( ), activityType.getDefaultTaskScheduleToCloseTimeout( ), DEFAULT_SCHEDULE_TO_CLOSE_TIMEOUT_UNDEFINED ), parseActivityPeriod( scheduleActivity.getScheduleToStartTimeout( ), activityType.getDefaultTaskScheduleToStartTimeout( ), DEFAULT_SCHEDULE_TO_START_TIMEOUT_UNDEFINED ), parseActivityPeriod( scheduleActivity.getStartToCloseTimeout( ), activityType.getDefaultTaskStartToCloseTimeout( ), DEFAULT_START_TO_CLOSE_TIMEOUT_UNDEFINED ), parseActivityPeriod( scheduleActivity.getHeartbeatTimeout() , activityType.getDefaultTaskHeartbeatTimeout( ), DEFAULT_HEARTBEAT_TIMEOUT_UNDEFINED ) ) ); activityTaskScheduledCount++; notificationTypeListPairs.add( Pair.pair( "activity", list ) ); } catch ( final ScheduleActivityTaskException e ) { workflowExecution.addHistoryEvent( WorkflowHistoryEvent.create( workflowExecution, new ScheduleActivityTaskFailedEventAttributes( ) .withActivityId( scheduleActivity.getActivityId( ) ) .withActivityType( scheduleActivity.getActivityType( ) ) .withCause( e.getFailedCause( ) ) .withDecisionTaskCompletedEventId( completedId ) ) ); scheduleDecisionTask = true; } catch ( final Exception e ) { throw up( e ); } break; case "SignalExternalWorkflowExecution": final SignalExternalWorkflowExecutionDecisionAttributes signalExternalWorkflow = decision.getSignalExternalWorkflowExecutionDecisionAttributes(); workflowExecution.addHistoryEvent( WorkflowHistoryEvent.create( workflowExecution, new SignalExternalWorkflowExecutionFailedEventAttributes( ) .withCause( SignalExternalWorkflowExecutionFailedCause.UNKNOWN_EXTERNAL_WORKFLOW_EXECUTION ) .withControl( signalExternalWorkflow.getControl() ) .withDecisionTaskCompletedEventId( completedId ) .withRunId( signalExternalWorkflow.getRunId( ) ) .withWorkflowId( signalExternalWorkflow.getWorkflowId( ) ) ) ); scheduleDecisionTask = true; break; case "StartChildWorkflowExecution": final StartChildWorkflowExecutionDecisionAttributes startChildWorkflow = decision.getStartChildWorkflowExecutionDecisionAttributes(); workflowExecution.addHistoryEvent( WorkflowHistoryEvent.create( workflowExecution, new StartChildWorkflowExecutionFailedEventAttributes() .withCause( StartChildWorkflowExecutionFailedCause.OPERATION_NOT_PERMITTED ) .withControl( startChildWorkflow.getControl() ) .withDecisionTaskCompletedEventId( completedId ) .withWorkflowId( startChildWorkflow.getWorkflowId() ) .withWorkflowType( startChildWorkflow.getWorkflowType() ) ) ); scheduleDecisionTask = true; break; case "ScheduleLambdaFunction": final ScheduleLambdaFunctionDecisionAttributes scheduleLambdaFunction = decision.getScheduleLambdaFunctionDecisionAttributes(); workflowExecution.addHistoryEvent(WorkflowHistoryEvent.create( workflowExecution, new ScheduleLambdaFunctionFailedEventAttributes() .withId(scheduleLambdaFunction.getId()) .withName(scheduleLambdaFunction.getName()) .withCause( ScheduleLambdaFunctionFailedCause.LAMBDA_SERVICE_NOT_AVAILABLE_IN_REGION ) .withDecisionTaskCompletedEventId( completedId ))); scheduleDecisionTask = true; break; case "StartTimer": final StartTimerDecisionAttributes startTimer = decision.getStartTimerDecisionAttributes( ); try { if ( !timers.listByExample( Timer.exampleWithTimerId( accountFullName, workflowExecution.getDomainName(), workflowExecution.getDisplayName(), startTimer.getTimerId() ), Predicates.alwaysTrue(), Functions.identity() ).isEmpty() ) { throw new StartTimerException( StartTimerFailedCause.TIMER_ID_ALREADY_IN_USE ); } final Long startedId = workflowExecution.addHistoryEvent( WorkflowHistoryEvent.create( workflowExecution, new TimerStartedEventAttributes() .withControl( startTimer.getControl() ) .withDecisionTaskCompletedEventId( completedId ) .withStartToFireTimeout( startTimer.getStartToFireTimeout() ) .withTimerId( startTimer.getTimerId() ) ) ); if ( timers.countByWorkflowExecution( accountFullName, domain.getDisplayName(), workflowExecution.getDisplayName() ) >= SimpleWorkflowProperties.getOpenTimersPerWorkflowExecution() ) { throw new StartTimerException( StartTimerFailedCause.OPEN_TIMERS_LIMIT_EXCEEDED ); } timers.save( Timer.create( userFullName, workflowExecution, workflowExecution.getDomainName(), workflowExecution.getDomainUuid(), startTimer.getTimerId(), startTimer.getControl(), parsePeriod( startTimer.getStartToFireTimeout(), 0 ), completedId, startedId ) ); } catch ( StartTimerException e ) { workflowExecution.addHistoryEvent( WorkflowHistoryEvent.create( workflowExecution, new StartTimerFailedEventAttributes() .withCause( e.getFailedCause( ) ) .withDecisionTaskCompletedEventId( completedId ) .withTimerId( startTimer.getTimerId() ) ) ); scheduleDecisionTask = true; } catch ( SwfMetadataException e ) { throw up( e ); } break; default: throw up( new SimpleWorkflowException( "InternalFailure", Role.Receiver, "Unsupported decision type: " + decision.getDecisionType( ) ) ); } } if ( scheduleDecisionTask && workflowExecution.getDecisionStatus( ) != Pending ) { workflowExecution.addHistoryEvent( WorkflowHistoryEvent.create( workflowExecution, new DecisionTaskScheduledEventAttributes() .withTaskList( new TaskList( ).withName( workflowExecution.getTaskList( ) ) ) .withStartToCloseTimeout( String.valueOf( workflowExecution.getTaskStartToCloseTimeout( ) ) ) ) ); workflowExecution.setDecisionStatus( Pending ); workflowExecution.setDecisionTimestamp( new Date( ) ); notificationTypeListPairs.add( Pair.pair( "decision", workflowExecution.getTaskList( ) ) ); } else { workflowExecution.updateTimeStamps( ); } } return workflowExecution; } } ); } //TODO:STEVE: update API to allow batch notification for ( final Pair<String,String> notificationTypeListPair : notificationTypeListPairs ) { notifyTaskList( accountFullName, domain.getDisplayName( ), notificationTypeListPair.getLeft( ), notificationTypeListPair.getRight( ) ); } } catch( Exception e ) { throw handleException( e ); } return request.reply( new SimpleWorkflowMessage( ) ); } public SimpleWorkflowMessage signalWorkflowExecution( final SignalWorkflowExecutionRequest request ) throws SimpleWorkflowException { final Context ctx = Contexts.lookup( ); final UserFullName userFullName = ctx.getUserFullName( ); final AccountFullName accountFullName = userFullName.asAccountFullName( ); final Predicate<? super WorkflowExecution> accessible = SimpleWorkflowMetadatas.filteringFor( WorkflowExecution.class ).byPrivileges( ).buildPredicate( ); try { final WorkflowExecution example = WorkflowExecution.exampleForOpenWorkflow( accountFullName, request.getDomain(), request.getWorkflowId(), request.getRunId() ); final Pair<String,String> domainUuidRunIdPair = workflowExecutions.lookupByExample( example, accountFullName, request.getWorkflowId( ), Predicates.alwaysTrue( ), Pair.builder( WorkflowExecutions.WorkflowExecutionStringFunctions.DOMAIN_UUID, SimpleWorkflowMetadatas.toDisplayName( ) ) ); final Pair<String,String> domainTaskListPair; try ( final WorkflowLock lock = WorkflowLock.lock( accountFullName, domainUuidRunIdPair ) ) { domainTaskListPair = workflowExecutions.withRetries().updateByExample( example, accountFullName, request.getWorkflowId(), workflowExecution -> { if ( accessible.apply( workflowExecution ) ) { workflowExecution.addHistoryEvent( WorkflowHistoryEvent.create( workflowExecution, new WorkflowExecutionSignaledEventAttributes() .withExternalInitiatedEventId( 0L ) .withInput( request.getInput() ) .withSignalName( request.getSignalName() ) ) ); if ( workflowExecution.getDecisionStatus() != Pending ) { workflowExecution.addHistoryEvent( WorkflowHistoryEvent.create( workflowExecution, new DecisionTaskScheduledEventAttributes() .withTaskList( new TaskList().withName( workflowExecution.getTaskList() ) ) .withStartToCloseTimeout( String.valueOf( workflowExecution.getTaskStartToCloseTimeout() ) ) ) ); if ( workflowExecution.getDecisionStatus() == Idle ) { workflowExecution.setDecisionStatus( Pending ); workflowExecution.setDecisionTimestamp( new Date() ); return Pair.pair( workflowExecution.getDomainName(), workflowExecution.getTaskList() ); } } } return null; } ); } if ( domainTaskListPair != null ) { notifyTaskList( accountFullName, domainTaskListPair.getLeft( ), "decision", domainTaskListPair.getRight( ) ); } } catch ( SwfMetadataNotFoundException e ) { throw new SimpleWorkflowClientException( "UnknownResourceFault", request.getRunId( ) == null ? "Unknown execution, workflowId = " + request.getWorkflowId( ) : "Unknown execution: WorkflowExecution=[workflowId=" + request.getWorkflowId( ) + ", runId="+ request.getRunId( ) +"]" ); } catch ( SwfMetadataException e ) { throw handleException( e ); } return request.reply( new SimpleWorkflowMessage( ) ); } public SimpleWorkflowMessage requestCancelWorkflowExecution( final RequestCancelWorkflowExecutionRequest request ) throws SimpleWorkflowException { final Context ctx = Contexts.lookup( ); final UserFullName userFullName = ctx.getUserFullName( ); final AccountFullName accountFullName = userFullName.asAccountFullName( ); final Predicate<? super WorkflowExecution> accessible = SimpleWorkflowMetadatas.filteringFor( WorkflowExecution.class ).byPrivileges( ).buildPredicate( ); try { final WorkflowExecution example = WorkflowExecution.exampleForOpenWorkflow( accountFullName, request.getDomain(), request.getWorkflowId(), request.getRunId() ); final Pair<String,String> domainUuidRunIdPair = workflowExecutions.lookupByExample( example, accountFullName, request.getWorkflowId( ), Predicates.alwaysTrue( ), Pair.builder( WorkflowExecutions.WorkflowExecutionStringFunctions.DOMAIN_UUID, SimpleWorkflowMetadatas.toDisplayName( ) ) ); final Pair<String, String> domainTaskListPair; try ( final WorkflowLock lock = WorkflowLock.lock( accountFullName, domainUuidRunIdPair ) ) { domainTaskListPair = workflowExecutions.withRetries().updateByExample( example, accountFullName, request.getWorkflowId(), workflowExecution -> { if ( accessible.apply( workflowExecution ) ) { workflowExecution.setCancelRequested( true ); workflowExecution.addHistoryEvent( WorkflowHistoryEvent.create( workflowExecution, new WorkflowExecutionCancelRequestedEventAttributes() .withExternalInitiatedEventId( 0L ) ) ); if ( workflowExecution.getDecisionStatus() != Pending ) { workflowExecution.addHistoryEvent( WorkflowHistoryEvent.create( workflowExecution, new DecisionTaskScheduledEventAttributes() .withTaskList( new TaskList().withName( workflowExecution.getTaskList() ) ) .withStartToCloseTimeout( String.valueOf( workflowExecution.getTaskStartToCloseTimeout() ) ) ) ); if ( workflowExecution.getDecisionStatus() == Idle ) { workflowExecution.setDecisionStatus( Pending ); workflowExecution.setDecisionTimestamp( new Date() ); return Pair.pair( workflowExecution.getDomainName(), workflowExecution.getTaskList() ); } } } return null; } ); } if ( domainTaskListPair != null ) { notifyTaskList( accountFullName, domainTaskListPair.getLeft( ), "decision", domainTaskListPair.getRight( ) ); } } catch ( SwfMetadataNotFoundException e ) { throw new SimpleWorkflowClientException( "UnknownResourceFault", request.getRunId( ) == null ? "Unknown execution, workflowId = " + request.getWorkflowId( ) : "Unknown execution: WorkflowExecution=[workflowId=" + request.getWorkflowId( ) + ", runId="+ request.getRunId( ) +"]" ); } catch ( SwfMetadataException e ) { throw handleException( e ); } return request.reply( new SimpleWorkflowMessage( ) ); } public SimpleWorkflowMessage terminateWorkflowExecution( final TerminateWorkflowExecutionRequest request ) throws SimpleWorkflowException { final Context ctx = Contexts.lookup( ); final UserFullName userFullName = ctx.getUserFullName( ); final AccountFullName accountFullName = userFullName.asAccountFullName( ); final Predicate<? super WorkflowExecution> accessible = SimpleWorkflowMetadatas.filteringFor( WorkflowExecution.class ).byPrivileges( ).buildPredicate( ); try { final WorkflowExecution example = WorkflowExecution.exampleForOpenWorkflow( accountFullName, request.getDomain(), request.getWorkflowId(), request.getRunId() ); final Pair<String,String> domainUuidRunIdPair = workflowExecutions.lookupByExample( example, accountFullName, request.getWorkflowId( ), Predicates.alwaysTrue( ), Pair.builder( WorkflowExecutions.WorkflowExecutionStringFunctions.DOMAIN_UUID, SimpleWorkflowMetadatas.toDisplayName( ) ) ); try ( final WorkflowLock lock = WorkflowLock.lock( accountFullName, domainUuidRunIdPair ) ) { workflowExecutions.withRetries().updateByExample( example, accountFullName, request.getWorkflowId(), (Function<WorkflowExecution, Void>) workflowExecution -> { if ( accessible.apply( workflowExecution ) ) { workflowExecution.closeWorkflow( WorkflowExecution.CloseStatus.Terminated, WorkflowHistoryEvent.create( workflowExecution, new WorkflowExecutionTerminatedEventAttributes() .withChildPolicy( MoreObjects.firstNonNull( request.getChildPolicy(), workflowExecution.getChildPolicy() ) ) .withDetails( request.getDetails() ) .withReason( request.getReason() ) ) ); } deleteActivities( activityTasks, accountFullName, workflowExecution ); return null; } ); } } catch ( SwfMetadataNotFoundException e ) { throw new SimpleWorkflowClientException( "UnknownResourceFault", request.getRunId( ) == null ? "Unknown execution, workflowId = " + request.getWorkflowId( ) : "Unknown execution: WorkflowExecution=[workflowId=" + request.getWorkflowId( ) + ", runId="+ request.getRunId( ) +"]" ); } catch ( SwfMetadataException e ) { throw handleException( e ); } return request.reply( new SimpleWorkflowMessage( ) ); } public History getWorkflowExecutionHistory( final GetWorkflowExecutionHistoryRequest request ) throws SimpleWorkflowException { final Context ctx = Contexts.lookup(); final UserFullName userFullName = ctx.getUserFullName( ); final AccountFullName accountFullName = userFullName.asAccountFullName(); final Predicate<? super WorkflowExecution> accessible = SimpleWorkflowMetadatas.filteringFor( WorkflowExecution.class ).byPrivileges( ).buildPredicate( ); final History history; try { history = workflowExecutions.lookupByExample( WorkflowExecution.exampleWithName( accountFullName, request.getExecution().getRunId( ) ), accountFullName, request.getExecution().getRunId(), accessible, workflowExecution -> { final List<WorkflowHistoryEvent> events = workflowExecution.getWorkflowHistory(); final List<WorkflowHistoryEvent> reverseEvents = Lists.reverse( events ); return new History( ) .withEvents( Collections2.transform( MoreObjects.firstNonNull( request.isReverseOrder( ), Boolean.FALSE ) ? reverseEvents : events, TypeMappers.lookup( WorkflowHistoryEvent.class, HistoryEvent.class ) ) ); } ); } catch ( SwfMetadataNotFoundException e ) { throw new SimpleWorkflowClientException( "UnknownResourceFault", "Unknown execution, runId = " + request.getExecution().getRunId( ) ); } catch ( Exception e ) { throw handleException( e ); } return request.reply( history ); } private <T extends AbstractPersistent & RestrictedType> T allocate( final Supplier<T> allocator, final Class<T> type, final String name ) throws SimpleWorkflowException { try { return RestrictedTypes.allocateUnitlessResources( type, 1, transactional( allocator ) ).get( 0 ); } catch ( Exception e ) { final SQLException sqlException = Exceptions.findCause( e, SQLException.class ); //TODO:STEVE: why no ConstraintViolationException? final ConstraintViolationException constraintViolationException = Exceptions.findCause( e, ConstraintViolationException.class ); if ( constraintViolationException != null || ( sqlException != null && "23505".equals( sqlException.getSQLState( ) ) ) ) { final String typeName = type.getSimpleName( ); final String faultPrefix = typeName.endsWith( "Type" ) ? "Type" : typeName; throw new SimpleWorkflowClientException( faultPrefix + "AlreadyExistsFault", typeName + " already exists: " + name ); } throw handleException( e ); } } @SuppressWarnings( "WeakerAccess" ) protected <E extends AbstractPersistent> Supplier<E> transactional( final Supplier<E> supplier ) { return Entities.asTransaction( supplier ); } private static void deleteActivities( final ActivityTasks activityTasks, final AccountFullName accountFullName, final WorkflowExecution workflowExecution ) { try { activityTasks.deleteByExample( ActivityTask.exampleWithWorkflowExecution( accountFullName, workflowExecution.getDomainName( ), workflowExecution.getDisplayName( ) ) ); } catch ( SwfMetadataException e ) { throw up( e ); } } private static void buildFilters( final ClosedWorkflowExecutionFilterParameters parameters, final Conjunction filter, final Map<String,String> aliases ) { if ( parameters.getCloseStatusFilter( ) != null ) { filter.add( Restrictions.eq( "closeStatus", WorkflowExecution.CloseStatus.fromString( parameters.getCloseStatusFilter().getStatus( ) ) ) ); } if ( parameters.getCloseTimeFilter( ) != null ) { if ( parameters.getCloseTimeFilter( ).getOldestDate( ) != null ) { filter.add( Restrictions.ge( "closeTimestamp", parameters.getCloseTimeFilter( ).getOldestDate( ) ) ); } if ( parameters.getCloseTimeFilter( ).getLatestDate( ) != null ) { filter.add( Restrictions.le( "closeTimestamp", parameters.getCloseTimeFilter( ).getLatestDate( ) ) ); } } buildFilters( (WorkflowExecutionFilterParameters) parameters, filter, aliases ); } private static void buildFilters( final WorkflowExecutionFilterParameters parameters, final Conjunction filter, final Map<String,String> aliases ) { if ( parameters.getExecutionFilter( ) != null ) { filter.add( Restrictions.eq( "workflowId", parameters.getExecutionFilter( ).getWorkflowId( ) ) ); } if ( parameters.getStartTimeFilter( ) != null ) { if ( parameters.getStartTimeFilter().getOldestDate( ) != null ) { filter.add( Restrictions.ge( "creationTimestamp", parameters.getStartTimeFilter().getOldestDate( ) ) ); } if ( parameters.getStartTimeFilter().getLatestDate( ) != null ) { filter.add( Restrictions.le( "creationTimestamp", parameters.getStartTimeFilter().getLatestDate( ) ) ); } } if ( parameters.getTagFilter( ) != null ) { aliases.put( "tagList", "tag" ); filter.add( Restrictions.eq( "tag.elements", parameters.getTagFilter( ).getTag( ) ) ); } if ( parameters.getTypeFilter( ) != null ) { if ( parameters.getTypeFilter( ).getName() != null ) { aliases.put( "workflowType", "workflowType" ); filter.add( Restrictions.eq( "workflowType.displayName", parameters.getTypeFilter( ).getName() ) ); } if ( parameters.getTypeFilter( ).getVersion( ) != null ) { aliases.put( "workflowType", "workflowType" ); filter.add( Restrictions.eq( "workflowType.workflowVersion", parameters.getTypeFilter().getVersion() ) ); } } } /** * Method always throws, signature allows use of "throw up ..." */ private static RuntimeException up( final Throwable throwable ) { throw Exceptions.toUndeclared( throwable ); } /** * Method always throws, signature allows use of "throw upClient ..." */ private static RuntimeException upClient( final String errorCode, final String message ) { throw up( new SimpleWorkflowClientException( errorCode, message ) ); } /** * Method always throws, signature allows use of "throw handleException ..." */ private SimpleWorkflowException handleException( final Exception e ) throws SimpleWorkflowException { final SimpleWorkflowException cause = Exceptions.findCause( e, SimpleWorkflowException.class ); if ( cause != null ) { throw cause; } final WorkflowHistorySizeLimitException historySizeLimitCause = Exceptions.findCause( e, WorkflowHistorySizeLimitException.class ); if ( historySizeLimitCause != null ) { WorkflowExecutions.Utils.terminateWorkflowExecution( workflowExecutions, "EVENT_LIMIT_EXCEEDED", historySizeLimitCause.getAccountNumber( ), historySizeLimitCause.getDomain( ), historySizeLimitCause.getWorkflowId( ) ); throw new SimpleWorkflowClientException( "LimitExceededFault", "Request would exceed history limit for workflow execution" ); } final AuthQuotaException quotaCause = Exceptions.findCause( e, AuthQuotaException.class ); if ( quotaCause != null ) { throw new SimpleWorkflowClientException( "LimitExceededFault", "Request would exceed quota for type: " + quotaCause.getType( ) ); } final TaskTokenException tokenCause = Exceptions.findCause( e, TaskTokenException.class ); if ( tokenCause != null ) { throw new SimpleWorkflowClientException( "InvalidParameterValue", "Invalid task token." ); } logger.error( e, e ); final SimpleWorkflowException exception = new SimpleWorkflowException( "InternalError", Role.Receiver, String.valueOf(e.getMessage( )) ); if ( Contexts.lookup( ).hasAdministrativePrivileges( ) ) { exception.initCause( e ); } throw exception; } private static Integer parsePeriod( final String period, final Integer noneValue ) { if ( period == null ) { return null; } else if ( "NONE".equals( period ) ) { return noneValue; } else { return Integer.parseInt( period ); } } private static Integer parseActivityPeriod( final String period, final Integer defaultValue, final ScheduleActivityTaskFailedCause failureOnNoDefault ) throws ScheduleActivityTaskException { if ( period == null && defaultValue == null ) { throw new ScheduleActivityTaskException( failureOnNoDefault ); } else if ( period == null ) { return defaultValue < 0 ? null : defaultValue; } else if ( "NONE".equals( period ) ) { return null; } else { return Integer.parseInt( period ); } } private static Integer requireDefault( final Integer value, final Integer defaultValue, final String description ) throws SimpleWorkflowClientException { if ( value == null && defaultValue == null ) { throw new SimpleWorkflowClientException( "DefaultUndefinedFault" , description + " is required" ); } return MoreObjects.firstNonNull( value, defaultValue ); } private static <T> Iterable<List<T>> shufflePartitions( final List<T> pending ) { return (Iterable<List<T>>) Iterables.transform( Iterables.partition( pending, 40 ), segment -> { List<T> copy = Lists.newArrayList( segment ); Collections.shuffle( copy ); return copy; } ); } private static void noteTaskListActivity( final AccountFullName accountFullName, final String domain, final String type, final String taskList ) { taskListActivity.put( NotifyTaskList.of( accountFullName, domain, type, taskList ), System.currentTimeMillis( ) ); } private static boolean taskListActive( final AccountFullName accountFullName, final String domain, final String type, final String taskList, final long timestamp ) { return taskListActivity.getOrDefault( NotifyTaskList.of( accountFullName, domain, type, taskList ), 0L ) > timestamp; } private static void taskListActivityCleanup( long timestamp ) { for ( final Map.Entry<NotifyTaskList,Long> entry : taskListActivity.entrySet( ) ) { if ( entry.getValue( ) < timestamp ) { taskListActivity.remove( entry.getKey( ), entry.getValue( ) ); } } } private static void notifyTaskList( final AccountFullName accountFullName, final String domain, final String type, final String taskList ) { noteTaskListActivity( accountFullName, domain, type, taskList ); NotifyClient.notifyTaskList( accountFullName, domain, type, taskList ); } private static final long EXPIRY_MILLIS = TimeUnit.SECONDS.toMillis( 30 ); private static <R extends SimpleWorkflowMessage> R handleTaskPolling( final AccountFullName accountFullName, final String domain, final String type, final String taskList, final String correlationId, final R emptyResponse, final Callable<R> responseCallable) { final Long pollTimeout = System.currentTimeMillis() + EXPIRY_MILLIS; return handleTaskPolling(accountFullName, domain, type, taskList, correlationId, emptyResponse, responseCallable, true, pollTimeout); } private static <R extends SimpleWorkflowMessage> R handleTaskPolling( final AccountFullName accountFullName, final String domain, final String type, final String taskList, final String correlationId, final R emptyResponse, final Callable<R> responseCallable, final boolean checkImmediately, final long pollTimeout) { final String list = Joiner.on('/').join( type, domain, taskList ); try { final long activityTimestamp = System.currentTimeMillis( ) - TimeUnit.SECONDS.toMillis( 1L ); if ( checkImmediately && taskListActive( accountFullName, domain, type, taskList, activityTimestamp ) ) { final R taskResponse = responseCallable.call( ); if ( taskResponse != null ) { taskResponse.setCorrelationId(correlationId); return taskResponse; } } final Consumer<Boolean> consumer = NotifyClient.pollTaskList(accountFullName, domain, type, taskList, pollTimeout, Contexts.consumerWithCurrentContext( (notified) -> { try { if (notified) { final SimpleWorkflowMessage taskResponse = responseCallable.call(); if (taskResponse != null) { taskResponse.setCorrelationId(correlationId); Contexts.response(taskResponse); return; } else if ( System.currentTimeMillis() < pollTimeout ) { handleTaskPolling( accountFullName, domain, type, taskList, correlationId, emptyResponse, responseCallable, false, pollTimeout ); return; } } emptyResponse.setCorrelationId(correlationId); Contexts.response(emptyResponse); } catch (final InterruptedException e) { logger.info("Interrupted while polling for task " + list, e); } catch (final Exception e) { logger.error("Error polling for task " + list, e); } } )); timestampedPollers.add( Pair.of( pollTimeout, consumer ) ); return null; } catch ( Exception e ) { logger.error( "Error polling for task " + list, e ); emptyResponse.setCorrelationId( correlationId ); return emptyResponse; } } private static void pollerCleanup( final long timestamp ) { // drop fast if excessive pollers final int remove = timestampedPollers.size( ) - 1000; for ( int i=0; i<remove; i++ ) { timestampedPollers.pollFirst( ); } // remove expired pollers Pair<Long,Consumer<Boolean>> consumerPair; while ( ( consumerPair = timestampedPollers.peekFirst( ) ) != null ) { if ( consumerPair.getLeft( ) < timestamp ) { timestampedPollers.remove( consumerPair ); } else { break; } } } private static void pollerShutdown( ) { Pair<Long,Consumer<Boolean>> consumerPair; while ( ( consumerPair = timestampedPollers.pollFirst( ) ) != null ) { consumerPair.getRight( ).accept( false ); } } public static class SimpleWorkflowServiceCleanup implements EventListener<ClockTick> { public static void register() { Listeners.register( ClockTick.class, new SimpleWorkflowServiceCleanup( ) ); } @Override public void fireEvent( final ClockTick event ) { taskListActivityCleanup( System.currentTimeMillis( ) - TimeUnit.SECONDS.toMillis( 10 ) ); pollerCleanup( System.currentTimeMillis( ) ); } } private static final class ScheduleActivityTaskException extends Exception { private static final long serialVersionUID = 1L; private final ScheduleActivityTaskFailedCause failedCause; ScheduleActivityTaskException( final ScheduleActivityTaskFailedCause failedCause ) { this.failedCause = failedCause; } ScheduleActivityTaskFailedCause getFailedCause() { return failedCause; } } private static final class StartTimerException extends Exception { private static final long serialVersionUID = 1L; private final StartTimerFailedCause failedCause; StartTimerException( final StartTimerFailedCause failedCause ) { this.failedCause = failedCause; } StartTimerFailedCause getFailedCause() { return failedCause; } } private static final class PollerShutdown implements Runnable { @Override public void run( ) { logger.info( "Notifying task pollers on shutdown" ); pollerShutdown( ); } public String toString( ) { return "Simple workflow task poller notification"; } } }