com.eucalyptus.simpleworkflow.SimpleWorkflowService.java Source code

Java tutorial

Introduction

Here is the source code for com.eucalyptus.simpleworkflow.SimpleWorkflowService.java

Source

/*************************************************************************
 * Copyright 2009-2014 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.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.Callable;
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.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.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.Callback;
import com.eucalyptus.util.CollectionUtils;
import com.eucalyptus.util.Consumer;
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.Objects;
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")
@ComponentNamed
public class SimpleWorkflowService {

    private static final Logger logger = Logger.getLogger(SimpleWorkflowService.class);

    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(),
                            Objects.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(), new Callback<Domain>() {
                        @Override
                        public void fire(final Domain 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.<String, String>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.<Domain>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));
                    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(), new Callback<ActivityType>() {
                        @Override
                        public void fire(final ActivityType 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(),
                    Objects.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.<Domain>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(), new Callback<WorkflowType>() {
                        @Override
                        public void fire(final WorkflowType 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(),
                    Objects.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,
                    new Function<WorkflowExecution, WorkflowExecutionDetail>() {
                        @Override
                        public WorkflowExecutionDetail apply(final WorkflowExecution 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));
                            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, new Function<Domain, WorkflowExecutionCount>() {
                        @Override
                        public WorkflowExecutionCount apply(final Domain domain) {
                            final Conjunction filter = Restrictions.conjunction();
                            final Map<String, String> aliases = Maps.newHashMap();
                            buildFilters(request, filter, aliases);
                            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, new Function<Domain, WorkflowExecutionCount>() {
                        @Override
                        public WorkflowExecutionCount apply(final Domain domain) {
                            final Conjunction filter = Restrictions.conjunction();
                            final Map<String, String> aliases = Maps.newHashMap();
                            buildFilters(request, filter, aliases);
                            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 {
            pendingTaskCount = domains.lookupByExample(Domain.exampleWithName(accountFullName, request.getDomain()),
                    accountFullName, request.getDomain(), accessible, new Function<Domain, PendingTaskCount>() {
                        @Override
                        public PendingTaskCount apply(final Domain domain) {
                            return 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 {
            pendingTaskCount = domains.lookupByExample(Domain.exampleWithName(accountFullName, request.getDomain()),
                    accountFullName, request.getDomain(), accessible, new Function<Domain, PendingTaskCount>() {
                        @Override
                        public PendingTaskCount apply(final Domain domain) {
                            return 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(),
                    Objects.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(),
                    Objects.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.<Domain>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 = Objects.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(request.getTaskList())));
                    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 = new Callable<com.eucalyptus.simpleworkflow.common.model.ActivityTask>() {
            @Override
            public com.eucalyptus.simpleworkflow.common.model.ActivityTask call() throws Exception {
                com.eucalyptus.simpleworkflow.common.model.ActivityTask activityTask = null;
                final List<ActivityTask> pending = activityTasks.listByExample(
                        ActivityTask.examplePending(accountFullName, domain, taskList), accessible,
                        Functions.<ActivityTask>identity());
                Collections.sort(pending, Ordering.natural().onResultOf(AbstractPersistentSupport.creation()));
                for (final ActivityTask pendingTask : pending) {
                    if (activityTask != null)
                        break;
                    boolean retry = true;
                    while (retry)
                        try (final WorkflowLock lock = WorkflowLock.lock(accountFullName,
                                pendingTask.getDomainUuid(), pendingTask.getWorkflowRunId())) {
                            retry = false;
                            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) {
                            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("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, will retry.");
                                Thread.sleep(10);
                                retry = true;
                            } else {
                                logger.error(
                                        "Error taking activity task for domain " + domain + ", list " + taskList,
                                        e);
                            }
                        }
                }
                return activityTask;
            }
        };

        try {
            handleTaskPolling(accountFullName, domain, "activity", taskList, request.getCorrelationId(),
                    new com.eucalyptus.simpleworkflow.common.model.ActivityTask(), taskCallable);
        } catch (Exception e) {
            throw handleException(e);
        }

        return null;
    }

    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(),
                    new Function<ActivityTask, ActivityTask>() {
                        @Override
                        public ActivityTask apply(final ActivityTask 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(),
                        new Function<ActivityTask, Pair<String, String>>() {
                            @Override
                            public Pair<String, String> apply(final ActivityTask 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.<Domain>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.<Domain>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 = new Callable<DecisionTask>() {
            @Override
            public DecisionTask call() throws Exception {
                final List<WorkflowExecution> pending = workflowExecutions.listByExample(
                        WorkflowExecution.exampleWithPendingDecision(accountFullName, domain, taskList), accessible,
                        Functions.<WorkflowExecution>identity());
                Collections.sort(pending, Ordering.natural().onResultOf(AbstractPersistentSupport.creation()));
                DecisionTask decisionTask = null;
                for (final WorkflowExecution execution : pending) {
                    if (decisionTask != null)
                        break;
                    boolean retry = true;
                    while (retry)
                        try (final WorkflowLock lock = WorkflowLock.lock(accountFullName, execution.getDomainUuid(),
                                execution.getDisplayName())) {
                            retry = false;
                            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(
                                                                Objects.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, will retry.");
                                Thread.sleep(10);
                                retry = true;
                            } else {
                                logger.error(
                                        "Error taking decision task for workflow " + execution.getDisplayName(), e);
                            }
                        }
                }
                return decisionTask;
            }
        };

        try {
            handleTaskPolling(accountFullName, domain, "decision", taskList, request.getCorrelationId(),
                    new DecisionTask(), taskCallable);
        } catch (Exception e) {
            throw handleException(e);
        }

        return null;
    }

    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.<Domain>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(new Supplier<Long>() {
                                                @Override
                                                public Long get() {
                                                    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.<Timer>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":
                                                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(),
                                                            new Function<ActivityTask, Void>() {
                                                                @Override
                                                                public Void apply(final ActivityTask 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.<ActivityType>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 "StartTimer":
                                                final StartTimerDecisionAttributes startTimer = decision
                                                        .getStartTimerDecisionAttributes();
                                                try {
                                                    if (!timers.listByExample(
                                                            Timer.exampleWithTimerId(accountFullName,
                                                                    workflowExecution.getDomainName(),
                                                                    workflowExecution.getDisplayName(),
                                                                    startTimer.getTimerId()),
                                                            Predicates.alwaysTrue(), Functions.<Timer>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(), new Function<WorkflowExecution, Pair<String, String>>() {
                            @Override
                            public Pair<String, String> apply(final WorkflowExecution 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(), new Function<WorkflowExecution, Pair<String, String>>() {
                            @Override
                            public Pair<String, String> apply(final WorkflowExecution 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(),
                        new Function<WorkflowExecution, Void>() {
                            @Override
                            public Void apply(final WorkflowExecution workflowExecution) {
                                if (accessible.apply(workflowExecution)) {
                                    workflowExecution
                                            .closeWorkflow(WorkflowExecution.CloseStatus.Terminated,
                                                    WorkflowHistoryEvent
                                                            .create(workflowExecution,
                                                                    new WorkflowExecutionTerminatedEventAttributes()
                                                                            .withChildPolicy(Objects.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,
                    new Function<WorkflowExecution, History>() {
                        @Override
                        public History apply(final WorkflowExecution workflowExecution) {
                            final List<WorkflowHistoryEvent> events = workflowExecution.getWorkflowHistory();
                            final List<WorkflowHistoryEvent> reverseEvents = Lists.reverse(events);
                            return new History().withEvents(Collections2.transform(
                                    Objects.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);
        }
    }

    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 Objects.firstNonNull(value, defaultValue);
    }

    private static void notifyTaskList(final AccountFullName accountFullName, final String domain,
            final String type, final String taskList) {
        NotifyClient.notifyTaskList(accountFullName, domain, type, taskList);
    }

    private static void handleTaskPolling(final AccountFullName accountFullName, final String domain,
            final String type, final String taskList, final String correlationId,
            final SimpleWorkflowMessage emptyResponse,
            final Callable<? extends SimpleWorkflowMessage> responseCallable) {
        final String list = Joiner.on('/').join(type, domain, taskList);
        try {
            NotifyClient.pollTaskList(accountFullName, domain, type, taskList,
                    Contexts.consumerWithCurrentContext(new Consumer<Boolean>() {
                        @Override
                        public void accept(final Boolean notified) {
                            try {
                                if (notified) {
                                    final SimpleWorkflowMessage taskResponse = responseCallable.call();
                                    if (taskResponse != null) {
                                        taskResponse.setCorrelationId(correlationId);
                                        Contexts.response(taskResponse);
                                        return;
                                    }
                                }
                            } 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);
                            }
                            emptyResponse.setCorrelationId(correlationId);
                            Contexts.response(emptyResponse);
                        }
                    }));
        } catch (Exception e) {
            logger.error("Error polling for task " + list, e);
            emptyResponse.setCorrelationId(correlationId);
            Contexts.response(emptyResponse);
        }
    }

    private static final class ScheduleActivityTaskException extends Exception {
        private static final long serialVersionUID = 1L;

        private final ScheduleActivityTaskFailedCause failedCause;

        public ScheduleActivityTaskException(final ScheduleActivityTaskFailedCause failedCause) {
            this.failedCause = failedCause;
        }

        public ScheduleActivityTaskFailedCause getFailedCause() {
            return failedCause;
        }
    }

    private static final class StartTimerException extends Exception {
        private static final long serialVersionUID = 1L;

        private final StartTimerFailedCause failedCause;

        public StartTimerException(final StartTimerFailedCause failedCause) {
            this.failedCause = failedCause;
        }

        public StartTimerFailedCause getFailedCause() {
            return failedCause;
        }
    }
}