Beispiel #1
0
        public async Task <OpenApiResult> HandleTrigger(IOpenApiContext context, IWorkflowTrigger body)
        {
            ITenant tenant = await this.marainServicesTenancy.GetRequestingTenantAsync(context.CurrentTenantId).ConfigureAwait(false);

            string delegatedTenantId = await this.marainServicesTenancy.GetDelegatedTenantIdForRequestingTenantAsync(tenant.Id).ConfigureAwait(false);

            var operationId = Guid.NewGuid();
            CreateOperationHeaders operationHeaders =
                await this.operationsControl.CreateOperationAsync(delegatedTenantId, operationId).ConfigureAwait(false);

            var envelope = new WorkflowMessageEnvelope(this.propertyBagFactory.Create(PropertyBagValues.Empty))
            {
                Trigger     = body,
                OperationId = operationId,
                TenantId    = context.CurrentTenantId,
            };

            var durableFunctionsOpenApiContext = (DurableFunctionsOpenApiContext)context;

            await durableFunctionsOpenApiContext.OrchestrationClient.StartNewWithCustomSerializationSettingsAsync(
                nameof(TriggerExecutionOrchestrator),
                operationId.ToString(),
                envelope,
                this.serializerSettingsProvider.Instance).ConfigureAwait(false);

            return(this.AcceptedResult(operationHeaders.Location));
        }
        /// <inheritdoc />
        public Task ExecuteAsync(WorkflowInstance instance, IWorkflowTrigger trigger)
        {
#pragma warning disable CA2254 // Template should be a static expression - the whole point of this action type is to allow user-defined log messages
            this.logger.LogDebug(BuildMessage(this.LogMessage, instance));
#pragma warning restore CA2254
            return(Task.CompletedTask);
        }
Beispiel #3
0
        /// <inheritdoc/>
        public Task <bool> EvaluateAsync(WorkflowInstance instance, IWorkflowTrigger trigger)
        {
            if (trigger is HostedWorkflowTrigger hostedTrigger)
            {
                string actualValue     = null;
                bool   parameterExists = hostedTrigger.Parameters?.TryGetValue(this.ParameterName, out actualValue) ?? false;

                switch (this.ParameterTestType)
                {
                case HostedWorkflowTriggerParameterTestType.Equals:
                    return(Task.FromResult(parameterExists && actualValue == this.ParameterValue));

                case HostedWorkflowTriggerParameterTestType.DoesNotEqual:
                    return(Task.FromResult(parameterExists && actualValue != this.ParameterValue));

                case HostedWorkflowTriggerParameterTestType.Exists:
                    return(Task.FromResult(parameterExists));

                case HostedWorkflowTriggerParameterTestType.DoesNotExist:
                    return(Task.FromResult(!parameterExists));
                }
            }

            return(Task.FromResult(false));
        }
 public async Task ExecuteAsync(WorkflowInstance instance, IWorkflowTrigger trigger)
 {
     if (trigger is EditCatalogItemTrigger patchTrigger)
     {
         await this.ExecuteAsync(instance, patchTrigger.PatchDetails);
     }
 }
        public Task ExecuteAsync(WorkflowInstance instance, IWorkflowTrigger trigger)
        {
            var newTrigger = new CreateCatalogItemTrigger {
                CatalogItemId = instance.Id
            };

            return(this.queue.EnqueueTriggerAsync(newTrigger, default(Guid)));
        }
Beispiel #6
0
        public Task ExecuteAsync(WorkflowInstance instance, IWorkflowTrigger trigger)
        {
            var log = GetTraces();

            log.Add(this.Message);

            return(Task.CompletedTask);
        }
Beispiel #7
0
        public Task <bool> EvaluateAsync(WorkflowInstance instance, IWorkflowTrigger trigger)
        {
            if (trigger is ICatalogItemTrigger itemTrigger)
            {
                instance.Context["CatalogItemId"] = itemTrigger.CatalogItemId;
            }

            return(Task.FromResult(true));
        }
        public async Task <bool> EvaluateAsync(WorkflowInstance instance, IWorkflowTrigger trigger)
        {
            if (trigger is EditCatalogItemTrigger editTrigger)
            {
                return(await this.EvaluateAsync(instance, editTrigger.PatchDetails).ConfigureAwait(false));
            }

            return(false);
        }
        public async Task <bool> EvaluateAsync(WorkflowInstance instance, IWorkflowTrigger trigger)
        {
            if (trigger is ICatalogItemTrigger patchTrigger)
            {
                return(await this.EvaluateAsync(instance, patchTrigger.CatalogItemId));
            }

            return(false);
        }
