diff --git a/spring-batch-execution/src/main/java/org/springframework/batch/execution/step/support/AbstractStepFactoryBean.java b/spring-batch-execution/src/main/java/org/springframework/batch/execution/step/support/AbstractStepFactoryBean.java
index 21e641b45..4666450b4 100644
--- a/spring-batch-execution/src/main/java/org/springframework/batch/execution/step/support/AbstractStepFactoryBean.java
+++ b/spring-batch-execution/src/main/java/org/springframework/batch/execution/step/support/AbstractStepFactoryBean.java
@@ -106,6 +106,22 @@ public abstract class AbstractStepFactoryBean extends AbstractFactoryBean implem
this.itemWriter = itemWriter;
}
+ /**
+ * Protected getter for the {@link ItemReader} for subclasses to use.
+ * @return the itemReader
+ */
+ protected ItemReader getItemReader() {
+ return itemReader;
+ }
+
+ /**
+ * Protected getter for the {@link ItemWriter} for subclasses to use
+ * @return the itemWriter
+ */
+ protected ItemWriter getItemWriter() {
+ return itemWriter;
+ }
+
/**
* Public setter for {@link JobRepository}.
*
@@ -154,20 +170,4 @@ public abstract class AbstractStepFactoryBean extends AbstractFactoryBean implem
return Step.class;
}
- /**
- * Public getter for the ItemReader.
- * @return the itemReader
- */
- public ItemReader getItemReader() {
- return itemReader;
- }
-
- /**
- * Public getter for the ItemWriter.
- * @return the itemWriter
- */
- public ItemWriter getItemWriter() {
- return itemWriter;
- }
-
}
\ No newline at end of file
diff --git a/spring-batch-execution/src/main/java/org/springframework/batch/execution/step/support/BatchListenerFactoryHelper.java b/spring-batch-execution/src/main/java/org/springframework/batch/execution/step/support/BatchListenerFactoryHelper.java
new file mode 100644
index 000000000..6f32e93fb
--- /dev/null
+++ b/spring-batch-execution/src/main/java/org/springframework/batch/execution/step/support/BatchListenerFactoryHelper.java
@@ -0,0 +1,173 @@
+/*
+ * 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.step.support;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.springframework.batch.core.domain.BatchListener;
+import org.springframework.batch.core.domain.ChunkListener;
+import org.springframework.batch.core.domain.ItemReadListener;
+import org.springframework.batch.core.domain.ItemWriteListener;
+import org.springframework.batch.core.domain.StepListener;
+import org.springframework.batch.execution.listener.CompositeChunkListener;
+import org.springframework.batch.execution.listener.CompositeItemReadListener;
+import org.springframework.batch.execution.listener.CompositeItemWriteListener;
+import org.springframework.batch.item.ItemReader;
+import org.springframework.batch.item.ItemWriter;
+import org.springframework.batch.item.reader.DelegatingItemReader;
+import org.springframework.batch.item.writer.DelegatingItemWriter;
+import org.springframework.batch.repeat.RepeatContext;
+import org.springframework.batch.repeat.RepeatOperations;
+import org.springframework.batch.repeat.listener.RepeatListenerSupport;
+import org.springframework.batch.repeat.support.RepeatTemplate;
+import org.springframework.util.Assert;
+
+/**
+ * Package private helper for step factory beans.
+ *
+ * @author Dave Syer
+ *
+ */
+class BatchListenerFactoryHelper {
+
+ /**
+ * @param itemReader2
+ * @param listeners
+ * @return
+ */
+ public ItemReader getItemReader(ItemReader itemReader, BatchListener[] listeners) {
+
+ final CompositeItemReadListener multicaster = new CompositeItemReadListener();
+
+ for (int i = 0; i < listeners.length; i++) {
+ BatchListener listener = listeners[i];
+ if (listener instanceof ItemReadListener) {
+ multicaster.register((ItemReadListener) listener);
+ }
+ }
+
+ itemReader = new DelegatingItemReader(itemReader) {
+ public Object read() throws Exception {
+ try {
+ multicaster.beforeRead();
+ Object item = super.read();
+ multicaster.afterRead(item);
+ return item;
+ }
+ catch (Exception e) {
+ multicaster.onReadError(e);
+ throw e;
+ }
+ }
+ };
+
+ return itemReader;
+ }
+
+ /**
+ * @param itemWriter2
+ * @param listeners
+ * @return
+ */
+ public ItemWriter getItemWriter(ItemWriter itemWriter, BatchListener[] listeners) {
+ final CompositeItemWriteListener multicaster = new CompositeItemWriteListener();
+
+ for (int i = 0; i < listeners.length; i++) {
+ BatchListener listener = listeners[i];
+ if (listener instanceof ItemWriteListener) {
+ multicaster.register((ItemWriteListener) listener);
+ }
+ }
+
+ itemWriter = new DelegatingItemWriter(itemWriter) {
+ public void write(Object item) throws Exception {
+ try {
+ multicaster.beforeWrite(item);
+ super.write(item);
+ multicaster.afterWrite();
+ }
+ catch (Exception e) {
+ multicaster.onWriteError(e, item);
+ throw e;
+ }
+ }
+ };
+
+ return itemWriter;
+
+ }
+
+ /**
+ * @param stepOperations
+ * @param listeners
+ * @return
+ */
+ public RepeatOperations getStepOperations(RepeatOperations stepOperations, BatchListener[] listeners) {
+
+ final CompositeChunkListener multicaster = new CompositeChunkListener();
+
+ boolean hasChunkListener = false;
+
+ for (int i = 0; i < listeners.length; i++) {
+ BatchListener listener = listeners[i];
+ if (listener instanceof ChunkListener) {
+ hasChunkListener = true;
+ }
+ if (listener instanceof ChunkListener) {
+ multicaster.register((ChunkListener) listener);
+ }
+ }
+
+ if (hasChunkListener) {
+
+ Assert.state(stepOperations instanceof RepeatTemplate,
+ "Step operations is injected but not a RepeatTemplate, so chunk listeners cannot also be registered. "
+ + "Either inject a RepeatTemplate, or remove the ChunkListener.");
+
+ RepeatTemplate stepTemplate = (RepeatTemplate) stepOperations;
+ stepTemplate.registerListener(new RepeatListenerSupport() {
+ public void open(RepeatContext context) {
+ multicaster.beforeChunk();
+ }
+
+ public void close(RepeatContext context) {
+ multicaster.afterChunk();
+ }
+ });
+
+ }
+
+ return stepOperations;
+
+ }
+
+ /**
+ * @param listeners
+ * @return
+ */
+ public StepListener[] getStepListeners(BatchListener[] listeners) {
+ List list = new ArrayList();
+ for (int i = 0; i < listeners.length; i++) {
+ BatchListener listener = listeners[i];
+ if (listener instanceof StepListener) {
+ list.add(listener);
+ }
+ }
+ return (StepListener[]) list.toArray(new StepListener[list.size()]);
+ }
+
+}
diff --git a/spring-batch-execution/src/main/java/org/springframework/batch/execution/step/support/DefaultStepFactoryBean.java b/spring-batch-execution/src/main/java/org/springframework/batch/execution/step/support/DefaultStepFactoryBean.java
index 79994ec8f..160a9bb44 100644
--- a/spring-batch-execution/src/main/java/org/springframework/batch/execution/step/support/DefaultStepFactoryBean.java
+++ b/spring-batch-execution/src/main/java/org/springframework/batch/execution/step/support/DefaultStepFactoryBean.java
@@ -22,11 +22,7 @@ import org.springframework.batch.execution.step.ItemOrientedStep;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemStream;
import org.springframework.batch.item.ItemWriter;
-import org.springframework.batch.item.reader.DelegatingItemReader;
-import org.springframework.batch.item.writer.DelegatingItemWriter;
-import org.springframework.batch.repeat.RepeatContext;
import org.springframework.batch.repeat.exception.handler.SimpleLimitExceptionHandler;
-import org.springframework.batch.repeat.listener.RepeatListenerSupport;
import org.springframework.batch.repeat.support.RepeatTemplate;
import org.springframework.batch.repeat.support.TaskExecutorRepeatTemplate;
import org.springframework.core.task.TaskExecutor;
@@ -48,7 +44,7 @@ public class DefaultStepFactoryBean extends SimpleStepFactoryBean {
private TaskExecutor taskExecutor;
/**
- * Public setter for the flag that determines skip policy. If this flag is
+ * Public setter for a flag that determines skip policy. If this flag is
* true then an exception in chunk processing will cause the item to be
* skipped and no exceptions propagated. If it is false then all exceptions
* will be propagated from the chunk and cause the step to abort.
@@ -116,51 +112,21 @@ public class DefaultStepFactoryBean extends SimpleStepFactoryBean {
step.registerStepListener((StepListener) itemWriter);
}
- itemReader = new DelegatingItemReader(itemReader) {
- public Object read() throws Exception {
- try {
- listener.beforeRead();
- Object item = super.read();
- listener.afterRead(item);
- return item;
- }
- catch (Exception e) {
- listener.onReadError(e);
- throw e;
- }
- }
- };
- // In case it is used by subclasses:
- setItemReader(itemReader);
- step.setItemReader(itemReader);
-
- itemWriter = new DelegatingItemWriter(itemWriter) {
- public void write(Object item) throws Exception {
- try {
- listener.beforeWrite(item);
- super.write(item);
- listener.afterWrite();
- }
- catch (Exception e) {
- listener.onWriteError(e, item);
- throw e;
- }
- }
- };
- // In case it is used by subclasses:
- setItemWriter(itemWriter);
- step.setItemWriter(itemWriter);
+ BatchListenerFactoryHelper helper = new BatchListenerFactoryHelper();
+ StepListener[] stepListeners = helper.getStepListeners(listeners);
+ itemReader = helper.getItemReader(itemReader, listeners);
+ itemWriter = helper.getItemWriter(itemWriter, listeners);
RepeatTemplate stepOperations = new RepeatTemplate();
- stepOperations.setListener(new RepeatListenerSupport() {
- public void open(RepeatContext context) {
- listener.beforeChunk();
- }
+ stepOperations = (RepeatTemplate) helper.getStepOperations(stepOperations, listeners);
- public void close(RepeatContext context) {
- listener.afterChunk();
- }
- });
+ // In case they are used by subclasses:
+ setItemReader(itemReader);
+ setItemWriter(itemWriter);
+
+ step.setStepListeners(stepListeners);
+ step.setItemReader(itemReader);
+ step.setItemWriter(itemWriter);
if (taskExecutor != null) {
TaskExecutorRepeatTemplate repeatTemplate = new TaskExecutorRepeatTemplate();
diff --git a/spring-batch-execution/src/main/java/org/springframework/batch/execution/step/support/RepeatOperationsStepFactoryBean.java b/spring-batch-execution/src/main/java/org/springframework/batch/execution/step/support/RepeatOperationsStepFactoryBean.java
new file mode 100644
index 000000000..3b47aa2a9
--- /dev/null
+++ b/spring-batch-execution/src/main/java/org/springframework/batch/execution/step/support/RepeatOperationsStepFactoryBean.java
@@ -0,0 +1,141 @@
+/*
+ * 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.step.support;
+
+import org.springframework.batch.core.domain.BatchListener;
+import org.springframework.batch.core.domain.Step;
+import org.springframework.batch.core.domain.StepListener;
+import org.springframework.batch.execution.step.ItemOrientedStep;
+import org.springframework.batch.item.ItemReader;
+import org.springframework.batch.item.ItemStream;
+import org.springframework.batch.item.ItemWriter;
+import org.springframework.batch.repeat.RepeatOperations;
+import org.springframework.batch.repeat.support.RepeatTemplate;
+
+/**
+ * Extends a {@link SimpleStepFactoryBean} allowing registration of listeners
+ * and also direct injection of the {@link RepeatOperations} needed at step and
+ * chunk level.
+ *
+ * @author Dave Syer
+ *
+ */
+public class RepeatOperationsStepFactoryBean extends AbstractStepFactoryBean {
+
+ private ItemStream[] streams = new ItemStream[0];
+
+ private BatchListener[] listeners = new BatchListener[0];
+
+ private RepeatOperations chunkOperations = new RepeatTemplate();
+
+ private RepeatOperations stepOperations = new RepeatTemplate();
+
+ /**
+ * The streams to inject into the {@link Step}. Any instance of
+ * {@link ItemStream} can be used, and will then receive callbacks at the
+ * appropriate stage in the step.
+ *
+ * @param streams an array of listeners
+ */
+ public void setStreams(ItemStream[] streams) {
+ this.streams = streams;
+ }
+
+ /**
+ * The listeners to inject into the {@link Step}. Any instance of
+ * {@link BatchListener} can be used, and will then receive callbacks at the
+ * appropriate stage in the step.
+ *
+ * @param listeners an array of listeners
+ */
+ public void setListeners(BatchListener[] listeners) {
+ this.listeners = listeners;
+ }
+
+ /**
+ * The {@link RepeatOperations} to use for the outer loop of the batch
+ * processing. Should be set up by the caller through a factory. Defaults to
+ * a plain {@link RepeatTemplate}.
+ *
+ * @param stepOperations a {@link RepeatOperations} instance.
+ */
+ public void setStepOperations(RepeatOperations stepOperations) {
+ this.stepOperations = stepOperations;
+ }
+
+ /**
+ * The {@link RepeatOperations} to use for the inner loop of the batch
+ * processing. should be set up by the caller through a factory. defaults to
+ * a plain {@link RepeatTemplate}.
+ *
+ * @param chunkOperations a {@link RepeatOperations} instance.
+ */
+ public void setChunkOperations(RepeatOperations chunkOperations) {
+ this.chunkOperations = chunkOperations;
+ }
+
+ /**
+ * @param step
+ *
+ */
+ protected void applyConfiguration(ItemOrientedStep step) {
+
+ super.applyConfiguration(step);
+
+ step.setStreams(streams);
+
+ ItemReader itemReader = getItemReader();
+ ItemWriter itemWriter = getItemWriter();
+
+ /*
+ * Since we are going to wrap these things with listener callbacks we
+ * need to register them here because the step will not know we did
+ * that.
+ */
+ if (itemReader instanceof ItemStream) {
+ step.registerStream((ItemStream) itemReader);
+ }
+ if (itemReader instanceof StepListener) {
+ step.registerStepListener((StepListener) itemReader);
+ }
+ if (itemWriter instanceof ItemStream) {
+ step.registerStream((ItemStream) itemWriter);
+ }
+ if (itemWriter instanceof StepListener) {
+ step.registerStepListener((StepListener) itemWriter);
+ }
+
+ BatchListenerFactoryHelper helper = new BatchListenerFactoryHelper();
+
+ StepListener[] stepListeners = helper.getStepListeners(listeners);
+ itemReader = helper.getItemReader(itemReader, listeners);
+ itemWriter = helper.getItemWriter(itemWriter, listeners);
+ RepeatOperations stepOperations = helper.getStepOperations(this.stepOperations, listeners);
+
+ // In case they are used by subclasses:
+ setItemReader(itemReader);
+ setItemWriter(itemWriter);
+
+ step.setStepListeners(stepListeners);
+ step.setItemReader(itemReader);
+ step.setItemWriter(itemWriter);
+
+ step.setChunkOperations(chunkOperations);
+ step.setStepOperations(stepOperations);
+
+ }
+
+}
diff --git a/spring-batch-execution/src/main/java/org/springframework/batch/execution/step/support/StatefulRetryStepFactoryBean.java b/spring-batch-execution/src/main/java/org/springframework/batch/execution/step/support/StatefulRetryStepFactoryBean.java
index aa4b50788..b7d03beee 100644
--- a/spring-batch-execution/src/main/java/org/springframework/batch/execution/step/support/StatefulRetryStepFactoryBean.java
+++ b/spring-batch-execution/src/main/java/org/springframework/batch/execution/step/support/StatefulRetryStepFactoryBean.java
@@ -34,9 +34,6 @@ import org.springframework.batch.retry.support.RetryTemplate;
* limit given by the {@link RetryPolicy}. When the retry is exhausted instead
* of the item being skipped it is handled by an {@link ItemRecoverer}.
*
- * TODO: make sure listeners are called, and add item listener callbacks to the
- * recovery path.
- *
* TODO: checking for null retry callback is a sucky way of determining if a
* stateful retry has been requested.
*
diff --git a/spring-batch-execution/src/test/java/org/springframework/batch/execution/listener/CompositeJobListenerTests.java b/spring-batch-execution/src/test/java/org/springframework/batch/execution/listener/CompositeJobListenerTests.java
index cf027385d..046727f9e 100644
--- a/spring-batch-execution/src/test/java/org/springframework/batch/execution/listener/CompositeJobListenerTests.java
+++ b/spring-batch-execution/src/test/java/org/springframework/batch/execution/listener/CompositeJobListenerTests.java
@@ -57,7 +57,7 @@ public class CompositeJobListenerTests extends TestCase {
/**
* Test method for
- * {@link org.springframework.batch.execution.listener.CompositeJobListener#setListener(org.springframework.batch.core.domain.JobListener)}.
+ * {@link org.springframework.batch.execution.listener.CompositeJobListener#registerListener(org.springframework.batch.core.domain.JobListener)}.
*/
public void testSetListener() {
listener.register(new JobListenerSupport() {
diff --git a/spring-batch-execution/src/test/java/org/springframework/batch/execution/listener/CompositeStepListenerTests.java b/spring-batch-execution/src/test/java/org/springframework/batch/execution/listener/CompositeStepListenerTests.java
index 58b73a750..06158f769 100644
--- a/spring-batch-execution/src/test/java/org/springframework/batch/execution/listener/CompositeStepListenerTests.java
+++ b/spring-batch-execution/src/test/java/org/springframework/batch/execution/listener/CompositeStepListenerTests.java
@@ -58,7 +58,7 @@ public class CompositeStepListenerTests extends TestCase {
/**
* Test method for
- * {@link org.springframework.batch.execution.listener.CompositeStepListener#setListener(org.springframework.batch.core.domain.StepListener)}.
+ * {@link org.springframework.batch.execution.listener.CompositeStepListener#registerListener(org.springframework.batch.core.domain.StepListener)}.
*/
public void testSetListener() {
listener.register(new StepListenerSupport() {
diff --git a/spring-batch-execution/src/test/java/org/springframework/batch/execution/step/support/RepeatOperationsStepFactoryBeanTests.java b/spring-batch-execution/src/test/java/org/springframework/batch/execution/step/support/RepeatOperationsStepFactoryBeanTests.java
new file mode 100644
index 000000000..9b60306bf
--- /dev/null
+++ b/spring-batch-execution/src/test/java/org/springframework/batch/execution/step/support/RepeatOperationsStepFactoryBeanTests.java
@@ -0,0 +1,81 @@
+/*
+ * 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.step.support;
+
+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.JobParameters;
+import org.springframework.batch.core.domain.Step;
+import org.springframework.batch.core.domain.StepExecution;
+import org.springframework.batch.execution.job.JobSupport;
+import org.springframework.batch.execution.launch.EmptyItemWriter;
+import org.springframework.batch.item.reader.ListItemReader;
+import org.springframework.batch.repeat.ExitStatus;
+import org.springframework.batch.repeat.RepeatCallback;
+import org.springframework.batch.repeat.RepeatOperations;
+import org.springframework.batch.support.transaction.ResourcelessTransactionManager;
+
+/**
+ * @author Dave Syer
+ *
+ */
+public class RepeatOperationsStepFactoryBeanTests extends TestCase {
+
+ private RepeatOperationsStepFactoryBean factory = new RepeatOperationsStepFactoryBean();
+
+ private List list;
+
+ private JobExecution jobExecution = new JobExecution(new JobInstance(new Long(0L), new JobParameters(),
+ new JobSupport("job")));;
+
+ public void testType() throws Exception {
+ assertEquals(Step.class, factory.getObjectType());
+ }
+
+ public void testDefaultValue() throws Exception {
+ assertTrue(factory.getObject() instanceof Step);
+ }
+
+ public void testStepOperationsWithoutChunkListener() throws Exception {
+
+ factory.setItemReader(new ListItemReader(new ArrayList()));
+ factory.setItemWriter(new EmptyItemWriter());
+ factory.setJobRepository(new JobRepositorySupport());
+ factory.setTransactionManager(new ResourcelessTransactionManager());
+
+ factory.setStepOperations(new RepeatOperations() {
+
+ public ExitStatus iterate(RepeatCallback callback) {
+ list = new ArrayList();
+ list.add("foo");
+ return ExitStatus.FINISHED;
+ }
+ });
+
+ factory.setSingleton(false);
+
+ Step step = (Step) factory.getObject();
+ step.execute(new StepExecution(step, jobExecution));
+
+ assertEquals(1, list.size());
+ }
+
+}
diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/repeat/support/RepeatTemplate.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/repeat/support/RepeatTemplate.java
index 94ef90a07..57c2b6bd2 100644
--- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/repeat/support/RepeatTemplate.java
+++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/repeat/support/RepeatTemplate.java
@@ -16,7 +16,10 @@
package org.springframework.batch.repeat.support;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
+import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -47,15 +50,15 @@ import org.springframework.util.Assert;
* finish when exceptions are received. This is not the default behaviour.
*
* Clients that want to take some business action when an exception is thrown by
- * the {@link RepeatCallback} can consider using a custom
- * {@link RepeatListener} instead of trying to customise the
- * {@link CompletionPolicy}. This is generally a friendlier interface to
- * implement, and the {@link RepeatListener#after(RepeatContext, ExitStatus)}
- * method is passed in the result of the callback, which would be an instance of
- * {@link Throwable} if the business processing had thrown an exception. If the
- * exception is not to be propagated to the caller, then a non-default
- * {@link CompletionPolicy} needs to be provided as well, but that could be off
- * the shelf, with the business action implemented only in the interceptor.
+ * the {@link RepeatCallback} can consider using a custom {@link RepeatListener}
+ * instead of trying to customise the {@link CompletionPolicy}. This is
+ * generally a friendlier interface to implement, and the
+ * {@link RepeatListener#after(RepeatContext, ExitStatus)} method is passed in
+ * the result of the callback, which would be an instance of {@link Throwable}
+ * if the business processing had thrown an exception. If the exception is not
+ * to be propagated to the caller, then a non-default {@link CompletionPolicy}
+ * needs to be provided as well, but that could be off the shelf, with the
+ * business action implemented only in the interceptor.
*
* @author Dave Syer
*
@@ -70,12 +73,25 @@ public class RepeatTemplate implements RepeatOperations {
private ExceptionHandler exceptionHandler = new DefaultExceptionHandler();
+ /**
+ * Set the listeners for this template, registering them for callbacks at
+ * appropriate times in the iteration.
+ *
+ * @param listeners
+ */
public void setListeners(RepeatListener[] listeners) {
this.listeners = listeners;
}
- public void setListener(RepeatListener listener) {
- listeners = new RepeatListener[] { listener };
+ /**
+ * Register an additional listener.
+ *
+ * @param listener
+ */
+ public void registerListener(RepeatListener listener) {
+ List list = new ArrayList(Arrays.asList(listeners));
+ list.add(listener);
+ listeners = (RepeatListener[]) list.toArray(new RepeatListener[list.size()]);
}
/**
@@ -87,8 +103,7 @@ public class RepeatTemplate implements RepeatOperations {
* @see DefaultExceptionHandler
* @see #setCompletionPolicy(CompletionPolicy)
*
- * @param exceptionHandler
- * the {@link ExceptionHandler} to use.
+ * @param exceptionHandler the {@link ExceptionHandler} to use.
*/
public void setExceptionHandler(ExceptionHandler exceptionHandler) {
this.exceptionHandler = exceptionHandler;
@@ -103,10 +118,8 @@ public class RepeatTemplate implements RepeatOperations {
*
* @see #setExceptionHandler(ExceptionHandler)
*
- * @param terminationPolicy
- * a TerminationPolicy.
- * @throws IllegalArgumentException
- * if the argument is null
+ * @param terminationPolicy a TerminationPolicy.
+ * @throws IllegalArgumentException if the argument is null
*/
public void setCompletionPolicy(CompletionPolicy terminationPolicy) {
Assert.notNull(terminationPolicy);
@@ -129,7 +142,8 @@ public class RepeatTemplate implements RepeatOperations {
// This works with an asynchronous TaskExecutor: the
// interceptors have to wait for the child processes.
result = executeInternal(callback);
- } finally {
+ }
+ finally {
RepeatSynchronizationManager.clear();
if (outer != null) {
RepeatSynchronizationManager.register(outer);
@@ -143,11 +157,10 @@ public class RepeatTemplate implements RepeatOperations {
* Internal convenience method to loop over interceptors and batch
* callbacks.
*
- * @param callback
- * the callback to process each element of the loop.
+ * @param callback the callback to process each element of the loop.
*
* @return the aggregate of {@link ContinuationPolicy#canContinue(Object)}
- * for all the results from the callback.
+ * for all the results from the callback.
*
*/
private ExitStatus executeInternal(final RepeatCallback callback) {
@@ -193,15 +206,15 @@ public class RepeatTemplate implements RepeatOperations {
// Check that we are still running...
if (running) {
- logger.debug("Batch operation about to start at count="
- + context.getStartedCount());
+ logger.debug("Batch operation about to start at count=" + context.getStartedCount());
try {
result = getNextResult(context, callback, state);
executeAfterInterceptors(context, result);
- } catch (Throwable throwable) {
+ }
+ catch (Throwable throwable) {
// An exception alone is not sufficient grounds for not
// continuing
@@ -213,22 +226,19 @@ public class RepeatTemplate implements RepeatOperations {
interceptor.onError(context, throwable);
// This is not an error - only log at debug
// level.
- logger.debug("Exception intercepted ("
- + (i + 1) + " of "
- + listeners.length + ")", throwable);
+ logger.debug("Exception intercepted (" + (i + 1) + " of " + listeners.length + ")",
+ throwable);
}
- exceptionHandler
- .handleException(context, throwable);
+ exceptionHandler.handleException(context, throwable);
- } catch (Throwable handled) {
+ }
+ catch (Throwable handled) {
throwables.add(handled);
}
}
// N.B. the order may be important here:
- if (isComplete(context, result)
- || isMarkedComplete(context)
- || !throwables.isEmpty()) {
+ if (isComplete(context, result) || isMarkedComplete(context) || !throwables.isEmpty()) {
running = false;
}
}
@@ -254,14 +264,16 @@ public class RepeatTemplate implements RepeatOperations {
rethrow((Throwable) throwables.iterator().next());
}
- } finally {
+ }
+ finally {
try {
for (int i = listeners.length; i-- > 0;) {
RepeatListener interceptor = listeners[i];
interceptor.close(context);
}
- } finally {
+ }
+ finally {
// TODO: extend this to the completion policy?
context.close();
}
@@ -283,8 +295,7 @@ public class RepeatTemplate implements RepeatOperations {
throw (RuntimeException) next;
}
;
- throw new RepeatException(
- "Rethrowing exception that is no RuntimeException.", next);
+ throw new RepeatException("Rethrowing exception that is no RuntimeException.", next);
}
/**
@@ -294,8 +305,7 @@ public class RepeatTemplate implements RepeatOperations {
* an accumulation of Throwable instances for processing at the end of the
* batch.
*
- * @param context
- * the current {@link RepeatContext}
+ * @param context the current {@link RepeatContext}
* @return a {@link RepeatInternalState} instance.
*/
protected RepeatInternalState createInternalState(RepeatContext context) {
@@ -306,23 +316,20 @@ public class RepeatTemplate implements RepeatOperations {
* Get the next completed result, possibly executing several callbacks until
* one finally finishes.
*
- * @param context
- * current BatchContext.
- * @param callback
- * the callback to execute.
- * @param state
- * maintained by the implementation.
+ * @param context current BatchContext.
+ * @param callback the callback to execute.
+ * @param state maintained by the implementation.
* @return a finished result.
*
* @see {@link #isComplete(RepeatContext)}
*/
- protected ExitStatus getNextResult(RepeatContext context,
- RepeatCallback callback, RepeatInternalState state)
+ protected ExitStatus getNextResult(RepeatContext context, RepeatCallback callback, RepeatInternalState state)
throws Throwable {
try {
update(context);
return callback.doInIteration(context);
- } catch (Throwable t) {
+ }
+ catch (Throwable t) {
throw t;
}
}
@@ -331,10 +338,9 @@ public class RepeatTemplate implements RepeatOperations {
* If necessary, wait for results to come back from remote or concurrent
* processes. By default does nothing and returns true.
*
- * @param state
- * the internal state.
+ * @param state the internal state.
* @return true if {@link #canContinue(Object)} is true for all results
- * retrieved.
+ * retrieved.
*/
protected boolean waitForResults(RepeatInternalState state) {
// no-op by default
@@ -344,8 +350,7 @@ public class RepeatTemplate implements RepeatOperations {
/**
* Check return value from batch operation.
*
- * @param value
- * the last callback result.
+ * @param value the last callback result.
* @return true if the value is {@link ExitStatus#CONTINUABLE}.
*/
protected final boolean canContinue(ExitStatus value) {
@@ -367,13 +372,10 @@ public class RepeatTemplate implements RepeatOperations {
/**
* Convenience method to execute after interceptors on a callback result.
*
- * @param context
- * the current batch context.
- * @param value
- * the result of the callback to process.
+ * @param context the current batch context.
+ * @param value the result of the callback to process.
*/
- protected void executeAfterInterceptors(final RepeatContext context,
- ExitStatus value) {
+ protected void executeAfterInterceptors(final RepeatContext context, ExitStatus value) {
// Don't re-throw exceptions here: let the exception handler deal with
// that...
@@ -392,13 +394,12 @@ public class RepeatTemplate implements RepeatOperations {
* Delegate to the {@link CompletionPolicy}.
*
* @see org.springframework.batch.repeat.CompletionPolicy#isComplete(RepeatContext,
- * ExitStatus)
+ * ExitStatus)
*/
public boolean isComplete(RepeatContext context, ExitStatus result) {
boolean complete = completionPolicy.isComplete(context, result);
if (complete) {
- logger
- .debug("Batch is complete according to policy and result value.");
+ logger.debug("Batch is complete according to policy and result value.");
}
return complete;
}
@@ -411,8 +412,7 @@ public class RepeatTemplate implements RepeatOperations {
public boolean isComplete(RepeatContext context) {
boolean complete = completionPolicy.isComplete(context);
if (complete) {
- logger
- .debug("Batch is complete according to policy alone not including result.");
+ logger.debug("Batch is complete according to policy alone not including result.");
}
return complete;
}
diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/retry/support/RetryTemplate.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/retry/support/RetryTemplate.java
index 878855030..13f2053ae 100644
--- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/retry/support/RetryTemplate.java
+++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/retry/support/RetryTemplate.java
@@ -16,6 +16,10 @@
package org.springframework.batch.retry.support;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.retry.RetryCallback;
@@ -80,12 +84,14 @@ public class RetryTemplate implements RetryOperations {
}
/**
- * Setter for single listener if there is only one.
+ * Register an additional listener.
* @param listener
* @see #setListeners(RetryListener[])
*/
- public void setListener(RetryListener listener) {
- this.listeners = new RetryListener[] { listener };
+ public void registerListener(RetryListener listener) {
+ List list = new ArrayList(Arrays.asList(listeners));
+ list.add(listener);
+ listeners = (RetryListener[]) list.toArray(new RetryListener[list.size()]);
}
/**
diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/repeat/listener/CompositeRepeatListenerTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/repeat/listener/CompositeRepeatListenerTests.java
index 81e23d301..7e97d70a4 100644
--- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/repeat/listener/CompositeRepeatListenerTests.java
+++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/repeat/listener/CompositeRepeatListenerTests.java
@@ -57,7 +57,7 @@ public class CompositeRepeatListenerTests extends TestCase {
/**
* Test method for
- * {@link org.springframework.batch.execution.listener.CompositeStepListener#setListener(org.springframework.batch.core.domain.StepListener)}.
+ * {@link org.springframework.batch.execution.listener.CompositeStepListener#registerListener(org.springframework.batch.core.domain.StepListener)}.
*/
public void testSetListener() {
listener.register(new RepeatListenerSupport() {
diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/repeat/listener/RepeatListenerTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/repeat/listener/RepeatListenerTests.java
index 35068423c..58da1cd6c 100644
--- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/repeat/listener/RepeatListenerTests.java
+++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/repeat/listener/RepeatListenerTests.java
@@ -63,7 +63,7 @@ public class RepeatListenerTests extends TestCase {
public void testBeforeInterceptorCanVeto() throws Exception {
RepeatTemplate template = new RepeatTemplate();
final List calls = new ArrayList();
- template.setListener(new RepeatListenerSupport() {
+ template.registerListener(new RepeatListenerSupport() {
public void before(RepeatContext context) {
calls.add("1");
context.setCompleteOnly();
@@ -130,7 +130,7 @@ public class RepeatListenerTests extends TestCase {
public void testSingleOpenInterceptor() throws Exception {
RepeatTemplate template = new RepeatTemplate();
final List calls = new ArrayList();
- template.setListener(new RepeatListenerSupport() {
+ template.registerListener(new RepeatListenerSupport() {
public void open(RepeatContext context) {
calls.add("1");
}
diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/retry/listener/RetryListenerTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/retry/listener/RetryListenerTests.java
index 203e56fb6..e77ced424 100644
--- a/spring-batch-infrastructure/src/test/java/org/springframework/batch/retry/listener/RetryListenerTests.java
+++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/retry/listener/RetryListenerTests.java
@@ -25,7 +25,6 @@ import org.springframework.batch.retry.RetryCallback;
import org.springframework.batch.retry.RetryContext;
import org.springframework.batch.retry.RetryListener;
import org.springframework.batch.retry.exception.TerminatedRetryException;
-import org.springframework.batch.retry.listener.RetryListenerSupport;
import org.springframework.batch.retry.policy.NeverRetryPolicy;
import org.springframework.batch.retry.support.RetryTemplate;
@@ -62,7 +61,7 @@ public class RetryListenerTests extends TestCase {
}
public void testOpenCanVetoRetry() throws Exception {
- template.setListener(new RetryListenerSupport() {
+ template.registerListener(new RetryListenerSupport() {
public boolean open(RetryContext context, RetryCallback callback) {
list.add("1");
return false;
@@ -140,7 +139,7 @@ public class RetryListenerTests extends TestCase {
}
public void testCloseInterceptorsAfterRetry() throws Exception {
- template.setListener(new RetryListenerSupport() {
+ template.registerListener(new RetryListenerSupport() {
public void close(RetryContext context, RetryCallback callback, Throwable t) {
list.add("" + count);
// The last attempt should have been successful: