BATCH-804: add interface for RetryState

This commit is contained in:
dsyer
2008-09-02 09:24:37 +00:00
parent 91ecbc6382
commit 311779037a
13 changed files with 265 additions and 111 deletions

View File

@@ -16,6 +16,8 @@
package org.springframework.batch.retry;
import org.springframework.batch.retry.support.DefaultRetryState;
/**
* Defines the basic set of operations implemented by {@link RetryOperations} to
* execute operations with configurable retry behaviour.
@@ -50,7 +52,7 @@ public interface RetryOperations {
/**
* A simple stateful retry. Execute the supplied {@link RetryCallback} with
* a target object for the attempt identified by the {@link RetryState}.
* a target object for the attempt identified by the {@link DefaultRetryState}.
* Exceptions thrown by the callback are always propagated immediately so
* the state is required to be able to identify the previous attempt, if
* there is one - hence the state is required. Normal patterns would see
@@ -72,7 +74,7 @@ public interface RetryOperations {
* A stateful retry with a recovery path. Execute the supplied
* {@link RetryCallback} with a fallback on exhausted retry to the
* {@link RecoveryCallback} and a target object for the retry attempt
* identified by the {@link RetryState}.
* identified by the {@link DefaultRetryState}.
*
* @see #execute(RetryCallback, RetryState)
*

View File

@@ -16,38 +16,45 @@
package org.springframework.batch.retry;
/**
* Stateful retry is characterised by having to recognise the items that are
* being processed, so this interface is used primarily to provide a cache key in
* between failed attempts. It also provides a hints to the
* {@link RetryOperations} for optimisations to do with avoidable cache hits and
* switching to stateless retry if a rollback is not needed.
*
* @author Dave Syer
*
*/
public class RetryState {
final private Object key;
final private boolean forceRefresh;
public interface RetryState {
/**
* @param key
* @param forceRefresh
*/
public RetryState(Object key, boolean forceRefresh) {
this.key = key;
this.forceRefresh = forceRefresh;
}
public RetryState(Object key) {
this(key, false);
}
/**
* Key representing the state for a retry attempt. Stateful retry is
* characterised by having to recognise the items that are being processed,
* so this value is used as a cache key in between failed attempts.
*
* @return the key that this state represents
*/
public Object getKey() {
return key;
}
Object getKey();
/**
* @return true if the state requires an explicit check for the key
* Indicate whether a cache lookup can be avoided. If the key is known ahead
* of the retry attempt to be fresh (i.e. has never been seen before) then a
* cache lookup can be avoided if this flag is true.
*
* @return true if the state does not require an explicit check for the key
*/
public boolean isForceRefresh() {
return forceRefresh;
}
boolean isForceRefresh();
}
/**
* Check whether this exception requires a rollback. The default is always
* true, which is conservative, so this method provides an optimisation for
* switching to stateless retry if there is an exception for which rollback
* is unnecessary. Example usage would be for a stateful retry to specify a
* validation exception as not for rollback.
*
* @param exception the exception that caused a retry attempt to fail
* @return true if this exception should cause a rollback
*/
boolean rollbackFor(Exception exception);
}

View File

