All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.jabb.util.attempt.AttemptStrategyWithRetryOnResult Maven / Gradle / Ivy

/**
 * 
 */
package net.sf.jabb.util.attempt;

import java.time.Duration;
import java.util.concurrent.Callable;
import java.util.function.Predicate;

import javax.annotation.Nonnull;

import net.sf.jabb.util.parallel.BackoffStrategy;
import net.sf.jabb.util.parallel.WaitStrategy;

import com.google.common.base.Preconditions;
import com.google.common.util.concurrent.TimeLimiter;

/**
 * The strategy controlling how a Callable will be attempted multiple times.
 * @author James Hu
 *
 * @param 	Return value type of the Callable
 */
public class AttemptStrategyWithRetryOnResult extends AttemptStrategyImpl {
	protected Predicate> retryConditionOnResult;
	
	/**
	 * Attempt the Callable according to the strategies defined, throwing all the exceptions in their original forms.
	 * @param callable			the Callable to be attempted
	 * @return					the result returned by the Callable
	 * @throws AttemptException		Any exception happened while applying the attempt strategy.
	 * 								For example, {@link TooManyAttemptsException} if no more attempt is allowed by the stop strategy,
	 * 								or {@link InterruptedBeforeAttemptException} if InterruptedException happened while applying attempt time limit strategy or backoff strategy
	 * @throws Exception		It can be any exception thrown from within the Callable that is considered as non-recoverable by the retry strategies
	 */
    public R callThrowingAll(Callable callable)
    		throws AttemptException, Exception {
    	return callThrowingAll(callable, retryConditionOnResult);
    }
    
    /**
     * Synonym of {@link #callThrowingAll(Callable)}
	 * @param callable			the Callable to be attempted
	 * @return					the result returned by the Callable
	 * @throws AttemptException		Any exception happened while applying the attempt strategy.
	 * 								For example, {@link TooManyAttemptsException} if no more attempt is allowed by the stop strategy,
	 * 								or {@link InterruptedBeforeAttemptException} if InterruptedException happened while applying attempt time limit strategy or backoff strategy
	 * @throws Exception		It can be any exception thrown from within the Callable that is considered as non-recoverable by the retry strategies
     */
    public R call(Callable callable) throws AttemptException, Exception {
    	return callThrowingAll(callable, retryConditionOnResult);
    }
    


    /**
     * Constructor initiating an strategy by copying configurations from another instance.
     * @param that		another attempt strategy
     */
    @SuppressWarnings("unchecked")
	AttemptStrategyWithRetryOnResult(AttemptStrategyImpl that){
    	super(that);
    	if (that instanceof AttemptStrategyWithRetryOnResult){
    		this.retryConditionOnResult = ((AttemptStrategyWithRetryOnResult)that).retryConditionOnResult;
    	}
    }
    
    /////////////////////////////////
    
    /**
     * Sets the stop strategy used to decide when to stop attempting. The default strategy is to not stop at all .
     *
     * @param stopStrategy the strategy used to decide when to stop retrying
     * @return this
     * @throws IllegalStateException if a stop strategy has already been set.
     */
    @SuppressWarnings("unchecked")
	public AttemptStrategyWithRetryOnResult withStopStrategy(@Nonnull StopStrategy stopStrategy) throws IllegalStateException {
        return (AttemptStrategyWithRetryOnResult) super.withStopStrategy(stopStrategy);
    }

    /**
     * Sets the stop strategy used to decide when to stop attempting. The default strategy is to not stop at all .
     *
     * @param stopStrategy the strategy used to decide when to stop retrying
     * @return this
     */
    @SuppressWarnings("unchecked")
	public AttemptStrategyWithRetryOnResult overrideStopStrategy(@Nonnull StopStrategy stopStrategy){
        return (AttemptStrategyWithRetryOnResult) super.overrideStopStrategy(stopStrategy);
    }

