001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.eclipse.aether.named.support;
020
021import java.util.concurrent.Callable;
022import java.util.concurrent.TimeUnit;
023import java.util.function.Predicate;
024
025import org.slf4j.Logger;
026import org.slf4j.LoggerFactory;
027
028/**
029 * Retry helper: retries given {@code Callable} as long as it returns {@code null} (interpreted
030 * as "no answer yet") or given time passes. This helper implements similar semantics regarding
031 * caller threads as {@link java.util.concurrent.locks.Lock#tryLock(long, TimeUnit)} method does:
032 * blocks the caller thread until operation return non-{@code null} value within the given waiting
033 * time and the current thread has not been {@linkplain Thread#interrupt interrupted}.
034 *
035 * @since 1.7.3
036 */
037public final class Retry {
038    private static final Logger LOGGER = LoggerFactory.getLogger(Retry.class);
039
040    private Retry() {
041        // no instances
042    }
043
044    /**
045     * Retries for given amount of time (time, unit) the passed in operation, sleeping given
046     * {@code sleepMills} between retries. In case operation returns {@code null}, it is assumed
047     * "is not done yet" state, so retry will happen (if time barrier allows). If time barrier
048     * passes, and still {@code null} ("is not done yet") is returned from operation, the
049     * {@code defaultResult} is returned.
050     */
051    public static <R> R retry(
052            final long time,
053            final TimeUnit unit,
054            final long sleepMillis,
055            final Callable<R> operation,
056            final Predicate<Exception> retryPredicate,
057            final R defaultResult)
058            throws InterruptedException {
059        long now = System.nanoTime();
060        final long barrier = now + unit.toNanos(time);
061        int attempt = 1;
062        R result = null;
063        while (now < barrier && result == null) {
064            try {
065                result = operation.call();
066                if (result == null) {
067                    LOGGER.trace("Retry attempt {}: no result", attempt);
068                    Thread.sleep(sleepMillis);
069                }
070            } catch (InterruptedException e) {
071                throw e;
072            } catch (Exception e) {
073                LOGGER.trace("Retry attempt {}: operation failure", attempt, e);
074                if (retryPredicate != null && !retryPredicate.test(e)) {
075                    throw new IllegalStateException(e);
076                }
077            }
078            now = System.nanoTime();
079            attempt++;
080        }
081        return result == null ? defaultResult : result;
082    }
083
084    /**
085     * Retries attempting max given times the passed in operation, sleeping given
086     * {@code sleepMills} between retries. In case operation returns {@code null}, it is assumed
087     * "is not done yet" state, so retry will happen (if attempt count allows). If all attempts
088     * used, and still {@code null} ("is not done yet") is returned from operation, the
089     * {@code defaultResult} is returned.
090     * <p>
091     * Just to clear things up: 5 attempts is really 4 retries (once do it and retry 4 times). 0 attempts means
092     * "do not even try it", and this method returns without doing anything.
093     */
094    public static <R> R retry(
095            final int attempts,
096            final long sleepMillis,
097            final Callable<R> operation,
098            final Predicate<Exception> retryPredicate,
099            final R defaultResult)
100            throws InterruptedException {
101        int attempt = 1;
102        R result = null;
103        while (attempt <= attempts && result == null) {
104            try {
105                result = operation.call();
106                if (result == null) {
107                    LOGGER.trace("Retry attempt {}: no result", attempt);
108                    Thread.sleep(sleepMillis);
109                }
110            } catch (InterruptedException e) {
111                throw e;
112            } catch (Exception e) {
113                LOGGER.trace("Retry attempt {}: operation failure", attempt, e);
114                if (retryPredicate != null && !retryPredicate.test(e)) {
115                    throw new IllegalStateException(e);
116                }
117            }
118            attempt++;
119        }
120        return result == null ? defaultResult : result;
121    }
122}