1
16
17 package org.springframework.retry.annotation;
18
19 import java.lang.reflect.Method;
20 import java.util.HashMap;
21 import java.util.Map;
22
23 import org.springframework.classify.SubclassClassifier;
24 import org.springframework.core.annotation.AnnotationUtils;
25 import org.springframework.retry.ExhaustedRetryException;
26 import org.springframework.retry.interceptor.MethodInvocationRecoverer;
27 import org.springframework.util.ReflectionUtils;
28 import org.springframework.util.ReflectionUtils.MethodCallback;
29
30
48 public class RecoverAnnotationRecoveryHandler<T> implements MethodInvocationRecoverer<T> {
49
50 private SubclassClassifier<Throwable, Method> classifier = new SubclassClassifier<Throwable, Method>();
51
52 private Map<Method, SimpleMetadata> methods = new HashMap<Method, SimpleMetadata>();
53
54 private Object target;
55
56 public RecoverAnnotationRecoveryHandler(Object target, Method method) {
57 this.target = target;
58 init(target, method);
59 }
60
61 @Override
62 public T recover(Object[] args, Throwable cause) {
63 Method method = findClosestMatch(args, cause.getClass());
64 if (method == null) {
65 throw new ExhaustedRetryException("Cannot locate recovery method", cause);
66 }
67 SimpleMetadata meta = this.methods.get(method);
68 Object[] argsToUse = meta.getArgs(cause, args);
69 boolean methodAccessible = method.isAccessible();
70 try {
71 ReflectionUtils.makeAccessible(method);
72 @SuppressWarnings("unchecked")
73 T result = (T) ReflectionUtils.invokeMethod(method, this.target, argsToUse);
74 return result;
75 }
76 finally {
77 if (methodAccessible != method.isAccessible()) {
78 method.setAccessible(methodAccessible);
79 }
80 }
81 }
82
83 private Method findClosestMatch(Object[] args, Class<? extends Throwable> cause) {
84 int min = Integer.MAX_VALUE;
85 Method result = null;
86 for (Method method : this.methods.keySet()) {
87 SimpleMetadata meta = this.methods.get(method);
88 Class<? extends Throwable> type = meta.getType();
89 if (type == null) {
90 type = Throwable.class;
91 }
92 if (type.isAssignableFrom(cause)) {
93 int distance = calculateDistance(cause, type);
94 if (distance < min) {
95 min = distance;
96 result = method;
97 }
98 else if (distance == min) {
99 boolean parametersMatch = compareParameters(args, meta.getArgCount(),
100 method.getParameterTypes());
101 if (parametersMatch) {
102 result = method;
103 }
104 }
105 }
106 }
107 return result;
108 }
109
110 private int calculateDistance(Class<? extends Throwable> cause,
111 Class<? extends Throwable> type) {
112 int result = 0;
113 Class<?> current = cause;
114 while (current != type && current != Throwable.class) {
115 result++;
116 current = current.getSuperclass();
117 }
118 return result;
119 }
120
121 private boolean compareParameters(Object[] args, int argCount,
122 Class<?>[] parameterTypes) {
123 if (argCount == (args.length + 1)) {
124 int startingIndex = 0;
125 if (parameterTypes.length > 0
126 && Throwable.class.isAssignableFrom(parameterTypes[0])) {
127 startingIndex = 1;
128 }
129 for (int i = startingIndex; i < parameterTypes.length; i++) {
130 final Object argument = i - startingIndex < args.length
131 ? args[i - startingIndex] : null;
132 if (argument == null) {
133 continue;
134 }
135 if (!parameterTypes[i].isAssignableFrom(argument.getClass())) {
136 return false;
137 }
138 }
139 return true;
140 }
141 return false;
142 }
143
144 private void init(Object target, Method method) {
145 final Map<Class<? extends Throwable>, Method> types = new HashMap<Class<? extends Throwable>, Method>();
146 final Method failingMethod = method;
147 ReflectionUtils.doWithMethods(failingMethod.getDeclaringClass(),
148 new MethodCallback() {
149 @Override
150 public void doWith(Method method)
151 throws IllegalArgumentException, IllegalAccessException {
152 Recover recover = AnnotationUtils.findAnnotation(method,
153 Recover.class);
154 if (recover != null && method.getReturnType()
155 .isAssignableFrom(failingMethod.getReturnType())) {
156 Class<?>[] parameterTypes = method.getParameterTypes();
157 if (parameterTypes.length > 0 && Throwable.class
158 .isAssignableFrom(parameterTypes[0])) {
159 @SuppressWarnings("unchecked")
160 Class<? extends Throwable> type = (Class<? extends Throwable>) parameterTypes[0];
161 types.put(type, method);
162 RecoverAnnotationRecoveryHandler.this.methods.put(method,
163 new SimpleMetadata(parameterTypes.length, type));
164 }
165 else {
166 RecoverAnnotationRecoveryHandler.this.classifier
167 .setDefaultValue(method);
168 RecoverAnnotationRecoveryHandler.this.methods.put(method,
169 new SimpleMetadata(parameterTypes.length, null));
170 }
171 }
172 }
173 });
174 this.classifier.setTypeMap(types);
175 optionallyFilterMethodsBy(failingMethod.getReturnType());
176 }
177
178 private void optionallyFilterMethodsBy(Class<?> returnClass) {
179 Map<Method, SimpleMetadata> filteredMethods = new HashMap<Method, SimpleMetadata>();
180 for (Method method : this.methods.keySet()) {
181 if (method.getReturnType() == returnClass) {
182 filteredMethods.put(method, this.methods.get(method));
183 }
184 }
185 if (filteredMethods.size() > 0) {
186 this.methods = filteredMethods;
187 }
188 }
189
190 private static class SimpleMetadata {
191
192 private int argCount;
193
194 private Class<? extends Throwable> type;
195
196 public SimpleMetadata(int argCount, Class<? extends Throwable> type) {
197 super();
198 this.argCount = argCount;
199 this.type = type;
200 }
201
202 public int getArgCount() {
203 return this.argCount;
204 }
205
206 public Class<? extends Throwable> getType() {
207 return this.type;
208 }
209
210 public Object[] getArgs(Throwable t, Object[] args) {
211 Object[] result = new Object[getArgCount()];
212 int startArgs = 0;
213 if (this.type != null) {
214 result[0] = t;
215 startArgs = 1;
216 }
217 int length = result.length - startArgs > args.length ? args.length
218 : result.length - startArgs;
219 if (length == 0) {
220 return result;
221 }
222 System.arraycopy(args, 0, result, startArgs, length);
223 return result;
224 }
225
226 }
227
228 }
229