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

net.sf.jabb.util.attempt.AttemptStrategy 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 com.google.common.util.concurrent.TimeLimiter;

import net.sf.jabb.util.ex.ExceptionUncheckUtility;
import net.sf.jabb.util.ex.ExceptionUncheckUtility.RunnableThrowsExceptions;
import net.sf.jabb.util.parallel.BackoffStrategy;
import net.sf.jabb.util.parallel.WaitStrategy;

/**
 * The strategy controlling how an operation (Runnable or Callable) will be attempted multiple times.
 * @author James Hu
 *
 */
public class AttemptStrategy extends AttemptStrategyImpl {
	
	/**
	 * Attempt the RunnableThrowsExceptions according to the strategies defined, throwing all the exceptions in their original forms.
	 * @param runnable			the RunnableThrowsExceptions to be attempted
	 * @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 Runnable that is considered as non-recoverable by the retry strategies
	 */
    public void runThrowingAll(RunnableThrowsExceptions runnable)
    		throws AttemptException, Exception {
    	callThrowingAll(() -> {runnable.run(); return null;}, null);
    }
    
	/**
	 * Attempt the Runnable according to the strategies defined, throwing all the exceptions in their original forms.
	 * @param runnable			the Runnable to be attempted
	 * @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
	 */
    public void runThrowingAttempt(Runnable runnable)
    		throws AttemptException {
    	ExceptionUncheckUtility.runThrowingUnchecked(()->callThrowingAll(() -> {runnable.run(); return null;}, null));
    }
    
    /**
     * Attempt the RunnableThrowsExceptions according to the strategies defined, throwing exceptions in their original or modified forms.
     * If no more attempt is allowed by the stop strategy or InterruptedException happened while applying attempt time limit strategy or backoff strategy,
     * a TooManyAttemptsException or InterruptedException will be added as a suppressed exception to the exception thrown from within the last attempt.
	 * @param runnable			the RunnableThrowsExceptions to be attempted
	 * @throws Exception		It can be any exception thrown from within the Runnable that is considered as non-recoverable by the retry strategies.
	 * 							Optionally with a TooManyAttemptsException or InterruptedException as one of its suppressed exceptions.
     */
    public void runThrowingSuppressed(RunnableThrowsExceptions runnable) throws Exception{
    	try {
    		callThrowingAll(() -> {runnable.run(); return null;}, null);
		} catch (TooManyAttemptsException e) {
			Attempt lastAttempt = e.getLastAttempt();
			Exception lastCause = lastAttempt.getException();
			if (lastCause != null){
				lastCause.addSuppressed(e.copyWithoutCause());	// avoid loop of suppressed exceptions
				throw lastCause;
			}else{	// should never happen
				throw e;
			}
		} catch (InterruptedBeforeAttemptException e) {
			Attempt lastAttempt = e.getLastAttempt();
			Exception lastCause = lastAttempt.getException();
			if (lastCause != null){
				lastCause.addSuppressed(e.copyWithoutSuppressed());	// avoid loop of suppressed exceptions
				throw lastCause;
			}else{	// should never happen
				throw e;
			}
		}
    }
    
  
    /**
     * Synonym of {@link #runThrowingSuppressed(net.sf.jabb.util.ex.ExceptionUncheckUtility.RunnableThrowsExceptions)}
	 * @param runnable			the RunnableThrowsExceptions to be attempted
 	 * @throws Exception		It can be any exception thrown from within the Runnable that is considered as non-recoverable by the retry strategies.
	 * 							Optionally with a TooManyAttemptsException or InterruptedException as one of its suppressed exceptions.
    */
    public void run(RunnableThrowsExceptions runnable) throws Exception{
       	runThrowingSuppressed(runnable);
    }
    
    /**
     * Constructor initiating an empty strategy
     */
    public AttemptStrategy() {
		super();
	}

    /**
     * Constructor initiating an strategy by copying configurations from another instance.
     * @param that		another attempt strategy
     */
    public AttemptStrategy(AttemptStrategyImpl that){
    	super(that);
    }
    
    /**
     * Create an instance with empty strategy
     * @return	the new instance
     */
    static public AttemptStrategy create(){
    	return new AttemptStrategy();
    }

    /**
     * Create an instance with configurations copied from another instance
     * @param  type of the result of the attempt
     * @param that	another instance
     * @return	the new instance
     */
    static public  AttemptStrategyWithRetryOnResult create(AttemptStrategyWithRetryOnResult that){
   		return new AttemptStrategyWithRetryOnResult(that);
    }

    /**
     * Create an instance with configurations copied from another instance
     * @param that	another instance
     * @return	the new instance
     */
    static public AttemptStrategy create(AttemptStrategyImpl that){
    	return new AttemptStrategy(that);
    }

	///////////////////////////

    /**
     * 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.
     */
    public AttemptStrategy withStopStrategy(@Nonnull StopStrategy stopStrategy) throws IllegalStateException {
        return (AttemptStrategy) 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
     */
    public AttemptStrategy overrideStopStrategy(@Nonnull StopStrategy stopStrategy) {
        return (AttemptStrategy) 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.
     */
    public AttemptStrategy withBackoffStrategy(@Nonnull AttemptBackoffStrategy backoffStrategy) throws IllegalStateException {
        return (AttemptStrategy) 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
     */
    public AttemptStrategy overrideBackoffStrategy(@Nonnull AttemptBackoffStrategy backoffStrategy) {
        return (AttemptStrategy) 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.
     */
	public AttemptStrategy withBackoffStrategy(@Nonnull BackoffStrategy backoffStrategy) throws IllegalStateException {
    	return (AttemptStrategy) 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
     */
	public AttemptStrategy overrideBackoffStrategy(@Nonnull BackoffStrategy backoffStrategy) {
    	return (AttemptStrategy) 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.
     */
    public AttemptStrategy withWaitStrategy(@Nonnull WaitStrategy waitStrategy) throws IllegalStateException {
    	return (AttemptStrategy) 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
     */
    public AttemptStrategy overrideWaitStrategy(@Nonnull WaitStrategy waitStrategy) {
    	return (AttemptStrategy) 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.
     */
    public AttemptStrategy withAttemptTimeLimit(TimeLimiter attemptTimeLimiter, Duration attemptTimeLimit) throws IllegalStateException{
        return (AttemptStrategy) 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
     */
    public AttemptStrategy overrideAttemptTimeLimit(TimeLimiter attemptTimeLimiter, Duration attemptTimeLimit){
        return (AttemptStrategy) super.overrideAttemptTimeLimit(attemptTimeLimiter, attemptTimeLimit);
    }

    /**
     * Add a listener. More than one listeners can be added and they will be called in the same
     * order they are added.
     * @param listener a listener
     * @return this
     */
    public AttemptStrategy withAttemptListener(@Nonnull AttemptListener listener) {
        return (AttemptStrategy) 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 propagated 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
     */
    public AttemptStrategy retryIfAttemptHasException(@Nonnull Predicate> exceptionPredicate) {
        return (AttemptStrategy) 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 propagated 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
     */
    public AttemptStrategy retryIfException(@Nonnull Predicate exceptionPredicate) {
        return (AttemptStrategy) 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
     */
	public  AttemptStrategy retryIfException(@Nonnull Class exceptionClass, @Nonnull Predicate exceptionPredicate) {
		return (AttemptStrategy) 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 propagated 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
     */
    public AttemptStrategy retryIfException(@Nonnull Class exceptionClass) {
        return (AttemptStrategy) 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 propagated and there will be no further attempt.
     * @return this
     */
    public AttemptStrategy retryIfRuntimeException() {
        return (AttemptStrategy) 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 propagated and there will be no further attempt.
     * @return this
     */
    public AttemptStrategy retryIfException() {
        return (AttemptStrategy) super.retryIfException();
    }

    
    ///////////  switching to AttemptStrategyWithRetryOnResult
    
    /**
     * 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  type of the result of the 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 a new instance of AttemptStrategyWithRetryOnResult with the same configuration as this
     */
    public  AttemptStrategyWithRetryOnResult retryIfAttemptHasResult(@Nonnull Predicate> resultPredicate) {
    	AttemptStrategyWithRetryOnResult that = new AttemptStrategyWithRetryOnResult(this);
    	return that.retryIfAttemptHasResult(resultPredicate);
    }

    /**
     * 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  type of the result of the 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 a new instance of AttemptStrategyWithRetryOnResult with the same configuration as this
     */
    public  AttemptStrategyWithRetryOnResult retryIfResult(@Nonnull Predicate resultPredicate) {
    	AttemptStrategyWithRetryOnResult that = new AttemptStrategyWithRetryOnResult(this);
    	return that.retryIfResult(resultPredicate);
    }

    /**
     * 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  type of the result of the 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) {
    	AttemptStrategyWithRetryOnResult that = new AttemptStrategyWithRetryOnResult(this);
        return that.retryIfResultEquals(resultValue);
    }

    /**
     * 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.
     * @param  type of the result of the attempt
     * @return a new instance of AttemptStrategyWithRetryOnResult with the same configuration as this
     */
     public  AttemptStrategyWithRetryOnResult retryIfResultIsNull() {
    	AttemptStrategyWithRetryOnResult that = new AttemptStrategyWithRetryOnResult(this);
    	return that.retryIfResultIsNull();
    }

	/**
	 * Attempt the Callable according to the strategies defined, throwing all the exceptions in their original forms.
     * @param  type of the result of the attempt
	 * @param callable			the Callable to be attempted
	 * @return					the result returned by the Callable
	 * @throws TooManyAttemptsException				If no more attempt is allowed by the stop strategy
	 * @throws InterruptedBeforeAttemptException	If InterruptedException happened while applying attempt time limit strategy or backoff strategy
	 * @throws AttemptException		Any exception happened while applying the attempt 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, null);
    }
    
    /**
     * Attempt the Callable according to the strategies defined, throwing exceptions in their original or modified forms.
     * If no more attempt is allowed by the stop strategy or InterruptedException happened while applying attempt time limit strategy or backoff strategy,
     * a TooManyAttemptsException or InterruptedException will be added as a suppressed exception to the exception thrown from within the last attempt.
     * @param  type of the result of the attempt
	 * @param callable			the Callable to be attempted
	 * @return					the result returned by the Callable
	 * @throws Exception		It can be any exception thrown from within the Callable that is considered as non-recoverable by the retry strategies.
	 * 							Optionally with a TooManyAttemptsException or InterruptedException as one of its suppressed exceptions.
     */
    public  R callThrowingSuppressed(Callable callable) throws Exception{
    	try {
    		return callThrowingAll(callable, null);
		} catch (TooManyAttemptsException e) {
			Attempt lastAttempt = e.getLastAttempt();
			Exception lastCause = lastAttempt.getException();
			if (lastCause != null){
				lastCause.addSuppressed(e.copyWithoutCause());
				throw lastCause;
			}else{	// should never happen
				throw e;
			}
		} catch (InterruptedBeforeAttemptException e) {
			Attempt lastAttempt = e.getLastAttempt();
			Exception lastCause = lastAttempt.getException();
			if (lastCause != null){
				lastCause.addSuppressed(e.copyWithoutSuppressed());
				throw lastCause;
			}else{	// should never happen
				throw e;
			}
		}
    }
    
    /**
     * Synonym of {@link #callThrowingSuppressed(Callable)}
     * @param  type of the result of the attempt
	 * @param callable			the Callable to be attempted
	 * @return					the result returned by the Callable
	 * @throws Exception		It can be any exception thrown from within the Callable that is considered as non-recoverable by the retry strategies.
	 * 							Optionally with a TooManyAttemptsException or InterruptedException as one of its suppressed exceptions.
     */
    public  R call(Callable callable) throws Exception{
       	return callThrowingSuppressed(callable);
    }
 

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy