Configuring a DirectoryScanJob in Quartz.Net 2.0

In today’s post I will explain how to configure a DirectoryScanJob in Quartz.Net 2.0. Configuring the DirectoryScanJob can be done either programmatically or using the quartz_jobs.xml file. We will cover how to configure the job using the xml file in this post as it is not exactly straightforward to do so. If you’re interested in a post on how to configure the job programmatically, please leave a comment.

How to Configure the DirectoryScanJob using quartz_jobs.xml

For our example we will configure a DirectoryScanJob that looks at given directory for changes and then writes out to the log how many files were updated. Then we will add a file to the directory and we will see what happens on the scheduler. Let’s get started.
First, we’ll create the folder that will be our scan folder: C:\FolderToScan.
Now, let’s create the quartz_jobs.xml file and configure it appropriately. It will look something like this:
   1:  <schedule>


   2:   


   3:  <job>


   4:    <name>directoryScanJobExample</name>


   5:    <group>directoryScanJobExample</group>


   6:    <description>Sample job for Quartz Server</description>


   7:    <job-type>Quartz.Job.DirectoryScanJob, Quartz</job-type>


   8:    <job-data-map>


   9:      <entry>


  10:        <key>DIRECTORY_NAME</key>


  11:        <value>C:\FolderToScan</value>


  12:      </entry>


  13:      <entry>


  14:        <key>DIRECTORY_SCAN_LISTENER_NAME</key>


  15:        <value>DirectoryScanListenerExample</value>


  16:      </entry>


  17:    </job-data-map>


  18:  </job>


  19:   


  20:  <trigger>


  21:    <simple>


  22:      <name>directoryScanJobExampleSimpleTrigger</name>


  23:      <group>directoryScanJobExampleSimpleTriggerGroup</group>


  24:      <description>Simple trigger to simply fire sample job</description>


  25:      <job-name>directoryScanJobExample</job-name>


  26:      <job-group>directoryScanJobExample</job-group>


  27:      <misfire-instruction>SmartPolicy</misfire-instruction>


  28:      <repeat-count>-1</repeat-count>


  29:      <repeat-interval>10000</repeat-interval>


  30:    </simple>


  31:  </trigger>


  32:   


  33:  <job>


  34:    <name>addDirectoryScanListener</name>


  35:    <group>directoryScanJobExample</group>


  36:    <description>Sample job for Quartz Server</description>


  37:    <job-type>Examples.DirectoryScanListenerExample, Examples</job-type>


  38:  </job>


  39:   


  40:  <trigger>


  41:    <simple>


  42:      <name>addDirectoryScanListenerSimpleTrigger</name>


  43:      <group>directoryScanJobExampleSimpleTriggerGroup</group>


  44:      <description>Simple trigger to simply fire sample job</description>


  45:      <job-name>addDirectoryScanListener</job-name>


  46:      <job-group>directoryScanJobExample</job-group>


  47:      <misfire-instruction>SmartPolicy</misfire-instruction>


  48:      <repeat-count>0</repeat-count>


  49:      <repeat-interval>10000</repeat-interval>


  50:    </simple>


  51:  </trigger>


  52:  </schedule>




Lines 3-31 of the xml file are used to define the DirectoryScanJob and to set the required properties on it. The job is set to look at the C:\FolderToScan folder that we created earlier (lines 9-12). The listener that will be notified when files are updated in the folder is called DirectoryScanListenerExample (lines 13-16). This is just the listener’s friendly name (the key used to look it up actually). We could have called it Jim.  Next up is the trigger definition (lines 20-31), which creates a SimpleTrigger that fires every 10 seconds until the scheduler is stopped.


Right after the configuration of the DirectoryScanJob you will find another job configuration. This extra job is necessary because we have no way other way of adding the DirectoryScanListener to the scheduler through the quartz_jobs.xml. What we have done is created a job that runs once and adds the DirectoryScanListener to the scheduler. Let’s take a look at the code for Examples.DirectoryScanListenerExample, which is both the job that adds the listener as well as the listener itself. This is done by implementing both IJob and IDirectoryScanListener. You probably don’t want to do this in your production code, but for a quick example it works fine. Here’s the code for DirectoryListenerExample:


public class DirectoryScanListenerExample : IDirectoryScanListener, IJob
{
public void FilesUpdatedOrAdded(FileInfo[] updatedFiles)
{
logger.InfoFormat("Found {0} updated files", updatedFiles.Length);
}

public void Execute(IJobExecutionContext context)
{
logger.Info("Adding the listener to the context");
context.Scheduler.Context.Add("DirectoryScanListenerExample", new DirectoryScanListenerExample());
logger.Info("Added the listener to the context");
}

private static readonly ILog logger = LogManager.GetLogger(typeof(JobListenerExample));
}



This implementation of the IDirectoryScanListener is quite simple. The FilesUpdatedOrAdded method gets called when the job detects changes and it prints out to the log how many files were updated. The Execute method is called once, and here is where we add the listener to the scheduler’s context. The listener’s name is hard coded here, but you could also make that a parameter that gets passed in from the quartz_jobs.xml file via the job data map.


Running the DirectoryScanJob



Let’s start up our scheduler with this quartz_jobs.xml and see what happens. Here is the output from the scheduler’s start up process (I’ve removed the non interesting bits…).



   1:  Parsing XML file: C:\quartznet2\quartz_jobs.xml with systemId: ~/quartz_jobs.xml


   2:  Adding 2 jobs, 2 triggers.


   3:  Adding job: directoryScanJobExample.addDirectoryScanListener


   4:  Adding job: directoryScanJobExample.directoryScanJobExample


   5:  Scheduler ServerScheduler_$_NON_CLUSTERED started.


   6:  Adding the listener to the context


   7:  Job directoryScanJobExample.directoryScanJob


   8:  Example threw a JobExecutionException:


   9:  Parameters: refire = False, unscheduleFiringTrigger = False, unscheduleAllTriggers = False


  10:   Quartz.JobExecutionException: DirectoryScanListener named 'DirectoryScanListenerExample' not found in SchedulerContext


  11:     at Quartz.Job.DirectoryScanJob.Execute(IJobExecutionContext context) in C:\git\quartznet\src\Quartz\Job\DirectoryScanJob.cs:line 90


  12:     at Quartz.Core.JobRunShell.Run() in C:\git\quartznet\src\Quartz\Core\JobRunShell.cs:line 188


  13:  Added the listener to the context




Lines 1 – 5 show us the scheduler starting, the xml file being parsed and the jobs being added. So far so good. It gets a little confusing from now on because we have several threads running, but I think we can make sense of it. Line 6 tells us that the job to add the listener has kicked off. However, lines 7 – 12 tell us that the scan job is trying to run, but it fails to run because the listener is not available. That’s because the job that adds the listener is not finished yet. Line 13 tells us that the listener is ready and has been added to the scheduler. From now on, the scan job will run successfully because it has its listener in place. Let’s add a file to the folder and see what happens.


After adding a file to the folder, we see the following:


Directory 'C:\FolderToScan' contents updated, notifying listener.
Found 1 updated files



This is what we expected, so all is good. We’ve successfully configured a DirectoryScanJob and a listener using the quartz_jobs.xml.

Continue Reading

Implementing a Job Listener in Quartz.Net 2.0

In this post we will be implementing the job listener that we discussed in the previous post. The previous post showed us how to add a scheduler listener to the scheduler using a plugin. Now we will describe what you need to do to implement a job listener in Quartz.Net 2.0. If you’re interested in learning how to implement a job listener in version 1.0 of Quartz.Net, take a look at this post.

Implementing the IJobListener Interface

To create a job listener, we must write a class that implements the IJobListener interface. This interface is defined as follows:
public interface IJobListener
{
string Name { get; }
void JobExecutionVetoed(IJobExecutionContext context);
void JobToBeExecuted(IJobExecutionContext context);
void JobWasExecuted(IJobExecutionContext context, JobExecutionException jobException);
}


The Name property will hold the name of the job listener and is read-only. The rest of the interface is what is of most interest to us. These three methods give you the opportunity to interact with a job during its lifecycle. The first method, JobExecutionVetoed gets called whenever a trigger listener vetoes the execution of a job. If this method is called, no other methods in this interface will be called. The second method, JobToBeExecuted, is called right before the job is about to be executed. The third method, JobWasExecuted, is called after the job has been executed. You do not have to provide implementations for all 3 methods unless you want to, but the methods do need to be present in your class to satisfy the interface.


The JobListenerExample



In our example we will provide very simple implementations of these methods, only to illustrate how to build a listener. All the JobListenerExample does is log an informational message whenever one of the methods of the interface are called. The code for the simple listener is listed below.


class JobListenerExample : IJobListener
{
public void JobExecutionVetoed(IJobExecutionContext context)
{
logger.Info("JobExecutionVetoed");
}
public void JobToBeExecuted(IJobExecutionContext context)
{
logger.Info("JobToBeExecuted");
}
public void JobWasExecuted(IJobExecutionContext context, JobExecutionException jobException)
{
logger.Info("JobWasExecuted");
}
public string Name
{
get { return "JobListenerExample"; }
}
private static readonly ILog logger = LogManager.GetLogger(typeof(JobListenerExample));
}



If you would like a more in depth example of a job listener that actually does something useful, take look at this post, which describes how to implement a job listener that logs job execution information to a database. The example is for Quartz.Net 1.0, but you’ll notice that the only difference between the two interfaces is that the method signatures are now defined using interfaces instead of concrete types.


Loading and Executing the Listener



In order to load and execute your listener, you will have to add it to the scheduler upon start up. You must also specify whether it should executed for all jobs, or for some job in particular. How to do this using a plugin is described in the previous post. Finally, you must make sure that the assembly that contains your listener is available somewhere that the scheduler can load it upon startup.

Continue Reading

Implementing a Plugin in Quartz.Net 2.0

Today we will implement a plugin for Quartz.Net 2.0. If you’re looking for a way to implement a plugin for version 1.0 of Quartz.Net, take a look at this post. Most of the steps are the same, but there are some API changes, so the plugin isn’t exactly the same, as you will see.
What are plugins used for in Quartz.Net 2.0? Well, plugins come in handy if there is something you want to do whenever the scheduler is started or stopped. Some examples of handy plugins are:

  • plugins to load jobs into a RAM jobstore upon scheduler startup (this one is available out of the box)
  • plugins to register listeners (listeners must be registered with the scheduler upon startup since they are not persisted)
In order to implement a listener in Quartz.Net 2.0 there are several steps we need to take. First we must implement the ISchedulerPlugin interface and then we must configure the scheduler to load our plugin. Let’s get started.

Implementing the ISchedulerPlugin Interface

In order to create your own plugin, you have to implement the ISchedulerPlugin interface. Here is the ISchedulerPlugin interface definition:
public interface ISchedulerPlugin
{
void Initialize(string pluginName, IScheduler sched);
void Start();
void Shutdown();
}



As you can see it’s not a very complicated interface. Let’s take some time to look at these methods in a little more detail.


Initialize


The Initialize method is called before the scheduler is started and it gives your plugin a chance to initialize itself. Keep in mind that at this point the scheduler itself is not running nor fully initialized. Here is what the code comments have to say:



Called during creation of the IScheduler in order to give the ISchedulerPlugin a chance to Initialize. At this point, the Scheduler's IJobStore is not yet (initialized?). If you need direct access (to) your plugin, you can have it explicitly put a reference to itself in the IScheduler’s SchedulerContext (in the) Initialize(string, IScheduler) method.


Start


The Start method is probably the method where most of the logic for your plugin will go. I will let the method’s comments describe the method’s purpose:


Called when the associated IScheduler is started, in order to let the plugin know it can now make calls into the scheduler if it needs to.


Shutdown


The shutdown method is there to allow you to do any cleanup that might be necessary for your plugin. The code comments are as follows:



Called in order to inform the ISchedulerPlugin that it should free up all of it's resources because the scheduler is shutting down.


Sample Implementation



Now we will go ahead and actually implement the plugin. In our sample implementation all that our plugin will do is to register a job listener. We are following the same approach we took while discussing how to implement a plugin in version 1.0 of Quartz.Net. This is intentional, to allow you see what the differences are between the two versions.


The implementation of our plugin is still very simple, albeit a little different from the one on the previous post:




public class PluginExample : ISchedulerPlugin
{
public void Initialize(string pluginName, IScheduler sched)
{
sched.ListenerManager.AddJobListener(new JobListenerExample(), EverythingMatcher<JobKey>.AllJobs());
}

public void Shutdown()
{
//Do Nothing
}

public void Start()
{
//Do Nothing
}
}



Configuring the Scheduler to Load the Plugin



Now that we have created our plugin, the next step is to tell the scheduler to load it. This is done by adding this line to our properties file :