Beispiel #10
0
 /// <summary>
 /// Executes all of the actions in the supplied collection against the given
 /// <see cref="WorkflowInstance" /> with the context of the specified trigger.
 /// </summary>
 /// <param name="actions">The list of <see cref="IWorkflowAction" />s to execute.</param>
 /// <param name="instance">The <see cref="WorkflowInstance" /> to execute the actions against.</param>
 /// <param name="trigger">
 /// The <see cref="IWorkflowTrigger" /> that is being processed to cause these actions to be
 /// executed. It is possible for this parameter to be null if the actions being executed
 /// are the entry actions for the initial state of a new <see cref="WorkflowInstance" />.
 /// </param>
 /// <returns>A <see cref="Task" /> that will complete when all actions have been run.</returns>
 /// <remarks>
 /// The actions will be executed sequentially. If an action throws an
 /// exception, subsequent actions will not be executed.
 /// This is a helper method for internal use only. It is used to make code
 /// more readable when executing exit/transition/entry actions.
 /// </remarks>
 private async Task ExecuteAsync(
     IEnumerable <IWorkflowAction> actions,
     WorkflowInstance instance,
     IWorkflowTrigger trigger)
 {
     foreach (IWorkflowAction obj in actions)
     {
         await obj.ExecuteAsync(instance, trigger).ConfigureAwait(false);
     }
 }
Beispiel #11
0
        /// <inheritdoc />
        public Task <bool> EvaluateAsync(WorkflowInstance instance, IWorkflowTrigger trigger)
        {
            if (trigger is HostedWorkflowTrigger hostedTrigger)
            {
                bool result = hostedTrigger.TriggerName == this.TriggerName;
                return(Task.FromResult(result));
            }

            return(Task.FromResult(false));
        }
Beispiel #12
0
 /// <inheritdoc />
 /// <exception cref="InvalidOperationException">
 /// Thrown if the <see cref="FinishProcessing" /> method has been called.
 /// </exception>
 public Task EnqueueTriggerAsync(
     IWorkflowTrigger trigger,
     Guid operationId)
 {
     return(this.EnqueueMessageEnvelopeAsync(
                new WorkflowMessageEnvelope(this.propertyBagFactory.Create(PropertyBagValues.Empty))
     {
         Trigger = trigger,
     }));
 }
Beispiel #13
0
        public Task <bool> EvaluateAsync(WorkflowInstance instance, IWorkflowTrigger trigger)
        {
            foreach (var current in this.RequiredContextItems)
            {
                if (!instance.Context.ContainsKey(current))
                {
                    return(Task.FromResult(false));
                }
            }

            return(Task.FromResult(true));
        }
