1 /*
2  * Copyright 2014 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.LinkedHashSet;
22 import java.util.List;
23 import java.util.Set;
24 import java.util.concurrent.atomic.AtomicBoolean;
25
26 import javax.annotation.PostConstruct;
27
28 import org.aopalliance.aop.Advice;
29 import org.springframework.aop.ClassFilter;
30 import org.springframework.aop.IntroductionAdvisor;
31 import org.springframework.aop.MethodMatcher;
32 import org.springframework.aop.Pointcut;
33 import org.springframework.aop.support.AbstractPointcutAdvisor;
34 import org.springframework.aop.support.ComposablePointcut;
35 import org.springframework.aop.support.StaticMethodMatcherPointcut;
36 import org.springframework.aop.support.annotation.AnnotationClassFilter;
37 import org.springframework.aop.support.annotation.AnnotationMethodMatcher;
38 import org.springframework.beans.factory.BeanFactory;
39 import org.springframework.beans.factory.BeanFactoryAware;
40 import org.springframework.beans.factory.annotation.Autowired;
41 import org.springframework.context.annotation.Configuration;
42 import org.springframework.core.annotation.AnnotationUtils;
43 import org.springframework.retry.RetryListener;
44 import org.springframework.retry.backoff.Sleeper;
45 import org.springframework.retry.interceptor.MethodArgumentsKeyGenerator;
46 import org.springframework.retry.interceptor.NewMethodArgumentsIdentifier;
47 import org.springframework.retry.policy.RetryContextCache;
48 import org.springframework.util.ObjectUtils;
49 import org.springframework.util.ReflectionUtils;
50 import org.springframework.util.ReflectionUtils.MethodCallback;
51
52 /**
53  * Basic configuration for <code>@Retryable</code> processing. For stateful retry, if there is a unique bean elsewhere
54  * in the context of type {@link RetryContextCache}, {@link MethodArgumentsKeyGenerator} or
55  * {@link NewMethodArgumentsIdentifier} it will be used by the corresponding retry interceptor (otherwise sensible
56  * defaults are adopted).
57  *
58  * @author Dave Syer
59  * @author Artem Bilan
60  * @since 1.1
61  *
62  */

63 @SuppressWarnings("serial")
64 @Configuration
65 public class RetryConfiguration extends AbstractPointcutAdvisor implements IntroductionAdvisor, BeanFactoryAware {
66
67     private Advice advice;
68
69     private Pointcut pointcut;
70
71     @Autowired(required = false)
72     private RetryContextCache retryContextCache;
73
74     @Autowired(required = false)
75     private List<RetryListener> retryListeners;
76
77     @Autowired(required = false)
78     private MethodArgumentsKeyGenerator methodArgumentsKeyGenerator;
79
80     @Autowired(required = false)
81     private NewMethodArgumentsIdentifier newMethodArgumentsIdentifier;
82
83     @Autowired(required = false)
84     private Sleeper sleeper;
85
86     private BeanFactory beanFactory;
87
88     @PostConstruct
89     public void init() {
90         Set<Class<? extends Annotation>> retryableAnnotationTypes = new LinkedHashSet<Class<? extends Annotation>>(1);
91         retryableAnnotationTypes.add(Retryable.class);
92         this.pointcut = buildPointcut(retryableAnnotationTypes);
93         this.advice = buildAdvice();
94         if (this.advice instanceof BeanFactoryAware) {
95             ((BeanFactoryAware) this.advice).setBeanFactory(beanFactory);
96         }
97     }
98
99     /**
100      * Set the {@code BeanFactory} to be used when looking up executors by qualifier.
101      */

102     @Override
103     public void setBeanFactory(BeanFactory beanFactory) {
104         this.beanFactory = beanFactory;
105     }
106
107     @Override
108     public ClassFilter getClassFilter() {
109         return pointcut.getClassFilter();
110     }
111
112     @Override
113     public Class<?>[] getInterfaces() {
114         return new Class[] { org.springframework.retry.interceptor.Retryable.class };
115     }
116
117     @Override
118     public void validateInterfaces() throws IllegalArgumentException {
119     }
120
121     @Override
122     public Advice getAdvice() {
123         return this.advice;
124     }
125
126     @Override
127     public Pointcut getPointcut() {
128         return this.pointcut;
129     }
130
131     protected Advice buildAdvice() {
132         AnnotationAwareRetryOperationsInterceptor interceptor = new AnnotationAwareRetryOperationsInterceptor();
133         if (retryContextCache != null) {
134             interceptor.setRetryContextCache(retryContextCache);
135         }
136         if (retryListeners != null) {
137             interceptor.setListeners(retryListeners);
138         }
139         if (methodArgumentsKeyGenerator != null) {
140             interceptor.setKeyGenerator(methodArgumentsKeyGenerator);
141         }
142         if (newMethodArgumentsIdentifier != null) {
143             interceptor.setNewItemIdentifier(newMethodArgumentsIdentifier);
144         }
145         if (sleeper != null) {
146             interceptor.setSleeper(sleeper);
147         }
148         return interceptor;
149     }
150
151     /**
152      * Calculate a pointcut for the given retry annotation types, if any.
153      *
154      * @param retryAnnotationTypes the retry annotation types to introspect
155      * @return the applicable Pointcut object, or {@code nullif none
156      */

157     protected Pointcut buildPointcut(Set<Class<? extends Annotation>> retryAnnotationTypes) {
158         ComposablePointcut result = null;
159         for (Class<? extends Annotation> retryAnnotationType : retryAnnotationTypes) {
160             Pointcut filter = new AnnotationClassOrMethodPointcut(retryAnnotationType);
161             if (result == null) {
162                 result = new ComposablePointcut(filter);
163             }
164             else {
165                 result.union(filter);
166             }
167         }
168         return result;
169     }
170
171     private final class AnnotationClassOrMethodPointcut extends StaticMethodMatcherPointcut {
172
173         private final MethodMatcher methodResolver;
174
175         AnnotationClassOrMethodPointcut(Class<? extends Annotation> annotationType) {
176             this.methodResolver = new AnnotationMethodMatcher(annotationType);
177             setClassFilter(new AnnotationClassOrMethodFilter(annotationType));
178         }
179
180         @Override
181         public boolean matches(Method method, Class<?> targetClass) {
182             return getClassFilter().matches(targetClass) || this.methodResolver.matches(method, targetClass);
183         }
184         
185         @Override
186         public boolean equals(Object other) {
187             if (this == other) {
188                 return true;
189             }
190             if (!(other instanceof AnnotationClassOrMethodPointcut)) {
191                 return false;
192             }
193             AnnotationClassOrMethodPointcut otherAdvisor = (AnnotationClassOrMethodPointcut) other;
194             return ObjectUtils.nullSafeEquals(this.methodResolver, otherAdvisor.methodResolver);
195         }
196
197     }
198
199     private final class AnnotationClassOrMethodFilter extends AnnotationClassFilter {
200
201         private final AnnotationMethodsResolver methodResolver;
202
203         AnnotationClassOrMethodFilter(Class<? extends Annotation> annotationType) {
204             super(annotationType, true);
205             this.methodResolver = new AnnotationMethodsResolver(annotationType);
206         }
207
208         @Override
209         public boolean matches(Class<?> clazz) {
210             return super.matches(clazz) || this.methodResolver.hasAnnotatedMethods(clazz);
211         }
212
213     }
214
215     private static class AnnotationMethodsResolver {
216         
217         private Class<? extends Annotation> annotationType;
218         
219         public AnnotationMethodsResolver(Class<? extends Annotation> annotationType) {
220             this.annotationType = annotationType;
221         }
222         
223         public boolean hasAnnotatedMethods(Class<?> clazz) {
224             final AtomicBoolean found = new AtomicBoolean(false);
225             ReflectionUtils.doWithMethods(clazz,
226                     new MethodCallback() {
227                         @Override
228                         public void doWith(Method method) throws IllegalArgumentException,
229                                 IllegalAccessException {
230                             if (found.get()) {
231                                 return;
232                             }
233                             Annotation annotation = AnnotationUtils.findAnnotation(method,
234                                     annotationType);
235                             if (annotation != null) { found.set(true); }
236                         }
237             });
238             return found.get();
239         }
240         
241     }
242     
243 }
244