    /**
     * Sets the backoff strategy used to decide how long to backoff before next attempt. The default strategy is to not backoff at all .
     *
     * @param backoffStrategy the strategy used to decide how long to backoff before next attempt
     * @return this
     * @throws IllegalStateException if a backoff strategy has already been set.
     */
    @SuppressWarnings("unchecked")
	public AttemptStrategyWithRetryOnResult withBackoffStrategy(@Nonnull AttemptBackoffStrategy backoffStrategy) throws IllegalStateException {
        return (AttemptStrategyWithRetryOnResult) super.withBackoffStrategy(backoffStrategy);
    }

    /**
     * Sets the backoff strategy used to decide how long to backoff before next attempt. The default strategy is to not backoff at all .
     *
     * @param backoffStrategy the strategy used to decide how long to backoff before next attempt
     * @return this
     */
    @SuppressWarnings("unchecked")
	public AttemptStrategyWithRetryOnResult overrideBackoffStrategy(@Nonnull AttemptBackoffStrategy backoffStrategy){
        return (AttemptStrategyWithRetryOnResult) super.overrideBackoffStrategy(backoffStrategy);
    }

    /**
     * Sets the backoff strategy used to decide how long to backoff before next attempt. The default strategy is to not backoff at all .
     * @param backoffStrategy the strategy used to decide how long to backoff before next attempt
     * @return this
     * @throws IllegalStateException if a backoff strategy has already been set.
     */
    @SuppressWarnings("unchecked")
	public AttemptStrategyWithRetryOnResult withBackoffStrategy(@Nonnull BackoffStrategy backoffStrategy) throws IllegalStateException {
    	return (AttemptStrategyWithRetryOnResult) super.withBackoffStrategy(backoffStrategy);
    }
    
    /**
     * Sets the backoff strategy used to decide how long to backoff before next attempt. The default strategy is to not backoff at all .
     * @param backoffStrategy the strategy used to decide how long to backoff before next attempt
     * @return this
     */
    @SuppressWarnings("unchecked")
	public AttemptStrategyWithRetryOnResult overrideBackoffStrategy(@Nonnull BackoffStrategy backoffStrategy){
    	return (AttemptStrategyWithRetryOnResult) super.overrideBackoffStrategy(backoffStrategy);
    }
    
    /**
     * Sets the wait strategy used to decide how to perform waiting before next attempt. The default strategy is to use Thread.sleep() .
     *
     * @param waitStrategy the strategy used to decide how to perform waiting before next attempt
     * @return this
     * @throws IllegalStateException if a wait strategy has already been set.
     */
    @SuppressWarnings("unchecked")
	public AttemptStrategyWithRetryOnResult withWaitStrategy(@Nonnull WaitStrategy waitStrategy) throws IllegalStateException {
    	return (AttemptStrategyWithRetryOnResult) super.withWaitStrategy(waitStrategy);
    }
    
    /**
     * Sets the wait strategy used to decide how to perform waiting before next attempt. The default strategy is to use Thread.sleep() .
     *
     * @param waitStrategy the strategy used to decide how to perform waiting before next attempt
     * @return this
     */
    @SuppressWarnings("unchecked")
	public AttemptStrategyWithRetryOnResult overrideWaitStrategy(@Nonnull WaitStrategy waitStrategy) {
    	return (AttemptStrategyWithRetryOnResult) super.overrideWaitStrategy(waitStrategy);
    }
    
    /**
     * Sets the time limit for each of the attempts. By default there will be no time limit applied.
     *
     * @param attemptTimeLimiter the TimeLimiter implementation
     * @param attemptTimeLimit the maximum time allowed for an attempt
     * @return this
     * @throws IllegalStateException if a time limit has already been set.
     */
    @SuppressWarnings("unchecked")
	public AttemptStrategyWithRetryOnResult withAttemptTimeLimit(TimeLimiter attemptTimeLimiter, Duration attemptTimeLimit) throws IllegalStateException{
        return (AttemptStrategyWithRetryOnResult) super.withAttemptTimeLimit(attemptTimeLimiter, attemptTimeLimit);
    }

