package com.nurkiewicz.asyncretry; import com.nurkiewicz.asyncretry.backoff.Backoff; import com.nurkiewicz.asyncretry.backoff.ExponentialDelayBackoff; import com.nurkiewicz.asyncretry.backoff.FixedIntervalBackoff; import com.nurkiewicz.asyncretry.function.RetryCallable; import com.nurkiewicz.asyncretry.function.RetryRunnable; import com.nurkiewicz.asyncretry.policy.RetryPolicy; import java.util.Objects; import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ScheduledExecutorService; import java.util.function.Predicate; import static java.util.concurrent.TimeUnit.MILLISECONDS; /** * @author Tomasz Nurkiewicz * @since 7/15/13, 11:06 PM */ public class AsyncRetryExecutor implements RetryExecutor { private final ScheduledExecutorService scheduler; private final boolean fixedDelay; private final RetryPolicy retryPolicy; private final Backoff backoff; public AsyncRetryExecutor(ScheduledExecutorService scheduler) { this(scheduler, RetryPolicy.DEFAULT, Backoff.DEFAULT); } public AsyncRetryExecutor(ScheduledExecutorService scheduler, Backoff backoff) { this(scheduler, RetryPolicy.DEFAULT, backoff); } public AsyncRetryExecutor(ScheduledExecutorService scheduler, RetryPolicy retryPolicy) { this(scheduler, retryPolicy, Backoff.DEFAULT); } public AsyncRetryExecutor(ScheduledExecutorService scheduler, RetryPolicy retryPolicy, Backoff backoff) { this(scheduler, retryPolicy, backoff, false); } public AsyncRetryExecutor(ScheduledExecutorService scheduler, RetryPolicy retryPolicy, Backoff backoff, boolean fixedDelay) { this.scheduler = Objects.requireNonNull(scheduler); this.retryPolicy = Objects.requireNonNull(retryPolicy); this.backoff = Objects.requireNonNull(backoff); this.fixedDelay = fixedDelay; } @Override public CompletableFuture<Void> doWithRetry(RetryRunnable action) { return getWithRetry(context -> { action.run(context); return null; }); } @Override public <V> CompletableFuture<V> getWithRetry(Callable<V> task) { return getWithRetry(ctx -> task.call()); } @Override public <V> CompletableFuture<V> getWithRetry(RetryCallable<V> task) { return scheduleImmediately(createTask(task)); } @Override public <V> CompletableFuture<V> getFutureWithRetry(RetryCallable<CompletableFuture<V>> task) { return scheduleImmediately(createFutureTask(task)); } private <V> CompletableFuture<V> scheduleImmediately(RetryJob<V> job) { scheduler.schedule(job, 0, MILLISECONDS); return job.getFuture(); } protected <V> RetryJob<V> createTask(RetryCallable<V> function) { return new SyncRetryJob<>(function, this); } protected <V> RetryJob<V> createFutureTask(RetryCallable<CompletableFuture<V>> function) { return new AsyncRetryJob<>(function, this); } public ScheduledExecutorService getScheduler() { return scheduler; } public boolean isFixedDelay() { return fixedDelay; } public RetryPolicy getRetryPolicy() { return retryPolicy; } public Backoff getBackoff() { return backoff; } public AsyncRetryExecutor withScheduler(ScheduledExecutorService scheduler) { return new AsyncRetryExecutor(scheduler, retryPolicy, backoff, fixedDelay); } public AsyncRetryExecutor withRetryPolicy(RetryPolicy retryPolicy) { return new AsyncRetryExecutor(scheduler, retryPolicy, backoff, fixedDelay); } public AsyncRetryExecutor withExponentialBackoff(long initialDelayMillis, double multiplier) { final ExponentialDelayBackoff backoff = new ExponentialDelayBackoff(initialDelayMillis, multiplier); return new AsyncRetryExecutor(scheduler, retryPolicy, backoff, fixedDelay); } public AsyncRetryExecutor withFixedBackoff(long delayMillis) { final FixedIntervalBackoff backoff = new FixedIntervalBackoff(delayMillis); return new AsyncRetryExecutor(scheduler, retryPolicy, backoff, fixedDelay); } public AsyncRetryExecutor withBackoff(Backoff backoff) { return new AsyncRetryExecutor(scheduler, retryPolicy, backoff, fixedDelay); } public AsyncRetryExecutor withFixedRate() { return new AsyncRetryExecutor(scheduler, retryPolicy, backoff, true); } public AsyncRetryExecutor withFixedRate(boolean fixedDelay) { return new AsyncRetryExecutor(scheduler, retryPolicy, backoff, fixedDelay); } @SafeVarargs public final AsyncRetryExecutor retryOn(Class<? extends Throwable>... retryOnThrowables) { return this.withRetryPolicy(retryPolicy.retryOn(retryOnThrowables)); } @SafeVarargs public final AsyncRetryExecutor abortOn(Class<? extends Throwable>... abortOnThrowable) { return this.withRetryPolicy(retryPolicy.abortOn(abortOnThrowable)); } public AsyncRetryExecutor retryIf(Predicate<Throwable> retryPredicate) { return this.withRetryPolicy(retryPolicy.retryIf(retryPredicate)); } public AsyncRetryExecutor abortIf(Predicate<Throwable> abortPredicate) { return this.withRetryPolicy(retryPolicy.abortIf(abortPredicate)); } public AsyncRetryExecutor withUniformJitter() { return this.withBackoff(this.backoff.withUniformJitter()); } public AsyncRetryExecutor withUniformJitter(long range) { return this.withBackoff(this.backoff.withUniformJitter(range)); } public AsyncRetryExecutor withProportionalJitter() { return this.withBackoff(this.backoff.withProportionalJitter()); } public AsyncRetryExecutor withProportionalJitter(double multiplier) { return this.withBackoff(this.backoff.withProportionalJitter(multiplier)); } public AsyncRetryExecutor withMinDelay(long minDelayMillis) { return this.withBackoff(this.backoff.withMinDelay(minDelayMillis)); } public AsyncRetryExecutor withMaxDelay(long maxDelayMillis) { return this.withBackoff(this.backoff.withMaxDelay(maxDelayMillis)); } public AsyncRetryExecutor withMaxRetries(int times) { return this.withRetryPolicy(this.retryPolicy.withMaxRetries(times)); } public AsyncRetryExecutor dontRetry() { return this.withRetryPolicy(this.retryPolicy.dontRetry()); } public AsyncRetryExecutor retryInfinitely() { return this.withMaxRetries(Integer.MAX_VALUE); } public AsyncRetryExecutor withNoDelay() { return this.withBackoff(new FixedIntervalBackoff(0)); } public AsyncRetryExecutor firstRetryNoDelay() { return this.withBackoff(this.backoff.withFirstRetryNoDelay()); } }