(blog-of ‘Alex’)

A personal blog about software development and other things. All opinions expressed here are my own, unless explicitly stated.

Quartz+Spring scheduling-on-demand sample

Some time ago I stumbled at issues with Quartz 2 and Spring 3. All the standard samples illustrate fairly simple use cases and there were no samples dedicated to quite a frequent task: schedule certain job on demand, not to mention that some “standard” spring classes dedicated to ease work with Quartz are outdated and introduces some misterious errors in runtime (IncompatibleClassChangeError is one of these ones).

To illustrate this particular kind of task when you need scheduling-on-demand suppose you have a procedure that must be scheduled after the user entered some data and then executed after some time.

One of the best solution that is reliable and has fail-over on top of inner DB persistence, clustering capabilities is Quartz.

I’ve wrote a JUnit test that doesn’t test anything but actually illustrates the work with Quartz, it is quite self evident:

import org.junit.Test;
import org.junit.runner.RunWith;
import org.quartz.Job;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.Scheduler;
import org.quartz.SchedulerContext;
import org.quartz.SchedulerException;
import org.quartz.SimpleTrigger;
import org.quartz.Trigger;
import org.quartz.impl.triggers.SimpleTriggerImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.quartz.JobDetailFactoryBean;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.Date;

/**
 * Test scheduling-on-demand with spring and quartz
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = { SchedTest.Config.class })
public class SchedTest {

    private static final String SERVICE_KEY = "SERVICE_KEY";

    @Autowired
    private Scheduler scheduler;

    @Autowired
    private ApplicationContext context;

    @Test
    public void shouldExecuteSampleJob() throws Exception {
        {
            final String id = "1";
            scheduler.scheduleJob(createJobDetail(id, "task1"), createSingleshotTrigger(id, 100L));
        }
        {
            final String id = "2";
            scheduler.scheduleJob(createJobDetail(id, "task2"), createSingleshotTrigger(id, 150L));
        }
        Thread.sleep(1000L);
    }

    private Trigger createSingleshotTrigger(String triggerName, long delay) {
        final SimpleTriggerImpl simpleTrigger = new SimpleTriggerImpl();
        simpleTrigger.setMisfireInstruction(SimpleTrigger.MISFIRE_INSTRUCTION_FIRE_NOW);
        simpleTrigger.setStartTime(new Date(System.currentTimeMillis() + delay));
        simpleTrigger.setRepeatCount(0);
        simpleTrigger.setName(triggerName);
        simpleTrigger.setRepeatCount(0);
        return simpleTrigger;
    }

    private JobDetail createJobDetail(String jobName, String url) {
        final JobDetailFactoryBean jobDetailFactoryBean = new JobDetailFactoryBean();
        jobDetailFactoryBean.setApplicationContext(context);
        jobDetailFactoryBean.setName(jobName);
        jobDetailFactoryBean.setJobClass(JobStarter.class);
        jobDetailFactoryBean.afterPropertiesSet();

        final JobDetail jobDetail = jobDetailFactoryBean.getObject();
        // data
        jobDetail.getJobDataMap().put("url", url);

        return jobDetail;
    }

    public static class JobStarter implements Job {

        @Override
        public void execute(JobExecutionContext context) throws JobExecutionException {
            try {
                final SchedulerContext schedulerContext = context.getScheduler().getContext();
                schedulerContext.get(SERVICE_KEY);

                final ApplicationContext applicationContext = (ApplicationContext) schedulerContext.get(
                        ApplicationContext.class.getName());

                final JobDetail jobDetail = context.getJobDetail();
                final String url = jobDetail.getJobDataMap().getString("url");
                System.out.println("url=" + url + ", applicationContext=" + applicationContext);
            } catch (SchedulerException e) {
                throw new JobExecutionException(e);
            }
        }
    }

    @Configuration
    public static class Config {

        @Bean
        public SchedulerFactoryBean schedulerFactoryBean() {
            final SchedulerFactoryBean bean = new SchedulerFactoryBean();
            bean.setApplicationContextSchedulerContextKey(ApplicationContext.class.getName());
            return bean;
        }
    }
}

Have fun!