2017-09-25 21:00:08 +03:00
/ * 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>
2018-03-12 14:32:29 +03:00
private static readonly object SyncRoot = new object ( ) ;
2017-09-25 21:00:08 +03:00
/// <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 )
{
2018-03-12 14:32:29 +03:00
lock ( SyncRoot )
2017-09-25 21:00:08 +03:00
{
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 ) ) ;
2018-03-12 14:32:29 +03:00
if ( _scheduler = = null | | ! _schedulerFactory . GetAllSchedulers ( ) . Result . Contains ( _scheduler ) )
_scheduler = _schedulerFactory . GetScheduler ( Address ) . Result ? ? _schedulerFactory . GetScheduler ( ) . Result ;
2017-09-25 21:00:08 +03:00
}
catch ( Exception ex )
{
MessageBox . Show ( ex . Message , Resources . Connection_error ) ;
}
}
/// <summary>
/// Connects this instance.
/// </summary>
public void Connect ( )
{
try
{
_schedulerFactory = new StdSchedulerFactory ( ) ;
2018-03-12 14:32:29 +03:00
_scheduler = _schedulerFactory . GetScheduler ( ) . Result ;
2017-09-25 21:00:08 +03:00
}
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 ( )
{
2018-03-12 14:32:29 +03:00
DataTable table ;
2017-09-25 21:00:08 +03:00
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" ) ;
2018-03-12 14:32:29 +03:00
var jobGroups = GetScheduler ( ) . GetJobGroupNames ( ) . Result ;
2017-09-25 21:00:08 +03:00
foreach ( var group in jobGroups )
{
var groupMatcher = GroupMatcher < JobKey > . GroupContains ( group ) ;
2018-03-12 14:32:29 +03:00
var jobKeys = GetScheduler ( ) . GetJobKeys ( groupMatcher ) . Result ;
2017-09-25 21:00:08 +03:00
foreach ( var jobKey in jobKeys )
{
2018-03-12 14:32:29 +03:00
var detail = GetScheduler ( ) . GetJobDetail ( jobKey ) . Result ;
2017-09-25 21:00:08 +03:00
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" ] ;
2018-03-12 14:32:29 +03:00
var triggers = GetScheduler ( ) . GetTriggersOfJob ( jobKey ) . Result ;
2018-07-12 01:14:30 +03:00
if ( triggers . Count > 0 )
{
var nextFireTime = triggers . First ( ) . GetNextFireTimeUtc ( ) ;
if ( nextFireTime . HasValue )
row [ "NextFireTime" ] = TimeZone . CurrentTimeZone . ToLocalTime ( nextFireTime . Value . DateTime ) ;
2017-09-25 21:00:08 +03:00
2018-07-12 01:14:30 +03:00
var previousFireTime = triggers . First ( ) . GetPreviousFireTimeUtc ( ) ;
if ( previousFireTime . HasValue )
row [ "PreviousFireTime" ] =
TimeZone . CurrentTimeZone . ToLocalTime ( previousFireTime . Value . DateTime ) ;
2019-02-22 22:34:08 +03:00
var status = GetScheduler ( ) . GetTriggerState ( triggers . First ( ) . Key ) . Result . ToString ( ) ;
if ( status = = "Blocked" )
status = "Executing" ;
row [ "JobStatus" ] = status ;
2018-07-12 01:14:30 +03:00
}
2017-09-25 21:00:08 +03:00
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." ) ;
}
2018-03-12 14:32:29 +03:00
var jobGroupNames = scheduler . GetJobGroupNames ( ) . Result ;
2017-09-25 21:00:08 +03:00
var jobDetails = new List < IJobDetail > ( ) ;
2018-03-12 14:32:29 +03:00
var triggerGroupNames = scheduler . GetTriggerGroupNames ( ) . Result ;
2017-09-25 21:00:08 +03:00
var triggerDetails = new List < ITrigger > ( ) ;
foreach ( var jobGroup in jobGroupNames )
{
var groupMatcher = GroupMatcher < JobKey > . GroupContains ( jobGroup ) ;
2018-03-12 14:32:29 +03:00
var jobKeys = scheduler . GetJobKeys ( groupMatcher ) . Result ;
jobDetails . AddRange ( jobKeys . Select ( jobKey = > scheduler . GetJobDetail ( jobKey ) . Result ) ) ;
2017-09-25 21:00:08 +03:00
}
foreach ( var triggerGroup in triggerGroupNames )
{
var groupMatcher = GroupMatcher < TriggerKey > . GroupContains ( triggerGroup ) ;
2018-03-12 14:32:29 +03:00
var keys = scheduler . GetTriggerKeys ( groupMatcher ) . Result ;
triggerDetails . AddRange ( keys . Select ( triggerKey = > scheduler . GetTrigger ( triggerKey ) . Result ) ) ;
2017-09-25 21:00:08 +03:00
}
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 )
{
2019-12-13 18:27:46 +03:00
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 )
)
2017-09-25 21:00:08 +03:00
) ;
2019-12-13 18:27:46 +03:00
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 ) ) ;
2017-09-25 21:00:08 +03:00
}
2019-12-13 18:27:46 +03:00
doc . Root ? . Add ( schedule ) ;
doc . Save ( writer ) ;
2017-09-25 21:00:08 +03:00
}
/// <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 ) ,
2020-06-03 19:16:45 +03:00
new XElement ( ns + "time-zone" , trigger . TimeZone . Id )
2017-09-25 21:00:08 +03:00
) ;
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 )
{
2019-12-13 18:27:46 +03:00
return misfireInstruction switch
2017-09-25 21:00:08 +03:00
{
2019-12-13 18:27:46 +03:00
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." ) ,
} ;
2017-09-25 21:00:08 +03:00
}
/// <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 )
{
2019-12-13 18:27:46 +03:00
return misfireInstruction switch
2017-09-25 21:00:08 +03:00
{
2019-12-13 18:27:46 +03:00
0 = > "SmartPolicy" ,
1 = > "FireOnceNow" ,
2 = > "DoNothing" ,
_ = > throw new ArgumentOutOfRangeException ( $"{misfireInstruction} is not a supported misfire instruction for CronTrigger See Quartz.MisfireInstruction for more details." ) ,
} ;
2017-09-25 21:00:08 +03:00
}
/// <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 ;
}
}
}