Beispiel #14
0
        /// <summary>
        /// The Process method is invoked asynchronously when <see cref="StartProcessing" /> is
        /// called. It will run on a background thread until <see cref="FinishProcessing" />
        /// is called.
        /// </summary>
        /// <returns>
        /// A <see cref="Task" /> that will complete after <see cref="FinishProcessing" /> is called.
        /// </returns>
        private async Task Process()
        {
            while (true)
            {
                if (this.queue.IsEmpty)
                {
                    if (this.shouldComplete)
                    {
                        break;
                    }

                    await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);

                    continue;
                }

                this.queue.TryPeek(out WorkflowMessageEnvelope item);

                IWorkflowInstanceStore instanceStore =
                    await this.workflowInstanceStoreFactory.GetWorkflowInstanceStoreForTenantAsync(this.tenantProvider.Root).ConfigureAwait(false);

                IWorkflowEngine engine =
                    await this.workflowEngineFactory.GetWorkflowEngineAsync(this.tenantProvider.Root).ConfigureAwait(false);

                if (item.IsTrigger)
                {
                    this.logger.LogInformation("Processing trigger with content type {ContentType}", item.ContentType);

                    IWorkflowTrigger trigger = item.Trigger;

                    IEnumerable <string> instanceIds = await instanceStore.GetMatchingWorkflowInstanceIdsForSubjectsAsync(
                        item.Trigger.GetSubjects(),
                        int.MaxValue,
                        0).ConfigureAwait(false);

                    foreach (string current in instanceIds)
                    {
                        await engine.ProcessTriggerAsync(trigger, current).ConfigureAwait(false);
                    }
                }
                else
                {
                    this.logger.LogInformation("Processing start workflow instance request");
                    await engine.StartWorkflowInstanceAsync(item.StartWorkflowInstanceRequest)
                    .ConfigureAwait(false);
                }

                this.queue.TryDequeue(out _);
            }
        }
        public async Task ExecuteAsync(WorkflowInstance instance, IWorkflowTrigger trigger)
        {
            var item = new CatalogItem
            {
                Id         = instance.Id,
                Identifier = instance.Context["Identifier"],
                Type       = instance.Context["Type"]
            };

            instance.Context.Remove("Identifier");
            instance.Context.Remove("Type");

            var repository = this.repositoryFactory.GetRepository();
            await repository.UpsertItemAsync(item).ConfigureAwait(false);
        }
Beispiel #16
0
        /// <inheritdoc />
        public Task <bool> EvaluateAsync(WorkflowInstance instance, IWorkflowTrigger trigger)
        {
            bool result = false;

            if (trigger is EntityIdTrigger etrigger)
            {
                if (instance.Context.TryGetValue(this.EntityIdContextProperty, out string value))
                {
                    result = value == etrigger.EntityId;
                }

                result = result && etrigger.Activity == this.Activity;
            }

            return(Task.FromResult(result));
        }
Beispiel #17
0
        /// <summary>
        /// Finds the first <see cref="WorkflowTransition"/> in the supplied
        /// list that can accept the specified <see cref="IWorkflowTrigger"/>.
        /// </summary>
        /// <param name="transitions">The transitions to evaluate.</param>
        /// <param name="instance">The <see cref="WorkflowInstance"/> to which the transition belongs.</param>
        /// <param name="trigger">The trigger that is currently being processed.</param>
        /// <returns>
        /// A <see cref="Task"/> whose result will be the first transition
        /// from the list that can accept the trigger. If none of the transitions
        /// can accept the trigger, null is returned.
        /// </returns>
        private async Task <WorkflowTransition> FindTransitionAsync(
            IEnumerable <WorkflowTransition> transitions,
            WorkflowInstance instance,
            IWorkflowTrigger trigger)
        {
            foreach (WorkflowTransition transition in transitions)
            {
                this.logger.LogDebug("Considering transition {TransitionId} [{TransitionDisplayName}]", transition.Id, transition.DisplayName);
                if (await this.CheckConditionsAsync(transition.Conditions, instance, trigger).ConfigureAwait(false))
                {
                    this.logger.LogDebug("Accepted transition {TransitionId} [{TransitionDisplayName}]", transition.Id, transition.DisplayName);
                    return(transition);
                }

                this.logger.LogDebug("Rejected transition {TransitionId} [{TransitionDisplayName}]", transition.Id, transition.DisplayName);
            }

            return(null);
        }
Beispiel #18
0
        /// <summary>
        /// Passes a trigger to an instance and returns the new state.
        /// </summary>
        /// <param name="instance">
        /// The instance that should process the trigger.
        /// </param>
        /// <param name="trigger">
        /// The trigger that should be processed.
        /// </param>
        /// <returns>
        /// A <see cref="Task"/> that completes when trigger processing has finished.
        /// </returns>
        /// <remarks>
        /// See the documentation for the <see cref="AcceptTriggerAsync(Workflow, WorkflowState, WorkflowInstance, IWorkflowTrigger)"/>
        /// for a full explanation of the processing steps.
        /// </remarks>
        private async Task <WorkflowTransition> AcceptTriggerAsync(
            WorkflowInstance instance,
            IWorkflowTrigger trigger)
        {
            Workflow workflow = await this.workflowStore.GetWorkflowAsync(instance.WorkflowId).ConfigureAwait(false);

            WorkflowState state = workflow.GetState(instance.StateId);

            if (instance.Status == WorkflowStatus.Faulted)
            {
                return(null);
            }

            if (instance.Status == WorkflowStatus.Complete)
            {
                return(null);
            }

            return(await this.AcceptTriggerAsync(workflow, state, instance, trigger).ConfigureAwait(false));
        }