@@ -21,13 +21,14 @@ import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.retry.ExhaustedRetryException;
import org.springframework.batch.retry.RetryState;
import org.springframework.batch.retry.RecoveryCallback;
import org.springframework.batch.retry.RetryCallback;
import org.springframework.batch.retry.RetryContext;
import org.springframework.batch.retry.RetryOperations;
import org.springframework.batch.retry.RetryPolicy;
import org.springframework.batch.retry.RetryState;
import org.springframework.batch.retry.policy.NeverRetryPolicy;
import org.springframework.batch.retry.support.DefaultRetryState;
import org.springframework.batch.retry.support.RetryTemplate;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
@@ -138,7 +139,7 @@ public class StatefulRetryOperationsInterceptor implements MethodInterceptor {
}
final Object item = arg;
RetryState retryState = new RetryState(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 = retryTemplate.execute(new MethodInvocationRetryCallback(invocation), new ItemRecovererCallback(args, recoverer), retryState);

View File

@@ -0,0 +1,107 @@
/*
* Copyright 2006-2007 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.batch.retry.support;
import org.springframework.batch.retry.RecoveryCallback;
import org.springframework.batch.retry.RetryCallback;
import org.springframework.batch.retry.RetryOperations;
import org.springframework.batch.retry.RetryState;
import org.springframework.batch.support.Classifier;
/**
*
* @author Dave Syer
*
*/
public class DefaultRetryState implements RetryState {
final private Object key;
final private boolean forceRefresh;
final private Classifier<? super Throwable, Boolean> rollbackClassifier;
/**
* Create a {@link DefaultRetryState} representing the state for a new retry
* attempt.
*
* @see RetryOperations#execute(RetryCallback, RetryState)
* @see RetryOperations#execute(RetryCallback, RecoveryCallback, RetryState)
*
* @param key the key for the state to allow this retry attempt to be
* recognised
* @param forceRefresh true if the attempt is known to be a brand new state
* @param rollbackClassifier the rollback classifier to set. The rollback
* classifier answers true if the exception provided should cause a
* rollback.
*/
public DefaultRetryState(Object key, boolean forceRefresh, Classifier<? super Throwable, Boolean> rollbackClassifier) {
this.key = key;
this.forceRefresh = forceRefresh;
this.rollbackClassifier = rollbackClassifier;
}
/**
* Defaults the rollback classifier to null.
* @see DefaultRetryState#DefaultRetryState(Object, boolean, Classifier)
*/
public DefaultRetryState(Object key, Classifier<? super Throwable, Boolean> rollbackClassifier) {
this(key, false, rollbackClassifier);
}
/**
* Defaults the rollback classifier to null.
* @see DefaultRetryState#DefaultRetryState(Object, boolean, Classifier)
*/
public DefaultRetryState(Object key, boolean forceRefresh) {
this(key, forceRefresh, null);
}
/**
* Defaults the force refresh flag (to false) and the rollback classifier
* (to null).
*
* @see DefaultRetryState#DefaultRetryState(Object, boolean, Classifier)
*/
public DefaultRetryState(Object key) {
this(key, false, null);
}
/* (non-Javadoc)
* @see org.springframework.batch.retry.IRetryState#getKey()
*/
public Object getKey() {
return key;
}
/* (non-Javadoc)
* @see org.springframework.batch.retry.IRetryState#isForceRefresh()
*/
public boolean isForceRefresh() {
return forceRefresh;
}
/* (non-Javadoc)
* @see org.springframework.batch.retry.IRetryState#rollbackFor(java.lang.Exception)
*/
public boolean rollbackFor(Exception exception) {
if (rollbackClassifier == null) {
return true;
}
return rollbackClassifier.classify(exception);
}
}

View File

@@ -23,6 +23,7 @@ import java.util.List;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.batch.retry.ExhaustedRetryException;
import org.springframework.batch.retry.RetryState;
import org.springframework.batch.retry.RecoveryCallback;
import org.springframework.batch.retry.RetryCallback;
import org.springframework.batch.retry.RetryContext;
@@ -30,7 +31,6 @@ import org.springframework.batch.retry.RetryException;
import org.springframework.batch.retry.RetryListener;
import org.springframework.batch.retry.RetryOperations;
import org.springframework.batch.retry.RetryPolicy;
import org.springframework.batch.retry.RetryState;
import org.springframework.batch.retry.TerminatedRetryException;
import org.springframework.batch.retry.backoff.BackOffContext;
import org.springframework.batch.retry.backoff.BackOffInterruptedException;
@@ -39,7 +39,6 @@ import org.springframework.batch.retry.backoff.NoBackOffPolicy;
import org.springframework.batch.retry.policy.MapRetryContextCache;
import org.springframework.batch.retry.policy.RetryContextCache;
import org.springframework.batch.retry.policy.SimpleRetryPolicy;
import org.springframework.batch.support.Classifier;
/**
* Template class that simplifies the execution of operations with retry
@@ -79,45 +78,6 @@ public class RetryTemplate implements RetryOperations {
private RetryContextCache retryContextCache = new MapRetryContextCache();
private Classifier<? super Throwable, Boolean> rollbackClassifier = null;
/**
* Public setter for the rollback classifier. This classifier answers its
* default if the exception provided should not cause a rollback. I.e.
* anything other than the default will lead to a rollback.<br/><br/>
*
* The decision whether to rollback or not is unrelated to that of the
* {@link RetryPolicy}, but the policy can be accidentally inconsistent
* with the rollback decision. E.g. in a stateless retry the policy might be
* configured to allow retry on a rollback, but that wouldn't make sense - a
* stateful retry should have been used. The best we can do in such
* circumstances is throw a {@link RetryException} from
* {@link #execute(RetryCallback)} or
* {@link #execute(RetryCallback, RecoveryCallback)}. The recovery path
* will not be taken in such situations.<br/><br/>
*
* For stateless retry it is often adequate to use the default behaviour, as
* long as one is careful with the retry policy (exceptions which should
* cause rollback are still not really retryable in a transactional
* setting).<br/><br/>
*
* For stateful retry adding a classifier will allow an optimisation:
* exceptions which are not marked for rollback can still be retried, but
* without paying the cost of a rollback. Effectively one is overriding the
* stateful quality of the retry dynamically, according to the exception
* type.<br/><br/>
*
* Example usage would be for a stateful retry to specify a validation exception as not for rollback
*
* If not set then the default is to rollback for all exceptions when the
* retry is stateful, and for none when it is stateless.
*
* @param rollbackClassifier the rollback classifier to set
*/
public void setRollbackClassifier(Classifier<? super Throwable, Boolean> rollbackClassifier) {
this.rollbackClassifier = rollbackClassifier;
}
/**
* Public setter for the {@link RetryContextCache}.
* @param retryContextCache the {@link RetryContextCache} to set.
@@ -446,20 +406,12 @@ public class RetryTemplate implements RetryOperations {
* otherwise
*/
protected boolean shouldRethrow(RetryPolicy retryPolicy, RetryContext context, RetryState state) {
// Allow stateless behaviour to take over for certain exception types
if (rollbackClassifier != null) {
// TODO: remove this. Make it part of the stateful execution parameters?
// Then we wouldn't have to make assertions about the stateless case.
boolean rollback = rollbackClassifier.classify(context.getLastThrowable());
if (rollback && state == null && retryPolicy.canRetry(context)) {
throw new RetryException("Inconsistent configuration. The retry policy says we can retry but "
+ "the exception has been marked for rollback.", context.getLastThrowable());
}
return rollback;
if (state == null) {
return false;
}
else {
return state.rollbackFor(context.getLastThrowable());
}
// If no classifier is provided, just assume the all exceptions are for
// rollback if the execution is stateful, and none otherwise.
return state != null;
}
private <T> boolean doOpenInterceptors(RetryCallback<T> callback, RetryContext context) {