diff --git a/doc/reference/src/messaging.xml b/doc/reference/src/messaging.xml
index dc9c4745..0a210d0b 100644
--- a/doc/reference/src/messaging.xml
+++ b/doc/reference/src/messaging.xml
@@ -1428,6 +1428,26 @@ namespace MyApp
initialization. Default is true, optionally set to
false.
+
+
+ error-handler
+
+ A reference to a IErrorHandler that will handle any
+ uncaught exceptions other than those of the type NMSException
+ (in the case of ActiveMQ or EMSException int he case of TIBCO
+ EMS) that may occur during the execution of the message
+ listener. By default no ErrorHandler is registered and that
+ error-level logging is the default behavior.
+
+
+
+ exception-listener
+
+ A reference to an
+ Spring.Messaging.Nms.Core.IExceptionListener or
+ TIBCO.EMS.IExceptionListener as appropriate. Is invokved in case
+ of a NMSException or EMSException.
+
diff --git a/src/Spring/Spring.Core/Spring.Core.2003.csproj b/src/Spring/Spring.Core/Spring.Core.2003.csproj
index 7b5eb9b5..03ebab20 100644
--- a/src/Spring/Spring.Core/Spring.Core.2003.csproj
+++ b/src/Spring/Spring.Core/Spring.Core.2003.csproj
@@ -2070,11 +2070,6 @@
SubType = "Code"
BuildAction = "Compile"
/>
-
+
+
+
diff --git a/src/Spring/Spring.Core/Spring.Core.2008.csproj b/src/Spring/Spring.Core/Spring.Core.2008.csproj
index c0a91e23..e780bbd4 100644
--- a/src/Spring/Spring.Core/Spring.Core.2008.csproj
+++ b/src/Spring/Spring.Core/Spring.Core.2008.csproj
@@ -1113,6 +1113,7 @@
+
diff --git a/src/Spring/Spring.Core/Util/IErrorHandler.cs b/src/Spring/Spring.Core/Util/IErrorHandler.cs
new file mode 100644
index 00000000..564f3420
--- /dev/null
+++ b/src/Spring/Spring.Core/Util/IErrorHandler.cs
@@ -0,0 +1,42 @@
+#region License
+
+/*
+ * Copyright 2002-2009 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;
+
+namespace Spring.Util
+{
+ ///
+ /// A strategy for handling errors. This is especially useful for handling
+ /// errors that occur during asynchronous execution as in such cases it may not be
+ /// possible to throw the error to the original caller.
+ ///
+ /// Mark Fisher
+ /// Mark Pollack (.NET)
+ public interface IErrorHandler
+ {
+ ///
+ /// Handles the error.
+ ///
+ /// The exception.
+ void HandleError(Exception exception);
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/Spring/Spring.Messaging.Ems/Messaging/Ems/Config/MessageListenerContainerObjectDefinitionParser.cs b/src/Spring/Spring.Messaging.Ems/Messaging/Ems/Config/MessageListenerContainerObjectDefinitionParser.cs
index 9fc3a226..bab73576 100644
--- a/src/Spring/Spring.Messaging.Ems/Messaging/Ems/Config/MessageListenerContainerObjectDefinitionParser.cs
+++ b/src/Spring/Spring.Messaging.Ems/Messaging/Ems/Config/MessageListenerContainerObjectDefinitionParser.cs
@@ -94,6 +94,10 @@ namespace Spring.Messaging.Ems.Config
private readonly string PUBSUB_DOMAIN_ATTRIBUTE = "pubsub-domain";
private readonly string AUTO_STARTUP = "auto-startup";
+
+ private readonly string ERROR_HANDLER_ATTRIBUTE = "error-handler";
+
+ private readonly string EXCEPTION_LISTENER_ATTRIBUTE = "exception-listener";
#endregion
@@ -257,11 +261,26 @@ namespace Spring.Messaging.Ems.Config
containerDef.AddPropertyValue("ConnectionFactory", new RuntimeObjectReference(connectionFactoryObjectName));
- string destinationResolverBeanName = containerElement.GetAttribute(DESTINATION_RESOLVER_ATTRIBUTE);
- if (StringUtils.HasText(destinationResolverBeanName))
+
+ string errorHandlerObjectName = containerElement.GetAttribute(ERROR_HANDLER_ATTRIBUTE);
+ if (StringUtils.HasText(errorHandlerObjectName))
+ {
+ containerDef.AddPropertyValue("ErrorHandler",
+ new RuntimeObjectReference(errorHandlerObjectName));
+ }
+
+ string exceptionListenerObjectName = containerElement.GetAttribute(EXCEPTION_LISTENER_ATTRIBUTE);
+ if (StringUtils.HasText(exceptionListenerObjectName))
+ {
+ containerDef.AddPropertyValue("ExceptionListener",
+ new RuntimeObjectReference(exceptionListenerObjectName));
+ }
+
+ string destinationResolverObjectName = containerElement.GetAttribute(DESTINATION_RESOLVER_ATTRIBUTE);
+ if (StringUtils.HasText(destinationResolverObjectName))
{
containerDef.AddPropertyValue("DestinationResolver",
- new RuntimeObjectReference(destinationResolverBeanName));
+ new RuntimeObjectReference(destinationResolverObjectName));
}
string acknowledge = containerElement.GetAttribute(ACKNOWLEDGE_ATTRIBUTE);
diff --git a/src/Spring/Spring.Messaging.Ems/Messaging/Ems/Config/spring-ems-1.3.xsd b/src/Spring/Spring.Messaging.Ems/Messaging/Ems/Config/spring-ems-1.3.xsd
index 2364c72e..43c8b3b1 100644
--- a/src/Spring/Spring.Messaging.Ems/Messaging/Ems/Config/spring-ems-1.3.xsd
+++ b/src/Spring/Spring.Messaging.Ems/Messaging/Ems/Config/spring-ems-1.3.xsd
@@ -83,7 +83,27 @@
]]>
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ /// Sets an ErrorHandler to be invoked in case of any uncaught exceptions thrown
+ /// while processing a Message. By default there will be no ErrorHandler
+ /// so that error-level logging is the only result.
+ ///
+ /// The error handler.
+ public IErrorHandler ErrorHandler
+ {
+ set { errorHandler = value; }
+ }
+
///
/// Gets or sets a value indicating whether to expose listener session to a registered
@@ -545,8 +558,8 @@ namespace Spring.Messaging.Ems.Listener
if (Active)
{
// Regular case: failed while active.
- // Log at error level.
- logger.Error("Execution of EMS message listener failed", ex);
+ // Invoke ErrorHandler if available.
+ InvokeErrorHandler(ex);
}
else
{
@@ -556,6 +569,18 @@ namespace Spring.Messaging.Ems.Listener
}
}
+ protected virtual void InvokeErrorHandler(Exception exception)
+ {
+ if (errorHandler != null)
+ {
+ errorHandler.HandleError(exception);
+ }
+ else if (logger.IsWarnEnabled)
+ {
+ logger.Warn("Execution of EMS message listener failed, and no ErrorHandler has been set.", exception);
+ }
+ }
+
///
/// Invokes the registered exception listener, if any.
///
diff --git a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Config/MessageListenerContainerObjectDefinitionParser.cs b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Config/MessageListenerContainerObjectDefinitionParser.cs
index 9177578a..e610a8a9 100644
--- a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Config/MessageListenerContainerObjectDefinitionParser.cs
+++ b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Config/MessageListenerContainerObjectDefinitionParser.cs
@@ -21,7 +21,6 @@
using System;
using System.Xml;
using Apache.NMS;
-using Spring.Core.TypeConversion;
using Spring.Core.TypeResolution;
using Spring.Messaging.Nms.Listener;
using Spring.Messaging.Nms.Listener.Adapter;
@@ -95,6 +94,10 @@ namespace Spring.Messaging.Nms.Config
private readonly string PUBSUB_DOMAIN_ATTRIBUTE = "pubsub-domain";
private readonly string AUTO_STARTUP = "auto-startup";
+
+ private readonly string ERROR_HANDLER_ATTRIBUTE = "error-handler";
+
+ private readonly string EXCEPTION_LISTENER_ATTRIBUTE = "exception-listener";
#endregion
@@ -260,11 +263,26 @@ namespace Spring.Messaging.Nms.Config
containerDef.AddPropertyValue("ConnectionFactory", new RuntimeObjectReference(connectionFactoryObjectName));
- string destinationResolverBeanName = containerElement.GetAttribute(DESTINATION_RESOLVER_ATTRIBUTE);
- if (StringUtils.HasText(destinationResolverBeanName))
+
+ string errorHandlerObjectName = containerElement.GetAttribute(ERROR_HANDLER_ATTRIBUTE);
+ if (StringUtils.HasText(errorHandlerObjectName))
+ {
+ containerDef.AddPropertyValue("ErrorHandler",
+ new RuntimeObjectReference(errorHandlerObjectName));
+ }
+
+ string exceptionListenerObjectName = containerElement.GetAttribute(EXCEPTION_LISTENER_ATTRIBUTE);
+ if (StringUtils.HasText(exceptionListenerObjectName))
+ {
+ containerDef.AddPropertyValue("ExceptionListener",
+ new RuntimeObjectReference(exceptionListenerObjectName));
+ }
+
+ string destinationResolverObjectName = containerElement.GetAttribute(DESTINATION_RESOLVER_ATTRIBUTE);
+ if (StringUtils.HasText(destinationResolverObjectName))
{
containerDef.AddPropertyValue("DestinationResolver",
- new RuntimeObjectReference(destinationResolverBeanName));
+ new RuntimeObjectReference(destinationResolverObjectName));
}
string acknowledge = containerElement.GetAttribute(ACKNOWLEDGE_ATTRIBUTE);
diff --git a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Config/NmsNamespaceParser.cs b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Config/NmsNamespaceParser.cs
index 13356a91..cee8d733 100644
--- a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Config/NmsNamespaceParser.cs
+++ b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Config/NmsNamespaceParser.cs
@@ -33,7 +33,7 @@ namespace Spring.Messaging.Nms.Config
NamespaceParser(
Namespace = "http://www.springframework.net/nms",
SchemaLocationAssemblyHint = typeof (NmsNamespaceParser),
- SchemaLocation = "/Spring.Messaging.Nms.Config/spring-nms-1.2.xsd"
+ SchemaLocation = "/Spring.Messaging.Nms.Config/spring-nms-1.3.xsd"
)
]
public class NmsNamespaceParser : NamespaceParserSupport
diff --git a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Config/spring-nms-1.3.xsd b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Config/spring-nms-1.3.xsd
new file mode 100644
index 00000000..92f502c4
--- /dev/null
+++ b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Config/spring-nms-1.3.xsd
@@ -0,0 +1,250 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Listener/AbstractMessageListenerContainer.cs b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Listener/AbstractMessageListenerContainer.cs
index c816fee0..97445009 100644
--- a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Listener/AbstractMessageListenerContainer.cs
+++ b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Listener/AbstractMessageListenerContainer.cs
@@ -56,6 +56,8 @@ namespace Spring.Messaging.Nms.Listener
private IExceptionListener exceptionListener;
+ private IErrorHandler errorHandler;
+
private bool exposeListenerISession = true;
private bool acceptMessagesWhileStopping = false;
@@ -208,6 +210,17 @@ namespace Spring.Messaging.Nms.Listener
set { exceptionListener = value; }
}
+ ///
+ /// Sets an ErrorHandler to be invoked in case of any uncaught exceptions thrown
+ /// while processing a Message. By default there will be no ErrorHandler
+ /// so that error-level logging is the only result.
+ ///
+ /// The error handler.
+ public IErrorHandler ErrorHandler
+ {
+ set { errorHandler = value; }
+ }
+
///
/// Gets or sets a value indicating whether to expose listener session to a registered
@@ -544,8 +557,8 @@ namespace Spring.Messaging.Nms.Listener
if (Active)
{
// Regular case: failed while active.
- // Log at error level.
- logger.Error("Execution of NMS message listener failed", ex);
+ // Invoke ErrorHandler if available.
+ InvokeErrorHandler(ex);
}
else
{
@@ -555,6 +568,22 @@ namespace Spring.Messaging.Nms.Listener
}
}
+ ///
+ /// Invokes the error handler.
+ ///
+ /// The exception.
+ protected virtual void InvokeErrorHandler(Exception exception)
+ {
+ if (errorHandler != null)
+ {
+ errorHandler.HandleError(exception);
+ }
+ else if(logger.IsWarnEnabled)
+ {
+ logger.Warn("Execution of NMS message listener failed, and no ErrorHandler has been set.", exception);
+ }
+ }
+
///
/// Invokes the registered exception listener, if any.
///
@@ -568,6 +597,8 @@ namespace Spring.Messaging.Nms.Listener
exListener.OnException(ex);
}
}
+
+
#endregion
diff --git a/src/Spring/Spring.Messaging.Nms/Spring.Messaging.Nms.2008.csproj b/src/Spring/Spring.Messaging.Nms/Spring.Messaging.Nms.2008.csproj
index 035ee761..4f46c357 100644
--- a/src/Spring/Spring.Messaging.Nms/Spring.Messaging.Nms.2008.csproj
+++ b/src/Spring/Spring.Messaging.Nms/Spring.Messaging.Nms.2008.csproj
@@ -119,6 +119,9 @@
+
+
+
diff --git a/test/Spring/Spring.Messaging.Ems.Tests/Messaging/Ems/Core/SimpleMessageListenerContainerTests.cs b/test/Spring/Spring.Messaging.Ems.Tests/Messaging/Ems/Core/SimpleMessageListenerContainerTests.cs
index 47c0855c..6156fb92 100644
--- a/test/Spring/Spring.Messaging.Ems.Tests/Messaging/Ems/Core/SimpleMessageListenerContainerTests.cs
+++ b/test/Spring/Spring.Messaging.Ems.Tests/Messaging/Ems/Core/SimpleMessageListenerContainerTests.cs
@@ -26,6 +26,7 @@ using NUnit.Framework;
using Rhino.Mocks;
using Spring.Messaging.Ems.Common;
using Spring.Messaging.Ems.Listener;
+using Spring.Util;
using TIBCO.EMS;
#endregion
@@ -99,17 +100,56 @@ namespace Spring.Messaging.Ems.Core
// manually trigger an Exception with the above bad MessageListener...
messageConsumer.SendMessage(message);
+ mocks.VerifyAll();
+ }
+ [Test]
+ public void RegisteredErrorHandlerIsInvokedOnException()
+ {
+ SimpleMessageConsumer messageConsumer = new SimpleMessageConsumer();
+
+ ISession session = (ISession)mocks.CreateMock(typeof(ISession));
+ Expect.Call(session.CreateQueue(DESTINATION_NAME)).Return(QUEUE_DESTINATION);
+ Expect.Call(session.CreateConsumer(QUEUE_DESTINATION, null)).Return(messageConsumer);
+ // an exception is thrown, so the rollback logic is being applied here...
+ Expect.Call(session.Transacted).Return(false);
+
+ IConnection connection = (IConnection)mocks.CreateMock(typeof(IConnection));
+ //connection.ExceptionListener += container.OnException;
+ connection.ExceptionListener = container;
+ Expect.Call(connection.CreateSession(false, container.SessionAcknowledgeMode)).Return(session);
+ connection.Start();
+
+ IConnectionFactory connectionFactory = (IConnectionFactory)mocks.CreateMock(typeof(IConnectionFactory));
+ Expect.Call(connectionFactory.CreateConnection()).Return(connection);
+
+ IllegalStateException theException = new IllegalStateException(EXCEPTION_MESSAGE);
+
+ IErrorHandler errorHandler = (IErrorHandler)mocks.CreateMock(typeof(IErrorHandler));
+ errorHandler.HandleError(theException);
+
+ //IMessage message = (IMessage) mocks.CreateMock(typeof (IMessage));
+ TextMessage message = new TextMessage(null, "hello");
+
+
+ mocks.ReplayAll();
+
+
+ container.ConnectionFactory = connectionFactory;
+ container.DestinationName = DESTINATION_NAME;
+ container.MessageListener = new BadSessionAwareMessageListener(theException);
+ container.ErrorHandler = errorHandler;
+ container.AfterPropertiesSet();
+
+ // manually trigger an Exception with the above bad MessageListener...
+ messageConsumer.SendMessage(message);
mocks.VerifyAll();
-
-
-
-
-
}
}
+
+
internal class BadSessionAwareMessageListener : ISessionAwareMessageListener
{
private EMSException exception;
diff --git a/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Core/SimpleMessageListenerContainerTests.cs b/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Core/SimpleMessageListenerContainerTests.cs
index e20b0a09..5e414e11 100644
--- a/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Core/SimpleMessageListenerContainerTests.cs
+++ b/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Core/SimpleMessageListenerContainerTests.cs
@@ -25,6 +25,7 @@ using Apache.NMS;
using NUnit.Framework;
using Rhino.Mocks;
using Spring.Messaging.Nms.Listener;
+using Spring.Util;
#endregion
@@ -94,15 +95,48 @@ namespace Spring.Messaging.Nms.Core
// manually trigger an Exception with the above bad MessageListener...
messageConsumer.SendMessage(message);
+ mocks.VerifyAll();
+ }
+
+ [Test]
+ public void RegisteredErrorHandlerIsInvokedOnException()
+ {
+ SimpleMessageConsumer messageConsumer = new SimpleMessageConsumer();
+
+ ISession session = (ISession)mocks.CreateMock(typeof(ISession));
+ Expect.Call(session.GetQueue(DESTINATION_NAME)).Return(QUEUE_DESTINATION);
+ Expect.Call(session.CreateConsumer(QUEUE_DESTINATION, null)).Return(messageConsumer);
+ // an exception is thrown, so the rollback logic is being applied here...
+ Expect.Call(session.Transacted).Return(false);
+
+ IConnection connection = (IConnection)mocks.CreateMock(typeof(IConnection));
+ connection.ExceptionListener += container.OnException;
+ Expect.Call(connection.CreateSession(container.SessionAcknowledgeMode)).Return(session);
+ connection.Start();
+
+ IConnectionFactory connectionFactory = (IConnectionFactory)mocks.CreateMock(typeof(IConnectionFactory));
+ Expect.Call(connectionFactory.CreateConnection()).Return(connection);
+
+ IllegalStateException theException = new IllegalStateException(EXCEPTION_MESSAGE);
+
+ IErrorHandler errorHandler = (IErrorHandler)mocks.CreateMock(typeof(IErrorHandler));
+ errorHandler.HandleError(theException);
+
+ IMessage message = (IMessage)mocks.CreateMock(typeof(IMessage));
+
+ mocks.ReplayAll();
+ container.ConnectionFactory = connectionFactory;
+ container.DestinationName = DESTINATION_NAME;
+ container.MessageListener = new BadSessionAwareMessageListener(theException);
+ container.ErrorHandler = errorHandler;
+ container.AfterPropertiesSet();
+
+ // manually trigger an Exception with the above bad MessageListener...
+ messageConsumer.SendMessage(message);
mocks.VerifyAll();
-
-
-
-
-
}
}