Beispiel #19
0
        /// <summary>
        /// Evaluates all of the conditions in the supplied collection against the given
        /// <see cref="WorkflowInstance" /> with the context of the specified trigger.
        /// </summary>
        /// <param name="conditions">The list of <see cref="IWorkflowCondition" />s to evaluate.</param>
        /// <param name="instance">The <see cref="WorkflowInstance" /> to evaluate the conditions actions against.</param>
        /// <param name="trigger">
        /// The <see cref="IWorkflowTrigger" /> that is being processed to cause these conditions to be
        /// evaluated. It is possible for this parameter to be null if the conditions being evaluated
        /// are the entry conditions for the initial state of a new <see cref="WorkflowInstance" />.
        /// </param>
        /// <returns>A <see cref="Task" /> that will complete when all conditions have been evaluated.</returns>
        /// <remarks>
        /// The actions will be executed sequentially. If a condition is not met, remaining
        /// conditions will not be evaluated.
        /// This is a helper method for internal use only. It is used to make code
        /// more readable when evaluating exit/transition/entry conditions.
        /// </remarks>
        private async Task <bool> CheckConditionsAsync(
            IEnumerable <IWorkflowCondition> conditions,
            WorkflowInstance instance,
            IWorkflowTrigger trigger)
        {
            foreach (IWorkflowCondition condition in conditions)
            {
                string conditionName = condition.GetType().Name;
                this.logger.LogDebug("Evaluating condition {ConditionId} {ConditionName}", condition.Id, conditionName);

                if (!await condition.EvaluateAsync(instance, trigger).ConfigureAwait(false))
                {
                    this.logger.LogDebug("Condition false: {ConditionId} {ConditionName}", condition.Id, conditionName);
                    return(false);
                }

                this.logger.LogDebug("Condition true: {ConditionId} {ConditionName}", condition.Id, conditionName);
            }

            return(true);
        }
Beispiel #20
0
 /// <summary>
 /// Initializes a new instance of the <see cref="WorkflowInstanceTransitionCloudEventData"/> class.
 /// </summary>
 /// <param name="workflowInstanceId">The <see cref="WorkflowInstanceId"/>.</param>
 /// <param name="trigger">The (optional) <see cref="Trigger"/>.</param>
 public WorkflowInstanceTransitionCloudEventData(string workflowInstanceId, IWorkflowTrigger trigger = null)
 {
     this.WorkflowInstanceId = workflowInstanceId;
     this.Trigger            = trigger;
 }
Beispiel #21
0
        /// <inheritdoc />
        public Task <bool> EvaluateAsync(WorkflowInstance instance, IWorkflowTrigger trigger)
        {
            bool result = trigger.ContentType == this.TriggerContentType;

            return(Task.FromResult(result));
        }
Beispiel #22
0
 /// <inheritdoc/>
 public Task ProcessTriggerAsync(IWorkflowTrigger trigger, string workflowInstanceId, string partitionKey = null)
 {
     return(this.ProcessInstanceWithLeaseAsync(trigger, workflowInstanceId, partitionKey));
 }
Beispiel #23
0
        public async Task <OpenApiResult> HandleTrigger(string tenantId, string workflowInstanceId, IWorkflowTrigger body)
        {
            ITenant tenant = await this.marainServicesTenancy.GetRequestingTenantAsync(tenantId).ConfigureAwait(false);

            IWorkflowEngine workflowEngine = await this.workflowEngineFactory.GetWorkflowEngineAsync(tenant).ConfigureAwait(false);

            await workflowEngine.ProcessTriggerAsync(body, workflowInstanceId).ConfigureAwait(false);

            return(this.OkResult());
        }
