-
-
Save jlucansky/1c5b8e4befa3b745b4485184811caa2f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Quartz; | |
using Quartz; | |
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Net; | |
using System.Net.Http; | |
using System.Text; | |
using System.Threading; | |
using System.Threading.Tasks; | |
using System.Web.Http; | |
using System.Web.Http.Description; | |
namespace Quartz.Web.Controllers | |
{ | |
[ExceptionFilter] | |
[RoutePrefix("scheduler")] | |
public class SchedulerController : ApiController | |
{ | |
IScheduler Scheduler => Request.GetOwinContext().Get<IScheduler>("quartz.scheduler"); | |
/// <summary> | |
/// Returns the name of the scheduler. | |
/// </summary> | |
[Route("name"), HttpGet] | |
public string SchedulerName() | |
{ | |
return Scheduler.SchedulerName; | |
} | |
/// <summary> | |
/// Returns the instance Id of the scheduler. | |
/// </summary> | |
[Route("instance"), HttpGet] | |
public string SchedulerInstanceId() | |
{ | |
return Scheduler.SchedulerInstanceId; | |
} | |
/// <summary> | |
/// Reports whether the scheduler is in stand-by mode. | |
/// </summary> | |
[Route("in-standby-mode"), HttpGet] | |
public bool InStandbyMode() | |
{ | |
return Scheduler.InStandbyMode; | |
} | |
/// <summary> | |
/// Reports whether the scheduler has been Shutdown. | |
/// </summary> | |
[Route("is-shutdown"), HttpGet] | |
public bool IsShutdown() | |
{ | |
return Scheduler.IsShutdown; | |
} | |
/// <summary> | |
/// Whether the scheduler has been started. | |
/// </summary> | |
[Route("is-started"), HttpGet] | |
public bool IsStarted() | |
{ | |
return Scheduler.IsStarted; | |
} | |
/// <summary> | |
/// Add (register) the given calendar to the Scheduler. | |
/// </summary> | |
/// <param name="calName">Name of the calendar.</param> | |
/// <param name="replace">if set to <c>true</c> [replace].</param> | |
/// <param name="updateTriggers">whether or not to update existing triggers that | |
/// referenced the already existing calendar so that they are 'correct' | |
/// based on the new trigger.</param> | |
/// <param name="cancellationToken">The cancellation instruction.</param> | |
/// <remarks> | |
/// Očakáva sa Content-Type: multipart/form-data; s položkami 'calendar.type' a 'calendar.data' | |
/// </remarks> | |
[Route("calendar"), HttpPut] | |
public async Task AddCalendar(string calName, bool replace, bool updateTriggers, CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
var calendar = await GetMultipartData<ICalendar>("calendar", cancellationToken); | |
await Scheduler.AddCalendar(calName, calendar, replace, updateTriggers, cancellationToken); | |
} | |
/// <summary> | |
/// Add the given job to the Scheduler - with no associated trigger. | |
/// </summary> | |
/// <remarks> | |
/// Očakáva sa Content-Type: multipart/form-data; s položkami 'jobDetail.type' a 'jobDetail.data'. | |
/// The job must by definition be 'durable', if it is not, | |
/// SchedulerException will be thrown. | |
/// </remarks> | |
/// <response code="204">No Content</response> | |
/// <response code="500">Jobs added with no trigger must be durable.</response> | |
[Route("job"), HttpPut] | |
public async Task AddJob(bool replace, CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
var jobDetail = await GetMultipartData<IJobDetail>("jobDetail", cancellationToken); | |
await Scheduler.AddJob(jobDetail, replace, cancellationToken); | |
} | |
/// <summary> | |
/// Add the given job to the Scheduler - with no associated trigger. | |
/// </summary> | |
/// <remarks> | |
/// Očakáva sa Content-Type: multipart/form-data; s položkami 'jobDetail.type' a 'jobDetail.data'. | |
/// A non-durable job can be stored. Once it is | |
/// scheduled, it will resume normal non-durable behavior (i.e. be deleted | |
/// once there are no remaining associated triggers). | |
/// </remarks> | |
[Route("job-nondurable"), HttpPut] | |
public async Task AddJobNonDurable(bool replace, CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
var jobDetail = await GetMultipartData<IJobDetail>("jobDetail", cancellationToken); | |
await Scheduler.AddJob(jobDetail, replace, true, cancellationToken); | |
} | |
/// <summary> | |
/// Determine whether a job with the given identifier already | |
/// exists within the scheduler. | |
/// </summary> | |
/// <remarks> | |
/// Returns true if a Job exists with the given identifier | |
/// </remarks> | |
/// <param name="jobKey">the identifier to check for</param> | |
/// <param name="cancellationToken"></param> | |
[Route("job-exists"), HttpPost] | |
public Task<bool> CheckExists([FromBody] JobKey jobKey, CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
return Scheduler.CheckExists(jobKey, cancellationToken); | |
} | |
/// <summary> | |
/// Determine whether a trigger with the given identifier already | |
/// exists within the scheduler. | |
/// </summary> | |
/// <remarks> | |
/// Returns true if a Trigger exists with the given identifier | |
/// </remarks> | |
/// <param name="triggerKey">the identifier to check for</param> | |
/// <param name="cancellationToken"></param> | |
[Route("trigger-exists"), HttpPost] | |
public Task<bool> CheckExists([FromBody] TriggerKey triggerKey, CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
return Scheduler.CheckExists(triggerKey, cancellationToken); | |
} | |
/// <summary> | |
/// Clears (deletes!) all scheduling data - all jobs, triggers, calendars. | |
/// </summary> | |
[Route("clear"), HttpGet] | |
public Task Clear(CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
return Scheduler.Clear(cancellationToken); | |
} | |
/// <summary> | |
/// Delete the identified calendar from the Scheduler. | |
/// </summary> | |
/// <param name="calName">Name of the calendar.</param> | |
/// <param name="cancellationToken"></param> | |
/// <remarks> | |
/// Returns true if the Calendar was found and deleted. | |
/// </remarks> | |
[Route("calendar"), HttpDelete] | |
public Task<bool> DeleteCalendar(string calName, CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
return Scheduler.DeleteCalendar(calName, cancellationToken); | |
} | |
/// <summary> | |
/// Delete the identified job from the Scheduler - and any | |
/// associated triggers. | |
/// </summary> | |
/// <remarks> | |
/// Returns true if the Job was found and deleted. | |
/// </remarks> | |
[Route("job"), HttpDelete] | |
public Task<bool> DeleteJob(JobKey jobKey, CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
return Scheduler.DeleteJob(jobKey, cancellationToken); | |
} | |
/// <summary> | |
/// Delete the identified jobs from the Scheduler - and any | |
/// associated triggers. | |
/// </summary> | |
/// <remarks> | |
/// <para>Note that while this bulk operation is likely more efficient than | |
/// invoking delete job several | |
/// times, it may have the adverse affect of holding data locks for a | |
/// single long duration of time (rather than lots of small durations | |
/// of time).</para> | |
/// Returns true if all of the Jobs were found and deleted, false if | |
/// one or more were not deleted. | |
/// </remarks> | |
[Route("jobs"), HttpDelete] | |
public Task<bool> DeleteJobs([FromBody] IReadOnlyCollection<JobKey> jobKeys, CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
return Scheduler.DeleteJobs(jobKeys, cancellationToken); | |
} | |
/// <summary> | |
/// Get the calendar instance with the given name. | |
/// </summary> | |
/// <remarks> | |
/// Vráti sa Content-Type: multipart/form-data; s položkami 'type' a 'data' | |
/// </remarks> | |
[Route("calendar"), HttpGet] | |
[ResponseType(typeof(ICalendar))] | |
public async Task<HttpResponseMessage> GetCalendar(string calName, CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
return CreateMultipartResponse(await Scheduler.GetCalendar(calName, cancellationToken)); | |
} | |
/// <summary> | |
/// Get the names of all registered calendar. | |
/// </summary> | |
[Route("calendar-names"), HttpGet] | |
public Task<IReadOnlyCollection<string>> GetCalendarNames(CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
return Scheduler.GetCalendarNames(cancellationToken); | |
} | |
[Route("executing-jobs"), HttpGet] | |
public async Task<HttpResponseMessage> GetCurrentlyExecutingJobs(CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
var jobs = await Scheduler.GetCurrentlyExecutingJobs(cancellationToken); | |
return CreateMultipartResponse(jobs.Select(x => RemoteJobExecutionContext.CopyFrom(x)).ToArray()); | |
} | |
/// <summary> | |
/// Get the JobDetail for the job instance with the given key . | |
/// </summary> | |
/// <remarks> | |
/// The returned JobDetail object will be a snap-shot of the actual stored | |
/// JobDetail. If you wish to modify the JobDetail, you must re-store the JobDetail afterward. | |
/// Vráti sa Content-Type: multipart/form-data; s položkami 'type' a 'data' | |
/// </remarks> | |
[Route("job-detail"), HttpPost] | |
[ResponseType(typeof(IJobDetail))] | |
public async Task<HttpResponseMessage> GetJobDetail([FromBody] JobKey jobKey, CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
return CreateMultipartResponse(await Scheduler.GetJobDetail(jobKey, cancellationToken)); | |
} | |
/// <summary> | |
/// Get the names of all known job groups. | |
/// </summary> | |
[Route("job-groups"), HttpGet] | |
public Task<IReadOnlyCollection<string>> GetJobGroupNames(CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
return Scheduler.GetJobGroupNames(cancellationToken); | |
} | |
/// <summary> | |
/// Get the keys of all the jobs in the matching groups. | |
/// </summary> | |
[Route("job-keys"), HttpPost] | |
public Task<IReadOnlyCollection<JobKey>> GetJobKeys([FromBody] SerializableGroupMatcher<JobKey> matcher, CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
return Scheduler.GetJobKeys(matcher, cancellationToken); | |
} | |
/// <summary> | |
/// Get a SchedulerMetaData object describing the settings | |
/// and capabilities of the scheduler instance. | |
/// </summary> | |
/// <remarks> | |
/// Note that the data returned is an 'instantaneous' snap-shot, and that as | |
/// soon as it's returned, the meta data values may be different. | |
/// </remarks> | |
[Route("metadata"), HttpGet] | |
public Task<SchedulerMetaData> GetMetaData(CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
return Scheduler.GetMetaData(cancellationToken); | |
} | |
/// <summary> | |
/// Get the names of all trigger groups that are paused. | |
/// </summary> | |
[Route("paused-trigger-groups"), HttpGet] | |
public Task<IReadOnlyCollection<string>> GetPausedTriggerGroups(CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
return Scheduler.GetPausedTriggerGroups(cancellationToken); | |
} | |
/// <summary> | |
/// Get the trigger instance with the given key. | |
/// </summary> | |
/// <remarks> | |
/// The returned Trigger object will be a snap-shot of the actual stored | |
/// trigger. If you wish to modify the trigger, you must re-store the | |
/// trigger afterward (e.g. see 'reschedule-job'). | |
/// Vráti sa Content-Type: multipart/form-data; s položkami 'type' a 'data' | |
/// </remarks> | |
[Route("trigger"), HttpPost] | |
[ResponseType(typeof(global::Quartz.ITrigger))] | |
public async Task<HttpResponseMessage> GetTrigger([FromBody] TriggerKey triggerKey, CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
return CreateMultipartResponse(await Scheduler.GetTrigger(triggerKey, cancellationToken)); | |
} | |
/// <summary> | |
/// Get the names of all known trigger groups. | |
/// </summary> | |
[Route("trigger-groups"), HttpGet] | |
public Task<IReadOnlyCollection<string>> GetTriggerGroupNames(CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
return Scheduler.GetTriggerGroupNames(cancellationToken); | |
} | |
/// <summary> | |
/// Get the names of all the triggers in the given groups. | |
/// </summary> | |
[Route("trigger-keys"), HttpPost] | |
public Task<IReadOnlyCollection<TriggerKey>> GetTriggerKeys([FromBody] SerializableGroupMatcher<TriggerKey> matcher, CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
return Scheduler.GetTriggerKeys(matcher, cancellationToken); | |
} | |
/// <summary> | |
/// Get all triggers that are associated with the identified job. | |
/// </summary> | |
/// <remarks> | |
/// The returned Trigger objects will be snap-shots of the actual stored | |
/// triggers. If you wish to modify a trigger, you must re-store the | |
/// trigger afterward (e.g. see 'reschedule-job'). | |
/// </remarks> | |
[Route("triggers-of-job"), HttpPost] | |
[ResponseType(typeof(IReadOnlyCollection<global::Quartz.ITrigger>))] | |
public async Task<HttpResponseMessage> GetTriggersOfJob([FromBody] JobKey jobKey, CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
return CreateMultipartResponse(await Scheduler.GetTriggersOfJob(jobKey, cancellationToken)); | |
} | |
/// <summary> | |
/// Get the current state of the identified trigger. | |
/// </summary> | |
[Route("trigger-state"), HttpPost] | |
public Task<TriggerState> GetTriggerState([FromBody] TriggerKey triggerKey, CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
return Scheduler.GetTriggerState(triggerKey, cancellationToken); | |
} | |
/// <summary> | |
/// Request the cancellation, within this Scheduler instance, of all | |
/// currently executing instances of the identified job. | |
/// </summary> | |
/// <remarks> | |
/// <para> | |
/// If more than one instance of the identified job is currently executing, | |
/// the cancellation token will be set on each instance. However, there is a limitation that in the case that | |
/// 'interrupt' on one instances throws an exception, all | |
/// remaining instances (that have not yet been interrupted) will not have | |
/// their Interrupt method called. | |
/// </para> | |
/// <para> | |
/// This method is not cluster aware. That is, it will only interrupt | |
/// instances of the identified InterruptableJob currently executing in this | |
/// Scheduler instance, not across the entire cluster. | |
/// </para> | |
/// Returns true if at least one instance of the identified job was found and interrupted. | |
/// </remarks> | |
[Route("interrupt"), HttpPost] | |
public Task<bool> Interrupt([FromBody] JobKey jobKey, CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
return Scheduler.Interrupt(jobKey, cancellationToken); | |
} | |
/// <summary> | |
/// Request the cancellation, within this Scheduler instance, of the | |
/// identified executing job instance. | |
/// </summary> | |
/// <remarks> | |
/// This method is not cluster aware. That is, it will only interrupt | |
/// instances of the identified InterruptableJob currently executing in this | |
/// Scheduler instance, not across the entire cluster. | |
/// Returns true if the identified job instance was found and interrupted. | |
/// </remarks> | |
/// <param name="fireInstanceId"> | |
/// The unique identifier of the job instance to be interrupted | |
/// </param> | |
[Route("interrupt"), HttpGet] | |
public Task<bool> Interrupt(string fireInstanceId, CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
return Scheduler.Interrupt(fireInstanceId, cancellationToken); | |
} | |
/// <summary> | |
/// Returns true if the given JobGroup is paused | |
/// </summary> | |
[Route("is-job-group-paused"), HttpGet] | |
public Task<bool> IsJobGroupPaused(string groupName, CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
return Scheduler.IsJobGroupPaused(groupName, cancellationToken); | |
} | |
/// <summary> | |
/// Returns true if the given JobGroup is paused | |
/// </summary> | |
[Route("is-trigger-group-paused"), HttpGet] | |
public Task<bool> IsTriggerGroupPaused(string groupName, CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
return Scheduler.IsTriggerGroupPaused(groupName, cancellationToken); | |
} | |
/// <summary> | |
/// Pause all triggers | |
/// </summary> | |
/// <remarks> | |
/// Similar to calling 'pause-trigger' on every group, however, after using this method 'resume-all' | |
/// must be called to clear the scheduler's state of 'remembering' that all | |
/// new triggers will be paused as they are added. | |
/// When 'resume-all' is called (to un-pause), trigger misfire | |
/// instructions WILL be applied. | |
/// </remarks> | |
[Route("pause-all"), HttpGet] | |
public Task PauseAll(CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
return Scheduler.PauseAll(cancellationToken); | |
} | |
/// <summary> | |
/// Pause the job with the given key - by pausing all of its current triggers. | |
/// </summary> | |
[Route("pause-job"), HttpPost] | |
public Task PauseJob([FromBody] JobKey jobKey, CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
return Scheduler.PauseJob(jobKey, cancellationToken); | |
} | |
/// <summary> | |
/// Pause all of the jobs in the | |
/// matching groups - by pausing all of their triggers. | |
/// </summary> | |
/// <remarks> | |
/// <para> | |
/// The Scheduler will "remember" that the groups are paused, and impose the | |
/// pause on any new jobs that are added to any of those groups until it is resumed. | |
/// </para> | |
/// <para>NOTE: There is a limitation that only exactly matched groups | |
/// can be remembered as paused. For example, if there are pre-existing | |
/// job in groups "aaa" and "bbb" and a matcher is given to pause | |
/// groups that start with "a" then the group "aaa" will be remembered | |
/// as paused and any subsequently added jobs in group "aaa" will be paused, | |
/// however if a job is added to group "axx" it will not be paused, | |
/// as "axx" wasn't known at the time the "group starts with a" matcher | |
/// was applied. HOWEVER, if there are pre-existing groups "aaa" and | |
/// "bbb" and a matcher is given to pause the group "axx" (with a | |
/// group equals matcher) then no jobs will be paused, but it will be | |
/// remembered that group "axx" is paused and later when a job is added | |
/// in that group, it will become paused.</para> | |
/// </remarks> | |
[Route("pause-jobs"), HttpPost] | |
public Task PauseJobs([FromBody] SerializableGroupMatcher<JobKey> matcher, CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
return Scheduler.PauseJobs(matcher, cancellationToken); | |
} | |
/// <summary> | |
/// Pause the trigger with the given key. | |
/// </summary> | |
[Route("pause-trigger"), HttpPost] | |
public Task PauseTrigger([FromBody] TriggerKey triggerKey, CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
return Scheduler.PauseTrigger(triggerKey, cancellationToken); | |
} | |
/// <summary> | |
/// Pause all of the triggers in the groups matching. | |
/// </summary> | |
/// <remarks> | |
/// <para> | |
/// The Scheduler will "remember" all the groups paused, and impose the | |
/// pause on any new triggers that are added to any of those groups until it is resumed. | |
/// </para> | |
/// <para>NOTE: There is a limitation that only exactly matched groups | |
/// can be remembered as paused. For example, if there are pre-existing | |
/// triggers in groups "aaa" and "bbb" and a matcher is given to pause | |
/// groups that start with "a" then the group "aaa" will be remembered as | |
/// paused and any subsequently added triggers in that group be paused, | |
/// however if a trigger is added to group "axx" it will not be paused, | |
/// as "axx" wasn't known at the time the "group starts with a" matcher | |
/// was applied. HOWEVER, if there are pre-existing groups "aaa" and | |
/// "bbb" and a matcher is given to pause the group "axx" (with a | |
/// group equals matcher) then no triggers will be paused, but it will be | |
/// remembered that group "axx" is paused and later when a trigger is added | |
/// in that group, it will become paused.</para> | |
/// </remarks> | |
[Route("pause-triggers"), HttpPost] | |
public Task PauseTriggers([FromBody] SerializableGroupMatcher<TriggerKey> matcher, CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
return Scheduler.PauseTriggers(matcher, cancellationToken); | |
} | |
/// <summary> | |
/// Remove the trigger with the given key, and store the new given one | |
/// </summary> | |
/// <remarks> | |
/// Remove (delete) the trigger with the | |
/// given key, and store the new given one - which must be associated | |
/// with the same job (the new trigger must have the job name & group specified) - however, | |
/// the new trigger need not have the same name as the old trigger. | |
/// Returns <c>null</c> if a trigger with the given | |
/// name and group was not found and removed from the store (and the | |
/// new trigger is therefore not stored), otherwise | |
/// the first fire time of the newly scheduled trigger. | |
/// Očakáva sa Content-Type: multipart/form-data; s položkami 'triggerKey.type', 'triggerKey.data', 'newTrigger.type' a 'newTrigger.data'. | |
/// </remarks> | |
[Route("reschedule-job"), HttpPost] | |
public async Task<DateTimeOffset?> RescheduleJob(CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
var data = await DeserializeMultipart(cancellationToken); | |
var triggerKey = GetMultipartData<TriggerKey>(data, "triggerKey"); | |
var newTrigger = GetMultipartData<global::Quartz.ITrigger>(data, "newTrigger"); | |
return await Scheduler.RescheduleJob(triggerKey, newTrigger, cancellationToken); | |
} | |
/// <summary> | |
/// Resume (un-pause) all triggers - similar to calling | |
/// 'resume-triggers' on every group. | |
/// </summary> | |
/// <remarks> | |
/// If any trigger missed one or more fire-times, then the | |
/// triggers's misfire instruction will be applied. | |
/// </remarks> | |
[Route("resume-all"), HttpGet] | |
public Task ResumeAll(CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
return Scheduler.ResumeAll(cancellationToken); | |
} | |
/// <summary> | |
/// Resume (un-pause) the job with | |
/// the given key. | |
/// </summary> | |
/// <remarks> | |
/// If any of the job'strigger s missed one | |
/// or more fire-times, then the trigger's misfire | |
/// instruction will be applied. | |
/// </remarks> | |
[Route("resume-job"), HttpPost] | |
public Task ResumeJob([FromBody] JobKey jobKey, CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
return Scheduler.ResumeJob(jobKey, cancellationToken); | |
} | |
/// <summary> | |
/// Resume (un-pause) all of the jobs | |
/// in matching groups. | |
/// </summary> | |
/// <remarks> | |
/// If any of the job s had trigger s that | |
/// missed one or more fire-times, then the trigger's | |
/// misfire instruction will be applied. | |
/// </remarks> | |
[Route("resume-jobs"), HttpPost] | |
public Task ResumeJobs([FromBody] SerializableGroupMatcher<JobKey> matcher, CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
return Scheduler.ResumeJobs(matcher, cancellationToken); | |
} | |
/// <summary> | |
/// Resume (un-pause) the trigger with the given | |
/// key. | |
/// </summary> | |
/// <remarks> | |
/// If the trigger missed one or more fire-times, then the | |
/// trigger's misfire instruction will be applied. | |
/// </remarks> | |
[Route("resume-trigger"), HttpPost] | |
public Task ResumeTrigger([FromBody] TriggerKey triggerKey, CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
return Scheduler.ResumeTrigger(triggerKey, cancellationToken); | |
} | |
/// <summary> | |
/// Resume (un-pause) all of the triggers in matching groups. | |
/// </summary> | |
/// <remarks> | |
/// If any trigger missed one or more fire-times, then the | |
/// trigger's misfire instruction will be applied. | |
/// </remarks> | |
[Route("resume-triggers"), HttpPost] | |
public Task ResumeTriggers([FromBody] SerializableGroupMatcher<TriggerKey> matcher, CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
return Scheduler.ResumeTriggers(matcher, cancellationToken); | |
} | |
/// <summary> | |
/// Add the given job to the | |
/// Scheduler, and associate the given trigger with it. | |
/// </summary> | |
/// <remarks> | |
/// If the given Trigger does not reference any job, then it | |
/// will be set to reference the Job passed with it into this method. | |
/// Očakáva sa Content-Type: multipart/form-data; s položkami 'trigger.type', 'trigger.data' a nepovinné: 'jobDetail.type' a 'jobDetail.data'. | |
/// </remarks> | |
[Route("schedule-job"), HttpPost] | |
public async Task<DateTimeOffset> ScheduleJob(CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
var data = await DeserializeMultipart(cancellationToken); | |
var trigger = GetMultipartData<global::Quartz.ITrigger>(data, "trigger"); | |
if (data.ContainsKey("jobDetail")) | |
{ | |
var jobDetail = GetMultipartData<IJobDetail>(data, "jobDetail"); | |
return await Scheduler.ScheduleJob(jobDetail, trigger, cancellationToken); | |
} | |
else | |
{ | |
return await Scheduler.ScheduleJob(trigger, cancellationToken); | |
} | |
} | |
/// <summary> | |
/// Schedule all of the given jobs with the related set of triggers. | |
/// </summary> | |
/// <remarks> | |
/// If any of the given jobs or triggers already exist (or more | |
/// specifically, if the keys are not unique) and the replace | |
/// parameter is not set to true then an exception will be thrown. | |
/// </remarks> | |
[Route("schedule-jobs"), HttpPost] | |
public async Task ScheduleJobs(bool replace, CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
var triggersAndJobs = await GetMultipartData<object[]>("triggersAndJobs", cancellationToken); | |
var dict = new Dictionary<IJobDetail, IReadOnlyCollection<global::Quartz.ITrigger>>(); | |
for (int i = 0; i < triggersAndJobs.Length; i++) | |
{ | |
// polozky su na striedacku: job, triggers, job, triggers... | |
var key = triggersAndJobs[i]; | |
var value = triggersAndJobs[++i]; | |
dict[(IJobDetail)key] = (IReadOnlyCollection<global::Quartz.ITrigger>)value; | |
} | |
await Scheduler.ScheduleJobs(dict, replace, cancellationToken); | |
} | |
/// <summary> | |
/// Halts the scheduler's firing of triggers. | |
/// </summary> | |
/// <remarks> | |
/// Halts the scheduler's firing of triggers, | |
/// and cleans up all resources associated with the Scheduler. Equivalent to Shutdown(false). | |
/// The scheduler cannot be re-started. | |
/// </remarks> | |
[Route("shutdown-immediately"), HttpGet] | |
public Task Shutdown(CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
return Scheduler.Shutdown(cancellationToken); | |
} | |
/// <summary> | |
/// Halts the scheduler's firing of triggers. | |
/// </summary> | |
/// <remarks> | |
/// Halts the scheduler's firing of triggers, | |
/// and cleans up all resources associated with the Scheduler. | |
/// The scheduler cannot be re-started. | |
/// </remarks> | |
/// <param name="waitForJobsToComplete"> | |
/// if <c>true</c>, the scheduler will not allow this method | |
/// to return until all currently executing jobs have completed. | |
/// </param> | |
[Route("shutdown"), HttpGet] | |
public Task Shutdown(bool waitForJobsToComplete, CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
return Scheduler.Shutdown(waitForJobsToComplete, cancellationToken); | |
} | |
/// <summary> | |
/// Temporarily halts the scheduler's firing of triggers. | |
/// </summary> | |
/// <remarks> | |
/// <para> | |
/// When 'start' is called (to bring the scheduler out of | |
/// stand-by mode), trigger misfire instructions will NOT be applied | |
/// during the execution of the 'start' method - any misfires | |
/// will be detected immediately afterward | |
/// </para> | |
/// <para> | |
/// The scheduler is not destroyed, and can be re-started at any time. | |
/// </para> | |
/// </remarks> | |
[Route("standby"), HttpGet] | |
public Task Standby(CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
return Scheduler.Standby(cancellationToken); | |
} | |
/// <summary> | |
/// Starts the scheduler's threads that fire triggers. | |
/// </summary> | |
/// <remarks> | |
/// When a scheduler is first created it is in "stand-by" mode, and will not | |
/// fire triggers. The scheduler can also be put into stand-by mode by | |
/// calling the 'standby' method. | |
/// The misfire/recovery process will be started, if it is the initial call | |
/// to this method on this scheduler instance. | |
/// </remarks> | |
[Route("start"), HttpGet] | |
public Task Start(CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
return Scheduler.Start(cancellationToken); | |
} | |
/// <summary> | |
/// Calls 'start' after the indicated delay. | |
/// </summary> | |
/// <remarks> | |
/// This call does not block. This can be useful within applications that | |
/// have initializers that create the scheduler immediately, before the | |
/// resources needed by the executing jobs have been fully initialized. | |
/// </remarks> | |
/// <param name="delay">TimeSpan Ticks</param> | |
[Route("start-delayed"), HttpGet] | |
public Task StartDelayed([FromBody] long delay, CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
return Scheduler.StartDelayed(TimeSpan.FromTicks(delay), cancellationToken); | |
} | |
/// <summary> | |
/// Trigger the identified job (Execute it now). | |
/// </summary> | |
/// <remarks> | |
/// Očakáva sa Content-Type: multipart/form-data; s položkami 'jobKey.type', 'jobKey.data' a nepovinné: 'jobDataMap.type' a 'jobDataMap.data'. | |
/// </remarks> | |
[Route("trigger-job"), HttpPost] | |
public async Task TriggerJob(CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
var multipart = await DeserializeMultipart(cancellationToken); | |
JobKey jobKey = null; | |
JobDataMap jobDataMap = null; | |
jobKey = GetMultipartData<JobKey>(multipart, nameof(jobKey)); | |
if (multipart.ContainsKey(nameof(jobDataMap))) | |
jobDataMap = GetMultipartData<JobDataMap>(multipart, nameof(jobDataMap)); | |
await Scheduler.TriggerJob(jobKey, jobDataMap, cancellationToken); | |
} | |
/// <summary> | |
/// Remove the indicated trigger from the scheduler. | |
/// </summary> | |
/// <remarks> | |
/// If the related job does not have any other triggers, and the job is | |
/// not durable, then the job will also be deleted. | |
/// </remarks> | |
[Route("unschedule-job"), HttpPost] | |
public Task<bool> UnscheduleJob([FromBody] TriggerKey triggerKey, CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
return Scheduler.UnscheduleJob(triggerKey); | |
} | |
/// <summary> | |
/// Remove all of the indicated triggers from the scheduler. | |
/// </summary> | |
/// <remarks> | |
/// <para> | |
/// If the related job does not have any other triggers, and the job is | |
/// not durable, then the job will also be deleted. | |
/// </para> | |
/// Note that while this bulk operation is likely more efficient than | |
/// invoking 'unschedule-job' several | |
/// times, it may have the adverse affect of holding data locks for a | |
/// single long duration of time (rather than lots of small durations | |
/// of time). | |
/// </remarks> | |
[Route("unschedule-jobs"), HttpPost] | |
public Task<bool> UnscheduleJobs([FromBody] IReadOnlyCollection<TriggerKey> triggerKeys, CancellationToken cancellationToken = default(CancellationToken)) | |
{ | |
return Scheduler.UnscheduleJobs(triggerKeys, cancellationToken); | |
} | |
#region Multipart Serialization | |
private async Task<T> GetMultipartData<T>(string key, CancellationToken cancellationToken) | |
{ | |
return GetMultipartData<T>(await DeserializeMultipart(cancellationToken), key); | |
} | |
private T GetMultipartData<T>(Dictionary<string, Tuple<Type, object>> multipart, string key) | |
{ | |
if (!multipart.ContainsKey(key)) | |
throw new InvalidOperationException($"Missing content disposition named '{key}.type' and '{key}.data'"); | |
Tuple<Type, object> data = multipart[key]; | |
if (!typeof(T).IsAssignableFrom(data.Item1)) | |
throw new InvalidOperationException($"{typeof(T)} is not assignable from {data.Item1}"); | |
return (T)data.Item2; | |
} | |
private async Task<Dictionary<string, Tuple<Type, object>>> DeserializeMultipart(CancellationToken cancellationToken) | |
{ | |
/* | |
Expected form-data: | |
calendar.type = "Quartz.Impl.Calendar.DailyCalendar, Quartz" | |
calendar.data = "{"$type": ...}" | |
*/ | |
Dictionary<string, string> typeDict = new Dictionary<string, string>(); | |
Dictionary<string, byte[]> dataDict = new Dictionary<string, byte[]>(); | |
var multipart = (await Request.Content.ReadAsMultipartAsync(cancellationToken)); | |
foreach (var item in multipart.Contents) | |
{ | |
string contentName = item.Headers.ContentDisposition.Name.Trim('"', '\''); | |
var contentNameParts = contentName.Split('.'); | |
if (contentNameParts.Length != 2) | |
throw new InvalidOperationException("Invalid content disposition name."); | |
string key = contentNameParts.First(); | |
string field = contentNameParts.Last(); | |
if (field.Equals("type", StringComparison.OrdinalIgnoreCase)) | |
typeDict.Add(key, await item.ReadAsStringAsync()); | |
if (field.Equals("data", StringComparison.OrdinalIgnoreCase)) | |
dataDict.Add(key, await item.ReadAsByteArrayAsync()); | |
} | |
Dictionary<string, Tuple<Type, object>> result = new Dictionary<string, Tuple<Type, object>>(StringComparer.OrdinalIgnoreCase); | |
foreach (var key in typeDict.Keys) | |
{ | |
Type type = Type.GetType(typeDict[key], true); | |
if (!dataDict.ContainsKey(key)) | |
throw new InvalidOperationException($"Missing content disposition named '{key}.data'"); | |
result.Add(key, Tuple.Create(type, SerializationHelper.Deserialize(type, dataDict[key]))); | |
} | |
return result; | |
} | |
HttpResponseMessage CreateMultipartResponse<T>(T data) where T : class | |
{ | |
if (data == null) | |
return new HttpResponseMessage(HttpStatusCode.NoContent); | |
string typeName = SerializationHelper.RemoveAssemblyDetails(data.GetType()); | |
MultipartFormDataContent multiPartContent = new MultipartFormDataContent(); | |
multiPartContent.Add(new ByteArrayContent(Encoding.Default.GetBytes(typeName)), "type"); | |
multiPartContent.Add(new ByteArrayContent(SerializationHelper.Serialize(data)), "data"); | |
return new HttpResponseMessage(HttpStatusCode.OK) | |
{ | |
Content = multiPartContent | |
}; | |
} | |
#endregion | |
} | |
} | |
public class SerializableGroupMatcher<TKey> | |
where TKey : Key<TKey> | |
{ | |
public string CompareToValue { get; set; } | |
public string CompareWithOperator { get; set; } | |
static readonly Dictionary<StringOperator, string> stringOperatorMap = new Dictionary<StringOperator, string>() { | |
{ StringOperator.Equality, nameof(StringOperator.Equality) }, | |
{ StringOperator.StartsWith, nameof(StringOperator.StartsWith) }, | |
{ StringOperator.EndsWith, nameof(StringOperator.EndsWith) }, | |
{ StringOperator.Contains, nameof(StringOperator.Contains) }, | |
{ StringOperator.Anything, nameof(StringOperator.Anything) }, | |
}; | |
public static implicit operator SerializableGroupMatcher<TKey>(GroupMatcher<TKey> matcher) | |
{ | |
return new SerializableGroupMatcher<TKey>() | |
{ | |
CompareToValue = matcher.CompareToValue, | |
CompareWithOperator = stringOperatorMap[matcher.CompareWithOperator] | |
}; | |
} | |
public static implicit operator GroupMatcher<TKey>(SerializableGroupMatcher<TKey> matcher) | |
{ | |
switch (matcher.CompareWithOperator) | |
{ | |
case nameof(StringOperator.Anything): | |
return GroupMatcher<TKey>.AnyGroup(); | |
case nameof(StringOperator.Contains): | |
return GroupMatcher<TKey>.GroupContains(matcher.CompareToValue); | |
case nameof(StringOperator.EndsWith): | |
return GroupMatcher<TKey>.GroupEndsWith(matcher.CompareToValue); | |
case nameof(StringOperator.Equality): | |
return GroupMatcher<TKey>.GroupEquals(matcher.CompareToValue); | |
case nameof(StringOperator.StartsWith): | |
return GroupMatcher<TKey>.GroupStartsWith(matcher.CompareToValue); | |
default: | |
throw new ArgumentException("Invalid Operator: " + matcher.CompareWithOperator); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment