Tuesday, March 15, 2011

New in Quartz.Net 2.0–New Job File Format

NOTE: I'm now blogging at http://jayvilalta.com/blog and not updating this blog anymore. For information on the latest version of Quartz.Net, visit me there.

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.

Wednesday, March 9, 2011

Scheduling Jobs Programmatically in Quartz.Net 2.0

NOTE: I'm now blogging at http://jayvilalta.com/blog and not updating this blog anymore. For information on the latest version of Quartz.Net, visit me there.

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.

Scheduling Jobs Programmatically in Quartz.Net 1.0

NOTE: I'm now blogging at http://jayvilalta.com/blog and not updating this blog anymore. For information on the latest version of Quartz.Net, visit me there.

This post is one of those back to basics posts and is inspired by a comment left by Jan on one of the Getting Started posts. Today we’ll take a look at how to schedule a job programmatically in Quartz.Net 1.0. I’ll follow up with a post on how to schedule a job programmatically in Quartz.Net 2.0 as well.

Scheduling Jobs in Quartz.Net

There are two ways you can schedule a job to run on Quartz.Net. The first way is to schedule it using the plugin that reads an xml file containing the job and scheduling information. If you want more information on how to schedule jobs using the xml plugin, take a look at this post.
You can also schedule jobs by interacting directly with the scheduler API. The Quartz.Net scheduler supports remote access via the .Net remoting facility, so this method of scheduling jobs is available to you regardless of whether the scheduler is running as a standalone windows service or whether it is embedded in your application. The remote scheduler does have some limitations, but we will not run into them while scheduling jobs. Let’s take a look at how we can schedule jobs using the scheduler API.

Scheduling Jobs via the Scheduler API

In a nutshell, these are the steps you would follow to schedule a job using the API:
  1. Get a reference to the scheduler object. How you do this will depend on whether you are running a standalone or embedded scheduler.
  2. Create a JobDetail object and set all the necessary properties. At a minimum you must set the Name, the Group and JobType. Please note that because the type of JobType is Type, the dll containing the JobType MUST be available to the code that is scheduling the job AS WELL as to the scheduler.
  3. Create a Trigger object (Trigger itself is an abstract class, so you will actually be creating one of these concrete types: SimpleTrigger, CronTrigger or NthIncludedDayTrigger) and set all the necessary properties. At a minimum you must set the Name, and the Group. The JobGroup and the JobName are also necessary as they are what links the JobDetail you created in the previous step with the Trigger you are creating now. However, you do not need to set these properties right now. Read on to step 4 to understand why.
  4. Call the ScheduleJob method on your scheduler object passing in your Trigger and your Job. If you call this overload of the ScheduleJob method, then the scheduler will link your trigger and your job, so there is no need for you to set the link explicitly.

Code Example

I’d like to close out this post with a code sample that implements the steps outlined above.
public void ScheduleOneTimeJob(Type jobType, JobDataMap dataMap)
{
  string name = DateTime.Now.ToString("yyyyMMddHHmmss"));
  string group = "OneTimeJobs";

  // create job
  JobDetail jobDetail = new JobDetail(name, group, jobType);
  jobDetail.Description = "One time job.";
  jobDetail.JobDataMap = dataMap;
  
  // create trigger
  SimpleTrigger trigger = new SimpleTrigger();
  trigger.Name = name;
  trigger.Group = group;
  trigger.StartTimeUtc = DateTime.UtcNow;
  trigger.RepeatCount = 0;
  trigger.RepeatInterval = TimeSpan.Zero;
 
  // get reference to scheduler (remote or local) and schedule job
  GetScheduler().ScheduleJob(jobDetail, trigger);
}



This sample code demonstrates one way of creating and scheduling a job that runs once, immediately.


In Quartz.Net 2.0 the scheduler API has changed significantly, so we will cover that topic in another post.

Tuesday, March 1, 2011

How Does Quartz.Net Configuration Work?

NOTE: I'm now blogging at http://jayvilalta.com/blog and not updating this blog anymore. For information on the latest version of Quartz.Net, visit me there.

In this post we will explain in great detail how the Quartz.Net scheduler loads its configuration upon startup. Since the scheduler configuration did not change between versions 1.0 and 2.0, the information here is applicable to both Quartz.Net versions.
In Quartz.Net, the StdSchedulerFactory is responsible for configuring the scheduler. When the Quartz.Net scheduler is started, the factory will try to automatically configure a scheduler by looking for configuration information in different places:
  • the hosting application’s configuration file
  • a file specified in an environment variable
  • the quartz.config file
  • the embedded configuration file

Configuring From the Hosting Application’s Configuration File

First, the factory will try to load the scheduler configuration from the <quartz> section of your application’s config file. If you are running Quartz.Net as a windows service, then the service’s configuration file will be used. This file (Quartz.Server.exe.config) can be found in the same folder as the server executable. If you are hosting the Quartz.Net scheduler in your web application, then the web.config file will be checked.

Configuring From A File Specified in an Environment Variable

If the factory was not able to load the quartz configuration section, then the second place it will check is the environment variables of the process. Specifically, the factory will check to see if the quartz.config environment variable has been defined. If such a variable exists and is not empty, the factory will try to load the configuration from whatever file is specified as the value of the variable. For example, let’s say that your configuration file is called myconfig.config. If you set the quartz.config environment variable equal to myconfig.config, then the scheduler will load the configuration information from the file named myconfig.config.

Configuring From the Quartz.config File

Let’s assume that so far, the factory has not been able to load the scheduler’s configuration. The next step that the factory will take is to try to load a file called quartz.config from the same directory where the hosting application’s assembly was loaded. In fact, this is the file that an out-of-the-box Quartz.Net server/service installation uses to configure itself.

Configuring From the Embedded Configuration File

If all of the previous configuration options fail, then the factory falls back on loading the configuration file that is embedded in the quartz assembly. In case you’re curious, here are the configuration properties that are embedded in the assembly:
Property Value
quartz.scheduler.instanceName DefaultQuartzScheduler
quartz.threadPool.type Quartz.Simpl.SimpleThreadPool, Quartz
quartz.threadPool.threadCount 10
quartz.threadPool.threadPriority Normal
quartz.jobStore.type Quartz.Simpl.RAMJobStore, Quartz
quartz.jobStore.misfireThreshold 60000
If after checking all of the above locations for configuration information, the factory was not able to configure the scheduler, then an exception is thrown. As you can probably gather from the previous paragraph, unless you actually change the quartz assembly, this configuration exception will not be raised, because a configuration file is already embedded in the quartz assembly by default.

One Final Step

You’d think that by now, the whole configuration process is finished and the scheduler has been configured successfully. Well, the default scheduler factory takes one last step before giving you the configured scheduler.
Here’s what happens just after your configuration is loaded: if any of the configuration properties that you set in a configuration are also present in the environment variables, then the factory will overwrite them with the environment value. This the expected behavior. However, due to a bug, this does not work in Quartz.Net 1.0, but is fixed in Quartz.Net 2.0.