View Javadoc
1   /*
2    * Copyright 2017 SPF4J.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.spf4j.failsafe;
17  
18  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
19  import java.util.Arrays;
20  import java.util.concurrent.Callable;
21  import java.util.function.Function;
22  import java.util.function.Supplier;
23  import javax.annotation.Nonnull;
24  import javax.annotation.Nullable;
25  import javax.annotation.concurrent.ThreadSafe;
26  import org.slf4j.Logger;
27  import org.slf4j.LoggerFactory;
28  import org.spf4j.log.Level;
29  import org.spf4j.log.SLf4jXLogAdapter;
30  import org.spf4j.log.XLog;
31  
32  /**
33   * @author Zoltan Farkas
34   */
35  @SuppressFBWarnings("AI_ANNOTATION_ISSUES_NEEDS_NULLABLE") // false positive...
36  @ThreadSafe
37  final class DefaultRetryPredicate<T> implements RetryPredicate<T, Callable<T>> {
38  
39    private static final Level RETRY_LOG_LEVEL =
40            Level.valueOf(System.getProperty("spf4j.failsafe.retryLogLevel", "WARN"));
41  
42    private static final PartialResultRetryPredicate[] NO_RP = new PartialResultRetryPredicate[0];
43  
44    private static final PartialExceptionRetryPredicate[] NO_EP = new PartialExceptionRetryPredicate[0];
45  
46    private static final Logger LOG =  LoggerFactory.getLogger(DefaultRetryPredicate.class);
47  
48    private final Function<Object, RetryDelaySupplier> defaultBackoffSupplier;
49  
50    private final PartialResultRetryPredicate<T, Callable<T>>[] resultPredicates;
51  
52    private final PartialExceptionRetryPredicate<T, Callable<T>>[] exceptionPredicates;
53  
54    private final XLog log;
55  
56    @SuppressFBWarnings("LO_SUSPECT_LOG_PARAMETER") // not suspect if you want to customize logging behavior.
57    DefaultRetryPredicate(@Nullable final Logger log, final long startNanos, final long deadlineNanos,
58            final Supplier<Function<Object, RetryDelaySupplier>> defaultBackoffSupplierSupplier,
59            final TimedSupplier<PartialResultRetryPredicate<T, Callable<T>>>[] resultPredicates,
60            final TimedSupplier<PartialExceptionRetryPredicate<T, Callable<T>>>... exceptionPredicates) {
61      this.log = new SLf4jXLogAdapter(log == null ? LOG : log);
62      this.defaultBackoffSupplier = defaultBackoffSupplierSupplier.get();
63      int rpl = resultPredicates.length;
64      if (rpl > 0) {
65        this.resultPredicates = new PartialResultRetryPredicate[rpl];
66        for (int i = 0; i < rpl; i++) {
67          this.resultPredicates[i] = resultPredicates[i].get(startNanos, deadlineNanos);
68        }
69      } else {
70        this.resultPredicates = NO_RP;
71      }
72      int epl = exceptionPredicates.length;
73      if (epl > 0) {
74        this.exceptionPredicates = new PartialExceptionRetryPredicate[epl];
75        for (int i = 0; i < epl; i++) {
76          this.exceptionPredicates[i] = exceptionPredicates[i].get(startNanos, deadlineNanos);
77        }
78      } else {
79        this.exceptionPredicates = NO_EP;
80      }
81    }
82  
83    @Override
84    @Nonnull
85    public RetryDecision<T, Callable<T>> getDecision(final T value, final Callable<T> what) {
86  
87      for (PartialResultRetryPredicate<T, Callable<T>> predicate : resultPredicates) {
88        RetryDecision<T, Callable<T>> decision = predicate.getDecision(value, what);
89        if (decision != null) {
90          if (decision.getDecisionType() == RetryDecision.Type.Retry) {
91            Callable<?> newCallable = decision.getNewCallable();
92            if (decision.getDelayNanos() < 0) {
93              RetryDelaySupplier backoff = defaultBackoffSupplier.apply(value);
94              decision = (RetryDecision) RetryDecision.retry(backoff.nextDelay(), newCallable);
95            }
96            log.log(null, RETRY_LOG_LEVEL, "Result {}, retrying {} with {}", value,  newCallable, decision);
97          }
98          return decision;
99        }
100     }
101     return RetryDecision.abortReturn(value);
102   }
103 
104   @Override
105   @Nonnull
106   public RetryDecision<T, Callable<T>> getExceptionDecision(final Throwable value, final Callable<T> what) {
107     for (PartialExceptionRetryPredicate<T, Callable<T>> predicate : exceptionPredicates) {
108       RetryDecision<T, Callable<T>> decision = predicate.getExceptionDecision(value, what);
109       if (decision != null) {
110         if (decision.getDecisionType() == RetryDecision.Type.Retry) {
111           Callable<?> newCallable = decision.getNewCallable();
112           if (decision.getDelayNanos() < 0) {
113             RetryDelaySupplier backoff = defaultBackoffSupplier.apply(value);
114             decision = (RetryDecision) RetryDecision.retry(backoff.nextDelay(), newCallable);
115           }
116           log.log(null, RETRY_LOG_LEVEL,
117                   "Result {}, retrying {} with {}", value.getClass().getName(), newCallable, decision, value);
118         }
119         return decision;
120       }
121     }
122     return RetryDecision.abortThrow(value);
123 
124   }
125 
126   @Override
127   public String toString() {
128     return "DefaultRetryPredicate{" + "defaultBackoffSupplier=" + defaultBackoffSupplier
129             + ", resultPredicates=" + Arrays.toString(resultPredicates)
130             + ", exceptionPredicates=" + Arrays.toString(exceptionPredicates) + '}';
131   }
132 
133 }