diff --git a/spring-batch-core-tests/src/main/resources/META-INF/batch/footballSkipJob.xml b/spring-batch-core-tests/src/main/resources/META-INF/batch/footballSkipJob.xml index 90102beb0..1854c496d 100644 --- a/spring-batch-core-tests/src/main/resources/META-INF/batch/footballSkipJob.xml +++ b/spring-batch-core-tests/src/main/resources/META-INF/batch/footballSkipJob.xml @@ -9,10 +9,13 @@ + skip-limit="#{jobParameters['skip.limit']}" retry-limit="#{jobParameters['retry.limit']}"> + + + diff --git a/spring-batch-core-tests/src/test/java/org/springframework/batch/core/test/football/FootballJobSkipIntegrationTests.java b/spring-batch-core-tests/src/test/java/org/springframework/batch/core/test/football/FootballJobSkipIntegrationTests.java index 64e9cc49a..b4da01443 100644 --- a/spring-batch-core-tests/src/test/java/org/springframework/batch/core/test/football/FootballJobSkipIntegrationTests.java +++ b/spring-batch-core-tests/src/test/java/org/springframework/batch/core/test/football/FootballJobSkipIntegrationTests.java @@ -89,7 +89,7 @@ public class FootballJobSkipIntegrationTests { logger.info("Processed: " + stepExecution); } // They all skip on the second execution because of a primary key violation - execution = jobLauncher.run(job, new JobParametersBuilder().addLong("skip.limit", 100000L) + execution = jobLauncher.run(job, new JobParametersBuilder().addLong("skip.limit", 100000L).addLong("retry.limit", 2L) .toJobParameters()); assertEquals(BatchStatus.COMPLETED, execution.getStatus()); for (StepExecution stepExecution : execution.getStepExecutions()) { diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/ChunkElementParser.java b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/ChunkElementParser.java index 7f8bbf4fa..4358a76d6 100644 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/ChunkElementParser.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/configuration/xml/ChunkElementParser.java @@ -18,8 +18,10 @@ package org.springframework.batch.core.configuration.xml; import java.util.List; import org.springframework.batch.core.listener.StepListenerMetaData; +import org.springframework.batch.core.step.item.ForceRollbackForWriteSkipException; import org.springframework.batch.core.step.skip.LimitCheckingItemSkipPolicy; import org.springframework.batch.repeat.policy.SimpleCompletionPolicy; +import org.springframework.batch.retry.policy.SimpleRetryPolicy; import org.springframework.beans.MutablePropertyValues; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanDefinitionHolder; @@ -119,15 +121,19 @@ public class ChunkElementParser { } String skipLimit = element.getAttribute("skip-limit"); + ManagedMap skippableExceptions = handleExceptionElement(element, parserContext, "skippable-exception-classes"); boolean hasSkipPolicy = false; if (StringUtils.hasText(skipLimit)) { + if (skippableExceptions == null) { + parserContext.getReaderContext().error( + "The element must have skippable-exceptions if a skip-limit is specified.", element); + } if (skipLimit.startsWith("#")) { // It's a late binding expression, so we need step scope... BeanDefinitionBuilder skipPolicy = BeanDefinitionBuilder .genericBeanDefinition(LimitCheckingItemSkipPolicy.class); skipPolicy.setScope("step"); - handleExceptionElement(element, parserContext, skipPolicy.getBeanDefinition().getPropertyValues(), - "skippable-exception-classes", "skippableExceptionMap"); + skipPolicy.addPropertyValue("skippableExceptionMap", skippableExceptions); skipPolicy.addPropertyValue("skipLimit", skipLimit); propertyValues.addPropertyValue("skipPolicy", skipPolicy.getBeanDefinition()); hasSkipPolicy = true; @@ -137,21 +143,45 @@ public class ChunkElementParser { } } if (!hasSkipPolicy) { - handleExceptionElement(element, parserContext, propertyValues, "skippable-exception-classes", - "skippableExceptionClasses"); + // Even if there is no retryLimit, we can still accept exception + // classes for an abstract parent bean definition + propertyValues.addPropertyValue("skippableExceptionClasses", skippableExceptions); } handleItemHandler("skip-policy", "skipPolicy", null, false, element, parserContext, propertyValues, underspecified); + String retryLimit = element.getAttribute("retry-limit"); + ManagedMap retryableExceptions = handleExceptionElement(element, parserContext, "retryable-exception-classes"); + boolean hasRetryPolicy = false; + if (StringUtils.hasText(retryLimit)) { + if (retryableExceptions == null) { + parserContext.getReaderContext().error( + "The element must have retryable-exceptions if a retry-limit is specified.", element); + } + if (retryLimit.startsWith("#")) { + // It's a late binding expression, so we need step scope... + BeanDefinitionBuilder retryPolicy = BeanDefinitionBuilder + .genericBeanDefinition(SimpleRetryPolicy.class); + retryPolicy.setScope("step"); + retryPolicy.addPropertyValue("maxAttempts", retryLimit); + retryPolicy.addPropertyValue("retryableExceptions", retryableExceptions); + propertyValues.addPropertyValue("retryPolicy", retryPolicy.getBeanDefinition()); + hasRetryPolicy = true; + } + else { + propertyValues.addPropertyValue("retryLimit", retryLimit); + } + } + if (!hasRetryPolicy) { + // Even if there is no retryLimit, we can still accept exception + // classes for an abstract parent bean definition + propertyValues.addPropertyValue("retryableExceptionClasses", retryableExceptions); + } + handleItemHandler("retry-policy", "retryPolicy", null, false, element, parserContext, propertyValues, underspecified); - String retryLimit = element.getAttribute("retry-limit"); - if (StringUtils.hasText(retryLimit)) { - propertyValues.addPropertyValue("retryLimit", retryLimit); - } - String cacheCapacity = element.getAttribute("cache-capacity"); if (StringUtils.hasText(cacheCapacity)) { propertyValues.addPropertyValue("cacheCapacity", cacheCapacity); @@ -167,9 +197,6 @@ public class ChunkElementParser { propertyValues.addPropertyValue("processorTransactional", isProcessorTransactional); } - handleExceptionElement(element, parserContext, propertyValues, "retryable-exception-classes", - "retryableExceptionClasses"); - handleRetryListenersElement(element, propertyValues, parserContext, bd); handleStreamsElement(element, propertyValues, parserContext); @@ -315,23 +342,24 @@ public class ChunkElementParser { } @SuppressWarnings("unchecked") - private void handleExceptionElement(Element element, ParserContext parserContext, - MutablePropertyValues propertyValues, String exceptionListName, String propertyName) { + private ManagedMap handleExceptionElement(Element element, ParserContext parserContext, String exceptionListName) { List children = DomUtils.getChildElementsByTagName(element, exceptionListName); if (children.size() == 1) { - Element exceptionClassesElement = children.get(0); ManagedMap map = new ManagedMap(); + Element exceptionClassesElement = children.get(0); map.setMergeEnabled(exceptionClassesElement.hasAttribute(MERGE_ATTR) && Boolean.valueOf(exceptionClassesElement.getAttribute(MERGE_ATTR))); addExceptionClasses("include", true, exceptionClassesElement, map, parserContext); addExceptionClasses("exclude", false, exceptionClassesElement, map, parserContext); - propertyValues.addPropertyValue(propertyName, map); + map.put(ForceRollbackForWriteSkipException.class, true); + return map; } else if (children.size() > 1) { parserContext.getReaderContext().error( "The <" + exceptionListName + "/> element may not appear more than once in a single <" + element.getNodeName() + "/>.", element); } + return null; } @SuppressWarnings("unchecked") diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBean.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBean.java index d7aedb0ff..a3f9565be 100755 --- a/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBean.java +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/item/FaultTolerantStepFactoryBean.java @@ -29,6 +29,7 @@ import org.springframework.batch.classify.SubclassClassifier; import org.springframework.batch.core.JobInterruptedException; import org.springframework.batch.core.Step; import org.springframework.batch.core.step.FatalStepExecutionException; +import org.springframework.batch.core.step.skip.CompositeSkipPolicy; import org.springframework.batch.core.step.skip.ExceptionClassifierSkipPolicy; import org.springframework.batch.core.step.skip.LimitCheckingItemSkipPolicy; import org.springframework.batch.core.step.skip.NeverSkipItemSkipPolicy; @@ -47,6 +48,7 @@ import org.springframework.batch.retry.RetryException; import org.springframework.batch.retry.RetryListener; import org.springframework.batch.retry.RetryPolicy; import org.springframework.batch.retry.backoff.BackOffPolicy; +import org.springframework.batch.retry.policy.CompositeRetryPolicy; import org.springframework.batch.retry.policy.ExceptionClassifierRetryPolicy; import org.springframework.batch.retry.policy.MapRetryContextCache; import org.springframework.batch.retry.policy.NeverRetryPolicy; @@ -373,21 +375,12 @@ public class FaultTolerantStepFactoryBean extends SimpleStepFactoryBean configureChunkProvider() { - if (skipPolicy != null) { - if (!skippableExceptionClasses.isEmpty()) { - logger.info("Skippable exceptions will be ignored because a SkipPolicy was specified explicitly"); - } - if (skipLimit > 0) { - logger.info("Skip limit will be ignored because a SkipPolicy was specified explicitly"); - } - } - - SkipPolicy readSkipPolicy = skipPolicy != null ? skipPolicy : new LimitCheckingItemSkipPolicy(skipLimit, - getSkippableExceptionClasses()); + SkipPolicy readSkipPolicy = createSkipPolicy(); readSkipPolicy = getFatalExceptionAwareProxy(readSkipPolicy); FaultTolerantChunkProvider chunkProvider = new FaultTolerantChunkProvider(getItemReader(), getChunkOperations()); - chunkProvider.setMaxSkipsOnRead(Math.max(getCommitInterval(), FaultTolerantChunkProvider.DEFAULT_MAX_SKIPS_ON_READ)); + chunkProvider.setMaxSkipsOnRead(Math.max(getCommitInterval(), + FaultTolerantChunkProvider.DEFAULT_MAX_SKIPS_ON_READ)); chunkProvider.setSkipPolicy(readSkipPolicy); chunkProvider.setRollbackClassifier(getRollbackClassifier()); @@ -395,6 +388,22 @@ public class FaultTolerantStepFactoryBean extends SimpleStepFactoryBean extends SimpleStepFactoryBean extends SimpleStepFactoryBean, Boolean> map = new HashMap, Boolean>( + retryableExceptionClasses); + map.put(ForceRollbackForWriteSkipException.class, true); + simpleRetryPolicy = new SimpleRetryPolicy(retryLimit, map); + if (retryPolicy == null) { - Map, Boolean> map = new HashMap, Boolean>( - retryableExceptionClasses); - map.put(ForceRollbackForWriteSkipException.class, true); - // set.addAll(noRollbackExceptionClasses); // should only be - // retryable on write - retryPolicy = new SimpleRetryPolicy(retryLimit, map); + retryPolicy = simpleRetryPolicy; + } + else if ((!retryableExceptionClasses.isEmpty() && retryLimit > 0)) { + CompositeRetryPolicy compositeRetryPolicy = new CompositeRetryPolicy(); + compositeRetryPolicy.setPolicies(new RetryPolicy[] { retryPolicy, simpleRetryPolicy }); + retryPolicy = compositeRetryPolicy; } RetryPolicy retryPolicyWrapper = getFatalExceptionAwareProxy(retryPolicy); @@ -544,7 +560,7 @@ public class FaultTolerantStepFactoryBean extends SimpleStepFactoryBean>)exceptions; + nonRetryableExceptionClasses = (List>) exceptions; } } diff --git a/spring-batch-core/src/main/java/org/springframework/batch/core/step/skip/CompositeSkipPolicy.java b/spring-batch-core/src/main/java/org/springframework/batch/core/step/skip/CompositeSkipPolicy.java new file mode 100644 index 000000000..2e5ad9396 --- /dev/null +++ b/spring-batch-core/src/main/java/org/springframework/batch/core/step/skip/CompositeSkipPolicy.java @@ -0,0 +1,47 @@ +/* + * Copyright 2006-2011 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; + +/** + * @author Dave Syer + * + */ +public class CompositeSkipPolicy implements SkipPolicy { + + private SkipPolicy[] skipPolicies; + + public CompositeSkipPolicy() { + this(new SkipPolicy[0]); + } + + public CompositeSkipPolicy(SkipPolicy[] skipPolicies) { + this.skipPolicies = skipPolicies; + } + + public void setSkipPolicies(SkipPolicy[] skipPolicies) { + this.skipPolicies = skipPolicies; + } + + public boolean shouldSkip(Throwable t, int skipCount) throws SkipLimitExceededException { + for (SkipPolicy policy : skipPolicies) { + if (policy.shouldSkip(t, skipCount)) { + return true; + } + } + return false; + } + +} 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 13f99084c..1f734905d 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 @@ -29,6 +29,7 @@ import org.junit.Test; import org.springframework.batch.classify.SubclassClassifier; import org.springframework.batch.core.Step; import org.springframework.batch.core.step.item.SimpleChunkProcessor; +import org.springframework.batch.core.step.skip.SkipPolicy; import org.springframework.batch.core.step.tasklet.TaskletStep; import org.springframework.batch.item.ItemStream; import org.springframework.batch.item.support.CompositeItemStream; @@ -37,11 +38,13 @@ import org.springframework.batch.retry.listener.RetryListenerSupport; import org.springframework.batch.retry.policy.SimpleRetryPolicy; import org.springframework.beans.PropertyAccessorUtils; import org.springframework.beans.factory.BeanCreationException; +import org.springframework.beans.factory.parsing.BeanDefinitionParsingException; import org.springframework.context.ApplicationContext; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.dao.CannotAcquireLockException; import org.springframework.dao.CannotSerializeTransactionException; +import org.springframework.dao.ConcurrencyFailureException; import org.springframework.dao.DeadlockLoserDataAccessException; import org.springframework.dao.PessimisticLockingFailureException; import org.springframework.test.util.ReflectionTestUtils; @@ -68,11 +71,32 @@ public class ChunkElementParserTests { @Test public void testCommitIntervalLateBinding() throws Exception { ConfigurableApplicationContext context = new ClassPathXmlApplicationContext( - "org/springframework/batch/core/configuration/xml/ChunkElementCommitIntervalParserTests-context.xml"); + "org/springframework/batch/core/configuration/xml/ChunkElementLateBindingParserTests-context.xml"); Step step = (Step) context.getBean("s1", Step.class); assertNotNull("Step not parsed", step); } + @Test + public void testSkipAndRetryAttributes() throws Exception { + ConfigurableApplicationContext context = new ClassPathXmlApplicationContext( + "org/springframework/batch/core/configuration/xml/ChunkElementSkipAndRetryAttributeParserTests-context.xml"); + Step step = (Step) context.getBean("s1", Step.class); + assertNotNull("Step not parsed", step); + } + + @Test + public void testIllegalSkipAndRetryAttributes() throws Exception { + try { + ConfigurableApplicationContext context = new ClassPathXmlApplicationContext( + "org/springframework/batch/core/configuration/xml/ChunkElementIllegalSkipAndRetryAttributeParserTests-context.xml"); + Step step = (Step) context.getBean("s1", Step.class); + assertNotNull("Step not parsed", step); + fail("Expected BeanDefinitionParsingException"); + } catch (BeanDefinitionParsingException e) { + // expected + } + } + @Test public void testRetryPolicyAttribute() throws Exception { ConfigurableApplicationContext context = new ClassPathXmlApplicationContext( @@ -98,19 +122,18 @@ public class ChunkElementParserTests { public void testSkipPolicyAttribute() throws Exception { ConfigurableApplicationContext context = new ClassPathXmlApplicationContext( "org/springframework/batch/core/configuration/xml/ChunkElementSkipPolicyParserTests-context.xml"); - Map, Boolean> skippable = getSkippableExceptionClasses("s1", context); - assertEquals(2, skippable.size()); - containsClassified(skippable, NullPointerException.class, true); - containsClassified(skippable, ArithmeticException.class, true); + SkipPolicy policy = getSkipPolicy("s1", context); + assertTrue(policy.shouldSkip(new NullPointerException(), 0)); + assertTrue(policy.shouldSkip(new ArithmeticException(), 0)); } @Test public void testSkipPolicyElement() throws Exception { ConfigurableApplicationContext context = new ClassPathXmlApplicationContext( "org/springframework/batch/core/configuration/xml/ChunkElementSkipPolicyParserTests-context.xml"); - Map, Boolean> skippable = getSkippableExceptionClasses("s2", context); - assertEquals(1, skippable.size()); - containsClassified(skippable, ArithmeticException.class, true); + SkipPolicy policy = getSkipPolicy("s2", context); + assertFalse(policy.shouldSkip(new NullPointerException(), 0)); + assertTrue(policy.shouldSkip(new ArithmeticException(), 0)); } @Test @@ -164,6 +187,7 @@ public class ChunkElementParserTests { @Test public void testInheritSkippable() throws Exception { Map, Boolean> skippable = getSkippableExceptionClasses("s1", getContext()); + System.err.println(skippable); assertEquals(5, skippable.size()); containsClassified(skippable, NullPointerException.class, true); containsClassified(skippable, ArithmeticException.class, true); @@ -175,9 +199,9 @@ public class ChunkElementParserTests { public void testInheritSkippableWithNoMerge() throws Exception { Map, Boolean> skippable = getSkippableExceptionClasses("s2", getContext()); assertEquals(3, skippable.size()); - containsClassified(skippable, NullPointerException.class, true); + containsClassified(skippable, IllegalArgumentException.class, true); assertFalse(skippable.containsKey(ArithmeticException.class)); - containsClassified(skippable, CannotAcquireLockException.class, false); + containsClassified(skippable, ConcurrencyFailureException.class, false); assertFalse(skippable.containsKey(DeadlockLoserDataAccessException.class)); } @@ -244,6 +268,11 @@ public class ChunkElementParserTests { "skippableExceptionClassifier"); } + private SkipPolicy getSkipPolicy(String stepName, + ApplicationContext ctx) throws Exception { + return (SkipPolicy) getNestedPathInStep(stepName, ctx, "tasklet.chunkProvider.skipPolicy"); + } + private Map, Boolean> getRetryableExceptionClasses(String stepName, ApplicationContext ctx) throws Exception { return getNestedExceptionMap(stepName, ctx, diff --git a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepParserTests.java b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepParserTests.java index 6082b82e8..8cdcebf35 100644 --- a/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepParserTests.java +++ b/spring-batch-core/src/test/java/org/springframework/batch/core/configuration/xml/StepParserTests.java @@ -40,6 +40,7 @@ import org.springframework.batch.core.repository.support.SimpleJobRepository; import org.springframework.batch.core.step.AbstractStep; import org.springframework.batch.core.step.item.FatalSkippableException; import org.springframework.batch.core.step.item.FatalRuntimeException; +import org.springframework.batch.core.step.item.ForceRollbackForWriteSkipException; import org.springframework.batch.core.step.item.SkippableException; import org.springframework.batch.core.step.item.SkippableRuntimeException; import org.springframework.batch.core.step.tasklet.Tasklet; @@ -428,9 +429,11 @@ public class StepParserTests { skippable.put(SkippableException.class, true); skippable.put(FatalRuntimeException.class, false); skippable.put(FatalSkippableException.class, false); + skippable.put(ForceRollbackForWriteSkipException.class, true); Map, Boolean> retryable = new HashMap, Boolean>(); retryable.put(DeadlockLoserDataAccessException.class, true); retryable.put(FatalSkippableException.class, true); + retryable.put(ForceRollbackForWriteSkipException.class, true); List> streams = Arrays.asList(CompositeItemStream.class, TestReader.class); List> retryListeners = Arrays.asList(RetryListenerSupport.class, DummyRetryListener.class); @@ -464,8 +467,10 @@ public class StepParserTests { Map, Boolean> skippable = new HashMap, Boolean>(); skippable.put(SkippableException.class, true); skippable.put(FatalSkippableException.class, false); + skippable.put(ForceRollbackForWriteSkipException.class, true); Map, Boolean> retryable = new HashMap, Boolean>(); retryable.put(FatalSkippableException.class, true); + retryable.put(ForceRollbackForWriteSkipException.class, true); List> streams = Arrays.asList(CompositeItemStream.class); List> retryListeners = Arrays.asList(DummyRetryListener.class); List> stepListeners = Arrays.asList(CompositeStepExecutionListener.class); @@ -495,8 +500,8 @@ public class StepParserTests { StepParserStepFactoryBean fb = (StepParserStepFactoryBean) ctx .getBean("&stepWithListsOverrideWithEmpty"); - assertEquals(0, getExceptionMap(fb, "skippableExceptionClasses").size()); - assertEquals(0, getExceptionMap(fb, "retryableExceptionClasses").size()); + assertEquals(1, getExceptionMap(fb, "skippableExceptionClasses").size()); + assertEquals(1, getExceptionMap(fb, "retryableExceptionClasses").size()); assertEquals(0, ((ItemStream[]) ReflectionTestUtils.getField(fb, "streams")).length); assertEquals(0, ((RetryListener[]) ReflectionTestUtils.getField(fb, "retryListeners")).length); assertEquals(0, ((StepListener[]) ReflectionTestUtils.getField(fb, "listeners")).length); diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/ChunkElementIllegalSkipAndRetryAttributeParserTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/ChunkElementIllegalSkipAndRetryAttributeParserTests-context.xml new file mode 100644 index 000000000..c815b4c4c --- /dev/null +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/ChunkElementIllegalSkipAndRetryAttributeParserTests-context.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/ChunkElementLateBindingParserTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/ChunkElementLateBindingParserTests-context.xml index bb478be52..a5facdb82 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/ChunkElementLateBindingParserTests-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/ChunkElementLateBindingParserTests-context.xml @@ -7,9 +7,19 @@ - + - + + + + + + + + + + diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/ChunkElementParentAttributeParserTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/ChunkElementParentAttributeParserTests-context.xml index 00ad97896..aef8df686 100644 --- a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/ChunkElementParentAttributeParserTests-context.xml +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/ChunkElementParentAttributeParserTests-context.xml @@ -34,8 +34,8 @@ - - + + diff --git a/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/ChunkElementSkipAndRetryAttributeParserTests-context.xml b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/ChunkElementSkipAndRetryAttributeParserTests-context.xml new file mode 100644 index 000000000..a47e44b13 --- /dev/null +++ b/spring-batch-core/src/test/resources/org/springframework/batch/core/configuration/xml/ChunkElementSkipAndRetryAttributeParserTests-context.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/retry/policy/SimpleRetryPolicy.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/retry/policy/SimpleRetryPolicy.java index e3c8af509..70ba48ec4 100644 --- a/spring-batch-infrastructure/src/main/java/org/springframework/batch/retry/policy/SimpleRetryPolicy.java +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/retry/policy/SimpleRetryPolicy.java @@ -50,7 +50,7 @@ public class SimpleRetryPolicy implements RetryPolicy { private volatile int maxAttempts; - private BinaryExceptionClassifier retryableClassifier = new BinaryExceptionClassifier(false); + private volatile BinaryExceptionClassifier retryableClassifier = new BinaryExceptionClassifier(false); /** * Create a {@link SimpleRetryPolicy} with the default number of retry @@ -74,6 +74,13 @@ public class SimpleRetryPolicy implements RetryPolicy { this.retryableClassifier = new BinaryExceptionClassifier(retryableExceptions); } + /** + * @param retryableExceptions + */ + public void setRetryableExceptions(Map, Boolean> retryableExceptions) { + this.retryableClassifier = new BinaryExceptionClassifier(retryableExceptions); + } + /** * Setter for retry attempts. * @@ -83,7 +90,7 @@ public class SimpleRetryPolicy implements RetryPolicy { public void setMaxAttempts(int retryAttempts) { this.maxAttempts = retryAttempts; } - + /** * The maximum number of retry attempts before failure. *