Beispiel #24
0
        /// <summary>
        /// Retrieves a single <see cref="WorkflowInstance" /> and passes it the trigger
        /// to process.
        /// </summary>
        /// <param name="trigger">The trigger to process.</param>
        /// <param name="instanceId">The Id of the <see cref="WorkflowInstance" /> that will process the trigger.</param>
        /// <param name="partitionKey">The partition key for the instance. If not supplied, the Id will be used.</param>
        /// <returns>A <see cref="Task" /> that will complete when the instance has finished processing the trigger.</returns>
        /// <remarks>
        /// This method retrieves the workflow instance from storage, passes it the trigger
        /// and, if the instance has updated as a result of the trigger, puts it back in
        /// storage.
        /// </remarks>
        private async Task ProcessInstanceAsync(IWorkflowTrigger trigger, string instanceId, string partitionKey)
        {
            WorkflowInstance item     = null;
            Workflow         workflow = null;

            // We need to gather data for the final CloudEvent prior to applying the transition, as some of the data
            // we publish is pre-transition - e.g. previous state and context.
            var    workflowEventData = new WorkflowInstanceTransitionCloudEventData(instanceId, trigger);
            string workflowEventType = WorkflowEventTypes.TransitionCompleted;

            try
            {
                item = await this.workflowInstanceStore.GetWorkflowInstanceAsync(instanceId, partitionKey).ConfigureAwait(false);

                workflow = await this.workflowStore.GetWorkflowAsync(item.WorkflowId).ConfigureAwait(false);

                workflowEventData.PreviousContext = item.Context.ToDictionary(x => x.Key, x => x.Value);
                workflowEventData.WorkflowId      = item.WorkflowId;
                workflowEventData.PreviousState   = item.StateId;
                workflowEventData.PreviousStatus  = item.Status;

                this.logger.LogDebug("Accepting trigger {TriggerId} in instance {ItemId}", trigger.Id, item.Id);

                WorkflowTransition transition = await this.AcceptTriggerAsync(item, trigger).ConfigureAwait(false);

                workflowEventData.TransitionId = transition?.Id;
                workflowEventData.NewState     = item.StateId;
                workflowEventData.NewStatus    = item.Status;
                workflowEventData.NewContext   = item.Context;

                this.logger.LogDebug("Accepted trigger {TriggerId} in instance {ItemId}", trigger.Id, item.Id);
            }
            catch (WorkflowInstanceNotFoundException)
            {
                // Bubble this specific exception out as the caller needs to know that they sent through an
                // invalid workflow instance id.
                this.logger.LogError(
                    new EventId(0),
                    "Unable to locate the specified instance {InstanceId} for trigger {TriggerId}",
                    instanceId,
                    trigger.Id);

                throw;
            }
            catch (Exception ex)
            {
                this.logger.LogError(
                    new EventId(0),
                    ex,
                    "Error accepting trigger {TriggerId} in instance {ItemId}",
                    trigger.Id,
                    item?.Id);

                if (item != null)
                {
                    item.Status  = WorkflowStatus.Faulted;
                    item.IsDirty = true;
                    workflowEventData.NewStatus = item.Status;
                    workflowEventType           = WorkflowEventTypes.InstanceFaulted;
                }
            }
            finally
            {
                if (item?.IsDirty == true)
                {
                    await this.workflowInstanceStore.UpsertWorkflowInstanceAsync(item, partitionKey).ConfigureAwait(false);

                    await this.cloudEventPublisher.PublishWorkflowEventDataAsync(
                        this.cloudEventSource,
                        workflowEventType,
                        item.Id,
                        workflowEventData.ContentType,
                        workflowEventData,
                        workflow.WorkflowEventSubscriptions).ConfigureAwait(false);
                }
            }
        }
Beispiel #25
0
 /// <summary>
 /// Wraps the <see cref="ProcessInstanceAsync" /> method to use the current <see cref="leaseProvider" />
 /// to take a lease on the workflow instance prior to processing.
 /// </summary>
 /// <param name="trigger">The trigger to process.</param>
 /// <param name="instanceId">The Id of the <see cref="WorkflowInstance" /> that will process the trigger.</param>
 /// <param name="partitionKey">The partition key for the instance. If not supplied, the Id will be used.</param>
 /// <returns>A <see cref="Task" /> that will complete when the instance has finished processing the trigger.</returns>
 /// <remarks>
 /// If another instance of WorkflowEngine already has a lease on the WorkflowInstance,
 /// this method will wait for the lease to become available.
 /// </remarks>
 private Task ProcessInstanceWithLeaseAsync(IWorkflowTrigger trigger, string instanceId, string partitionKey)
 {
     return(this.leaseProvider
            .ExecuteWithMutexAsync(_ => this.ProcessInstanceAsync(trigger, instanceId, partitionKey), instanceId));
 }
