Recurring-Integrations-Sche.../Scheduler/Scheduler.cs

434 строки
17 KiB
C#

/* Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the MIT License. */
using Quartz;
using Quartz.Impl;
using Quartz.Impl.Matchers;
using Quartz.Impl.Triggers;
using RecurringIntegrationsScheduler.Properties;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Data;
using System.IO;
using System.Linq;
using System.Windows.Forms;
using System.Xml.Linq;
namespace RecurringIntegrationsScheduler
{
/// <summary>
/// Instantiated as singleton
/// </summary>
public sealed class Scheduler
{
/// <summary>
/// The instance
/// </summary>
private static volatile Scheduler _instance;
/// <summary>
/// The scheduler
/// </summary>
private IScheduler _scheduler;
/// <summary>
/// The scheduler factory
/// </summary>
private ISchedulerFactory _schedulerFactory;
/// <summary>
/// Synchro object
/// </summary>
private static readonly object SyncRoot = new object();
/// <summary>
/// Prevents a default instance of the <see cref="Scheduler"/> class from being created.
/// </summary>
private Scheduler()
{
}
/// <summary>
/// Gets the address.
/// </summary>
/// <value>
/// The address.
/// </value>
public string Address { get; private set; }
/// <summary>
/// Gets the instance.
/// </summary>
/// <value>
/// The instance.
/// </value>
public static Scheduler Instance
{
get
{
if (_instance == null)
{
lock (SyncRoot)
{
if (_instance == null)
_instance = new Scheduler();
}
}
return _instance;
}
}
/// <summary>
/// Connects the specified server.
/// </summary>
/// <param name="server">The server.</param>
/// <param name="port">The port.</param>
/// <param name="scheduler">The scheduler.</param>
public void Connect(string server, int port, string scheduler)
{
try
{
Address = $"tcp://{server}:{port}/{scheduler}";
_schedulerFactory = new StdSchedulerFactory(GetProperties(Address));
if (_scheduler == null || !_schedulerFactory.GetAllSchedulers().Result.Contains(_scheduler))
_scheduler = _schedulerFactory.GetScheduler(Address).Result ?? _schedulerFactory.GetScheduler().Result;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, Resources.Connection_error);
}
}
/// <summary>
/// Connects this instance.
/// </summary>
public void Connect()
{
try
{
_schedulerFactory = new StdSchedulerFactory();
_scheduler = _schedulerFactory.GetScheduler().Result;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, Resources.Connection_error);
}
}
/// <summary>
/// Gets the scheduler.
/// </summary>
/// <returns></returns>
public IScheduler GetScheduler()
{
return _scheduler;
}
/// <summary>
/// Gets the jobs.
/// </summary>
/// <returns></returns>
public DataTable GetJobs()
{
DataTable table;
try
{
table = new DataTable();
table.Columns.Add("JobName");
table.Columns.Add("JobGroup");
table.Columns.Add("JobDescription");
table.Columns.Add("JobType");
table.Columns.Add("JobStatus");
table.Columns.Add("NextFireTime");
table.Columns.Add("PreviousFireTime");
table.Columns.Add("Instance");
var jobGroups = GetScheduler().GetJobGroupNames().Result;
foreach (var group in jobGroups)
{
var groupMatcher = GroupMatcher<JobKey>.GroupContains(group);
var jobKeys = GetScheduler().GetJobKeys(groupMatcher).Result;
foreach (var jobKey in jobKeys)
{
var detail = GetScheduler().GetJobDetail(jobKey).Result;
var row = table.NewRow();
row["JobGroup"] = group;
row["JobName"] = jobKey.Name;
row["JobDescription"] = detail.Description;
row["JobType"] = detail.JobType.Name;
row["Instance"] = detail.JobDataMap["AosUri"];
var triggers = GetScheduler().GetTriggersOfJob(jobKey).Result;
if (triggers.Count > 0)
{
var nextFireTime = triggers.First().GetNextFireTimeUtc();
if (nextFireTime.HasValue)
row["NextFireTime"] = TimeZone.CurrentTimeZone.ToLocalTime(nextFireTime.Value.DateTime);
var previousFireTime = triggers.First().GetPreviousFireTimeUtc();
if (previousFireTime.HasValue)
row["PreviousFireTime"] =
TimeZone.CurrentTimeZone.ToLocalTime(previousFireTime.Value.DateTime);
var status = GetScheduler().GetTriggerState(triggers.First().Key).Result.ToString();
if (status == "Blocked")
status = "Executing";
row["JobStatus"] = status;
}
table.Rows.Add(row);
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, Resources.Unexpected_error);
table = null;
}
return table;
}
/// <summary>
/// Backups to file.
/// </summary>
/// <param name="file">The file.</param>
public void BackupToFile(FileInfo file)
{
try
{
var scheduler = GetScheduler();
if (scheduler == null)
{
throw new ArgumentException("Scheduler is missing.");
}
var jobGroupNames = scheduler.GetJobGroupNames().Result;
var jobDetails = new List<IJobDetail>();
var triggerGroupNames = scheduler.GetTriggerGroupNames().Result;
var triggerDetails = new List<ITrigger>();
foreach (var jobGroup in jobGroupNames)
{
var groupMatcher = GroupMatcher<JobKey>.GroupContains(jobGroup);
var jobKeys = scheduler.GetJobKeys(groupMatcher).Result;
jobDetails.AddRange(jobKeys.Select(jobKey => scheduler.GetJobDetail(jobKey).Result));
}
foreach (var triggerGroup in triggerGroupNames)
{
var groupMatcher = GroupMatcher<TriggerKey>.GroupContains(triggerGroup);
var keys = scheduler.GetTriggerKeys(groupMatcher).Result;
triggerDetails.AddRange(keys.Select(triggerKey => scheduler.GetTrigger(triggerKey).Result));
}
WriteToFile(file, jobDetails, triggerDetails);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, Resources.Unexpected_error);
}
}
/// <summary>
/// Gets the properties.
/// </summary>
/// <param name="address">The address.</param>
/// <returns></returns>
private static NameValueCollection GetProperties(string address)
{
var properties = new NameValueCollection
{
["quartz.scheduler.instanceName"] = "RemoteClient",
["quartz.scheduler.proxy"] = "true",
["quartz.threadPool.threadCount"] = "0",
["quartz.scheduler.proxy.address"] = address
};
return properties;
}
/// <summary>
/// Writes to file.
/// </summary>
/// <param name="file">The file.</param>
/// <param name="jobDetails">The job details.</param>
/// <param name="triggerDetails">The trigger details.</param>
private static void WriteToFile(FileInfo file, List<IJobDetail> jobDetails, List<ITrigger> triggerDetails)
{
using var writer = file.CreateText();
XNamespace ns = "http://quartznet.sourceforge.net/JobSchedulingData";
var doc = new XDocument(new XDeclaration("1.0", "UTF-8", "yes")
, new XElement(ns + "job-scheduling-data"
, new XAttribute(XNamespace.Xmlns + "xsi", "http://www.w3.org/2001/XMLSchema-instance")
, new XAttribute("version", "2.0")
));
doc.Root?.Add(new XElement(ns + "processing-directives"
, new XElement(ns + "overwrite-existing-data", true))
);
var schedule = new XElement(ns + "schedule");
foreach (var detail in jobDetails)
schedule.Add(
new XElement(ns + "job"
, new XElement(ns + "name", detail.Key.Name)
, new XElement(ns + "group", detail.Key.Group)
, new XElement(ns + "description", detail.Description)
,
new XElement(ns + "job-type",
detail.JobType.FullName + "," + detail.JobType.Assembly.FullName)
, new XElement(ns + "durable", detail.Durable)
, new XElement(ns + "recover", detail.RequestsRecovery)
, GetJobDataMap(ns, detail.JobDataMap)
)
);
foreach (var trigger in triggerDetails)
{
if (trigger is SimpleTriggerImpl simple)
schedule.Add(GetSimpleTrigger(ns, simple));
if (trigger is CronTriggerImpl cron)
schedule.Add(GetCronTrigger(ns, cron));
if (trigger is CalendarIntervalTriggerImpl calendar)
schedule.Add(GetCalendarIntervalTrigger(ns, calendar));
}
doc.Root?.Add(schedule);
doc.Save(writer);
}
/// <summary>
/// Gets the job data map.
/// </summary>
/// <param name="ns">The ns.</param>
/// <param name="jobDataMap">The job data map.</param>
/// <returns></returns>
private static XElement GetJobDataMap(XNamespace ns, JobDataMap jobDataMap)
{
var map = new XElement(ns + "job-data-map");
foreach (var key in jobDataMap.GetKeys())
map.Add(new XElement(ns + "entry"
, new XElement(ns + "key", key)
, new XElement(ns + "value", jobDataMap[key])
)
);
return map;
}
/// <summary>
/// Gets the cron trigger.
/// </summary>
/// <param name="ns">The ns.</param>
/// <param name="trigger">The trigger.</param>
/// <returns></returns>
private static XElement GetCronTrigger(XNamespace ns, CronTriggerImpl trigger)
{
var cronTrigger = new XElement(ns + "trigger");
var cron = new XElement(ns + "cron");
cronTrigger.Add(cron);
AddCommonTriggerData(ns, cron, trigger);
cron.Add(
new XElement(ns + "cron-expression", trigger.CronExpressionString),
new XElement(ns + "time-zone", trigger.TimeZone.Id)
);
return cronTrigger;
}
/// <summary>
/// Gets the calendar interval trigger.
/// </summary>
/// <param name="ns">The ns.</param>
/// <param name="trigger">The trigger.</param>
/// <returns></returns>
private static XElement GetCalendarIntervalTrigger(XNamespace ns, CalendarIntervalTriggerImpl trigger)
{
var calendarTrigger = new XElement(ns + "trigger");
var calendar = new XElement(ns + "calendar-interval");
calendarTrigger.Add(calendar);
AddCommonTriggerData(ns, calendar, trigger);
calendar.Add(
new XElement(ns + "repeat-interval", trigger.RepeatInterval),
new XElement(ns + "repeat-interval-unit", trigger.RepeatIntervalUnit)
);
return calendarTrigger;
}
/// <summary>
/// Adds the common trigger data.
/// </summary>
/// <param name="ns">The ns.</param>
/// <param name="rootTriggerElement">The root trigger element.</param>
/// <param name="trigger">The trigger.</param>
private static void AddCommonTriggerData(XNamespace ns, XElement rootTriggerElement, AbstractTrigger trigger)
{
rootTriggerElement.Add(
new XElement(ns + "name", trigger.Key.Name)
, new XElement(ns + "group", trigger.Key.Group)
, new XElement(ns + "description", trigger.Description)
, new XElement(ns + "job-name", trigger.JobName)
, new XElement(ns + "job-group", trigger.JobGroup)
, new XElement(ns + "misfire-instruction", GetMisfireInstructionText(trigger))
);
}
/// <summary>
/// Gets the misfire instruction text.
/// </summary>
/// <param name="trigger">The trigger.</param>
/// <returns></returns>
private static string GetMisfireInstructionText(AbstractTrigger trigger)
{
if (trigger is CronTriggerImpl)
return GetCronTriggerMisfireInstructionText(trigger.MisfireInstruction);
return GetSimpleTriggerMisfireInstructionText(trigger.MisfireInstruction);
}
/// <summary>
/// Gets the simple trigger misfire instruction text.
/// </summary>
/// <param name="misfireInstruction">The misfire instruction.</param>
/// <returns></returns>
/// <exception cref="System.ArgumentOutOfRangeException"></exception>
private static string GetSimpleTriggerMisfireInstructionText(int misfireInstruction)
{
return misfireInstruction switch
{
0 => "SmartPolicy",
1 => "FireNow",
2 => "RescheduleNowWithExistingRepeatCount",
3 => "RescheduleNowWithRemainingRepeatCount",
4 => "RescheduleNextWithRemainingCount",
5 => "RescheduleNextWithExistingCount",
_ => throw new ArgumentOutOfRangeException($"{misfireInstruction} is not a supported misfire instruction for SimpleTrigger See Quartz.MisfireInstruction for more details."),
};
}
/// <summary>
/// Gets the cron trigger misfire instruction text.
/// </summary>
/// <param name="misfireInstruction">The misfire instruction.</param>
/// <returns></returns>
/// <exception cref="System.ArgumentOutOfRangeException"></exception>
private static string GetCronTriggerMisfireInstructionText(int misfireInstruction)
{
return misfireInstruction switch
{
0 => "SmartPolicy",
1 => "FireOnceNow",
2 => "DoNothing",
_ => throw new ArgumentOutOfRangeException($"{misfireInstruction} is not a supported misfire instruction for CronTrigger See Quartz.MisfireInstruction for more details."),
};
}
/// <summary>
/// Gets the simple trigger.
/// </summary>
/// <param name="ns">The ns.</param>
/// <param name="trigger">The trigger.</param>
/// <returns></returns>
private static XElement GetSimpleTrigger(XNamespace ns, SimpleTriggerImpl trigger)
{
var simpleTrigger = new XElement(ns + "trigger");
var simple = new XElement(ns + "simple");
simpleTrigger.Add(simple);
AddCommonTriggerData(ns, simple, trigger);
simple.Add(
new XElement(ns + "repeat-count", trigger.RepeatCount),
new XElement(ns + "repeat-interval", trigger.RepeatInterval.TotalMilliseconds)
);
return simpleTrigger;
}
}
}