diff --git a/dictionary.txt b/dictionary.txt
index 90d2baced..78553c4fb 100644
--- a/dictionary.txt
+++ b/dictionary.txt
@@ -94,3 +94,4 @@ payloads
unflushed
memento
michael
+minella
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/ChunkListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/ChunkListener.java
index 0aa5c74c5..c314d22a1 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/ChunkListener.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/ChunkListener.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2008 the original author or authors.
+ * Copyright 2006-2013 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.
@@ -15,23 +15,42 @@
*/
package org.springframework.batch.core;
+import org.springframework.batch.core.scope.context.ChunkContext;
+
/**
* Listener interface for the lifecycle of a chunk. A chunk
- * can be through of as a collection of items that will be
+ * can be through of as a collection of items that will be
* committed together.
- *
+ *
* @author Lucas Ward
+ * @author Michael Minella
*
*/
public interface ChunkListener extends StepListener {
+ static final String ROLLBACK_EXCEPTION_KEY = "sb_rollback_exception";
+
/**
* Callback before the chunk is executed, but inside the transaction.
*/
void beforeChunk();
-
+
/**
* Callback after the chunk is executed, outside the transaction.
*/
void afterChunk();
+
+ /**
+ * Callback after a chunk has been marked for rollback. It is invoked
+ * after transaction rollback. While the rollback will have occurred,
+ * transactional resources might still be active and accessible. Due to
+ * this, data access code within this callback will still "participate" in
+ * the original transaction unless it declares that it run in its own
+ * transaction. Hence: Use PROPAGATION_REQUIRES_NEW for any
+ * transactional operation that is called from here.
+ *
+ * @param context the chunk context containing the exception that caused
+ * the underlying rollback.
+ */
+ void afterChunkError(ChunkContext context);
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterChunkError.java b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterChunkError.java
new file mode 100644
index 000000000..65c92159e
--- /dev/null
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/annotation/AfterChunkError.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2012-2013 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.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.springframework.batch.core.ChunkListener;
+import org.springframework.batch.core.scope.context.ChunkContext;
+
+/**
+ * Marks a method to be called after a has failed and been
+ * marked for rollback.
+ *
+ * Expected signature: void afterFailedChunk(ChunkContext context)
+ *
+ * @author Michael Minella
+ * @since 2.2
+ * @see ChunkListener#afterChunkError(ChunkContext)
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD})
+public @interface AfterChunkError {
+
+}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ChunkListenerSupport.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ChunkListenerSupport.java
index 8d7979de5..e36fef4e0 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ChunkListenerSupport.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/ChunkListenerSupport.java
@@ -16,6 +16,7 @@
package org.springframework.batch.core.listener;
import org.springframework.batch.core.ChunkListener;
+import org.springframework.batch.core.scope.context.ChunkContext;
/**
* Basic support implementation of {@link ChunkListener}
@@ -39,4 +40,12 @@ public class ChunkListenerSupport implements ChunkListener {
public void beforeChunk() {
}
+
+ @Override
+ /* (non-Javadoc)
+ * @see org.springframework.batch.core.domain.ChunkListener#afterChunkError(ChunkContext)
+ */
+ public void afterChunkError(ChunkContext context) {
+ }
+
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeChunkListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeChunkListener.java
index 2109fba0b..0207b717c 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeChunkListener.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/CompositeChunkListener.java
@@ -19,6 +19,7 @@ import java.util.Iterator;
import java.util.List;
import org.springframework.batch.core.ChunkListener;
+import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.core.Ordered;
/**
@@ -73,4 +74,12 @@ public class CompositeChunkListener implements ChunkListener {
listener.beforeChunk();
}
}
+
+ @Override
+ public void afterChunkError(ChunkContext context) {
+ for (Iterator iterator = listeners.iterator(); iterator.hasNext();) {
+ ChunkListener listener = iterator.next();
+ listener.afterChunkError(context);
+ }
+ }
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/MulticasterBatchListener.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/MulticasterBatchListener.java
index 83eb33171..24b496ee5 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/MulticasterBatchListener.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/MulticasterBatchListener.java
@@ -26,6 +26,7 @@ import org.springframework.batch.core.SkipListener;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.StepExecutionListener;
import org.springframework.batch.core.StepListener;
+import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.item.ItemStream;
/**
@@ -317,4 +318,13 @@ ItemProcessListener, ItemWriteListener, SkipListener {
skipListener.onSkipInProcess(item, t);
}
+ @Override
+ public void afterChunkError(ChunkContext context) {
+ try {
+ chunkListener.afterChunkError(context);
+ }
+ catch (RuntimeException e) {
+ throw new StepListenerFailedException("Error in afterFailedChunk.", e);
+ }
+ }
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerMetaData.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerMetaData.java
index 50a23c371..21f85eaff 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerMetaData.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerMetaData.java
@@ -29,6 +29,7 @@ import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.StepExecutionListener;
import org.springframework.batch.core.StepListener;
import org.springframework.batch.core.annotation.AfterChunk;
+import org.springframework.batch.core.annotation.AfterChunkError;
import org.springframework.batch.core.annotation.AfterProcess;
import org.springframework.batch.core.annotation.AfterRead;
import org.springframework.batch.core.annotation.AfterStep;
@@ -44,6 +45,7 @@ import org.springframework.batch.core.annotation.OnSkipInProcess;
import org.springframework.batch.core.annotation.OnSkipInRead;
import org.springframework.batch.core.annotation.OnSkipInWrite;
import org.springframework.batch.core.annotation.OnWriteError;
+import org.springframework.batch.core.scope.context.ChunkContext;
/**
* Enumeration for {@link StepListener} meta data, which ties together the names
@@ -59,6 +61,7 @@ public enum StepListenerMetaData implements ListenerMetaData {
AFTER_STEP("afterStep", "after-step-method", AfterStep.class, StepExecutionListener.class, StepExecution.class),
BEFORE_CHUNK("beforeChunk", "before-chunk-method", BeforeChunk.class, ChunkListener.class),
AFTER_CHUNK("afterChunk", "after-chunk-method", AfterChunk.class, ChunkListener.class),
+ AFTER_CHUNK_ERROR("afterChunkError", "after-chunk-error-method", AfterChunkError.class, ChunkListener.class, ChunkContext.class),
BEFORE_READ("beforeRead", "before-read-method", BeforeRead.class, ItemReadListener.class),
AFTER_READ("afterRead", "after-read-method", AfterRead.class, ItemReadListener.class, Object.class),
ON_READ_ERROR("onReadError", "on-read-error-method", OnReadError.class, ItemReadListener.class, Exception.class),
@@ -138,7 +141,7 @@ public enum StepListenerMetaData implements ListenerMetaData {
}
public static ListenerMetaData[] taskletListenerMetaData() {
- return new ListenerMetaData[] {BEFORE_CHUNK, AFTER_CHUNK};
+ return new ListenerMetaData[] {BEFORE_CHUNK, AFTER_CHUNK, AFTER_CHUNK_ERROR};
}
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerSupport.java b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerSupport.java
index 70d9d4028..e401b7fb9 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerSupport.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/listener/StepListenerSupport.java
@@ -26,6 +26,7 @@ import org.springframework.batch.core.SkipListener;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.StepExecutionListener;
import org.springframework.batch.core.StepListener;
+import org.springframework.batch.core.scope.context.ChunkContext;
/**
* Basic no-op implementations of all {@link StepListener} interfaces.
@@ -149,4 +150,8 @@ ItemReadListener, ItemProcessListener, ItemWriteListener, SkipListene
public void onSkipInWrite(S item, Throwable t) {
}
+ @Override
+ public void afterChunkError(ChunkContext context) {
+ }
+
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.java
index 2e0dec69c..1d1bad01f 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/builder/FaultTolerantStepBuilder.java
@@ -29,6 +29,7 @@ import org.springframework.batch.core.JobInterruptedException;
import org.springframework.batch.core.SkipListener;
import org.springframework.batch.core.StepListener;
import org.springframework.batch.core.listener.StepListenerFactoryBean;
+import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.FatalStepExecutionException;
import org.springframework.batch.core.step.item.BatchRetryTemplate;
import org.springframework.batch.core.step.item.ChunkMonitor;
@@ -667,6 +668,14 @@ public class FaultTolerantStepBuilder extends SimpleStepBuilder {
}
}
+ @Override
+ public void afterChunkError(ChunkContext context) {
+ try {
+ chunkListener.afterChunkError(context);
+ }
+ catch (Throwable t) {
+ throw new FatalStepExecutionException("ChunkListener threw exception, rethrowing as fatal", t);
+ }
+ }
}
-
}
diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/TaskletStep.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/TaskletStep.java
index 552cded4d..9d9c07b42 100644
--- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/TaskletStep.java
+++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/tasklet/TaskletStep.java
@@ -70,6 +70,7 @@ import org.springframework.util.Assert;
* @author Lucas Ward
* @author Ben Hale
* @author Robert Kasanicky
+ * @author Michael Minella
*/
@SuppressWarnings("serial")
public class TaskletStep extends AbstractStep {
@@ -351,6 +352,7 @@ public class TaskletStep extends AbstractStep {
rollback(stepExecution);
}
}
+ chunkListener.afterChunkError(chunkContext);
}
if (status == TransactionSynchronization.STATUS_UNKNOWN) {
logger.error("Rolling back with transaction in unknown state");
@@ -396,10 +398,10 @@ public class TaskletStep extends AbstractStep {
}
catch (Exception e) {
if (transactionAttribute.rollbackOn(e)) {
+ chunkContext.setAttribute(ChunkListener.ROLLBACK_EXCEPTION_KEY, e);
throw e;
}
}
-
}
finally {
diff --git a/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.1.xsd b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.1.xsd
index addde7b81..5bff17301 100644
--- a/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.1.xsd
+++ b/spring-batch-core/src/main/resources/org/springframework/batch/core/configuration/xml/spring-batch-2.1.xsd
@@ -1069,6 +1069,7 @@ ref" is not required, and only needs to be specified explicitly
+
diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/listener/CompositeChunkListenerTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/listener/CompositeChunkListenerTests.java
index 30f6a8138..19ba25d07 100644
--- a/spring-batch-core/src/test/java/org/springframework/batch/core/listener/CompositeChunkListenerTests.java
+++ b/spring-batch-core/src/test/java/org/springframework/batch/core/listener/CompositeChunkListenerTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2006-2008 the original author or authors.
+ * Copyright 2006-2013 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.
@@ -15,44 +15,56 @@
*/
package org.springframework.batch.core.listener;
-import static org.easymock.EasyMock.*;
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
import org.junit.Before;
import org.junit.Test;
import org.springframework.batch.core.ChunkListener;
-import org.springframework.batch.core.listener.CompositeChunkListener;
+import org.springframework.batch.core.scope.context.ChunkContext;
/**
* @author Lucas Ward
+ * @author Michael Minella
*
*/
public class CompositeChunkListenerTests {
ChunkListener listener;
CompositeChunkListener compositeListener;
-
+
@Before
public void setUp() throws Exception {
listener = createMock(ChunkListener.class);
compositeListener = new CompositeChunkListener();
compositeListener.register(listener);
}
-
+
@Test
public void testBeforeChunk(){
-
+
listener.beforeChunk();
replay(listener);
compositeListener.beforeChunk();
verify(listener);
}
-
+
@Test
public void testAfterChunk(){
-
+
listener.afterChunk();
replay(listener);
compositeListener.afterChunk();
verify(listener);
}
+
+ @Test
+ public void testAfterChunkFailed(){
+ ChunkContext context = new ChunkContext(null);
+ listener.afterChunkError(context);
+ replay(listener);
+ compositeListener.afterChunkError(context);
+ verify(listener);
+ }
}
diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/listener/StepListenerFactoryBeanTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/listener/StepListenerFactoryBeanTests.java
index 3288289ef..7b118078a 100644
--- a/spring-batch-core/src/test/java/org/springframework/batch/core/listener/StepListenerFactoryBeanTests.java
+++ b/spring-batch-core/src/test/java/org/springframework/batch/core/listener/StepListenerFactoryBeanTests.java
@@ -18,7 +18,6 @@ package org.springframework.batch.core.listener;
import static junit.framework.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.springframework.batch.core.listener.StepListenerMetaData.AFTER_CHUNK;
import static org.springframework.batch.core.listener.StepListenerMetaData.AFTER_STEP;
import static org.springframework.batch.core.listener.StepListenerMetaData.AFTER_WRITE;
@@ -44,6 +43,8 @@ import org.springframework.batch.core.SkipListener;
import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.StepExecutionListener;
import org.springframework.batch.core.StepListener;
+import org.springframework.batch.core.annotation.AfterChunk;
+import org.springframework.batch.core.annotation.AfterChunkError;
import org.springframework.batch.core.annotation.AfterProcess;
import org.springframework.batch.core.annotation.AfterRead;
import org.springframework.batch.core.annotation.AfterStep;
@@ -57,6 +58,7 @@ import org.springframework.batch.core.annotation.OnProcessError;
import org.springframework.batch.core.annotation.OnReadError;
import org.springframework.batch.core.annotation.OnWriteError;
import org.springframework.batch.core.configuration.xml.AbstractTestComponent;
+import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.Ordered;
import org.springframework.util.Assert;
@@ -83,10 +85,10 @@ public class StepListenerFactoryBeanTests {
public void testStepAndChunk() throws Exception {
TestListener testListener = new TestListener();
factoryBean.setDelegate(testListener);
- Map metaDataMap = new HashMap();
- metaDataMap.put(AFTER_STEP.getPropertyName(), "destroy");
- metaDataMap.put(AFTER_CHUNK.getPropertyName(), "afterChunk");
- factoryBean.setMetaDataMap(metaDataMap);
+ // Map metaDataMap = new HashMap();
+ // metaDataMap.put(AFTER_STEP.getPropertyName(), "destroy");
+ // metaDataMap.put(AFTER_CHUNK.getPropertyName(), "afterChunk");
+ // factoryBean.setMetaDataMap(metaDataMap);
String readItem = "item";
Integer writeItem = 2;
List writeItems = Arrays.asList(writeItem);
@@ -95,6 +97,7 @@ public class StepListenerFactoryBeanTests {
((StepExecutionListener) listener).afterStep(stepExecution);
((ChunkListener) listener).beforeChunk();
((ChunkListener) listener).afterChunk();
+ ((ChunkListener) listener).afterChunkError(new ChunkContext(null));
((ItemReadListener) listener).beforeRead();
((ItemReadListener) listener).afterRead(readItem);
((ItemReadListener) listener).onReadError(new Exception());
@@ -110,6 +113,7 @@ public class StepListenerFactoryBeanTests {
assertTrue(testListener.beforeStepCalled);
assertTrue(testListener.beforeChunkCalled);
assertTrue(testListener.afterChunkCalled);
+ assertTrue(testListener.afterChunkErrorCalled);
assertTrue(testListener.beforeReadCalled);
assertTrue(testListener.afterReadCalled);
assertTrue(testListener.onReadErrorCalled);
@@ -132,7 +136,6 @@ public class StepListenerFactoryBeanTests {
ThreeStepExecutionListener delegate = new ThreeStepExecutionListener();
factoryBean.setDelegate(delegate);
Map metaDataMap = new HashMap();
- ;
metaDataMap.put(AFTER_STEP.getPropertyName(), "destroy");
factoryBean.setMetaDataMap(metaDataMap);
StepListener listener = (StepListener) factoryBean.getObject();
@@ -428,6 +431,8 @@ public class StepListenerFactoryBeanTests {
boolean afterChunkCalled = false;
+ boolean afterChunkErrorCalled = false;
+
boolean beforeReadCalled = false;
boolean afterReadCalled = false;
@@ -457,6 +462,7 @@ public class StepListenerFactoryBeanTests {
beforeStepCalled = true;
}
+ @AfterStep
public void destroy() {
afterStepCalled = true;
}
@@ -466,10 +472,16 @@ public class StepListenerFactoryBeanTests {
beforeChunkCalled = true;
}
+ @AfterChunk
public void afterChunk() {
afterChunkCalled = true;
}
+ @AfterChunkError
+ public void afterChunkError(ChunkContext context) {
+ afterChunkErrorCalled = true;
+ }
+
@BeforeRead
public void beforeReadMethod() {
beforeReadCalled = true;
diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBeanRollbackTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBeanRollbackTests.java
index bf646e3fb..ae2e803d7 100644
--- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBeanRollbackTests.java
+++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBeanRollbackTests.java
@@ -29,6 +29,7 @@ import org.springframework.batch.core.StepExecution;
import org.springframework.batch.core.StepListener;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean;
+import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.FatalStepExecutionException;
import org.springframework.batch.core.step.factory.FaultTolerantStepFactoryBean;
import org.springframework.batch.item.ItemReader;
@@ -110,7 +111,7 @@ public class FaultTolerantStepFactoryBeanRollbackTests {
@Test
public void testBeforeChunkListenerException() throws Exception{
- factory.setListeners(new StepListener []{new ExceptionThrowingChunkListener(true)});
+ factory.setListeners(new StepListener []{new ExceptionThrowingChunkListener(1)});
Step step = (Step) factory.getObject();
step.execute(stepExecution);
assertEquals(FAILED, stepExecution.getStatus());
@@ -123,7 +124,7 @@ public class FaultTolerantStepFactoryBeanRollbackTests {
@Test
public void testAfterChunkListenerException() throws Exception{
- factory.setListeners(new StepListener []{new ExceptionThrowingChunkListener(false)});
+ factory.setListeners(new StepListener []{new ExceptionThrowingChunkListener(2)});
Step step = (Step) factory.getObject();
step.execute(stepExecution);
assertEquals(FAILED, stepExecution.getStatus());
@@ -590,24 +591,31 @@ public class FaultTolerantStepFactoryBeanRollbackTests {
class ExceptionThrowingChunkListener implements ChunkListener{
- private boolean throwBefore = true;
+ private int phase = -1;
- public ExceptionThrowingChunkListener(boolean throwBefore) {
- this.throwBefore = throwBefore;
+ public ExceptionThrowingChunkListener(int throwPhase) {
+ this.phase = throwPhase;
}
@Override
public void beforeChunk() {
- if(throwBefore){
+ if(phase == 1){
throw new IllegalArgumentException("Planned exception");
}
}
@Override
public void afterChunk() {
- throw new IllegalArgumentException("Planned exception");
+ if(phase == 2) {
+ throw new IllegalArgumentException("Planned exception");
+ }
+ }
+ @Override
+ public void afterChunkError(ChunkContext context) {
+ if(phase == 3) {
+ throw new IllegalArgumentException("Planned exception");
+ }
}
}
-
}
diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBeanTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBeanTests.java
index 84c4c0a02..8a2484751 100644
--- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBeanTests.java
+++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBeanTests.java
@@ -33,6 +33,7 @@ import org.springframework.batch.core.StepListener;
import org.springframework.batch.core.listener.SkipListenerSupport;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.repository.support.MapJobRepositoryFactoryBean;
+import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.factory.FaultTolerantStepFactoryBean;
import org.springframework.batch.core.step.skip.LimitCheckingItemSkipPolicy;
import org.springframework.batch.core.step.skip.SkipLimitExceededException;
@@ -806,6 +807,9 @@ public class FaultTolerantStepFactoryBeanTests {
listenerCalls.add(5);
}
+ @Override
+ public void afterChunkError(ChunkContext context) {
+ }
}
factory.setItemWriter(new TestItemListenerWriter());
@@ -1109,7 +1113,6 @@ public class FaultTolerantStepFactoryBeanTests {
@SuppressWarnings("serial")
public static class NonExistentException extends Exception {
-
}
}
diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SimpleStepFactoryBeanTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SimpleStepFactoryBeanTests.java
index d362bff4f..2e6e4fd99 100644
--- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SimpleStepFactoryBeanTests.java
+++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/SimpleStepFactoryBeanTests.java
@@ -46,6 +46,7 @@ import org.springframework.batch.core.repository.dao.MapJobExecutionDao;
import org.springframework.batch.core.repository.dao.MapJobInstanceDao;
import org.springframework.batch.core.repository.dao.MapStepExecutionDao;
import org.springframework.batch.core.repository.support.SimpleJobRepository;
+import org.springframework.batch.core.scope.context.ChunkContext;
import org.springframework.batch.core.step.AbstractStep;
import org.springframework.batch.core.step.factory.SimpleStepFactoryBean;
import org.springframework.batch.item.ItemProcessor;
@@ -217,7 +218,7 @@ public class SimpleStepFactoryBeanTests {
@Test
public void testChunkListeners() throws Exception {
- String[] items = new String[] { "1", "2", "3", "4", "5", "6", "7" };
+ String[] items = new String[] { "1", "2", "3", "4", "5", "6", "7", "error" };
int commitInterval = 3;
SimpleStepFactoryBean factory = getStepFactory(items);
@@ -227,6 +228,10 @@ public class SimpleStepFactoryBeanTests {
@Override
public void beforeWrite(List extends Object> items) {
+ if(items.contains("error")) {
+ throw new RuntimeException("rollback the last chunk");
+ }
+
trail = trail + "2";
}
@@ -241,6 +246,8 @@ public class SimpleStepFactoryBeanTests {
int afterCount = 0;
+ int failedCount = 0;
+
private AssertingWriteListener writeListener;
public CountingChunkListener(AssertingWriteListener writeListener) {
@@ -259,6 +266,12 @@ public class SimpleStepFactoryBeanTests {
writeListener.trail = writeListener.trail + "1";
beforeCount++;
}
+
+ @Override
+ public void afterChunkError(ChunkContext context) {
+ writeListener.trail = writeListener.trail + "5";
+ failedCount++;
+ }
}
AssertingWriteListener writeListener = new AssertingWriteListener();
CountingChunkListener chunkListener = new CountingChunkListener(writeListener);
@@ -272,13 +285,15 @@ public class SimpleStepFactoryBeanTests {
JobExecution jobExecution = repository.createJobExecution(job.getName(), new JobParameters());
job.execute(jobExecution);
- assertEquals(BatchStatus.COMPLETED, jobExecution.getStatus());
+ assertEquals(BatchStatus.FAILED, jobExecution.getStatus());
assertNull(reader.read());
- assertEquals(items.length, written.size());
+ assertEquals(6, written.size());
int expectedListenerCallCount = (items.length / commitInterval) + 1;
- assertEquals(expectedListenerCallCount, chunkListener.afterCount);
+ assertEquals(expectedListenerCallCount - 1, chunkListener.afterCount);
assertEquals(expectedListenerCallCount, chunkListener.beforeCount);
+ assertEquals(1, chunkListener.failedCount);
+ assertEquals("1234123415", writeListener.trail);
assertTrue("Listener order not as expected: " + writeListener.trail, writeListener.trail.startsWith("1234"));
}
@@ -392,6 +407,10 @@ public class SimpleStepFactoryBeanTests {
public void beforeChunk() {
}
+ @Override
+ public void afterChunkError(ChunkContext context) {
+ }
+
}
TestItemListenerWriter itemWriter = new TestItemListenerWriter();