Scheduler Module

Description

Scheduler module allows users to schedule, execute and browse jobs of several types. A job is a Motech event that will fire when the job is triggered. It uses quartz library to store scheduled jobs. All the information can be stored either in RAM job store or in a database, which can be any database supported by quartz (though we recommend using MySQL or Postgresql). Using RAM storage is not recommended in production because of possibility of running out of memory and lack of jobs persistence, which can lead to data loss. Module comes with scripts stored in /sql sub-folder, which can be used to create database structure for quartz.

You can schedule many types of jobs, for example you can set a job which will fire its MotechEvent every day. To achieve that you can use CronSchedulableJob. You can schedule job which will be fired only on several days of week, which can be achieved with DayAWeekSchedulableJob. Scheduler will take care of firing all the jobs at the right time, but to handle them you’ll need a MotechListener. Once MotechEvent is captured, you can do whatever you want with it, for example send an e-mail, if your module supports that.

Quartz preparation

There are two things that have to be done to make the scheduler module functional. First, proper database needs to be created for quartz. Second, you need to configure quartz with either quartz.properties file or database.

Database creation
Preparing database to be used with quartz is very simple and won’t take more than few minutes.
  1. Create database with name {NAME}.

  2. Create proper structure with proper script from /sql sub-folder.
    mysql -u{USER_NAME} -p{PASSWORD} {NAME} < mysql_quartz_schema_v2.1.sql for MySQL database.
    psql postgres_quartz_schema_v2.1.sql for Postgresql database.
Quartz configuration
Quartz is configured with quartz.properties file included in the module.
  • org.quartz.scheduler.instanceName

    Scheduler name. This is only used to distinguish one scheduler from another.

  • org.quartz.threadPool.class

    Name of the ThreadPool implementation to use. The threadpool that ships with Quartz is org.quartz.simpl.SimpleThreadPool, and should meet the needs of nearly every user.

  • org.quartz.threadPool.threadCount

    Number of threads available for concurrent execution of jobs.

  • org.quartz.jobStore.class

    Class used to store scheduling information (job, triggers and calendars) within a relational database.

  • org.quartz.jobStore.driverDelegateClass

    Responsible for doing all JDBC work for specified database.
    org.quartz.impl.jdbcjobstore.StdJDBCDelegate for MySQL database.
    org.quartz.impl.jdbcjobstore.PostgreSQLDelegate for Postgresql database.
    We also provide computed variable ${sql.quartz.delegateClass}, based on driver class.
  • org.quartz.jobStore.dataSource = {dataSource}

    DataSource which JobStore should use. The value of this property must be the name of one the DataSources defined in the configuration properties file.

  • org.quartz.jobStore.tablePrefix

    Prefix to use with quartz tables.

  • org.quartz.jobStore.driverDelegateInitString

    Properties and their values delimited by pipe, passed to DriverDelegate during initialization. All delegates shipped with Quartz support property called triggerPersistenceDelegateClasses, which can be set to a coma-separated list of classes that implement the TriggerPersistenceDelegate interface for storing custom trigger types.

  • org.quartz.dataSource.{dataSource}.driver

    JDBC driver for the chosen database.
    com.mysql.jdbc.Driver for MySQL database.
    org.postgresql.Driver for Postgresql.
  • org.quartz.dataSource.{dataSource}.URL

    URL for connecting to database.

  • org.quartz.dataSource.{dataSource}.user

    User name used to connect to database.

  • org.quartz.dataSource.{dataSource}.password

    User password used to connect to database.

  • org.quartz.dataSource.{dataSource}.maxConnections

    Maximum number of connection that DataSource can create in it’s pool of connections.

Job types

Scheduler makes use of job types listed below:

