package org.oddjob.scheduling; import java.io.IOException; import java.text.ParseException; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import junit.framework.TestCase; import org.oddjob.FailedToStopException; import org.oddjob.MockOddjobServices; import org.oddjob.MockStateful; import org.oddjob.Oddjob; import org.oddjob.OddjobLookup; import org.oddjob.Resetable; import org.oddjob.Stateful; import org.oddjob.arooa.utils.DateHelper; import org.oddjob.arooa.xml.XMLConfiguration; import org.oddjob.framework.SimpleJob; import org.oddjob.framework.StopWait; import org.oddjob.jobs.SequenceJob; import org.oddjob.jobs.structural.ParallelJob; import org.oddjob.schedules.Interval; import org.oddjob.schedules.IntervalTo; import org.oddjob.schedules.Schedule; import org.oddjob.schedules.ScheduleContext; import org.oddjob.schedules.schedules.CountSchedule; import org.oddjob.schedules.schedules.DateSchedule; import org.oddjob.schedules.schedules.IntervalSchedule; import org.oddjob.schedules.schedules.NowSchedule; import org.oddjob.schedules.schedules.TimeSchedule; import org.oddjob.scheduling.state.TimerState; import org.oddjob.state.FlagState; import org.oddjob.state.JobState; import org.oddjob.state.ParentState; import org.oddjob.state.Resets; import org.oddjob.state.StateConditions; import org.oddjob.state.StateEvent; import org.oddjob.state.StateListener; import org.oddjob.tools.OddjobTestHelper; import org.oddjob.tools.StateSteps; import org.oddjob.util.Clock; public class RetryTest extends TestCase { private class OurClock implements Clock { Date date; public OurClock() { } public OurClock(String text) throws ParseException { date = DateHelper.parseDateTime(text); } public Date getDate() { return date; } } private class OurOddjobServices extends MockOddjobServices { Runnable runnable; long delay; boolean canceled; public ScheduledExecutorService getScheduledExecutor() { return new MockScheduledExecutorService() { public ScheduledFuture<?> schedule(Runnable runnable, long delay, TimeUnit unit) { OurOddjobServices.this.delay = delay; OurOddjobServices.this.runnable = runnable; return new MockScheduledFuture<Void>() { public boolean cancel(boolean interrupt) { canceled = true; return true; } }; } }; } } public void testRetry() throws ParseException, FailedToStopException { FlagState job = new FlagState(); job.setState(JobState.INCOMPLETE); TimeSchedule time = new TimeSchedule(); time.setFrom("14:45"); time.setTo("15:00"); Interval limits = time.nextDue( new ScheduleContext( DateHelper.parseDateTime("2009-02-10 12:00"))); IntervalSchedule retry = new IntervalSchedule(); retry.setInterval("00:05"); OurClock clock = new OurClock(); clock.date = DateHelper.parseDateTime("2009-02-10 14:50"); Retry test = new Retry(); test.setLimits(limits); test.setSchedule(retry); test.setClock(clock); test.setJob(job); OurOddjobServices oddjobServices = new OurOddjobServices(); test.setScheduleExecutorService( oddjobServices.getScheduledExecutor()); test.run(); assertNotNull(oddjobServices.runnable); assertEquals(0, oddjobServices.delay); assertEquals( DateHelper.parseDateTime("2009-02-10 14:50"), test.getNextDue()); oddjobServices.delay = -1; clock.date = DateHelper.parseDateTime("2009-02-10 14:53"); oddjobServices.runnable.run(); assertEquals(TimerState.STARTED, test.lastStateEvent().getState()); assertEquals(2 * 60 * 1000, oddjobServices.delay); assertEquals( DateHelper.parseDateTime("2009-02-10 14:55"), test.getNextDue()); oddjobServices.delay = -1; oddjobServices.runnable.run(); assertEquals(-1, oddjobServices.delay); assertEquals(null, test.getNextDue()); assertEquals(TimerState.INCOMPLETE, test.lastStateEvent().getState()); test.stop(); assertFalse(oddjobServices.canceled); } /** * Simulate retry taking longer than 5 minutes. * * @throws ParseException * @throws FailedToStopException */ public void testLongRetry() throws ParseException, FailedToStopException { FlagState job = new FlagState(); job.setState(JobState.INCOMPLETE); TimeSchedule time = new TimeSchedule(); time.setFrom("14:45"); time.setTo("15:00"); Interval limits = time.nextDue( new ScheduleContext( DateHelper.parseDateTime("2009-02-10 12:00"))); IntervalSchedule retry = new IntervalSchedule(); retry.setInterval("00:05"); OurClock clock = new OurClock(); clock.date = DateHelper.parseDateTime("2009-02-10 14:50"); Retry test = new Retry(); test.setLimits(limits); test.setSchedule(retry); test.setClock(clock); test.setJob(job); OurOddjobServices oddjobServices = new OurOddjobServices(); test.setScheduleExecutorService( oddjobServices.getScheduledExecutor()); test.run(); assertNotNull(oddjobServices.runnable); assertEquals(0, oddjobServices.delay); assertEquals( DateHelper.parseDateTime("2009-02-10 14:50"), test.getNextDue()); oddjobServices.delay = -1; clock.date = DateHelper.parseDateTime("2009-02-10 14:57"); oddjobServices.runnable.run(); assertEquals(TimerState.ACTIVE, test.lastStateEvent().getState()); assertEquals(0, oddjobServices.delay); assertEquals( DateHelper.parseDateTime("2009-02-10 14:55"), test.getNextDue()); oddjobServices.delay = -1; oddjobServices.runnable.run(); assertEquals(-1, oddjobServices.delay); assertEquals(null, test.getNextDue()); assertEquals(TimerState.INCOMPLETE, test.lastStateEvent().getState()); test.stop(); assertFalse(oddjobServices.canceled); } /** * Test A Manual Re-run to complete cancels the retry. * * @throws ParseException * @throws FailedToStopException */ public void testManualRetry() throws ParseException, FailedToStopException { FlagState job = new FlagState(); job.setState(JobState.INCOMPLETE); IntervalSchedule schedule = new IntervalSchedule(); schedule.setInterval("12:00"); OurClock clock = new OurClock(); clock.date = DateHelper.parseDateTime("2009-04-03 08:00"); Retry test = new Retry(); test.setSchedule(schedule); test.setClock(clock); test.setJob(job); OurOddjobServices oddjobServices = new OurOddjobServices(); test.setScheduleExecutorService( oddjobServices.getScheduledExecutor()); test.run(); oddjobServices.runnable.run(); assertEquals(12 * 60 * 60 * 1000, oddjobServices.delay); job.setState(JobState.COMPLETE); job.softReset(); job.run(); // No affect. assertEquals(12 * 60 * 60 * 1000, oddjobServices.delay); assertEquals( DateHelper.parseDateTime("2009-04-03 20:00"), test.getNextDue()); oddjobServices.delay = -1; // next time timer runs job it should go straight to complete. oddjobServices.runnable.run(); assertEquals(TimerState.COMPLETE, test.lastStateEvent().getState()); assertEquals(-1, oddjobServices.delay); test.stop(); assertFalse(oddjobServices.canceled); assertEquals(TimerState.COMPLETE, test.lastStateEvent().getState()); } private class OurJob extends MockStateful implements Runnable, Resetable { boolean reset; final List<StateListener> listeners = new ArrayList<StateListener>(); public void addStateListener(StateListener listener) { listeners.add(listener); listener.jobStateChange(new StateEvent(this, JobState.READY)); } public void removeStateListener(StateListener listener) { listeners.remove(listener); } public void run() { List<StateListener> copy = new ArrayList<StateListener>(listeners); for (StateListener listener: copy) { listener.jobStateChange(new StateEvent(this, JobState.COMPLETE)); } } public boolean hardReset() { throw new RuntimeException("Unexpected."); } public boolean softReset() { reset = true; return true; } } /** * Test a one off schedule that completes. * * @throws Exception */ public void testSimpleSchedule() throws Exception { DateSchedule schedule = new DateSchedule(); schedule.setOn("2020-12-25"); OurJob ourJob = new OurJob(); Retry test = new Retry(); test.setSchedule(schedule); test.setJob(ourJob); OurOddjobServices oddjobServices = new OurOddjobServices(); test.setScheduleExecutorService( oddjobServices.getScheduledExecutor()); test.run(); Date expected = DateHelper.parseDate("2020-12-25"); assertEquals(expected, test.getNextDue()); assertEquals(expected, test.getCurrent().getFromDate()); oddjobServices.delay = -1; oddjobServices.runnable.run(); assertNull(null, test.getNextDue()); assertEquals(-1, oddjobServices.delay); assertTrue(ourJob.reset); assertEquals(TimerState.COMPLETE, test.lastStateEvent().getState()); test.setJob(null); test.destroy(); assertEquals(0, ourJob.listeners.size()); assertFalse(oddjobServices.canceled); } /** * Schedule doesn't have to be serializable. * * @throws IOException * @throws ClassNotFoundException */ public void testSerializeUnserializbleSchedule() throws IOException, ClassNotFoundException { Retry test = new Retry(); test.setSchedule(new Schedule() { public IntervalTo nextDue(ScheduleContext context) { return null; } }); Retry copy = OddjobTestHelper.copy(test); assertNull(copy.getSchedule()); } public void testStateNotifications() throws InterruptedException { FlagState incomplete = new FlagState(JobState.INCOMPLETE); DefaultExecutors defaultServices = new DefaultExecutors(); Retry test = new Retry(); StateSteps testStates = new StateSteps(test); testStates.startCheck(TimerState.STARTABLE, TimerState.STARTING, TimerState.ACTIVE, TimerState.INCOMPLETE); CountSchedule count = new CountSchedule(); count.setCount(3); count.setRefinement(new NowSchedule()); test.setSchedule(count); test.setScheduleExecutorService( defaultServices.getScheduledExecutor()); SequenceJob sequence = new SequenceJob(); sequence.setFrom(1); Resets resets = new Resets(); resets.setHarden(true); resets.setJob(sequence); ParallelJob parallel = new ParallelJob(); parallel.setExecutorService(defaultServices.getPoolExecutor()); parallel.setJobs(0, resets); parallel.setJobs(1, incomplete); test.setJob(parallel); test.run(); testStates.checkWait(); assertEquals(3, sequence.getCurrent().intValue()); testStates.startCheck(TimerState.INCOMPLETE, TimerState.STARTABLE); test.softReset(); testStates.checkNow(); testStates.startCheck(TimerState.STARTABLE, TimerState.STARTING, TimerState.ACTIVE, TimerState.INCOMPLETE); test.run(); testStates.checkWait(); assertEquals(6, sequence.getCurrent().intValue()); defaultServices.stop(); } private class ExecuteImmediately extends MockScheduledExecutorService { public ScheduledFuture<?> schedule(Runnable runnable, long delay, TimeUnit unit) { new Thread(runnable).start(); return new MockScheduledFuture<Void>(); } }; private class LockExamineJob extends SimpleJob { int i = 0; @Override protected int execute() throws Throwable { if (i++ == 2) { return 0; } return 1; } @Override public boolean hardReset() { throw new RuntimeException("Unexpected."); } @Override public boolean softReset() { try { stateHandler().assertLockHeld(); fail("Shouldn't be locked."); } catch (IllegalStateException e) { /// expected. } return super.softReset(); } } public void testLocking() throws FailedToStopException { Retry test = new Retry(); test.setScheduleExecutorService( new ExecuteImmediately()); test.setSchedule(new NowSchedule()); test.setJob(new LockExamineJob()); test.run(); new StopWait(test).run(); assertEquals(TimerState.COMPLETE, test.lastStateEvent().getState()); } private class TestListeners extends SimpleJob { int i = 0; int listeners = 0; @Override protected int execute() throws Throwable { assertEquals(2, listeners); if (i++ == 2) { return 0; } return 1; } @Override public boolean hardReset() { throw new RuntimeException("Unexpected."); } @Override public boolean softReset() { return super.softReset(); } @Override public void addStateListener(StateListener listener) { listeners++; super.addStateListener(listener); } @Override public void removeStateListener(StateListener listener) { listeners--; super.removeStateListener(listener); } } /** Test for Bug where listener wasn't removed before scheduling. * @throws FailedToStopException */ public void testListeners() throws FailedToStopException { Retry test = new Retry(); test.setScheduleExecutorService(new ExecuteImmediately()); test.setSchedule(new NowSchedule()); test.setJob(new TestListeners()); test.run(); new StopWait(test).run(); assertEquals(TimerState.COMPLETE, test.lastStateEvent().getState()); } private class ExecuteNever extends MockScheduledExecutorService { public ScheduledFuture<?> schedule(Runnable runnable, long delay, TimeUnit unit) { return new MockScheduledFuture<Void>() { @Override public boolean cancel(boolean mayInterruptIfRunning) { return true; } }; } }; private class LaterJob extends SimpleJob { void start() { stateHandler().waitToWhen(StateConditions.READY, new Runnable() { public void run() { stateHandler().setState(JobState.EXECUTING); stateHandler().fireEvent(); } }); } void complete() { stateHandler().waitToWhen(StateConditions.EXECUTING, new Runnable() { public void run() { stateHandler().setState(JobState.COMPLETE); stateHandler().fireEvent(); } }); } @Override protected int execute() throws Throwable { throw new RuntimeException("Unexpected."); } @Override public boolean hardReset() { throw new RuntimeException("Unexpected."); } @Override public boolean softReset() { throw new RuntimeException("Unexpected."); } } public void testStopFirst() throws FailedToStopException { Retry test = new Retry(); test.setScheduleExecutorService(new ExecuteNever()); test.setSchedule(new NowSchedule()); // schedule is irrelevant final LaterJob job = new LaterJob(); test.setJob(job); test.run(); assertEquals(TimerState.ACTIVE, test.lastStateEvent().getState()); job.start(); assertEquals(TimerState.ACTIVE, test.lastStateEvent().getState()); SimpleJob stop = new SimpleJob() { @Override protected int execute() throws Throwable { job.complete(); return 0; } }; new Thread(stop).start(); test.stop(); assertEquals(TimerState.STARTABLE, test.lastStateEvent().getState()); } public void testStopLast() throws FailedToStopException { Retry test = new Retry(); test.setScheduleExecutorService(new ExecuteNever()); test.setSchedule(new NowSchedule()); // schedule is irrelevant LaterJob job = new LaterJob(); test.setJob(job); test.run(); assertEquals(TimerState.ACTIVE, test.lastStateEvent().getState()); job.start(); job.complete(); assertEquals(TimerState.ACTIVE, test.lastStateEvent().getState()); test.stop(); assertEquals(TimerState.STARTABLE, test.lastStateEvent().getState()); } public void testWithLimitsLongOverDue() throws ParseException { FlagState job = new FlagState(JobState.INCOMPLETE); OurClock clock = new OurClock("2010-07-25 08:00"); IntervalTo limits = new IntervalTo( DateHelper.parseDateTime("2010-06-10 07:00"), DateHelper.parseDateTime("2010-06-10 10:00") ); OurOddjobServices services = new OurOddjobServices(); Schedule schedule = new IntervalSchedule(15 * 60 * 1000L); Retry test = new Retry(); test.setClock(clock); test.setLimits(limits); test.setSchedule(schedule); test.setScheduleExecutorService(services.getScheduledExecutor()); test.setJob(job); test.run(); assertNotNull(services.runnable); assertEquals(0, services.delay); assertEquals(DateHelper.parseDateTime("2010-06-10 07:00"), test.getNextDue()); services.runnable.run(); assertEquals(null, test.getNextDue()); } public void testWithLimitsLongOverDueCountSchedule() throws ParseException { FlagState job = new FlagState(JobState.INCOMPLETE); OurClock clock = new OurClock("2010-07-25 08:00"); IntervalTo limits = new IntervalTo( DateHelper.parseDateTime("2010-06-10 07:00"), DateHelper.parseDateTime("2010-06-10 10:00") ); OurOddjobServices services = new OurOddjobServices(); CountSchedule schedule = new CountSchedule(3); schedule.setRefinement(new NowSchedule()); Retry test = new Retry(); test.setClock(clock); test.setLimits(limits); test.setSchedule(schedule); test.setScheduleExecutorService(services.getScheduledExecutor()); test.setJob(job); test.run(); assertNotNull(services.runnable); assertEquals(0, services.delay); assertEquals(DateHelper.parseDateTime("2010-06-10 07:00"), test.getNextDue()); services.runnable.run(); assertEquals(null, test.getNextDue()); } public void testFilePollingExample() throws FailedToStopException, InterruptedException { Oddjob oddjob = new Oddjob(); oddjob.setConfiguration(new XMLConfiguration( "org/oddjob/scheduling/RetryExample.xml", getClass().getClassLoader())); oddjob.load(); OddjobLookup lookup = new OddjobLookup(oddjob); Stateful exists = (Stateful) lookup.lookup("check"); StateSteps oddjobStates = new StateSteps(oddjob); oddjobStates.startCheck(ParentState.READY, ParentState.EXECUTING, ParentState.ACTIVE, ParentState.STARTED); StateSteps existsStates = new StateSteps(exists); existsStates.startCheck(JobState.READY, JobState.EXECUTING, JobState.INCOMPLETE); oddjob.run(); oddjobStates.checkWait(); existsStates.checkWait(); oddjobStates.startCheck(ParentState.STARTED, ParentState.READY); oddjob.stop(); oddjobStates.checkNow(); oddjob.destroy(); } }