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

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

/*
 * Copyright 2012-2015 Ray Holder
 * Copyright 2015 James Hu
 *
 * 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 net.sf.jabb.util.attempt;

import java.time.Duration;
import java.time.Instant;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;

import javax.annotation.Nonnull;

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

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

/**
 * Base class with core functions of AttemptStrategy
 *
 * @author JB
 * @author Jason Dunkelberger (dirkraft)
 * @author James Hu
 */
class AttemptStrategyImpl {
	protected StopStrategy stopStrategy;
	protected AttemptBackoffStrategy backoffStrategy;
	protected WaitStrategy waitStrategy;
	protected TimeLimiter attemptTimeLimiter;
	protected Duration attemptTimeLimit;
	protected Predicate> retryConditionOnExceptions;
	protected List listeners;
    
    protected AttemptStrategyImpl(){
    }

    protected AttemptStrategyImpl(AttemptStrategyImpl that){
    	this.stopStrategy = that.stopStrategy;
    	this.backoffStrategy = that.backoffStrategy;
    	this.waitStrategy = that.waitStrategy;
    	this.attemptTimeLimiter = that.attemptTimeLimiter;
    	this.attemptTimeLimit = that.attemptTimeLimit;
    	this.retryConditionOnExceptions = that.retryConditionOnExceptions;
    	if (that.listeners != null){
    		this.listeners = new LinkedList<>(that.listeners);
    	}
    }
    
    protected void setDefaultsIfNotSet(){
    	if (stopStrategy == null){
    		stopStrategy = StopStrategies.neverStop();
    	}
    	if (backoffStrategy == null){
    		backoffStrategy = AttemptBackoffStrategies.simpleBackoff(BackoffStrategies.noBackoff());
    	}
    	if (waitStrategy == null){
    		waitStrategy = WaitStrategies.threadSleepStrategy();
    	}
    }
    
    /**
     * Executes the given callable. If the rejection predicate
     * accepts the attempt, the stop strategy is used to decide if a new attempt
     * must be made. Then the wait strategy is used to decide how much time to sleep
     * and a new attempt is made.
     * @param  type of the result of the attempt
     * @param callable 					the callable task to be executed
     * @param retryConditionOnResult	retry condition based on the result
     * @return the computed result of the given 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
     */
    protected  V callThrowingAll(Callable callable, Predicate> retryConditionOnResult) 
    		throws AttemptException, Exception {
    	setDefaultsIfNotSet();
    	
        Attempt attempt = null;
        Attempt attemptWithResult = null;
        Attempt attemptWithException = null;

        Object context = null;
        Instant firstStartTime = Instant.now();
        for (int attemptNumber = 1; ; attemptNumber++) {
            try {
                V result = attemptTimeLimiter == null ? callable.call() 
                		: attemptTimeLimiter.callWithTimeout(callable, attemptTimeLimit.toMillis(), TimeUnit.MILLISECONDS, true);
                attemptWithResult = Attempt.withResult(context, attemptNumber, firstStartTime, Instant.now(), result);
                attempt = attemptWithResult;
            } catch (Exception t) {
            	attemptWithException = Attempt.withException(context, attemptNumber, firstStartTime, Instant.now(), t);
            	attempt = attemptWithException;
            }

            if (listeners != null){
                for (AttemptListener listener : listeners) {
                    listener.onAttempted(attempt);
                }
                context = attempt.getContext();		// it may have been changed from inside the listeners
            }

            if (attempt.hasException()){	// no result
            	if (attemptTimeLimiter != null && attempt.getException() instanceof UncheckedTimeoutException 	// always retry in this situation
            			|| retryConditionOnExceptions != null && retryConditionOnExceptions.test(attemptWithException)) {
            		// should retry
            	} else { // should abort
            		throw attempt.getException();
            	}
            }else{	// has a result
            	if(retryConditionOnResult != null && retryConditionOnResult.test(attemptWithResult)){
            		// should retry
            	}else{	// should return
                	return attemptWithResult.getResult();
            	}
            }
            
            if (stopStrategy.shouldStop(attempt)) {
                throw new TooManyAttemptsException(attempt);
            } else {
                long sleepTime = backoffStrategy.computeBackoffMilliseconds(attempt);
                try {
                    waitStrategy.await(sleepTime);
                } catch (InterruptedException e) {
                	waitStrategy.handleInterruptedException(e);
                    throw new InterruptedBeforeAttemptException(e, attempt);
                }
            }
        }
    }
    
    
    protected void addAttemptListener(AttemptListener listener) {
        Preconditions.checkNotNull(listener, "listener may not be null");
        if (listeners == null){
        	listeners = new LinkedList<>();
        }
        listeners.add(listener);
    }

	protected void setStopStrategy(StopStrategy stopStrategy) {
        Preconditions.checkNotNull(stopStrategy, "stopStrategy may not be null");
		this.stopStrategy = stopStrategy;
	}

	protected void setBackoffStrategy(AttemptBackoffStrategy backoffStrategy) {
        Preconditions.checkNotNull(backoffStrategy, "backoffStrategy may not be null");
		this.backoffStrategy = backoffStrategy;
	}

	protected void setWaitStrategy(WaitStrategy waitStrategy) {
        Preconditions.checkNotNull(waitStrategy, "waitStrategy may not be null");
		this.waitStrategy = waitStrategy;
	}

