Parameterize exception type in RetryCallback

So RetryCallback<T, E extends Throwable> and the E parameter appears
in RetryOperations too, making it possible to call it with an unchecked
exception type in the parameter and not catch exceptions.

Users should beware: it's just syntactic sugar, and the actual runtime
type of the exception is never checked at runtime. So, for instance,
declaring a RetryCallback<Object,IllegalArgumentException> doesn't
mean that other Exceptions won't be retried, just that you won't be
able to explicitly throw them if they are checked.

A project using Spring Batch 2.2 was used to test that this works
with user code that uses a library compiled agains Spring Retry 1.0.

Fixes gh-6
This commit is contained in:
Dave Syer
2014-04-24 12:10:40 +01:00
parent 9fb92bccbd
commit 5fc484df58
14 changed files with 149 additions and 105 deletions

View File

@@ -23,7 +23,7 @@ package org.springframework.retry;
* @author Rob Harrop
* @author Dave Syer
*/
public interface RetryCallback<T> {
public interface RetryCallback<T, E extends Throwable> {
/**
* Execute an operation with retry semantics. Operations should generally be
@@ -33,5 +33,5 @@ public interface RetryCallback<T> {
* @return the result of the successful operation.
* @throws Exception if processing fails
*/
T doWithRetry(RetryContext context) throws Throwable;
T doWithRetry(RetryContext context) throws E;
}

View File

@@ -38,7 +38,7 @@ public interface RetryListener {
* @param callback the current {@link RetryCallback}.
* @return true if the retry should proceed.
*/
<T> boolean open(RetryContext context, RetryCallback<T> callback);
<T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback);
/**
* Called after the final attempt (successful or not). Allow the interceptor
@@ -49,7 +49,7 @@ public interface RetryListener {
* @param callback the current {@link RetryCallback}.
* @param throwable the last exception that was thrown by the callback.
*/
<T> void close(RetryContext context, RetryCallback<T> callback, Throwable throwable);
<T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable);
/**
* Called after every unsuccessful attempt at a retry.
@@ -58,5 +58,5 @@ public interface RetryListener {
* @param callback the current {@link RetryCallback}.
* @param throwable the last exception that was thrown by the callback.
*/
<T> void onError(RetryContext context, RetryCallback<T> callback, Throwable throwable);
<T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable);
}

View File

@@ -37,7 +37,7 @@ public interface RetryOperations {
* {@link RetryCallback} upon unsuccessful retry.
* @throws Throwable
*/
<T> T execute(RetryCallback<T> retryCallback) throws Throwable;
<T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback) throws E;
/**
* Execute the supplied {@link RetryCallback} with a fallback on exhausted
@@ -49,7 +49,7 @@ public interface RetryOperations {
* @throws Exception any {@link Exception} raised by the
* {@link RecoveryCallback} upon unsuccessful retry.
*/
<T> T execute(RetryCallback<T> retryCallback, RecoveryCallback<T> recoveryCallback) throws Throwable;
<T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback, RecoveryCallback<T> recoveryCallback) throws E;
/**
* A simple stateful retry. Execute the supplied {@link RetryCallback} with
@@ -69,7 +69,7 @@ public interface RetryOperations {
* @throws ExhaustedRetryException if the last attempt for this state has
* already been reached
*/
<T> T execute(RetryCallback<T> retryCallback, RetryState retryState) throws Throwable, ExhaustedRetryException;
<T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback, RetryState retryState) throws E, ExhaustedRetryException;
/**
* A stateful retry with a recovery path. Execute the supplied
@@ -84,7 +84,7 @@ public interface RetryOperations {
* @throws Exception any {@link Exception} raised by the
* {@link RecoveryCallback} upon unsuccessful retry.
*/
<T> T execute(RetryCallback<T> retryCallback, RecoveryCallback<T> recoveryCallback, RetryState retryState)
throws Throwable;
<T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback, RecoveryCallback<T> recoveryCallback, RetryState retryState)
throws E;
}