RunOnceSchedulableJob
Job, which will be fired only once at a date given by user.
Type Name Description
MotechEvent motechEvent Motech event which will be fired when the job triggers.
Date startTime Date at which job should become ACTIVE.
CronSchedulableJob
Job, which will be fired on every match with given cron expression.
Type Name Description
MotechEvent motechEvent Motech event which will be fired when the job triggers.
String cronExpression Standard cron expression, which defines when the job should be fired.
Date startTime Date at which job should become ACTIVE.
Date endTime Date at which job should be stopped. Should be null if job should never end.
boolean ignorePastFiresAtStart Defines whether job should ignore past fires at start or not.
DayOfWeekSchedulableJob
Job, which will be fired at given time on days provided by user.
Type Name Description
MotechEvent motechEvent Motech event which will be fired when the job triggers.
LocalDate start Date at which job should become ACTIVE.
LocalDate end Date at which job should be stopped. Should be null if job should never end.
List<DayOfWeek> days List of days at which job should be fired.
Time time Time at which job should be fired. Time is a class from org.motechproject.commons.date.model package. It stores hour and minutes.
boolean ignorePastFiresAtStart Defines whether job should ignore past fires at start or not.
RepeatingSchedulableJob
Job, which will be fired every user-specified time interval(in milliseconds), but won’t be fired more than given number of times.
Type Name Description
MotechEvent motechEvent Motech event which will be fired when the job triggers.
Date startTime Date at which job should become ACTIVE.
Date endTime Date at which job should be stopped. Should be null if job should never end.
Integer repeatCount Defines how many times job should be repeated, which mean that with 0 if will fire once, with -1 it will fire infinite number of times and with null it will repeat number of times predefined in MotechSchedulerServiceImpl.
Long repeatIntervalInMilliSeconds Defines how often(in milliseconds) job should be fired.
boolean ignorePastFiresAtStart Defines whether job should ignore past fires at start or not.
boolean useOriginalFireTimeAfterMisfire Defines whether job should use original fire time after misfire.
RepeatingPeriodSchedulableJob
Job, which will be fired every, user-specified period. Period is an instance of org.joda.time.Period class.
Type Name Description
MotechEvent motechEvent Motech event which will be fired when the job triggers.
Date startTime Date at which job should become ACTIVE.
Date endTime Date at which job should be stopped. Should be null if job should never end.
org.joda.time.Period repeatPeriod Defines how often job should be fired.
boolean ignorePastFiresAtStart Defines whether job should ignore past fires at start or not.
boolean useOriginalFireTimeAfterMisfire Defines whether job should use original fire time after misfire.

OSGi Services

Motech Schedule Service
Motech Scheduler Service Interface provides methods to schedule, reschedule and unschedule a job. It provides separate methods for scheduling, safe-scheduling and unscheduling every type of job.
  • void scheduleDayOfWeekJob(DayOfWeekSchedulableJob dayOfWeekSchedulableJob);
    void scheduleJob(CronSchedulableJob cronSchedulableJob);
    void scheduleRepeatingJob(RepeatingSchedulableJob repeatingSchedulableJob);
    void scheduleRepeatingPeriodJob(RepeatingPeriodSchedulableJob repeatingPeriodSchedulableJob);
    void scheduleRunOnceJob(RunOnceSchedulableJob schedulableJob);
    Schedules the given schedulable job. The Job ID by which the job will be referencing in the future should be provided in an Instance of MotechEvent in SchedulableJob. If a job with the same job ID as the given exists, this job will be unscheduled and the given schedulable job will be scheduled. If you set “JobID” param in MotechEvent of a job it will be used as jobs ID.

  • void safeScheduleJob(CronSchedulableJob cronSchedulableJob);
    void safeScheduleRepeatingJob(RepeatingSchedulableJob repeatingSchedulableJob);
    void safeScheduleRepeatingPeriodJob(RepeatingPeriodSchedulableJob repeatingPeriodSchedulableJob);
    void safeScheduleRunOnceJob(RunOnceSchedulableJob schedulableJob);

    Same as standard schedule methods, except that these would update existing job if one exists instead of creating a new one.

  • void rescheduleJob(String subject, String externalId, String cronExpression);

    Reschedules a job with the given job ID to be fired according to the given Cron Expression. Previous version of the configured Motech Scheduled Event that will be created when the job is fired remains as it was.

  • void unscheduleJob(String subject, String externalId);
    void unscheduleRepeatingJob(String subject, String externalId);
    void unscheduleRunOnceJob(String subject, String externalId);

    Unschedules a job with the given ID.

  • void unscheduleAllJobs(String jobIdPrefix);

    Unschedules all jobs with given prefix.

  • void safeUnscheduleJob(String subject, String externalId);
    void safeUnscheduleRepeatingJob(String subject, String externalId);
    void safeUnscheduleRunOnceJob(String subject, String externalId);

    Same as standard unschedule methods except that these would not throw an exception if the job doesn’t exist.

  • void safeUnscheduleAllJobs(String jobIdPrefix);

    Same as unscheduleAllJobs except that it would not throw an exception.

  • DateTime getPreviousFireDate(JobId jobId);

    Returns last date the job with given ID was fired.

  • DateTime getNextFireDate(JobId jobId);

    Returns next date the job with given ID will be fired.

  • List<Date> getScheduledJobTimings(String subject, String externalJobId, Date startDate, Date endDate);

    Returns timings between start and end dates for job with given ID.

  • List<Date> getScheduledJobTimingsWithPrefix(String subject, String externalJobIdPrefix, Date startDate, Date endDate);

    Returns timings between start and end dates for jobs with given prefix.

  • List<JobBasicInfo> getScheduledJobsBasicInfo();

    Returns basic information about job as a list of JobBasicInfo instances.

  • List<JobKey> getFilteredAndSortedJobKeys(Filter filter, String sortColumn) throws SchedulerException;

    Returns list of job keys, which are filtered and sorted using values defined in filter.

  • JobBasicInfo getBasicInfoForJobKey(JobKey jobKey) throws SchedulerException;

    Returns basic information about job with given jobKey. Those information are returned as instance of JobsRecords.

  • JobDetailedInfo getScheduledJobDetailedInfo(JobBasicInfo jobBasicInfo);

    Returns detailed information about job with given JobBasicInfo.