    /**
     * Sets the time limit for each of the attempts. By default there will be no time limit applied.
     *
     * @param attemptTimeLimiter the TimeLimiter implementation
     * @param attemptTimeLimit the maximum time allowed for an attempt
     * @return this
     */
    @SuppressWarnings("unchecked")
	public AttemptStrategyWithRetryOnResult overrideAttemptTimeLimit(TimeLimiter attemptTimeLimiter, Duration attemptTimeLimit){
        return (AttemptStrategyWithRetryOnResult) super.overrideAttemptTimeLimit(attemptTimeLimiter, attemptTimeLimit);
    }

    /**
     * Add a listener. More than one listeners can be added.
     * @param listener a listener
     */
    @SuppressWarnings("unchecked")
	public AttemptStrategyWithRetryOnResult withAttemptListener(@Nonnull AttemptListener listener) {
        return (AttemptStrategyWithRetryOnResult) super.withAttemptListener(listener);
    }
    
    
    /////////////////////////////////
    
    /**
     * Adds a predicate to decide whether next attempt is needed when exception happened in previous attempt.
     * retryIf*Exceptin(...) methods can be called multiple times, all the predicates will be or-ed.
     * If no retryIf*Exceptin(...) has been called or if an exception happened during an attempt cannot
     * make any of the predicates true, it will be propageted and there will be no further attempt.
     * @param exceptionPredicate The predicate to be called when exception happened in previous attempt.
     * 								It should return true if another attempt is needed.
     * @return this
     */
    @SuppressWarnings("unchecked")
	public AttemptStrategyWithRetryOnResult retryIfAttemptHasException(@Nonnull Predicate> exceptionPredicate) {
    	return (AttemptStrategyWithRetryOnResult) super.retryIfAttemptHasException(exceptionPredicate);
    }
    
    /**
     * Adds a predicate to decide whether next attempt is needed when exception happened in previous attempt.
     * retryIf*Exceptin(...) methods can be called multiple times, all the predicates will be or-ed.
     * If no retryIf*Exceptin(...) has been called or if an exception happened during an attempt cannot
     * make any of the predicates true, it will be propageted and there will be no further attempt.
     * @param exceptionPredicate The predicate to be called when exception happened in previous attempt.
     * 								It should return true if another attempt is needed.
     * @return this
     */
    @SuppressWarnings("unchecked")
	public AttemptStrategyWithRetryOnResult retryIfException(@Nonnull Predicate exceptionPredicate) {
        return (AttemptStrategyWithRetryOnResult) super.retryIfException(exceptionPredicate);
    }

    /**
     * Adds a predicate to decide whether next attempt is needed when exception of specified type happened in previous attempt.
     * retryIf*Exceptin(...) methods can be called multiple times, all the predicates will be or-ed.
     * If no retryIf*Exceptin(...) has been called or if an exception happened during an attempt cannot
     * make any of the predicates true, it will be propagated and there will be no further attempt.
     * @param exceptionClass		type of the exception that the predicate applies to
     * @param exceptionPredicate The predicate to be called when exception of specified type happened in previous attempt.
     * 								It should return true if another attempt is needed.
     * @return this
     */
	@SuppressWarnings("unchecked")
	public  AttemptStrategyWithRetryOnResult retryIfException(@Nonnull Class exceptionClass, @Nonnull Predicate exceptionPredicate) {
		return (AttemptStrategyWithRetryOnResult) super.retryIfException(exceptionClass, exceptionPredicate);
	}
	
    /**
     * Add a predicate that if a specific type of exception happened during an attempt, there needs to be a next attempt. 
     * retryIf*Exceptin(...) methods can be called multiple times, all the predicates will be or-ed.
     * If no retryIf*Exceptin(...) has been called or if an exception happened during an attempt cannot
     * make any of the predicates true, it will be propageted and there will be no further attempt.
     * @param exceptionClass  type of the exception that will cause next attempt. 
     * 						exceptionClass.isAssignableFrom(...) will be used to decide whether an exception is of this type.
     * @return this
     */
    @SuppressWarnings("unchecked")
	public AttemptStrategyWithRetryOnResult retryIfException(@Nonnull Class exceptionClass) {
        return (AttemptStrategyWithRetryOnResult) super.retryIfException(exceptionClass);
    }

