Added Service for searching through ActivityRegistry

Added Refreshing of registry on miss when searching
This commit is contained in:
Raymond den Haan 2024-05-15 11:06:22 +02:00 committed by raymonddenhaan
parent 2abe58fa10
commit b092d62bcc
29 changed files with 232 additions and 201 deletions

View file

@ -22,12 +22,12 @@ public interface IFunctionActivityRegistry
/// </summary>
/// <param name="descriptor">The descriptor that describes the function.</param>
void RegisterFunction(FunctionActivityDescriptor descriptor);
/// <summary>
/// Resolves a function to an activity that can be invoked from a DSL script.
/// </summary>
/// <param name="functionName">The name of the function.</param>
/// <param name="arguments">The arguments that are passed to the function.</param>
/// <returns>An activity that can be invoked from a DSL script.</returns>
IActivity ResolveFunction(string functionName, IEnumerable<object?>? arguments = default);
Task<IActivity> ResolveFunction(string functionName, IEnumerable<object?>? arguments = default);
}

View file

@ -27,7 +27,7 @@ public partial class WorkflowDefinitionBuilderInterpreter
var functionName = context.ID().GetText();
var argsNode = context.args();
var args = argsNode != null ? _argValues.Get(context.args()) : default;
var activity = _functionActivityRegistry.ResolveFunction(functionName, args);
var activity = _functionActivityRegistry.ResolveFunction(functionName, args).GetAwaiter().GetResult();
_expressionValue.Put(context, activity);
return DefaultResult;

View file

@ -29,7 +29,7 @@ public partial class WorkflowDefinitionBuilderInterpreter
/// <inheritdoc />
public override IWorkflowBuilder VisitObject(ElsaParser.ObjectContext context)
{
var @object = GetObject(context);
var @object = GetObject(context).GetAwaiter().GetResult();
_object.Put(context, @object);
_expressionValue.Put(context, @object);
@ -38,12 +38,12 @@ public partial class WorkflowDefinitionBuilderInterpreter
return DefaultResult;
}
private object GetObject(ElsaParser.ObjectContext context)
private async Task<object> GetObject(ElsaParser.ObjectContext context)
{
var objectTypeName = context.ID().GetText();
// First, check if the symbol matches an activity type.
var activityDescriptor = _activityRegistry.Find(x => x.Name == objectTypeName);
var activityDescriptor = await _activityRegistryLookup.Find(x => x.Name == objectTypeName);
if (activityDescriptor != null)
{

View file

@ -9,7 +9,7 @@ namespace Elsa.Dsl.Interpreters;
public partial class WorkflowDefinitionBuilderInterpreter : ElsaParserBaseVisitor<IWorkflowBuilder>
{
private readonly ITypeSystem _typeSystem;
private readonly IActivityRegistry _activityRegistry;
private readonly IActivityRegistryLookupService _activityRegistryLookup;
private readonly IFunctionActivityRegistry _functionActivityRegistry;
private readonly IWorkflowBuilder _workflowBuilder;
private readonly ParseTreeProperty<object> _object = new();
@ -22,12 +22,12 @@ public partial class WorkflowDefinitionBuilderInterpreter : ElsaParserBaseVisito
/// <inheritdoc />
public WorkflowDefinitionBuilderInterpreter(
ITypeSystem typeSystem,
IActivityRegistry activityRegistry,
IActivityRegistryLookupService activityRegistryLookup,
IFunctionActivityRegistry functionActivityRegistry,
IWorkflowBuilderFactory workflowBuilderFactory)
{
_typeSystem = typeSystem;
_activityRegistry = activityRegistry;
_activityRegistryLookup = activityRegistryLookup;
_functionActivityRegistry = functionActivityRegistry;
_workflowBuilder = workflowBuilderFactory.CreateBuilder();
}

View file

@ -10,7 +10,7 @@ namespace Elsa.Dsl.Services;
public class DslEngine : IDslEngine
{
private readonly ITypeSystem _typeSystem;
private readonly IActivityRegistry _activityRegistry;
private readonly IActivityRegistryLookupService _activityRegistryLookup;
private readonly IFunctionActivityRegistry _functionActivityRegistry;
private readonly IWorkflowBuilderFactory _workflowBuilderFactory;
@ -19,12 +19,12 @@ public class DslEngine : IDslEngine
/// </summary>
public DslEngine(
ITypeSystem typeSystem,
IActivityRegistry activityRegistry,
IActivityRegistryLookupService activityRegistryLookup,
IFunctionActivityRegistry functionActivityRegistry,
IWorkflowBuilderFactory workflowBuilderFactory)
{
_typeSystem = typeSystem;
_activityRegistry = activityRegistry;
_activityRegistryLookup = activityRegistryLookup;
_functionActivityRegistry = functionActivityRegistry;
_workflowBuilderFactory = workflowBuilderFactory;
}
@ -40,7 +40,7 @@ public class DslEngine : IDslEngine
var interpreter = new WorkflowDefinitionBuilderInterpreter(
_typeSystem,
_activityRegistry,
_activityRegistryLookup,
_functionActivityRegistry,
_workflowBuilderFactory);

View file

@ -12,15 +12,15 @@ namespace Elsa.Dsl.Services;
/// <inheritdoc />
public class FunctionActivityRegistry : IFunctionActivityRegistry
{
private readonly IActivityRegistry _activityRegistry;
private readonly IActivityRegistryLookupService _activityRegistryLookup;
private readonly IDictionary<string, FunctionActivityDescriptor> _dictionary = new Dictionary<string, FunctionActivityDescriptor>();
/// <summary>
/// Creates a new instance of the <see cref="FunctionActivityRegistry"/> class.
/// </summary>
public FunctionActivityRegistry(IActivityRegistry activityRegistry)
public FunctionActivityRegistry(IActivityRegistryLookupService activityRegistryLookup)
{
_activityRegistry = activityRegistry;
_activityRegistryLookup = activityRegistryLookup;
}
/// <inheritdoc />
@ -37,12 +37,12 @@ public class FunctionActivityRegistry : IFunctionActivityRegistry
}
/// <inheritdoc />
public IActivity ResolveFunction(string functionName, IEnumerable<object?>? arguments = default)
public async Task<IActivity> ResolveFunction(string functionName, IEnumerable<object?>? arguments = default)
{
if (!_dictionary.TryGetValue(functionName, out var descriptor))
throw new Exception($"Could not resolve function {functionName}. Did you forget to register it?");
var activityDescriptor = _activityRegistry.Find(x => x.Name == descriptor.ActivityTypeName || x.TypeName == descriptor.ActivityTypeName);
var activityDescriptor = await _activityRegistryLookup.Find(x => x.Name == descriptor.ActivityTypeName || x.TypeName == descriptor.ActivityTypeName);
if (activityDescriptor == null)
throw new Exception($"Could not find activity descriptor for activity type {descriptor.ActivityTypeName}");

View file

@ -100,7 +100,7 @@ public class JintJavaScriptEvaluator : IJavaScriptEvaluator
CreateWorkflowInputAccessors(engine, context);
// Create output getters for each activity.
CreateActivityOutputAccessors(engine, context);
await CreateActivityOutputAccessors(engine, context);
// Create argument getters for each argument.
foreach (var argument in options.Arguments)
@ -171,11 +171,11 @@ public class JintJavaScriptEvaluator : IJavaScriptEvaluator
}
}
private static void CreateActivityOutputAccessors(Engine engine, ExpressionExecutionContext context)
private static async Task CreateActivityOutputAccessors(Engine engine, ExpressionExecutionContext context)
{
var activityOutputs = context.GetActivityOutputs();
foreach (var activityOutput in activityOutputs)
await foreach (var activityOutput in activityOutputs)
foreach (var outputName in activityOutput.OutputNames)
engine.SetValue($"get{outputName}From{activityOutput.ActivityName}", (Func<object?>)(() => context.GetOutput(activityOutput.ActivityId, outputName)));
}

View file

@ -12,13 +12,13 @@ namespace Elsa.JavaScript.TypeDefinitions.Providers;
internal class ActivityOutputFunctionsDefinitionProvider : FunctionDefinitionProvider
{
private readonly IActivityVisitor _activityVisitor;
private readonly IActivityRegistry _activityRegistry;
private readonly IActivityRegistryLookupService _activityRegistryLookup;
private readonly IIdentityGraphService _identityGraphService;
public ActivityOutputFunctionsDefinitionProvider(IActivityVisitor activityVisitor, IActivityRegistry activityRegistry, IIdentityGraphService identityGraphService)
public ActivityOutputFunctionsDefinitionProvider(IActivityVisitor activityVisitor, IActivityRegistryLookupService activityRegistryLookup, IIdentityGraphService identityGraphService)
{
_activityVisitor = activityVisitor;
_activityRegistry = activityRegistry;
_activityRegistryLookup = activityRegistryLookup;
_identityGraphService = identityGraphService;
}
@ -29,12 +29,12 @@ internal class ActivityOutputFunctionsDefinitionProvider : FunctionDefinitionPro
var nodes = (await _activityVisitor.VisitAsync(workflow.Root, context.CancellationToken)).Flatten().Distinct().ToList();
// Ensure identities.
_identityGraphService.AssignIdentities(nodes);
await _identityGraphService.AssignIdentities(nodes);
var activitiesWithOutputs = nodes.GetActivitiesWithOutputs(_activityRegistry);
var activitiesWithOutputs = nodes.GetActivitiesWithOutputs(_activityRegistryLookup);
var definitions = new List<FunctionDefinition>();
foreach (var (activity, activityDescriptor) in activitiesWithOutputs)
await foreach (var (activity, activityDescriptor) in activitiesWithOutputs)
{
definitions.AddRange(from output in activityDescriptor.Outputs
select output.Name.Pascalize()

View file

@ -8,14 +8,14 @@ namespace Elsa.Workflows.Api.Endpoints.ActivityDescriptorOptions.Get;
internal class Get : ElsaEndpoint<Request, Response>
{
private readonly IServiceProvider _serviceProvider;
private readonly IActivityRegistry _registry;
private readonly IActivityRegistryLookupService _registryLookup;
private readonly IPropertyUIHandlerResolver _optionsResolver;
/// <inheritdoc />
public Get(IServiceProvider serviceProvider, IActivityRegistry registry, IPropertyUIHandlerResolver optionsResolver)
public Get(IServiceProvider serviceProvider, IActivityRegistryLookupService registryLookup, IPropertyUIHandlerResolver optionsResolver)
{
_serviceProvider = serviceProvider;
_registry = registry;
_registryLookup = registryLookup;
_optionsResolver = optionsResolver;
}
@ -29,7 +29,7 @@ internal class Get : ElsaEndpoint<Request, Response>
/// <inheritdoc />
public override async Task HandleAsync(Request request, CancellationToken cancellationToken)
{
var descriptor = request.Version == null ? _registry.Find(request.ActivityTypeName) : _registry.Find(request.ActivityTypeName, request.Version.Value);
var descriptor = request.Version == null ? await _registryLookup.Find(request.ActivityTypeName) : await _registryLookup.Find(request.ActivityTypeName, request.Version.Value);
if (descriptor == null)
await SendNotFoundAsync(cancellationToken);

View file

@ -8,12 +8,12 @@ namespace Elsa.Workflows.Api.Endpoints.ActivityDescriptors.Get;
[PublicAPI]
internal class Get : ElsaEndpoint<Request, ActivityDescriptor>
{
private readonly IActivityRegistry _registry;
private readonly IActivityRegistryLookupService _registryLookup;
/// <inheritdoc />
public Get(IActivityRegistry registry)
public Get(IActivityRegistryLookupService registryLookup)
{
_registry = registry;
_registryLookup = registryLookup;
}
/// <inheritdoc />
@ -26,7 +26,7 @@ internal class Get : ElsaEndpoint<Request, ActivityDescriptor>
/// <inheritdoc />
public override async Task HandleAsync(Request request, CancellationToken cancellationToken)
{
var descriptor = request.Version == null ? _registry.Find(request.TypeName) : _registry.Find(request.TypeName, request.Version.Value);
var descriptor = request.Version == null ? await _registryLookup.Find(request.TypeName) : await _registryLookup.Find(request.TypeName, request.Version.Value);
if (descriptor == null)
await SendNotFoundAsync(cancellationToken);

View file

@ -240,7 +240,7 @@ public class WorkflowBuilder : IWorkflowBuilder
await _activityRegistry.RegisterAsync(distinctActivityTypes, cancellationToken);
// Assign identities to all activities.
_identityGraphService.AssignIdentities(nodes);
await _identityGraphService.AssignIdentities(nodes);
// Give unnamed variables in each variable container a predictable name.
var variableContainers = nodes.Where(x => x.Activity is IVariableContainer).Select(x => (IVariableContainer)x.Activity).ToList();

View file

@ -60,6 +60,7 @@ public partial class WorkflowExecutionContext : IExecutionContext
ServiceProvider = serviceProvider;
SystemClock = serviceProvider.GetRequiredService<ISystemClock>();
ActivityRegistry = serviceProvider.GetRequiredService<IActivityRegistry>();
ActivityRegistryLookup = serviceProvider.GetRequiredService<IActivityRegistryLookupService>();
_hasher = serviceProvider.GetRequiredService<IHasher>();
SubStatus = WorkflowSubStatus.Pending;
Id = id;
@ -152,7 +153,7 @@ public partial class WorkflowExecutionContext : IExecutionContext
cancellationTokens);
var workflowStateExtractor = serviceProvider.GetRequiredService<IWorkflowStateExtractor>();
workflowStateExtractor.Apply(workflowExecutionContext, workflowState);
await workflowStateExtractor.Apply(workflowExecutionContext, workflowState);
return workflowExecutionContext;
}
@ -228,6 +229,11 @@ public partial class WorkflowExecutionContext : IExecutionContext
/// </summary>
public IActivityRegistry ActivityRegistry { get; }
/// <summary>
/// Gets the <see cref="IActivityRegistryLookupService"/>.
/// </summary>
public IActivityRegistryLookupService ActivityRegistryLookup { get; }
/// <summary>
/// Gets the workflow graph.
/// </summary>
@ -572,9 +578,9 @@ public partial class WorkflowExecutionContext : IExecutionContext
/// <summary>
/// Creates a new <see cref="ActivityExecutionContext"/> for the specified activity.
/// </summary>
public ActivityExecutionContext CreateActivityExecutionContext(IActivity activity, ActivityInvocationOptions? options = default)
public async Task<ActivityExecutionContext> CreateActivityExecutionContext(IActivity activity, ActivityInvocationOptions? options = default)
{
var activityDescriptor = ActivityRegistry.Find(activity) ?? throw new Exception($"Activity with type {activity.Type} not found in registry");
var activityDescriptor = await ActivityRegistryLookup.Find(activity) ?? throw new Exception($"Activity with type {activity.Type} not found in registry");
var tag = options?.Tag;
var parentContext = options?.Owner;
var parentExpressionExecutionContext = parentContext?.ExpressionExecutionContext ?? ExpressionExecutionContext;

View file

@ -14,13 +14,6 @@ public interface IActivityRegistry : IActivityProvider
/// <param name="descriptor">The activity descriptor to add.</param>
void Add(Type providerType, ActivityDescriptor descriptor);
/// <summary>
/// Adds multiple activity descriptors to the registry.
/// </summary>
/// <param name="providerType">The type of the activity provider.</param>
/// <param name="descriptors">The activity descriptors to add.</param>
void AddMany(Type providerType, IEnumerable<ActivityDescriptor> descriptors);
/// <summary>
/// Removes an activity descriptor from the registry.
/// </summary>
@ -28,17 +21,6 @@ public interface IActivityRegistry : IActivityProvider
/// <param name="descriptor">The activity descriptor to remove.</param>
void Remove(Type providerType, ActivityDescriptor descriptor);
/// <summary>
/// Clears all activity descriptors from the registry.
/// </summary>
void Clear();
/// <summary>
/// Clears all activity descriptors provided by the specified provider.
/// </summary>
/// <param name="providerType">The type of the activity provider.</param>
void ClearProvider(Type providerType);
/// <summary>
/// Returns all activity descriptors in the registry.
/// </summary>
@ -100,4 +82,12 @@ public interface IActivityRegistry : IActivityProvider
/// <param name="activityTypes">The activity types to register.</param>
/// <param name="cancellationToken">An optional cancellation token.</param>
Task RegisterAsync(IEnumerable<Type> activityTypes, CancellationToken cancellationToken = default);
/// <summary>
/// Refreshes the activity descriptors in the registry by querying the specified activity providers.
/// </summary>
/// <param name="activityProviders">The activity providers used to retrieve the descriptors.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task representing the asynchronous operation.</returns>
Task RefreshDescriptors(IEnumerable<IActivityProvider> activityProviders, CancellationToken cancellationToken = default);
}

View file

@ -0,0 +1,38 @@
using Elsa.Workflows.Models;
namespace Elsa.Workflows.Contracts;
/// <summary>
/// Represents a service for looking up activity descriptors.
/// </summary>
public interface IActivityRegistryLookupService
{
/// <summary>
/// Returns the activity descriptor for the specified activity type.
/// </summary>
/// <param name="type">The activity type.</param>
/// <returns>The activity descriptor for the specified activity type.</returns>
Task<ActivityDescriptor?> Find(string type);
/// <summary>
/// Returns the activity descriptor for the specified activity type and version.
/// </summary>
/// <param name="type">The activity type.</param>
/// <param name="version">The activity version.</param>
/// <returns>The activity descriptor for the specified activity type and version.</returns>
Task<ActivityDescriptor?> Find(string type, int version);
/// <summary>
/// Returns the activity descriptor matching the specified predicate.
/// </summary>
/// <param name="predicate">The predicate to match.</param>
/// <returns>The activity descriptor matching the specified predicate.</returns>
Task<ActivityDescriptor?> Find(Func<ActivityDescriptor, bool> predicate);
/// <summary>
/// Returns all activity descriptors matching the specified predicate.
/// </summary>
/// <param name="predicate">The predicate to match.</param>
/// <returns>All activity descriptors matching the specified predicate.</returns>
IEnumerable<ActivityDescriptor> FindMany(Func<ActivityDescriptor, bool> predicate);
}

View file

@ -21,17 +21,17 @@ public interface IIdentityGraphService
/// <summary>
/// Assign unique identities tot the specified <see cref="ActivityNode"/> graph.
/// </summary>
void AssignIdentities(ActivityNode root);
Task AssignIdentities(ActivityNode root);
/// <summary>
/// Assign unique identities tot the specified flattened list of <see cref="ActivityNode"/>s.
/// </summary>
void AssignIdentities(ICollection<ActivityNode> flattenedList);
Task AssignIdentities(ICollection<ActivityNode> flattenedList);
/// <summary>
/// Assign unique identities to input and output properties of the specified <see cref="IActivity"/> graph.
/// </summary>
void AssignInputOutputs(IActivity activity);
Task AssignInputOutputs(IActivity activity);
/// <summary>
/// Assign unique identities to variables of the specified <see cref="IVariableContainer"/>.

View file

@ -13,12 +13,12 @@ public interface IWorkflowStateExtractor
/// <param name="workflowExecutionContext">The workflow execution context to map.</param>
/// <returns>The mapped workflow state.</returns>
WorkflowState Extract(WorkflowExecutionContext workflowExecutionContext);
/// <summary>
/// Applies workflow state to a workflow execution context.
/// </summary>
/// <param name="workflowExecutionContext">The workflow execution context to map onto.</param>
/// <param name="state">The workflow state to apply.</param>
/// <returns>The updated workflow execution context.</returns>
WorkflowExecutionContext Apply(WorkflowExecutionContext workflowExecutionContext, WorkflowState state);
Task<WorkflowExecutionContext> Apply(WorkflowExecutionContext workflowExecutionContext, WorkflowState state);
}

View file

@ -138,24 +138,24 @@ public static class ActivityExecutionContextExtensions
/// <summary>
/// Evaluates a specific input property of the activity.
/// </summary>
public static Task<object?> EvaluateInputPropertyAsync(this ActivityExecutionContext context, string inputName)
public static async Task<object?> EvaluateInputPropertyAsync(this ActivityExecutionContext context, string inputName)
{
var activity = context.Activity;
var activityRegistry = context.GetRequiredService<IActivityRegistry>();
var activityDescriptor = activityRegistry.Find(activity.Type) ?? throw new Exception("Activity descriptor not found");
var activityRegistryLookup = context.GetRequiredService<IActivityRegistryLookupService>();
var activityDescriptor = await activityRegistryLookup.Find(activity.Type) ?? throw new Exception("Activity descriptor not found");
var inputDescriptor = activityDescriptor.GetWrappedInputPropertyDescriptor(activity, inputName);
if (inputDescriptor == null)
throw new Exception($"No input with name {inputName} could be found");
return EvaluateInputPropertyAsync(context, activityDescriptor, inputDescriptor);
return await EvaluateInputPropertyAsync(context, activityDescriptor, inputDescriptor);
}
/// <summary>
/// Returns a set of tuples containing the activity and its descriptor for all activities with outputs.
/// </summary>
/// <param name="activityExecutionContext">The <see cref="ActivityExecutionContext"/> being extended.</param>
public static IEnumerable<(IActivity Activity, ActivityDescriptor ActivityDescriptor)> GetActivitiesWithOutputs(this ActivityExecutionContext activityExecutionContext)
public static async IAsyncEnumerable<(IActivity Activity, ActivityDescriptor ActivityDescriptor)> GetActivitiesWithOutputs(this ActivityExecutionContext activityExecutionContext)
{
// Get current container.
var currentContainerNode = activityExecutionContext.FindParentWithVariableContainer()?.ActivityNode;
@ -168,28 +168,25 @@ public static class ActivityExecutionContextExtensions
var containedNodes = workflowExecutionContext.Nodes.Where(x => x.Parents.Contains(currentContainerNode)).Distinct().ToList();
// Select activities with outputs.
var activityRegistry = workflowExecutionContext.GetRequiredService<IActivityRegistry>();
var activityRegistry = workflowExecutionContext.GetRequiredService<IActivityRegistryLookupService>();
var activitiesWithOutputs = containedNodes.GetActivitiesWithOutputs(activityRegistry);
foreach (var (activity, activityDescriptor) in activitiesWithOutputs)
await foreach (var (activity, activityDescriptor) in activitiesWithOutputs)
yield return (activity, activityDescriptor);
}
/// <summary>
/// Returns a set of tuples containing the activity and its descriptor for all activities with outputs.
/// </summary>
public static IEnumerable<(IActivity Activity, ActivityDescriptor ActivityDescriptor)> GetActivitiesWithOutputs(this IEnumerable<ActivityNode> nodes, IActivityRegistry activityRegistry)
public static async IAsyncEnumerable<(IActivity activity, ActivityDescriptor activityDescriptor)> GetActivitiesWithOutputs(this IEnumerable<ActivityNode> nodes, IActivityRegistryLookupService activityRegistryLookup)
{
// Select activities with outputs.
var activitiesWithOutputs =
from node in nodes
let activity = node.Activity
let activityDescriptor = activityRegistry.Find(activity.Type, activity.Version)
where activityDescriptor.Outputs.Any()
select (activity, activityDescriptor);
foreach (var (activity, activityDescriptor) in activitiesWithOutputs)
yield return (activity, activityDescriptor);
foreach (var node in nodes)
{
var activity = node.Activity;
var activityDescriptor = await activityRegistryLookup.Find(activity.Type, activity.Version);
if (activityDescriptor != null && activityDescriptor.Outputs.Any())
yield return (activity, activityDescriptor);
}
}
/// <summary>

View file

@ -13,7 +13,7 @@ public static class ActivityRegistryExtensions
/// <summary>
/// Finds the activity descriptor for the specified activity.
/// </summary>
public static ActivityDescriptor? Find(this IActivityRegistry activityRegistry, IActivity activity) => activityRegistry.Find(activity.Type, activity.Version);
public static Task<ActivityDescriptor?> Find(this IActivityRegistryLookupService activityRegistry, IActivity activity) => activityRegistry.Find(activity.Type, activity.Version);
/// <summary>
/// Finds the activity descriptor for the specified activity type.

View file

@ -434,7 +434,7 @@ public static class ExpressionExecutionContextExtensions
/// <summary>
/// Returns all activity outputs.
/// </summary>
public static IEnumerable<ActivityOutputs> GetActivityOutputs(this ExpressionExecutionContext context)
public static async IAsyncEnumerable<ActivityOutputs> GetActivityOutputs(this ExpressionExecutionContext context)
{
if (!context.TryGetActivityExecutionContext(out var activityExecutionContext))
yield break;
@ -445,7 +445,7 @@ public static class ExpressionExecutionContextExtensions
if (useActivityName)
activitiesWithOutputs = activitiesWithOutputs.Where(x => !string.IsNullOrWhiteSpace(x.Activity.Name));
foreach (var activityWithOutput in activitiesWithOutputs)
await foreach (var activityWithOutput in activitiesWithOutputs)
{
var activity = activityWithOutput.Activity;
var activityDescriptor = activityWithOutput.ActivityDescriptor;

View file

@ -136,6 +136,7 @@ public class WorkflowsFeature : FeatureBase
.AddScoped<IBookmarkPayloadSerializer>(sp => ActivatorUtilities.CreateInstance<BookmarkPayloadSerializer>(sp))
.AddSingleton<IActivityDescriber, ActivityDescriber>()
.AddSingleton<IActivityRegistry, ActivityRegistry>()
.AddScoped<IActivityRegistryLookupService, ActivityRegistryLookupService>()
.AddSingleton<IPropertyDefaultValueResolver, PropertyDefaultValueResolver>()
.AddSingleton<IPropertyUIHandlerResolver, PropertyUIHandlerResolver>()
.AddSingleton<IActivityFactory, ActivityFactory>()

View file

@ -30,7 +30,7 @@ public class ActivityInvoker : IActivityInvoker
if (activityExecutionContext == null)
{
// Create a new activity execution context.
activityExecutionContext = workflowExecutionContext.CreateActivityExecutionContext(activity, options);
activityExecutionContext = await workflowExecutionContext.CreateActivityExecutionContext(activity, options);
// Add the activity context to the workflow context.
workflowExecutionContext.AddActivityExecutionContext(activityExecutionContext);

View file

@ -7,37 +7,15 @@ using Microsoft.Extensions.Logging;
namespace Elsa.Workflows.Services;
/// <inheritdoc />
public class ActivityRegistry : IActivityRegistry
public class ActivityRegistry(IActivityDescriber activityDescriber, IEnumerable<IActivityDescriptorModifier> modifiers, ILogger<ActivityRegistry> logger) : IActivityRegistry
{
private readonly IActivityDescriber _activityDescriber;
private readonly IEnumerable<IActivityDescriptorModifier> _modifiers;
private readonly ILogger _logger;
private readonly ISet<ActivityDescriptor> _manualActivityDescriptors = new HashSet<ActivityDescriptor>();
private readonly ConcurrentDictionary<Type, ICollection<ActivityDescriptor>> _providedActivityDescriptors = new();
private readonly ConcurrentDictionary<(string Type, int Version), ActivityDescriptor> _activityDescriptors = new();
/// <summary>
/// Initializes a new instance of the <see cref="ActivityRegistry"/> class.
/// </summary>
public ActivityRegistry(IActivityDescriber activityDescriber, IEnumerable<IActivityDescriptorModifier> modifiers, ILogger<ActivityRegistry> logger)
{
_activityDescriber = activityDescriber;
_modifiers = modifiers;
_logger = logger;
}
private ConcurrentDictionary<Type, ICollection<ActivityDescriptor>> _providedActivityDescriptors = new();
private ConcurrentDictionary<(string Type, int Version), ActivityDescriptor> _activityDescriptors = new();
/// <inheritdoc />
public void Add(Type providerType, ActivityDescriptor descriptor) => Add(descriptor, GetOrCreateDescriptors(providerType));
/// <inheritdoc />
public void AddMany(Type providerType, IEnumerable<ActivityDescriptor> descriptors)
{
var target = GetOrCreateDescriptors(providerType);
foreach (var descriptor in descriptors)
Add(descriptor, target);
}
/// <inheritdoc />
public void Remove(Type providerType, ActivityDescriptor descriptor)
{
@ -45,24 +23,6 @@ public class ActivityRegistry : IActivityRegistry
_activityDescriptors.Remove((descriptor.TypeName, descriptor.Version), out _);
}
/// <inheritdoc />
public void Clear()
{
_activityDescriptors.Clear();
_providedActivityDescriptors.Clear();
}
/// <inheritdoc />
public void ClearProvider(Type providerType)
{
var descriptors = ListByProvider(providerType).ToList();
foreach (var descriptor in descriptors)
_activityDescriptors.Remove((descriptor.TypeName, descriptor.Version), out _);
_providedActivityDescriptors.Remove(providerType, out _);
}
/// <inheritdoc />
public IEnumerable<ActivityDescriptor> ListAll() => _activityDescriptors.Values;
@ -95,7 +55,7 @@ public class ActivityRegistry : IActivityRegistry
if (_activityDescriptors.Values.Any(x => x.TypeName == activityTypeName))
return;
var activityDescriptor = await _activityDescriber.DescribeActivityAsync(activityType, cancellationToken);
var activityDescriptor = await activityDescriber.DescribeActivityAsync(activityType, cancellationToken);
Add(GetType(), activityDescriptor);
_manualActivityDescriptors.Add(activityDescriptor);
}
@ -106,24 +66,51 @@ public class ActivityRegistry : IActivityRegistry
foreach (var activityType in activityTypes)
await RegisterAsync(activityType, cancellationToken);
}
/// <inheritdoc />
public ValueTask<IEnumerable<ActivityDescriptor>> GetDescriptorsAsync(CancellationToken cancellationToken = default) => new(_manualActivityDescriptors);
public async Task RefreshDescriptors(IEnumerable<IActivityProvider> activityProviders, CancellationToken cancellationToken = default)
{
var providersDictionary = new ConcurrentDictionary<Type, ICollection<ActivityDescriptor>>();
var activityDescriptors = new ConcurrentDictionary<(string Type, int Version), ActivityDescriptor>();
foreach (IActivityProvider activityProvider in activityProviders)
{
var descriptors = (await activityProvider.GetDescriptorsAsync(cancellationToken)).ToList();
var providerDescriptors = new List<ActivityDescriptor>();
providersDictionary[activityProvider.GetType()] = providerDescriptors;
foreach (var descriptor in descriptors)
{
Add(descriptor, activityDescriptors, providerDescriptors);
}
}
Interlocked.Exchange(ref _activityDescriptors, activityDescriptors);
Interlocked.Exchange(ref _providedActivityDescriptors, providersDictionary);
}
private void Add(ActivityDescriptor descriptor, ICollection<ActivityDescriptor> target)
{
foreach (var modifier in _modifiers)
Add(descriptor, _activityDescriptors, target);
}
private void Add(ActivityDescriptor descriptor, ConcurrentDictionary<(string Type, int Version), ActivityDescriptor> activityDescriptors, ICollection<ActivityDescriptor> providerDescriptors)
{
foreach (var modifier in modifiers)
modifier.Modify(descriptor);
// If the descriptor already exists, replace it. But log a warning.
if (_activityDescriptors.TryGetValue((descriptor.TypeName, descriptor.Version), out var existingDescriptor))
if (activityDescriptors.TryGetValue((descriptor.TypeName, descriptor.Version), out var existingDescriptor))
{
// Remove the existing descriptor from the target collection.
target.Remove(existingDescriptor);
// Remove the existing descriptor from the providerDescriptors collection.
providerDescriptors.Remove(existingDescriptor);
// Log a warning.
_logger.LogWarning("Activity descriptor {ActivityType} v{ActivityVersion} was already registered. Replacing with new descriptor", descriptor.TypeName, descriptor.Version);
logger.LogWarning("Activity descriptor {ActivityType} v{ActivityVersion} was already registered. Replacing with new descriptor", descriptor.TypeName, descriptor.Version);
}
_activityDescriptors[(descriptor.TypeName, descriptor.Version)] = descriptor;
target.Add(descriptor);
activityDescriptors[(descriptor.TypeName, descriptor.Version)] = descriptor;
providerDescriptors.Add(descriptor);
}
private ICollection<ActivityDescriptor> GetOrCreateDescriptors(Type provider)
@ -136,7 +123,4 @@ public class ActivityRegistry : IActivityRegistry
return descriptors;
}
/// <inheritdoc />
public ValueTask<IEnumerable<ActivityDescriptor>> GetDescriptorsAsync(CancellationToken cancellationToken = default) => new(_manualActivityDescriptors);
}

View file

@ -0,0 +1,44 @@
using Elsa.Workflows.Contracts;
using Elsa.Workflows.Models;
namespace Elsa.Workflows.Services;
/// <summary>
/// Represents a service used to lookup activity descriptors in the activity registry.
/// </summary>
public class ActivityRegistryLookupService(IActivityRegistry activityRegistry, IEnumerable<IActivityProvider> providers) : IActivityRegistryLookupService
{
/// <inheritdoc />
public Task<ActivityDescriptor?> Find(string type)
{
return Find(() => activityRegistry.Find(type));
}
/// <inheritdoc />
public Task<ActivityDescriptor?> Find(string type, int version)
{
return Find(() => activityRegistry.Find(type, version));
}
/// <inheritdoc />
public Task<ActivityDescriptor?> Find(Func<ActivityDescriptor, bool> predicate)
{
return Find(() => activityRegistry.Find(predicate));
}
/// <inheritdoc />
public IEnumerable<ActivityDescriptor> FindMany(Func<ActivityDescriptor, bool> predicate)
{
return activityRegistry.FindMany(predicate);
}
private async Task<ActivityDescriptor?> Find(Func<ActivityDescriptor?> findPredicate)
{
var descriptor = findPredicate.Invoke();
if (descriptor is not null)
return descriptor;
await activityRegistry.RefreshDescriptors(providers);
return findPredicate.Invoke();
}
}

View file

@ -10,15 +10,15 @@ namespace Elsa.Workflows.Services;
public class IdentityGraphService : IIdentityGraphService
{
private readonly IActivityVisitor _activityVisitor;
private readonly IActivityRegistry _activityRegistry;
private readonly IActivityRegistryLookupService _activityRegistryLookup;
/// <summary>
/// Constructor.
/// </summary>
public IdentityGraphService(IActivityVisitor activityVisitor, IActivityRegistry activityRegistry)
public IdentityGraphService(IActivityVisitor activityVisitor, IActivityRegistryLookupService activityRegistryLookup)
{
_activityVisitor = activityVisitor;
_activityRegistry = activityRegistry;
_activityRegistryLookup = activityRegistryLookup;
}
/// <inheritdoc />
@ -31,14 +31,14 @@ public class IdentityGraphService : IIdentityGraphService
public async Task AssignIdentitiesAsync(IActivity root, CancellationToken cancellationToken = default)
{
var graph = await _activityVisitor.VisitAsync(root, cancellationToken);
AssignIdentities(graph);
await AssignIdentities(graph);
}
/// <inheritdoc />
public void AssignIdentities(ActivityNode root) => AssignIdentities(root.Flatten().ToList());
public Task AssignIdentities(ActivityNode root) => AssignIdentities(root.Flatten().ToList());
/// <inheritdoc />
public void AssignIdentities(ICollection<ActivityNode> flattenedList)
public async Task AssignIdentities(ICollection<ActivityNode> flattenedList)
{
var identityCounters = new Dictionary<string, int>();
@ -46,7 +46,7 @@ public class IdentityGraphService : IIdentityGraphService
{
node.Activity.Id = CreateId(node, identityCounters, flattenedList);
node.Activity.NodeId = node.NodeId;
AssignInputOutputs(node.Activity);
await AssignInputOutputs(node.Activity);
if (node.Activity is IVariableContainer variableContainer)
AssignVariables(variableContainer);
@ -54,9 +54,9 @@ public class IdentityGraphService : IIdentityGraphService
}
/// <inheritdoc />
public void AssignInputOutputs(IActivity activity)
public async Task AssignInputOutputs(IActivity activity)
{
var activityDescriptor = _activityRegistry.Find(activity.Type, activity.Version) ?? throw new Exception("Activity descriptor not found");
var activityDescriptor = await _activityRegistryLookup.Find(activity.Type, activity.Version) ?? throw new Exception("Activity descriptor not found");
var inputDictionary = activityDescriptor.GetWrappedInputProperties(activity);
foreach (var (inputName, input) in inputDictionary)

View file

@ -14,7 +14,7 @@ public class WorkflowGraphBuilder(IActivityVisitor activityVisitor, IIdentityGra
var graph = await activityVisitor.VisitAsync(workflow, cancellationToken);
var nodes = graph.Flatten().ToList();
identityGraphService.AssignIdentities(nodes);
await identityGraphService.AssignIdentities(nodes);
return new WorkflowGraph(workflow, graph, nodes);
}
}

View file

@ -41,7 +41,7 @@ public class WorkflowStateExtractor : IWorkflowStateExtractor
}
/// <inheritdoc />
public WorkflowExecutionContext Apply(WorkflowExecutionContext workflowExecutionContext, WorkflowState state)
public async Task<WorkflowExecutionContext> Apply(WorkflowExecutionContext workflowExecutionContext, WorkflowState state)
{
workflowExecutionContext.Id = state.Id;
workflowExecutionContext.CorrelationId = state.CorrelationId;
@ -55,7 +55,7 @@ public class WorkflowStateExtractor : IWorkflowStateExtractor
workflowExecutionContext.FinishedAt = state.FinishedAt;
ApplyInput(state, workflowExecutionContext);
ApplyProperties(state, workflowExecutionContext);
ApplyActivityExecutionContexts(state, workflowExecutionContext);
await ApplyActivityExecutionContexts(state, workflowExecutionContext);
ApplyCompletionCallbacks(state, workflowExecutionContext);
ApplyScheduledActivities(state, workflowExecutionContext);
return workflowExecutionContext;
@ -96,10 +96,10 @@ public class WorkflowStateExtractor : IWorkflowStateExtractor
workflowExecutionContext.Properties[property.Key] = property.Value;
}
private static void ApplyActivityExecutionContexts(WorkflowState state, WorkflowExecutionContext workflowExecutionContext)
private static async Task ApplyActivityExecutionContexts(WorkflowState state, WorkflowExecutionContext workflowExecutionContext)
{
var activityExecutionContexts = state.ActivityExecutionContexts
.Select(CreateActivityExecutionContext)
var activityExecutionContexts = (await Task.WhenAll(
state.ActivityExecutionContexts.Select(async item => await CreateActivityExecutionContext(item))))
.Where(x => x != null)
.Select(x => x!)
.ToList();
@ -128,7 +128,7 @@ public class WorkflowStateExtractor : IWorkflowStateExtractor
workflowExecutionContext.ActivityExecutionContexts = activityExecutionContexts;
return;
ActivityExecutionContext? CreateActivityExecutionContext(ActivityExecutionContextState activityExecutionContextState)
async Task<ActivityExecutionContext?> CreateActivityExecutionContext(ActivityExecutionContextState activityExecutionContextState)
{
var activity = workflowExecutionContext.FindActivityByNodeId(activityExecutionContextState.ScheduledActivityNodeId);
@ -137,7 +137,7 @@ public class WorkflowStateExtractor : IWorkflowStateExtractor
return null;
var properties = activityExecutionContextState.Properties;
var activityExecutionContext = workflowExecutionContext.CreateActivityExecutionContext(activity);
var activityExecutionContext = await workflowExecutionContext.CreateActivityExecutionContext(activity);
activityExecutionContext.Id = activityExecutionContextState.Id;
activityExecutionContext.Properties = properties;
activityExecutionContext.ActivityState = activityExecutionContextState.ActivityState ?? new Dictionary<string, object>();

View file

@ -45,7 +45,7 @@ public class WorkflowDefinitionActivity : Composite, IInitializable
/// <inheritdoc />
protected override async ValueTask ExecuteAsync(ActivityExecutionContext context)
{
CopyInputOutputToVariables(context);
await CopyInputOutputToVariables(context);
await context.ScheduleActivityAsync(Root, OnChildCompletedAsync);
}
@ -84,10 +84,10 @@ public class WorkflowDefinitionActivity : Composite, IInitializable
await activityExecutionContext.CompleteActivityAsync(completeCompositeSignal?.Value);
}
private void CopyInputOutputToVariables(ActivityExecutionContext context)
private async Task CopyInputOutputToVariables(ActivityExecutionContext context)
{
var serviceProvider = context.GetRequiredService<IServiceProvider>();
var activityDescriptor = FindActivityDescriptor(serviceProvider);
var activityDescriptor = await FindActivityDescriptor(serviceProvider);
DeclareInputAsVariables(activityDescriptor, (descriptor, variable) =>
{
@ -99,7 +99,7 @@ public class WorkflowDefinitionActivity : Composite, IInitializable
variable.Set(context, evaluatedExpression);
});
DeclareOutputAsVariables(activityDescriptor, (descriptor, variable) => context.ExpressionExecutionContext.Memory.Declare(variable));
DeclareOutputAsVariables(activityDescriptor, (_, variable) => context.ExpressionExecutionContext.Memory.Declare(variable));
}
private void DeclareInputAsVariables(ActivityDescriptor activityDescriptor, Action<InputDescriptor, Variable> configureVariable)
@ -165,10 +165,10 @@ public class WorkflowDefinitionActivity : Composite, IInitializable
return workflowGraph;
}
private ActivityDescriptor? FindActivityDescriptor(IServiceProvider serviceProvider)
private async Task<ActivityDescriptor?> FindActivityDescriptor(IServiceProvider serviceProvider)
{
var activityRegistry = serviceProvider.GetRequiredService<IActivityRegistry>();
return activityRegistry.Find(Type, Version) ?? activityRegistry.Find(Type);
var activityRegistryLookup = serviceProvider.GetRequiredService<IActivityRegistryLookupService>();
return await activityRegistryLookup.Find(Type, Version) ?? await activityRegistryLookup.Find(Type);
}
async ValueTask IInitializable.InitializeAsync(InitializationContext context)
@ -186,7 +186,7 @@ public class WorkflowDefinitionActivity : Composite, IInitializable
if (workflowGraph == null)
throw new Exception($"Could not find workflow definition with ID {WorkflowDefinitionId}.");
var activityDescriptor = FindActivityDescriptor(serviceProvider);
var activityDescriptor = await FindActivityDescriptor(serviceProvider);
if (activityDescriptor == null)
{

View file

@ -7,41 +7,18 @@ namespace Elsa.Workflows.Management.Services;
/// <summary>
/// Populates the <see cref="IActivityRegistry"/> with descriptors provided by <see cref="IActivityProvider"/>s.
/// </summary>
public class ActivityRegistryPopulator : IActivityRegistryPopulator
public class ActivityRegistryPopulator(IEnumerable<IActivityProvider> providers, IActivityRegistry registry) : IActivityRegistryPopulator
{
private readonly IEnumerable<IActivityProvider> _providers;
private readonly IActivityRegistry _registry;
/// <summary>
/// Constructor.
/// </summary>
public ActivityRegistryPopulator(IEnumerable<IActivityProvider> providers, IActivityRegistry registry)
{
_providers = providers;
_registry = registry;
}
/// <inheritdoc />
public async Task PopulateRegistryAsync(CancellationToken cancellationToken)
{
_registry.Clear();
foreach (var provider in _providers)
await PopulateRegistryAsync(provider, cancellationToken);
}
/// <inheritdoc />
public async Task PopulateRegistryAsync(Type providerType, CancellationToken cancellationToken = default)
{
_registry.ClearProvider(providerType);
var provider = _providers.First(x => x.GetType() == providerType);
await PopulateRegistryAsync(provider, cancellationToken);
await registry.RefreshDescriptors(providers, cancellationToken);
}
/// <inheritdoc />
public async Task AddToRegistry(Type providerType, string workflowDefinitionVersionId, CancellationToken cancellationToken = default)
{
var provider = _providers.First(x => x.GetType() == providerType);
var provider = providers.First(x => x.GetType() == providerType);
var descriptors = await provider.GetDescriptorsAsync(cancellationToken);
var descriptorToAdd = descriptors
.SingleOrDefault(d =>
@ -49,13 +26,13 @@ public class ActivityRegistryPopulator : IActivityRegistryPopulator
val.ToString() == workflowDefinitionVersionId);
if (descriptorToAdd is not null)
_registry.Add(providerType, descriptorToAdd);
registry.Add(providerType, descriptorToAdd);
}
/// <inheritdoc />
public void RemoveDefinitionFromRegistry(Type providerType, string workflowDefinitionId, CancellationToken cancellationToken = default)
{
var providerDescriptors = _registry.ListByProvider(providerType);
var providerDescriptors = registry.ListByProvider(providerType);
var descriptorsToRemove = providerDescriptors
.Where(d =>
@ -64,14 +41,14 @@ public class ActivityRegistryPopulator : IActivityRegistryPopulator
foreach (ActivityDescriptor activityDescriptor in descriptorsToRemove)
{
_registry.Remove(providerType, activityDescriptor);
registry.Remove(providerType, activityDescriptor);
}
}
/// <inheritdoc />
public void RemoveDefinitionVersionFromRegistry(Type providerType, string workflowDefinitionVersionId, CancellationToken cancellationToken = default)
{
var providerDescriptors = _registry.ListByProvider(providerType);
var providerDescriptors = registry.ListByProvider(providerType);
var descriptorToRemove = providerDescriptors
.SingleOrDefault(d =>
@ -79,12 +56,6 @@ public class ActivityRegistryPopulator : IActivityRegistryPopulator
val.ToString() == workflowDefinitionVersionId);
if (descriptorToRemove is not null)
_registry.Remove(providerType, descriptorToRemove);
}
private async Task PopulateRegistryAsync(IActivityProvider provider, CancellationToken cancellationToken = default)
{
var descriptors = await provider.GetDescriptorsAsync(cancellationToken);
_registry.AddMany(provider.GetType(), descriptors);
registry.Remove(providerType, descriptorToRemove);
}
}

View file

@ -23,7 +23,7 @@ public class ScheduleActivityExecutionContextTests(ITestOutputHelper testOutputH
var workflowGraphBuilder = _serviceProvider.GetRequiredService<IWorkflowGraphBuilder>();
var workflowGraph = await workflowGraphBuilder.BuildAsync(workflow);
var workflowExecutionContext = await WorkflowExecutionContext.CreateAsync(_serviceProvider, workflowGraph, "test");
var activityExecutionContext = workflowExecutionContext.CreateActivityExecutionContext(writeLineA);
var activityExecutionContext = await workflowExecutionContext.CreateActivityExecutionContext(writeLineA);
await Assert.ThrowsAsync<InvalidOperationException>(() => activityExecutionContext.ScheduleActivityAsync(writeLineB).AsTask());
}