On our current project, we have the requirement to be able to run our application on more than JBoss AS alone. When confronted with the need to schedule certain tasks, we considered a few options:
- Use EJB timers: Due to the nature of the tasks (e.g. interdependencies) this mechanism is not suitable – it’s simply just not sophisticated enough.
- Use the Quartz functionality provided along with the JBoss distribution: While this may do exactly what we need, it wouldn’t be portable to app servers from other vendors.
So the decision was made to use Quartz, not as readily available from JBoss but as an add-on library (‘external’, if you will). I found a couple of articles that pointed me in the right direction:
- “Another way to use Quartz in JBoss”
- “JBoss Quartz tutorial”
- “Scheduling a job using Jboss-5.1.0.GA and Quartz”
We used Quartz version 2.0.2 – the latest and greatest at the time I write this – and since Maven is our build tool of choice, the following dependency pulls all required libs into our project:
<dependency> <groupid>org.quartz-scheduler</groupid> <artifactid>quartz-jboss</artifactid> <version>2.0.2</version> <scope>provided</scope> </dependency>
We use provided scope here since we won’t be including Quartz in our project’s deliverables; instead we put such dependencies on our server explicitly. Either approach would work, though.
1) Clean up your installation.
So, the first job at hand is to remove the Quartz artifacts from our JBoss installation. I guess it makes sense to prevent different versions from showing up in your classpath. The files to be removed are:
- ${jboss.home.dir}/common/lib/quartz.jar
- ${jboss.home.dir}/server/[SERVER_NAME]/deploy/quartz-ra.rar (in EAP this is an exploded RAR, so remove the directory)
Before deleting anything from the common/lib directory, be sure that there aren’t any other servers running from the same AS installation that need that file!
2) Add the required Quartz libraries to you server.
The following file should be in place after this step:
- ${jboss.home.dir}/server/[SERVER_NAME]/lib/quartz-all-2.0.2.jar
That’s right. Just one jar, very convenient – and the added bonus is that this already contains the stuff that’s needed for deployment in other app servers, so no need to have different dependencies in different deployments.
Note that this file is not downloaded if you include the dependencies through Maven as indicated above, you have to download the full distribution to get it.
3) Add an MBean to the JBoss configuration.
The following is a simple example of a Quartz MBean configuration:
<server> <mbean code="org.quartz.ee.jmx.jboss.QuartzService" name="user:service=QuartzService,name=QuartzService"> <attribute name="JndiName">Quartz</attribute> <attribute name="Properties"> org.quartz.scheduler.instanceName = DefaultQuartzScheduler org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool org.quartz.threadPool.threadCount = 5 org.quartz.threadPool.threadPriority = 4 org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore </attribute> </mbean> </server>
If you use this, no extra DataSource needs to be configured and Quartz keeps its jobs in memory. For more specifics on the configuration possibilities (there are a lot!), see http://www.quartz-scheduler.org/docs/configuration/.
Put the XML above in a x-service.xml file in your server’s deploy dir, like e.g.:
- ${jboss.home.dir}/server/[SERVER_NAME]/deploy/quartz-service.xml
4) Point Quartz at the jobs you want done.
Obviously it now is possible to use Quartz from code inside deployed applications. Just retrieve the scheduler from JNDI like so:
InitialContext ctx = new InitialContext(); Scheduler scheduler = (Scheduler) ctx.lookup("Quartz");
But for our purpose this just isn’t good enough, we want to be able to schedule tasks from configuration files. To accomplish that, we need to perform a number of steps:
a) Enable the Quartz plugin that reads jobs and triggers from an indicated XML file. This is done by adding the following properties to the configuration shown in step 3):
org.quartz.plugin.jobInitializer.class = org.quartz.plugins.xml.XMLSchedulingDataProcessorPlugin org.quartz.plugin.jobInitializer.fileNames = ${jboss.server.home.dir}/conf/quartz-jobs.xml org.quartz.plugin.jobInitializer.scanInterval = 120
For details on this plugin see http://www.quartz-scheduler.org/api/2.0.0/org/quartz/plugins/xml/XMLSchedulingDataProcessorPlugin.html.
b) Create a class that implements the org.quartz.Job interface for each such a task. This interface exposes exactly one method, void execute(JobExecutionContext ctx), which is called when the job is triggered.
c) Provide the quartz-jobs.xml file that is indicated in the extra configuration in step a) (the file name and path can be adjusted to your liking) with the appropriate timing to start your jobs. For our tasks we use Cron-like jobs, with the Quartz CronTrigger (see http://www.quartz-scheduler.org/api/2.0.0/org/quartz/CronTrigger.html). An example configuration is:
<job-scheduling-data version="1.8" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.quartz-scheduler.org/xml/JobSchedulingData" xsi:schemalocation="http://www.quartz-scheduler.org/xml/JobSchedulingData http://www.quartz-scheduler.org/xml/job_scheduling_data_1_8.xsd"> <pre-processing-commands> <delete-jobs-in-group>*</delete-jobs-in-group> <delete-triggers-in-group>*</delete-triggers-in-group> </pre-processing-commands> <processing-directives> <overwrite-existing-data>true</overwrite-existing-data> <ignore-duplicates>false</ignore-duplicates> </processing-directives> <schedule> <job> <name>TestJob</name> <job-class>com.acme.quartz.TestJob</job-class> </job> <trigger> <cron> <name>TestCronTrigger</name> <job-name>TestJob</job-name> <cron-expression>0 0 3 ? * MON-FRI</cron-expression> </cron> </trigger> </schedule> </job-scheduling-data>
In this example the task executed by the com.acme.quartz.TestJob class is triggered at 3:00 AM on weekdays.
One last CAVEAT: On Windows, the ${jboss.server.home.dir} expression resolves to a String that contains backward slashes (‘\’) instead of forward slashes as path delimiter. The way the Quartz extension for JBoss reads in the properties is not able to cope with that, so you may need to provide a full path explicitly for any file names.