1 /*
2 * EL4J, the Extension Library for the J2EE, adds incremental enhancements to
3 * the spring framework, http://el4j.sf.net
4 * Copyright (C) 2005 by ELCA Informatique SA, Av. de la Harpe 22-24,
5 * 1000 Lausanne, Switzerland, http://www.elca.ch
6 *
7 * EL4J is published under the GNU Lesser General Public License (LGPL)
8 * Version 2.1. See http://www.gnu.org/licenses/
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Lesser General Public License for more details.
14 *
15 * For alternative licensing, please contact info@elca.ch
16 */
17
18 package ch.elca.el4j.services.exceptionhandler;
19
20 import java.lang.reflect.InvocationTargetException;
21 import java.lang.reflect.Method;
22
23 import org.aopalliance.intercept.MethodInterceptor;
24 import org.aopalliance.intercept.MethodInvocation;
25 import org.slf4j.Logger;
26 import org.slf4j.LoggerFactory;
27 import org.springframework.aop.framework.ReflectiveMethodInvocation;
28 import org.springframework.aop.target.HotSwappableTargetSource;
29
30 /**
31 * This class provides functionality to handle exceptions centrally (with
32 * respect to a number of classes or the whole system).
33 *
34 * @svnLink $Revision: 3873 $;$Date: 2009-08-04 13:59:45 +0200 (Di, 04. Aug 2009) $;$Author: swismer $;$URL: https://el4j.svn.sourceforge.net/svnroot/el4j/branches/el4j_3_1/el4j/framework/modules/exception_handling/src/main/java/ch/elca/el4j/services/exceptionhandler/AbstractExceptionHandlerInterceptor.java $
35 *
36 * @author Andreas Bur (ABU)
37 */
38 public abstract class AbstractExceptionHandlerInterceptor
39 implements MethodInterceptor {
40
41 /** Holds the number of retries. */
42 protected static final ThreadLocal s_retries = new ThreadLocal();
43
44 /** The static logger. */
45 private static Logger s_logger = LoggerFactory.getLogger(
46 AbstractExceptionHandlerInterceptor.class);
47
48 /**
49 * Holds the exception interceptor's default behaviour when no appropriate
50 * exception handler was found.
51 */
52 private boolean m_defaultBehaviourConsume = true;
53
54 /**
55 * Holds whether to handle all exceptions, even those which are defined in
56 * a method's signature.
57 */
58 private boolean m_forwardSignatureExceptions;
59
60 /**
61 * Holds whether to handle runtime exceptions that are listed in a
62 * method's signature.
63 */
64 private boolean m_handleRTSignatureExceptions;
65
66 /**
67 * @return Returns the number of retries left, allowing
68 * {@link
69 * ch.elca.el4j.services.exceptionhandler.handler.AbstractRetryExceptionHandler}s
70 * to determine whether they have to call their handling routines.
71 * <code>-1</code> signals that no retires have been registered so far.
72 */
73 public static int getRetries() {
74 Object retries = s_retries.get();
75 if (retries == null) {
76 return -1;
77 } else {
78 return ((Integer) retries).intValue();
79 }
80 }
81
82 /**
83 * Sets the number of retries. Each retry reinvokes the the interceptor
84 * chain as it would have been called by the client directly.
85 *
86 * @param retries
87 * The number of retries to set.
88 */
89 protected static void setRetries(int retries) {
90 s_retries.set(Integer.valueOf(retries));
91 }
92
93 /**
94 * Sets whether runtime exceptions, that are listed in a method's signature,
95 * have to be handled.
96 *
97 * @param handleRTSignatureExceptions
98 * <code>true</code> to handle unchecked exceptions by this exception
99 * handler, <code>false</code> to rethorw them.
100 */
101 public void setHandleRTSignatureExceptions(
102 boolean handleRTSignatureExceptions) {
103 m_handleRTSignatureExceptions = handleRTSignatureExceptions;
104 }
105
106 /**
107 * Sets the interceptor's exception handling default policy. This affects
108 * exceptions that aren't handled by any exception handler only. Default is
109 * to consume them.
110 *
111 * @param defaultBehaviourConsume
112 * <code>true</code> to consume all exceptions that are not handled by
113 * an exception handler, <code>false</code> to forward all unhandled
114 * exceptions.
115 */
116 public void setDefaultBehaviourConsume(boolean defaultBehaviourConsume) {
117 m_defaultBehaviourConsume = defaultBehaviourConsume;
118 }
119
120 /**
121 * Sets whether all exceptions have to be handled by this safety facade,
122 * even those which are listed in the signature.
123 *
124 * @param forwardSignatureException
125 * <code>true</code> forces to forward any exceptions listed in a
126 * method's interface (even unchecked exceptions, if they are in the
127 * signature). <code>false</code> handles all exceptions by an
128 * appropriate registered exception handler.
129 *
130 * @see #setHandleRTSignatureExceptions(boolean)
131 */
132 public void setForwardSignatureExceptions(
133 boolean forwardSignatureException) {
134 m_forwardSignatureExceptions = forwardSignatureException;
135 }
136
137 /**
138 * {@inheritDoc}
139 */
140 public Object invoke(MethodInvocation invocation) throws Throwable {
141 Object result = null;
142
143 try {
144 HotSwappableTargetSource swapper = null;
145
146 while (true) {
147 try {
148 if (swapper == null) {
149 /* Clones the invocation before each execution. This
150 * allows to execute the MethodInvocation.proceed()
151 * several times (see p. 163 in Java Development with
152 * the Spring Framework, Rod Johnson et al., 2005,
153 * Wiley, ISBN 0-7645-7483-3).
154 */
155 MethodInvocation localInvocation
156 = ((ReflectiveMethodInvocation) invocation).
157 invocableClone();
158
159 result = doInvoke(localInvocation);
160
161 } else {
162 result = invokeHotSwappable(swapper, invocation);
163 }
164 break;
165 } catch (RetryException re) {
166 int retries = getRetries();
167 if (retries == -1) {
168 retries = re.getRetries();
169 setRetries(retries);
170 }
171 if (retries > 0) {
172 setRetries(retries - 1);
173 swapper = re.getSwapper();
174
175 } else {
176 break;
177 }
178 }
179 }
180 } finally {
181 // clean up thread local
182 s_retries.set(null);
183 }
184
185 return result;
186 }
187
188 /**
189 * Performs the actual invocation of the
190 * <code>MethodInvocation.prceed()</code> method.
191 *
192 * @param invocation
193 * The <code>MethodInvocation</code> to call <code>proceed()</code> on.
194 *
195 * @return Returns the target's result;
196 *
197 * @throws RetryException
198 * Signals that the complete invocation has to be rerun.
199 *
200 * @throws Throwable
201 * Any exception thrown by the original method's invocation or by
202 * one of the used exception handlers.
203 */
204 protected Object doInvoke(MethodInvocation invocation)
205 throws RetryException, Throwable {
206 Object result = null;
207 try {
208 result = invocation.proceed();
209
210 } catch (RetryException re) {
211 throw re;
212 } catch (Throwable t) {
213 if (m_forwardSignatureExceptions) {
214 handleInterfaceExceptions(invocation, t);
215 }
216 result = handleException(t, invocation);
217 }
218 return result;
219 }
220
221 /**
222 * Handles the given exception that was thrown in the given method
223 * invocation's execution.
224 *
225 * @param t
226 * The exception to handle.
227 *
228 * @param invocation
229 * The method invocation that threw the exception.
230 *
231 * @return Returns an object which is treated as the original invocation's
232 * result.
233 *
234 * @throws RetryException
235 * Signals that the complete invocation has to be rerun.
236 *
237 * @throws Throwable
238 * Any exception thrown by a exception handler.
239 */
240 protected abstract Object handleException(Throwable t,
241 MethodInvocation invocation) throws RetryException, Throwable;
242
243 /**
244 * Handles exceptions that are listed in a method's signature.
245 *
246 * @param invocation
247 * The called method invocation.
248 *
249 * @param t
250 * The caught exception.
251 *
252 * @throws Throwable
253 * The given exception <code>t</code> if it has to be forwarded to the
254 * invoker.
255 */
256 protected void handleInterfaceExceptions(MethodInvocation invocation,
257 Throwable t) throws Throwable {
258 if (m_handleRTSignatureExceptions && t instanceof RuntimeException) {
259 return;
260 }
261
262 Class[] exceptions = invocation.getMethod().getExceptionTypes();
263 for (int i = 0; i < exceptions.length; i++) {
264 if (t.getClass() == exceptions[i]) {
265 s_logger.debug("Rethrowing exception (defined on signature).");
266 throw t;
267 }
268 }
269 }
270
271 /**
272 * Handles the given exception that was thrown given method invocation using
273 * the provided exception configurations.
274 *
275 * @param t
276 * The exception to handle.
277 *
278 * @param invocation
279 * The method invocation that threw the exception.
280 *
281 * @param exceptionConfigurations
282 * The exception handler configuration to use.
283 *
284 * @return Returns an object which is treated as the original invocation's
285 * result.
286 *
287 * @throws RetryException
288 * Signals that the complete invocation has to be rerun.
289 *
290 * @throws Throwable
291 * Any exception thrown by a exception handler.
292 */
293 protected Object doHandleException(Throwable t, MethodInvocation invocation,
294 ExceptionConfiguration[] exceptionConfigurations)
295 throws RetryException, Throwable {
296 for (int i = 0; i < exceptionConfigurations.length; i++) {
297
298 ExceptionConfiguration next = exceptionConfigurations[i];
299
300 if (next.handlesExceptions(t, invocation)) {
301 s_logger.debug(
302 "Found an appropriate exception handler.");
303
304 try {
305 return next.getExceptionHandler().
306 handleException(t, this, invocation);
307 } catch (InappropriateHandlerException whe) {
308 break;
309 }
310 }
311 }
312
313 s_logger.info("No appropriate exception handler found.", t);
314 if (!m_defaultBehaviourConsume) {
315 throw t;
316 }
317
318 return null;
319 }
320
321 /**
322 * Invokes the method on the object provided by the target source.
323 *
324 * @param swapper
325 * The hot swappable target source to get the target from.
326 *
327 * @param invocation
328 * The original invocation.
329 *
330 * @return Returns the result that was expected by the call on the original
331 * method.
332 *
333 * @throws Throwable
334 * Any exception.
335 */
336 private Object invokeHotSwappable(HotSwappableTargetSource swapper,
337 MethodInvocation invocation) throws Throwable {
338
339 /* HACK The swappable exception handler swaps the target of th
340 * TargetSource. But this change is not reflected in the current
341 * MethodInvocation which still points to the original target.
342 * Retrieving the TargetSource in the retry exception allows to
343 * get all the needed information to call the new target.
344 * This solution does not check if the called method does really
345 * interchange the original one (i.e. that its class is in the
346 * same hierarchy as the original one) which potentially leads to
347 * the problem that the invocation works if the original target's
348 * invocation fails and the retry is started in this method, but
349 * that it doesn't if the method is invoked again on the proxy.
350 */
351 Object result = null;
352
353 Object newTarget = swapper.getTarget();
354 Method origMethod = invocation.getMethod();
355 try {
356 Method method = newTarget.getClass().getMethod(
357 origMethod.getName(), origMethod.getParameterTypes());
358
359 result = method.invoke(newTarget, invocation.getArguments());
360
361 } catch (NoSuchMethodException nsme) {
362 // The new target does not implement the same interface. The
363 // RetryException forces to change the target again.
364 throw new RetryException(getRetries());
365
366 } catch (IllegalAccessException iae) {
367 // same as for NoSuchMethodException
368 throw new RetryException(getRetries());
369
370 } catch (InvocationTargetException ite) {
371 Throwable te = ite.getTargetException();
372 if (te instanceof UnsupportedOperationException) {
373 // same as for NoSuchMethodException
374 throw new RetryException(getRetries());
375 }
376 throw te;
377 }
378
379 return result;
380 }
381 }