quartz.plugin.myplugin.type = Examples.PluginExample, Examples



The plugin property needs to be structured like this:


quartz.plugin.{name}.type = {type name}, {assembly name}

Let’s discuss the components of the property. First, the quartz.plugin part of the property tells the scheduler that this is a plugin that needs to be loaded. The {name} part is the name that you want to give to your plug-in, and is the name that is passed in to the Initialize method above. The value of the property {type name}, {assembly name} tells the scheduler the Type of the plug-in, so that it can be loaded into memory. {type name} is the full name of your plugin, which in our case would be Examples.PluginExample. {assembly name} is the name of your assembly file, minus the .dll extension. Here’s another example of a plugin configuration property, lifted from the Quartz.Net default configuration file:


quartz.plugin.xml.type = Quartz.Plugin.Xml.XMLSchedulingDataProcessorPlugin, Quartz


Final Step



Now that your plugin has been added to the configuration, all you need to do is start the scheduler and check the log for errors. Remember that the dll that contains your plugin needs to be in the same folder as the Quartz.Net binaries so that it can be found by the .Net framework and loaded into memory.

Continue Reading

New in Quartz.Net 2.0–New Job File Format

The xml format for the quartz_jobs.xml file has changed in Quartz.Net 2.0. This is a breaking change, so you won’t be able to use your existing jobs file with the new version of Quartz.Net without updating it to the 2.0 format. If you’re interested in the details of what can be configured in this file, I would recommend looking at the xsd file that defines the schema for the file. The xsd file that defines the new file format can be downloaded directly from the source code repository, here.
Today I’m going to provide a sample quartz_jobs.xml file in the new format and walk you through the creation of the file. Alternatively, you can take a look at the default file (that will be) provided with the Quartz.Net 2.0 distribution, here.
The new file format starts off with the following root element:

<job-scheduling-data xmlns="http://quartznet.sourceforge.net/JobSchedulingData" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0">

Right after the root node, our configuration file has two elements, which we will describe in detail below:
  1. The <processing-directives> element

  2. The <schedule> element

The <processing-directives> Element

The processing directives element contains two elements. The first element, <pre-processing-commands> is used to run commands prior to adding jobs and triggers to the scheduler. I’m not going to spend much time going over these in detail, but here is a list of the commands available to you:

  • delete the jobs in a group
  • delete the triggers in a group
  • delete a job
  • delete a trigger
If you find yourself in need of help with these elements, leave a comment and I will write a follow-up post.

The second element, <processing-directives>, lets you specify how the xml file is to be processed. Here you will find a familiar configuration setting (from Quartz.net 1.0), the <overwrite-existing-data> element. Setting this element’s value to true will replace any jobs currently scheduled with the new schedule given in the xml file (true is the default setting!). The <ignore-duplicates> element is the other element allowed under the <processing-directives> element.

The <schedule> Element

The <schedule> element is where we describe the jobs and triggers that we want to schedule. This section has also changed its format. Now, instead of grouping jobs and triggers under an element, jobs and triggers are all specified at the same level, and are not grouped under the job element, as was the case in version 1.0.

Under the <schedule> element, we can have <job> and <trigger> elements, which are the building blocks that we use to put together our schedule. Let’s take a look at these elements now.

The <job> Element

The <job> element is used to describe the IJob that we want the scheduler to execute. Job details are specified by using the following elements:
  • <name>
  • <group>
  • <description>
  • <job-type>
  • <durable>
  • <recover>
  • <job-data-map>
All of these elements serve the same purpose as they did in version 1 and, except for the job-data-map, they’re all simple types, so I’m not going to go into great detail here either. If you need a little more help with these, leave a comment and I’ll expand on the explanation as needed.

The job-data-map, being a complex element, supports an <entry> element inside. The <entry> element has <key> and <value> elements inside, which describe the job property to be added to the job map.

The <trigger> Element
The <trigger> element is used to describe the trigger that we want to attach to a given job. The xml file loader plugin supports 3 types of triggers:
  • simpleTriggerType
  • cronTriggerType
  • calendarIntervalTriggerType
These three trigger types are based on the abstractTriggerType, which has the following common elements:
  • <name>
  • <group>
  • <description>
  • <job-name>
  • <job-group>
  • <priority>
  • <calendar-name>
  • <job-data-map>
