1 /*
2  * Copyright 2014-2018 the original author or authors.
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  *      https://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
17 package org.springframework.retry.annotation;
18
19 import java.lang.annotation.Annotation;
20 import java.lang.reflect.Method;
21 import java.util.ArrayList;
22 import java.util.Collection;
23 import java.util.HashMap;
24 import java.util.Map;
25 import java.util.concurrent.atomic.AtomicBoolean;
26
27 import org.aopalliance.intercept.MethodInterceptor;
28 import org.aopalliance.intercept.MethodInvocation;
29
30 import org.springframework.aop.IntroductionInterceptor;
31 import org.springframework.beans.BeansException;
32 import org.springframework.beans.factory.BeanFactory;
33 import org.springframework.beans.factory.BeanFactoryAware;
34 import org.springframework.beans.factory.config.ConfigurableBeanFactory;
35 import org.springframework.context.expression.BeanFactoryResolver;
36 import org.springframework.core.annotation.AnnotationAwareOrderComparator;
37 import org.springframework.core.annotation.AnnotationUtils;
38 import org.springframework.expression.common.TemplateParserContext;
39 import org.springframework.expression.spel.standard.SpelExpressionParser;
40 import org.springframework.expression.spel.support.StandardEvaluationContext;
41 import org.springframework.retry.RetryListener;
42 import org.springframework.retry.RetryPolicy;
43 import org.springframework.retry.backoff.BackOffPolicy;
44 import org.springframework.retry.backoff.ExponentialBackOffPolicy;
45 import org.springframework.retry.backoff.ExponentialRandomBackOffPolicy;
46 import org.springframework.retry.backoff.FixedBackOffPolicy;
47 import org.springframework.retry.backoff.NoBackOffPolicy;
48 import org.springframework.retry.backoff.Sleeper;
49 import org.springframework.retry.backoff.UniformRandomBackOffPolicy;
50 import org.springframework.retry.interceptor.FixedKeyGenerator;
51 import org.springframework.retry.interceptor.MethodArgumentsKeyGenerator;
52 import org.springframework.retry.interceptor.MethodInvocationRecoverer;
53 import org.springframework.retry.interceptor.NewMethodArgumentsIdentifier;
54 import org.springframework.retry.interceptor.RetryInterceptorBuilder;
55 import org.springframework.retry.policy.CircuitBreakerRetryPolicy;
56 import org.springframework.retry.policy.ExpressionRetryPolicy;
57 import org.springframework.retry.policy.MapRetryContextCache;
58 import org.springframework.retry.policy.RetryContextCache;
59 import org.springframework.retry.policy.SimpleRetryPolicy;
60 import org.springframework.retry.support.RetryTemplate;
61 import org.springframework.util.ReflectionUtils;
62 import org.springframework.util.ReflectionUtils.MethodCallback;
63 import org.springframework.util.StringUtils;
64
65 /**
66  * Interceptor that parses the retry metadata on the method it is invoking and
67  * delegates to an appropriate RetryOperationsInterceptor.
68  *
69  * @author Dave Syer
70  * @author Artem Bilan
71  * @author Gary Russell
72  * @since 1.1
73  *
74  */

75 public class AnnotationAwareRetryOperationsInterceptor implements IntroductionInterceptor, BeanFactoryAware {
76
77     private static final TemplateParserContext PARSER_CONTEXT = new TemplateParserContext();
78
79     private static final SpelExpressionParser PARSER = new SpelExpressionParser();
80
81     private final StandardEvaluationContext evaluationContext = new StandardEvaluationContext();
82
83     private final Map<Object, Map<Method, MethodInterceptor>> delegates =
84             new HashMap<Object, Map<Method, MethodInterceptor>>();
85
86     private RetryContextCache retryContextCache = new MapRetryContextCache();
87
88     private MethodArgumentsKeyGenerator methodArgumentsKeyGenerator;
89
90     private NewMethodArgumentsIdentifier newMethodArgumentsIdentifier;
91
92     private Sleeper sleeper;
93
94     private BeanFactory beanFactory;
95
96     private RetryListener[] globalListeners;
97
98     /**
99      * @param sleeper the sleeper to set
100      */

101     public void setSleeper(Sleeper sleeper) {
102         this.sleeper = sleeper;
103     }
104
105     /**
106      * Public setter for the {@link RetryContextCache}.
107      *
108      * @param retryContextCache the {@link RetryContextCache} to set.
109      */

110     public void setRetryContextCache(RetryContextCache retryContextCache) {
111         this.retryContextCache = retryContextCache;
112     }
113
114     /**
115      * @param methodArgumentsKeyGenerator the {@link MethodArgumentsKeyGenerator}
116      */

117     public void setKeyGenerator(MethodArgumentsKeyGenerator methodArgumentsKeyGenerator) {
118         this.methodArgumentsKeyGenerator = methodArgumentsKeyGenerator;
119     }
120
121     /**
122      * @param newMethodArgumentsIdentifier the {@link NewMethodArgumentsIdentifier}
123      */

124     public void setNewItemIdentifier(NewMethodArgumentsIdentifier newMethodArgumentsIdentifier) {
125         this.newMethodArgumentsIdentifier = newMethodArgumentsIdentifier;
126     }
127
128     /**
129      * Default retry listeners to apply to all operations.
130      * @param globalListeners the default listeners
131      */

132     public void setListeners(Collection<RetryListener> globalListeners) {
133         ArrayList<RetryListener> retryListeners = new ArrayList<RetryListener>(globalListeners);
134         AnnotationAwareOrderComparator.sort(retryListeners);
135         this.globalListeners = retryListeners.toArray(new RetryListener[0]);
136     }
137
138     @Override
139     public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
140         this.beanFactory = beanFactory;
141         this.evaluationContext.setBeanResolver(new BeanFactoryResolver(beanFactory));
142     }
143
144     @Override
145     public boolean implementsInterface(Class<?> intf) {
146         return org.springframework.retry.interceptor.Retryable.class.isAssignableFrom(intf);
147     }
148
149     @Override
150     public Object invoke(MethodInvocation invocation) throws Throwable {
151         MethodInterceptor delegate = getDelegate(invocation.getThis(), invocation.getMethod());
152         if (delegate != null) {
153             return delegate.invoke(invocation);
154         }
155         else {
156             return invocation.proceed();
157         }
158     }
159
160     private MethodInterceptor getDelegate(Object target, Method method) {
161         if (!this.delegates.containsKey(target) || !this.delegates.get(target).containsKey(method)) {
162             synchronized (this.delegates) {
163                 if (!this.delegates.containsKey(target)) {
164                     this.delegates.put(target, new HashMap<Method, MethodInterceptor>());
165                 }
166                 Map<Method, MethodInterceptor> delegatesForTarget = this.delegates.get(target);
167                 if (!delegatesForTarget.containsKey(method)) {
168                     Retryable retryable = AnnotationUtils.findAnnotation(method, Retryable.class);
169                     if (retryable == null) {
170                         retryable = AnnotationUtils.findAnnotation(method.getDeclaringClass(), Retryable.class);
171                     }
172                     if (retryable == null) {
173                         retryable = findAnnotationOnTarget(target, method);
174                     }
175                     if (retryable == null) {
176                         return delegatesForTarget.put(method, null);
177                     }
178                     MethodInterceptor delegate;
179                     if (StringUtils.hasText(retryable.interceptor())) {
180                         delegate = this.beanFactory.getBean(retryable.interceptor(), MethodInterceptor.class);
181                     }
182                     else if (retryable.stateful()) {
183                         delegate = getStatefulInterceptor(target, method, retryable);
184                     }
185                     else {
186                         delegate = getStatelessInterceptor(target, method, retryable);
187                     }
188                     delegatesForTarget.put(method, delegate);
189                 }
190             }
191         }
192         return this.delegates.get(target).get(method);
193     }
194
195     private Retryable findAnnotationOnTarget(Object target, Method method) {
196         try {
197             Method targetMethod = target.getClass().getMethod(method.getName(), method.getParameterTypes());
198             Retryable retryable = AnnotationUtils.findAnnotation(targetMethod, Retryable.class);
199             if (retryable == null) {
200                 retryable = AnnotationUtils.findAnnotation(targetMethod.getDeclaringClass(), Retryable.class);
201             }
202
203             return retryable;
204         }
205         catch (Exception e) {
206             return null;
207         }
208     }
209
210     private MethodInterceptor getStatelessInterceptor(Object target, Method method, Retryable retryable) {
211         RetryTemplate template = createTemplate(retryable.listeners());
212         template.setRetryPolicy(getRetryPolicy(retryable));
213         template.setBackOffPolicy(getBackoffPolicy(retryable.backoff()));
214         return RetryInterceptorBuilder.stateless()
215                 .retryOperations(template)
216                 .label(retryable.label())
217                 .recoverer(getRecoverer(target, method))
218                 .build();
219     }
220
221     private MethodInterceptor getStatefulInterceptor(Object target, Method method, Retryable retryable) {
222         RetryTemplate template = createTemplate(retryable.listeners());
223         template.setRetryContextCache(this.retryContextCache);
224
225         CircuitBreaker circuit = AnnotationUtils.findAnnotation(method, CircuitBreaker.class);
226         if (circuit!=null) {
227             RetryPolicy policy = getRetryPolicy(circuit);
228             CircuitBreakerRetryPolicy breaker = new CircuitBreakerRetryPolicy(policy);
229             breaker.setOpenTimeout(getOpenTimeout(circuit));
230             breaker.setResetTimeout(getResetTimeout(circuit));
231             template.setRetryPolicy(breaker);
232             template.setBackOffPolicy(new NoBackOffPolicy());
233             String label = circuit.label();
234             if (!StringUtils.hasText(label))  {
235                 label = method.toGenericString();
236             }
237             return RetryInterceptorBuilder.circuitBreaker()
238                     .keyGenerator(new FixedKeyGenerator("circuit"))
239                     .retryOperations(template)
240                     .recoverer(getRecoverer(target, method))
241                     .label(label)
242                     .build();
243         }
244         RetryPolicy policy = getRetryPolicy(retryable);
245         template.setRetryPolicy(policy);
246         template.setBackOffPolicy(getBackoffPolicy(retryable.backoff()));
247         String label = retryable.label();
248         return RetryInterceptorBuilder.stateful()
249                 .keyGenerator(this.methodArgumentsKeyGenerator)
250                 .newMethodArgumentsIdentifier(this.newMethodArgumentsIdentifier)
251                 .retryOperations(template)
252                 .label(label)
253                 .recoverer(getRecoverer(target, method))
254                 .build();
255     }
256
257     private long getOpenTimeout(CircuitBreaker circuit) {
258         if (StringUtils.hasText(circuit.openTimeoutExpression())) {
259             Long value = PARSER.parseExpression(resolve(circuit.openTimeoutExpression()), PARSER_CONTEXT)
260                     .getValue(Long.class);
261             if (value != null) {
262                 return value;
263             }
264         }
265         return circuit.openTimeout();
266     }
267
268     private long getResetTimeout(CircuitBreaker circuit) {
269         if (StringUtils.hasText(circuit.resetTimeoutExpression())) {
270             Long value = PARSER.parseExpression(resolve(circuit.resetTimeoutExpression()), PARSER_CONTEXT)
271                     .getValue(Long.class);
272             if (value != null) {
273                 return value;
274             }
275         }
276         return circuit.resetTimeout();
277     }
278
279     private RetryTemplate createTemplate(String[] listenersBeanNames) {
280         RetryTemplate template = new RetryTemplate();
281         if (listenersBeanNames.length > 0) {
282             template.setListeners(getListenersBeans(listenersBeanNames));
283         } else if (globalListeners !=null) {
284             template.setListeners(globalListeners);
285         }
286         return template;
287     }
288
289     private RetryListener[] getListenersBeans(String[] listenersBeanNames) {
290         RetryListener[] listeners = new RetryListener[listenersBeanNames.length];
291         for (int i = 0; i < listeners.length; i++) {
292             listeners[i] = beanFactory.getBean(listenersBeanNames[i], RetryListener.class);
293         }
294         return listeners;
295     }
296
297     private MethodInvocationRecoverer<?> getRecoverer(Object target, Method method) {
298         if (target instanceof MethodInvocationRecoverer) {
299             return (MethodInvocationRecoverer<?>) target;
300         }
301         final AtomicBoolean foundRecoverable = new AtomicBoolean(false);
302         ReflectionUtils.doWithMethods(target.getClass(), new MethodCallback() {
303             @Override
304             public void doWith(Method method) throws IllegalArgumentException,
305                     IllegalAccessException {
306                 if (AnnotationUtils.findAnnotation(method, Recover.class) != null) {
307                     foundRecoverable.set(true);
308                 }
309             }
310         });
311
312         if (!foundRecoverable.get()) {
313             return null;
314         }
315         return new RecoverAnnotationRecoveryHandler<Object>(target, method);
316     }
317
318     private RetryPolicy getRetryPolicy(Annotation retryable) {
319         Map<String, Object> attrs = AnnotationUtils.getAnnotationAttributes(retryable);
320         @SuppressWarnings("unchecked")
321         Class<? extends Throwable>[] includes = (Class<? extends Throwable>[]) attrs.get("value");
322         String exceptionExpression = (String) attrs.get("exceptionExpression");
323         boolean hasExpression = StringUtils.hasText(exceptionExpression);
324         if (includes.length == 0) {
325             @SuppressWarnings("unchecked")
326             Class<? extends Throwable>[] value = (Class<? extends Throwable>[]) attrs.get("include");
327             includes = value;
328         }
329         @SuppressWarnings("unchecked")
330         Class<? extends Throwable>[] excludes = (Class<? extends Throwable>[]) attrs.get("exclude");
331         Integer maxAttempts = (Integer) attrs.get("maxAttempts");
332         String maxAttemptsExpression = (String) attrs.get("maxAttemptsExpression");
333         if (StringUtils.hasText(maxAttemptsExpression)) {
334             maxAttempts = PARSER.parseExpression(resolve(maxAttemptsExpression), PARSER_CONTEXT)
335                     .getValue(this.evaluationContext, Integer.class);
336         }
337         if (includes.length == 0 && excludes.length == 0) {
338             SimpleRetryPolicy simple = hasExpression ? new ExpressionRetryPolicy(resolve(exceptionExpression))
339                                                             .withBeanFactory(this.beanFactory)
340                                                      : new SimpleRetryPolicy();
341             simple.setMaxAttempts(maxAttempts);
342             return simple;
343         }
344         Map<Class<? extends Throwable>, Boolean> policyMap = new HashMap<Class<? extends Throwable>, Boolean>();
345         for (Class<? extends Throwable> type : includes) {
346             policyMap.put(type, true);
347         }
348         for (Class<? extends Throwable> type : excludes) {
349             policyMap.put(type, false);
350         }
351         boolean retryNotExcluded = includes.length == 0;
352         if (hasExpression) {
353             return new ExpressionRetryPolicy(maxAttempts, policyMap, true, exceptionExpression, retryNotExcluded)
354                     .withBeanFactory(this.beanFactory);
355         }
356         else {
357             return new SimpleRetryPolicy(maxAttempts, policyMap, true, retryNotExcluded);
358         }
359     }
360
361     private BackOffPolicy getBackoffPolicy(Backoff backoff) {
362         long min = backoff.delay() == 0 ? backoff.value() : backoff.delay();
363         if (StringUtils.hasText(backoff.delayExpression())) {
364             min = PARSER.parseExpression(resolve(backoff.delayExpression()), PARSER_CONTEXT)
365                     .getValue(this.evaluationContext, Long.class);
366         }
367         long max = backoff.maxDelay();
368         if (StringUtils.hasText(backoff.maxDelayExpression())) {
369             max = PARSER.parseExpression(resolve(backoff.maxDelayExpression()), PARSER_CONTEXT)
370                     .getValue(this.evaluationContext, Long.class);
371         }
372         double multiplier = backoff.multiplier();
373         if (StringUtils.hasText(backoff.multiplierExpression())) {
374             multiplier = PARSER.parseExpression(resolve(backoff.multiplierExpression()), PARSER_CONTEXT)
375                     .getValue(this.evaluationContext, Double.class);
376         }
377         if (multiplier > 0) {
378             ExponentialBackOffPolicy policy = new ExponentialBackOffPolicy();
379             if (backoff.random()) {
380                 policy = new ExponentialRandomBackOffPolicy();
381             }
382             policy.setInitialInterval(min);
383             policy.setMultiplier(multiplier);
384             policy.setMaxInterval(max > min ? max : ExponentialBackOffPolicy.DEFAULT_MAX_INTERVAL);
385             if (this.sleeper != null) {
386                 policy.setSleeper(this.sleeper);
387             }
388             return policy;
389         }
390         if (max > min) {
391             UniformRandomBackOffPolicy policy = new UniformRandomBackOffPolicy();
392             policy.setMinBackOffPeriod(min);
393             policy.setMaxBackOffPeriod(max);
394             if (this.sleeper != null) {
395                 policy.setSleeper(this.sleeper);
396             }
397             return policy;
398         }
399         FixedBackOffPolicy policy = new FixedBackOffPolicy();
400         policy.setBackOffPeriod(min);
401         if (this.sleeper != null) {
402             policy.setSleeper(this.sleeper);
403         }
404         return policy;
405     }
406
407     /**
408      * Resolve the specified value if possible.
409      *
410      * @see ConfigurableBeanFactory#resolveEmbeddedValue
411      */

412     private String resolve(String value) {
413         if (this.beanFactory != null && this.beanFactory instanceof ConfigurableBeanFactory) {
414             return ((ConfigurableBeanFactory) this.beanFactory).resolveEmbeddedValue(value);
415         }
416         return value;
417     }
418
419 }
420