Change method signatures to Throwable (from Exception)
This allows use of spring-retry with naughty libraries that use Error conditions to signal retryable exceptions. Users can still declare their RetryCallback as "throws Exception" if they want to be conservative. Also added throwLastExceptionOnExhausted to RetryTemplate to throw the last exception instead of the ExhaustedRetryException.
This commit is contained in:
@@ -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 Exception;
|
||||
T doWithRetry(RetryContext context) throws Throwable;
|
||||
}
|
||||
|
||||
@@ -35,8 +35,9 @@ public interface RetryOperations {
|
||||
* invocation.
|
||||
* @throws Exception any {@link Exception} raised by the
|
||||
* {@link RetryCallback} upon unsuccessful retry.
|
||||
* @throws Throwable
|
||||
*/
|
||||
<T> T execute(RetryCallback<T> retryCallback) throws Exception;
|
||||
<T> T execute(RetryCallback<T> retryCallback) throws Throwable;
|
||||
|
||||
/**
|
||||
* Execute the supplied {@link RetryCallback} with a fallback on exhausted
|
||||
@@ -48,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 Exception;
|
||||
<T> T execute(RetryCallback<T> retryCallback, RecoveryCallback<T> recoveryCallback) throws Throwable;
|
||||
|
||||
/**
|
||||
* A simple stateful retry. Execute the supplied {@link RetryCallback} with
|
||||
@@ -68,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 Exception, ExhaustedRetryException;
|
||||
<T> T execute(RetryCallback<T> retryCallback, RetryState retryState) throws Throwable, ExhaustedRetryException;
|
||||
|
||||
/**
|
||||
* A stateful retry with a recovery path. Execute the supplied
|
||||
@@ -84,6 +85,6 @@ public interface RetryOperations {
|
||||
* {@link RecoveryCallback} upon unsuccessful retry.
|
||||
*/
|
||||
<T> T execute(RetryCallback<T> retryCallback, RecoveryCallback<T> recoveryCallback, RetryState retryState)
|
||||
throws Exception;
|
||||
throws Throwable;
|
||||
|
||||
}
|
||||
|
||||
@@ -18,7 +18,6 @@ import java.util.Arrays;
|
||||
import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.springframework.aop.ProxyMethodInvocation;
|
||||
import org.springframework.retry.ExhaustedRetryException;
|
||||
import org.springframework.retry.RecoveryCallback;
|
||||
import org.springframework.retry.RetryCallback;
|
||||
import org.springframework.retry.RetryContext;
|
||||
@@ -27,14 +26,17 @@ import org.springframework.retry.support.RetryTemplate;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link MethodInterceptor} that can be used to automatically retry calls to a method on a service if it fails. The
|
||||
* injected {@link RetryOperations} is used to control the number of retries. By default it will retry a fixed number of
|
||||
* times, according to the defaults in {@link RetryTemplate}.<br/>
|
||||
* A {@link MethodInterceptor} that can be used to automatically retry calls to a method
|
||||
* on a service if it fails. The injected {@link RetryOperations} is used to control the
|
||||
* number of retries. By default it will retry a fixed number of times, according to the
|
||||
* defaults in {@link RetryTemplate}.<br/>
|
||||
*
|
||||
* Hint about transaction boundaries. If you want to retry a failed transaction you need to make sure that the
|
||||
* transaction boundary is inside the retry, otherwise the successful attempt will roll back with the whole transaction.
|
||||
* If the method being intercepted is also transactional, then use the ordering hints in the advice declarations to
|
||||
* ensure that this one is before the transaction interceptor in the advice chain.
|
||||
* Hint about transaction boundaries. If you want to retry a failed transaction you need
|
||||
* to make sure that the transaction boundary is inside the retry, otherwise the
|
||||
* successful attempt will roll back with the whole transaction. If the method being
|
||||
* intercepted is also transactional, then use the ordering hints in the advice
|
||||
* declarations to ensure that this one is before the transaction interceptor in the
|
||||
* advice chain.
|
||||
*
|
||||
* @author Rob Harrop
|
||||
* @author Dave Syer
|
||||
@@ -60,13 +62,15 @@ public class RetryOperationsInterceptor implements MethodInterceptor {
|
||||
public Object doWithRetry(RetryContext context) throws Exception {
|
||||
|
||||
/*
|
||||
* If we don't copy the invocation carefully it won't keep a reference to the other interceptors in the
|
||||
* chain. We don't have a choice here but to specialise to ReflectiveMethodInvocation (but how often
|
||||
* would another implementation come along?).
|
||||
* If we don't copy the invocation carefully it won't keep a reference to
|
||||
* the other interceptors in the chain. We don't have a choice here but to
|
||||
* specialise to ReflectiveMethodInvocation (but how often would another
|
||||
* implementation come along?).
|
||||
*/
|
||||
if (invocation instanceof ProxyMethodInvocation) {
|
||||
try {
|
||||
return ((ProxyMethodInvocation) invocation).invocableClone().proceed();
|
||||
return ((ProxyMethodInvocation) invocation).invocableClone()
|
||||
.proceed();
|
||||
} catch (Exception e) {
|
||||
throw e;
|
||||
} catch (Error e) {
|
||||
@@ -83,7 +87,8 @@ public class RetryOperationsInterceptor implements MethodInterceptor {
|
||||
};
|
||||
|
||||
if (recoverer != null) {
|
||||
ItemRecovererCallback recoveryCallback = new ItemRecovererCallback(invocation.getArguments(), recoverer);
|
||||
ItemRecovererCallback recoveryCallback = new ItemRecovererCallback(
|
||||
invocation.getArguments(), recoverer);
|
||||
return this.retryOperations.execute(retryCallback, recoveryCallback);
|
||||
}
|
||||
|
||||
@@ -104,16 +109,14 @@ public class RetryOperationsInterceptor implements MethodInterceptor {
|
||||
/**
|
||||
* @param args the item that failed.
|
||||
*/
|
||||
private ItemRecovererCallback(Object[] args, MethodInvocationRecoverer<? extends Object> recoverer) {
|
||||
private ItemRecovererCallback(Object[] args,
|
||||
MethodInvocationRecoverer<? extends Object> recoverer) {
|
||||
this.args = Arrays.asList(args).toArray();
|
||||
this.recoverer = recoverer;
|
||||
}
|
||||
|
||||
public Object recover(RetryContext context) {
|
||||
if (recoverer != null) {
|
||||
return recoverer.recover(args, context.getLastThrowable());
|
||||
}
|
||||
throw new ExhaustedRetryException("Retry was exhausted but there was no recovery path.");
|
||||
return recoverer.recover(args, context.getLastThrowable());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import org.aopalliance.intercept.MethodInterceptor;
|
||||
import org.aopalliance.intercept.MethodInvocation;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.retry.ExhaustedRetryException;
|
||||
import org.springframework.retry.RecoveryCallback;
|
||||
import org.springframework.retry.RetryCallback;
|
||||
import org.springframework.retry.RetryContext;
|
||||
@@ -35,20 +34,20 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
|
||||
/**
|
||||
* A {@link MethodInterceptor} that can be used to automatically retry calls to
|
||||
* a method on a service if it fails. The argument to the service method is
|
||||
* treated as an item to be remembered in case the call fails. So the retry
|
||||
* operation is stateful, and the item that failed is tracked by its unique key
|
||||
* (via {@link MethodArgumentsKeyGenerator}) until the retry is exhausted, at
|
||||
* which point the {@link MethodInvocationRecoverer} is called.<br/>
|
||||
* A {@link MethodInterceptor} that can be used to automatically retry calls to a method
|
||||
* on a service if it fails. The argument to the service method is treated as an item to
|
||||
* be remembered in case the call fails. So the retry operation is stateful, and the item
|
||||
* that failed is tracked by its unique key (via {@link MethodArgumentsKeyGenerator})
|
||||
* until the retry is exhausted, at which point the {@link MethodInvocationRecoverer} is
|
||||
* called.<br/>
|
||||
*
|
||||
* The main use case for this is where the service is transactional, via a
|
||||
* transaction interceptor on the interceptor chain. In this case the retry (and
|
||||
* recovery on exhausted) always happens in a new transaction.<br/>
|
||||
* The main use case for this is where the service is transactional, via a transaction
|
||||
* interceptor on the interceptor chain. In this case the retry (and recovery on
|
||||
* exhausted) always happens in a new transaction.<br/>
|
||||
*
|
||||
* The injected {@link RetryOperations} is used to control the number of
|
||||
* retries. By default it will retry a fixed number of times, according to the
|
||||
* defaults in {@link RetryTemplate}.<br/>
|
||||
* The injected {@link RetryOperations} is used to control the number of retries. By
|
||||
* default it will retry a fixed number of times, according to the defaults in
|
||||
* {@link RetryTemplate}.<br/>
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@@ -80,13 +79,10 @@ public class StatefulRetryOperationsInterceptor implements MethodInterceptor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the {@link MethodInvocationRecoverer} to use if the
|
||||
* retry is exhausted. The recoverer should be able to return an object of
|
||||
* the same type as the target object because its return value will be used
|
||||
* to return to the caller in the case of a recovery.<br/>
|
||||
*
|
||||
* If no recoverer is set then an exhausted retry will result in an
|
||||
* {@link ExhaustedRetryException}.
|
||||
* Public setter for the {@link MethodInvocationRecoverer} to use if the retry is
|
||||
* exhausted. The recoverer should be able to return an object of the same type as the
|
||||
* target object because its return value will be used to return to the caller in the
|
||||
* case of a recovery.<br/>
|
||||
*
|
||||
* @param recoverer the {@link MethodInvocationRecoverer} to set
|
||||
*/
|
||||
@@ -99,53 +95,57 @@ public class StatefulRetryOperationsInterceptor implements MethodInterceptor {
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the {@link NewMethodArgumentsIdentifier}. Only set this
|
||||
* if the arguments to the intercepted method can be inspected to find out
|
||||
* if they have never been processed before.
|
||||
* @param newMethodArgumentsIdentifier the
|
||||
* {@link NewMethodArgumentsIdentifier} to set
|
||||
* Public setter for the {@link NewMethodArgumentsIdentifier}. Only set this if the
|
||||
* arguments to the intercepted method can be inspected to find out if they have never
|
||||
* been processed before.
|
||||
* @param newMethodArgumentsIdentifier the {@link NewMethodArgumentsIdentifier} to set
|
||||
*/
|
||||
public void setNewItemIdentifier(NewMethodArgumentsIdentifier newMethodArgumentsIdentifier) {
|
||||
public void setNewItemIdentifier(
|
||||
NewMethodArgumentsIdentifier newMethodArgumentsIdentifier) {
|
||||
this.newMethodArgumentsIdentifier = newMethodArgumentsIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the method invocation in a stateful retry with the policy and other
|
||||
* helpers provided. If there is a failure the exception will generally be
|
||||
* re-thrown. The only time it is not re-thrown is when retry is exhausted
|
||||
* and the recovery path is taken (though the
|
||||
* {@link MethodInvocationRecoverer} provided if there is one). In that case
|
||||
* the value returned from the method invocation will be the value returned
|
||||
* by the recoverer (so the return type for that should be the same as the
|
||||
* intercepted method).
|
||||
* Wrap the method invocation in a stateful retry with the policy and other helpers
|
||||
* provided. If there is a failure the exception will generally be re-thrown. The only
|
||||
* time it is not re-thrown is when retry is exhausted and the recovery path is taken
|
||||
* (though the {@link MethodInvocationRecoverer} provided if there is one). In that
|
||||
* case the value returned from the method invocation will be the value returned by
|
||||
* the recoverer (so the return type for that should be the same as the intercepted
|
||||
* method).
|
||||
*
|
||||
* @see org.aopalliance.intercept.MethodInterceptor#invoke(org.aopalliance.intercept.MethodInvocation)
|
||||
* @see MethodInvocationRecoverer#recover(Object[], Throwable)
|
||||
*
|
||||
* @throws ExhaustedRetryException if the retry is exhausted and no
|
||||
* {@link MethodInvocationRecoverer} is provided.
|
||||
*/
|
||||
public Object invoke(final MethodInvocation invocation) throws Throwable {
|
||||
|
||||
logger.debug("Executing proxied method in stateful retry: " + invocation.getStaticPart() + "("
|
||||
logger.debug("Executing proxied method in stateful retry: "
|
||||
+ invocation.getStaticPart() + "("
|
||||
+ ObjectUtils.getIdentityHexString(invocation) + ")");
|
||||
|
||||
Object[] args = invocation.getArguments();
|
||||
Assert.state(args.length > 0, "Stateful retry applied to method that takes no arguments: "
|
||||
+ invocation.getStaticPart());
|
||||
Assert.state(
|
||||
args.length > 0,
|
||||
"Stateful retry applied to method that takes no arguments: "
|
||||
+ invocation.getStaticPart());
|
||||
Object arg = args;
|
||||
if (args.length == 1) {
|
||||
arg = args[0];
|
||||
}
|
||||
final Object item = arg;
|
||||
|
||||
RetryState retryState = new DefaultRetryState(keyGenerator != null ? keyGenerator.getKey(args) : item,
|
||||
newMethodArgumentsIdentifier != null ? newMethodArgumentsIdentifier.isNew(args) : false);
|
||||
RetryState retryState = new DefaultRetryState(
|
||||
keyGenerator != null ? keyGenerator.getKey(args) : item,
|
||||
newMethodArgumentsIdentifier != null ? newMethodArgumentsIdentifier
|
||||
.isNew(args) : false);
|
||||
|
||||
Object result = retryOperations.execute(new MethodInvocationRetryCallback(invocation),
|
||||
new ItemRecovererCallback(args, recoverer), retryState);
|
||||
Object result = retryOperations.execute(new MethodInvocationRetryCallback(
|
||||
invocation), recoverer != null ? new ItemRecovererCallback(args,
|
||||
recoverer) : null, retryState);
|
||||
|
||||
logger.debug("Exiting proxied method in stateful retry with result: (" + result + ")");
|
||||
logger.debug("Exiting proxied method in stateful retry with result: (" + result
|
||||
+ ")");
|
||||
|
||||
return result;
|
||||
|
||||
@@ -155,7 +155,8 @@ public class StatefulRetryOperationsInterceptor implements MethodInterceptor {
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
private static final class MethodInvocationRetryCallback implements RetryCallback<Object> {
|
||||
private static final class MethodInvocationRetryCallback implements
|
||||
RetryCallback<Object> {
|
||||
/**
|
||||
*
|
||||
*/
|
||||
@@ -171,14 +172,11 @@ public class StatefulRetryOperationsInterceptor implements MethodInterceptor {
|
||||
public Object doWithRetry(RetryContext context) throws Exception {
|
||||
try {
|
||||
return invocation.proceed();
|
||||
}
|
||||
catch (Exception e) {
|
||||
} catch (Exception e) {
|
||||
throw e;
|
||||
}
|
||||
catch (Error e) {
|
||||
} catch (Error e) {
|
||||
throw e;
|
||||
}
|
||||
catch (Throwable e) {
|
||||
} catch (Throwable e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
}
|
||||
@@ -197,16 +195,14 @@ public class StatefulRetryOperationsInterceptor implements MethodInterceptor {
|
||||
/**
|
||||
* @param args the item that failed.
|
||||
*/
|
||||
private ItemRecovererCallback(Object[] args, MethodInvocationRecoverer<? extends Object> recoverer) {
|
||||
private ItemRecovererCallback(Object[] args,
|
||||
MethodInvocationRecoverer<? extends Object> recoverer) {
|
||||
this.args = Arrays.asList(args).toArray();
|
||||
this.recoverer = recoverer;
|
||||
}
|
||||
|
||||
public Object recover(RetryContext context) {
|
||||
if (recoverer != null) {
|
||||
return recoverer.recover(args, context.getLastThrowable());
|
||||
}
|
||||
throw new ExhaustedRetryException("Retry was exhausted but there was no recovery path.");
|
||||
return recoverer.recover(args, context.getLastThrowable());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ public class RetrySimulator {
|
||||
template.execute(new FailingRetryCallback());
|
||||
} catch(FailingRetryException e) {
|
||||
|
||||
} catch(Exception e) {
|
||||
} catch(Throwable e) {
|
||||
throw new RuntimeException("Unexpected exception", e);
|
||||
}
|
||||
|
||||
|
||||
@@ -43,28 +43,24 @@ import org.springframework.retry.policy.RetryContextCache;
|
||||
import org.springframework.retry.policy.SimpleRetryPolicy;
|
||||
|
||||
/**
|
||||
* Template class that simplifies the execution of operations with retry
|
||||
* semantics. <br/>
|
||||
* Retryable operations are encapsulated in implementations of the
|
||||
* {@link RetryCallback} interface and are executed using one of the supplied
|
||||
* execute methods. <br/>
|
||||
* Template class that simplifies the execution of operations with retry semantics. <br/>
|
||||
* Retryable operations are encapsulated in implementations of the {@link RetryCallback}
|
||||
* interface and are executed using one of the supplied execute methods. <br/>
|
||||
*
|
||||
* By default, an operation is retried if is throws any {@link Exception} or
|
||||
* subclass of {@link Exception}. This behaviour can be changed by using the
|
||||
* By default, an operation is retried if is throws any {@link Exception} or subclass of
|
||||
* {@link Exception}. This behaviour can be changed by using the
|
||||
* {@link #setRetryPolicy(RetryPolicy)} method. <br/>
|
||||
*
|
||||
* Also by default, each operation is retried for a maximum of three attempts
|
||||
* with no back off in between. This behaviour can be configured using the
|
||||
* {@link #setRetryPolicy(RetryPolicy)} and
|
||||
* {@link #setBackOffPolicy(BackOffPolicy)} properties. The
|
||||
* {@link org.springframework.retry.backoff.BackOffPolicy} controls how
|
||||
* Also by default, each operation is retried for a maximum of three attempts with no back
|
||||
* off in between. This behaviour can be configured using the
|
||||
* {@link #setRetryPolicy(RetryPolicy)} and {@link #setBackOffPolicy(BackOffPolicy)}
|
||||
* properties. The {@link org.springframework.retry.backoff.BackOffPolicy} controls how
|
||||
* long the pause is between each individual retry attempt. <br/>
|
||||
*
|
||||
* This class is thread-safe and suitable for concurrent access when executing
|
||||
* operations and when performing configuration changes. As such, it is possible
|
||||
* to change the number of retries on the fly, as well as the
|
||||
* {@link BackOffPolicy} used and no in progress retryable operations will be
|
||||
* affected.
|
||||
* This class is thread-safe and suitable for concurrent access when executing operations
|
||||
* and when performing configuration changes. As such, it is possible to change the number
|
||||
* of retries on the fly, as well as the {@link BackOffPolicy} used and no in progress
|
||||
* retryable operations will be affected.
|
||||
*
|
||||
* @author Rob Harrop
|
||||
* @author Dave Syer
|
||||
@@ -76,13 +72,23 @@ public class RetryTemplate implements RetryOperations {
|
||||
|
||||
private volatile BackOffPolicy backOffPolicy = new NoBackOffPolicy();
|
||||
|
||||
private volatile RetryPolicy retryPolicy = new SimpleRetryPolicy(3, Collections
|
||||
.<Class<? extends Throwable>, Boolean> singletonMap(Exception.class, true));
|
||||
private volatile RetryPolicy retryPolicy = new SimpleRetryPolicy(3,
|
||||
Collections.<Class<? extends Throwable>, Boolean> singletonMap(
|
||||
Exception.class, true));
|
||||
|
||||
private volatile RetryListener[] listeners = new RetryListener[0];
|
||||
|
||||
private RetryContextCache retryContextCache = new MapRetryContextCache();
|
||||
|
||||
private boolean throwLastExceptionOnExhausted;
|
||||
|
||||
/**
|
||||
* @param throwLastExceptionOnExhausted the throwLastExceptionOnExhausted to set
|
||||
*/
|
||||
public void setThrowLastExceptionOnExhausted(boolean throwLastExceptionOnExhausted) {
|
||||
this.throwLastExceptionOnExhausted = throwLastExceptionOnExhausted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Public setter for the {@link RetryContextCache}.
|
||||
*
|
||||
@@ -93,15 +99,15 @@ public class RetryTemplate implements RetryOperations {
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for listeners. The listeners are executed before and after a retry
|
||||
* block (i.e. before and after all the attempts), and on an error (every
|
||||
* attempt).
|
||||
* Setter for listeners. The listeners are executed before and after a retry block
|
||||
* (i.e. before and after all the attempts), and on an error (every attempt).
|
||||
*
|
||||
* @param listeners
|
||||
* @see RetryListener
|
||||
*/
|
||||
public void setListeners(RetryListener[] listeners) {
|
||||
this.listeners = Arrays.asList(listeners).toArray(new RetryListener[listeners.length]);
|
||||
this.listeners = Arrays.asList(listeners).toArray(
|
||||
new RetryListener[listeners.length]);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -135,68 +141,68 @@ public class RetryTemplate implements RetryOperations {
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep executing the callback until it either succeeds or the policy
|
||||
* dictates that we stop, in which case the most recent exception thrown by
|
||||
* the callback will be rethrown.
|
||||
* Keep executing the callback until it either succeeds or the policy dictates that we
|
||||
* stop, in which case the most recent exception thrown by the callback will be
|
||||
* rethrown.
|
||||
*
|
||||
* @see RetryOperations#execute(RetryCallback)
|
||||
*
|
||||
* @throws TerminatedRetryException if the retry has been manually
|
||||
* terminated by a listener.
|
||||
* @throws TerminatedRetryException if the retry has been manually terminated by a
|
||||
* listener.
|
||||
*/
|
||||
public final <T> T execute(RetryCallback<T> retryCallback) throws Exception {
|
||||
public final <T> T execute(RetryCallback<T> retryCallback) throws Throwable {
|
||||
return doExecute(retryCallback, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep executing the callback until it either succeeds or the policy
|
||||
* dictates that we stop, in which case the recovery callback will be
|
||||
* executed.
|
||||
* Keep executing the callback until it either succeeds or the policy dictates that we
|
||||
* stop, in which case the recovery callback will be executed.
|
||||
*
|
||||
* @see RetryOperations#execute(RetryCallback, RecoveryCallback)
|
||||
*
|
||||
* @throws TerminatedRetryException if the retry has been manually
|
||||
* terminated by a listener.
|
||||
* @throws TerminatedRetryException if the retry has been manually terminated by a
|
||||
* listener.
|
||||
*/
|
||||
public final <T> T execute(RetryCallback<T> retryCallback, RecoveryCallback<T> recoveryCallback) throws Exception {
|
||||
public final <T> T execute(RetryCallback<T> retryCallback,
|
||||
RecoveryCallback<T> recoveryCallback) throws Throwable {
|
||||
return doExecute(retryCallback, recoveryCallback, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the callback once if the policy dictates that we can, re-throwing
|
||||
* any exception encountered so that clients can re-present the same task
|
||||
* later.
|
||||
* Execute the callback once if the policy dictates that we can, re-throwing any
|
||||
* exception encountered so that clients can re-present the same task later.
|
||||
*
|
||||
* @see RetryOperations#execute(RetryCallback, RetryState)
|
||||
*
|
||||
* @throws ExhaustedRetryException if the retry has been exhausted.
|
||||
*/
|
||||
public final <T> T execute(RetryCallback<T> retryCallback, RetryState retryState) throws Exception,
|
||||
ExhaustedRetryException {
|
||||
public final <T> T execute(RetryCallback<T> retryCallback, RetryState retryState)
|
||||
throws Throwable, ExhaustedRetryException {
|
||||
return doExecute(retryCallback, null, retryState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the callback once if the policy dictates that we can, re-throwing
|
||||
* any exception encountered so that clients can re-present the same task
|
||||
* later.
|
||||
* Execute the callback once if the policy dictates that we can, re-throwing any
|
||||
* exception encountered so that clients can re-present the same task later.
|
||||
*
|
||||
* @see RetryOperations#execute(RetryCallback, RetryState)
|
||||
*/
|
||||
public final <T> T execute(RetryCallback<T> retryCallback, RecoveryCallback<T> recoveryCallback,
|
||||
RetryState retryState) throws Exception, ExhaustedRetryException {
|
||||
public final <T> T execute(RetryCallback<T> retryCallback,
|
||||
RecoveryCallback<T> recoveryCallback, RetryState retryState)
|
||||
throws Throwable, ExhaustedRetryException {
|
||||
return doExecute(retryCallback, recoveryCallback, retryState);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the callback once if the policy dictates that we can, otherwise
|
||||
* execute the recovery callback.
|
||||
* Execute the callback once if the policy dictates that we can, otherwise execute the
|
||||
* recovery callback.
|
||||
*
|
||||
* @see RetryOperations#execute(RetryCallback, RecoveryCallback, RetryState)
|
||||
* @throws ExhaustedRetryException if the retry has been exhausted.
|
||||
*/
|
||||
protected <T> T doExecute(RetryCallback<T> retryCallback, RecoveryCallback<T> recoveryCallback, RetryState state)
|
||||
throws Exception, ExhaustedRetryException {
|
||||
protected <T> T doExecute(RetryCallback<T> retryCallback,
|
||||
RecoveryCallback<T> recoveryCallback, RetryState state) throws Throwable,
|
||||
ExhaustedRetryException {
|
||||
|
||||
RetryPolicy retryPolicy = this.retryPolicy;
|
||||
BackOffPolicy backOffPolicy = this.backOffPolicy;
|
||||
@@ -219,7 +225,8 @@ public class RetryTemplate implements RetryOperations {
|
||||
boolean running = doOpenInterceptors(retryCallback, context);
|
||||
|
||||
if (!running) {
|
||||
throw new TerminatedRetryException("Retry terminated abnormally by interceptor before first attempt");
|
||||
throw new TerminatedRetryException(
|
||||
"Retry terminated abnormally by interceptor before first attempt");
|
||||
}
|
||||
|
||||
// Get or Start the backoff context...
|
||||
@@ -240,10 +247,10 @@ public class RetryTemplate implements RetryOperations {
|
||||
}
|
||||
|
||||
/*
|
||||
* We allow the whole loop to be skipped if the policy or context
|
||||
* already forbid the first try. This is used in the case of
|
||||
* external retry to allow a recovery in handleRetryExhausted
|
||||
* without the callback processing (which would throw an exception).
|
||||
* We allow the whole loop to be skipped if the policy or context already
|
||||
* forbid the first try. This is used in the case of external retry to allow a
|
||||
* recovery in handleRetryExhausted without the callback processing (which
|
||||
* would throw an exception).
|
||||
*/
|
||||
while (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {
|
||||
|
||||
@@ -253,8 +260,7 @@ public class RetryTemplate implements RetryOperations {
|
||||
// the close interceptors will not think we failed...
|
||||
lastException = null;
|
||||
return retryCallback.doWithRetry(context);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
} catch (Throwable e) {
|
||||
|
||||
lastException = e;
|
||||
|
||||
@@ -263,48 +269,49 @@ public class RetryTemplate implements RetryOperations {
|
||||
try {
|
||||
registerThrowable(retryPolicy, state, context, e);
|
||||
} catch (Exception ex) {
|
||||
throw new TerminatedRetryException("Could not register throwable", ex);
|
||||
throw new TerminatedRetryException(
|
||||
"Could not register throwable", ex);
|
||||
}
|
||||
|
||||
if (canRetry(retryPolicy, context) && !context.isExhaustedOnly()) {
|
||||
try {
|
||||
backOffPolicy.backOff(backOffContext);
|
||||
}
|
||||
catch (BackOffInterruptedException ex) {
|
||||
} catch (BackOffInterruptedException ex) {
|
||||
lastException = e;
|
||||
// back off was prevented by another thread - fail
|
||||
// the retry
|
||||
logger.debug("Abort retry because interrupted: count=" + context.getRetryCount());
|
||||
logger.debug("Abort retry because interrupted: count="
|
||||
+ context.getRetryCount());
|
||||
throw ex;
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug("Checking for rethrow: count=" + context.getRetryCount());
|
||||
if (shouldRethrow(retryPolicy, context, state)) {
|
||||
logger.debug("Rethrow in retry for policy: count=" + context.getRetryCount());
|
||||
logger.debug("Rethrow in retry for policy: count="
|
||||
+ context.getRetryCount());
|
||||
throw wrapIfNecessary(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
* A stateful attempt that can retry should have rethrown the
|
||||
* exception by now - i.e. we shouldn't get this far for a
|
||||
* stateful attempt if it can retry.
|
||||
* A stateful attempt that can retry should have rethrown the exception by
|
||||
* now - i.e. we shouldn't get this far for a stateful attempt if it can
|
||||
* retry.
|
||||
*/
|
||||
}
|
||||
|
||||
logger.debug("Retry failed last attempt: count=" + context.getRetryCount());
|
||||
|
||||
if (context.isExhaustedOnly()) {
|
||||
throw new ExhaustedRetryException("Retry exhausted after last attempt with no recovery path.", context
|
||||
.getLastThrowable());
|
||||
rethrow(context,
|
||||
"Retry exhausted after last attempt with no recovery path.");
|
||||
}
|
||||
|
||||
return handleRetryExhausted(recoveryCallback, context, state);
|
||||
|
||||
}
|
||||
finally {
|
||||
} finally {
|
||||
close(retryPolicy, context, state, lastException == null);
|
||||
doCloseInterceptors(retryCallback, context, lastException);
|
||||
RetrySynchronizationManager.clear();
|
||||
@@ -313,9 +320,9 @@ public class RetryTemplate implements RetryOperations {
|
||||
}
|
||||
|
||||
/**
|
||||
* Decide whether to proceed with the ongoing retry attempt. This method is
|
||||
* called before the {@link RetryCallback} is executed, but after the
|
||||
* backoff and open interceptors.
|
||||
* Decide whether to proceed with the ongoing retry attempt. This method is called
|
||||
* before the {@link RetryCallback} is executed, but after the backoff and open
|
||||
* interceptors.
|
||||
*
|
||||
* @param retryPolicy the policy to apply
|
||||
* @param context the current retry context
|
||||
@@ -326,21 +333,21 @@ public class RetryTemplate implements RetryOperations {
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up the cache if necessary and close the context provided (if the
|
||||
* flag indicates that processing was successful).
|
||||
* Clean up the cache if necessary and close the context provided (if the flag
|
||||
* indicates that processing was successful).
|
||||
*
|
||||
* @param context
|
||||
* @param state
|
||||
* @param succeeded
|
||||
*/
|
||||
protected void close(RetryPolicy retryPolicy, RetryContext context, RetryState state, boolean succeeded) {
|
||||
protected void close(RetryPolicy retryPolicy, RetryContext context, RetryState state,
|
||||
boolean succeeded) {
|
||||
if (state != null) {
|
||||
if (succeeded) {
|
||||
retryContextCache.remove(state.getKey());
|
||||
retryPolicy.close(context);
|
||||
}
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
retryPolicy.close(context);
|
||||
}
|
||||
}
|
||||
@@ -351,13 +358,15 @@ public class RetryTemplate implements RetryOperations {
|
||||
* @param context
|
||||
* @param e
|
||||
*/
|
||||
protected void registerThrowable(RetryPolicy retryPolicy, RetryState state, RetryContext context, Throwable e) {
|
||||
protected void registerThrowable(RetryPolicy retryPolicy, RetryState state,
|
||||
RetryContext context, Throwable e) {
|
||||
if (state != null) {
|
||||
Object key = state.getKey();
|
||||
if (context.getRetryCount() > 0 && !retryContextCache.containsKey(key)) {
|
||||
throw new RetryException("Inconsistent state for failed item key: cache key has changed. "
|
||||
+ "Consider whether equals() or hashCode() for the key might be inconsistent, "
|
||||
+ "or if you need to supply a better key");
|
||||
throw new RetryException(
|
||||
"Inconsistent state for failed item key: cache key has changed. "
|
||||
+ "Consider whether equals() or hashCode() for the key might be inconsistent, "
|
||||
+ "or if you need to supply a better key");
|
||||
}
|
||||
retryContextCache.put(key, context);
|
||||
}
|
||||
@@ -365,12 +374,12 @@ public class RetryTemplate implements RetryOperations {
|
||||
}
|
||||
|
||||
/**
|
||||
* Delegate to the {@link RetryPolicy} having checked in the cache for an
|
||||
* existing value if the state is not null.
|
||||
* Delegate to the {@link RetryPolicy} having checked in the cache for an existing
|
||||
* value if the state is not null.
|
||||
*
|
||||
* @param retryPolicy a {@link RetryPolicy} to delegate the context creation
|
||||
* @return a retry context, either a new one or the one used last time the
|
||||
* same state was encountered
|
||||
* @return a retry context, either a new one or the one used last time the same state
|
||||
* was encountered
|
||||
*/
|
||||
protected RetryContext open(RetryPolicy retryPolicy, RetryState state) {
|
||||
|
||||
@@ -393,9 +402,10 @@ public class RetryTemplate implements RetryOperations {
|
||||
RetryContext context = retryContextCache.get(key);
|
||||
if (context == null) {
|
||||
if (retryContextCache.containsKey(key)) {
|
||||
throw new RetryException("Inconsistent state for failed item: no history found. "
|
||||
+ "Consider whether equals() or hashCode() for the item might be inconsistent, "
|
||||
+ "or if you need to supply a better ItemKeyGenerator");
|
||||
throw new RetryException(
|
||||
"Inconsistent state for failed item: no history found. "
|
||||
+ "Consider whether equals() or hashCode() for the item might be inconsistent, "
|
||||
+ "or if you need to supply a better ItemKeyGenerator");
|
||||
}
|
||||
// The cache could have been expired in between calls to
|
||||
// containsKey(), so we have to live with this:
|
||||
@@ -415,19 +425,19 @@ public class RetryTemplate implements RetryOperations {
|
||||
}
|
||||
|
||||
/**
|
||||
* Actions to take after final attempt has failed. If there is state clean
|
||||
* up the cache. If there is a recovery callback, execute that and return
|
||||
* its result. Otherwise throw an exception.
|
||||
* Actions to take after final attempt has failed. If there is state clean up the
|
||||
* cache. If there is a recovery callback, execute that and return its result.
|
||||
* Otherwise throw an exception.
|
||||
*
|
||||
* @param recoveryCallback the callback for recovery (might be null)
|
||||
* @param context the current retry context
|
||||
* @throws Exception if the callback does, and if there is no callback and
|
||||
* the state is null then the last exception from the context
|
||||
* @throws ExhaustedRetryException if the state is not null and there is no
|
||||
* recovery callback
|
||||
* @throws Exception if the callback does, and if there is no callback and the state
|
||||
* is null then the last exception from the context
|
||||
* @throws ExhaustedRetryException if the state is not null and there is no recovery
|
||||
* callback
|
||||
*/
|
||||
protected <T> T handleRetryExhausted(RecoveryCallback<T> recoveryCallback, RetryContext context, RetryState state)
|
||||
throws Exception {
|
||||
protected <T> T handleRetryExhausted(RecoveryCallback<T> recoveryCallback,
|
||||
RetryContext context, RetryState state) throws Throwable {
|
||||
if (state != null) {
|
||||
retryContextCache.remove(state.getKey());
|
||||
}
|
||||
@@ -436,28 +446,34 @@ public class RetryTemplate implements RetryOperations {
|
||||
}
|
||||
if (state != null) {
|
||||
logger.debug("Retry exhausted after last attempt with no recovery path.");
|
||||
throw new ExhaustedRetryException("Retry exhausted after last attempt with no recovery path", context
|
||||
.getLastThrowable());
|
||||
rethrow(context, "Retry exhausted after last attempt with no recovery path");
|
||||
}
|
||||
throw wrapIfNecessary(context.getLastThrowable());
|
||||
}
|
||||
|
||||
protected void rethrow(RetryContext context, String message) throws Throwable {
|
||||
if (throwLastExceptionOnExhausted) {
|
||||
throw (Throwable) context.getLastThrowable();
|
||||
} else {
|
||||
throw new ExhaustedRetryException(message, context.getLastThrowable());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension point for subclasses to decide on behaviour after catching an
|
||||
* exception in a {@link RetryCallback}. Normal stateless behaviour is not
|
||||
* to rethrow, and if there is state we rethrow.
|
||||
* Extension point for subclasses to decide on behaviour after catching an exception
|
||||
* in a {@link RetryCallback}. Normal stateless behaviour is not to rethrow, and if
|
||||
* there is state we rethrow.
|
||||
*
|
||||
* @param retryPolicy
|
||||
* @param context the current context
|
||||
*
|
||||
* @return true if the state is not null but subclasses might choose
|
||||
* otherwise
|
||||
* @return true if the state is not null but subclasses might choose otherwise
|
||||
*/
|
||||
protected boolean shouldRethrow(RetryPolicy retryPolicy, RetryContext context, RetryState state) {
|
||||
protected boolean shouldRethrow(RetryPolicy retryPolicy, RetryContext context,
|
||||
RetryState state) {
|
||||
if (state == null) {
|
||||
return false;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return state.rollbackFor(context.getLastThrowable());
|
||||
}
|
||||
}
|
||||
@@ -474,30 +490,30 @@ public class RetryTemplate implements RetryOperations {
|
||||
|
||||
}
|
||||
|
||||
private <T> void doCloseInterceptors(RetryCallback<T> callback, RetryContext context, Throwable lastException) {
|
||||
private <T> void doCloseInterceptors(RetryCallback<T> 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, RetryContext context, Throwable throwable) {
|
||||
private <T> void doOnErrorInterceptors(RetryCallback<T> callback,
|
||||
RetryContext context, Throwable throwable) {
|
||||
for (int i = listeners.length; i-- > 0;) {
|
||||
listeners[i].onError(context, callback, throwable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-throws the original throwable if it is unchecked, wraps checked
|
||||
* exceptions into {@link RetryException}.
|
||||
* Re-throws the original throwable if it is unchecked, wraps checked exceptions into
|
||||
* {@link RetryException}.
|
||||
*/
|
||||
private static Exception wrapIfNecessary(Throwable throwable) {
|
||||
if (throwable instanceof Error) {
|
||||
throw (Error) throwable;
|
||||
}
|
||||
else if (throwable instanceof Exception) {
|
||||
} else if (throwable instanceof Exception) {
|
||||
return (Exception) throwable;
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
return new RetryException("Exception in batch process", throwable);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,7 @@ import org.springframework.context.annotation.AnnotationConfigApplicationContext
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
||||
import org.springframework.retry.config.EnableRetry;
|
||||
import org.springframework.retry.config.Retryable;
|
||||
import org.springframework.retry.backoff.Sleeper;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
@@ -88,6 +87,15 @@ public class EnableRetryTests {
|
||||
@EnableAspectJAutoProxy(proxyTargetClass = true)
|
||||
protected static class TestConfiguration {
|
||||
|
||||
@Bean
|
||||
public Sleeper sleper() {
|
||||
return new Sleeper() {
|
||||
@Override
|
||||
public void sleep(long period) throws InterruptedException {
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Bean
|
||||
public Service service() {
|
||||
return new Service();
|
||||
|
||||
@@ -174,7 +174,7 @@ public class StatefulRetryOperationsInterceptorTests {
|
||||
catch (ExhaustedRetryException e) {
|
||||
// expected
|
||||
String message = e.getMessage();
|
||||
assertTrue("Wrong message: " + message, message.startsWith("Retry was exhausted but there was no recover"));
|
||||
assertTrue("Wrong message: " + message, message.startsWith("Retry exhausted"));
|
||||
}
|
||||
assertEquals(1, count);
|
||||
}
|
||||
|
||||
@@ -40,7 +40,7 @@ public class RetryListenerTests {
|
||||
List<String> list = new ArrayList<String>();
|
||||
|
||||
@Test
|
||||
public void testOpenInterceptors() throws Exception {
|
||||
public void testOpenInterceptors() throws Throwable {
|
||||
template.setListeners(new RetryListener[] { new RetryListenerSupport() {
|
||||
public <T> boolean open(RetryContext context, RetryCallback<T> callback) {
|
||||
count++;
|
||||
@@ -65,7 +65,7 @@ public class RetryListenerTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOpenCanVetoRetry() throws Exception {
|
||||
public void testOpenCanVetoRetry() throws Throwable {
|
||||
template.registerListener(new RetryListenerSupport() {
|
||||
public <T> boolean open(RetryContext context, RetryCallback<T> callback) {
|
||||
list.add("1");
|
||||
@@ -90,7 +90,7 @@ public class RetryListenerTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCloseInterceptors() throws Exception {
|
||||
public void testCloseInterceptors() throws Throwable {
|
||||
template.setListeners(new RetryListener[] { new RetryListenerSupport() {
|
||||
public <T> void close(RetryContext context, RetryCallback<T> callback, Throwable t) {
|
||||
count++;
|
||||
@@ -114,7 +114,7 @@ public class RetryListenerTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnError() throws Exception {
|
||||
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) {
|
||||
@@ -146,7 +146,7 @@ public class RetryListenerTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCloseInterceptorsAfterRetry() throws Exception {
|
||||
public void testCloseInterceptorsAfterRetry() throws Throwable {
|
||||
template.registerListener(new RetryListenerSupport() {
|
||||
public <T> void close(RetryContext context, RetryCallback<T> callback, Throwable t) {
|
||||
list.add("" + count);
|
||||
|
||||
@@ -32,7 +32,7 @@ import org.springframework.retry.support.RetryTemplate;
|
||||
public class FatalExceptionRetryPolicyTests {
|
||||
|
||||
@Test
|
||||
public void testFatalExceptionWithoutState() throws Exception {
|
||||
public void testFatalExceptionWithoutState() throws Throwable {
|
||||
MockRetryCallback callback = new MockRetryCallback();
|
||||
callback.setExceptionToThrow(new IllegalArgumentException());
|
||||
|
||||
@@ -66,7 +66,7 @@ public class FatalExceptionRetryPolicyTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFatalExceptionWithState() throws Exception {
|
||||
public void testFatalExceptionWithState() throws Throwable {
|
||||
MockRetryCallback callback = new MockRetryCallback();
|
||||
callback.setExceptionToThrow(new IllegalArgumentException());
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ import org.springframework.retry.support.RetryTemplate;
|
||||
public class StatefulRetryIntegrationTests {
|
||||
|
||||
@Test
|
||||
public void testExternalRetryWithFailAndNoRetry() throws Exception {
|
||||
public void testExternalRetryWithFailAndNoRetry() throws Throwable {
|
||||
MockRetryCallback callback = new MockRetryCallback();
|
||||
|
||||
RetryState retryState = new DefaultRetryState("foo");
|
||||
@@ -86,7 +86,7 @@ public class StatefulRetryIntegrationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExternalRetryWithSuccessOnRetry() throws Exception {
|
||||
public void testExternalRetryWithSuccessOnRetry() throws Throwable {
|
||||
MockRetryCallback callback = new MockRetryCallback();
|
||||
|
||||
RetryState retryState = new DefaultRetryState("foo");
|
||||
@@ -120,7 +120,7 @@ public class StatefulRetryIntegrationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExponentialBackOffIsExponential() throws Exception {
|
||||
public void testExponentialBackOffIsExponential() throws Throwable {
|
||||
ExponentialBackOffPolicy policy = new ExponentialBackOffPolicy();
|
||||
policy.setInitialInterval(100);
|
||||
policy.setMultiplier(1.5);
|
||||
|
||||
@@ -42,7 +42,7 @@ public class RetrySynchronizationManagerTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStatusIsStoredByTemplate() throws Exception {
|
||||
public void testStatusIsStoredByTemplate() throws Throwable {
|
||||
|
||||
RetryContext status = RetrySynchronizationManager.getContext();
|
||||
assertNull(status);
|
||||
|
||||
@@ -54,7 +54,7 @@ public class RetryTemplateTests {
|
||||
int count = 0;
|
||||
|
||||
@Test
|
||||
public void testSuccessfulRetry() throws Exception {
|
||||
public void testSuccessfulRetry() throws Throwable {
|
||||
for (int x = 1; x <= 10; x++) {
|
||||
MockRetryCallback callback = new MockRetryCallback();
|
||||
callback.setAttemptsBeforeSuccess(x);
|
||||
@@ -67,7 +67,7 @@ public class RetryTemplateTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSuccessfulRecovery() throws Exception {
|
||||
public void testSuccessfulRecovery() throws Throwable {
|
||||
MockRetryCallback callback = new MockRetryCallback();
|
||||
callback.setAttemptsBeforeSuccess(3);
|
||||
RetryTemplate retryTemplate = new RetryTemplate();
|
||||
@@ -84,7 +84,7 @@ public class RetryTemplateTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAlwaysTryAtLeastOnce() throws Exception {
|
||||
public void testAlwaysTryAtLeastOnce() throws Throwable {
|
||||
MockRetryCallback callback = new MockRetryCallback();
|
||||
RetryTemplate retryTemplate = new RetryTemplate();
|
||||
retryTemplate.setRetryPolicy(new NeverRetryPolicy());
|
||||
@@ -93,7 +93,7 @@ public class RetryTemplateTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoSuccessRetry() throws Exception {
|
||||
public void testNoSuccessRetry() throws Throwable {
|
||||
MockRetryCallback callback = new MockRetryCallback();
|
||||
// Something that won't be thrown by JUnit...
|
||||
callback.setExceptionToThrow(new IllegalArgumentException());
|
||||
@@ -115,7 +115,7 @@ public class RetryTemplateTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDefaultConfigWithExceptionSubclass() throws Exception {
|
||||
public void testDefaultConfigWithExceptionSubclass() throws Throwable {
|
||||
MockRetryCallback callback = new MockRetryCallback();
|
||||
int attempts = 3;
|
||||
callback.setAttemptsBeforeSuccess(attempts);
|
||||
@@ -129,7 +129,7 @@ public class RetryTemplateTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRollbackClassifierOverridesRetryPolicy() throws Exception {
|
||||
public void testRollbackClassifierOverridesRetryPolicy() throws Throwable {
|
||||
MockRetryCallback callback = new MockRetryCallback();
|
||||
int attempts = 3;
|
||||
callback.setAttemptsBeforeSuccess(attempts);
|
||||
@@ -145,7 +145,7 @@ public class RetryTemplateTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSetExceptions() throws Exception {
|
||||
public void testSetExceptions() throws Throwable {
|
||||
RetryTemplate template = new RetryTemplate();
|
||||
SimpleRetryPolicy policy = new SimpleRetryPolicy(3, Collections
|
||||
.<Class<? extends Throwable>, Boolean> singletonMap(RuntimeException.class, true));
|
||||
@@ -170,7 +170,7 @@ public class RetryTemplateTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBackOffInvoked() throws Exception {
|
||||
public void testBackOffInvoked() throws Throwable {
|
||||
for (int x = 1; x <= 10; x++) {
|
||||
MockRetryCallback callback = new MockRetryCallback();
|
||||
MockBackOffStrategy backOff = new MockBackOffStrategy();
|
||||
@@ -187,7 +187,7 @@ public class RetryTemplateTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEarlyTermination() throws Exception {
|
||||
public void testEarlyTermination() throws Throwable {
|
||||
try {
|
||||
RetryTemplate retryTemplate = new RetryTemplate();
|
||||
retryTemplate.execute(new RetryCallback<Object>() {
|
||||
@@ -206,15 +206,35 @@ public class RetryTemplateTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNestedContexts() throws Exception {
|
||||
public void testEarlyTerminationWithOriginalException() throws Throwable {
|
||||
try {
|
||||
RetryTemplate retryTemplate = new RetryTemplate();
|
||||
retryTemplate.setThrowLastExceptionOnExhausted(true);
|
||||
retryTemplate.execute(new RetryCallback<Object>() {
|
||||
public Object doWithRetry(RetryContext status) throws Exception {
|
||||
status.setExhaustedOnly();
|
||||
throw new IllegalStateException("Retry this operation");
|
||||
}
|
||||
});
|
||||
fail("Expected ExhaustedRetryException");
|
||||
}
|
||||
catch (IllegalStateException ex) {
|
||||
// Expected for internal retry policy (external would recover
|
||||
// gracefully)
|
||||
assertEquals("Retry this operation", ex.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNestedContexts() throws Throwable {
|
||||
RetryTemplate outer = new RetryTemplate();
|
||||
final RetryTemplate inner = new RetryTemplate();
|
||||
outer.execute(new RetryCallback<Object>() {
|
||||
public Object doWithRetry(RetryContext status) throws Exception {
|
||||
public Object doWithRetry(RetryContext status) throws Throwable {
|
||||
context = status;
|
||||
count++;
|
||||
Object result = inner.execute(new RetryCallback<Object>() {
|
||||
public Object doWithRetry(RetryContext status) throws Exception {
|
||||
public Object doWithRetry(RetryContext status) throws Throwable {
|
||||
count++;
|
||||
assertNotNull(context);
|
||||
assertNotSame(status, context);
|
||||
@@ -231,7 +251,7 @@ public class RetryTemplateTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRethrowError() throws Exception {
|
||||
public void testRethrowError() throws Throwable {
|
||||
RetryTemplate retryTemplate = new RetryTemplate();
|
||||
retryTemplate.setRetryPolicy(new NeverRetryPolicy());
|
||||
try {
|
||||
@@ -248,7 +268,7 @@ public class RetryTemplateTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFailedPolicy() throws Exception {
|
||||
public void testFailedPolicy() throws Throwable {
|
||||
RetryTemplate retryTemplate = new RetryTemplate();
|
||||
retryTemplate.setRetryPolicy(new NeverRetryPolicy() {
|
||||
@Override
|
||||
@@ -270,7 +290,7 @@ public class RetryTemplateTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBackOffInterrupted() throws Exception {
|
||||
public void testBackOffInterrupted() throws Throwable {
|
||||
RetryTemplate retryTemplate = new RetryTemplate();
|
||||
retryTemplate.setBackOffPolicy(new StatelessBackOffPolicy() {
|
||||
protected void doBackOff() throws BackOffInterruptedException {
|
||||
@@ -295,7 +315,7 @@ public class RetryTemplateTests {
|
||||
* re-thrown.
|
||||
*/
|
||||
@Test
|
||||
public void testNoBackOffForRethrownException() throws Exception {
|
||||
public void testNoBackOffForRethrownException() throws Throwable {
|
||||
|
||||
RetryTemplate tested = new RetryTemplate();
|
||||
tested.setRetryPolicy(new SimpleRetryPolicy(1, Collections.<Class<? extends Throwable>, Boolean> singletonMap(
|
||||
|
||||
@@ -81,7 +81,7 @@ public class StatefulRecoveryRetryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRecover() throws Exception {
|
||||
public void testRecover() throws Throwable {
|
||||
retryTemplate.setRetryPolicy(new SimpleRetryPolicy(1, Collections
|
||||
.<Class<? extends Throwable>, Boolean> singletonMap(Exception.class, true)));
|
||||
final String input = "foo";
|
||||
@@ -114,7 +114,7 @@ public class StatefulRecoveryRetryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSwitchToStatelessForNoRollback() throws Exception {
|
||||
public void testSwitchToStatelessForNoRollback() throws Throwable {
|
||||
retryTemplate.setRetryPolicy(new SimpleRetryPolicy(1, Collections
|
||||
.<Class<? extends Throwable>, Boolean> singletonMap(Exception.class, true)));
|
||||
// Roll back for these:
|
||||
@@ -145,7 +145,7 @@ public class StatefulRecoveryRetryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExhaustedClearsHistoryAfterLastAttempt() throws Exception {
|
||||
public void testExhaustedClearsHistoryAfterLastAttempt() throws Throwable {
|
||||
RetryPolicy retryPolicy = new SimpleRetryPolicy(1, Collections
|
||||
.<Class<? extends Throwable>, Boolean> singletonMap(Exception.class, true));
|
||||
retryTemplate.setRetryPolicy(retryPolicy);
|
||||
@@ -225,7 +225,7 @@ public class StatefulRecoveryRetryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCacheCapacity() throws Exception {
|
||||
public void testCacheCapacity() throws Throwable {
|
||||
|
||||
retryTemplate.setRetryPolicy(new SimpleRetryPolicy(1, Collections
|
||||
.<Class<? extends Throwable>, Boolean> singletonMap(Exception.class, true)));
|
||||
@@ -257,7 +257,7 @@ public class StatefulRecoveryRetryTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCacheCapacityNotReachedIfRecovered() throws Exception {
|
||||
public void testCacheCapacityNotReachedIfRecovered() throws Throwable {
|
||||
|
||||
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy(1, Collections
|
||||
.<Class<? extends Throwable>, Boolean> singletonMap(Exception.class, true));
|
||||
|
||||
Reference in New Issue
Block a user