Examples
Let’s say we have a module, which is able to send a SMS whenever proper MotechEvent is fired. It uses proper @MotechListener to listen for events and then handle them. We want to extend it with ability to use MotechSchedulerService and be able to schedule our own jobs. The following examples will illustrate how to achieve this. Let’s add methods for sending “Hello!” message to 000000000 every day at 8:00 AM and ability to schedule the same SMS for sending it next day.
@Autowired
MotechSchedulerService schedulerService;

private MotechEvent prepareMessage() {

    //Params below are very basic information about a SMS.
    //Those params means that SMS module will send a SMS with message "Hello!" to number 000000000.
    Map<String, Object> params = new HashMap();
    params.add("message", "Hello!");
    params.add("recipient", 000000000);

    return new MotechEvent("send_SMS_now", params);
}

public void scheduleSendSMSJob() {

    //First, we need a MotechEvent.
    MotechEvent = prepareMessage();

    //We'll also need a cron expression
    String cronExpression = "0 0 8 1/1 * ? *"

    //and a start date.
    Calendar calendar = Calendar.getInstance();
    calendar.add(Calendar.DAY_OF_YEAR, 1);
    Date tomorrow = calendar.getTime();

    //Now let' create our job.
    //We don't want it to stop so we set end date to null.
    //We also want to ignore past fires so we set ignorePastFiresAtStart flag to true.
    CronSchedulableJob cronJob = new CronSchedulableJob(motechEvent, cronExpression, tomorrow, null, true);

    //Now we need to schedule our job.
    schedulerService.safeScheduleJob(cronJob);
}

//Now same scenario, but we only want to send that SMS once, and we want to do it tomorow.
public void scheduleSendSMSNowJob() {

    MotechEvent = prepareMessage();

    Calendar calendar = Calendar.getInstance();
    calendar.add(Calendar.DAY_OF_YEAR, 1);
    Date tomorrow = calendar.getTime();

    //Now let' create our job
    RunOnceSchedulableJob job = new RunOnceSchedulableJob(motechEvent, tomorrow);

    schedulerService.safeScheduleRunOnceJob(job);
}

That’s it, scheduler module will take care of firing it at the right time. However, you need to have your listener ready to listen for the motech event and then handle it.

Handling of past fires

If ignorePastFiresAtStart is set to true and start date is in the past, fires, which occurred before current time will be ignored. Otherwise they will be fired immediately.

Handling misfires

If job that, for some reason, couldn’t be fired at specified time will be fired as soon as possible. However, if useOrginalFireTimeAfterMisfire is set to true it will have it’s fire date set to the original scheduled date. Otherwise it will be set to date of actual fire.

Additional resources

  • quartz library

    Quartz website containing all the information about quartz library and it’s classes.

  • cron expression

    Website explaining what cron expression is and how to build one.