Thursday, October 7, 2010

Show Java Quartz Scheduler information

Quartz is a great open source project that allows to do Job Scheduling. Quartz is a production stage open source project that has been around long enough to be trusted. You can integrate it in your applications in a very short time and benefit from simple to complex scheduling requirements.
After you integrate Quartz to your project, you don’t really know what is going on. Which job is currently running/not running, when was the last time a job fired, when will the job will fire again.
In order to know the state of your jobs, Quartz provides several informative methods. We will use these methods in order to visually show Quartz scheduler information. The information will be shown using a JSP page that will dynamically gather Quartz information and build a table of all the current existing jobs.
This is an example of how this page looks like:
 
The Quartz data that is shown in the table is:
  • Group – The group of the job (in Quartz every job belongs to a group).
  • Job – The name of the job.
  • Cron Expression – An expression that describes the times in which the job should run. Note, that not all Quartz jobs use cron expressions for their execution. It is also possible to schedule jobs to run according specific intervals.
  • Previous fire time – The last time in which job was executed.
  • Next fire time – The next time a job is scheduled to run.
  • Stateful – An indication if a job is Stateful. When a job is Stateful, no more than a single instance of that job will run at any given time.
  • Interruptable – Indication if the job can be interrupted. A job that can be interrupted should implement InterruptableJob interface.
  • Running – An indication if a job is currently running or not. In parenthesis will be shown the number of instances of the job that are currently running.
  • Actions – This column allows to do 2 actions with Jobs:
    • Exec – Allows to immediately execute a job without waiting for it to be fired.
    • Pause/Resume – Pause/Resume a job. This will prevent the job from being executed by the scheduler.
In addition to this data, note that a running job is marked as bold and a paused job is marked in red bold.
Let’s have a look at the JSP file that creates this table. It mainly contains 2 parts:
  • The first part is a Java code that handles the 2 actions: Exec and Pause/Resume. It is responsible of executing a job if the Exec action was pressed or Pausing/Resuming a job if a job was paused/resumed.
  • The second part, is the part that actually gets all the jobs information from Quartz and shows it as a table.