View File

@@ -57,7 +57,7 @@ public class RetryOperationsInterceptor implements MethodInterceptor {
public Object invoke(final MethodInvocation invocation) throws Throwable {
RetryCallback<Object> retryCallback = new RetryCallback<Object>() {
RetryCallback<Object, Throwable> retryCallback = new RetryCallback<Object, Throwable>() {
public Object doWithRetry(RetryContext context) throws Exception {

View File

@@ -156,7 +156,7 @@ public class StatefulRetryOperationsInterceptor implements MethodInterceptor {
*
*/
private static final class MethodInvocationRetryCallback implements
RetryCallback<Object> {
RetryCallback<Object, Throwable> {
/**
*
*/

View File

@@ -28,13 +28,13 @@ import org.springframework.retry.RetryListener;
*/
public class RetryListenerSupport implements RetryListener {
public <T> void close(RetryContext context, RetryCallback<T> callback, Throwable throwable) {
public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
}
public <T> void onError(RetryContext context, RetryCallback<T> callback, Throwable throwable) {
public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
}
public <T> boolean open(RetryContext context, RetryCallback<T> callback) {
public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
return true;
}

View File

@@ -96,7 +96,7 @@ public class RetrySimulator {
return stealingSleeper.getSleeps();
}
static class FailingRetryCallback implements RetryCallback<Object> {
static class FailingRetryCallback implements RetryCallback<Object, Exception> {
public Object doWithRetry(RetryContext context) throws Exception {
throw new FailingRetryException();
}

View File

@@ -150,7 +150,7 @@ public class RetryTemplate implements RetryOperations {
* @throws TerminatedRetryException if the retry has been manually terminated by a
* listener.
*/
public final <T> T execute(RetryCallback<T> retryCallback) throws Throwable {
public final <T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback) throws E {
return doExecute(retryCallback, null, null);
}
@@ -163,8 +163,8 @@ public class RetryTemplate implements RetryOperations {
* @throws TerminatedRetryException if the retry has been manually terminated by a
* listener.
*/
public final <T> T execute(RetryCallback<T> retryCallback,
RecoveryCallback<T> recoveryCallback) throws Throwable {
public final <T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback,
RecoveryCallback<T> recoveryCallback) throws E {
return doExecute(retryCallback, recoveryCallback, null);
}
@@ -176,8 +176,8 @@ public class RetryTemplate implements RetryOperations {
*
* @throws ExhaustedRetryException if the retry has been exhausted.
*/
public final <T> T execute(RetryCallback<T> retryCallback, RetryState retryState)
throws Throwable, ExhaustedRetryException {
public final <T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback, RetryState retryState)
throws E, ExhaustedRetryException {
return doExecute(retryCallback, null, retryState);
}
@@ -187,9 +187,9 @@ public class RetryTemplate implements RetryOperations {
*
* @see RetryOperations#execute(RetryCallback, RetryState)
*/
public final <T> T execute(RetryCallback<T> retryCallback,
public final <T, E extends Throwable> T execute(RetryCallback<T, E> retryCallback,
RecoveryCallback<T> recoveryCallback, RetryState retryState)
throws Throwable, ExhaustedRetryException {
throws E, ExhaustedRetryException {
return doExecute(retryCallback, recoveryCallback, retryState);
}
@@ -198,10 +198,11 @@ public class RetryTemplate implements RetryOperations {
* recovery callback.
*
* @see RetryOperations#execute(RetryCallback, RecoveryCallback, RetryState)
* @throws ExhaustedRetryException if the retry has been exhausted.
* @throws ExhaustedRetryException if the retry has been exhausted. finally {
*/
protected <T> T doExecute(RetryCallback<T> retryCallback,
RecoveryCallback<T> recoveryCallback, RetryState state) throws Throwable,
protected <T, E extends Throwable> T doExecute(RetryCallback<T, E> retryCallback,
RecoveryCallback<T> recoveryCallback, RetryState state) throws E,
ExhaustedRetryException {
RetryPolicy retryPolicy = this.retryPolicy;
@@ -290,7 +291,9 @@ public class RetryTemplate implements RetryOperations {
if (shouldRethrow(retryPolicy, context, state)) {
logger.debug("Rethrow in retry for policy: count="
+ context.getRetryCount());
throw wrapIfNecessary(e);
@SuppressWarnings("unchecked")
E rethrow = (E) wrapIfNecessary(e);
throw rethrow;
}
}
@@ -311,7 +314,12 @@ public class RetryTemplate implements RetryOperations {
return handleRetryExhausted(recoveryCallback, context, state);
} finally {
} catch (Throwable e) {
@SuppressWarnings("unchecked")
E rethrow = (E) wrapIfNecessary(e);
throw rethrow;
}
finally {
close(retryPolicy, context, state, lastException == null);
doCloseInterceptors(retryCallback, context, lastException);
RetrySynchronizationManager.clear();
@@ -451,9 +459,11 @@ public class RetryTemplate implements RetryOperations {
throw wrapIfNecessary(context.getLastThrowable());
}
protected void rethrow(RetryContext context, String message) throws Throwable {
protected <E extends Throwable> void rethrow(RetryContext context, String message) throws E {
if (throwLastExceptionOnExhausted) {
throw (Throwable) context.getLastThrowable();
@SuppressWarnings("unchecked")
E rethrow = (E) context.getLastThrowable();
throw rethrow;
} else {
throw new ExhaustedRetryException(message, context.getLastThrowable());
}
@@ -478,7 +488,7 @@ public class RetryTemplate implements RetryOperations {
}
}
private <T> boolean doOpenInterceptors(RetryCallback<T> callback, RetryContext context) {
private <T, E extends Throwable> boolean doOpenInterceptors(RetryCallback<T, E> callback, RetryContext context) {
boolean result = true;
@@ -490,14 +500,14 @@ public class RetryTemplate implements RetryOperations {
}
private <T> void doCloseInterceptors(RetryCallback<T> callback, RetryContext context,
private <T, E extends Throwable> void doCloseInterceptors(RetryCallback<T, E> callback, RetryContext context,
Throwable lastException) {
for (int i = listeners.length; i-- > 0;) {
listeners[i].close(context, callback, lastException);
}
}
private <T> void doOnErrorInterceptors(RetryCallback<T> callback,
private <T, E extends Throwable> void doOnErrorInterceptors(RetryCallback<T, E> callback,
RetryContext context, Throwable throwable) {
for (int i = listeners.length; i-- > 0;) {
listeners[i].onError(context, callback, throwable);
@@ -508,13 +518,15 @@ public class RetryTemplate implements RetryOperations {
* Re-throws the original throwable if it is unchecked, wraps checked exceptions into
* {@link RetryException}.
*/
private static Exception wrapIfNecessary(Throwable throwable) {
private static <E extends Throwable> E wrapIfNecessary(Throwable throwable) throws RetryException {
if (throwable instanceof Error) {
throw (Error) throwable;
} else if (throwable instanceof Exception) {
return (Exception) throwable;
@SuppressWarnings("unchecked")
E rethrow = (E) throwable;
return rethrow;
} else {
return new RetryException("Exception in batch process", throwable);
throw new RetryException("Exception in batch process", throwable);
}
}

View File

@@ -42,19 +42,19 @@ public class RetryListenerTests {
@Test
public void testOpenInterceptors() throws Throwable {
template.setListeners(new RetryListener[] { new RetryListenerSupport() {
public <T> boolean open(RetryContext context, RetryCallback<T> callback) {
public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
count++;
list.add("1:" + count);
return true;
}
}, new RetryListenerSupport() {
public <T> boolean open(RetryContext context, RetryCallback<T> callback) {
public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
count++;
list.add("2:" + count);
return true;
}
} });
template.execute(new RetryCallback<String>() {
template.execute(new RetryCallback<String, Exception>() {
public String doWithRetry(RetryContext context) throws Exception {
return null;
}
@@ -67,13 +67,13 @@ public class RetryListenerTests {
@Test
public void testOpenCanVetoRetry() throws Throwable {
template.registerListener(new RetryListenerSupport() {
public <T> boolean open(RetryContext context, RetryCallback<T> callback) {
public <T, E extends Throwable> boolean open(RetryContext context, RetryCallback<T, E> callback) {
list.add("1");
return false;
}
});
try {
template.execute(new RetryCallback<String>() {
template.execute(new RetryCallback<String, Exception>() {
public String doWithRetry(RetryContext context) throws Exception {
count++;
return null;
@@ -92,17 +92,17 @@ public class RetryListenerTests {
@Test
public void testCloseInterceptors() throws Throwable {
template.setListeners(new RetryListener[] { new RetryListenerSupport() {
public <T> void close(RetryContext context, RetryCallback<T> callback, Throwable t) {
public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable t) {
count++;
list.add("1:" + count);
}
}, new RetryListenerSupport() {
public <T> void close(RetryContext context, RetryCallback<T> callback, Throwable t) {
public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable t) {
count++;
list.add("2:" + count);
}
} });
template.execute(new RetryCallback<String>() {
template.execute(new RetryCallback<String, Exception>() {
public String doWithRetry(RetryContext context) throws Exception {
return null;
}
@@ -117,16 +117,16 @@ public class RetryListenerTests {
public void testOnError() throws Throwable {
template.setRetryPolicy(new NeverRetryPolicy());
template.setListeners(new RetryListener[] { new RetryListenerSupport() {
public <T> void onError(RetryContext context, RetryCallback<T> callback, Throwable throwable) {
public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
list.add("1");
}
}, new RetryListenerSupport() {
public <T> void onError(RetryContext context, RetryCallback<T> callback, Throwable throwable) {
public <T, E extends Throwable> void onError(RetryContext context, RetryCallback<T, E> callback, Throwable throwable) {
list.add("2");
}
} });
try {
template.execute(new RetryCallback<String>() {
template.execute(new RetryCallback<String, Exception>() {
public String doWithRetry(RetryContext context) throws Exception {
count++;
throw new IllegalStateException("foo");
@@ -148,13 +148,13 @@ public class RetryListenerTests {
@Test
public void testCloseInterceptorsAfterRetry() throws Throwable {
template.registerListener(new RetryListenerSupport() {
public <T> void close(RetryContext context, RetryCallback<T> callback, Throwable t) {
public <T, E extends Throwable> void close(RetryContext context, RetryCallback<T, E> callback, Throwable t) {
list.add("" + count);
// The last attempt should have been successful:
assertNull(t);
}
});
template.execute(new RetryCallback<String>() {
template.execute(new RetryCallback<String, Exception>() {
public String doWithRetry(RetryContext context) throws Exception {
if (count++ < 1)
throw new RuntimeException("Retry!");

View File

@@ -100,7 +100,7 @@ public class FatalExceptionRetryPolicyTests {
assertEquals("bar", result);
}
private static class MockRetryCallback implements RetryCallback<String> {
private static class MockRetryCallback implements RetryCallback<String, Exception> {
private int attempts;

View File

@@ -130,7 +130,7 @@ public class StatefulRetryIntegrationTests {
RetryState retryState = new DefaultRetryState("bar");
for (int i = 0; i < 3; i++) {
try {
template.execute(new RetryCallback<String>() {
template.execute(new RetryCallback<String, Exception>() {
public String doWithRetry(RetryContext context) throws Exception {
times.add(System.currentTimeMillis());
throw new Exception("Fail");
@@ -155,7 +155,7 @@ public class StatefulRetryIntegrationTests {
* @author Dave Syer
*
*/
private static final class MockRetryCallback implements RetryCallback<String> {
private static final class MockRetryCallback implements RetryCallback<String, Exception> {
int attempts = 0;
public String doWithRetry(RetryContext context) throws Exception {

View File

@@ -47,7 +47,7 @@ public class RetrySynchronizationManagerTests {
RetryContext status = RetrySynchronizationManager.getContext();
assertNull(status);
template.execute(new RetryCallback<Object>() {
template.execute(new RetryCallback<Object, Exception>() {
public Object doWithRetry(RetryContext status) throws Exception {
RetryContext global = RetrySynchronizationManager.getContext();
assertNotNull(status);

View File

@@ -28,6 +28,7 @@ import static org.junit.Assert.assertSame;
import static org.junit.Assert.fail;
import java.util.Collections;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Test;
import org.springframework.classify.BinaryExceptionClassifier;
@@ -60,19 +61,48 @@ public class RetryTemplateTests {
callback.setAttemptsBeforeSuccess(x);
RetryTemplate retryTemplate = new RetryTemplate();
retryTemplate.setRetryPolicy(new SimpleRetryPolicy(x, Collections
.<Class<? extends Throwable>, Boolean> singletonMap(Exception.class, true)));
.<Class<? extends Throwable>, Boolean> singletonMap(Exception.class,
true)));
retryTemplate.execute(callback);
assertEquals(x, callback.attempts);
}
}
@Test
public void testSpecificExceptionRetry() throws Throwable {
for (int x = 1; x <= 10; x++) {
final int attemptsBeforeSuccess = x;
final AtomicInteger attempts = new AtomicInteger(0);
RetryCallback<String, IllegalStateException> callback = new RetryCallback<String, IllegalStateException>() {
@Override
public String doWithRetry(RetryContext context)
throws IllegalStateException {
if (attempts.incrementAndGet() < attemptsBeforeSuccess) {
// The parametrized exception type in the callback is really just
// syntactic sugar since rules of erasure mean that the handler
// can't really tell the difference between runtime exceptions.
throw new IllegalArgumentException("Planned");
}
return "foo";
}
};
RetryTemplate retryTemplate = new RetryTemplate();
retryTemplate.setRetryPolicy(new SimpleRetryPolicy(x, Collections
.<Class<? extends Throwable>, Boolean> singletonMap(Exception.class,
true)));
retryTemplate.execute(callback);
assertEquals(x, attempts.get());
}
}
@Test
public void testSuccessfulRecovery() throws Throwable {
MockRetryCallback callback = new MockRetryCallback();
callback.setAttemptsBeforeSuccess(3);
RetryTemplate retryTemplate = new RetryTemplate();
retryTemplate.setRetryPolicy(new SimpleRetryPolicy(2, Collections
.<Class<? extends Throwable>, Boolean> singletonMap(Exception.class, true)));
retryTemplate.setRetryPolicy(new SimpleRetryPolicy(2,
Collections.<Class<? extends Throwable>, Boolean> singletonMap(
Exception.class, true)));
final Object value = new Object();
Object result = retryTemplate.execute(callback, new RecoveryCallback<Object>() {
public Object recover(RetryContext context) throws Exception {
@@ -100,13 +130,13 @@ public class RetryTemplateTests {
callback.setAttemptsBeforeSuccess(Integer.MAX_VALUE);
RetryTemplate retryTemplate = new RetryTemplate();
int retryAttempts = 2;
retryTemplate.setRetryPolicy(new SimpleRetryPolicy(retryAttempts, Collections
.<Class<? extends Throwable>, Boolean> singletonMap(Exception.class, true)));
retryTemplate.setRetryPolicy(new SimpleRetryPolicy(retryAttempts,
Collections.<Class<? extends Throwable>, Boolean> singletonMap(
Exception.class, true)));
try {
retryTemplate.execute(callback);
fail("Expected IllegalArgumentException");
}
catch (IllegalArgumentException e) {
} catch (IllegalArgumentException e) {
assertNotNull(e);
assertEquals(retryAttempts, callback.attempts);
return;
@@ -122,8 +152,9 @@ public class RetryTemplateTests {
callback.setExceptionToThrow(new IllegalArgumentException());
RetryTemplate retryTemplate = new RetryTemplate();
retryTemplate.setRetryPolicy(new SimpleRetryPolicy(attempts, Collections
.<Class<? extends Throwable>, Boolean> singletonMap(Exception.class, true)));
retryTemplate.setRetryPolicy(new SimpleRetryPolicy(attempts,
Collections.<Class<? extends Throwable>, Boolean> singletonMap(
Exception.class, true)));
retryTemplate.execute(callback);
assertEquals(attempts, callback.attempts);
}
@@ -136,10 +167,13 @@ public class RetryTemplateTests {
callback.setExceptionToThrow(new IllegalArgumentException());
RetryTemplate retryTemplate = new RetryTemplate();
retryTemplate.setRetryPolicy(new SimpleRetryPolicy(attempts, Collections
.<Class<? extends Throwable>, Boolean> singletonMap(Exception.class, true)));
BinaryExceptionClassifier classifier = new BinaryExceptionClassifier(Collections
.<Class<? extends Throwable>> singleton(IllegalArgumentException.class), false);
retryTemplate.setRetryPolicy(new SimpleRetryPolicy(attempts,
Collections.<Class<? extends Throwable>, Boolean> singletonMap(
Exception.class, true)));
BinaryExceptionClassifier classifier = new BinaryExceptionClassifier(
Collections
.<Class<? extends Throwable>> singleton(IllegalArgumentException.class),
false);
retryTemplate.execute(callback, new DefaultRetryState("foo", classifier));
assertEquals(attempts, callback.attempts);
}
@@ -147,8 +181,9 @@ public class RetryTemplateTests {
@Test
public void testSetExceptions() throws Throwable {
RetryTemplate template = new RetryTemplate();
SimpleRetryPolicy policy = new SimpleRetryPolicy(3, Collections
.<Class<? extends Throwable>, Boolean> singletonMap(RuntimeException.class, true));
SimpleRetryPolicy policy = new SimpleRetryPolicy(3,
Collections.<Class<? extends Throwable>, Boolean> singletonMap(
RuntimeException.class, true));
template.setRetryPolicy(policy);
int attempts = 3;
@@ -158,8 +193,7 @@ public class RetryTemplateTests {
try {
template.execute(callback);
}
catch (Exception e) {
} catch (Exception e) {
assertNotNull(e);
assertEquals(1, callback.attempts);
}
@@ -178,7 +212,8 @@ public class RetryTemplateTests {
RetryTemplate retryTemplate = new RetryTemplate();
retryTemplate.setBackOffPolicy(backOff);
retryTemplate.setRetryPolicy(new SimpleRetryPolicy(x, Collections
.<Class<? extends Throwable>, Boolean> singletonMap(Exception.class, true)));
.<Class<? extends Throwable>, Boolean> singletonMap(Exception.class,
true)));
retryTemplate.execute(callback);
assertEquals(x, callback.attempts);
assertEquals(1, backOff.startCalls);
@@ -190,15 +225,14 @@ public class RetryTemplateTests {
public void testEarlyTermination() throws Throwable {
try {
RetryTemplate retryTemplate = new RetryTemplate();
retryTemplate.execute(new RetryCallback<Object>() {
retryTemplate.execute(new RetryCallback<Object, Exception>() {
public Object doWithRetry(RetryContext status) throws Exception {
status.setExhaustedOnly();
throw new IllegalStateException("Retry this operation");
}
});
fail("Expected ExhaustedRetryException");
}
catch (ExhaustedRetryException ex) {
} catch (ExhaustedRetryException ex) {
// Expected for internal retry policy (external would recover
// gracefully)
assertEquals("Retry this operation", ex.getCause().getMessage());
@@ -210,15 +244,14 @@ public class RetryTemplateTests {
try {
RetryTemplate retryTemplate = new RetryTemplate();
retryTemplate.setThrowLastExceptionOnExhausted(true);
retryTemplate.execute(new RetryCallback<Object>() {
retryTemplate.execute(new RetryCallback<Object, Throwable>() {
public Object doWithRetry(RetryContext status) throws Exception {
status.setExhaustedOnly();
throw new IllegalStateException("Retry this operation");
}
});
fail("Expected ExhaustedRetryException");
}
catch (IllegalStateException ex) {
} catch (IllegalStateException ex) {
// Expected for internal retry policy (external would recover
// gracefully)
assertEquals("Retry this operation", ex.getMessage());
@@ -229,21 +262,23 @@ public class RetryTemplateTests {
public void testNestedContexts() throws Throwable {
RetryTemplate outer = new RetryTemplate();
final RetryTemplate inner = new RetryTemplate();
outer.execute(new RetryCallback<Object>() {
outer.execute(new RetryCallback<Object, Throwable>() {
public Object doWithRetry(RetryContext status) throws Throwable {
context = status;
count++;
Object result = inner.execute(new RetryCallback<Object>() {
Object result = inner.execute(new RetryCallback<Object, Throwable>() {
public Object doWithRetry(RetryContext status) throws Throwable {
count++;
assertNotNull(context);
assertNotSame(status, context);
assertSame(context, status.getParent());
assertSame("The context should be the child", status, RetrySynchronizationManager.getContext());
assertSame("The context should be the child", status,
RetrySynchronizationManager.getContext());
return null;
}
});
assertSame("The context should be restored", status, RetrySynchronizationManager.getContext());
assertSame("The context should be restored", status,
RetrySynchronizationManager.getContext());
return result;
}
});
@@ -255,14 +290,13 @@ public class RetryTemplateTests {
RetryTemplate retryTemplate = new RetryTemplate();
retryTemplate.setRetryPolicy(new NeverRetryPolicy());
try {
retryTemplate.execute(new RetryCallback<Object>() {
retryTemplate.execute(new RetryCallback<Object, Exception>() {
public Object doWithRetry(RetryContext context) throws Exception {
throw new Error("Realllly bad!");
}
});
fail("Expected Error");
}
catch (Error e) {
} catch (Error e) {
assertEquals("Realllly bad!", e.getMessage());
}
}
@@ -277,14 +311,13 @@ public class RetryTemplateTests {
}
});
try {
retryTemplate.execute(new RetryCallback<Object>() {
retryTemplate.execute(new RetryCallback<Object, Exception>() {
public Object doWithRetry(RetryContext context) throws Exception {
throw new RuntimeException("Realllly bad!");
}
});
fail("Expected Error");
}
catch (TerminatedRetryException e) {
} catch (TerminatedRetryException e) {
assertEquals("Planned", e.getCause().getMessage());
}
}
@@ -298,28 +331,27 @@ public class RetryTemplateTests {
}
});
try {
retryTemplate.execute(new RetryCallback<Object>() {
retryTemplate.execute(new RetryCallback<Object, Exception>() {
public Object doWithRetry(RetryContext context) throws Exception {
throw new RuntimeException("Bad!");
}
});
fail("Expected RuntimeException");
}
catch (BackOffInterruptedException e) {
} catch (BackOffInterruptedException e) {
assertEquals("foo", e.getMessage());
}
}
/**
* {@link BackOffPolicy} should apply also for exceptions that are
* re-thrown.
* {@link BackOffPolicy} should apply also for exceptions that are re-thrown.
*/
@Test
public void testNoBackOffForRethrownException() throws Throwable {
RetryTemplate tested = new RetryTemplate();
tested.setRetryPolicy(new SimpleRetryPolicy(1, Collections.<Class<? extends Throwable>, Boolean> singletonMap(
Exception.class, true)));
tested.setRetryPolicy(new SimpleRetryPolicy(1,
Collections.<Class<? extends Throwable>, Boolean> singletonMap(
Exception.class, true)));
BackOffPolicy bop = createStrictMock(BackOffPolicy.class);
BackOffContext backOffContext = new BackOffContext() {
@@ -330,7 +362,7 @@ public class RetryTemplateTests {
replay(bop);
try {
tested.execute(new RetryCallback<Object>() {
tested.execute(new RetryCallback<Object, Exception>() {
public Object doWithRetry(RetryContext context) throws Exception {
throw new Exception("maybe next time!");
@@ -345,15 +377,14 @@ public class RetryTemplateTests {
});
fail();
}
catch (Exception expected) {
} catch (Exception expected) {
assertEquals("maybe next time!", expected.getMessage());
}
verify(bop);
}
private static class MockRetryCallback implements RetryCallback<Object> {
private static class MockRetryCallback implements RetryCallback<Object, Exception> {
private int attempts;
@@ -389,7 +420,8 @@ public class RetryTemplateTests {
return null;
}
public void backOff(BackOffContext backOffContext) throws BackOffInterruptedException {
public void backOff(BackOffContext backOffContext)
throws BackOffInterruptedException {
backOffCalls++;
}
}

View File

@@ -86,7 +86,7 @@ public class StatefulRecoveryRetryTests {
.<Class<? extends Throwable>, Boolean> singletonMap(Exception.class, true)));
final String input = "foo";
RetryState state = new DefaultRetryState(input);
RetryCallback<String> callback = new RetryCallback<String>() {
RetryCallback<String, Exception> callback = new RetryCallback<String, Exception>() {
public String doWithRetry(RetryContext context) throws Exception {
throw new RuntimeException("Barf!");
}
@@ -124,7 +124,7 @@ public class StatefulRecoveryRetryTests {
assertFalse(classifier.classify(new RuntimeException()));
final String input = "foo";
RetryState state = new DefaultRetryState(input, classifier);
RetryCallback<String> callback = new RetryCallback<String>() {
RetryCallback<String, Exception> callback = new RetryCallback<String, Exception>() {
public String doWithRetry(RetryContext context) throws Exception {
throw new RuntimeException("Barf!");
}
@@ -152,7 +152,7 @@ public class StatefulRecoveryRetryTests {
final String input = "foo";
RetryState state = new DefaultRetryState(input);
RetryCallback<String> callback = new RetryCallback<String>() {
RetryCallback<String, Exception> callback = new RetryCallback<String, Exception>() {
public String doWithRetry(RetryContext context) throws Exception {
throw new RuntimeException("Barf!");
}
@@ -188,7 +188,7 @@ public class StatefulRecoveryRetryTests {
final StringHolder item = new StringHolder("bar");
RetryState state = new DefaultRetryState(item);
RetryCallback<StringHolder> callback = new RetryCallback<StringHolder>() {
RetryCallback<StringHolder, Exception> callback = new RetryCallback<StringHolder, Exception>() {
public StringHolder doWithRetry(RetryContext context) throws Exception {
// This simulates what happens if someone uses a primary key
// for hashCode and equals and then relies on default key
@@ -231,7 +231,7 @@ public class StatefulRecoveryRetryTests {
.<Class<? extends Throwable>, Boolean> singletonMap(Exception.class, true)));
retryTemplate.setRetryContextCache(new MapRetryContextCache(1));
RetryCallback<Object> callback = new RetryCallback<Object>() {
RetryCallback<Object, Exception> callback = new RetryCallback<Object, Exception>() {
public Object doWithRetry(RetryContext context) throws Exception {
count++;
throw new RuntimeException("Barf!");
@@ -266,7 +266,7 @@ public class StatefulRecoveryRetryTests {
final StringHolder item = new StringHolder("foo");
RetryState state = new DefaultRetryState(item);
RetryCallback<Object> callback = new RetryCallback<Object>() {
RetryCallback<Object, Exception> callback = new RetryCallback<Object, Exception>() {
public Object doWithRetry(RetryContext context) throws Exception {
count++;
throw new RuntimeException("Barf!");