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));
+ }
+}