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 ab1874479..6deb50c1d 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 @@ -387,7 +387,16 @@ ref" is not required, and only needs to be specified explicitly - + + + + + + @@ -542,7 +551,15 @@ ref" is not required, and only needs to be specified explicitly - + + + + + + diff --git a/spring-batch-infrastructure/src/main/java/org/springframework/batch/retry/policy/CompositeRetryPolicy.java b/spring-batch-infrastructure/src/main/java/org/springframework/batch/retry/policy/CompositeRetryPolicy.java new file mode 100644 index 000000000..183c37347 --- /dev/null +++ b/spring-batch-infrastructure/src/main/java/org/springframework/batch/retry/policy/CompositeRetryPolicy.java @@ -0,0 +1,159 @@ +/* + * 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.retry.policy; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.springframework.batch.retry.RetryContext; +import org.springframework.batch.retry.RetryPolicy; +import org.springframework.batch.retry.context.RetryContextSupport; + +/** + * A {@link RetryPolicy} that composes a list of other policies and delegates + * calls to them in order. + * + * @author Dave Syer + * @author Michael Minella + * + */ +public class CompositeRetryPolicy implements RetryPolicy { + + RetryPolicy[] policies = new RetryPolicy[0]; + private boolean optimistic = false; + + /** + * Setter for the optimistic flag. + * + * @param optimistic + */ + public void setOptimistic(boolean optimistic) { + this.optimistic = optimistic; + } + + /** + * Setter for policies. + * + * @param policies + */ + public void setPolicies(RetryPolicy[] policies) { + this.policies = Arrays.asList(policies).toArray(new RetryPolicy[policies.length]); + } + + /** + * Delegate to the policies that were in operation when the context was + * created. By default, if any of them cannot retry then return false, otherwise return + * true. If the optimistic flag has been set to true and any of them can retry + * return true, otherwise return false. + * + * @see org.springframework.batch.retry.RetryPolicy#canRetry(org.springframework.batch.retry.RetryContext) + */ + public boolean canRetry(RetryContext context) { + RetryContext[] contexts = ((CompositeRetryContext) context).contexts; + RetryPolicy[] policies = ((CompositeRetryContext) context).policies; + + boolean retryable = true; + + if(optimistic) { + retryable = false; + for (int i = 0; i < contexts.length; i++) { + if (policies[i].canRetry(contexts[i])) { + retryable = true; + } + } + } + else { + for (int i = 0; i < contexts.length; i++) { + if (!policies[i].canRetry(contexts[i])) { + retryable = false; + } + } + } + + return retryable; + } + + /** + * Delegate to the policies that were in operation when the context was + * created. If any of them fails to close the exception is propagated (and + * those later in the chain are closed before re-throwing). + * + * @see org.springframework.batch.retry.RetryPolicy#close(org.springframework.batch.retry.RetryContext) + */ + public void close(RetryContext context) { + RetryContext[] contexts = ((CompositeRetryContext) context).contexts; + RetryPolicy[] policies = ((CompositeRetryContext) context).policies; + RuntimeException exception = null; + for (int i = 0; i < contexts.length; i++) { + try { + policies[i].close(contexts[i]); + } + catch (RuntimeException e) { + if (exception == null) { + exception = e; + } + } + } + if (exception != null) { + throw exception; + } + } + + /** + * Creates a new context that copies the existing policies and keeps a list + * of the contexts from each one. + * + * @see org.springframework.batch.retry.RetryPolicy#open(RetryContext) + */ + public RetryContext open(RetryContext parent) { + List list = new ArrayList(); + for (int i = 0; i < policies.length; i++) { + list.add(policies[i].open(parent)); + } + return new CompositeRetryContext(parent, list); + } + + /** + * Delegate to the policies that were in operation when the context was + * created. + * + * @see org.springframework.batch.retry.RetryPolicy#close(org.springframework.batch.retry.RetryContext) + */ + public void registerThrowable(RetryContext context, Throwable throwable) { + RetryContext[] contexts = ((CompositeRetryContext) context).contexts; + RetryPolicy[] policies = ((CompositeRetryContext) context).policies; + for (int i = 0; i < contexts.length; i++) { + policies[i].registerThrowable(contexts[i], throwable); + } + ((RetryContextSupport) context).registerThrowable(throwable); + } + + private class CompositeRetryContext extends RetryContextSupport { + RetryContext[] contexts; + + RetryPolicy[] policies; + + public CompositeRetryContext(RetryContext parent, List contexts) { + super(parent); + this.contexts = contexts.toArray(new RetryContext[0]); + this.policies = CompositeRetryPolicy.this.policies; + } + + } + +} diff --git a/spring-batch-infrastructure/src/test/java/org/springframework/batch/retry/policy/CompositeRetryPolicyTests.java b/spring-batch-infrastructure/src/test/java/org/springframework/batch/retry/policy/CompositeRetryPolicyTests.java new file mode 100644 index 000000000..0f146139e --- /dev/null +++ b/spring-batch-infrastructure/src/test/java/org/springframework/batch/retry/policy/CompositeRetryPolicyTests.java @@ -0,0 +1,150 @@ +/* + * 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.retry.policy; + +import java.util.ArrayList; +import java.util.List; + +import junit.framework.TestCase; + +import org.springframework.batch.retry.RetryContext; +import org.springframework.batch.retry.RetryPolicy; + +public class CompositeRetryPolicyTests extends TestCase { + + public void testEmptyPolicies() throws Exception { + CompositeRetryPolicy policy = new CompositeRetryPolicy(); + RetryContext context = policy.open(null); + assertNotNull(context); + assertTrue(policy.canRetry(context)); + } + + public void testTrivialPolicies() throws Exception { + CompositeRetryPolicy policy = new CompositeRetryPolicy(); + policy.setPolicies(new RetryPolicy[] { new MockRetryPolicySupport(), new MockRetryPolicySupport() }); + RetryContext context = policy.open(null); + assertNotNull(context); + assertTrue(policy.canRetry(context)); + } + + public void testNonTrivialPolicies() throws Exception { + CompositeRetryPolicy policy = new CompositeRetryPolicy(); + policy.setPolicies(new RetryPolicy[] { new MockRetryPolicySupport(), new MockRetryPolicySupport() { + public boolean canRetry(RetryContext context) { + return false; + } + } }); + RetryContext context = policy.open(null); + assertNotNull(context); + assertFalse(policy.canRetry(context)); + } + + public void testNonTrivialPoliciesWithThrowable() throws Exception { + CompositeRetryPolicy policy = new CompositeRetryPolicy(); + policy.setPolicies(new RetryPolicy[] { new MockRetryPolicySupport(), new MockRetryPolicySupport() { + boolean errorRegistered = false; + + public boolean canRetry(RetryContext context) { + return !errorRegistered; + } + + public void registerThrowable(RetryContext context, Throwable throwable) { + errorRegistered = true; + } + } }); + RetryContext context = policy.open(null); + assertNotNull(context); + assertTrue(policy.canRetry(context)); + policy.registerThrowable(context, null); + assertFalse("Should be still able to retry", policy.canRetry(context)); + } + + public void testNonTrivialPoliciesClose() throws Exception { + final List list = new ArrayList(); + CompositeRetryPolicy policy = new CompositeRetryPolicy(); + policy.setPolicies(new RetryPolicy[] { new MockRetryPolicySupport() { + public void close(RetryContext context) { + list.add("1"); + } + }, new MockRetryPolicySupport() { + public void close(RetryContext context) { + list.add("2"); + } + } }); + RetryContext context = policy.open(null); + assertNotNull(context); + policy.close(context); + assertEquals(2, list.size()); + } + + public void testExceptionOnPoliciesClose() throws Exception { + final List list = new ArrayList(); + CompositeRetryPolicy policy = new CompositeRetryPolicy(); + policy.setPolicies(new RetryPolicy[] { new MockRetryPolicySupport() { + public void close(RetryContext context) { + list.add("1"); + throw new RuntimeException("Pah!"); + } + }, new MockRetryPolicySupport() { + public void close(RetryContext context) { + list.add("2"); + } + } }); + RetryContext context = policy.open(null); + assertNotNull(context); + try { + policy.close(context); + fail("Expected RuntimeException"); + } catch (RuntimeException e) { + assertEquals("Pah!", e.getMessage()); + } + assertEquals(2, list.size()); + } + + public void testRetryCount() throws Exception { + CompositeRetryPolicy policy = new CompositeRetryPolicy(); + policy.setPolicies(new RetryPolicy[] { new MockRetryPolicySupport(), new MockRetryPolicySupport() }); + RetryContext context = policy.open(null); + assertNotNull(context); + policy.registerThrowable(context, null); + assertEquals(0, context.getRetryCount()); + policy.registerThrowable(context, new RuntimeException("foo")); + assertEquals(1, context.getRetryCount()); + assertEquals("foo", context.getLastThrowable().getMessage()); + } + + public void testParent() throws Exception { + CompositeRetryPolicy policy = new CompositeRetryPolicy(); + RetryContext context = policy.open(null); + RetryContext child = policy.open(context); + assertNotSame(child, context); + assertSame(context, child.getParent()); + } + + public void testOptimistic() throws Exception { + CompositeRetryPolicy policy = new CompositeRetryPolicy(); + policy.setOptimistic(true); + policy.setPolicies(new RetryPolicy[] { new MockRetryPolicySupport() { + public boolean canRetry(RetryContext context) { + return false; + } + }, new MockRetryPolicySupport() }); + RetryContext context = policy.open(null); + assertNotNull(context); + assertTrue(policy.canRetry(context)); + } +}