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: