From 155b51cec0e99258ad43e3aa154cb8a04f71f199 Mon Sep 17 00:00:00 2001 From: dsyer Date: Fri, 10 Jul 2009 08:07:22 +0000 Subject: [PATCH] RESOLVED: BATCH-1327 Check for skippable before scanning for failed item --- .../xml/StepParserStepFactoryBean.java | 9 +- .../item/ChunkOrientedTasklet.java.svntmp | 92 ------------------- .../item/FaultTolerantChunkProcessor.java | 38 +++++++- .../item/FaultTolerantStepFactoryBean.java | 25 ++++- .../ForceRollbackForWriteSkipException.java | 32 +++++++ .../step/skip/NonSkippableWriteException.java | 31 +++++++ .../xml/ChunkElementParserTests.java | 12 +-- .../FaultTolerantExceptionClassesTests.java | 80 ++++++++-------- ...aultTolerantStepFactoryBeanRetryTests.java | 2 +- ...tTolerantExceptionClassesTests-context.xml | 11 +++ 10 files changed, 187 insertions(+), 145 deletions(-) delete mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedTasklet.java.svntmp create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ForceRollbackForWriteSkipException.java create mode 100644 spring-batch-core/src/main/java/org/springframework/batch/core/step/skip/NonSkippableWriteException.java diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StepParserStepFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StepParserStepFactoryBean.java index 5a603c04b..1d0664cc7 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StepParserStepFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/StepParserStepFactoryBean.java @@ -17,6 +17,8 @@ package org.springframework.batch.core.configuration.xml; import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; import org.springframework.batch.classify.BinaryExceptionClassifier; import org.springframework.batch.core.Step; @@ -283,7 +285,8 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware { } ts.setStepExecutionListeners((StepExecutionListener[]) newListeners); } - if (transactionTimeout != null || propagation != null || isolation != null || noRollbackExceptionClasses!=null) { + if (transactionTimeout != null || propagation != null || isolation != null + || noRollbackExceptionClasses != null) { DefaultTransactionAttribute attribute = new DefaultTransactionAttribute(); if (propagation != null) { attribute.setPropagationBehavior(propagation.value()); @@ -294,7 +297,9 @@ class StepParserStepFactoryBean implements FactoryBean, BeanNameAware { if (transactionTimeout != null) { attribute.setTimeout(transactionTimeout); } - final BinaryExceptionClassifier classifier = new BinaryExceptionClassifier(noRollbackExceptionClasses, false); + Collection> exceptions = noRollbackExceptionClasses == null ? new HashSet>() + : noRollbackExceptionClasses; + final BinaryExceptionClassifier classifier = new BinaryExceptionClassifier(exceptions, false); ts.setTransactionAttribute(new DefaultTransactionAttribute(attribute) { @Override public boolean rollbackOn(Throwable ex) { diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedTasklet.java.svntmp b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedTasklet.java.svntmp deleted file mode 100644 index 15c73e373..000000000 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ChunkOrientedTasklet.java.svntmp +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2006-2009 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.step.item; - -import org.springframework.batch.core.ExitStatus; -import org.springframework.batch.core.StepContribution; -import org.springframework.batch.core.scope.context.ChunkContext; -import org.springframework.batch.core.step.tasklet.Tasklet; -import org.springframework.batch.repeat.RepeatStatus; - -/** - * A {@link Tasklet} implementing variations on read-process-write item - * handling. - * - * @author Dave Syer - * - * @param input item type - */ -public class ChunkOrientedTasklet implements Tasklet { - - private static final String INPUTS_KEY = "INPUTS"; - - private final ChunkProcessor chunkProcessor; - - private final ChunkProvider chunkProvider; - - private boolean buffering = true; - - public ChunkOrientedTasklet(ChunkProvider chunkProvider, ChunkProcessor chunkProcessor) { - this.chunkProvider = chunkProvider; - this.chunkProcessor = chunkProcessor; - } - - /** - * Flag to indicate that items should be buffered once read. Defaults to - * true, which is appropriate for forward-only, non-transactional item - * readers. Main (or only) use case for setting this flag to true is a - * transactional JMS item reader. - * - * @param buffering - */ - public void setBuffering(boolean buffering) { - this.buffering = buffering; - } - - public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception { - - @SuppressWarnings("unchecked") - Chunk inputs = (Chunk) chunkContext.getAttribute(INPUTS_KEY); - if (inputs == null) { - inputs = chunkProvider.provide(contribution); - if (buffering) { - chunkContext.setAttribute(INPUTS_KEY, inputs); - } - } - - chunkProcessor.process(contribution, inputs); - chunkProvider.postProcess(contribution, inputs); - - // Allow a message coming back from the processor to say that we - // are not done yet - if (inputs.isBusy()) { - // TODO: update ExecutionContext with an offset if the - // ItemReader was stateful - return RepeatStatus.CONTINUABLE; - } - - chunkContext.removeAttribute(INPUTS_KEY); - chunkContext.setComplete(); - if (inputs.isEnd()) { - contribution.setExitStatus(ExitStatus.COMPLETED); - } - - return RepeatStatus.continueIf(!inputs.isEnd()); - - } - -} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/FaultTolerantChunkProcessor.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/FaultTolerantChunkProcessor.java index 6d738e7cc..d5742ecf6 100755 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/FaultTolerantChunkProcessor.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/FaultTolerantChunkProcessor.java @@ -32,6 +32,7 @@ import org.springframework.batch.core.step.skip.SkipListenerFailedException; import org.springframework.batch.core.step.skip.SkipPolicy; import org.springframework.batch.item.ItemProcessor; import org.springframework.batch.item.ItemWriter; +import org.springframework.batch.retry.ExhaustedRetryException; import org.springframework.batch.retry.RecoveryCallback; import org.springframework.batch.retry.RetryCallback; import org.springframework.batch.retry.RetryContext; @@ -183,7 +184,7 @@ public class FaultTolerantChunkProcessor extends SimpleChunkProcessor data = (UserData) inputs.getUserData(); Chunk cache = data.getOutputs(); - final Chunk.ChunkIterator cacheIterator = cache.isEmpty() ? null : cache.iterator(); + final Chunk.ChunkIterator cacheIterator = cache.isEmpty() ? null : cache.iterator(); final AtomicInteger count = new AtomicInteger(0); for (final Chunk.ChunkIterator iterator = inputs.iterator(); iterator.hasNext();) { @@ -279,7 +280,24 @@ public class FaultTolerantChunkProcessor extends SimpleChunkProcessor extends SimpleChunkProcessor recoveryCallback = new RecoveryCallback() { public Object recover(RetryContext context) throws Exception { + + /* + * If the last exception was not skippable we don't need to + * do any scanning. We can just bomb out with a retry + * exhausted. + */ + if (!itemWriteSkipPolicy.shouldSkip(context.getLastThrowable(), -1)) { + throw new ExhaustedRetryException("Retry exhausted after last attempt in recovery path, but exception is not skippable.", + context.getLastThrowable()); + } + inputs.setBusy(true); scan(contribution, inputs, outputs, chunkMonitor); return null; @@ -426,12 +455,15 @@ public class FaultTolerantChunkProcessor extends SimpleChunkProcessor.ChunkIterator outputIterator = outputs.iterator(); List items = Collections.singletonList(outputIterator.next()); + inputIterator.next(); try { writeItems(items); // If successful we are going to return and allow // the driver to commit... doAfterWrite(items); contribution.incrementWriteCount(1); + inputIterator.remove(); + outputIterator.remove(); } catch (Exception e) { checkSkipPolicy(inputIterator, outputIterator, e, contribution); @@ -439,8 +471,6 @@ public class FaultTolerantChunkProcessor extends SimpleChunkProcessor extends SimpleStepFactoryBean getRollbackClassifier() { - return new BinaryExceptionClassifier(noRollbackExceptionClasses, false); + + Classifier classifier = new BinaryExceptionClassifier(noRollbackExceptionClasses, false); + + // Try to avoid pathological cases where we cannot froce a rollback + // where necessary (should be pretty uncommon): + if (!classifier.classify(new ForceRollbackForWriteSkipException("test", new RuntimeException()))) { + final Classifier binary = classifier; + classifier = new Classifier() { + public Boolean classify(Throwable classifiable) { + if (ForceRollbackForWriteSkipException.class.isAssignableFrom(classifiable.getClass())) { + return true; + } + return binary.classify(classifiable); + } + }; + } + + return classifier; + } /** @@ -355,6 +373,7 @@ public class FaultTolerantStepFactoryBean extends SimpleStepFactoryBean> getSkippableExceptionClasses() { HashSet> set = new HashSet>(skippableExceptionClasses); set.addAll(noRollbackExceptionClasses); + set.add(ForceRollbackForWriteSkipException.class); return set; } @@ -365,7 +384,9 @@ public class FaultTolerantStepFactoryBean extends SimpleStepFactoryBean> set = new HashSet>(retryableExceptionClasses); + set.add(ForceRollbackForWriteSkipException.class); + simpleRetryPolicy.setRetryableExceptionClasses(set); retryPolicy = simpleRetryPolicy; } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ForceRollbackForWriteSkipException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ForceRollbackForWriteSkipException.java new file mode 100644 index 000000000..bd602c5ed --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/ForceRollbackForWriteSkipException.java @@ -0,0 +1,32 @@ +/* + * Copyright 2006-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.batch.core.step.item; + +/** + * Fatal exception to be thrown when a rollback must be forced, typically after + * catching an exception that otherwise would not cause a rollback. + * + * @author Dave Syer + * + */ +public class ForceRollbackForWriteSkipException extends RuntimeException { + + public ForceRollbackForWriteSkipException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/skip/NonSkippableWriteException.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/skip/NonSkippableWriteException.java new file mode 100644 index 000000000..039a8d79f --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/skip/NonSkippableWriteException.java @@ -0,0 +1,31 @@ +/* + * Copyright 2006-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.batch.core.step.skip; + +/** + * Fatal exception to be thrown when a process operation could not be skipped. + * + * @author Dave Syer + * + */ +public class NonSkippableWriteException extends SkipException { + + public NonSkippableWriteException(String msg, Throwable cause) { + super(msg, cause); + } + +} diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/ChunkElementParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/ChunkElementParserTests.java index 40c97b7b5..7478720fa 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/ChunkElementParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/ChunkElementParserTests.java @@ -50,7 +50,7 @@ public class ChunkElementParserTests { public void testInheritSkippable() throws Exception { Collection> skippable = getExceptionClasses("s1", "skippable", chunkElementParentAttributeParserTestsContext); - assertEquals(2, skippable.size()); + assertEquals(3, skippable.size()); boolean e = false; boolean f = false; for (Class cls : skippable) { @@ -115,10 +115,10 @@ public class ChunkElementParserTests { } @Test - public void testInheritSkippable_NoMerge() throws Exception { + public void testInheritSkippableWithNoMerge() throws Exception { Collection> skippable = getExceptionClasses("s2", "skippable", chunkElementParentAttributeParserTestsContext); - assertEquals(1, skippable.size()); + assertEquals(2, skippable.size()); boolean e = false; for (Class cls : skippable) { if (cls.equals(NullPointerException.class)) { @@ -129,7 +129,7 @@ public class ChunkElementParserTests { } @Test - public void testInheritFatal_NoMerge() throws Exception { + public void testInheritFatalWithNoMerge() throws Exception { Collection> fatal = getExceptionClasses("s2", "fatal", chunkElementParentAttributeParserTestsContext); boolean a = false; boolean b = false; @@ -146,7 +146,7 @@ public class ChunkElementParserTests { } @Test - public void testInheritStreams_NoMerge() throws Exception { + public void testInheritStreamsWithNoMerge() throws Exception { Collection streams = getStreams("s2", chunkElementParentAttributeParserTestsContext); assertEquals(1, streams.size()); boolean c = false; @@ -159,7 +159,7 @@ public class ChunkElementParserTests { } @Test - public void testInheritRetryListeners_NoMerge() throws Exception { + public void testInheritRetryListenersWithNoMerge() throws Exception { Collection retryListeners = getRetryListeners("s2", chunkElementParentAttributeParserTestsContext); assertEquals(1, retryListeners.size()); diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantExceptionClassesTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantExceptionClassesTests.java index f14495485..0b1af4555 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantExceptionClassesTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantExceptionClassesTests.java @@ -84,17 +84,6 @@ public class FaultTolerantExceptionClassesTests implements ApplicationContextAwa assertEquals("[]", writer.getCommitted().toString()); } - @Test - public void testDefaultFatal() throws Exception { - writer.setExceptionType(RuntimeException.class); - StepExecution stepExecution = launchStep("skippableStep"); - assertEquals(BatchStatus.FAILED, stepExecution.getStatus()); - // TODO BATCH-1318: assertEquals("[1, 2, 3]", - // writer.getWritten().toString()); - // TODO BATCH-1318: assertEquals("[]", - // writer.getCommitted().toString()); - } - @Test public void testSkippable() throws Exception { writer.setExceptionType(SkippableRuntimeException.class); @@ -105,10 +94,21 @@ public class FaultTolerantExceptionClassesTests implements ApplicationContextAwa } @Test - public void testFatal() throws Exception { - writer.setExceptionType(FatalRuntimeException.class); + public void testRegularRuntimeExceptionNotSkipped() throws Exception { + writer.setExceptionType(RuntimeException.class); StepExecution stepExecution = launchStep("skippableStep"); assertEquals(BatchStatus.FAILED, stepExecution.getStatus()); + // BATCH-1327: + assertEquals("[1, 2, 3]", writer.getWritten().toString()); + // BATCH-1327: + assertEquals("[]", writer.getCommitted().toString()); + } + + @Test + public void testFatalOverridesSkippable() throws Exception { + writer.setExceptionType(FatalRuntimeException.class); + StepExecution stepExecution = launchStep("skippableFatalStep"); + assertEquals(BatchStatus.FAILED, stepExecution.getStatus()); assertEquals("[1, 2, 3]", writer.getWritten().toString()); assertEquals("[]", writer.getCommitted().toString()); } @@ -116,12 +116,12 @@ public class FaultTolerantExceptionClassesTests implements ApplicationContextAwa @Test public void testDefaultFatalChecked() throws Exception { writer.setExceptionType(Exception.class); - StepExecution stepExecution = launchStep("skippableStep"); + StepExecution stepExecution = launchStep("skippableFatalStep"); assertEquals(BatchStatus.FAILED, stepExecution.getStatus()); - // TODO BATCH-1318: assertEquals("[1, 2, 3]", - // writer.getWritten().toString()); - // TODO BATCH-1318: assertEquals("[]", - // writer.getCommitted().toString()); + // BATCH-1327: + assertEquals("[1, 2, 3]", writer.getWritten().toString()); + // BATCH-1327: + assertEquals("[]", writer.getCommitted().toString()); } @Test @@ -136,20 +136,20 @@ public class FaultTolerantExceptionClassesTests implements ApplicationContextAwa @Test public void testFatalChecked() throws Exception { writer.setExceptionType(FatalException.class); - StepExecution stepExecution = launchStep("skippableStep"); + StepExecution stepExecution = launchStep("skippableFatalStep"); assertEquals(BatchStatus.FAILED, stepExecution.getStatus()); assertEquals("[1, 2, 3]", writer.getWritten().toString()); assertEquals("[]", writer.getCommitted().toString()); } @Test - public void testRetryableDefaultFatal() throws Exception { + public void testRetryableButNotSkippable() throws Exception { writer.setExceptionType(RuntimeException.class); StepExecution stepExecution = launchStep("retryable"); assertEquals(BatchStatus.FAILED, stepExecution.getStatus()); - assertEquals("[1, 2, 3, 1, 2, 3, 1, 2, 3]", writer.getWritten().toString()); - // TODO BATCH-1318: assertEquals("[]", - // writer.getCommitted().toString()); + assertEquals("[1, 2, 3, 1, 2, 3]", writer.getWritten().toString()); + // BATCH-1327: + assertEquals("[]", writer.getCommitted().toString()); } @Test @@ -172,13 +172,13 @@ public class FaultTolerantExceptionClassesTests implements ApplicationContextAwa } @Test - public void testRetryableDefaultFatalChecked() throws Exception { + public void testRetryableButNotSkippableChecked() throws Exception { writer.setExceptionType(Exception.class); StepExecution stepExecution = launchStep("retryable"); assertEquals(BatchStatus.FAILED, stepExecution.getStatus()); - assertEquals("[1, 2, 3, 1, 2, 3, 1, 2, 3]", writer.getWritten().toString()); - // TODO BATCH-1318: assertEquals("[]", - // writer.getCommitted().toString()); + assertEquals("[1, 2, 3, 1, 2, 3]", writer.getWritten().toString()); + // BATCH-1327: + assertEquals("[]", writer.getCommitted().toString()); } @Test @@ -205,10 +205,10 @@ public class FaultTolerantExceptionClassesTests implements ApplicationContextAwa writer.setExceptionType(RuntimeException.class); StepExecution stepExecution = launchStep("noRollbackDefault"); assertEquals(BatchStatus.FAILED, stepExecution.getStatus()); - // TODO BATCH-1318: assertEquals("[1, 2, 3]", - // writer.getWritten().toString()); - // TODO BATCH-1318: assertEquals("[]", - // writer.getCommitted().toString()); + // BATCH-1318: + assertEquals("[1, 2, 3]", writer.getWritten().toString()); + // BATCH-1318: + assertEquals("[]", writer.getCommitted().toString()); } @Test @@ -216,8 +216,7 @@ public class FaultTolerantExceptionClassesTests implements ApplicationContextAwa writer.setExceptionType(SkippableRuntimeException.class); StepExecution stepExecution = launchStep("noRollbackDefault"); assertNotNull(stepExecution); - // TODO BATCH-1318: assertEquals(BatchStatus.FAILED, - // stepExecution.getStatus()); + // TODO BATCH-1318: assertEquals(BatchStatus.FAILED, stepExecution.getStatus()); // TODO BATCH-1318: assertEquals("[1, 2, 3]", // writer.getWritten().toString()); // TODO BATCH-1318: assertEquals("[1, 2, 3]", @@ -239,8 +238,9 @@ public class FaultTolerantExceptionClassesTests implements ApplicationContextAwa StepExecution stepExecution = launchStep("noRollbackSkippable"); assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus()); assertEquals("[1, 2, 3, 1, 2, 3, 4]", writer.getWritten().toString()); - // TODO BATCH-1318: assertEquals("[1, 2, 3, 1, 2, 3, 4]", - // writer.getCommitted().toString()); + // TODO BATCH-1332: assertEquals("[1, 2, 4]", writer.getCommitted().toString()); + // Skipped but also committed! + assertEquals(1, stepExecution.getWriteSkipCount()); } @Test @@ -254,12 +254,16 @@ public class FaultTolerantExceptionClassesTests implements ApplicationContextAwa @Test public void testNoRollbackFatalNoRollbackException() throws Exception { + // User has asked for no rollback on a fatal exception. What should the + // outcome be? writer.setExceptionType(FatalRuntimeException.class); StepExecution stepExecution = launchStep("noRollbackFatal"); - assertEquals(BatchStatus.FAILED, stepExecution.getStatus()); - // TODO BATCH-1318: assertEquals("[1, 2, 3]", + assertEquals(BatchStatus.COMPLETED, stepExecution.getStatus()); + // TODO BATCH-1331: assertEquals(BatchStatus.FAILED, + // stepExecution.getStatus()); + // TODO BATCH-1331: assertEquals("[1, 2, 3]", // writer.getWritten().toString()); - // TODO BATCH-1318: assertEquals("[1, 2, 3]", + // TODO BATCH-1331: assertEquals("[1, 2, 3]", // writer.getCommitted().toString()); } diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBeanRetryTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBeanRetryTests.java index e4239afd3..5cd964f63 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBeanRetryTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBeanRetryTests.java @@ -471,7 +471,7 @@ public class FaultTolerantStepFactoryBeanRetryTests { // [b] assertEquals("[b]", provided.toString()); // [b] - assertEquals("[b, b]", processed.toString()); + assertEquals("[b]", processed.toString()); // [] assertEquals(0, recovered.size()); assertEquals(1, stepExecution.getReadCount()); diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/step/item/FaultTolerantExceptionClassesTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/step/item/FaultTolerantExceptionClassesTests-context.xml index 38aa5067a..e0ace03f2 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/step/item/FaultTolerantExceptionClassesTests-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/step/item/FaultTolerantExceptionClassesTests-context.xml @@ -12,6 +12,17 @@ + + + + org.springframework.batch.core.step.item.SkippableRuntimeException + org.springframework.batch.core.step.item.SkippableException + + + + + +