diff --git a/execution/src/main/java/org/springframework/batch/execution/facade/JobExecutionListenerSupport.java b/execution/src/main/java/org/springframework/batch/execution/facade/JobExecutionListenerSupport.java new file mode 100644 index 000000000..59869073c --- /dev/null +++ b/execution/src/main/java/org/springframework/batch/execution/facade/JobExecutionListenerSupport.java @@ -0,0 +1,47 @@ +/* + * Copyright 2006-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.batch.execution.facade; + +import org.springframework.batch.core.domain.JobExecution; + +/** + * Simple no-op implementation of {@link JobExecutionListener} which does + * nothing. + * + * @author Dave Syer + * + */ +public class JobExecutionListenerSupport implements JobExecutionListener { + + /** + * No-op for subclasses to extend. + * + * @see org.springframework.batch.execution.facade.JobExecutionListener#after(org.springframework.batch.core.domain.JobExecution) + */ + public void after(JobExecution execution) { + // no-op + } + + /** + * No-op for subclasses to extend. + * + * @see org.springframework.batch.execution.facade.JobExecutionListener#before(org.springframework.batch.core.domain.JobExecution) + */ + public void before(JobExecution execution) { + // no-op + } + +} diff --git a/execution/src/main/java/org/springframework/batch/execution/facade/SimpleJobExecutorFacade.java b/execution/src/main/java/org/springframework/batch/execution/facade/SimpleJobExecutorFacade.java index 08ded6198..2eedf55bb 100644 --- a/execution/src/main/java/org/springframework/batch/execution/facade/SimpleJobExecutorFacade.java +++ b/execution/src/main/java/org/springframework/batch/execution/facade/SimpleJobExecutorFacade.java @@ -16,7 +16,10 @@ package org.springframework.batch.execution.facade; +import java.util.ArrayList; +import java.util.Collections; import java.util.Iterator; +import java.util.List; import java.util.Properties; import org.springframework.batch.core.configuration.JobConfiguration; @@ -64,6 +67,18 @@ public class SimpleJobExecutorFacade implements JobExecutorFacade, private Object mutex = new Object(); + private List listeners = new ArrayList(); + + /** + * Public setter for the listeners property. + * + * @param listeners + * the listeners to set - a list of {@link JobExecutionListener}. + */ + public void setJobExecutionListeners(List listeners) { + this.listeners = listeners; + } + /** * Public accessor for the running property. * @@ -158,7 +173,9 @@ public class SimpleJobExecutorFacade implements JobExecutorFacade, } /** - * Internal accounting for the job execution. Callback at start of job. + * Internal accounting for the job execution. Callback at start of job, + * dealing with internal housekeeping before delegating to listeners in the + * order that they were given. * * @param execution */ @@ -166,14 +183,28 @@ public class SimpleJobExecutorFacade implements JobExecutorFacade, synchronized (mutex) { running++; } + for (Iterator iterator = listeners.iterator(); iterator.hasNext();) { + JobExecutionListener listener = (JobExecutionListener) iterator + .next(); + listener.before(execution); + } } /** - * Internal accounting for the job execution. Callback at end of job. + * Internal accounting for the job execution. Callback at end of job + * delegating first to listeners, in reverse order to the list supplied, and + * then finally dealing with internal housekeeping. * * @param execution */ public void after(JobExecution execution) { + ArrayList reversed = new ArrayList(listeners); + Collections.reverse(reversed); + for (Iterator iterator = reversed.iterator(); iterator.hasNext();) { + JobExecutionListener listener = (JobExecutionListener) iterator + .next(); + listener.after(execution); + } synchronized (mutex) { // assume execution is synchronous so when we get to here we are // not running any more diff --git a/execution/src/main/java/org/springframework/batch/execution/step/simple/SimpleStepExecutor.java b/execution/src/main/java/org/springframework/batch/execution/step/simple/SimpleStepExecutor.java index f361797a5..7d7d468cb 100644 --- a/execution/src/main/java/org/springframework/batch/execution/step/simple/SimpleStepExecutor.java +++ b/execution/src/main/java/org/springframework/batch/execution/step/simple/SimpleStepExecutor.java @@ -77,11 +77,6 @@ public class SimpleStepExecutor implements StepExecutor { */ private static final String STEP_EXECUTION_KEY = "STEP_EXECUTION"; - /** - * Attribute key for statistics instance in step context. - */ - public static final String STATISTICS_KEY = "STATISTICS"; - private RepeatOperations chunkOperations = new RepeatTemplate(); private RepeatOperations stepOperations = new RepeatTemplate(); @@ -239,8 +234,6 @@ public class SimpleStepExecutor implements StepExecutor { } Properties statistics = getStatistics(module); stepExecution.setStatistics(statistics); - context.setAttribute(STATISTICS_KEY, - statistics); stepExecution.incrementCommitCount(); jobRepository.saveOrUpdate(stepExecution); return result; diff --git a/execution/src/test/java/org/springframework/batch/execution/facade/SimpleJobExecutorFacadeTests.java b/execution/src/test/java/org/springframework/batch/execution/facade/SimpleJobExecutorFacadeTests.java index ee955aac9..1356e500c 100644 --- a/execution/src/test/java/org/springframework/batch/execution/facade/SimpleJobExecutorFacadeTests.java +++ b/execution/src/test/java/org/springframework/batch/execution/facade/SimpleJobExecutorFacadeTests.java @@ -16,7 +16,9 @@ package org.springframework.batch.execution.facade; +import java.util.ArrayList; import java.util.Collections; +import java.util.List; import java.util.Properties; import junit.framework.TestCase; @@ -25,12 +27,9 @@ import org.easymock.MockControl; import org.springframework.batch.core.configuration.JobConfiguration; import org.springframework.batch.core.configuration.JobConfigurationLocator; import org.springframework.batch.core.configuration.NoSuchJobConfigurationException; -import org.springframework.batch.core.configuration.StepConfiguration; import org.springframework.batch.core.domain.JobExecution; import org.springframework.batch.core.domain.JobInstance; -import org.springframework.batch.core.domain.StepExecution; import org.springframework.batch.core.executor.JobExecutor; -import org.springframework.batch.core.executor.StepExecutor; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.runtime.JobExecutionRegistry; import org.springframework.batch.core.runtime.SimpleJobIdentifier; @@ -46,23 +45,23 @@ import org.springframework.batch.repeat.context.RepeatContextSupport; */ public class SimpleJobExecutorFacadeTests extends TestCase { - SimpleJobExecutorFacade jobExecutorFacade = new SimpleJobExecutorFacade(); + private SimpleJobExecutorFacade jobExecutorFacade = new SimpleJobExecutorFacade(); - JobExecutor jobExecutor; + private JobExecutor jobExecutor; - MockControl jobLifecycleControl = MockControl.createControl(JobExecutor.class); + private JobRepository jobRepository; - JobRepository jobRepository; + private MockControl jobRepositoryControl = MockControl.createControl(JobRepository.class); - MockControl jobRepositoryControl = MockControl.createControl(JobRepository.class); + private JobConfiguration jobConfiguration = new JobConfiguration(); - JobConfiguration jobConfiguration = new JobConfiguration(); + private volatile boolean running = false; - StepExecutor stepExecutor = new StepExecutor() { - public ExitStatus process(StepConfiguration configuration, StepExecution stepExecution) throws BatchCriticalException { - return ExitStatus.FINISHED; - } - }; + private SimpleJobIdentifier jobIdentifier = new SimpleJobIdentifier(); + + private JobExecution jobExecution = new JobExecution(new JobInstance(jobIdentifier)); + + private List list = new ArrayList(); protected void setUp() throws Exception { @@ -104,12 +103,6 @@ public class SimpleJobExecutorFacadeTests extends TestCase { return job; } - private volatile boolean running = false; - - private JobExecution jobExecution; - - private SimpleJobIdentifier jobIdentifier; - public void testIsRunning() throws Exception { jobExecutorFacade.setJobExecutor(new JobExecutor() { public ExitStatus run(JobConfiguration configuration, JobExecution jobExecutionContext) @@ -231,4 +224,42 @@ public class SimpleJobExecutorFacadeTests extends TestCase { assertTrue(statistics.containsKey("job1.step1")); control.verify(); } + + public void testListenersCalledLastOnAfter() throws Exception { + List listeners = new ArrayList(); + listeners.add(new JobExecutionListenerSupport() { + public void after(JobExecution execution) { + list.add("two"); + } + }); + listeners.add(new JobExecutionListenerSupport() { + public void after(JobExecution execution) { + list.add("one"); + } + }); + jobExecutorFacade.setJobExecutionListeners(listeners); + jobExecutorFacade.after(jobExecution); + assertEquals(2, list.size()); + assertEquals("two", list.get(1)); + } + + public void testOrderedListenersCalledFirstOnBefore() throws Exception { + List listeners = new ArrayList(); + listeners.add(new JobExecutionListenerSupport() { + public void before(JobExecution execution) { + list.add("one"); + } + }); + listeners.add(new JobExecutionListenerSupport() { + public void before(JobExecution execution) { + list.add("two"); + } + }); + jobExecutorFacade.setJobExecutionListeners(listeners); + jobExecutorFacade.before(jobExecution); + assertEquals(2, list.size()); + assertEquals("two", list.get(1)); + } + } +