#region License
/*
* Copyright 2002-2005 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#endregion
#region Imports
using System;
using System.Collections;
using System.Reflection;
using AopAlliance.Intercept;
using Common.Logging;
using Spring.Util;
#endregion
namespace Spring.Aop.Framework.Adapter{
/// <summary>Interceptor to wrap an after throwing advice.</summary>
/// <remarks>
/// <p>
/// Implementations of the <see cref="Spring.Aop.IThrowsAdvice"/> interface
/// <b>must</b> define methods of the form...
/// <code lang="C#">
/// AfterThrowing([MethodInfo method, Object[] args, Object target], Exception subclass);
/// </code>
/// The method name is fixed (i.e. your methods <b>must</b> be named
/// <c>AfterThrowing</c>. The first three arguments (<i>as a whole</i>) are
/// optional, and only useful if futher information about the joinpoint is
/// required. The return type <i>can</i> be anything, but is almost always
/// <see langword="void"/> by convention.
/// </p>
/// <p>
/// Please note that the object encapsulating the throws advice does not
/// need to implement the <see cref="Spring.Aop.IThrowsAdvice"/> interface.
/// Throws advice methods are discovered via reflection... the
/// <see cref="Spring.Aop.IThrowsAdvice"/> interface serves merely to
/// <i>discover</i> objects that are to be considered as throws advice.
/// Other mechanisms for discovering throws advice such as attributes are
/// also equally valid... all that this class cares about is that a throws
/// advice object implement one or more methods with a valid throws advice
/// signature (see above, and the examples below).
/// </p>
/// <p>
/// This is a framework class that should not normally need to be used
/// directly by Spring.NET users.
/// </p>
/// </remarks>
/// <example>
/// <p>
/// Find below some examples of valid <see cref="Spring.Aop.IThrowsAdvice"/>
/// method signatures...
/// </p>
/// <code language="C#">
/// public class GlobalExceptionHandlingAdvice : IThrowsAdvice
/// {
/// public void AfterThrowing(Exception ex) {
/// // handles absolutely any and every Exception...
/// }
/// }
/// </code>
/// <code language="C#">
/// public class RemotingExceptionHandlingAdvice : IThrowsAdvice
/// {
/// public void AfterThrowing(RemotingException ex) {
/// // handles any and every RemotingException (and subclasses of RemotingException)...
/// }
/// }
/// </code>
/// <code language="C#">
/// using System.Data;
///
/// public class DataExceptionHandlingAdvice
/// {
/// public void AfterThrowing(ConstraintException ex) {
/// // specialised handling of ConstraintExceptions
/// }
///
/// public void AfterThrowing(NoNullAllowedException ex) {
/// // specialised handling of NoNullAllowedExceptions
/// }
///
/// public void AfterThrowing(DataException ex) {
/// // handles all other DataExceptions...
/// }
/// }
/// </code>
/// </example>
/// <author>Rod Johnson</author>
/// <author>Aleksandar Seovic (.NET)</author>
/// <seealso cref="Spring.Aop.IThrowsAdvice"/>
[Serializable]
public sealed class ThrowsAdviceInterceptor : IMethodInterceptor
{
private static readonly ILog log = LogManager.GetLogger(typeof(ThrowsAdviceInterceptor));
private const string SpecialThrowingMethodName = "AfterThrowing";
private readonly object throwsAdvice;
/// <summary>
/// The mapping of exception Types to MethodInfo handlers.
/// </summary>
private readonly IDictionary exceptionHandlers;
/// <summary>
/// Creates a new instance of the
/// <see cref="Spring.Aop.Framework.Adapter.ThrowsAdviceInterceptor"/> class.
/// </summary>
/// <remarks>
/// </remarks>
/// <param name="advice">
/// The throws advice to check for exception handler methods.
/// </param>
/// <exception cref="System.ArgumentNullException">
/// If the supplied <paramref name="advice"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="System.ArgumentException">
/// If no (0) handler methods were discovered on the supplied <paramref name="advice"/>;
/// or if more than one handler method suitable for a particular
/// <see cref="System.Exception"/> type was discovered on the supplied
/// <paramref name="advice"/>.
/// </exception>
public ThrowsAdviceInterceptor(object advice)
{
AssertUtils.ArgumentNotNull(advice, "advice");
this.exceptionHandlers = new Hashtable();
this.throwsAdvice = advice;
MapAllExceptionHandlingMethods(advice);
if (exceptionHandlers.Count == 0)
{
throw new ArgumentException(
"At least one handler method must be found in class ["
+ advice.GetType().FullName + "].");
}
}
private void MapAllExceptionHandlingMethods(object advice)
{
MethodInfo[] methods = advice.GetType().GetMethods();
foreach (MethodInfo method in methods)
{
int numParams = method.GetParameters().Length;
if (method.Name.Equals(SpecialThrowingMethodName)
&& (numParams == 1 || numParams == 4))
{
Type lastParametersType = method.GetParameters()[numParams - 1].ParameterType;
if (typeof (Exception).IsAssignableFrom(lastParametersType))
{
#region Instrumentation
if(log.IsDebugEnabled)
{
log.Debug("Found exception handler method: " + method);
}
#endregion
if(this.exceptionHandlers.Contains(lastParametersType))
{
throw new ArgumentException(
"Throws advice handler method for the [" +
lastParametersType + "] type already exists; don't define " +
"both single and multiple argument methods for the same " +
"Exception type in the same class.");
}
this.exceptionHandlers[lastParametersType] = method;
}
}
}
}
/// <summary>
/// Convenience property that returns the number of exception handler
/// methods managed by this interceptor.
/// </summary>
/// <value>
/// The number of exception handler methods managed by this interceptor.
/// </value>
public int HandlerMethodCount
{
get { return exceptionHandlers.Count; }
}
/// <summary>
/// Executes interceptor if (and only if) the supplied
/// <paramref name="invocation"/> throws an exception that is mapped to
/// an appropriate exception handler.
/// </summary>
/// <param name="invocation">
/// The method invocation that is being intercepted.
/// </param>
/// <returns>
/// The result of the call to the
/// <see cref="AopAlliance.Intercept.IJoinpoint.Proceed"/> method of
/// the supplied <paramref name="invocation"/> (this assumes no
/// exception was thrown by the call to the supplied <paramref name="invocation"/>.
/// </returns>
/// <exception cref="System.Exception">
/// If any of the interceptors in the chain or the target object itself
/// throws an exception.
/// </exception>
/// <seealso cref="AopAlliance.Intercept.IMethodInterceptor.Invoke"/>
public object Invoke(IMethodInvocation invocation)
{
try
{
return invocation.Proceed();
}
catch (TargetInvocationException ex)
{
// bah, this is a tad gross...
Exception realException = ex.InnerException;
LookupAndInvokeAnyHandler(realException, invocation);
throw realException;
}
catch (Exception ex)
{
LookupAndInvokeAnyHandler(ex, invocation);
throw;
}
}
private void LookupAndInvokeAnyHandler(Exception ex, IMethodInvocation invocation)
{
MethodInfo handlerMethod = GetExceptionHandler(ex);
if (handlerMethod != null)
{
InvokeHandlerMethod(invocation, ex, handlerMethod);
}
}
/// <summary>
/// Gets the exception handler (if any) that has been mapped to the
/// supplied <paramref name="exception"/>.
/// </summary>
/// <remarks>
/// <p>
/// Will return <cref lang="null"/> if not found.
/// </p>
/// </remarks>
/// <returns>
/// The exception handler for the <see cref="System.Type"/> of the
/// supplied <paramref name="exception"/> given exception.
/// </returns>
/// <param name="exception">exception that was thrown</param>
private MethodInfo GetExceptionHandler(Exception exception)
{
Type exceptionClass = exception.GetType();
#region Instrumentation
if(log.IsDebugEnabled)
{
log.Debug("Trying to find handler for exception of type [" + exception.GetType().Name + "].");
}
#endregion
MethodInfo handler = (MethodInfo) this.exceptionHandlers[exceptionClass];
while (handler == null && !exceptionClass.Equals(typeof(Exception)))
{
exceptionClass = exceptionClass.BaseType;
handler = (MethodInfo) this.exceptionHandlers[exceptionClass];
}
return handler;
}
/// <summary>
/// Invokes handler method with appropriate number of parameters
/// </summary>
/// <param name="invocation">
/// The original method invocation that was intercepted.
/// </param>
/// <param name="triggeringException">
/// The exception that triggered this interceptor.
/// </param>
/// <param name="handlerMethod">
/// The exception handler method to invoke.
/// </param>
private void InvokeHandlerMethod(
IMethodInvocation invocation, Exception triggeringException, MethodInfo handlerMethod)
{
object[] handlerArgs;
if (handlerMethod.GetParameters().Length == 1)
{
handlerArgs = new object[] {triggeringException};
}
else
{
handlerArgs = new object[] {invocation.Method, invocation.Arguments, invocation.This, triggeringException};
}
try
{
handlerMethod.Invoke(this.throwsAdvice, handlerArgs);
}
catch (TargetInvocationException ex)
{
throw ReflectionUtils.UnwrapTargetInvocationException(ex);
}
}
}
}
|