elsa-core/src/modules/Elsa.Workflows.Runtime/Implementations/TriggerIndexer.cs

198 lines
8.2 KiB
C#
Raw Normal View History

2022-01-04 08:42:12 +00:00
using System.Runtime.CompilerServices;
2022-01-14 12:54:17 +00:00
using System.Text.Json;
using Elsa.Expressions.Models;
using Elsa.Expressions.Services;
using Elsa.Mediator.Services;
using Elsa.Workflows.Core;
using Elsa.Workflows.Core.Helpers;
using Elsa.Workflows.Core.Models;
using Elsa.Workflows.Core.Services;
using Elsa.Workflows.Management.Entities;
using Elsa.Workflows.Runtime.Comparers;
using Elsa.Workflows.Runtime.Entities;
using Elsa.Workflows.Runtime.Models;
using Elsa.Workflows.Runtime.Notifications;
using Elsa.Workflows.Runtime.Services;
2022-01-04 08:42:12 +00:00
using Microsoft.Extensions.Logging;
using Open.Linq.AsyncExtensions;
2022-01-04 08:42:12 +00:00
namespace Elsa.Workflows.Runtime.Implementations;
2022-01-04 08:42:12 +00:00
public class TriggerIndexer : ITriggerIndexer
{
2022-03-04 20:42:27 +00:00
private readonly IActivityWalker _activityWalker;
private readonly IWorkflowDefinitionService _workflowDefinitionService;
2022-01-04 08:42:12 +00:00
private readonly IExpressionEvaluator _expressionEvaluator;
2022-01-14 12:54:17 +00:00
private readonly IIdentityGenerator _identityGenerator;
private readonly ITriggerStore _triggerStore;
2022-01-14 12:54:17 +00:00
private readonly IEventPublisher _eventPublisher;
private readonly IServiceProvider _serviceProvider;
private readonly IBookmarkHasher _hasher;
2022-01-04 08:42:12 +00:00
private readonly ILogger _logger;
public TriggerIndexer(
2022-03-04 20:42:27 +00:00
IActivityWalker activityWalker,
IWorkflowDefinitionService workflowDefinitionService,
2022-01-04 08:42:12 +00:00
IExpressionEvaluator expressionEvaluator,
2022-01-14 12:54:17 +00:00
IIdentityGenerator identityGenerator,
ITriggerStore triggerStore,
2022-01-14 12:54:17 +00:00
IEventPublisher eventPublisher,
IServiceProvider serviceProvider,
IBookmarkHasher hasher,
2022-01-04 08:42:12 +00:00
ILogger<TriggerIndexer> logger)
{
2022-03-04 20:42:27 +00:00
_activityWalker = activityWalker;
2022-01-04 08:42:12 +00:00
_expressionEvaluator = expressionEvaluator;
2022-01-14 12:54:17 +00:00
_identityGenerator = identityGenerator;
_triggerStore = triggerStore;
2022-01-14 12:54:17 +00:00
_eventPublisher = eventPublisher;
_serviceProvider = serviceProvider;
2022-01-04 08:42:12 +00:00
_hasher = hasher;
_logger = logger;
_workflowDefinitionService = workflowDefinitionService;
2022-01-04 08:42:12 +00:00
}
public async Task<IndexedWorkflowTriggers> IndexTriggersAsync(WorkflowDefinition definition, CancellationToken cancellationToken = default)
2022-01-04 08:42:12 +00:00
{
var workflow = await _workflowDefinitionService.MaterializeWorkflowAsync(definition, cancellationToken);
return await IndexTriggersAsync(workflow, cancellationToken);
2022-01-04 08:42:12 +00:00
}
public async Task<IndexedWorkflowTriggers> IndexTriggersAsync(Workflow workflow, CancellationToken cancellationToken = default)
2022-01-04 08:42:12 +00:00
{
2022-02-22 11:51:59 +00:00
// Get current triggers
var currentTriggers = await GetCurrentTriggersAsync(workflow.Identity.DefinitionId, cancellationToken).ToList();
2022-02-22 11:51:59 +00:00
// Collect new triggers **if workflow is published**.
var newTriggers = workflow.Publication.IsPublished
? await GetTriggersAsync(workflow, cancellationToken).ToListAsync(cancellationToken)
: new List<StoredTrigger>(0);
2022-02-22 11:51:59 +00:00
// Diff triggers.
2022-04-07 09:22:44 +00:00
var diff = Diff.For(currentTriggers, newTriggers, new WorkflowTriggerHashEqualityComparer());
2022-01-04 08:42:12 +00:00
// Replace triggers for the specified workflow.
await _triggerStore.ReplaceAsync(diff.Removed, diff.Added, cancellationToken);
2022-02-22 11:51:59 +00:00
2022-04-07 09:22:44 +00:00
var indexedWorkflow = new IndexedWorkflowTriggers(workflow, diff.Added, diff.Removed, diff.Unchanged);
// Publish event.
2022-02-22 11:51:59 +00:00
await _eventPublisher.PublishAsync(new WorkflowTriggersIndexed(indexedWorkflow), cancellationToken);
return indexedWorkflow;
2022-01-04 08:42:12 +00:00
}
private async Task<IEnumerable<StoredTrigger>> GetCurrentTriggersAsync(string workflowDefinitionId, CancellationToken cancellationToken) =>
await _triggerStore.FindManyByWorkflowDefinitionIdAsync(workflowDefinitionId, cancellationToken);
2022-02-22 11:51:59 +00:00
private async IAsyncEnumerable<StoredTrigger> GetTriggersAsync(Workflow workflow, [EnumeratorCancellation] CancellationToken cancellationToken = default)
2022-01-04 08:42:12 +00:00
{
2022-03-07 11:01:27 +00:00
var context = new WorkflowIndexingContext(workflow, cancellationToken);
var nodes = await _activityWalker.WalkAsync(workflow.Root, cancellationToken);
2022-03-07 11:01:27 +00:00
// Get a list of activities that are configured as "startable".
var startableNodes = nodes
2022-03-07 11:01:27 +00:00
.Flatten()
.Where(x => x.Activity.CanStartWorkflow)
.ToList();
// For each startable node, create triggers.
foreach (var node in startableNodes)
2022-01-04 08:42:12 +00:00
{
2022-03-07 14:11:03 +00:00
var triggers = await GetTriggersAsync(context, node.Activity);
2022-01-04 08:42:12 +00:00
foreach (var trigger in triggers)
yield return trigger;
}
}
private async Task<IEnumerable<StoredTrigger>> GetTriggersAsync(WorkflowIndexingContext context, IActivity activity)
2022-03-07 11:01:27 +00:00
{
// If the activity implements ITrigger, request its trigger data. Otherwise.
2022-03-07 11:01:27 +00:00
if (activity is ITrigger trigger)
return await CreateWorkflowTriggersAsync(context, trigger);
// Else, create a single workflow trigger with no additional data.
var simpleTrigger = CreateWorkflowTrigger(context, activity);
return new[] { simpleTrigger };
}
private StoredTrigger CreateWorkflowTrigger(WorkflowIndexingContext context, IActivity activity)
2022-03-07 11:01:27 +00:00
{
var workflow = context.Workflow;
return new StoredTrigger
2022-03-07 11:01:27 +00:00
{
Id = _identityGenerator.GenerateId(),
WorkflowDefinitionId = workflow.Identity.DefinitionId,
Name = activity.Type,
ActivityId = activity.Id
2022-03-07 11:01:27 +00:00
};
}
private async Task<ICollection<StoredTrigger>> CreateWorkflowTriggersAsync(WorkflowIndexingContext context, ITrigger trigger)
2022-03-07 11:01:27 +00:00
{
var workflow = context.Workflow;
var cancellationToken = context.CancellationToken;
var expressionExecutionContext = await CreateExpressionExecutionContextAsync(context, trigger);
var triggerIndexingContext = new TriggerIndexingContext(context, expressionExecutionContext, trigger, cancellationToken);
var triggerData = await TryGetTriggerDataAsync(trigger, triggerIndexingContext);
2022-08-11 17:29:32 +00:00
var triggerTypeName = trigger.Type;
2022-03-07 11:01:27 +00:00
var triggers = triggerData.Select(x => new StoredTrigger
2022-03-07 11:01:27 +00:00
{
Id = _identityGenerator.GenerateId(),
WorkflowDefinitionId = workflow.Identity.DefinitionId,
Name = triggerTypeName,
ActivityId = trigger.Id,
Hash = _hasher.Hash(triggerTypeName, x),
2022-03-07 11:01:27 +00:00
Data = JsonSerializer.Serialize(x)
});
2022-03-08 13:40:47 +00:00
2022-03-07 11:01:27 +00:00
return triggers.ToList();
}
private async Task<ExpressionExecutionContext> CreateExpressionExecutionContextAsync(WorkflowIndexingContext context, ITrigger trigger)
2022-01-04 08:42:12 +00:00
{
var inputs = trigger.GetInputs();
2022-06-14 13:56:41 +00:00
var assignedInputs = inputs.Where(x => x.MemoryBlockReference != null!).ToList();
2022-01-04 08:42:12 +00:00
var register = context.GetOrCreateRegister(trigger);
2022-03-07 11:01:27 +00:00
var cancellationToken = context.CancellationToken;
2022-03-22 13:33:15 +00:00
var expressionInput = new Dictionary<string, object>();
var transientProperties = new Dictionary<object, object>();
var applicationProperties = ExpressionExecutionContextExtensions.CreateApplicationPropertiesFrom(context.Workflow, transientProperties, expressionInput);
var expressionExecutionContext = new ExpressionExecutionContext(_serviceProvider, register, default, applicationProperties, cancellationToken);
2022-01-04 08:42:12 +00:00
2022-03-07 11:01:27 +00:00
// Evaluate activity inputs before requesting trigger data.
2022-01-04 08:42:12 +00:00
foreach (var input in assignedInputs)
{
2022-06-14 13:56:41 +00:00
var locationReference = input.MemoryBlockReference;
2022-01-11 21:04:02 +00:00
try
{
var value = await _expressionEvaluator.EvaluateAsync(input, expressionExecutionContext);
locationReference.Set(expressionExecutionContext, value);
}
catch (Exception e)
{
_logger.LogWarning(e, "Failed to evaluate '{@Expression}'", input.Expression);
}
2022-01-04 08:42:12 +00:00
}
2022-03-07 11:01:27 +00:00
return expressionExecutionContext;
2022-01-04 08:42:12 +00:00
}
2022-01-11 21:04:02 +00:00
2022-03-07 11:01:27 +00:00
private async Task<ICollection<object>> TryGetTriggerDataAsync(ITrigger trigger, TriggerIndexingContext context)
2022-01-11 21:04:02 +00:00
{
try
{
return (await trigger.GetTriggerPayloadsAsync(context)).ToList();
2022-01-11 21:04:02 +00:00
}
catch (Exception e)
{
2022-03-07 11:01:27 +00:00
_logger.LogWarning(e, "Failed to get trigger data for activity {ActivityId}", trigger.Id);
2022-01-11 21:04:02 +00:00
}
return Array.Empty<object>();
2022-01-11 21:04:02 +00:00
}
2022-01-04 08:42:12 +00:00
}