    /**
     * Add a predicate that if any RuntimeException happened during an attempt, there needs to be a next attempt. 
     * retryIf*Exceptin(...) methods can be called multiple times, all the predicates will be or-ed.
     * If no retryIf*Exceptin(...) has been called or if an exception happened during an attempt cannot
     * make any of the predicates true, it will be propageted and there will be no further attempt.
     * @return this
     */
    @SuppressWarnings("unchecked")
	public AttemptStrategyWithRetryOnResult retryIfRuntimeException() {
        return (AttemptStrategyWithRetryOnResult) super.retryIfRuntimeException();
    }

    /**
     * Add a predicate that if any exception happened during an attempt, there needs to be a next attempt. 
     * retryIf*Exceptin(...) methods can be called multiple times, all the predicates will be or-ed.
     * If no retryIf*Exceptin(...) has been called or if an exception happened during an attempt cannot
     * make any of the predicates true, it will be propageted and there will be no further attempt.
     * @return this
     */
    @SuppressWarnings("unchecked")
	public AttemptStrategyWithRetryOnResult retryIfException() {
        return (AttemptStrategyWithRetryOnResult) super.retryIfException();
    }


    /**
     * Adds a predicate to decide whether next attempt is needed when there is a result from previous attempt.
     * retryIf*Result*(...) methods can be called multiple times, all the predicates will be or-ed.
     * If no retryIf*Result*(...) method has been called or if a result got from an attempt cannot
     * make any of the predicates true, it will be returned and there will be no further attempt.
     * @param resultPredicate	The predicate to be called when a result was returned in previous attempt.
     * 								It should return true if another attempt is needed.
     * @return this
     */
    public AttemptStrategyWithRetryOnResult retryIfAttemptHasResult(@Nonnull Predicate> resultPredicate) {
        Preconditions.checkNotNull(resultPredicate, "resultPredicate may not be null");
        retryConditionOnResult = retryConditionOnResult == null ? resultPredicate
        		: retryConditionOnResult.or(resultPredicate);
        return this;
    }

    /**
     * Adds a predicate to decide whether next attempt is needed when there is a result from previous attempt.
     * retryIf*Result*(...) methods can be called multiple times, all the predicates will be or-ed.
     * If no retryIf*Result*(...) method has been called or if a result got from an attempt cannot
     * make any of the predicates true, it will be returned and there will be no further attempt.
     * @param resultPredicate	The predicate to be called when a result was returned in previous attempt.
     * 								It should return true if another attempt is needed.
     * @return this
     */
    public AttemptStrategyWithRetryOnResult retryIfResult(@Nonnull Predicate resultPredicate) {
        Preconditions.checkNotNull(resultPredicate, "resultPredicate may not be null");
        return retryIfAttemptHasResult(attempt->resultPredicate.test(attempt.getResult()));
    }

    /**
     * Adds a predicate to decide whether next attempt is needed when a specified value equals to the result from previous attempt.
     * retryIf*Result*(...) methods can be called multiple times, all the predicates will be or-ed.
     * If no retryIf*Result*(...) method has been called or if a result got from an attempt cannot
     * make any of the predicates true, it will be returned and there will be no further attempt.
     * @param resultValue	The value to be compared when a result was returned in previous attempt.
     * 					resultValue.equals(...) will be used.
     * @return this
     */
    public AttemptStrategyWithRetryOnResult retryIfResultEquals(@Nonnull R resultValue) {
        Preconditions.checkNotNull(resultValue, "resultValue may not be null");
        return retryIfAttemptHasResult(attempt->resultValue.equals(attempt.getResult()));
    }

    /**
     * Adds a predicate to make sure that if there is a null result from an attempt there will be a need for next attempt.
     * retryIf*Result*(...) methods can be called multiple times, all the predicates will be or-ed.
     * If no retryIf*Result*(...) method has been called or if a result got from an attempt cannot
     * make any of the predicates true, it will be returned and there will be no further attempt.
     * @return this
     */
    public AttemptStrategyWithRetryOnResult retryIfResultIsNull() {
        return retryIfAttemptHasResult(attempt-> null == attempt.getResult());
    }



}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy