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

com.couchbase.client.java.util.retry.RetryBuilder Maven / Gradle / Ivy

/*
 * Copyright (c) 2016 Couchbase, Inc.
 *
 * 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 com.couchbase.client.java.util.retry;

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

import com.couchbase.client.core.annotations.InterfaceAudience;
import com.couchbase.client.core.annotations.InterfaceStability;
import com.couchbase.client.core.time.Delay;
import com.couchbase.client.java.error.CannotRetryException;
import rx.Scheduler;
import rx.functions.Action4;
import rx.functions.Func1;

/**
 * Builder for {@link RetryWhenFunction}. Start with {@link #any()}, {@link #anyOf(Class[])} or {@link #allBut(Class[])}
 * factory methods.
 *
 * By default, without calling additional methods it will retry on the specified exceptions, with a constant delay
 * (see {@link Retry#DEFAULT_DELAY}), and only once.
 *
 * Note that if retriable errors keep occurring more than the maximum allowed number of attempts, the last error that
 * triggered the extraneous attempt will be wrapped as the cause inside a {@link CannotRetryException}, which will be
 * emitted via the observable's onError method.
 *
 * @author Simon Baslé
 * @since 2.1
 */
@InterfaceStability.Committed
@InterfaceAudience.Public
public class RetryBuilder {

    private int maxAttempts;

    private Delay delay;

    private List> errorsStoppingRetry;
    private Action4 doOnRetryAction;
    private Func1 retryErrorPredicate;

    private boolean inverse;

    private Scheduler scheduler;


    private RetryBuilder() {
        this.maxAttempts = 1; //one attempt
        this.delay = Retry.DEFAULT_DELAY; //constant 1ms
        this.errorsStoppingRetry = null; //retry on any error
        this.inverse = false; //list above is indeed list of errors that can stop retry (none)
        this.scheduler = null; //operate on default Scheduler for timer delay
        this.doOnRetryAction = null; //no retry side effect
        this.retryErrorPredicate = null; //retry purely on the instanceOf
    }

    /** Only errors that are instanceOf the specified types will trigger a retry */
    public static RetryBuilder anyOf(Class... types) {
        RetryBuilder retryBuilder = new RetryBuilder();
        retryBuilder.maxAttempts = 1;

        retryBuilder.errorsStoppingRetry = Arrays.asList(types);
        retryBuilder.inverse = true;

        return retryBuilder;
    }

    /** Only errors that are NOT instanceOf the specified types will trigger a retry */
    public static RetryBuilder allBut(Class... types) {
        RetryBuilder retryBuilder = new RetryBuilder();
        retryBuilder.maxAttempts = 1;

        retryBuilder.errorsStoppingRetry = Arrays.asList(types);
        retryBuilder.inverse = false;

        return retryBuilder;
    }

    /** Any error will trigger a retry */
    public static RetryBuilder any() {
        RetryBuilder retryBuilder = new RetryBuilder();
        retryBuilder.maxAttempts = 1;
        return retryBuilder;
    }

    /** Any error that pass the predicate will trigger a retry */
    public static RetryBuilder anyMatches(Func1 retryErrorPredicate) {
        RetryBuilder retryBuilder = new RetryBuilder();
        retryBuilder.maxAttempts = 1;

        retryBuilder.retryErrorPredicate = retryErrorPredicate;
        return retryBuilder;
    }

    /**
     * Make only one retry attempt (default).
     *
     * If an error that can trigger a retry occurs twice in a row, it will be wrapped as the cause inside a
     * {@link CannotRetryException}, which will be emitted via the observable's onError method.
     */
    public RetryBuilder once() {
        this.maxAttempts = 1;
        return this;
    }

    /**
     * Make at most maxAttempts retry attempts.
     *
     * Note that the maximum accepted value is {@link Integer#MAX_VALUE}
     * - 1, the internal retry mechanism will ensure a total of maxAttempts + 1 total attempts,
     * accounting for the original call.
     *
     * If an error that can trigger a retry occurs more that maxAttempts, it will be wrapped as the
     * cause inside a {@link CannotRetryException}, which will be emitted via the observable's onError method.
     */
    public RetryBuilder max(int maxAttempts) {
        this.maxAttempts = Math.min(maxAttempts, Integer.MAX_VALUE - 1);
        return this;
    }

    /** Customize the retry {@link Delay} */
    public RetryBuilder delay(Delay delay) {
        return delay(delay, null);
    }

    /** Use {@link Retry#DEFAULT_DELAY} but wait on a specific {@link Scheduler} */
    public RetryBuilder delay(Scheduler scheduler) {
        return delay(null, scheduler);
    }

    /**
     * Set both the {@link Delay} and the {@link Scheduler} on which the delay is waited.
     * If the delay is null, {@link Retry#DEFAULT_DELAY} is used.
     */
    public RetryBuilder delay(Delay delay, Scheduler scheduler) {
        this.delay = (delay == null) ? Retry.DEFAULT_DELAY : delay;
        this.scheduler = scheduler;
        return this;
    }

    /**
     * Execute some code each time a retry is scheduled (at the moment the retriable exception
     * is caught, but before the retry delay is applied). Only quick executing code should be
     * performed, do not block in this action.
     *
     * The action receives the retry attempt number (1-n), the exception that caused the retry,
     * the delay duration and timeunit for the scheduled retry.
     *
     * @param doOnRetryAction the side-effect action to perform whenever a retry is scheduled.
     * @see OnRetryAction if you want a shorter signature.
     */
    public RetryBuilder doOnRetry(Action4 doOnRetryAction) {
        this.doOnRetryAction = doOnRetryAction;
        return this;
    }

    /** Construct the resulting {@link RetryWhenFunction} */
    public RetryWhenFunction build() {
        RetryWithDelayHandler handler;
        Func1 filter;
        if ((errorsStoppingRetry == null || errorsStoppingRetry.isEmpty()) && retryErrorPredicate == null) {
            //always retry on any error
            filter = null;
        } else if (retryErrorPredicate != null) {
            filter = new InversePredicate(retryErrorPredicate);
        } else {
            filter = new ShouldStopOnError(errorsStoppingRetry, inverse);
        }

        if (scheduler == null) {
            handler = new RetryWithDelayHandler(maxAttempts, delay, filter, doOnRetryAction);
        } else {
            handler = new RetryWithDelayHandler(maxAttempts, delay, filter, doOnRetryAction, scheduler);
        }
        return new RetryWhenFunction(handler);
    }

    protected static class InversePredicate implements Func1 {

        private final Func1 predicate;

        public InversePredicate(final Func1 predicate) {
            this.predicate = predicate;
        }

        @Override
        public Boolean call(Throwable throwable) {
            Boolean toInvert = predicate.call(throwable);
            if (toInvert == null) {
                return null;
            } else if (Boolean.TRUE.equals(toInvert)) {
                return Boolean.FALSE;
            }
            return Boolean.TRUE;
        }
    }

    protected static class ShouldStopOnError implements Func1 {

        private final List> errorsStoppingRetry;
        private final boolean inverse;

        public ShouldStopOnError(List> filterErrorList, boolean inverse) {
            this.errorsStoppingRetry = filterErrorList;
            this.inverse = inverse;
        }

        @Override
        public Boolean call(Throwable o) {
            //if inverse is false, only errors in the list should prevent retry
            //if inverse is true, all errors except ones in list should prevent retry
            for (Class aClass : errorsStoppingRetry) {
                if (aClass.isInstance(o)) {
                    return !inverse;
                }
            }
            return inverse;
        }
    }

    /**
     * An interface alias for Action4<Integer, Throwable, Long, TimeUnit>, suitable
     * for {@link RetryBuilder#doOnRetry(Action4)}.
     */
    public interface OnRetryAction extends Action4 {

    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy