#region License
/*
* Copyright 2002-2007 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
using System;
using System.Collections;
using System.Reflection;
using AopAlliance.Intercept;
using Common.Logging;
using Spring.Context;
using Spring.Objects.Factory.Config;
using Spring.Util;
namespace Spring.Aspects.Exceptions{
/// <summary>
/// Exception advice to perform exception translation, conversion of exceptions to default return values, and
/// exception swallowing. Configuration is via a DSL like string for ease of use in common cases as well as
/// allowing for custom translation logic by leveraging the Spring expression language.
/// </summary>
/// <remarks>
/// <para>
/// The exception handler collection can be filled with either instances of objects that implement the interface
/// <see cref="IExceptionHandler"/> or a string that follows a simple syntax for most common exception management
/// needs. The source exceptions to perform processing on are listed immediately after the keyword 'on' and can
/// be comma delmited. Following that is the action to perform, either log, translate, wrap, replace, return, or
/// swallow. Following the action is a Spring expression language (SpEL) fragment that is used to either create the
/// translated/wrapped/replaced exception or specify an alternative return value. The variables available to be
/// used in the expression language fragment are, #method, #args, #target, and #e which are 1) the method that
/// threw the exception, the arguments to the method, the target object itself, and the exception that was thrown.
/// Using SpEL gives you great flexibility in creating a translation of an exception that has access to the calling context.
/// </para>
/// <para>Common translation cases, wrap and rethrow, are supported with a shorter syntax where you can specify only
/// the exception text for the new translated exception. If you ommit the exception text a default value will be
/// used.</para>
/// <para>The exceptionsHandlers are compared to the thrown exception in the order they are listed. logging
/// an exception will continue the evaluation process, in all other cases exceution stops at that point and the
/// appropriate exceptions handler is executed.</para>
/// <code escaped="true">
/// <property name="exceptionHandlers">
/// <list>
/// <value>on FooException1 log 'My Message, Method Name ' + #method.Name</value>
/// <value>on FooException1 translate new BarException('My Message, Method Called = ' + #method.Name", #e)</value>
/// <value>on FooException2,Foo3Exception wrap BarException 'My Bar Message'</value>
/// <value>on FooException4 replace BarException 'My Bar Message'</value>
/// <value>on FooException5 return 32</value>
/// <value>on FooException6 swallow</value>
/// <ref object="exceptionExpression"/>
/// </list>
/// </property>
/// </code>
///
/// </remarks>
/// <author>Mark Pollack</author>
[Serializable]
public class ExceptionHandlerAdvice : AbstractExceptionHandlerAdvice
{
#region Fields
/// <summary>
/// Log instance available to subclasses
/// </summary>
protected static ILog log = LogManager.GetLogger(typeof(ExceptionHandlerAdvice));
private IList exceptionHandlers = new ArrayList();
/// <summary>
/// Holds shared handler definition templates
/// </summary>
private ExceptionHandlerTable exceptionHandlerTable = new ExceptionHandlerTable();
private string onExceptionNameRegex = @"^(on\s+exception\s+name)\s+(.*?)\s+(log|translate|wrap|replace|return|swallow|execute)\s*(.*?)$";
private string onExceptionRegex = @"^(on\s+exception\s+)(\(.*?\))\s+(log|translate|wrap|replace|return|swallow|execute)\s*(.*?)$";
#endregion
#region Properties
/// <summary>
/// Gets or sets the Regex string used to parse advice expressions starting with 'on exception name' and exception handling actions.
/// </summary>
/// <value>The regex string to parse advice expressions starting with 'on exception name' and exception handling actions.</value>
public override string OnExceptionNameRegex
{
get { return onExceptionNameRegex; }
set { onExceptionNameRegex = value; }
}
/// <summary>
/// Gets or sets the Regex string used to parse advice expressions starting with 'on exception (constraint)' and exception handling actions.
/// </summary>
/// <value>The regex string to parse advice expressions starting with 'on exception (constraint)' and exception handling actions.</value>
public override string OnExceptionRegex
{
get { return onExceptionRegex; }
set { onExceptionRegex = value; }
}
/// <summary>
/// Gets or sets the exception handler.
/// </summary>
/// <value>The exception handler.</value>
public IList ExceptionHandlers
{
get { return exceptionHandlers; }
set { exceptionHandlers = value; }
}
/// <summary>
/// Gets the exception handler dictionary. Allows for registration of a specific handler where the key
/// is the action type. This makes configuration of a custom exception handler easier, for example
/// LogExceptionHandler, in that only 'user friendly' properties such as LogName, etc., need to be configured
/// and not 'user unfriendly' properties such as ConstraintExpressionText and ActionExpressionText.
/// </summary>
/// <value>The exception handler dictionary.</value>
public ExceptionHandlerTable ExceptionHandlerDictionary
{
get { return exceptionHandlerTable; }
}
#endregion
#region IMethodInterceptor implementation
/// <summary>
/// Implement this method to perform extra treatments before and after
/// the call to the supplied <paramref name="invocation"/>.
/// </summary>
/// <remarks>
/// <p>
/// Polite implementations would certainly like to invoke
/// <see cref="AopAlliance.Intercept.IJoinpoint.Proceed"/>.
/// </p>
/// </remarks>
/// <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 return value may
/// well have been intercepted by the interceptor.
/// </returns>
/// <exception cref="System.Exception">
/// If any of the interceptors in the chain or the target object itself
/// throws an exception.
/// </exception>
public override object Invoke(IMethodInvocation invocation)
{
try
{
return invocation.Proceed();
}
catch (TargetInvocationException ex)
{
Exception realException = ex.InnerException;
InvokeHandlers(realException, invocation);
throw realException;
}
catch (Exception ex)
{
object returnVal = InvokeHandlers(ex, invocation);
if (returnVal == null)
{
return null;
}
// if only logged
if (returnVal.Equals("logged"))
{
throw;
}
//only here if we only are swallowing, returning alternative value, no matching handler was found.
// no matching handler.
if (returnVal.Equals("nomatch"))
{
throw;
}
else
{
//TODO make spring specific value.
if (!returnVal.Equals("swallow"))
{
return returnVal;
}
else
{
Type returnType = invocation.Method.ReturnType;
return returnType.IsValueType && !returnType.Equals(typeof(void))? Activator.CreateInstance(returnType) : null;
}
}
}
}
#endregion
#region IInitializingObject implementation
/// <summary>
/// Invoked by an <see cref="Spring.Objects.Factory.IObjectFactory"/>
/// after it has injected all of an object's dependencies.
/// </summary>
/// <remarks>
/// <p>
/// This method allows the object instance to perform the kind of
/// initialization only possible when all of it's dependencies have
/// been injected (set), and to throw an appropriate exception in the
/// event of misconfiguration.
/// </p>
/// <p>
/// Please do consult the class level documentation for the
/// <see cref="Spring.Objects.Factory.IObjectFactory"/> interface for a
/// description of exactly <i>when</i> this method is invoked. In
/// particular, it is worth noting that the
/// <see cref="Spring.Objects.Factory.IObjectFactoryAware"/>
/// and <see cref="Spring.Context.IApplicationContextAware"/>
/// callbacks will have been invoked <i>prior</i> to this method being
/// called.
/// </p>
/// </remarks>
/// <exception cref="System.Exception">
/// In the event of misconfiguration (such as the failure to set a
/// required property) or if initialization fails.
/// </exception>
public override void AfterPropertiesSet()
{
if (exceptionHandlers.Count == 0)
{
throw new ArgumentException("At least one handler is required");
}
IList newExceptionHandlers = new ArrayList();
foreach (object o in exceptionHandlers)
{
string handlerString = o as string;
if (handlerString != null)
{
IExceptionHandler handler = Parse(handlerString);
if (handler == null)
{
throw new ArgumentException("Was not able to parse exception handler string [" + handlerString +
"]");
}
newExceptionHandlers.Add(handler);
}
//explicitly configured advice, must also configure ConstraintExpressionText and ActionExpressionText!
IExceptionHandler handlerObject = o as IExceptionHandler;
if (handlerObject != null)
{
newExceptionHandlers.Add(handlerObject);
}
}
//TODO sync.
exceptionHandlers = newExceptionHandlers;
}
#endregion
#region Methods
/// <summary>
/// Invokes handlers registered for the passed exception and <see cref="IMethodInvocation"/>
/// </summary>
/// <param name="ex">The exception to be handled</param>
/// <param name="invocation">The <see cref="IMethodInvocation"/> that raised this exception.</param>
/// <returns>The output of <see cref="IExceptionHandler.HandleException"/> </returns>
protected virtual object InvokeHandlers(Exception ex, IMethodInvocation invocation)
{
IDictionary callContextDictionary = new Hashtable();
callContextDictionary.Add("method", invocation.Method);
callContextDictionary.Add("args", invocation.Arguments);
callContextDictionary.Add("target", invocation.Target);
callContextDictionary.Add("e", ex);
object retValue = "nomatch";
foreach (IExceptionHandler handler in exceptionHandlers)
{
if (handler != null)
{
if (handler.CanHandleException(ex, callContextDictionary))
{
retValue = handler.HandleException(callContextDictionary);
if (!handler.ContinueProcessing)
{
return retValue;
}
}
}
}
return retValue;
}
/// <summary>
/// Parses the specified handler string, creating an instance of IExceptionHander.
/// </summary>
/// <param name="handlerString">The handler string.</param>
/// <returns>an instance of an exception handler or null if was not able to correctly parse
/// handler string.</returns>
protected virtual IExceptionHandler Parse(string handlerString)
{
ParsedAdviceExpression parsedAdviceExpression = ParseAdviceExpression(handlerString);
if (!parsedAdviceExpression.Success)
{
log.Warn("Could not parse exception hander statement " + handlerString);
return null;
}
return CreateExceptionHandler(parsedAdviceExpression);
}
/// <summary>
/// Creates the exception handler.
/// </summary>
/// <param name="parsedAdviceExpression">The parsed advice expression.</param>
/// <returns>The exception handler instance</returns>
protected virtual IExceptionHandler CreateExceptionHandler(ParsedAdviceExpression parsedAdviceExpression)
{
if (parsedAdviceExpression.ActionText.IndexOf("log") >= 0)
{
IExceptionHandler handler;
if (exceptionHandlerTable.ContainsKey("log"))
{
handler = exceptionHandlerTable["log"];
AddExceptionNames(parsedAdviceExpression, handler);
} else
{
handler = CreateLogExceptionHandler(parsedAdviceExpression.ExceptionNames);
}
handler.ConstraintExpressionText = parsedAdviceExpression.ConstraintExpression;
handler.ActionExpressionText = parsedAdviceExpression.ActionExpressionText;
return handler;
}
else if (parsedAdviceExpression.ActionText.IndexOf("translate") >= 0)
{
IExceptionHandler handler;
if (exceptionHandlerTable.Contains("translate"))
{
handler = exceptionHandlerTable["translate"];
AddExceptionNames(parsedAdviceExpression, handler);
}
else
{
handler = CreateTranslationExceptionHandler(parsedAdviceExpression.ExceptionNames);
}
handler.ConstraintExpressionText = parsedAdviceExpression.ConstraintExpression;
handler.ActionExpressionText = parsedAdviceExpression.ActionExpressionText;
return handler;
}
else if (parsedAdviceExpression.ActionText.IndexOf("wrap") >= 0)
{
IExceptionHandler handler;
if (exceptionHandlerTable.Contains("wrap"))
{
handler = exceptionHandlerTable["wrap"];
AddExceptionNames(parsedAdviceExpression, handler);
}
else
{
handler = CreateTranslationExceptionHandler(parsedAdviceExpression.ExceptionNames);
}
handler.ConstraintExpressionText = parsedAdviceExpression.ConstraintExpression;
handler.ActionExpressionText = ParseWrappedExceptionExpression("wrap", parsedAdviceExpression.AdviceExpression);
return handler;
}
else if (parsedAdviceExpression.ActionText.IndexOf("replace") >= 0)
{
IExceptionHandler handler;
if (exceptionHandlerTable.Contains("replace"))
{
handler = exceptionHandlerTable["replace"];
AddExceptionNames(parsedAdviceExpression, handler);
} else
{
handler = CreateTranslationExceptionHandler(parsedAdviceExpression.ExceptionNames);
}
handler.ConstraintExpressionText = parsedAdviceExpression.ConstraintExpression;
handler.ActionExpressionText = ParseWrappedExceptionExpression("replace", parsedAdviceExpression.AdviceExpression);
return handler;
}
else if (parsedAdviceExpression.ActionText.IndexOf("swallow") >= 0)
{
IExceptionHandler handler;
if (exceptionHandlerTable.Contains("swallow"))
{
handler = exceptionHandlerTable["swallow"];
AddExceptionNames(parsedAdviceExpression, handler);
}
else
{
handler = CreateSwallowExceptionHander(parsedAdviceExpression);
}
handler.ConstraintExpressionText = parsedAdviceExpression.ConstraintExpression;
return handler;
}
else if (parsedAdviceExpression.ActionText.IndexOf("return") >= 0)
{
IExceptionHandler handler;
if (exceptionHandlerTable.Contains("return"))
{
handler = exceptionHandlerTable["return"];
AddExceptionNames(parsedAdviceExpression, handler);
}
else
{
handler = CreateReturnValueExceptionHandler(parsedAdviceExpression);
}
handler.ConstraintExpressionText = parsedAdviceExpression.ConstraintExpression;
handler.ActionExpressionText = parsedAdviceExpression.ActionExpressionText;
return handler;
}
else if (parsedAdviceExpression.ActionText.IndexOf("execute") >= 0)
{
IExceptionHandler handler;
if (exceptionHandlerTable.Contains("execute"))
{
handler = exceptionHandlerTable["execute"];
AddExceptionNames(parsedAdviceExpression, handler);
}
else
{
handler = CreateExecuteSpelExceptionHandler(parsedAdviceExpression);
}
handler.ConstraintExpressionText = parsedAdviceExpression.ConstraintExpression;
handler.ActionExpressionText = parsedAdviceExpression.ActionExpressionText;
return handler;
}
else
{
log.Warn("Could not parse exception hander statement " + parsedAdviceExpression.AdviceExpression);
}
return null;
}
/// <summary>
/// Creates the execute spel exception handler.
/// </summary>
/// <param name="parsedAdviceExpression">The parsed advice expression.</param>
/// <returns></returns>
protected virtual IExceptionHandler CreateExecuteSpelExceptionHandler(ParsedAdviceExpression parsedAdviceExpression)
{
IExceptionHandler handler;
handler = new ExecuteSpelExceptionHandler(parsedAdviceExpression.ExceptionNames);
return handler;
}
/// <summary>
/// Creates the return value exception handler.
/// </summary>
/// <param name="parsedAdviceExpression">The parsed advice expression.</param>
/// <returns></returns>
protected virtual IExceptionHandler CreateReturnValueExceptionHandler(ParsedAdviceExpression parsedAdviceExpression)
{
IExceptionHandler handler;
handler = new ReturnValueExceptionHandler(parsedAdviceExpression.ExceptionNames);
return handler;
}
/// <summary>
/// Creates the swallow exception hander.
/// </summary>
/// <param name="parsedAdviceExpression">The parsed advice expression.</param>
/// <returns></returns>
protected virtual IExceptionHandler CreateSwallowExceptionHander(ParsedAdviceExpression parsedAdviceExpression)
{
IExceptionHandler handler;
handler = new SwallowExceptionHandler(parsedAdviceExpression.ExceptionNames);
return handler;
}
/// <summary>
/// Creates the translation exception handler.
/// </summary>
/// <param name="exceptionNames">The exception names.</param>
/// <returns></returns>
protected virtual IExceptionHandler CreateTranslationExceptionHandler(string[] exceptionNames)
{
return new TranslationExceptionHandler(exceptionNames);
}
/// <summary>
/// Creates the log exception handler.
/// </summary>
/// <param name="exceptionNames">The exception names.</param>
/// <returns>Log exception</returns>
protected virtual LogExceptionHandler CreateLogExceptionHandler(string[] exceptionNames)
{
return new LogExceptionHandler(exceptionNames);
}
/// <summary>
/// Parses the wrapped exception expression.
/// </summary>
/// <param name="action">The action.</param>
/// <param name="handlerString">The handler string.</param>
/// <returns></returns>
protected string ParseWrappedExceptionExpression(string action, string handlerString)
{
int endOfActionIndex = handlerString.IndexOf(action) + action.Length;
string exceptionAndMessage = handlerString.Substring(endOfActionIndex).Trim();
int endOfExceptionTypeIndex = exceptionAndMessage.IndexOf(" ");
string rawExpressionTextPart;
string exception;
//Has two pieces.
if (endOfExceptionTypeIndex > 0)
{
exception = exceptionAndMessage.Substring(0, endOfExceptionTypeIndex).Trim();
rawExpressionTextPart = exceptionAndMessage.Substring(endOfExceptionTypeIndex).Trim();
}
else
{
exception = exceptionAndMessage;
if (action.Equals("wrap"))
{
rawExpressionTextPart = "'Wrapped ' + #e.GetType().Name";
} else
{
rawExpressionTextPart = "'Replaced ' + #e.GetType().Name";
}
}
if (action.Equals("wrap"))
{
return string.Format("new {0}({1}, #e)", exception, rawExpressionTextPart);
} else
{
return string.Format("new {0}({1})", exception, rawExpressionTextPart);
}
}
private void AddExceptionNames(ParsedAdviceExpression parsedAdviceExpression, IExceptionHandler handler)
{
foreach (string exceptionName in parsedAdviceExpression.ExceptionNames)
{
handler.SourceExceptionNames.Add(exceptionName);
}
}
#endregion
#region ExceptionHandlerTable class
/// <summary>
/// A specialized dictionary for key value pairs of (string, IExceptionHandler)
/// </summary>
public class ExceptionHandlerTable : Hashtable
{
/// <summary>
/// Adds the specified key.
/// </summary>
/// <param name="key">The key.</param>
/// <param name="value">The value.</param>
public void Add(string key, IExceptionHandler value)
{
lock (SyncRoot)
{
base[key] = value;
}
}
/// <summary>
/// Gets the <see cref="Spring.Aspects.IExceptionHandler"/> with the specified key.
/// </summary>
/// <value></value>
public IExceptionHandler this[string key]
{
get
{
lock (SyncRoot)
{
return (IExceptionHandler)base[key];
}
}
}
/// <summary>
/// Adds an element with the specified key and value into the <see cref="T:System.Collections.Hashtable"/>.
/// </summary>
/// <param name="key">The key of the element to add.</param>
/// <param name="value">The value of the element to add. The value can be null.</param>
/// <exception cref="T:System.ArgumentNullException">
/// <paramref name="key"/> is null. </exception>
/// <exception cref="T:System.ArgumentException">An element with the same key already exists in the <see cref="T:System.Collections.Hashtable"/>.
/// or key is not a string or value is not an IExceptionHandler.</exception>
/// <exception cref="T:System.NotSupportedException">The <see cref="T:System.Collections.Hashtable"/> is read-only.-or- The <see cref="T:System.Collections.Hashtable"/> has a fixed size. </exception>
public override void Add(object key, object value)
{
AssertUtils.AssertArgumentType(key, "key", typeof(string), "Key must be a string");
AssertUtils.AssertArgumentType(value, "value", typeof(IExceptionHandler), "Key must be a IExceptionHandler");
this.Add((string)key, (IExceptionHandler)value);
}
/// <summary>
/// Gets the <see cref="System.Object"/> with the specified key.
/// </summary>
/// <value></value>
public override object this[object key]
{
get
{
return this[(string)key];
}
}
}
#endregion
}
}
|