1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 package com.jcabi.aspects.aj;
31
32 import com.jcabi.aspects.Immutable;
33 import com.jcabi.aspects.RetryOnFailure;
34 import com.jcabi.log.Logger;
35 import java.lang.reflect.Method;
36 import java.security.SecureRandom;
37 import java.util.Random;
38 import org.aspectj.lang.ProceedingJoinPoint;
39 import org.aspectj.lang.annotation.Around;
40 import org.aspectj.lang.annotation.Aspect;
41 import org.aspectj.lang.reflect.MethodSignature;
42
43
44
45
46
47
48
49 @Aspect
50 @Immutable
51 public final class Repeater {
52
53
54
55
56 private static final Random RAND = new SecureRandom();
57
58
59
60
61
62
63
64
65
66
67
68 @Around("execution(* * (..)) && @annotation(com.jcabi.aspects.RetryOnFailure)")
69 @SuppressWarnings({ "PMD.AvoidCatchingThrowable", "PMD.GuardLogStatement" })
70 public Object wrap(final ProceedingJoinPoint point) throws Throwable {
71 final Method method = ((MethodSignature) point.getSignature())
72 .getMethod();
73 final RetryOnFailure rof = method.getAnnotation(RetryOnFailure.class);
74 int attempt = 0;
75 final long begin = System.nanoTime();
76 final Class<? extends Throwable>[] types = rof.types();
77 final ImprovedJoinPoint joinpoint = new ImprovedJoinPoint(point);
78 while (true) {
79 final long start = System.nanoTime();
80 try {
81 return point.proceed();
82 } catch (final InterruptedException ex) {
83 Thread.currentThread().interrupt();
84 throw ex;
85
86 } catch (final Throwable ex) {
87 if (Repeater.matches(ex.getClass(), rof.ignore())) {
88 throw ex;
89 }
90 if (!Repeater.matches(ex.getClass(), types)) {
91 throw ex;
92 }
93 ++attempt;
94 if (Logger.isWarnEnabled(joinpoint.targetize())) {
95 if (rof.verbose()) {
96 Logger.warn(
97 joinpoint.targetize(),
98
99 "#%s(): attempt #%d of %d failed in %[nano]s (%[nano]s waiting already) with %[exception]s",
100 method.getName(),
101 attempt, rof.attempts(), System.nanoTime() - start,
102 System.nanoTime() - begin, ex
103 );
104 } else {
105 Logger.warn(
106 joinpoint.targetize(),
107
108 "#%s(): attempt #%d/%d failed with %[type]s in %[nano]s (%[nano]s in total): %s",
109 method.getName(),
110 attempt, rof.attempts(), ex, System.nanoTime() - start,
111 System.nanoTime() - begin,
112 Repeater.message(ex)
113 );
114 }
115 }
116 if (attempt >= rof.attempts()) {
117 throw ex;
118 }
119 if (rof.delay() > 0L) {
120 this.delay(rof, attempt);
121 }
122 }
123 }
124 }
125
126
127
128
129
130
131
132 private void delay(final RetryOnFailure rof, final int attempt) throws
133 InterruptedException {
134 final long delay;
135 if (rof.randomize()) {
136 delay = (long) Repeater.RAND.nextInt(2 << attempt) * rof.delay();
137 } else {
138 delay = rof.delay() * (long) attempt;
139 }
140 rof.unit().sleep(delay);
141 }
142
143
144
145
146
147
148
149 private static String message(final Throwable exp) {
150 final StringBuilder text = new StringBuilder(0);
151 text.append(exp.getMessage());
152 if (exp.getCause() != null) {
153 text.append("; ").append(Repeater.message(exp.getCause()));
154 }
155 String msg = text.toString();
156 if (msg.length() > 100) {
157 msg = String.format("%s...", msg.substring(0, 100));
158 }
159 return msg;
160 }
161
162
163
164
165
166
167
168 @SafeVarargs
169 private static boolean matches(
170 final Class<? extends Throwable> thrown,
171 final Class<? extends Throwable>... types
172 ) {
173 boolean matches = false;
174 for (final Class<? extends Throwable> type : types) {
175 if (type.isAssignableFrom(thrown)) {
176 matches = true;
177 break;
178 }
179 }
180 return matches;
181 }
182
183 }