In addition to the elements listed above, each trigger type supports some additional elements. These additional elements are used to set parameters that are not common across all of the trigger types. The table below summarizes each of the trigger types and the elements they support.

Trigger Type Additional Elements
SimpleTrigger <misfire-instruction>,<repeat-count>,<repeat-interval>
CronTrigger <misfire-instruction>,<cron-expression>,<time-zone>
CalendarIntervalTrigger <misfire-instruction>,<repeat-interval>,<repeat-interval-unit>


I’m not going to go into detail here either. This is already a pretty long post, so if anybody needs more information leave me a comment and I will write a follow-up post.

Putting It All Together

At this point we’ve described all of the elements that are necessary to describe a job in the new Quartz.Net 2.0 xml format. To put it all together, here are the contents of a sample quartz_jobs.xml file that you can use as a guide:

<?xml version="1.0" encoding="UTF-8"?>
<!-- This file contains job definitions in schema version 2.0 format -->
<job-scheduling-data xmlns="http://quartznet.sourceforge.net/JobSchedulingData" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.0">
  <processing-directives>
    <overwrite-existing-data>true</overwrite-existing-data>
  </processing-directives>
  <schedule>
    <job>
      <name>nativeJobExample</name>
      <group>nativeJobExampleGroup</group>
      <description>Sample job for Quartz Server</description>
      <job-type>Quartz.Job.NativeJob, Quartz</job-type>
      <job-data-map>
        <entry>
          <key>command</key>
          <value>native_job_example.bat</value>
        </entry>
        <entry>
          <key>consumeStreams</key>
          <value>true</value>
        </entry>
      </job-data-map>
    </job>
    <trigger>
      <simple>
        <name>nativeJobExampleSimpleTrigger</name>
        <group>nativeJobExampleSimpleTriggerGroup</group>
        <description>Simple trigger example</description>
        <job-name>nativeJobExample</job-name>
        <job-group>nativeJobExampleGroup</job-group>
        <misfire-instruction>SmartPolicy</misfire-instruction>
        <repeat-count>5</repeat-count>
        <repeat-interval>10000</repeat-interval>
      </simple>
    </trigger>
  </schedule>
</job-scheduling-data>

This is a working example of a quartz_jobs.xml file that schedules a NativeJob using a SimpleTrigger. This example also shows how to configure the <job-data-map> to pass configuration information to the job.

I hope this post helps you in building a job configuration file for Quartz.Net 2.0.

Continue Reading

Scheduling Jobs Programmatically in Quartz.Net 2.0

Scheduling jobs programmatically in Quartz.Net 2.0 is very similar to scheduling a job in Quartz.Net 1.0. The overall process is the same, but the scheduler API has changed for version 2.0, so the code used will be significantly different.
In this post we will only highlight the differences between the two versions, so if you’re not familiar with the process, then read this post first. Then come back to this post to understand what has changed in version 2.0.

What Has Changed

Only a couple of things have changed in Quartz.Net 2.0 when it comes to scheduling a job programmatically: the way you build jobs and triggers.
The new Quartz.Net scheduler API uses more interfaces now. It also provides builder objects to create triggers (ITrigger now) and job details (IJobDetail now). I think the simplest way to highlight the differences is to take the code from my original post and convert it to the new API. Here’s the updated code:
public void ScheduleOneTimeJob(Type jobType, JobDataMap dataMap)
{
  string name = DateTime.Now.ToString("yyyyMMddHHmmss"));
  string group = "OneTimeJobs";
  // create job
  IJobDetail jobDetail = JobBuilder
    .Create()
    .OfType(jobType)
    .WithIdentity(new JobKey(name, group))
    .UsingJobData(dataMap)
    .Build();
  // create trigger
  ITrigger trigger = TriggerBuilder
    .Create()
    .ForJob(jobDetail)
    .WithIdentity(new TriggerKey(name, group));
    .WithSchedule(SimpleScheduleBuilder.Create())
    .StartNow()
    .Build();
  // get reference to scheduler (remote or local) and schedule job
  GetScheduler().ScheduleJob(jobDetail, trigger);
}


As you can see, the changes to the code are pretty significant, but the overall process remains the same. I’d like to provide you with some links to documentation or some other examples, but I’m unaware of any at this point, as Quartz.Net 2.0 has not yet been released.

Continue Reading