diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/domain/JobListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/domain/JobListener.java new file mode 100644 index 000000000..2fc58c5e5 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/domain/JobListener.java @@ -0,0 +1,36 @@ +/* + * Copyright 2006-2008 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.core.domain; + + +/** + * @author Dave Syer + * + */ +public interface JobListener { + + /** + * Initialise the state of the listener with the {@link JobExecution} from + * the current scope. + * @param jobExecution + */ + void beforeJob(JobExecution jobExecution); + + /** + * + */ + void afterJob(); +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/domain/StepListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/domain/StepListener.java index 9c29a40d6..d7ae6767e 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/domain/StepListener.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/domain/StepListener.java @@ -29,7 +29,7 @@ public interface StepListener { * the current scope. * @param stepExecution */ - void open(StepExecution stepExecution); + void beforeStep(StepExecution stepExecution); /** * The value returned will be combined with the normal exit status using @@ -38,7 +38,7 @@ public interface StepListener { * @param e an exception thrown by the step execution * @return an exit status to be combined with the normal one, or null */ - ExitStatus onError(Throwable e); + ExitStatus onErrorInStep(Throwable e); /** * Give a listener a chance to modify the exit status from a step. The value @@ -48,5 +48,5 @@ public interface StepListener { * @return an {@link ExitStatus} to combine with the normal value. Return * null to leave the old value unchanged. */ - ExitStatus close(); + ExitStatus afterStep(); } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/interceptor/CompositeJobListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/interceptor/CompositeJobListener.java new file mode 100644 index 000000000..8da625c23 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/interceptor/CompositeJobListener.java @@ -0,0 +1,86 @@ +/* + * 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.core.interceptor; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +import org.springframework.batch.core.domain.JobExecution; +import org.springframework.batch.core.domain.JobListener; +import org.springframework.batch.core.domain.StepListener; + +/** + * @author Dave Syer + * + */ +public class CompositeJobListener implements JobListener { + + private List listeners = new ArrayList(); + + /** + * Public setter for the listeners. + * + * @param listeners + */ + public void setListeners(JobListener[] listeners) { + this.listeners = Arrays.asList(listeners); + } + + /** + * Public setter for the listeners. The result will be as if + * {@link #setListeners(StepListener[])} was called with an array of length + * one. + * + * @param listener + */ + public void setListener(JobListener listener) { + setListeners(new JobListener[] {listener}); + } + + /** + * Register additional listener. + * + * @param stepListener + */ + public void register(JobListener stepListener) { + if (!listeners.contains(stepListener)) { + listeners.add(stepListener); + } + } + + /* (non-Javadoc) + * @see org.springframework.batch.core.domain.StepListener#close() + */ + public void afterJob() { + for (Iterator iterator = listeners.iterator(); iterator.hasNext();) { + JobListener listener = (JobListener) iterator.next(); + listener.afterJob(); + } + } + + /* (non-Javadoc) + * @see org.springframework.batch.core.domain.StepListener#open(org.springframework.batch.core.domain.JobParameters) + */ + public void beforeJob(JobExecution jobExecution) { + for (Iterator iterator = listeners.iterator(); iterator.hasNext();) { + JobListener listener = (JobListener) iterator.next(); + listener.beforeJob(jobExecution); + } + } + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/interceptor/CompositeStepListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/interceptor/CompositeStepListener.java index 10d2923f7..3f5822921 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/interceptor/CompositeStepListener.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/interceptor/CompositeStepListener.java @@ -66,11 +66,11 @@ public class CompositeStepListener implements StepListener { /* (non-Javadoc) * @see org.springframework.batch.core.domain.StepListener#close() */ - public ExitStatus close() { + public ExitStatus afterStep() { ExitStatus status = null; for (Iterator iterator = listeners.iterator(); iterator.hasNext();) { StepListener listener = (StepListener) iterator.next(); - ExitStatus close = listener.close(); + ExitStatus close = listener.afterStep(); status = status!=null ? status.and(close): close; } return status; @@ -79,21 +79,21 @@ public class CompositeStepListener implements StepListener { /* (non-Javadoc) * @see org.springframework.batch.core.domain.StepListener#open(org.springframework.batch.core.domain.JobParameters) */ - public void open(StepExecution stepExecution) { + public void beforeStep(StepExecution stepExecution) { for (Iterator iterator = listeners.iterator(); iterator.hasNext();) { StepListener listener = (StepListener) iterator.next(); - listener.open(stepExecution); + listener.beforeStep(stepExecution); } } /* (non-Javadoc) * @see org.springframework.batch.core.domain.StepListener#onError(java.lang.Throwable) */ - public ExitStatus onError(Throwable e) { + public ExitStatus onErrorInStep(Throwable e) { ExitStatus status = null; for (Iterator iterator = listeners.iterator(); iterator.hasNext();) { StepListener listener = (StepListener) iterator.next(); - ExitStatus close = listener.onError(e); + ExitStatus close = listener.onErrorInStep(e); status = status!=null ? status.and(close): close; } return status; diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/interceptor/JobListenerSupport.java b/spring-batch-core/src/main/java/org/springframework/batch/core/interceptor/JobListenerSupport.java new file mode 100644 index 000000000..60d90a9c5 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/interceptor/JobListenerSupport.java @@ -0,0 +1,39 @@ +/* + * 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.core.interceptor; + +import org.springframework.batch.core.domain.JobExecution; +import org.springframework.batch.core.domain.JobListener; + +/** + * @author Dave Syer + * + */ +public class JobListenerSupport implements JobListener { + + /* (non-Javadoc) + * @see org.springframework.batch.core.domain.JobListener#afterJob() + */ + public void afterJob() { + } + + /* (non-Javadoc) + * @see org.springframework.batch.core.domain.JobListener#beforeJob(org.springframework.batch.core.domain.JobExecution) + */ + public void beforeJob(JobExecution jobExecution) { + } + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/interceptor/StepListenerSupport.java b/spring-batch-core/src/main/java/org/springframework/batch/core/interceptor/StepListenerSupport.java index bea731b51..6e4c4e5ea 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/interceptor/StepListenerSupport.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/interceptor/StepListenerSupport.java @@ -28,20 +28,20 @@ public class StepListenerSupport implements StepListener { /* (non-Javadoc) * @see org.springframework.batch.core.domain.StepListener#close() */ - public ExitStatus close() { + public ExitStatus afterStep() { return null; } /* (non-Javadoc) * @see org.springframework.batch.core.domain.StepListener#open(org.springframework.batch.item.ExecutionContext) */ - public void open(StepExecution stepExecution) { + public void beforeStep(StepExecution stepExecution) { } /* (non-Javadoc) * @see org.springframework.batch.core.domain.StepListener#onError(java.lang.Throwable) */ - public ExitStatus onError(Throwable e) { + public ExitStatus onErrorInStep(Throwable e) { return null; } } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/interceptor/CompositeJobListenerTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/interceptor/CompositeJobListenerTests.java new file mode 100644 index 000000000..1b906ef5c --- /dev/null +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/interceptor/CompositeJobListenerTests.java @@ -0,0 +1,84 @@ +/* + * 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.core.interceptor; + +import java.util.ArrayList; +import java.util.List; + +import junit.framework.TestCase; + +import org.springframework.batch.core.domain.JobExecution; +import org.springframework.batch.core.domain.JobInstance; +import org.springframework.batch.core.domain.JobListener; +import org.springframework.batch.core.domain.JobSupport; + +/** + * @author Dave Syer + * + */ +public class CompositeJobListenerTests extends TestCase { + + private CompositeJobListener listener = new CompositeJobListener(); + + private List list = new ArrayList(); + + /** + * Test method for + * {@link org.springframework.batch.core.interceptor.CompositeJobListener#setListeners(org.springframework.batch.core.domain.JobListener[])}. + */ + public void testSetListeners() { + listener.setListeners(new JobListener[] { new JobListenerSupport() { + public void afterJob() { + list.add("fail"); + } + }, new JobListenerSupport() { + public void afterJob() { + list.add("continue"); + } + } }); + listener.afterJob(); + assertEquals(2, list.size()); + } + + /** + * Test method for + * {@link org.springframework.batch.core.interceptor.CompositeJobListener#setListener(org.springframework.batch.core.domain.JobListener)}. + */ + public void testSetListener() { + listener.setListener(new JobListenerSupport() { + public void afterJob() { + list.add("fail"); + } + }); + listener.afterJob(); + assertEquals(1, list.size()); + } + + /** + * Test method for + * {@link org.springframework.batch.core.interceptor.CompositeJobListener#beforeJob(JobExecution)}. + */ + public void testOpen() { + listener.setListener(new JobListenerSupport() { + public void beforeJob(JobExecution stepExecution) { + list.add("foo"); + } + }); + listener.beforeJob(new JobExecution(new JobInstance(new Long(11L), null, new JobSupport()))); + assertEquals(1, list.size()); + } + +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/interceptor/CompositeStepListenerTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/interceptor/CompositeStepListenerTests.java index 612c44a39..f78324d6b 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/interceptor/CompositeStepListenerTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/interceptor/CompositeStepListenerTests.java @@ -41,17 +41,17 @@ public class CompositeStepListenerTests extends TestCase { */ public void testSetListeners() { listener.setListeners(new StepListener[] { new StepListenerSupport() { - public ExitStatus close() { + public ExitStatus afterStep() { list.add("fail"); return ExitStatus.FAILED; } }, new StepListenerSupport() { - public ExitStatus close() { + public ExitStatus afterStep() { list.add("continue"); return ExitStatus.CONTINUABLE; } } }); - assertFalse(listener.close().isContinuable()); + assertFalse(listener.afterStep().isContinuable()); assertEquals(2, list.size()); } @@ -61,41 +61,41 @@ public class CompositeStepListenerTests extends TestCase { */ public void testSetListener() { listener.setListener(new StepListenerSupport() { - public ExitStatus close() { + public ExitStatus afterStep() { list.add("fail"); return ExitStatus.FAILED; } }); - assertFalse(listener.close().isContinuable()); + assertFalse(listener.afterStep().isContinuable()); assertEquals(1, list.size()); } /** * Test method for - * {@link org.springframework.batch.core.interceptor.CompositeStepListener#open(StepExecution)}. + * {@link org.springframework.batch.core.interceptor.CompositeStepListener#beforeStep(StepExecution)}. */ public void testOpen() { listener.setListener(new StepListenerSupport() { - public void open(StepExecution stepExecution) { + public void beforeStep(StepExecution stepExecution) { list.add("foo"); } }); - listener.open(new StepExecution(new StepSupport("foo"), null)); + listener.beforeStep(new StepExecution(new StepSupport("foo"), null)); assertEquals(1, list.size()); } /** * Test method for - * {@link org.springframework.batch.core.interceptor.CompositeStepListener#open(StepExecution)}. + * {@link org.springframework.batch.core.interceptor.CompositeStepListener#beforeStep(StepExecution)}. */ public void testOnError() { listener.setListener(new StepListenerSupport() { - public ExitStatus onError(Throwable e) { + public ExitStatus onErrorInStep(Throwable e) { list.add("foo"); return null; } }); - listener.onError(new RuntimeException()); + listener.onErrorInStep(new RuntimeException()); assertEquals(1, list.size()); } diff --git a/spring-batch-execution/src/main/java/org/springframework/batch/execution/job/SimpleJob.java b/spring-batch-execution/src/main/java/org/springframework/batch/execution/job/SimpleJob.java index c50b54e91..5e17539ca 100644 --- a/spring-batch-execution/src/main/java/org/springframework/batch/execution/job/SimpleJob.java +++ b/spring-batch-execution/src/main/java/org/springframework/batch/execution/job/SimpleJob.java @@ -25,8 +25,10 @@ import org.springframework.batch.core.domain.BatchStatus; import org.springframework.batch.core.domain.JobExecution; import org.springframework.batch.core.domain.JobInstance; import org.springframework.batch.core.domain.JobInterruptedException; +import org.springframework.batch.core.domain.JobListener; import org.springframework.batch.core.domain.Step; import org.springframework.batch.core.domain.StepExecution; +import org.springframework.batch.core.interceptor.CompositeJobListener; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.runtime.ExitStatusExceptionClassifier; import org.springframework.batch.execution.scope.SimpleStepContext; @@ -50,6 +52,18 @@ public class SimpleJob extends AbstractJob { private ExitStatusExceptionClassifier exceptionClassifier = new SimpleExitStatusExceptionClassifier(); + private CompositeJobListener listener = new CompositeJobListener(); + + public void setListeners(JobListener[] listeners) { + for (int i = 0; i < listeners.length; i++) { + this.listener.register(listeners[i]); + } + } + + public void setListener(JobListener listener) { + this.listener.register(listener); + } + /** * Run the specified job by looping through the steps and delegating to the * {@link Step}. @@ -73,6 +87,8 @@ public class SimpleJob extends AbstractJob { execution.setStartTime(new Date()); updateStatus(execution, BatchStatus.STARTING); + + listener.beforeJob(execution); int startedCount = 0; @@ -114,6 +130,8 @@ public class SimpleJob extends AbstractJob { updateStatus(execution, BatchStatus.COMPLETED); + listener.afterJob(); + } catch (JobInterruptedException e) { updateStatus(execution, BatchStatus.STOPPED); diff --git a/spring-batch-execution/src/main/java/org/springframework/batch/execution/step/TaskletStep.java b/spring-batch-execution/src/main/java/org/springframework/batch/execution/step/TaskletStep.java index 02610125f..b7fbf1952 100644 --- a/spring-batch-execution/src/main/java/org/springframework/batch/execution/step/TaskletStep.java +++ b/spring-batch-execution/src/main/java/org/springframework/batch/execution/step/TaskletStep.java @@ -182,10 +182,10 @@ public class TaskletStep implements Step, InitializingBean, BeanNameAware { Exception fatalException = null; try { - listener.open(stepExecution); + listener.beforeStep(stepExecution); exitStatus = tasklet.execute(); try { - exitStatus = exitStatus.and(listener.close()); + exitStatus = exitStatus.and(listener.afterStep()); } catch (Exception e) { logger.error("Encountered an error on listener close.", e); @@ -205,7 +205,7 @@ public class TaskletStep implements Step, InitializingBean, BeanNameAware { logger.error("Encountered an error running the tasklet"); updateStatus(stepExecution, BatchStatus.FAILED); try { - exitStatus = exitStatus.and(listener.onError(e)); + exitStatus = exitStatus.and(listener.onErrorInStep(e)); } catch (Exception ex) { logger.error("Encountered an error on listener close.", ex); diff --git a/spring-batch-execution/src/test/java/org/springframework/batch/execution/job/SimpleJobTests.java b/spring-batch-execution/src/test/java/org/springframework/batch/execution/job/SimpleJobTests.java index 5f62f96fe..9f82b3b49 100644 --- a/spring-batch-execution/src/test/java/org/springframework/batch/execution/job/SimpleJobTests.java +++ b/spring-batch-execution/src/test/java/org/springframework/batch/execution/job/SimpleJobTests.java @@ -28,6 +28,7 @@ import org.springframework.batch.core.domain.JobInterruptedException; import org.springframework.batch.core.domain.JobParameters; import org.springframework.batch.core.domain.Step; import org.springframework.batch.core.domain.StepExecution; +import org.springframework.batch.core.interceptor.JobListenerSupport; import org.springframework.batch.core.repository.JobRepository; import org.springframework.batch.core.runtime.ExitStatusExceptionClassifier; import org.springframework.batch.execution.repository.SimpleJobRepository; @@ -140,6 +141,19 @@ public class SimpleJobTests extends TestCase { assertNotNull(jobExecution.getStartTime()); } + public void testRunNormallyWithListener() throws Exception { + job.setListener(new JobListenerSupport() { + public void beforeJob(JobExecution jobExecution) { + list.add("before"); + } + public void afterJob() { + list.add("after"); + } + }); + job.execute(jobExecution); + assertEquals(4, list.size()); + } + public void testRunWithSimpleStepExecutor() throws Exception { job.setJobRepository(jobRepository); diff --git a/spring-batch-execution/src/test/java/org/springframework/batch/execution/step/TaskletStepTests.java b/spring-batch-execution/src/test/java/org/springframework/batch/execution/step/TaskletStepTests.java index 3111aa300..88b98d39c 100644 --- a/spring-batch-execution/src/test/java/org/springframework/batch/execution/step/TaskletStepTests.java +++ b/spring-batch-execution/src/test/java/org/springframework/batch/execution/step/TaskletStepTests.java @@ -105,10 +105,10 @@ public class TaskletStepTests extends TestCase { public void testSuccessfulExecutionWithListener() throws Exception { TaskletStep step = new TaskletStep(new StubTasklet(false, false), new JobRepositorySupport()); step.setListener(new StepListenerSupport() { - public void open(StepExecution context) { + public void beforeStep(StepExecution context) { list.add("open"); } - public ExitStatus close() { + public ExitStatus afterStep() { list.add("close"); return ExitStatus.CONTINUABLE; }