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

com.github.richardballard.arbeeutils.misc.TimeBoundAttempter Maven / Gradle / Ivy

There is a newer version: 2.0
Show newest version
/*
 * (C) Copyright 2016 Richard Ballard.
 *
 * 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.github.richardballard.arbeeutils.misc;


import net.jcip.annotations.Immutable;
import net.jcip.annotations.ThreadSafe;
import com.github.richardballard.arbeeutils.numeric.Count;
import com.github.richardballard.arbeeutils.time.TimeTick;
import org.jetbrains.annotations.NotNull;

import java.time.Duration;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;

/**
 * This class has the concept of an operation that is performed - if it fails then it has the option to ask for a retry
 * (within a max duration).  This is all run on the current thread.
 */
@ThreadSafe
public class TimeBoundAttempter {

    @NotNull
    private final Duration maxOperationDuration;

    @NotNull
    private final Supplier currentTimeTickSupplier;

    @NotNull
    private final Consumer> attemptFailedConsumer;

    @NotNull
    private final Function, ? extends T> allAttemptsFailedFunction;


    /**
     *
     * @param maxOperationDuration the max duration that will be spent on retrying an unsuccessful operation
     * @param attemptFailedConsumer if an attempt fails then this will be called. Typically this will be a delay.
     * @param allAttemptsFailedFunction if all attempts fail then this will be called.  The result of this function
     *                                  will be used as the result of the call to {@link #performOperation(Supplier)}
     */
    public TimeBoundAttempter(@NotNull final Duration maxOperationDuration,
                              @NotNull final Supplier currentTimeTickSupplier,
                              @NotNull final Consumer> attemptFailedConsumer,
                              @NotNull final Function, ? extends T> allAttemptsFailedFunction) {
        assert maxOperationDuration != null;
        assert currentTimeTickSupplier != null;
        assert attemptFailedConsumer != null;
        assert allAttemptsFailedFunction != null;

        this.maxOperationDuration = maxOperationDuration;
        this.currentTimeTickSupplier = currentTimeTickSupplier;
        this.attemptFailedConsumer = attemptFailedConsumer;
        this.allAttemptsFailedFunction = allAttemptsFailedFunction;
    }

    /**
     * @param operation this should throw {@link RetryRequestedException} if the operation should be retried
     * @return the operation result (if successful) or {@code allAttemptsFailedFunction} result if failed
     */
    public T performOperation(@NotNull final Supplier operation) {
        assert operation != null;

        final TimeTick startTimeTick = currentTimeTickSupplier.get();
        final TimeTick maxTimeTick = startTimeTick.plus(maxOperationDuration);

        for(int attemptCount = 1; ; attemptCount++) {
            try {
                return operation.get();
            }
            catch(final RetryRequestedException ignored) {
                final OperationWithAttemptCount operationWithAttemptCount
                        = new OperationWithAttemptCount<>(operation,
                                                          Count.valueOf(attemptCount,
                                                                        Count.OnExceedBoundary.THROW));

                // if it is not past max
                if(currentTimeTickSupplier.get().compareTo(maxTimeTick) <= 0) {
                    attemptFailedConsumer.accept(operationWithAttemptCount);
                }
                else {
                    return allAttemptsFailedFunction.apply(operationWithAttemptCount);
                }
            }
        }
    }

    public static class RetryRequestedException extends RuntimeException {

        private static final long serialVersionUID = 3301371646563173566L;
    }

    @Immutable
    public static class OperationWithAttemptCount {
        @NotNull
        private final Supplier operation;

        @NotNull
        private final Count attemptCount;

        public OperationWithAttemptCount(@NotNull final Supplier operation,
                                         @NotNull final Count attemptCount) {
            assert operation != null;
            assert attemptCount != null;

            this.operation = operation;
            this.attemptCount = attemptCount;
        }

        @NotNull
        public Supplier getOperation() {
            return operation;
        }

        @NotNull
        public Count getAttemptCount() {
            return attemptCount;
        }

        @Override
        public boolean equals(final Object o) {
            if(this == o) {
                return true;
            }
            if(o == null || getClass() != o.getClass()) {
                return false;
            }

            final OperationWithAttemptCount that = (OperationWithAttemptCount) o;

            if(!operation.equals(that.operation)) {
                return false;
            }
            return attemptCount.equals(that.attemptCount);

        }

        @Override
        public int hashCode() {
            int result = operation.hashCode();
            result = 31 * result + attemptCount.hashCode();
            return result;
        }

        @Override
        public String toString() {
            return "OperationWithAttemptCount{" +
                   "operation=" + operation +
                   ", attemptCount=" + attemptCount +
                   '}';
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy