View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.eclipse.aether.named.support;
20  
21  import java.util.concurrent.Callable;
22  import java.util.concurrent.TimeUnit;
23  import java.util.function.Predicate;
24  
25  import org.slf4j.Logger;
26  import org.slf4j.LoggerFactory;
27  
28  /**
29   * Retry helper: retries given {@code Callable} as long as it returns {@code null} (interpreted
30   * as "no answer yet") or given time passes. This helper implements similar semantics regarding
31   * caller threads as {@link java.util.concurrent.locks.Lock#tryLock(long, TimeUnit)} method does:
32   * blocks the caller thread until operation return non-{@code null} value within the given waiting
33   * time and the current thread has not been {@linkplain Thread#interrupt interrupted}.
34   *
35   * @since 1.7.3
36   */
37  public final class Retry {
38      private static final Logger LOGGER = LoggerFactory.getLogger(Retry.class);
39  
40      private Retry() {
41          // no instances
42      }
43  
44      /**
45       * Retries for given amount of time (time, unit) the passed in operation, sleeping given
46       * {@code sleepMills} between retries. In case operation returns {@code null}, it is assumed
47       * "is not done yet" state, so retry will happen (if time barrier allows). If time barrier
48       * passes, and still {@code null} ("is not done yet") is returned from operation, the
49       * {@code defaultResult} is returned.
50       */
51      public static <R> R retry(
52              final long time,
53              final TimeUnit unit,
54              final long sleepMillis,
55              final Callable<R> operation,
56              final Predicate<Exception> retryPredicate,
57              final R defaultResult)
58              throws InterruptedException {
59          long now = System.nanoTime();
60          final long barrier = now + unit.toNanos(time);
61          int attempt = 1;
62          R result = null;
63          while (now < barrier && result == null) {
64              try {
65                  result = operation.call();
66                  if (result == null) {
67                      LOGGER.trace("Retry attempt {}: no result", attempt);
68                      Thread.sleep(sleepMillis);
69                  }
70              } catch (InterruptedException e) {
71                  throw e;
72              } catch (Exception e) {
73                  LOGGER.trace("Retry attempt {}: operation failure", attempt, e);
74                  if (retryPredicate != null && !retryPredicate.test(e)) {
75                      throw new IllegalStateException(e);
76                  }
77              }
78              now = System.nanoTime();
79              attempt++;
80          }
81          return result == null ? defaultResult : result;
82      }
83  
84      /**
85       * Retries attempting max given times the passed in operation, sleeping given
86       * {@code sleepMills} between retries. In case operation returns {@code null}, it is assumed
87       * "is not done yet" state, so retry will happen (if attempt count allows). If all attempts
88       * used, and still {@code null} ("is not done yet") is returned from operation, the
89       * {@code defaultResult} is returned.
90       * <p>
91       * Just to clear things up: 5 attempts is really 4 retries (once do it and retry 4 times). 0 attempts means
92       * "do not even try it", and this method returns without doing anything.
93       */
94      public static <R> R retry(
95              final int attempts,
96              final long sleepMillis,
97              final Callable<R> operation,
98              final Predicate<Exception> retryPredicate,
99              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 }