package org.alfresco.util; import static org.junit.Assert.*; import java.util.*; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.quartz.Job; import org.quartz.JobDetail; import org.quartz.JobExecutionContext; import org.quartz.JobExecutionException; import org.quartz.Scheduler; import org.quartz.impl.StdSchedulerFactory; import org.springframework.beans.BeansException; import org.springframework.context.support.ClassPathXmlApplicationContext; public class CronTriggerBeanTest { // One second - an arbitrarily small amount of time to allow for the scheduler to start the jobs final long PRECISION_LEEWAY = 1000L; final long INTERVAL = 1000L;// One run every second private ClassPathXmlApplicationContext context; private Scheduler scheduler; private static Map> dummyJobRuns; private static Object lockToken = new Object(); @Before public void setUp() throws Exception { dummyJobRuns = new HashMap>(); this.context = null; this.scheduler = null; } @After public void tearDown() throws Exception { try { this.scheduler.shutdown(); } catch (Exception e) { // do nothing } try { context.close(); } catch (Exception e) { // do nothing } } @Test public void testCodedCronTriggerBean() throws Exception { final String JOB_NAME = "codedCronJob"; List jobRuns = this.getRunList(JOB_NAME); assertEquals(0, jobRuns.size()); scheduler = StdSchedulerFactory.getDefaultScheduler(); scheduler.start(); CronTriggerBean ctBean = new CronTriggerBean(); ctBean.setBeanName("Dummy"); ctBean.setCronExpression("0/1 * * * * ? *"); ctBean.setEnabled(true); JobDetail jobDetail = new JobDetail(JOB_NAME, "DefaultGroup", DummyJob.class); ctBean.setJobDetail(jobDetail); ctBean.setScheduler(scheduler); ctBean.afterPropertiesSet(); assertJobRunsAfterInterval(jobRuns); scheduler.shutdown(); this.assertJobStopsAfterShutdown(jobRuns); } @Test public void testConfiguredCronTriggerBean() throws BeansException, Exception { final String JOB_NAME = "configuredCronJob"; List jobRuns = this.getRunList(JOB_NAME); assertEquals(0, jobRuns.size()); context = new ClassPathXmlApplicationContext( "alfresco/scheduler-core-context.xml", "org/alfresco/util/test-scheduled-jobs-context.xml"); CronTriggerBean ctBean = context.getBean("cronTriggerBean", CronTriggerBean.class); scheduler = ctBean.getScheduler(); scheduler.start(); assertJobRunsAfterInterval(jobRuns); context.close(); // When the context closes, the scheduler should close, thereby stopping the job assertJobStopsAfterShutdown(jobRuns); } @Test public void testCodedDelayedCronTriggerBean() throws Exception { final String JOB_NAME = "codedDelayedCronJob"; List jobRuns = this.getRunList(JOB_NAME); assertEquals(0, jobRuns.size()); CronTriggerBean ctBean = new CronTriggerBean(); ctBean.setBeanName("Dummy"); ctBean.setCronExpression("0/1 * * * * ? *"); ctBean.setEnabled(true); JobDetail jobDetail = new JobDetail(JOB_NAME, "DefaultGroup", DummyJob.class); ctBean.setJobDetail(jobDetail); scheduler = StdSchedulerFactory.getDefaultScheduler(); ctBean.setScheduler(scheduler); final long START_DELAY = 4000L; ctBean.setStartDelay(START_DELAY); final long PRE_SCHEDULING = System.currentTimeMillis(); ctBean.afterPropertiesSet(); // This is when the trigger is actually scheduled long startTime = ctBean.getTrigger().getStartTime().getTime(); assertTrue("The startTime should be the time when the trigger is scheduled plus the START_DELAY.", startTime - PRE_SCHEDULING - START_DELAY <= PRECISION_LEEWAY); assertEquals(0, jobRuns.size()); scheduler.start(); assertJobDoesNotRunBeforeStartTime(jobRuns, startTime); assertJobRunsAfterInterval(jobRuns); scheduler.shutdown(); assertJobStopsAfterShutdown(jobRuns); } @Test public void testConfiguredDelayedCronTriggerBean() throws BeansException, Exception { final String JOB_NAME = "configuredDelayedCronJob"; List jobRuns = this.getRunList(JOB_NAME); assertEquals(0, jobRuns.size()); // Captures the system time before the Spring context is initialized and the triggers are scheduled final long PRE_INITIALIZATION = System.currentTimeMillis(); context = new ClassPathXmlApplicationContext( "alfresco/scheduler-core-context.xml", "org/alfresco/util/test-scheduled-jobs-context.xml"); CronTriggerBean ctBean = context.getBean("cronTriggerBeanDelayed", CronTriggerBean.class); final long START_DELAY = ctBean.getStartDelay(); long startTime = ctBean.getTrigger().getStartTime().getTime(); assertTrue("The startTime should be the time when the Spring context is initialized plus the START_DELAY.", startTime - PRE_INITIALIZATION - START_DELAY <= PRECISION_LEEWAY); assertEquals(0, jobRuns.size()); scheduler = ctBean.getScheduler(); scheduler.start(); assertJobDoesNotRunBeforeStartTime(jobRuns, startTime); assertJobRunsAfterInterval(jobRuns); context.close(); // When the context closes, the scheduler should close, thereby stopping the job assertJobStopsAfterShutdown(jobRuns); } private void assertJobStopsAfterShutdown(List jobRuns) throws InterruptedException { // Gives the job one final interval to stop, but after that, there should be no more runs Thread.sleep(INTERVAL); int runs = jobRuns.size(); Thread.sleep(INTERVAL); assertEquals(runs, jobRuns.size()); Thread.sleep(INTERVAL); assertEquals(runs, jobRuns.size()); } private void assertJobRunsAfterInterval(List jobRuns) throws InterruptedException { // After the interval, there should be at least one run Thread.sleep(INTERVAL); assertTrue(jobRuns.size() > 0); } private void assertJobDoesNotRunBeforeStartTime(List jobRuns, long startTime) throws InterruptedException { // Synchronizing on an object prevents jobs from running while checking synchronized (lockToken) { // It should not run before the start time while (System.currentTimeMillis() < startTime) { assertEquals(0, jobRuns.size()); Thread.sleep(20); // Sleeps so as to not take up all the CPU } } } public static class DummyJob implements Job { public void execute(JobExecutionContext context) throws JobExecutionException { synchronized (lockToken) { long now = System.currentTimeMillis(); ArrayList runs = dummyJobRuns.get(context.getJobDetail().getName()); if (runs == null) { runs = new ArrayList(); dummyJobRuns.put(context.getJobDetail().getName(), runs); } runs.add(now); } } } private List getRunList(String jobName) { ArrayList runs = dummyJobRuns.get(jobName); if (runs == null) { runs = new ArrayList(); dummyJobRuns.put(jobName, runs); } return runs; } }