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:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -156,7 +156,7 @@ public class StatefulRetryOperationsInterceptor implements MethodInterceptor {
|
||||
*
|
||||
*/
|
||||
private static final class MethodInvocationRetryCallback implements
|
||||
RetryCallback<Object> {
|
||||
RetryCallback<Object, Throwable> {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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!");
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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!");
|
||||
|
||||
Reference in New Issue
Block a user