Thursday, September 8, 2011

Kick-start Quartz 2 Tutorial

Quartz is a job scheduling framework, which provides a powerful way of executing and controlling scheduled jobs. This tutorial is based on the official tutorial on the main website of Quartz. The goal of this tutorial is to get a kick-start in using Quartz. Most details are left out. The example is designed to be easy to follow. The example consists of three classes:
  • Main: instantiates, and initiates Quartz classes
  • JobTask: the actual job definition
  • JobTaskListener: a listener class that monitors the execution of the job
Let's get right into the code. The first code fragment represents the state of a job. Job classes that Quartz uses are stateless. So we are forced to use the Memento design pattern to externalize state. This class should be serializable to prevent problems when we use Quartz to persist job states in the future. The state is meant to be accessed by the main thread and Quartz, so make sure it's thread-safe.
package com.javaeenotes;

import java.io.Serializable;

public class JobContext implements Serializable {

  private static final long serialVersionUID = 1L;

  private String state = "Initial state.";


  public String getState() {
    synchronized (state) {
       return state;
    }
  }


  public void setState(String state) {
    synchronized (state) {
      this.state = state;
    }
  }
}
The next code is the job definition class, which implements the method called by the Quartz scheduler. It's recommended to wrap the whole body of the method in a try-block in order to catch any exceptions that might occur. Wrap the exception in the checked JobExecutionException before throwing it. The main thread can deal with the exception later.
package com.javaeenotes;

import java.util.Date;

import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

// Disallow running multiple jobs based on this class at the same time.
@DisallowConcurrentExecution
public class JobTask implements Job {

  @Override
  public void execute(JobExecutionContext executionContext)
          throws JobExecutionException {

    // It's a good idea to wrap the entire body in a try-block, in order to
    // catch every exception thrown.
    try {
      // Retrieve the state object.
      JobContext jobContext = (JobContext) executionContext
            .getJobDetail().getJobDataMap().get("jobContext");

      // Update state.
      jobContext.setState(new Date().toString());

      // This is just a simulation of something going wrong.
     int number = 0;
     number = 123 / number;
    } catch (Exception e) {
      throw new JobExecutionException(e);
    }
  }
}
The next class is responsible for monitoring the job. It's also responsible for dealing with exceptions thrown by the job.
package com.javaeenotes;

import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobListener;

public class JobTaskListener implements JobListener {

  public static final String TRIGGER_NAME = "Trigger";

  @Override
  public String getName() {

    return TRIGGER_NAME;
  }

  @Override
  public void jobToBeExecuted(JobExecutionContext context) {

    System.out.println("Job is going to be executed: "
            + context.getJobDetail().getKey().toString());
  }

  @Override
  public void jobExecutionVetoed(JobExecutionContext context) {

    System.out.println("Job is vetoed by trigger: "
            + context.getJobDetail().getKey().toString());
  }

  @Override
  public void jobWasExecuted(
        JobExecutionContext context,
        JobExecutionException jobException) {

    System.out.println("Exception thrown by: "
            + context.getJobDetail().getKey().toString()
            + " Exception: "
            + jobException.getMessage());
  }
}
Now, we can use these classes to see how it runs. We use the Main-class for this purpose.
package com.javaeenotes;

import org.quartz.JobBuilder;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.JobListener;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.matchers.KeyMatcher;

public class Main {

