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

com.github.davidmoten.rx.RetryWhen Maven / Gradle / Ivy

package com.github.davidmoten.rx;

import static com.github.davidmoten.util.Optional.absent;
import static com.github.davidmoten.util.Optional.of;
import static rx.Observable.just;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;

import com.github.davidmoten.util.Optional;
import com.github.davidmoten.util.Preconditions;

import rx.Observable;
import rx.Scheduler;
import rx.functions.Action1;
import rx.functions.Func1;
import rx.functions.Func2;
import rx.schedulers.Schedulers;

/**
 * Provides builder for the {@link Func1} parameter of
 * {@link Observable#retryWhen(Func1)}. For example:
 * 
 * 
 * o.retryWhen(RetryWhen.maxRetries(4).delay(10, TimeUnit.SECONDS).action(log).build());
 * 
* *

* or *

* *
 * o.retryWhen(RetryWhen.exponentialBackoff(100, TimeUnit.MILLISECONDS).maxRetries(10).build());
 * 
*/ public final class RetryWhen { private static final long NO_MORE_DELAYS = -1; private static Func1, Observable> notificationHandler( final Observable delays, final Scheduler scheduler, final Action1 action, final List> retryExceptions, final List> failExceptions, final Func1 exceptionPredicate) { final Func1> checkExceptions = createExceptionChecker( retryExceptions, failExceptions, exceptionPredicate); return createNotificationHandler(delays, scheduler, action, checkExceptions); } private static Func1, Observable> createNotificationHandler( final Observable delays, final Scheduler scheduler, final Action1 action, final Func1> checkExceptions) { return new Func1, Observable>() { @Override public Observable call(Observable errors) { return errors // zip with delays, use -1 to signal completion .zipWith(delays.concatWith(just(NO_MORE_DELAYS)), TO_ERROR_AND_DURATION) // check retry and non-retry exceptions .flatMap(checkExceptions) // perform user action (for example log that a // delay is happening) .doOnNext(callActionExceptForLast(action)) // delay the time in ErrorAndDuration .flatMap(delay(scheduler)); } }; } private static Action1 callActionExceptForLast(final Action1 action) { return new Action1() { @Override public void call(ErrorAndDuration e) { if (e.durationMs() != NO_MORE_DELAYS) action.call(e); } }; } // TODO unit test private static Func1> createExceptionChecker( final List> retryExceptions, final List> failExceptions, final Func1 exceptionPredicate) { return new Func1>() { @Override public Observable call(ErrorAndDuration e) { if (!exceptionPredicate.call(e.throwable())) return Observable.error(e.throwable()); for (Class cls : failExceptions) { if (e.throwable().getClass().isAssignableFrom(cls)) return Observable.error(e.throwable()); } if (retryExceptions.size() > 0) { for (Class cls : retryExceptions) { if (e.throwable().getClass().isAssignableFrom(cls)) return just(e); } return Observable.error(e.throwable()); } else { return just(e); } } }; } private static Func2 TO_ERROR_AND_DURATION = new Func2() { @Override public ErrorAndDuration call(Throwable throwable, Long durationMs) { return new ErrorAndDuration(throwable, durationMs); } }; private static Func1> delay(final Scheduler scheduler) { return new Func1>() { @Override public Observable call(ErrorAndDuration e) { if (e.durationMs() == NO_MORE_DELAYS) return Observable.error(e.throwable()); else return Observable.timer(e.durationMs(), TimeUnit.MILLISECONDS, scheduler) .map(Functions.constant(e)); } }; } // Builder factory methods public static Builder retryWhenInstanceOf(Class... classes) { return new Builder().retryWhenInstanceOf(classes); } public static Builder failWhenInstanceOf(Class... classes) { return new Builder().failWhenInstanceOf(classes); } public static Builder retryIf(Func1 predicate) { return new Builder().retryIf(predicate); } public static Builder delays(Observable delays, TimeUnit unit) { return new Builder().delays(delays, unit); } public static Builder delaysInt(Observable delays, TimeUnit unit) { return new Builder().delaysInt(delays, unit); } public static Builder delay(long delay, final TimeUnit unit) { return new Builder().delay(delay, unit); } public static Builder maxRetries(int maxRetries) { return new Builder().maxRetries(maxRetries); } public static Builder scheduler(Scheduler scheduler) { return new Builder().scheduler(scheduler); } public Builder action(Action1 action) { return new Builder().action(action); } public static Builder exponentialBackoff(final long firstDelay, final TimeUnit unit, final double factor) { return new Builder().exponentialBackoff(firstDelay, unit, factor); } public static Builder exponentialBackoff(long firstDelay, TimeUnit unit) { return new Builder().exponentialBackoff(firstDelay, unit); } public static final class Builder { private final List> retryExceptions = new ArrayList>(); private final List> failExceptions = new ArrayList>(); private Func1 exceptionPredicate = Functions.alwaysTrue(); private Observable delays = Observable.just(0L).repeat(); private Optional maxRetries = absent(); private Optional scheduler = of(Schedulers.computation()); private Action1 action = Actions.doNothing1(); private Builder() { // must use static factory method to instantiate } public Builder retryWhenInstanceOf(Class... classes) { retryExceptions.addAll(Arrays.asList(classes)); return this; } public Builder failWhenInstanceOf(Class... classes) { failExceptions.addAll(Arrays.asList(classes)); return this; } public Builder retryIf(Func1 predicate) { this.exceptionPredicate = predicate; return this; } public Builder delays(Observable delays, TimeUnit unit) { this.delays = delays.map(toMillis(unit)); return this; } private static class ToLongHolder { static final Func1 INSTANCE = new Func1() { @Override public Long call(Integer n) { if (n == null) { return null; } else { return n.longValue(); } } }; } public Builder delaysInt(Observable delays, TimeUnit unit) { return delays(delays.map(ToLongHolder.INSTANCE), unit); } public Builder delay(Long delay, final TimeUnit unit) { this.delays = Observable.just(delay).map(toMillis(unit)).repeat(); return this; } private static Func1 toMillis(final TimeUnit unit) { return new Func1() { @Override public Long call(Long t) { return unit.toMillis(t); } }; } public Builder maxRetries(int maxRetries) { this.maxRetries = of(maxRetries); return this; } public Builder scheduler(Scheduler scheduler) { this.scheduler = of(scheduler); return this; } public Builder action(Action1 action) { this.action = action; return this; } public Builder exponentialBackoff(final long firstDelay, final TimeUnit unit, final double factor) { delays = Observable.range(1, Integer.MAX_VALUE) // make exponential .map(new Func1() { @Override public Long call(Integer n) { return Math.round(Math.pow(factor, n - 1) * unit.toMillis(firstDelay)); } }); return this; } public Builder exponentialBackoff(long firstDelay, TimeUnit unit) { return exponentialBackoff(firstDelay, unit, 2); } public Func1, Observable> build() { Preconditions.checkNotNull(delays); if (maxRetries.isPresent()) { delays = delays.take(maxRetries.get()); } return notificationHandler(delays, scheduler.get(), action, retryExceptions, failExceptions, exceptionPredicate); } } public static final class ErrorAndDuration { private final Throwable throwable; private final long durationMs; public ErrorAndDuration(Throwable throwable, long durationMs) { this.throwable = throwable; this.durationMs = durationMs; } public Throwable throwable() { return throwable; } public long durationMs() { return durationMs; } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy