Incremental work on scheduling activities
This commit is contained in:
parent
803f74b8b5
commit
1949221b23
10
Elsa.sln
10
Elsa.sln
|
|
@ -80,6 +80,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Serialization", "src\c
|
|||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Scripting.Liquid", "src\scripting\Elsa.Scripting.Liquid\Elsa.Scripting.Liquid.csproj", "{A7473430-F228-4534-AEA5-AA8D80E09364}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "modules", "modules", "{5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.Modules.Quartz", "src\modules\Elsa.Modules.Quartz\Elsa.Modules.Quartz.csproj", "{7D5A49B4-9A9B-496E-803B-DEB85B2C3132}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
|
@ -170,6 +174,10 @@ Global
|
|||
{A7473430-F228-4534-AEA5-AA8D80E09364}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{A7473430-F228-4534-AEA5-AA8D80E09364}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{A7473430-F228-4534-AEA5-AA8D80E09364}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{7D5A49B4-9A9B-496E-803B-DEB85B2C3132}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{7D5A49B4-9A9B-496E-803B-DEB85B2C3132}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{7D5A49B4-9A9B-496E-803B-DEB85B2C3132}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{7D5A49B4-9A9B-496E-803B-DEB85B2C3132}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(NestedProjects) = preSolution
|
||||
{155227F0-A33B-40AA-A4B4-06F813EB921B} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F}
|
||||
|
|
@ -205,5 +213,7 @@ Global
|
|||
{820016B7-01CD-4032-8239-6A4E53F1A352} = {9F8AE7FB-E5F9-4DCB-9CF8-0362B3D18DAA}
|
||||
{55F9B33D-5FAF-4355-97EF-445C93208E85} = {C6658DE0-2B2F-47F0-BB61-2CA66D435C09}
|
||||
{A7473430-F228-4534-AEA5-AA8D80E09364} = {2633B8B9-4AEC-4A54-8832-1C940B54E041}
|
||||
{5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79} = {61017E64-6D00-49CB-9E81-5002DC8F7D5F}
|
||||
{7D5A49B4-9A9B-496E-803B-DEB85B2C3132} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
|
|||
|
|
@ -2,13 +2,12 @@
|
|||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using Elsa.Attributes;
|
||||
using Elsa.Contracts;
|
||||
using Elsa.Management.Models;
|
||||
using Elsa.Models;
|
||||
|
||||
namespace Elsa.Activities.Http;
|
||||
|
||||
public class HttpTrigger : Trigger
|
||||
public class HttpTrigger : TriggerActivity
|
||||
{
|
||||
[Input] public Input<string> Path { get; set; } = default!;
|
||||
|
||||
|
|
@ -20,32 +19,14 @@ public class HttpTrigger : Trigger
|
|||
|
||||
[Output] public Output<HttpRequestModel>? Result { get; set; }
|
||||
|
||||
protected override IEnumerable<object> GetHashInputs(TriggerIndexingContext context)
|
||||
protected override IEnumerable<object> GetHashInputs(TriggerIndexingContext context) => GetHashInputs(context.ExpressionExecutionContext);
|
||||
protected override void Execute(ActivityExecutionContext context) => context.SetBookmarks(GetHashInputs(context.ExpressionExecutionContext));
|
||||
|
||||
private IEnumerable<object> GetHashInputs(ExpressionExecutionContext context)
|
||||
{
|
||||
var path = context.ExpressionExecutionContext.Get(Path);
|
||||
var methods = context.ExpressionExecutionContext.Get(SupportedMethods);
|
||||
// Generate a bookmark hash for path and selected methods.
|
||||
var path = context.Get(Path);
|
||||
var methods = context.Get(SupportedMethods);
|
||||
return methods!.Select(x => (path!.ToLowerInvariant(), x.ToLowerInvariant())).Cast<object>().ToArray();
|
||||
}
|
||||
|
||||
protected override void Execute(ActivityExecutionContext context)
|
||||
{
|
||||
var bookmarks = CreateBookmarks(context).ToList();
|
||||
context.SetBookmarks(bookmarks);
|
||||
}
|
||||
|
||||
private IEnumerable<Bookmark> CreateBookmarks(ActivityExecutionContext context)
|
||||
{
|
||||
var path = context.Get(Path)!;
|
||||
var methods = context.Get(SupportedMethods)!;
|
||||
var hasher = context.GetRequiredService<IHasher>();
|
||||
var identityGenerator = context.GetRequiredService<IIdentityGenerator>();
|
||||
|
||||
foreach (var method in methods)
|
||||
{
|
||||
var hashInput = (path.ToLowerInvariant(), method.ToLowerInvariant());
|
||||
var hash = hasher.Hash(hashInput);
|
||||
var bookmarkId = identityGenerator.GenerateId();
|
||||
yield return new Bookmark(bookmarkId, NodeType, hash, Id, context.Id);
|
||||
}
|
||||
}
|
||||
}
|
||||
10
src/activities/Elsa.Activities.Scheduling/Contracts/IJob.cs
Normal file
10
src/activities/Elsa.Activities.Scheduling/Contracts/IJob.cs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Elsa.Activities.Scheduling.Contracts;
|
||||
|
||||
public interface IJob
|
||||
{
|
||||
string JobId { get; }
|
||||
Task ExecuteAsync(CancellationToken cancellationToken);
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Elsa.Activities.Scheduling.Contracts;
|
||||
|
||||
public interface IJobScheduler
|
||||
{
|
||||
Task ScheduleAsync(IJob job, ISchedule schedule, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
namespace Elsa.Activities.Scheduling.Contracts;
|
||||
|
||||
public interface ISchedule
|
||||
{
|
||||
}
|
||||
|
|
@ -10,4 +10,8 @@
|
|||
<ProjectReference Include="..\..\core\Elsa.Core\Elsa.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Services" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Scheduling.Contracts;
|
||||
|
||||
namespace Elsa.Activities.Scheduling.Jobs;
|
||||
|
||||
public class ResumeWorkflowJob : IJob
|
||||
{
|
||||
public ResumeWorkflowJob()
|
||||
{
|
||||
}
|
||||
|
||||
public ResumeWorkflowJob(string workflowInstanceId, string activityId)
|
||||
{
|
||||
WorkflowInstanceId = workflowInstanceId;
|
||||
ActivityId = activityId;
|
||||
}
|
||||
|
||||
public string JobId => $"workflow-instance:{WorkflowInstanceId}-{ActivityId}";
|
||||
public string WorkflowInstanceId { get; init; } = default!;
|
||||
public string ActivityId { get; init; } = default!;
|
||||
|
||||
public Task ExecuteAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Activities.Scheduling.Contracts;
|
||||
using Elsa.Models;
|
||||
|
||||
namespace Elsa.Activities.Scheduling.Jobs;
|
||||
|
||||
public class RunWorkflowJob : IJob
|
||||
{
|
||||
public RunWorkflowJob()
|
||||
{
|
||||
}
|
||||
|
||||
public RunWorkflowJob(WorkflowIdentity workflowIdentity)
|
||||
{
|
||||
WorkflowIdentity = workflowIdentity;
|
||||
}
|
||||
|
||||
public string JobId => $"workflow:{WorkflowIdentity.DefinitionId}";
|
||||
public WorkflowIdentity WorkflowIdentity { get; init; } = default!;
|
||||
|
||||
|
||||
public Task ExecuteAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
using Elsa.Activities.Scheduling.Contracts;
|
||||
|
||||
namespace Elsa.Activities.Scheduling.Schedules;
|
||||
|
||||
public class CronSchedule : ISchedule
|
||||
{
|
||||
public string CronExpression { get; set; } = default!;
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
using System;
|
||||
using Elsa.Activities.Scheduling.Contracts;
|
||||
|
||||
namespace Elsa.Activities.Scheduling.Schedules;
|
||||
|
||||
public class RecurringSchedule : ISchedule
|
||||
{
|
||||
public DateTime StartAt { get; set; }
|
||||
public TimeSpan Interval { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
using System;
|
||||
using Elsa.Activities.Scheduling.Contracts;
|
||||
|
||||
namespace Elsa.Activities.Scheduling.Schedules;
|
||||
|
||||
public class SpecificInstantSchedule : ISchedule
|
||||
{
|
||||
public DateTime DateTime { get; set; }
|
||||
}
|
||||
|
|
@ -1,5 +1,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Elsa.Attributes;
|
||||
using Elsa.Contracts;
|
||||
using Elsa.Models;
|
||||
|
||||
namespace Elsa.Activities.Scheduling;
|
||||
|
|
@ -7,4 +9,12 @@ namespace Elsa.Activities.Scheduling;
|
|||
public class Timer : Trigger
|
||||
{
|
||||
[Input] public Input<TimeSpan> Interval { get; set; } = default!;
|
||||
|
||||
protected override IEnumerable<object> GetHashInputs(TriggerIndexingContext context)
|
||||
{
|
||||
var interval = context.ExpressionExecutionContext.Get(Interval);
|
||||
var clock = context.ExpressionExecutionContext.GetRequiredService<ISystemClock>();
|
||||
var executeAt = clock.UtcNow.Add(interval);
|
||||
return new object[] { executeAt, interval };
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,5 @@ namespace Elsa.Contracts;
|
|||
|
||||
public interface ITrigger : INode
|
||||
{
|
||||
string TriggerType { get; set; }
|
||||
ValueTask<IEnumerable<object>> GetHashInputsAsync(TriggerIndexingContext context, CancellationToken cancellationToken = default);
|
||||
}
|
||||
|
|
@ -47,18 +47,35 @@ public class ActivityExecutionContext
|
|||
ScheduleActivity(activity, completionCallback);
|
||||
}
|
||||
|
||||
public void SetBookmarks(IEnumerable<object> hashInputs, IDictionary<string, object?>? data = default, ExecuteActivityDelegate? callback = default)
|
||||
{
|
||||
foreach (var hashInput in hashInputs)
|
||||
SetBookmark(hashInput, data, callback);
|
||||
}
|
||||
|
||||
public void SetBookmarks(IEnumerable<Bookmark> bookmarks) => _bookmarks.AddRange(bookmarks);
|
||||
public void SetBookmark(Bookmark bookmark) => _bookmarks.Add(bookmark);
|
||||
|
||||
public void SetBookmark(string? hash, IDictionary<string, object?>? data = default, ExecuteActivityDelegate? callback = default) =>
|
||||
public void SetBookmark(object? hashInput, IDictionary<string, object?>? data = default, ExecuteActivityDelegate? callback = default)
|
||||
{
|
||||
var hasher = GetRequiredService<IHasher>();
|
||||
var hash = hashInput != null ? hasher.Hash(hashInput) : default;
|
||||
SetBookmark(hash, data, callback);
|
||||
}
|
||||
|
||||
public void SetBookmark(string? hash, IDictionary<string, object?>? data = default, ExecuteActivityDelegate? callback = default)
|
||||
{
|
||||
var identityGenerator = GetRequiredService<IIdentityGenerator>();
|
||||
|
||||
SetBookmark(new Bookmark(
|
||||
Guid.NewGuid().ToString(),
|
||||
identityGenerator.GenerateId(),
|
||||
Activity.NodeType,
|
||||
hash,
|
||||
Activity.Id,
|
||||
Id,
|
||||
data ?? new Dictionary<string, object?>(),
|
||||
callback?.Method.Name));
|
||||
}
|
||||
|
||||
public T? GetProperty<T>(string key) => Properties.TryGetValue(key, out var value) ? (T?)value : default;
|
||||
public void SetProperty<T>(string key, T value) => Properties[key] = value;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,14 @@
|
|||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Elsa.Models;
|
||||
|
||||
public class ExpressionExecutionContext
|
||||
{
|
||||
public ExpressionExecutionContext(Register register, ExpressionExecutionContext? parentContext)
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
public ExpressionExecutionContext(IServiceProvider serviceProvider, Register register, ExpressionExecutionContext? parentContext)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
Register = register;
|
||||
ParentContext = parentContext;
|
||||
}
|
||||
|
|
@ -31,5 +36,6 @@ public class ExpressionExecutionContext
|
|||
Set(output.LocationReference, convertedValue);
|
||||
}
|
||||
|
||||
public T GetRequiredService<T>() where T : notnull => _serviceProvider.GetRequiredService<T>();
|
||||
private RegisterLocation? GetLocationInternal(RegisterLocationReference locationReference) => Register.TryGetLocation(locationReference.Id, out var location) ? location : ParentContext?.GetLocationInternal(locationReference);
|
||||
}
|
||||
|
|
@ -1,22 +1,16 @@
|
|||
using Elsa.Contracts;
|
||||
using Elsa.Helpers;
|
||||
|
||||
namespace Elsa.Models;
|
||||
|
||||
public class Trigger : Activity, ITrigger
|
||||
public class Trigger : ITrigger
|
||||
{
|
||||
protected Trigger()
|
||||
{
|
||||
}
|
||||
protected Trigger() => NodeType = TypeNameHelper.GenerateTypeName(GetType());
|
||||
protected Trigger(string triggerType) => NodeType = triggerType;
|
||||
|
||||
protected Trigger(string triggerType) : base(triggerType)
|
||||
{
|
||||
}
|
||||
|
||||
public string TriggerType
|
||||
{
|
||||
get => NodeType;
|
||||
set => NodeType = value;
|
||||
}
|
||||
public string Id { get; set; } = default!;
|
||||
public string NodeType { get; set; }
|
||||
public IDictionary<string, object> Metadata { get; set; } = new Dictionary<string, object>();
|
||||
|
||||
public virtual ValueTask<IEnumerable<object>> GetHashInputsAsync(TriggerIndexingContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
|
|
|
|||
28
src/core/Elsa.Core/Models/TriggerActivity.cs
Normal file
28
src/core/Elsa.Core/Models/TriggerActivity.cs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
using Elsa.Contracts;
|
||||
|
||||
namespace Elsa.Models;
|
||||
|
||||
public class TriggerActivity : Activity, ITrigger
|
||||
{
|
||||
protected TriggerActivity()
|
||||
{
|
||||
}
|
||||
|
||||
protected TriggerActivity(string triggerType) : base(triggerType)
|
||||
{
|
||||
}
|
||||
|
||||
public string TriggerType
|
||||
{
|
||||
get => NodeType;
|
||||
set => NodeType = value;
|
||||
}
|
||||
|
||||
public virtual ValueTask<IEnumerable<object>> GetHashInputsAsync(TriggerIndexingContext context, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var hashes = GetHashInputs(context);
|
||||
return ValueTask.FromResult(hashes);
|
||||
}
|
||||
|
||||
protected virtual IEnumerable<object> GetHashInputs(TriggerIndexingContext context) => Enumerable.Empty<object>();
|
||||
}
|
||||
|
|
@ -6,10 +6,12 @@ namespace Elsa.Services;
|
|||
public class ActivityInvoker : IActivityInvoker
|
||||
{
|
||||
private readonly IActivityExecutionPipeline _pipeline;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
public ActivityInvoker(IActivityExecutionPipeline pipeline)
|
||||
public ActivityInvoker(IActivityExecutionPipeline pipeline, IServiceProvider serviceProvider)
|
||||
{
|
||||
_pipeline = pipeline;
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public async Task InvokeAsync(
|
||||
|
|
@ -25,7 +27,7 @@ public class ActivityInvoker : IActivityInvoker
|
|||
|
||||
// Setup an activity execution context.
|
||||
var register = new Register();
|
||||
var expressionExecutionContext = new ExpressionExecutionContext(register, parentActivityExecutionContext?.ExpressionExecutionContext);
|
||||
var expressionExecutionContext = new ExpressionExecutionContext(_serviceProvider, register, parentActivityExecutionContext?.ExpressionExecutionContext);
|
||||
var activityExecutionContext = new ActivityExecutionContext(workflowExecutionContext, parentActivityExecutionContext, expressionExecutionContext, activity, cancellationToken);
|
||||
|
||||
// Declare locations.
|
||||
|
|
|
|||
|
|
@ -9,6 +9,13 @@ namespace Elsa.Services;
|
|||
|
||||
public class WorkflowStateSerializer : IWorkflowStateSerializer
|
||||
{
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
|
||||
public WorkflowStateSerializer(IServiceProvider serviceProvider)
|
||||
{
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public WorkflowState ReadState(WorkflowExecutionContext workflowExecutionContext)
|
||||
{
|
||||
var state = new WorkflowState
|
||||
|
|
@ -119,7 +126,7 @@ public class WorkflowStateSerializer : IWorkflowStateSerializer
|
|||
{
|
||||
var activity = workflowExecutionContext.FindActivityById(activityExecutionContextState.ScheduledActivityId);
|
||||
var register = new Register(activityExecutionContextState.Register.Locations);
|
||||
var expressionExecutionContext = new ExpressionExecutionContext(register, default);
|
||||
var expressionExecutionContext = new ExpressionExecutionContext(_serviceProvider, register, default);
|
||||
var properties = activityExecutionContextState.Properties;
|
||||
var activityExecutionContext = new ActivityExecutionContext(workflowExecutionContext, default, expressionExecutionContext, activity, workflowExecutionContext.CancellationToken)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -45,9 +45,9 @@ public class TriggerDescriber : ITriggerDescriber
|
|||
InputProperties = DescribeInputProperties(inputProperties).ToList(),
|
||||
Constructor = context =>
|
||||
{
|
||||
var activity = _activityFactory.Create(triggerType, context);
|
||||
activity.TriggerType = fullTypeName;
|
||||
return activity;
|
||||
var trigger = _activityFactory.Create(triggerType, context);
|
||||
trigger.NodeType = fullTypeName;
|
||||
return trigger;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
using Elsa.Activities.Scheduling.Contracts;
|
||||
using IElsaJob = Elsa.Activities.Scheduling.Contracts.IJob;
|
||||
|
||||
namespace Elsa.Modules.Quartz.Contracts;
|
||||
|
||||
public interface IElsaJobSerializer
|
||||
{
|
||||
string Serialize(IJob job);
|
||||
T Deserialize<T>(string json) where T : IElsaJob;
|
||||
}
|
||||
19
src/modules/Elsa.Modules.Quartz/Elsa.Modules.Quartz.csproj
Normal file
19
src/modules/Elsa.Modules.Quartz/Elsa.Modules.Quartz.csproj
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Quartz" Version="3.3.3" />
|
||||
<PackageReference Include="Quartz.Extensions.DependencyInjection" Version="3.3.3" />
|
||||
<PackageReference Include="Quartz.Extensions.Hosting" Version="3.3.3" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\activities\Elsa.Activities.Scheduling\Elsa.Activities.Scheduling.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
using Elsa.Activities.Scheduling.Contracts;
|
||||
using Elsa.Activities.Scheduling.Jobs;
|
||||
using Elsa.Modules.Quartz.Contracts;
|
||||
using Elsa.Modules.Quartz.Jobs;
|
||||
using Elsa.Modules.Quartz.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Quartz;
|
||||
using IElsaJob = Elsa.Activities.Scheduling.Contracts.IJob;
|
||||
|
||||
namespace Elsa.Modules.Quartz.Extensions;
|
||||
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// This will register both Quartz as well as Elsa-specific services and jobs.
|
||||
/// If you prefer to register Quartz yourself, use <see cref="ConfigureQuartzModule"/>
|
||||
/// </summary>
|
||||
public static IServiceCollection AddQuartzModule(
|
||||
this IServiceCollection services,
|
||||
Action<QuartzOptions>? configureQuartzOptions = default,
|
||||
Action<IServiceCollectionQuartzConfigurator>? configureQuartz = default,
|
||||
Action<QuartzHostedServiceOptions>? configureQuartzHostedService = default)
|
||||
{
|
||||
if (configureQuartzOptions != null)
|
||||
services.Configure(configureQuartzOptions);
|
||||
|
||||
return services
|
||||
.AddQuartz(configure =>
|
||||
{
|
||||
ConfigureQuartz(configure, configureQuartz);
|
||||
ConfigureQuartzModule(services, configure);
|
||||
})
|
||||
.AddQuartzHostedService(options => ConfigureQuartzHostedService(options, configureQuartzHostedService));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This will register Elsa-specific services and jobs, but will **not** register Quartz itself. To register Quartz, you need to do so yourself, or use <see cref="AddQuartzModule"/> to register & configure Quartz for Elsa.
|
||||
/// </summary>
|
||||
public static IServiceCollection ConfigureQuartzModule(this IServiceCollection services, IServiceCollectionQuartzConfigurator quartz)
|
||||
{
|
||||
services
|
||||
.AddSingleton<IElsaJobSerializer, ElsaJobSerializer>()
|
||||
.AddSingleton<IJobScheduler, QuartzJobScheduler>();
|
||||
|
||||
quartz.AddElsaJobs();
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
private static IServiceCollectionQuartzConfigurator AddElsaJobs(this IServiceCollectionQuartzConfigurator quartz)
|
||||
{
|
||||
quartz.AddJob<RunWorkflowJob>();
|
||||
|
||||
return quartz;
|
||||
}
|
||||
|
||||
private static void ConfigureQuartzHostedService(QuartzHostedServiceOptions options, Action<QuartzHostedServiceOptions>? configureQuartzHostedService)
|
||||
{
|
||||
options.WaitForJobsToComplete = true;
|
||||
configureQuartzHostedService?.Invoke(options);
|
||||
}
|
||||
|
||||
private static IServiceCollectionQuartzConfigurator AddJob<TJob>(this IServiceCollectionQuartzConfigurator quartz) where TJob : IElsaJob =>
|
||||
quartz.AddJob<QuartzJob<RunWorkflowJob>>(job => job.StoreDurably().WithIdentity(nameof(RunWorkflowJob)));
|
||||
|
||||
private static void ConfigureQuartz(IServiceCollectionQuartzConfigurator quartz, Action<IServiceCollectionQuartzConfigurator>? configureQuartz)
|
||||
{
|
||||
quartz.UseMicrosoftDependencyInjectionJobFactory();
|
||||
quartz.AddJob<QuartzJob<RunWorkflowJob>>(job => job.StoreDurably().WithIdentity(nameof(RunWorkflowJob)));
|
||||
quartz.UseSimpleTypeLoader();
|
||||
quartz.UseInMemoryStore();
|
||||
configureQuartz?.Invoke(quartz);
|
||||
}
|
||||
}
|
||||
25
src/modules/Elsa.Modules.Quartz/Jobs/QuartzJob.cs
Normal file
25
src/modules/Elsa.Modules.Quartz/Jobs/QuartzJob.cs
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
using Elsa.Modules.Quartz.Contracts;
|
||||
using Elsa.Modules.Quartz.Services;
|
||||
using Quartz;
|
||||
using IElsaJob = Elsa.Activities.Scheduling.Contracts.IJob;
|
||||
|
||||
namespace Elsa.Modules.Quartz.Jobs;
|
||||
|
||||
/// <summary>
|
||||
/// A generic Quartz job that executes Elsa scheduled jobs.
|
||||
/// </summary>
|
||||
/// <typeparam name="TElsaJob"></typeparam>
|
||||
public class QuartzJob<TElsaJob> : IJob where TElsaJob : IElsaJob
|
||||
{
|
||||
private readonly IElsaJobSerializer _elsaJobSerializer;
|
||||
|
||||
public QuartzJob(IElsaJobSerializer elsaJobSerializer) => _elsaJobSerializer = elsaJobSerializer;
|
||||
|
||||
public async Task Execute(IJobExecutionContext context)
|
||||
{
|
||||
var json = context.MergedJobDataMap.GetString(QuartzJobScheduler.JobDataKey)!;
|
||||
var elsaJob = _elsaJobSerializer.Deserialize<TElsaJob>(json);
|
||||
|
||||
await elsaJob.ExecuteAsync(context.CancellationToken);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
using System.Text.Json;
|
||||
using Elsa.Activities.Scheduling.Contracts;
|
||||
using Elsa.Modules.Quartz.Contracts;
|
||||
using IElsaJob = Elsa.Activities.Scheduling.Contracts.IJob;
|
||||
|
||||
namespace Elsa.Modules.Quartz.Services;
|
||||
|
||||
public class ElsaJobSerializer : IElsaJobSerializer
|
||||
{
|
||||
public string Serialize(IJob job)
|
||||
{
|
||||
var serializerOptions = CreateSerializerOptions();
|
||||
return JsonSerializer.Serialize(job, serializerOptions);
|
||||
}
|
||||
|
||||
public T Deserialize<T>(string json) where T : IElsaJob
|
||||
{
|
||||
var serializerOptions = CreateSerializerOptions();
|
||||
return JsonSerializer.Deserialize<T>(json, serializerOptions)!;
|
||||
}
|
||||
|
||||
private JsonSerializerOptions CreateSerializerOptions() => new();
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
using Elsa.Activities.Scheduling.Schedules;
|
||||
using Elsa.Modules.Quartz.Contracts;
|
||||
using Elsa.Modules.Quartz.Jobs;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Quartz;
|
||||
using IElsaJobScheduler = Elsa.Activities.Scheduling.Contracts.IJobScheduler;
|
||||
using IElsaJob = Elsa.Activities.Scheduling.Contracts.IJob;
|
||||
using IElsaSchedule = Elsa.Activities.Scheduling.Contracts.ISchedule;
|
||||
|
||||
namespace Elsa.Modules.Quartz.Services;
|
||||
|
||||
public class QuartzJobScheduler : IElsaJobScheduler
|
||||
{
|
||||
public const string JobDataKey = "ElsaJob";
|
||||
private readonly IElsaJobSerializer _elsaJobSerializer;
|
||||
private readonly ISchedulerFactory _schedulerFactory;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
public QuartzJobScheduler(IElsaJobSerializer elsaJobSerializer, ISchedulerFactory schedulerFactory, ILogger<QuartzJobScheduler> logger)
|
||||
{
|
||||
_elsaJobSerializer = elsaJobSerializer;
|
||||
_schedulerFactory = schedulerFactory;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task ScheduleAsync(IElsaJob job, IElsaSchedule schedule, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var quartzTrigger = CreateTrigger(job, schedule);
|
||||
await ScheduleJob(quartzTrigger, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task ScheduleJob(ITrigger trigger, CancellationToken cancellationToken)
|
||||
{
|
||||
var scheduler = await _schedulerFactory.GetScheduler(cancellationToken);
|
||||
|
||||
try
|
||||
{
|
||||
await scheduler.ScheduleJob(trigger, cancellationToken);
|
||||
}
|
||||
catch (SchedulerException e)
|
||||
{
|
||||
_logger.LogWarning(e, "Failed to schedule trigger {TriggerKey}", trigger.Key.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
private ITrigger CreateTrigger(IElsaJob job, IElsaSchedule schedule)
|
||||
{
|
||||
var jobName = job.GetType().Name;
|
||||
var json = _elsaJobSerializer.Serialize(job);
|
||||
var builder = TriggerBuilder.Create().ForJob(jobName).WithIdentity(job.JobId).UsingJobData(JobDataKey, json);
|
||||
|
||||
switch (schedule)
|
||||
{
|
||||
case RecurringSchedule recurringSchedule:
|
||||
{
|
||||
builder.StartAt(recurringSchedule.StartAt);
|
||||
builder.WithSimpleSchedule(x => x.WithInterval(recurringSchedule.Interval).RepeatForever());
|
||||
break;
|
||||
}
|
||||
case CronSchedule cronSchedule:
|
||||
{
|
||||
builder.WithCronSchedule(cronSchedule.CronExpression);
|
||||
break;
|
||||
}
|
||||
case SpecificInstantSchedule specificInstantSchedule:
|
||||
{
|
||||
builder.StartAt(specificInstantSchedule.DateTime);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new NotSupportedException($"Schedule of type {schedule.GetType()} is not supported. But if you create an issue, we'll make this logic extensible & replaceable :)");
|
||||
}
|
||||
|
||||
return builder.Build();
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ public class TriggerIndexer : ITriggerIndexer
|
|||
private readonly IWorkflowRegistry _workflowRegistry;
|
||||
private readonly IExpressionEvaluator _expressionEvaluator;
|
||||
private readonly ICommandSender _mediator;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly IHasher _hasher;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
|
|
@ -24,12 +25,14 @@ public class TriggerIndexer : ITriggerIndexer
|
|||
IWorkflowRegistry workflowRegistry,
|
||||
IExpressionEvaluator expressionEvaluator,
|
||||
ICommandSender mediator,
|
||||
IServiceProvider serviceProvider,
|
||||
IHasher hasher,
|
||||
ILogger<TriggerIndexer> logger)
|
||||
{
|
||||
_workflowRegistry = workflowRegistry;
|
||||
_expressionEvaluator = expressionEvaluator;
|
||||
_mediator = mediator;
|
||||
_serviceProvider = serviceProvider;
|
||||
_hasher = hasher;
|
||||
_logger = logger;
|
||||
}
|
||||
|
|
@ -79,7 +82,7 @@ public class TriggerIndexer : ITriggerIndexer
|
|||
var inputs = trigger.GetInputs();
|
||||
var assignedInputs = inputs.Where(x => x.LocationReference != null!).ToList();
|
||||
var register = context.GetOrCreateRegister(trigger);
|
||||
var expressionExecutionContext = new ExpressionExecutionContext(register, default);
|
||||
var expressionExecutionContext = new ExpressionExecutionContext(_serviceProvider, register, default);
|
||||
|
||||
// Evaluate trigger inputs.
|
||||
foreach (var input in assignedInputs)
|
||||
|
|
@ -121,9 +124,9 @@ public class TriggerIndexer : ITriggerIndexer
|
|||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
_logger.LogWarning( e, "Failed to get hash inputs");
|
||||
_logger.LogWarning(e, "Failed to get hash inputs");
|
||||
}
|
||||
|
||||
return Array.Empty<object>() ;
|
||||
return Array.Empty<object>();
|
||||
}
|
||||
}
|
||||
|
|
@ -7,8 +7,10 @@
|
|||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\activities\Elsa.Activities.Http\Elsa.Activities.Http.csproj" />
|
||||
<ProjectReference Include="..\..\..\activities\Elsa.Activities.Scheduling\Elsa.Activities.Scheduling.csproj" />
|
||||
<ProjectReference Include="..\..\..\api\Elsa.Api\Elsa.Api.csproj" />
|
||||
<ProjectReference Include="..\..\..\core\Elsa\Elsa.csproj" />
|
||||
<ProjectReference Include="..\..\..\modules\Elsa.Modules.Quartz\Elsa.Modules.Quartz.csproj" />
|
||||
<ProjectReference Include="..\..\..\persistence\Elsa.Persistence.EntityFrameworkCore.Sqlite\Elsa.Persistence.EntityFrameworkCore.Sqlite.csproj" />
|
||||
<ProjectReference Include="..\..\..\runtime\Elsa.Runtime.ProtoActor\Elsa.Runtime.ProtoActor.csproj" />
|
||||
<ProjectReference Include="..\..\..\scripting\Elsa.Scripting.Liquid\Elsa.Scripting.Liquid.csproj" />
|
||||
|
|
|
|||
|
|
@ -2,12 +2,14 @@ using Elsa.Activities.Console;
|
|||
using Elsa.Activities.ControlFlow;
|
||||
using Elsa.Activities.Http;
|
||||
using Elsa.Activities.Http.Extensions;
|
||||
using Elsa.Activities.Scheduling;
|
||||
using Elsa.Activities.Workflows;
|
||||
using Elsa.Api.Extensions;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Management.Contracts;
|
||||
using Elsa.Management.Extensions;
|
||||
using Elsa.Mediator.Extensions;
|
||||
using Elsa.Modules.Quartz.Extensions;
|
||||
using Elsa.Persistence.EntityFrameworkCore.Extensions;
|
||||
using Elsa.Persistence.EntityFrameworkCore.Sqlite;
|
||||
using Elsa.Persistence.Middleware.WorkflowExecution;
|
||||
|
|
@ -52,17 +54,22 @@ services
|
|||
.AddActivity<ReadLine>()
|
||||
.AddActivity<If>()
|
||||
.AddActivity<HttpTrigger>()
|
||||
.AddActivity<Flowchart>();
|
||||
.AddActivity<Flowchart>()
|
||||
;
|
||||
|
||||
// Register available triggers.
|
||||
services
|
||||
.AddTrigger<HttpTrigger>();
|
||||
.AddTrigger<HttpTrigger>()
|
||||
.AddTrigger<Timer>();
|
||||
|
||||
// Register scripting languages.
|
||||
services
|
||||
.AddJavaScriptExpressions()
|
||||
.AddLiquidExpressions();
|
||||
|
||||
// Register modules.
|
||||
services.AddQuartzModule(); // Provides a scheduler implementation for Timer activities.
|
||||
|
||||
// Configure middleware pipeline.
|
||||
var app = builder.Build();
|
||||
var serviceProvider = app.Services;
|
||||
|
|
|
|||
Loading…
Reference in a new issue