Add project files.
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
using Quartz;
|
||||
|
||||
namespace Elsa.Quartz.Contracts;
|
||||
|
||||
internal interface IJobKeyProvider
|
||||
{
|
||||
JobKey GetJobKey<TJob>() where TJob : IJob;
|
||||
string GetGroupName();
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
|
||||
<Description>
|
||||
Provides integration with the Quartz.NET library and provide am implementation of Elsa's IJobScheduler using Quartz.NET.
|
||||
</Description>
|
||||
<PackageTags>elsa module scheduling quartz</PackageTags>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Quartz.Extensions.Hosting" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Elsa.Scheduling\Elsa.Scheduling.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,3 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=abstractions/@EntryIndexedValue">True</s:Boolean>
|
||||
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=extensions/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
|
||||
@@ -0,0 +1,45 @@
|
||||
using System.Text.Json;
|
||||
using Quartz;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Elsa.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for <see cref="JobDataMap"/>.
|
||||
/// </summary>
|
||||
public static class JobDataMapExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds a key/value pair to the map if the value is not null or whitespace.
|
||||
/// </summary>
|
||||
public static JobDataMap AddIfNotEmpty(this JobDataMap map, string key, string? value)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(value))
|
||||
map[key] = value;
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a key/value pair to the map if the value is not null or empty.
|
||||
/// </summary>
|
||||
public static JobDataMap AddIfNotEmpty(this JobDataMap map, string key, IDictionary<string, object>? dictionary)
|
||||
{
|
||||
if (dictionary == null || !dictionary.Any())
|
||||
return map;
|
||||
|
||||
var json = JsonSerializer.Serialize(dictionary);
|
||||
map[key] = json;
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a dictionary from the map.
|
||||
/// </summary>
|
||||
public static IDictionary<string, object>? GetDictionary(this JobDataMap map, string key)
|
||||
{
|
||||
var json = (string?)map.Get(key);
|
||||
return json == null ? null : JsonSerializer.Deserialize<Dictionary<string, object>>(json);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using Elsa.Common.Multitenancy;
|
||||
using Quartz;
|
||||
|
||||
namespace Elsa.Quartz;
|
||||
|
||||
internal static class JobExecutionExtensions
|
||||
{
|
||||
public static async Task<Tenant?> GetTenantAsync(this IJobExecutionContext context, ITenantFinder tenantFinder)
|
||||
{
|
||||
if(!context.MergedJobDataMap.ContainsKey("TenantId"))
|
||||
return null;
|
||||
|
||||
if(!context.MergedJobDataMap.TryGetString("TenantId", out var tenantId))
|
||||
return null;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(tenantId))
|
||||
return null;
|
||||
|
||||
return await tenantFinder.FindByIdAsync(tenantId, context.CancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using Elsa.Features.Services;
|
||||
using Elsa.Quartz.Features;
|
||||
using Elsa.Scheduling;
|
||||
using Elsa.Scheduling.Features;
|
||||
|
||||
// ReSharper disable once CheckNamespace
|
||||
namespace Elsa.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for the <see cref="SchedulingFeature"/>.
|
||||
/// </summary>
|
||||
public static class ModuleExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Installs and configures Quartz.NET. Only use this feature if you are not configuring Quartz.NET yourself.
|
||||
/// </summary>
|
||||
public static IModule UseQuartz(this IModule module, Action<QuartzFeature>? configure = default)
|
||||
{
|
||||
return module.Use(configure);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Installs a Quartz.NET implementation for <see cref="IWorkflowScheduler"/>.
|
||||
/// </summary>
|
||||
public static SchedulingFeature UseQuartzScheduler(this SchedulingFeature feature, Action<QuartzSchedulerFeature>? configure = default)
|
||||
{
|
||||
feature.Module.Use(configure);
|
||||
return feature;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using Elsa.Features.Abstractions;
|
||||
using Elsa.Features.Services;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Quartz;
|
||||
|
||||
namespace Elsa.Quartz.Features;
|
||||
|
||||
/// <summary>
|
||||
/// A feature that installs and configures Quartz.NET. Only enable this feature if you are not configuring Quartz.NET yourself.
|
||||
/// </summary>
|
||||
public class QuartzFeature : FeatureBase
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public QuartzFeature(IModule module) : base(module)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A delegate that can be used to configure Quartz.NET options.
|
||||
/// </summary>
|
||||
public Action<QuartzOptions>? ConfigureQuartzOptions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A delegate that can be used to configure Quartz.NET itself.
|
||||
/// </summary>
|
||||
public Action<IServiceCollectionQuartzConfigurator>? ConfigureQuartz { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A delegate that can be used to configure Quartz.NET hosted service.
|
||||
/// </summary>
|
||||
public Action<QuartzHostedServiceOptions>? ConfigureQuartzHostedService { get; set; } = options => options.WaitForJobsToComplete = true;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ConfigureHostedServices()
|
||||
{
|
||||
var type = Type.GetType("Quartz.QuartzHostedService, Quartz.Extensions.Hosting")!;
|
||||
Module.ConfigureHostedService(type);
|
||||
|
||||
if (ConfigureQuartzHostedService != null)
|
||||
Services.Configure(ConfigureQuartzHostedService);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Apply()
|
||||
{
|
||||
if (ConfigureQuartzOptions != null)
|
||||
Services.Configure(ConfigureQuartzOptions);
|
||||
|
||||
Services
|
||||
.AddQuartz(configure => { ConfigureQuartzInternal(configure, ConfigureQuartz); });
|
||||
}
|
||||
|
||||
private static void ConfigureQuartzInternal(IServiceCollectionQuartzConfigurator quartz, Action<IServiceCollectionQuartzConfigurator>? configureQuartz)
|
||||
{
|
||||
configureQuartz?.Invoke(quartz);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Features.Abstractions;
|
||||
using Elsa.Features.Attributes;
|
||||
using Elsa.Features.Services;
|
||||
using Elsa.Quartz.Contracts;
|
||||
using Elsa.Quartz.Handlers;
|
||||
using Elsa.Quartz.Services;
|
||||
using Elsa.Quartz.Tasks;
|
||||
using Elsa.Scheduling;
|
||||
using Elsa.Scheduling.Features;
|
||||
using Elsa.Workflows;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Quartz;
|
||||
|
||||
namespace Elsa.Quartz.Features;
|
||||
|
||||
/// <summary>
|
||||
/// A feature that installs Quartz.NET implementations for <see cref="IWorkflowScheduler"/>.
|
||||
/// </summary>
|
||||
[DependsOn(typeof(SchedulingFeature))]
|
||||
public class QuartzSchedulerFeature(IModule module) : FeatureBase(module)
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override void Configure()
|
||||
{
|
||||
Module.Configure<SchedulingFeature>(scheduling =>
|
||||
{
|
||||
// Configure the scheduling feature to use the Quartz workflow scheduler.
|
||||
scheduling.WorkflowScheduler = sp => sp.GetRequiredService<QuartzWorkflowScheduler>();
|
||||
|
||||
// Configure the cron parser to use the Quartz cron parser.
|
||||
scheduling.CronParser = sp => sp.GetRequiredService<QuartzCronParser>();
|
||||
});
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Apply()
|
||||
{
|
||||
Services
|
||||
.AddSingleton<IActivityDescriptorModifier, CronActivityDescriptorModifier>()
|
||||
.AddSingleton<QuartzCronParser>()
|
||||
.AddScoped<QuartzWorkflowScheduler>()
|
||||
.AddScoped<IJobKeyProvider, JobKeyProvider>()
|
||||
.AddStartupTask<RegisterJobsTask>()
|
||||
.AddQuartz();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
|
||||
<ConfigureAwait />
|
||||
</Weavers>
|
||||
@@ -0,0 +1,24 @@
|
||||
using Elsa.Scheduling.Activities;
|
||||
using Elsa.Workflows;
|
||||
using Elsa.Workflows.Helpers;
|
||||
using Elsa.Workflows.Models;
|
||||
|
||||
namespace Elsa.Quartz.Handlers;
|
||||
|
||||
/// <summary>
|
||||
/// Modifies the <see cref="ActivityDescriptor"/> of the <see cref="Cron"/> activity.
|
||||
/// </summary>
|
||||
public class CronActivityDescriptorModifier : IActivityDescriptorModifier
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public void Modify(ActivityDescriptor descriptor)
|
||||
{
|
||||
if (descriptor.TypeName != ActivityTypeNameHelper.GenerateTypeName<Cron>())
|
||||
return;
|
||||
|
||||
descriptor.Description = "Schedules the execution of the activity using a cron expression using Quartz.NET.";
|
||||
|
||||
var cronExpressionInput = descriptor.Inputs.First(x => x.Name == nameof(Cron.CronExpression));
|
||||
cronExpressionInput.Description = "The Quartz.NET cron expression to use for scheduling the activity.";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using Elsa.Common;
|
||||
using Elsa.Common.Multitenancy;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Scheduling;
|
||||
using Elsa.Workflows.Models;
|
||||
using Elsa.Workflows.Runtime;
|
||||
using Elsa.Workflows.Runtime.Messages;
|
||||
using Quartz;
|
||||
|
||||
namespace Elsa.Quartz.Jobs;
|
||||
|
||||
/// <summary>
|
||||
/// A job that resumes a workflow.
|
||||
/// </summary>
|
||||
public class ResumeWorkflowJob(IWorkflowRuntime workflowRuntime, IJsonSerializer jsonSerializer, ITenantFinder tenantFinder, ITenantAccessor tenantAccessor) : IJob
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task Execute(IJobExecutionContext context)
|
||||
{
|
||||
var tenant = await context.GetTenantAsync(tenantFinder);
|
||||
using (tenantAccessor.PushContext(tenant))
|
||||
{
|
||||
var map = context.MergedJobDataMap;
|
||||
var serializedActivityHandle = (string)map.Get(nameof(ScheduleExistingWorkflowInstanceRequest.ActivityHandle));
|
||||
var activityHandle = serializedActivityHandle != null! ? jsonSerializer.Deserialize<ActivityHandle>(serializedActivityHandle) : null;
|
||||
var workflowInstanceId = (string)map.Get(nameof(ScheduleExistingWorkflowInstanceRequest.WorkflowInstanceId));
|
||||
var workflowClient = await workflowRuntime.CreateClientAsync(workflowInstanceId, context.CancellationToken);
|
||||
var request = new RunWorkflowInstanceRequest
|
||||
{
|
||||
BookmarkId = (string)map.Get(nameof(ScheduleExistingWorkflowInstanceRequest.BookmarkId)),
|
||||
ActivityHandle = activityHandle,
|
||||
Input = map.GetDictionary(nameof(ScheduleExistingWorkflowInstanceRequest.Input)),
|
||||
Properties = map.GetDictionary(nameof(ScheduleExistingWorkflowInstanceRequest.Properties)),
|
||||
};
|
||||
await workflowClient.RunInstanceAsync(request, cancellationToken: context.CancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
using Elsa.Common.Multitenancy;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Scheduling;
|
||||
using Elsa.Workflows.Models;
|
||||
using Elsa.Workflows.Runtime;
|
||||
using Elsa.Workflows.Runtime.Exceptions;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Quartz;
|
||||
|
||||
namespace Elsa.Quartz.Jobs;
|
||||
|
||||
/// <summary>
|
||||
/// A job that runs a workflow.
|
||||
/// </summary>
|
||||
public class RunWorkflowJob(
|
||||
ITenantAccessor tenantAccessor,
|
||||
ITenantFinder tenantFinder,
|
||||
IWorkflowStarter workflowStarter,
|
||||
ILogger<RunWorkflowJob> logger) : IJob
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async Task Execute(IJobExecutionContext context)
|
||||
{
|
||||
var tenant = await context.GetTenantAsync(tenantFinder);
|
||||
using (tenantAccessor.PushContext(tenant))
|
||||
{
|
||||
var map = context.MergedJobDataMap;
|
||||
var cancellationToken = context.CancellationToken;
|
||||
|
||||
var startRequest = new StartWorkflowRequest
|
||||
{
|
||||
WorkflowDefinitionHandle = WorkflowDefinitionHandle.ByDefinitionVersionId((string)map.Get(nameof(ScheduleNewWorkflowInstanceRequest.WorkflowDefinitionHandle.DefinitionVersionId))),
|
||||
CorrelationId = (string?)map.Get(nameof(ScheduleNewWorkflowInstanceRequest.CorrelationId)),
|
||||
TriggerActivityId = (string?)map.Get(nameof(ScheduleNewWorkflowInstanceRequest.TriggerActivityId)),
|
||||
Input = map.GetDictionary(nameof(ScheduleNewWorkflowInstanceRequest.Input)),
|
||||
Variables = map.GetDictionary(nameof(ScheduleNewWorkflowInstanceRequest.Variables)),
|
||||
Properties = map.GetDictionary(nameof(ScheduleNewWorkflowInstanceRequest.Properties)),
|
||||
ParentId = (string?)map.Get(nameof(ScheduleNewWorkflowInstanceRequest.ParentId))
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
var startResponse = await workflowStarter.StartWorkflowAsync(startRequest, cancellationToken);
|
||||
|
||||
if (startResponse.CannotStart)
|
||||
{
|
||||
logger.LogWarning("Workflow activation strategy disallowed starting workflow {WorkflowDefinitionHandle} with correlation ID {CorrelationId}", startRequest.WorkflowDefinitionHandle, startRequest.CorrelationId);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.LogInformation("Started workflow {WorkflowInstanceId} with correlation ID {CorrelationId}", startResponse.WorkflowInstanceId, startRequest.CorrelationId);
|
||||
}
|
||||
catch (WorkflowGraphNotFoundException e)
|
||||
{
|
||||
logger.LogWarning(e, "Could not find workflow graph for workflow definition handle {WorkflowDefinitionHandle}", startRequest.WorkflowDefinitionHandle);
|
||||
await context.Scheduler.DeleteJob(context.JobDetail.Key, cancellationToken);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogError(e, "An error occurred while starting workflow {WorkflowDefinitionHandle} with correlation ID {CorrelationId}", startRequest.WorkflowDefinitionHandle, startRequest.CorrelationId);
|
||||
await context.Scheduler.DeleteJob(context.JobDetail.Key, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
using Elsa.Common.Multitenancy;
|
||||
using Elsa.Quartz.Contracts;
|
||||
using Quartz;
|
||||
|
||||
namespace Elsa.Quartz.Services;
|
||||
|
||||
internal class JobKeyProvider(ITenantAccessor tenantAccessor) : IJobKeyProvider
|
||||
{
|
||||
public JobKey GetJobKey<TJob>() where TJob : IJob
|
||||
{
|
||||
return new(typeof(TJob).Name, GetGroupName());
|
||||
}
|
||||
|
||||
public string GetGroupName()
|
||||
{
|
||||
var tenantId = tenantAccessor.Tenant?.Id;
|
||||
return string.IsNullOrWhiteSpace(tenantId) ? "Default" : tenantId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using Elsa.Common;
|
||||
using Elsa.Scheduling;
|
||||
using Quartz;
|
||||
|
||||
namespace Elsa.Quartz.Services;
|
||||
|
||||
/// <summary>
|
||||
/// A cron parser that uses Quartz.
|
||||
/// </summary>
|
||||
public class QuartzCronParser : ICronParser
|
||||
{
|
||||
private readonly ISystemClock _systemClock;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="QuartzCronParser"/> class.
|
||||
/// </summary>
|
||||
public QuartzCronParser(ISystemClock systemClock)
|
||||
{
|
||||
_systemClock = systemClock;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public DateTimeOffset GetNextOccurrence(string expression)
|
||||
{
|
||||
var schedule = new CronExpression(expression);
|
||||
var now = _systemClock.UtcNow;
|
||||
return schedule.GetNextValidTimeAfter(now).GetValueOrDefault();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
using Elsa.Common;
|
||||
using Elsa.Common.Multitenancy;
|
||||
using Elsa.Extensions;
|
||||
using Elsa.Quartz.Contracts;
|
||||
using Elsa.Quartz.Jobs;
|
||||
using Elsa.Scheduling;
|
||||
using Quartz;
|
||||
using IScheduler = Quartz.IScheduler;
|
||||
|
||||
namespace Elsa.Quartz.Services;
|
||||
|
||||
/// <summary>
|
||||
/// An implementation of <see cref="IWorkflowScheduler"/> that uses Quartz.NET.
|
||||
/// </summary>
|
||||
internal class QuartzWorkflowScheduler(ISchedulerFactory schedulerFactoryFactory, IJsonSerializer jsonSerializer, ITenantAccessor tenantAccessor, IJobKeyProvider jobKeyProvider) : IWorkflowScheduler
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public async ValueTask ScheduleAtAsync(string taskName, ScheduleNewWorkflowInstanceRequest request, DateTimeOffset at, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var scheduler = await schedulerFactoryFactory.GetScheduler(cancellationToken);
|
||||
|
||||
var trigger = TriggerBuilder.Create()
|
||||
.ForJob(GetRunWorkflowJobKey())
|
||||
.UsingJobData(CreateJobDataMap(request))
|
||||
.WithIdentity(GetTriggerKey(taskName))
|
||||
.StartAt(at)
|
||||
.Build();
|
||||
|
||||
await ScheduleJobAsync(scheduler, trigger, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask ScheduleAtAsync(string taskName, ScheduleExistingWorkflowInstanceRequest request, DateTimeOffset at, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var scheduler = await schedulerFactoryFactory.GetScheduler(cancellationToken);
|
||||
var trigger = TriggerBuilder.Create()
|
||||
.ForJob(GetResumeWorkflowJobKey())
|
||||
.UsingJobData(CreateJobDataMap(request))
|
||||
.WithIdentity(GetTriggerKey(taskName))
|
||||
.StartAt(at)
|
||||
.Build();
|
||||
|
||||
await ScheduleJobAsync(scheduler, trigger, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask ScheduleRecurringAsync(string taskName, ScheduleNewWorkflowInstanceRequest request, DateTimeOffset startAt, TimeSpan interval, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var scheduler = await schedulerFactoryFactory.GetScheduler(cancellationToken);
|
||||
var trigger = TriggerBuilder.Create()
|
||||
.WithIdentity(GetTriggerKey(taskName))
|
||||
.ForJob(GetRunWorkflowJobKey())
|
||||
.UsingJobData(CreateJobDataMap(request))
|
||||
.StartAt(startAt)
|
||||
.WithSimpleSchedule(schedule => schedule.WithInterval(interval).RepeatForever())
|
||||
.Build();
|
||||
|
||||
await ScheduleJobAsync(scheduler, trigger, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask ScheduleRecurringAsync(string taskName, ScheduleExistingWorkflowInstanceRequest request, DateTimeOffset startAt, TimeSpan interval, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var scheduler = await schedulerFactoryFactory.GetScheduler(cancellationToken);
|
||||
var trigger = TriggerBuilder.Create()
|
||||
.WithIdentity(GetTriggerKey(taskName))
|
||||
.ForJob(GetResumeWorkflowJobKey())
|
||||
.UsingJobData(CreateJobDataMap(request))
|
||||
.StartAt(startAt)
|
||||
.WithSimpleSchedule(schedule => schedule.WithInterval(interval).RepeatForever())
|
||||
.Build();
|
||||
|
||||
await ScheduleJobAsync(scheduler, trigger, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask ScheduleCronAsync(string taskName, ScheduleNewWorkflowInstanceRequest request, string cronExpression, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var scheduler = await schedulerFactoryFactory.GetScheduler(cancellationToken);
|
||||
var trigger = TriggerBuilder.Create()
|
||||
.UsingJobData(CreateJobDataMap(request))
|
||||
.ForJob(GetRunWorkflowJobKey())
|
||||
.WithIdentity(GetTriggerKey(taskName))
|
||||
.WithCronSchedule(cronExpression)
|
||||
.Build();
|
||||
|
||||
await ScheduleJobAsync(scheduler, trigger, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask ScheduleCronAsync(string taskName, ScheduleExistingWorkflowInstanceRequest request, string cronExpression, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var scheduler = await schedulerFactoryFactory.GetScheduler(cancellationToken);
|
||||
var trigger = TriggerBuilder.Create()
|
||||
.ForJob(GetResumeWorkflowJobKey())
|
||||
.UsingJobData(CreateJobDataMap(request))
|
||||
.WithIdentity(GetTriggerKey(taskName))
|
||||
.WithCronSchedule(cronExpression).Build();
|
||||
|
||||
await ScheduleJobAsync(scheduler, trigger, cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async ValueTask UnscheduleAsync(string taskName, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var scheduler = await schedulerFactoryFactory.GetScheduler(cancellationToken);
|
||||
var triggerKey = GetTriggerKey(taskName);
|
||||
await scheduler.UnscheduleJob(triggerKey, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task ScheduleJobAsync(IScheduler scheduler, ITrigger trigger, CancellationToken cancellationToken)
|
||||
{
|
||||
if (!await scheduler.CheckExists(trigger.Key, cancellationToken))
|
||||
await scheduler.ScheduleJob(trigger, cancellationToken);
|
||||
}
|
||||
|
||||
private JobDataMap CreateJobDataMap(ScheduleNewWorkflowInstanceRequest request)
|
||||
{
|
||||
return new JobDataMap()
|
||||
.AddIfNotEmpty("TenantId", tenantAccessor.Tenant?.Id)
|
||||
.AddIfNotEmpty(nameof(ScheduleNewWorkflowInstanceRequest.CorrelationId), request.CorrelationId)
|
||||
.AddIfNotEmpty(nameof(ScheduleNewWorkflowInstanceRequest.WorkflowDefinitionHandle.DefinitionVersionId), request.WorkflowDefinitionHandle.DefinitionVersionId)
|
||||
.AddIfNotEmpty(nameof(ScheduleNewWorkflowInstanceRequest.TriggerActivityId), request.TriggerActivityId)
|
||||
.AddIfNotEmpty(nameof(ScheduleNewWorkflowInstanceRequest.ParentId), request.ParentId)
|
||||
.AddIfNotEmpty(nameof(ScheduleNewWorkflowInstanceRequest.Input), request.Input)
|
||||
.AddIfNotEmpty(nameof(ScheduleNewWorkflowInstanceRequest.Properties), request.Properties)
|
||||
;
|
||||
}
|
||||
|
||||
private JobDataMap CreateJobDataMap(ScheduleExistingWorkflowInstanceRequest request)
|
||||
{
|
||||
var serializedActivityHandle = request.ActivityHandle != null ? jsonSerializer.Serialize(request.ActivityHandle) : null;
|
||||
|
||||
return new JobDataMap()
|
||||
.AddIfNotEmpty("TenantId", tenantAccessor.Tenant?.Id)
|
||||
.AddIfNotEmpty(nameof(ScheduleExistingWorkflowInstanceRequest.WorkflowInstanceId), request.WorkflowInstanceId)
|
||||
.AddIfNotEmpty(nameof(ScheduleExistingWorkflowInstanceRequest.Input), request.Input)
|
||||
.AddIfNotEmpty(nameof(ScheduleExistingWorkflowInstanceRequest.Properties), request.Properties)
|
||||
.AddIfNotEmpty(nameof(ScheduleExistingWorkflowInstanceRequest.ActivityHandle), serializedActivityHandle)
|
||||
.AddIfNotEmpty(nameof(ScheduleExistingWorkflowInstanceRequest.BookmarkId), request.BookmarkId);
|
||||
}
|
||||
|
||||
private JobKey GetRunWorkflowJobKey() => jobKeyProvider.GetJobKey<RunWorkflowJob>();
|
||||
private JobKey GetResumeWorkflowJobKey() => jobKeyProvider.GetJobKey<ResumeWorkflowJob>();
|
||||
private string GetGroupName() => jobKeyProvider.GetGroupName();
|
||||
private TriggerKey GetTriggerKey(string taskName) => new(taskName, GetGroupName());
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using Elsa.Common;
|
||||
using Elsa.Quartz.Contracts;
|
||||
using Elsa.Quartz.Jobs;
|
||||
using JetBrains.Annotations;
|
||||
using Quartz;
|
||||
|
||||
namespace Elsa.Quartz.Tasks;
|
||||
|
||||
/// <summary>
|
||||
/// Registers the Quartz jobs.
|
||||
/// </summary>
|
||||
/// <param name="schedulerFactoryFactory"></param>
|
||||
/// <param name="jobKeyProvider"></param>
|
||||
[UsedImplicitly]
|
||||
internal class RegisterJobsTask(ISchedulerFactory schedulerFactoryFactory, IJobKeyProvider jobKeyProvider) : IStartupTask
|
||||
{
|
||||
public async Task ExecuteAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var scheduler = await schedulerFactoryFactory.GetScheduler(cancellationToken);
|
||||
await CreateJobAsync<RunWorkflowJob>(scheduler, cancellationToken);
|
||||
await CreateJobAsync<ResumeWorkflowJob>(scheduler, cancellationToken);
|
||||
}
|
||||
|
||||
private async Task CreateJobAsync<TJobType>(IScheduler scheduler, CancellationToken cancellationToken) where TJobType : IJob
|
||||
{
|
||||
var key = jobKeyProvider.GetJobKey<TJobType>();
|
||||
var job = JobBuilder.Create<TJobType>()
|
||||
.WithIdentity(key)
|
||||
.StoreDurably()
|
||||
.Build();
|
||||
|
||||
if (!await scheduler.CheckExists(job.Key, cancellationToken))
|
||||
await scheduler.AddJob(job, false, cancellationToken);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user