  @SuppressWarnings("unchecked")
  public void run() {

    // The state of the job.
    JobContext jobContext = new JobContext();

    // The value or transfer object provided by Quartz that contains the
    // state of the job. Save it in JobDetail or Trigger.
    JobDataMap jobDataMap = new JobDataMap();
    jobDataMap.put("jobContext", jobContext);

    // Create an identifier for the job.
    JobKey jobKey = new JobKey("jobId", "jobGroup");

    // Object that contains the job class and transfer object.
    JobDetail jobDetail = JobBuilder.newJob(JobTask.class)
            .withIdentity(jobKey).usingJobData(jobDataMap).build();

    // Create the trigger that will instantiate and execute the job.
    // Execute the job with a 5 seconds interval.
    Trigger trigger = TriggerBuilder
            .newTrigger()
            .withIdentity("triggerId")
            .withSchedule(
                    SimpleScheduleBuilder.simpleSchedule()
                            .withIntervalInSeconds(5).repeatForever())
            .build();

    // Setup a listener for the job.
    JobListener jobListener = new JobTaskListener();

    // Use the Quartz scheduler to schedule the job.
    try {
      SchedulerFactory schedulerFactory = new StdSchedulerFactory();
      Scheduler scheduler = schedulerFactory.getScheduler();
      scheduler.scheduleJob(jobDetail, trigger);

      // Tell scheduler to listen for jobs with a particular key.
      scheduler.getListenerManager().addJobListener(
              jobListener,
              KeyMatcher.keyEquals(jobKey));

      // Start the scheduler after 5 seconds.
      scheduler.startDelayed(5);
    } catch (SchedulerException e) {
      e.printStackTrace();
    }

    // Print the job state with a 3 seconds interval.
    while (true) {
      try {
        System.out.println(jobContext.getState());
        Thread.sleep(3000);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }

  public static void main(String[] args) {
    new Main().run();
  }
}

The first thing we do is to create the state object, and wrap it in the transfer or value object provided by Quartz. The reason we do this, is because Quartz doesn't let us instantiate the job class ourselves.

The next step is to define and instantiate a JobKey object, which acts as the identifier for our job. This is also used together with the listener to tell the scheduler which jobs we want to monitor.

Then we create a JobDetail object, which contains details of the job. A trigger object is also needed to tell the scheduler when we want our job to be run. This provides a nice separation of the job and run schedule. Using multiple triggers we can run the same job at different times.

Next, we instantiate the listener class.

Finally, we retrieve an instance of the Quartz scheduler to schedule the job with the trigger. The listener class is also added, together with a matcher based on the job identifier. This way, the scheduler knows which jobs the listener is listening to. The scheduler is then started with a delay of 5 seconds.

The resulting output, when we run the Main class:
Initial state.
Initial state.
Job is going to be executed: jobGroup.jobId
Exception thrown by: jobGroup.jobId
    Exception: java.lang.ArithmeticException: / by zero
Job is going to be executed: jobGroup.jobId
Exception thrown by: jobGroup.jobId
    Exception: java.lang.ArithmeticException: / by zero
Thu Sep 08 18:07:03 CEST 2011
Thu Sep 08 18:07:03 CEST 2011
Job is going to be executed: jobGroup.jobId
Exception thrown by: jobGroup.jobId
    Exception: java.lang.ArithmeticException: / by zero

Quartz installation for Maven 2

To add Quartz to a Maven 2 project, just add the following code to your pom.xml file.

  org.quartz-scheduler
  quartz
  2.0.2

9 comments:

  1. Very detailed description of Quartz.. Its very resourceful information.
    I query i have in my mind is, where can we add the code related to sending the jobs status as email, is it after the run() method before while loop.

    ReplyDelete
  2. Aso, if i replace the job 123/0 with some other time-taking job. Does the job execute Asynchronously?
    Any help would be appreciated.

    ReplyDelete
  3. Kiến thức bạn share thật bổ ích, cảm ơn bạn đã share.
    Tìm hiểu website : Giá đá thạch anh

    ReplyDelete
  4. Kiến thức của tác giả thật hữu ích, thank bạn đã chia sẻ.
    Trang tham khảo : Thạch anh vụn dải bể cá

    ReplyDelete
  5. Bài post của tác giả rất cần thiết, thank bạn đã chia sẻ.
    Xem tại website : Gia công thạch anh

    ReplyDelete
  6. Thông tin của bạn thật được, bạn đã share.
    Xem thêm tại website: Vòng tay đá thạch anh

    ReplyDelete
  7. Thông tin của tác giả rất hữu ích, thank bạn đã chia sẻ.
    Xem tại website : Tỳ hưu

    ReplyDelete
  8. Nice tutorial.
    Visit beginnerstutorialexamples.com for more Java Quartz scheduler tutorial for beginners with examples.
    http://www.beginnerstutorialexamples.com/java-quartz-scheduler-tutorial-for-beginners-with-examples/

    ReplyDelete
  9. Good tutorial.

    Learn more Quartz scheduler tutorial with examples on http://tutorialspointexamples.com/.

    http://tutorialspointexamples.com/log4j-tutorial-beginners-eclipse/

    ReplyDelete