	protected void setAttemptTimeLimit(TimeLimiter attemptTimeLimiter, Duration attemptTimeLimit){
        Preconditions.checkNotNull(attemptTimeLimiter, "attemptTimeLimiter may not be null");
        Preconditions.checkNotNull(attemptTimeLimit, "attemptTimeLimit may not be null");
        Preconditions.checkArgument(!attemptTimeLimit.isNegative() && !attemptTimeLimit.isZero(), "attemptTimeLimit may not be negative or zero");
		this.attemptTimeLimiter = attemptTimeLimiter;
		this.attemptTimeLimit = attemptTimeLimit;
	}

	/////////////
	
	protected AttemptStrategyImpl withStopStrategy(@Nonnull StopStrategy stopStrategy) throws IllegalStateException {
        Preconditions.checkState(this.stopStrategy == null, "a stop strategy has already been set %s", this.stopStrategy);
        this.setStopStrategy(stopStrategy);
        return this;
    }
	
	protected AttemptStrategyImpl overrideStopStrategy(@Nonnull StopStrategy stopStrategy){
        this.setStopStrategy(stopStrategy);
        return this;
	}

    protected AttemptStrategyImpl withBackoffStrategy(@Nonnull AttemptBackoffStrategy backoffStrategy) throws IllegalStateException {
        Preconditions.checkState(this.backoffStrategy == null, "a backoff strategy has already been set %s", this.backoffStrategy);
        this.setBackoffStrategy(backoffStrategy);
        return this;
    }

    protected AttemptStrategyImpl overrideBackoffStrategy(@Nonnull AttemptBackoffStrategy backoffStrategy){
        this.setBackoffStrategy(backoffStrategy);
        return this;
    }

    protected AttemptStrategyImpl withBackoffStrategy(@Nonnull BackoffStrategy backoffStrategy) throws IllegalStateException {
        Preconditions.checkState(this.backoffStrategy == null, "a backoff strategy has already been set %s", this.backoffStrategy);
        this.setBackoffStrategy(AttemptBackoffStrategies.simpleBackoff(backoffStrategy));
        return this;
    }

    protected AttemptStrategyImpl overrideBackoffStrategy(@Nonnull BackoffStrategy backoffStrategy){
        this.setBackoffStrategy(AttemptBackoffStrategies.simpleBackoff(backoffStrategy));
        return this;
    }

    protected AttemptStrategyImpl withWaitStrategy(@Nonnull WaitStrategy waitStrategy) throws IllegalStateException {
        Preconditions.checkState(this.waitStrategy == null, "a wait strategy has already been set %s", this.backoffStrategy);
        this.setWaitStrategy(waitStrategy);
        return this;
    }
    
    protected AttemptStrategyImpl overrideWaitStrategy(@Nonnull WaitStrategy waitStrategy){
        this.setWaitStrategy(waitStrategy);
        return this;
    }
    
    protected AttemptStrategyImpl withAttemptTimeLimit(TimeLimiter attemptTimeLimiter, Duration attemptTimeLimit) throws IllegalStateException {
        Preconditions.checkState(this.attemptTimeLimit == null, "an attempt time limit has already been set %s", this.attemptTimeLimit);
        setAttemptTimeLimit(attemptTimeLimiter, attemptTimeLimit);
        return this;
    }

    protected AttemptStrategyImpl overrideAttemptTimeLimit(TimeLimiter attemptTimeLimiter, Duration attemptTimeLimit){
        setAttemptTimeLimit(attemptTimeLimiter, attemptTimeLimit);
        return this;
    }

    protected AttemptStrategyImpl withAttemptListener(@Nonnull AttemptListener listener) {
    	this.addAttemptListener(listener);
        return this;
    }
    
    protected AttemptStrategyImpl retryIfAttemptHasException(@Nonnull Predicate> exceptionPredicate) {
        Preconditions.checkNotNull(exceptionPredicate, "exceptionPredicate may not be null");
        retryConditionOnExceptions = retryConditionOnExceptions == null ? exceptionPredicate
        		: retryConditionOnExceptions.or(exceptionPredicate);
        return this;
    }

    protected AttemptStrategyImpl retryIfException(@Nonnull Predicate exceptionPredicate) {
        return retryIfAttemptHasException(attempt->exceptionPredicate.test(attempt.getException()));
    }

    @SuppressWarnings("unchecked")
	protected  AttemptStrategyImpl retryIfException(@Nonnull Class exceptionClass, @Nonnull Predicate exceptionPredicate) {
        return retryIfAttemptHasException(attempt->{
        	Exception e = attempt.getException();
        	if (exceptionClass.isAssignableFrom(attempt.getException().getClass())){
        		return exceptionPredicate.test((E)e);
        	}else{
        		return false;
        	}
        });
    }

    protected AttemptStrategyImpl retryIfException(@Nonnull Class exceptionClass) {
        Preconditions.checkNotNull(exceptionClass, "exception class may not be null");
        return retryIfAttemptHasException(new ExceptionClassPredicate(exceptionClass));
    }

    protected AttemptStrategyImpl retryIfRuntimeException() {
        return retryIfAttemptHasException(new ExceptionClassPredicate(RuntimeException.class));
    }

    protected AttemptStrategyImpl retryIfException() {
        return retryIfAttemptHasException(x->true);
    }

    private static final class ExceptionClassPredicate implements Predicate> {

        private Class exceptionClass;

        public ExceptionClassPredicate(Class exceptionClass) {
            this.exceptionClass = exceptionClass;
        }

        @Override
        public boolean test(Attempt attempt) {
            return exceptionClass.isAssignableFrom(attempt.getException().getClass());
        }
    }


}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy