From c04397943c79817ca5dcb2660a26db837374dd03 Mon Sep 17 00:00:00 2001 From: eeichinger Date: Mon, 16 Nov 2009 13:09:16 +0000 Subject: [PATCH] fix for SPRNET-1260. DefensiveEventRaiser now rethrows first caught exception, if any --- .../Support/AbstractApplicationContext.cs | 8 ++- .../Spring.Core/Spring.Core.2008.csproj | 1 + src/Spring/Spring.Core/Util/EventUtils.cs | 56 ++++++++++++++----- .../Util/IEventExceptionsCollector.cs | 12 ++++ .../AbstractApplicationContextTests.cs | 32 +++++++++++ .../Util/DefensiveEventRaiserTests.cs | 4 +- .../Util/EventRaiserTests.cs | 2 +- 7 files changed, 98 insertions(+), 17 deletions(-) create mode 100644 src/Spring/Spring.Core/Util/IEventExceptionsCollector.cs diff --git a/src/Spring/Spring.Core/Context/Support/AbstractApplicationContext.cs b/src/Spring/Spring.Core/Context/Support/AbstractApplicationContext.cs index b9e4f0d6..a695ee88 100644 --- a/src/Spring/Spring.Core/Context/Support/AbstractApplicationContext.cs +++ b/src/Spring/Spring.Core/Context/Support/AbstractApplicationContext.cs @@ -402,7 +402,13 @@ namespace Spring.Context.Support /// protected virtual void OnContextEvent(object source, ApplicationEventArgs e) { - _eventRaiser.Raise(ContextEvent, source, e); + IEventExceptionsCollector exceptions = _eventRaiser.Raise(ContextEvent, source, e); + if (exceptions.HasExceptions) + { + Delegate target = ContextEvent.GetInvocationList()[0]; + Exception exception = (Exception) exceptions[target]; + throw new ApplicationContextException(string.Format("An unhandled exception occured during processing application event {0} in handler {1}", e.GetType(), target.Method), exception); + } } /// diff --git a/src/Spring/Spring.Core/Spring.Core.2008.csproj b/src/Spring/Spring.Core/Spring.Core.2008.csproj index 95974b3d..ad06027b 100644 --- a/src/Spring/Spring.Core/Spring.Core.2008.csproj +++ b/src/Spring/Spring.Core/Spring.Core.2008.csproj @@ -1112,6 +1112,7 @@ + diff --git a/src/Spring/Spring.Core/Util/EventUtils.cs b/src/Spring/Spring.Core/Util/EventUtils.cs index c92b6755..6307e8c2 100644 --- a/src/Spring/Spring.Core/Util/EventUtils.cs +++ b/src/Spring/Spring.Core/Util/EventUtils.cs @@ -18,16 +18,12 @@ #endregion -#region Imports - using System; using System.Collections; using System.Diagnostics; using System.Reflection; using Common.Logging; -#endregion - namespace Spring.Util { /// @@ -36,6 +32,41 @@ namespace Spring.Util /// Rick Evans public class EventRaiser { + protected class EventExceptionsCollector : IEventExceptionsCollector + { + private readonly Hashtable _eventExceptions; + + public EventExceptionsCollector() + { + _eventExceptions = new Hashtable(); + } + + public bool HasExceptions + { + get { return _eventExceptions.Count > 0; } + } + + public Delegate[] Sources + { + get { return (Delegate[]) CollectionUtils.ToArray(_eventExceptions.Keys, typeof(Delegate)); } + } + + public Exception[] Exceptions + { + get { return (Exception[]) CollectionUtils.ToArray(_eventExceptions.Values, typeof (Exception)); } + } + + public Exception this[Delegate source] + { + get { return (Exception) _eventExceptions[source]; } + } + + public void Add(Delegate source, Exception exception) + { + _eventExceptions.Add(source, exception); + } + } + protected readonly ILog Log; /// @@ -54,19 +85,19 @@ namespace Spring.Util /// The event to be raised. /// The arguments to the event. /// a map of sink/exception entries that occurred during event raising - public virtual IDictionary Raise (Delegate source, params object [] arguments) + public virtual IEventExceptionsCollector Raise(Delegate source, params object[] arguments) { if (source == null) { return null; } - IDictionary exceptions = null; + EventExceptionsCollector exceptions = new EventExceptionsCollector(); Delegate [] delegates = source.GetInvocationList (); foreach (Delegate sink in delegates) { - exceptions = Invoke (sink, arguments, exceptions); + Invoke (sink, arguments, exceptions); } return exceptions; @@ -79,17 +110,18 @@ namespace Spring.Util /// The sink to be invoked. /// The arguments to the sink. /// the map of sink/exception entries to add any exception to - protected virtual IDictionary Invoke (Delegate sink, object [] arguments, IDictionary exceptions) + protected virtual void Invoke(Delegate sink, object[] arguments, EventExceptionsCollector exceptions) { try { sink.DynamicInvoke (arguments); - return exceptions; } catch (TargetInvocationException ex) { // unwrap the exception that actually caused the TargetInvocationException and throw that... - throw ReflectionUtils.UnwrapTargetInvocationException(ex); + Exception cause = ReflectionUtils.UnwrapTargetInvocationException(ex); + exceptions.Add(sink, cause); + throw cause; } } } @@ -114,7 +146,7 @@ namespace Spring.Util /// The sink to be invoked. /// The arguments to the sink. /// the map of sink/exception entries to add any exception to - protected override IDictionary Invoke(Delegate sink, object[] arguments, IDictionary exceptions) + protected override void Invoke(Delegate sink, object[] arguments, EventExceptionsCollector exceptions) { try { @@ -123,10 +155,8 @@ namespace Spring.Util catch(Exception ex) { Log.Warn("Error during raising an event from " + new StackTrace(), ex); - if (exceptions == null) exceptions = new Hashtable(); exceptions.Add(sink, ex); } - return exceptions; } } } diff --git a/src/Spring/Spring.Core/Util/IEventExceptionsCollector.cs b/src/Spring/Spring.Core/Util/IEventExceptionsCollector.cs new file mode 100644 index 00000000..bf6cc365 --- /dev/null +++ b/src/Spring/Spring.Core/Util/IEventExceptionsCollector.cs @@ -0,0 +1,12 @@ +using System; + +namespace Spring.Util +{ + public interface IEventExceptionsCollector + { + bool HasExceptions { get; } + Delegate[] Sources { get;} + Exception[] Exceptions { get; } + Exception this[Delegate source] { get; } + } +} \ No newline at end of file diff --git a/test/Spring/Spring.Core.Tests/Context/Support/AbstractApplicationContextTests.cs b/test/Spring/Spring.Core.Tests/Context/Support/AbstractApplicationContextTests.cs index add782a0..816ab3ec 100644 --- a/test/Spring/Spring.Core.Tests/Context/Support/AbstractApplicationContextTests.cs +++ b/test/Spring/Spring.Core.Tests/Context/Support/AbstractApplicationContextTests.cs @@ -63,6 +63,38 @@ namespace Spring.Context.Support } +#if NET_2_0 + [Test] + public void ExecutesAllContextEventHandlersAndRethrowsExceptionsThrownDuringContextEventHandlingByDefault() + { + MockApplicationContext appCtx = new MockApplicationContext(); + bool secondHandlerExecuted = false; + appCtx.ContextEvent += new ApplicationEventHandler(delegate(object sender, ApplicationEventArgs e) + { + throw new ApplicationException("dummy"); + } ); + appCtx.ContextEvent += new ApplicationEventHandler(delegate(object sender, ApplicationEventArgs e) + { + secondHandlerExecuted = true; + } ); + + + ApplicationException resultException = null; + try + { + appCtx.PublishEvent(this, new ApplicationEventArgs()); + Assert.Fail(); + } + catch (ApplicationContextException e) + { + resultException = (ApplicationException) e.GetBaseException(); + } + + Assert.AreEqual("dummy", resultException.Message); + Assert.IsTrue(secondHandlerExecuted); + } +#endif + [Test] public void DoesNotSearchParentContextForMessageSource() { diff --git a/test/Spring/Spring.Core.Tests/Util/DefensiveEventRaiserTests.cs b/test/Spring/Spring.Core.Tests/Util/DefensiveEventRaiserTests.cs index fd04c9cd..283b491c 100644 --- a/test/Spring/Spring.Core.Tests/Util/DefensiveEventRaiserTests.cs +++ b/test/Spring/Spring.Core.Tests/Util/DefensiveEventRaiserTests.cs @@ -61,9 +61,9 @@ namespace Spring.Util DefensiveEventRaiser eventRaiser = new DefensiveEventRaiser(); - IDictionary exceptions = bru.OnPop( "Iron Brew", eventRaiser ); + IEventExceptionsCollector exceptions = bru.OnPop( "Iron Brew", eventRaiser ); - Assert.AreEqual(1, exceptions.Count); + Assert.AreEqual(1, exceptions.Exceptions.Length); Assert.IsTrue(firstCall); Assert.IsTrue(secondCall); Assert.IsTrue(thirdCall); diff --git a/test/Spring/Spring.Core.Tests/Util/EventRaiserTests.cs b/test/Spring/Spring.Core.Tests/Util/EventRaiserTests.cs index b1732df4..a2cacfac 100644 --- a/test/Spring/Spring.Core.Tests/Util/EventRaiserTests.cs +++ b/test/Spring/Spring.Core.Tests/Util/EventRaiserTests.cs @@ -81,7 +81,7 @@ namespace Spring.Util { public event PopHandler Pop; - public IDictionary OnPop (string soda, EventRaiser raiser) + public IEventExceptionsCollector OnPop (string soda, EventRaiser raiser) { return raiser.Raise (Pop, this, soda); }