/* * Copyright (C) 2014 Alfresco Software Limited. * * This file is part of Alfresco * * Alfresco is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Alfresco is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Alfresco. If not, see . */ package org.alfresco.util; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; 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; /** * This class tests that the CronTriggerBean correctly delays jobs when specified. * This test runs in about 25 seconds. * * @author Ahmed Owian */ 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 } } /** * Ensures that jobs that are coded without a delay run without delay. * @throws Exception */ @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); } /** * Ensures that jobs that are configured without a delay run without delay. * @throws BeansException * @throws Exception */ @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); } /** * Ensures that jobs that are coded with a delay run after the delay. * @throws Exception */ @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); } /** * Ensures that jobs that are configured with a delay run after the delay. * @throws BeansException * @throws Exception */ @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; } }