From e308af0cc45749fbe77d4362bcda55092859ddb5 Mon Sep 17 00:00:00 2001 From: Ahmed Owian Date: Fri, 28 Feb 2014 10:42:22 +0000 Subject: [PATCH] ACE-403: As an administrator I want to be able to defer the start of cron based jobs - Added System Integration Test to enforce that all CronTriggerBeans have a specified delay. git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@63443 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../org/alfresco/Repository01TestSuite.java | 3 +- .../util/CronTriggerBeanSystemTest.java | 71 ++++ .../alfresco/util/CronTriggerBeanTest.java | 330 +++++++++++------- .../extension/wcm-bootstrap-context.xml | 6 + 4 files changed, 278 insertions(+), 132 deletions(-) create mode 100644 source/test-java/org/alfresco/util/CronTriggerBeanSystemTest.java diff --git a/source/test-java/org/alfresco/Repository01TestSuite.java b/source/test-java/org/alfresco/Repository01TestSuite.java index 7e4cb39167..837eb2c951 100644 --- a/source/test-java/org/alfresco/Repository01TestSuite.java +++ b/source/test-java/org/alfresco/Repository01TestSuite.java @@ -430,11 +430,12 @@ public class Repository01TestSuite extends TestSuite static void tests63(TestSuite suite) // tests="187" time="364.334" { suite.addTest(new JUnit4TestAdapter(org.alfresco.util.schemacomp.DbToXMLTest.class)); - suite.addTest(new JUnit4TestAdapter(org.alfresco.util.schemacomp.ExportDbTest.class)); + suite.addTest(new JUnit4TestAdapter(org.alfresco.util.schemacomp.ExportDbTest.class)); suite.addTest(new JUnit4TestAdapter(org.alfresco.util.schemacomp.SchemaReferenceFileTest.class)); suite.addTest(new JUnit4TestAdapter(org.alfresco.util.test.junitrules.AlfrescoPersonTest.class)); suite.addTest(new JUnit4TestAdapter(org.alfresco.util.test.junitrules.ApplicationContextInitTest.class)); suite.addTest(new JUnit4TestAdapter(org.alfresco.util.test.junitrules.TemporaryNodesTest.class)); suite.addTest(new JUnit4TestAdapter(org.alfresco.util.test.junitrules.TemporarySitesTest.class)); + suite.addTest(new JUnit4TestAdapter(org.alfresco.util.CronTriggerBeanTest.class)); } } diff --git a/source/test-java/org/alfresco/util/CronTriggerBeanSystemTest.java b/source/test-java/org/alfresco/util/CronTriggerBeanSystemTest.java new file mode 100644 index 0000000000..760b62f484 --- /dev/null +++ b/source/test-java/org/alfresco/util/CronTriggerBeanSystemTest.java @@ -0,0 +1,71 @@ +/* + * 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.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.*; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +/** + * This class tests that the CronTriggerBean correctly delays jobs when + * specified. + * + * @author Ahmed Owian + */ +public class CronTriggerBeanSystemTest +{ + private ClassPathXmlApplicationContext context; + + @Before + public void setUp() throws Exception + { + this.context = (ClassPathXmlApplicationContext) ApplicationContextHelper + .getApplicationContext(); + } + + /** + * All CronTriggerBean classes should be configured with a delay + * to allow the server to start before the jobs run. + */ + @Test + public void testAllCronTriggerBeansHaveDelay() + { + Map beans = this.context + .getBeansOfType(org.alfresco.util.CronTriggerBean.class); + assertFalse(beans.isEmpty()); + List undelayedJobs = new ArrayList<>(); + + for (Map.Entry entry : beans.entrySet()) + { + CronTriggerBean bean = entry.getValue(); + if (bean.getStartDelay() == 0) + { + undelayedJobs.add(entry.getKey()); + } + } + + assertTrue("Undelayed CronTriggerBeans: " + undelayedJobs, undelayedJobs.isEmpty()); + } +} diff --git a/source/test-java/org/alfresco/util/CronTriggerBeanTest.java b/source/test-java/org/alfresco/util/CronTriggerBeanTest.java index 998720c7f0..eb9b0ea322 100644 --- a/source/test-java/org/alfresco/util/CronTriggerBeanTest.java +++ b/source/test-java/org/alfresco/util/CronTriggerBeanTest.java @@ -1,3 +1,21 @@ +/* + * 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.*; @@ -16,139 +34,183 @@ 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; + +/** + * This class tests that the CronTriggerBean correctly delays jobs when specified. + * @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(); + private ClassPathXmlApplicationContext context; - @Before - public void setUp() throws Exception { - dummyJobRuns = new HashMap>(); - this.context = null; - this.scheduler = null; - } + private Scheduler scheduler; - @After - public void tearDown() throws Exception { - try { - this.scheduler.shutdown(); - } catch (Exception e) { - // do nothing - } - - try { - context.close(); - } catch (Exception e) { - // do nothing - } - } + private static Map> dummyJobRuns; - @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(); + private static Object lockToken = new Object(); - assertJobRunsAfterInterval(jobRuns); - scheduler.shutdown(); - this.assertJobStopsAfterShutdown(jobRuns); - } + @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 testConfiguredCronTriggerBean() throws BeansException, Exception { + 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"); + 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 + 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); + + /** + * 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); - + 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 + 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.", + 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 + 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 + // 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()); + int runs = jobRuns.size(); + Thread.sleep(INTERVAL); + assertEquals(runs, jobRuns.size()); Thread.sleep(INTERVAL); assertEquals(runs, jobRuns.size()); } @@ -156,45 +218,51 @@ public class CronTriggerBeanTest { private void assertJobRunsAfterInterval(List jobRuns) throws InterruptedException { // After the interval, there should be at least one run - Thread.sleep(INTERVAL); - assertTrue(jobRuns.size() > 0); + 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) + synchronized (lockToken) { // It should not run before the start time - while (System.currentTimeMillis() < startTime) { - assertEquals(0, jobRuns.size()); + 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; - } + + 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; + } } diff --git a/source/test-resources/alfresco/extension/wcm-bootstrap-context.xml b/source/test-resources/alfresco/extension/wcm-bootstrap-context.xml index fd3498995c..929b687ca4 100644 --- a/source/test-resources/alfresco/extension/wcm-bootstrap-context.xml +++ b/source/test-resources/alfresco/extension/wcm-bootstrap-context.xml @@ -77,6 +77,9 @@ 0 * * * * ? + + ${system.cronJob.startDelayMinutes} + @@ -102,6 +105,9 @@ 0 30 3 * * ? + + ${system.cronJob.startDelayMinutes} +