The JSP file that creates this table, gets an instance of Quartz Scheduler class from a singleton named: SchedulerSingleton. You can replace this line of code with whatever way you use to get access to your Scheduler instance.
And here is how the code looks like:
<%@ page import="com.todacell.management.SchedulerSingleton" %>
<%@ page import="org.quartz.*" %>
<%@ page import="java.util.Arrays" %>
<%@ page import="java.util.Date" %>
<%@ page import="java.util.List" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<%
try
{
  Scheduler scheduler = SchedulerSingleton.instance();
  String action = request.getParameter("action");
  if (action != null)
  {
    String groupParam = request.getParameter("group");
    String jobParam = request.getParameter("job");
    if (groupParam != null && jobParam != null) 
    {
      if ("exec".equals(action))
      {
        scheduler.triggerJob(jobParam, groupParam);
      }
      else if ("pause".equals(action))
      {
        if (scheduler.getTriggerState(jobParam, groupParam) == Trigger.STATE_PAUSED)
        {
          scheduler.resumeTrigger(jobParam, groupParam);
        }
        else
        {
          scheduler.pauseTrigger(jobParam, groupParam);
        }
      }
      response.sendRedirect("jobs.jsp");
      return;
    }
  }
%>
<head>
<title>Management Jobs</title>
<link rel="stylesheet" type="text/css" href="css.css">
</head>
<body>
<h1>Jobs Information</h1>
Current Time: <b><%= new Date() %></b><br/>
Scheduler State: <b><%= scheduler.isStarted() ? "Running" : "Not Running" %></b><br/><br/>
<table class="tableBorder" cellpadding="4" cellspacing="0">
<tr>
<td class="tdBorder bold center">
Group
</td>
<td class="tdBorder bold center">
Job
</td>
<td class="tdBorder bold center">
Cron Expression
</td>
<td class="tdBorder bold center">
Previous Fire Time
</td>
<td class="tdBorder bold center">
Next Fire Time
</td>
<td class="tdBorder bold center">
Stateful
</td>
<td class="tdBorder bold center">
Interruptable
</td>
<td class="tdBorder bold center">
Running
</td>
<td class="tdBorder bold center">
Actions
</td>
</tr>
<%
  String[] groups = scheduler.getJobGroupNames();
  Arrays.sort(groups);
  List<JobExecutionContext> executingJobs = scheduler.getCurrentlyExecutingJobs();
  for (String group : groups)
  {
    String[] jobs = scheduler.getJobNames(group);
    Arrays.sort(jobs);
    int i = 0;
    for (String job : jobs)
    {
      Trigger trigger = scheduler.getTrigger(job, group);
      JobDetail jobDetail = scheduler.getJobDetail(job, group);
      int numInstances = 0;
      for (JobExecutionContext jobExecutionContext : executingJobs)
      {
        JobDetail execJobDetail = jobExecutionContext.getJobDetail();
        if (execJobDetail.getKey().equals(jobDetail.getKey()))
        {
          numInstances++;
        }
      }
      boolean isPaused = scheduler.getTriggerState(job, group) == Trigger.STATE_PAUSED;
%>
<tr>
<%
if (i == 0)
{
%>
<td class="tdBorder" rowspan="<%= jobs.length %>">
<%= group %>
</td>
<%
}
%>
<td class="tdBorder <%= isPaused ? "error bold" : "" %>">
<%= job %>
</td>
<td class="tdBorder">
<%= trigger != null && trigger instanceof CronTrigger ? ((CronTrigger)trigger).getCronExpression() : "N/A" %>
</td>
<td class="tdBorder">
<%= trigger != null ? (trigger.getPreviousFireTime() != null ? trigger.getPreviousFireTime()  : "N/A") : "N/A" %>
</td>
<td class="tdBorder">
<%= trigger != null ? trigger.getNextFireTime() : "&nbsp;" %>
</td>
<td class="tdBorder center">
<%= StatefulJob.class.isAssignableFrom(jobDetail.getJobClass()) ? "YES" : "NO" %>
</td>
<td class="tdBorder center">
<%= InterruptableJob.class.isAssignableFrom(jobDetail.getJobClass()) ? "YES" : "NO" %>
</td>
<td class="tdBorder center">
<%= numInstances > 0 ? "<b>YES (" + numInstances + ")</b>" : "NO" %>
</td>
<td class="tdBorder center">
<a href="jobs.jsp?action=exec&group=<%= group %>&job=<%= job %>">Exec</a>&nbsp;|&nbsp;<a
href="jobs.jsp?action=pause&group=<%= group %>&job=<%= job %>"><%= isPaused ? "Resume" : "Pause" %></a>
</td>
</tr>
<%
i++;
}
}
%>
</table>
</body>
<%
}
catch (SchedulerException se) {
  se.printStackTrace();
}
%>
</html>

Note that this JSP file uses a style shit file named “css.css”. This css file gives a bit nicer look to the Jobs information page. You can download the “jobs.jsp” file as well as the “css.css” file from here.

4 comments:

  1. really good ..but if you post,com.todacell.management.SchedulerSingleton class ..that will be better

    ReplyDelete
  2. It's just a simple Singleton returning an instance of scheduler. Usually you should already have your own scheduler instance one way or another in your code.

    ReplyDelete
  3. Hi,
    thank you for this tutorial, can you please provide the class SchedulerSingleton ?
    thanks

    ReplyDelete
  4. Yes, sorry for the late reply. It should look something like:

    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    import org.quartz.CronTrigger;
    import org.quartz.JobDetail;
    import org.quartz.Scheduler;
    import org.quartz.SchedulerException;
    import org.quartz.impl.StdSchedulerFactory;


    public class SchedulerSingleton {

    private static Log log = LogFactory.getLog(SchedulerSingleton.class);

    private static Scheduler scheduler;

    public static void init() throws Exception {
    scheduler = new StdSchedulerFactory().getScheduler();

    try {
    JobDetail jobDetail = new JobDetail("Group", "Job", MyJob.class);
    scheduler.scheduleJob(jobDetail, new CronTrigger("Group", "Job", "0 0 3 * * ?"));
    } catch (Exception e) {
    log.fatal("Failed initializing Scheduler", e);
    }

    scheduler.start();
    }

    public static Scheduler instance() {
    return scheduler;
    }

    public static void destroy() {
    log.info("stopping jobs...");
    try {
    if (scheduler != null) {
    scheduler.pauseAll();
    scheduler.shutdown(true);
    scheduler = null;
    }
    } catch (SchedulerException se) {
    log.error("failed to shutdown jobs scheduler", se);
    }

    log.info("jobs have been stoped.");
    }
    }

    ReplyDelete