Tuesday, April 7, 2009

Getting Started With Quartz.Net: Part 3

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 Part 2 of this series you learned how to configure the Quartz.Net service, and now it’s time to get your server to actually do something. Let’s look at how to load some jobs via the quartz_jobs.xml file.
First, consider this short description of how the overall process works:
  • When you start the server, a job store is created in memory.
  • This job store is empty until we load some jobs in it.
  • As part of the server startup, the xml plug-in runs, and it reads the quartz_jobs.xml.
  • The plug-in then creates the jobs and triggers that are described in this file.
If you have set up your Quartz.net job server as we described in the previous posts, then all you need to do to load some jobs is to add them to quartz_jobs.xml file. The file that comes with the distribution sets up a NoOpJob, which as its name implies, does nothing. Let’s look at the very first line (ok, the second):
overwrite-existing-jobs="true"
This line tells the xml plug-in to delete any existing jobs that match the jobs that we have in the xml file. So, what makes a job unique in Quartz.Net? The job’s name AND the job’s group. You can create jobs with different names in the same group or jobs with the same name in different groups. If you try to create 2 jobs with the same name and in the same group, you’ll get an error. In this case however, it really doesn’t matter because the job store that we are using (where the job and trigger information is stored) is a RAM job store, therefore once the server is stopped, all this information disappears.
Now it’s time to configure our own job. We’ll configure a NativeJob, which is basically anything that can be run from the command line. In order to get a job to run, we need to configure 2 items: a job, and a trigger. The job is what actually does the work and the trigger is responsible for starting the job.
This xml fragment demonstrates how to configure a NativeJob and cron trigger that fires the 2nd and the 15th of every month at 4:00 am. Ok, that’s not very helpful for testing, so change the cron expression to 0 * * * * ? so that the job runs every minute. Also, if you end up setting your company’s payroll to run every minute, please let me know where I can apply for a job there :-).
<job>
<job-detail>
<name>PayrollProcessor</name>
<group>Payroll</group>
<description>Process paychecks</description>
<job-type>Quartz.Job.NativeJob, Quartz</job-type>
<volatile>false</volatile>
<durable>true</durable>
<recover>false</recover>
<job-data-map>
<entry>
<key>command</key>
<value>"payroll.bat"</value>
</entry>
<entry>
<key>parameters</key>
<value>"IncreaseSalary"</value>
</entry>
<entry>
<key>waitForProcess</key>
<value>true</value>
</entry>
<entry>
<key>consumeStreams</key>
<value>true</value>
</entry>
</job-data-map>
</job-detail>
<trigger>
<cron>
<name>PayrollProcessorTrigger</name>
<group>Payroll</group>
<description>Trigger payroll</description>
<misfire-instruction>SmartPolicy</misfire-instruction>
<volatile>false</volatile>
<job-name>PayrollProcessor</job-name>
<job-group>Payroll</job-group>
<cron-expression>0 0 4 2,15 1-12 ?</cron-expression>
</cron>
</trigger>
</job>

If you change the command from payroll.bat to the name of your executable (make sure you include the full path!), you should be able to get Quartz.Net to run your own job. I would recommend keeping the quotes there, because if your command has spaces in it and you don’t wrap the command in quotes, Quartz.Net won’t be able to run it.
Now that you’ve configured the job, let’s give it a try. Save the quart_jobs.xml file, start the Quartz.Net service and take a look at the event log. You should see some entries similar to these if everything was set up properly:
Quartz.Xml.JobSchedulingDataProcessor.ProcessFile(:0) - Parsing XML file: C:\quartz\quartz_jobs.xml with systemId: C:\quartz\quartz_jobs.xml validating: False validating schema: True
Quartz.Xml.JobSchedulingDataProcessor.ScheduleJobs(:0) - Scheduling 1 parsed jobs.
Quartz.Xml.JobSchedulingDataProcessor.ScheduleJob(:0) - Adding job: Payroll.PayrollProcessor
Quartz.Xml.JobSchedulingDataProcessor.ScheduleJobs(:0) - 1 scheduled jobs.
Quartz.Job.NativeJob.RunNativeCommand(:0) - About to run cmd.exe /C "C:\quartz\payroll.bat" IncreaseSalary

So there you have it, the scheduler is running the payroll.bat batch file and passing in the IncreaseSalary parameter. Plug in your own batch or program file and parameters and see what happens.
A word of caution: running command line jobs can get tricky because of spaces and parameter formats, so try running your program with the parameters from a command line first to see what happens. Sometimes it takes a little trial and error to get it to work just right. For this reason, you will usually want to create your own job (more on this later, or look at the Quartz.Net tutorial. A custom job gives you more control over what happens when and what to do in case of an emergency.
Because this is already a long post, I’ll defer until my next post the details of each of the job and trigger settings, but if you’re curious or in desperate and immediate need to know more, read through the following documentation links, which should answer most of your questions:
General Job (IJob) documentation
NativeJob documentation
General Trigger documentation
Cron trigger documentation

11 comments:

Alex said...

once again--wow. Thanks. Just one question--how did you learn all this about quartz? I feel like the documentation is quite scarce--especially that regarding configuration through the XML file.
Also--if you make changes to the XML file does the quartz system process scan the file to load the new job or would you need to restart quartz? If not, at what interval is it scanning the file (every second, millisecond, etc)?

J said...

You're welcome. The easiest way to figure out how it all works is to poke around the source code. There is an xsd file that helps to understand what elements can be used. You can also take a look at the source code of the initialization plugin.

To answer your question... you need to restart the server in order to have the plugin run and load/replace the jobs again. BTW there is a bug with the xml plugin where it will not actually replace existing jobs. This is a problem if you use a persistent job store. Otherwise you don't need to worry about it. Here's the thread: http://groups.google.com/group/quartznet/browse_thread/thread/8ed73b647dd8589f/827d5bf3287764f3?lnk=gst&q=overwrite

Rexon Jeyasingh said...

overwrite-existing-jobs=true solved my Quartz.net memory leak issue

Bryan and Christina said...

You don't know how helpful these three parts were. We use 4 different ways to schedule jobs here and it's killing me. Cheers!

J said...

Thanks! Glad to be able to help out.

Jan said...

Quick question, how can you manage adding new jobs to the scheduler?

Currently, I'm looking at using the OnCustomCommand event of the service.

J said...

@Jan, I started writing a response to you comment but it ended up being rather long, so I instead turned my comment into a couple of posts. These posts explain how to add jobs programmatically, which I think is what you're interested in. Let me know if that's not the case! Here is a link to the first post: http://jvilalta.blogspot.com/2011/03/scheduling-jobs-programmatically-in.html

Your Blog said...

Akki

I am using XML file to configure job but its not firing that job. Neither its showing any exception at all. Please assist me with this. I think I am committing mistake in attribute. What should be the value of this property. Here is the quartzjobs.xml code:






jobName1
jobGroup1
Trying To write something in text file on desktop
Website4.Job.Class1,Website4
false
true
false




simpleName
simpleGroup
SimpleTriggerDescription
SmartPolicy

false
jobName1
jobGroup1
2011-05-20T15:42:00.0Z

-1
60000







My job class is in root folder>Job>Class1.cs and following is the code I have written on Page_Load event:

ISchedulerFactory sf = new StdSchedulerFactory();
IScheduler sched = sf.GetScheduler();
sched.Start();

Please help me out. My e-mail is jainismrulez@gmail.com....It will be very helpful if you can send the solution to my mail id as well as on this site

Your Blog said...

Here is one scenario:
User composed a mail and want to send it every monday at 8AM. How to do it dynamically. He again want to schedule a different job that will be executed everyday at 6AM retaining earlier scheduled job.

How to do this please assist

Your Blog said...

I am using XML file to configure job but its not firing that job. Neither its showing any exception at all. Please assist me with this. I think I am committing mistake in attribute. What should be the value of this property. Here is the quartzjobs.xml code:


@





jobName1
jobGroup1
Trying To write something in text file on desktop
Website4.Job.Class1,Website4
false
true
false




simpleName
simpleGroup
SimpleTriggerDescription
SmartPolicy

false
jobName1
jobGroup1
2011-05-20T15:42:00.0Z

-1
60000











My job class is in root folder>Job>Class1.cs and following is the code I have written on Page_Load event:

ISchedulerFactory sf = new StdSchedulerFactory();
IScheduler sched = sf.GetScheduler();
sched.Start();

Please help me out. My e-mail is jainismrulez@gmail.com....It will be very helpful if you can send the solution to my mail id as well as on this site

J said...

@Yourblog... were you able to solve your problem? Have you checked the event log to see if any errors are being logged?