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