View Javadoc

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 }