Beispiel #26
0
 public Task <bool> EvaluateAsync(WorkflowInstance instance, IWorkflowTrigger trigger)
 {
     return(Task.FromResult(trigger.Id == this.TriggerId));
 }
Beispiel #27
0
        /// <summary>
        /// Determines whether an <see cref="WorkflowState" /> is able to accept a trigger,
        /// and processes it if so.
        /// </summary>
        /// <param name="workflow">The workflow that is in operation.</param>
        /// <param name="state">The state that will process the trigger.</param>
        /// <param name="instance">The <see cref="WorkflowInstance" /> that is in this state.</param>
        /// <param name="trigger">The <see cref="IWorkflowTrigger" /> to process.</param>
        /// <returns>
        /// A <see cref="Task" /> that will complete when the trigger has been
        /// processed, or it is determined that the trigger can't be processed
        /// for the given <see cref="WorkflowInstance" /> and <see cref="WorkflowState" />.
        /// </returns>
        /// <remarks>
        /// <para>
        /// This method consists of three parts:
        /// <list type="bullet">
        ///     <item>
        ///         <description>
        ///             Determining if the supplied trigger can be accepted by the
        ///             <see cref="WorkflowInstance" /> in its current state.
        ///         </description>
        ///     </item>
        ///     <item>
        ///         <description>
        ///             Executing the actions required to move to the new state.
        ///         </description>
        ///     </item>
        ///     <item>
        ///         <description>
        ///             Updating the <see cref="WorkflowInstance" /> with the new state and
        ///             associated interests.
        ///         </description>
        ///     </item>
        /// </list>
        /// </para>
        /// <para>
        /// To determine the transition to use, the following steps are taken:
        /// <list type="bullet">
        ///     <item>
        ///         <description>
        ///             Evaluate the exit conditions of the current state (from
        ///             <see cref="WorkflowState.ExitConditions" />. If any conditions
        ///             evaluate to false, processing ends.
        ///         </description>
        ///     </item>
        ///     <item>
        ///         <description>
        ///             Iterate the <see cref="WorkflowState.Transitions" /> collection
        ///             and select the first <see cref="WorkflowTransition" /> whose
        ///             <see cref="WorkflowTransition.Conditions" /> all evaluate to true.
        ///             If no transitions match, then processing ends.
        ///         </description>
        ///     </item>
        ///     <item>
        ///         <description>
        ///             Retrieve the target state from the transition, and evaluate its
        ///             entry conditions (from <see cref="WorkflowState.EntryConditions" />.
        ///             If any conditions evaluate to false, processing ends.
        ///         </description>
        ///     </item>
        /// </list>
        /// </para>
        /// <para>
        /// Once it has been determined that the trigger can be processed, actions
        /// from the current state, transition and target state are executed in order:
        /// <list type="bullet">
        ///     <item>
        ///         <description>
        ///             The current state's <see cref="WorkflowState.ExitActions" />.
        ///         </description>
        ///     </item>
        ///     <item>
        ///         <description>
        ///             The transition's <see cref="WorkflowTransition.Actions" />
        ///         </description>
        ///     </item>
        ///     <item>
        ///         <description>
        ///             The target state's <see cref="WorkflowState.ExitActions" />.
        ///         </description>
        ///     </item>
        /// </list>
        /// </para>
        /// <para>
        /// Once all actions have been processed, the <see cref="WorkflowInstance" />s status
        /// is set back to <see cref="WorkflowStatus.Waiting" />, if the new current state
        /// contains any transitions. If the new current state doesn't contain any transitions,
        /// there is no way of leaving this state and the workflow status is set to
        /// <see cref="WorkflowStatus.Complete" />. Additionally, the <see cref="WorkflowInstance.IsDirty" />
        /// property is set to true to ensure that the instance is saved by the <see cref="IWorkflowEngine" />.
        /// </para>
        /// </remarks>
        private async Task <WorkflowTransition> AcceptTriggerAsync(
            Workflow workflow,
            WorkflowState state,
            WorkflowInstance instance,
            IWorkflowTrigger trigger)
        {
            bool exitAllowed = await this.CheckConditionsAsync(state.ExitConditions, instance, trigger).ConfigureAwait(false);

            if (!exitAllowed)
            {
                this.logger.LogDebug(
                    "Exit not permitted from state {StateId} [{StateDisplayName}] in instance {InstanceId} with trigger {TriggerId}",
                    state.Id,
                    state.DisplayName,
                    instance.Id,
                    trigger.Id);
                instance.Status = WorkflowStatus.Waiting;
                return(null);
            }

            WorkflowTransition transition = await this.FindTransitionAsync(state.Transitions, instance, trigger).ConfigureAwait(false);

            if (transition == null)
            {
                this.logger.LogDebug(
                    "No transition found from state {StateId} [{StateDisplayName}] in instance {InstanceId} with trigger {TriggerId}",
                    state.Id,
                    state.DisplayName,
                    instance.Id,
                    trigger.Id);
                instance.Status = WorkflowStatus.Waiting;
                return(transition);
            }

            WorkflowState targetState = workflow.GetState(transition.TargetStateId);

            this.logger.LogDebug(
                "Transition {TransitionId}  [{TransitionDisplayName}] found from state {StateId} [{StateDisplayName}] to state {TargetStateId} [{TargetStateDisplayName}] in instance {InstanceId} with trigger {TriggerId}",
                transition.Id,
                transition.DisplayName,
                state.Id,
                state.DisplayName,
                targetState.Id,
                targetState.DisplayName,
                instance.Id,
                trigger.Id);

            bool entryAllowed = await this.CheckConditionsAsync(targetState.EntryConditions, instance, trigger)
                                .ConfigureAwait(false);

            if (!entryAllowed)
            {
                this.logger.LogDebug(
                    "Entry not permitted into state {TargetStateId} [{TargetStateDisplayName}] in instance {InstanceId} with trigger {TriggerId}",
                    targetState.Id,
                    targetState.DisplayName,
                    instance.Id,
                    trigger.Id);
                instance.Status = WorkflowStatus.Waiting;
                return(transition);
            }

            this.logger.LogDebug(
                "Executing exit actions on transition {TransitionId} from state {StateId} [{StateDisplayName}] to state {TargetStateId} [{TargetStateDisplayName}] in instance {InstanceId} with trigger {TriggerId}",
                transition.Id,
                state.Id,
                state.DisplayName,
                targetState.Id,
                targetState.DisplayName,
                instance.Id,
                trigger.Id);

            await this.ExecuteAsync(state.ExitActions, instance, trigger).ConfigureAwait(false);

            this.logger.LogDebug(
                "Executing transition actions on transition {TransitionId} from state {StateId} [{StateDisplayName}] to state {TargetStateId} [{TargetStateDisplayName}] in instance {InstanceId} with trigger {TriggerId}",
                transition.Id,
                state.Id,
                state.DisplayName,
                targetState.Id,
                targetState.DisplayName,
                instance.Id,
                trigger.Id);
            await this.ExecuteAsync(transition.Actions, instance, trigger).ConfigureAwait(false);

            instance.Status = targetState.Transitions.Count == 0 ? WorkflowStatus.Complete : WorkflowStatus.Waiting;
            instance.SetState(targetState);

            this.logger.LogDebug(
                "Executing entry actions on transition {TransitionId} from state {StateId} [{StateDisplayName}] to state {TargetStateId} [{TargetStateDisplayName}] in instance {InstanceId} with trigger {TriggerId}",
                transition.Id,
                state.Id,
                state.DisplayName,
                targetState.Id,
                targetState.DisplayName,
                instance.Id,
                trigger.Id);
            await this.ExecuteAsync(targetState.EntryActions, instance, trigger).ConfigureAwait(false);

            // Then update the instance status, set the new state
            instance.IsDirty = true;
            return(transition);
        }