From 1eed54d668e649c2b7c4ee68a3d9c040799c1b5d Mon Sep 17 00:00:00 2001 From: markpollack Date: Fri, 1 Aug 2008 06:22:54 +0000 Subject: [PATCH] NMS development --- lib/Net/2.0/Apache.NMS.dll | Bin 19456 -> 19456 bytes ...ListenerContainerObjectDefinitionParser.cs | 367 ++++++++++++++++++ .../Nms/Config/NmsNamespaceParser.cs | 49 +++ .../Messaging/Nms/Config/spring-nms-1.2.xsd | 183 +++++++++ .../Connections/SingleConnectionFactory.cs | 8 +- .../Messaging/Nms/IMessageOperations.cs | 14 + .../Messaging/Nms/ISessionCallback.cs | 5 +- .../Nms/Listener/AbstractListenerContainer.cs | 2 +- .../ListenerExecutionFailedException.cs | 53 +++ .../Adapter/MessageListenerAdapter.cs | 84 +++- .../SimpleMessageListenerContainer.cs | 1 + .../Messaging/Nms/MessageTemplate.cs | 37 +- ...ySupport.cs => MessagingGatewaySupport.cs} | 0 .../Messaging/Nms/SessionDelegate.cs | 39 ++ .../DynamicDestinationResolver.cs | 2 +- .../Destinations/IDestinationResolver.cs | 2 +- .../MessageDestinationAccessor.cs | 2 +- .../Spring.Messaging.Nms.2005.csproj | 11 +- .../Spring.Messaging.Nms.build | 15 +- .../Nms/Config/NmsNamespaceHandlerTests.cs | 156 ++++++++ .../Nms/Config/NmsNamespaceHandlerTests.xml | 40 ++ .../Nms/Connections/TestConnectionFactory.cs | 23 ++ .../Nms/Connections/TestMessageListener.cs | 52 +++ .../Messaging/Nms/Connections/TestSession.cs | 10 +- .../Messaging/Nms/MessageTemplateTests.cs | 198 ++++++++++ .../Spring.Messaging.Nms.Tests.2005.csproj | 7 +- 26 files changed, 1334 insertions(+), 26 deletions(-) create mode 100644 src/Spring/Spring.Messaging.Nms/Messaging/Nms/Config/MessageListenerContainerObjectDefinitionParser.cs create mode 100644 src/Spring/Spring.Messaging.Nms/Messaging/Nms/Config/NmsNamespaceParser.cs create mode 100644 src/Spring/Spring.Messaging.Nms/Messaging/Nms/Config/spring-nms-1.2.xsd create mode 100644 src/Spring/Spring.Messaging.Nms/Messaging/Nms/Listener/Adapter/ListenerExecutionFailedException.cs rename src/Spring/Spring.Messaging.Nms/Messaging/Nms/{MessageGatewaySupport.cs => MessagingGatewaySupport.cs} (100%) create mode 100644 src/Spring/Spring.Messaging.Nms/Messaging/Nms/SessionDelegate.cs create mode 100644 test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Config/NmsNamespaceHandlerTests.cs create mode 100644 test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Config/NmsNamespaceHandlerTests.xml create mode 100644 test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Connections/TestConnectionFactory.cs create mode 100644 test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Connections/TestMessageListener.cs create mode 100644 test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/MessageTemplateTests.cs diff --git a/lib/Net/2.0/Apache.NMS.dll b/lib/Net/2.0/Apache.NMS.dll index b2369c8f45f71d9110460227ab4f05e2ce8ad997..b56408418094f0e3b2236d578288013d41ec54b3 100644 GIT binary patch delta 6224 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 + /// Parser for the NMS <listener-container> element. + /// + /// Mark Fisher + /// Juergen Hoeller + /// Mark Pollack (.NET) + public class MessageListenerContainerObjectDefinitionParser : IObjectDefinitionParser + { + #region Fields + + private readonly string LISTENER_ELEMENT = "listener"; + + private readonly string ID_ATTRIBUTE = "id"; + + private readonly string DESTINATION_ATTRIBUTE = "destination"; + + private readonly string SUBSCRIPTION_ATTRIBUTE = "subscription"; + + private readonly string SELECTOR_ATTRIBUTE = "selector"; + + private readonly string REF_ATTRIBUTE = "ref"; + + private readonly string METHOD_ATTRIBUTE = "method"; + + private readonly string DESTINATION_RESOLVER_ATTRIBUTE = "destination-resolver"; + + private readonly string MESSAGE_CONVERTER_ATTRIBUTE = "message-converter"; + + private readonly string RESPONSE_DESTINATION_ATTRIBUTE = "response-destination"; + + private readonly string DESTINATION_TYPE_ATTRIBUTE = "destination-type"; + + private readonly string DESTINATION_TYPE_QUEUE = "queue"; + + private readonly string DESTINATION_TYPE_TOPIC = "topic"; + + private readonly string DESTINATION_TYPE_DURABLE_TOPIC = "durableTopic"; + + private readonly string CLIENT_ID_ATTRIBUTE = "client-id"; + + private readonly string ACKNOWLEDGE_ATTRIBUTE = "acknowledge"; + + private readonly string ACKNOWLEDGE_AUTO = "auto"; + + private readonly string ACKNOWLEDGE_CLIENT = "client"; + + private readonly string ACKNOWLEDGE_DUPS_OK = "dups-ok"; + + private readonly string ACKNOWLEDGE_TRANSACTED = "transacted"; + + private readonly string CONCURRENCY_ATTRIBUTE = "concurrency"; + + private readonly string CONNECTION_FACTORY_ATTRIBUTE = "connection-factory"; + + #endregion + + #region IObjectDefinitionParser Members + + /// + /// Parse the specified XmlElement and register the resulting + /// ObjectDefinitions with the IObjectDefinitionRegistry + /// embedded in the supplied + /// + /// The element to be parsed. + /// TThe object encapsulating the current state of the parsing process. + /// Provides access to a IObjectDefinitionRegistry + /// The primary object definition. + /// + ///

+ /// This method is never invoked if the parser is namespace aware + /// and was called to process the root node. + ///

+ ///
+ public IObjectDefinition ParseElement(XmlElement element, ParserContext parserContext) + { + + XmlNodeList childNodes = element.ChildNodes; + foreach (XmlNode childNode in childNodes) + { + if (childNode.NodeType == XmlNodeType.Element) + { + string localName = childNode.LocalName; + if (LISTENER_ELEMENT.Equals(localName)) + { + ParseListener((XmlElement) childNode, element, parserContext); + } + } + } + return null; + } + + #endregion + + private void ParseListener(XmlElement listenerElement, XmlElement containerElement, ParserContext parserContext) + { + ObjectDefinitionBuilder listenerDefBuilder = + parserContext.ParserHelper.CreateRootObjectDefinitionBuilder(typeof (MessageListenerAdapter)); + + string reference = listenerElement.GetAttribute(REF_ATTRIBUTE); + if (!StringUtils.HasText(reference)) + { + parserContext.ReaderContext.ReportException(listenerElement, LISTENER_ELEMENT, + "Listener '" + REF_ATTRIBUTE + + "' attribute contains empty value."); + } + listenerDefBuilder.AddPropertyValue("HandlerObject", new RuntimeObjectReference(reference)); + + string handlerMethod = null; + if (listenerElement.HasAttribute(METHOD_ATTRIBUTE)) + { + handlerMethod = listenerElement.GetAttribute(METHOD_ATTRIBUTE); + { + if (!StringUtils.HasText(handlerMethod)) + { + parserContext.ReaderContext.ReportException(listenerElement, LISTENER_ELEMENT, + "Listener '" + METHOD_ATTRIBUTE + + "' attribute contains empty value."); + } + } + } + listenerDefBuilder.AddPropertyValue("DefaultHandlerMethod", handlerMethod); + + if (containerElement.HasAttribute(MESSAGE_CONVERTER_ATTRIBUTE)) + { + string messageConverter = containerElement.GetAttribute(MESSAGE_CONVERTER_ATTRIBUTE); + listenerDefBuilder.AddPropertyValue("MessageConverter", new RuntimeObjectReference(messageConverter)); + } + + ObjectDefinitionBuilder containerDefBuilder = ParseContainer(listenerElement, containerElement, parserContext); + + if (listenerElement.HasAttribute(RESPONSE_DESTINATION_ATTRIBUTE)) + { + string responseDestination = listenerElement.GetAttribute(RESPONSE_DESTINATION_ATTRIBUTE); + bool pubSubDomain = IndicatesPubSub(containerDefBuilder.RawObjectDefinition); + listenerDefBuilder.AddPropertyValue(pubSubDomain ? "DefaultResponseTopicName" : "DefaultResponseQueueName", + responseDestination); + if (containerDefBuilder.RawObjectDefinition.PropertyValues.Contains("DestinationResolver")) + { + listenerDefBuilder.AddPropertyValue("DestinationResolver", + containerDefBuilder.RawObjectDefinition.PropertyValues.GetPropertyValue + ( + "DestinationResolver").Value); + } + } + + containerDefBuilder.AddPropertyValue("MessageListener", listenerDefBuilder.ObjectDefinition); + + string containerObjectName = listenerElement.GetAttribute(ID_ATTRIBUTE); + // If no object id is given auto generate one using the ReaderContext's ObjectNameGenerator + if (!StringUtils.HasText(containerObjectName)) + { + containerObjectName = + parserContext.ReaderContext.GenerateObjectName(containerDefBuilder.RawObjectDefinition); + } + + parserContext.Registry.RegisterObjectDefinition(containerObjectName, containerDefBuilder.ObjectDefinition); + } + + private ObjectDefinitionBuilder ParseContainer(XmlElement listenerElement, XmlElement containerElement, + ParserContext parserContext) + { + //Only support SimpleMessageListenerContainer + ObjectDefinitionBuilder containerDef = + parserContext.ParserHelper.CreateRootObjectDefinitionBuilder(typeof (SimpleMessageListenerContainer)); + + ParseListenerConfiguration(listenerElement, parserContext, containerDef); + ParseContainerConfiguration(containerElement, parserContext, containerDef); + + string connectionFactoryObjectName = "connectionFactory"; + if (containerElement.HasAttribute(CONNECTION_FACTORY_ATTRIBUTE)) + { + connectionFactoryObjectName = containerElement.GetAttribute(CONNECTION_FACTORY_ATTRIBUTE); + if (!StringUtils.HasText(connectionFactoryObjectName)) + { + parserContext.ReaderContext.ReportException(listenerElement, LISTENER_ELEMENT, + "Listener container '" + CONNECTION_FACTORY_ATTRIBUTE + + "' attribute contains empty value."); + } + } + + containerDef.AddPropertyValue("ConnectionFactory", new RuntimeObjectReference(connectionFactoryObjectName)); + + string destinationResolverBeanName = containerElement.GetAttribute(DESTINATION_RESOLVER_ATTRIBUTE); + if (StringUtils.HasText(destinationResolverBeanName)) + { + containerDef.AddPropertyValue("DestinationResolver", + new RuntimeObjectReference(destinationResolverBeanName)); + } + + string acknowledge = containerElement.GetAttribute(ACKNOWLEDGE_ATTRIBUTE); + if (StringUtils.HasText(acknowledge)) + { + AcknowledgementMode acknowledgementMode = ParseAcknowledgementMode(containerElement, parserContext); + containerDef.AddPropertyValue("SessionAcknowledgeMode", acknowledgementMode); + } + + int[] concurrency = ParseConcurrency(containerElement, parserContext); + if (concurrency != null) + { + containerDef.AddPropertyValue("ConcurrentConsumers", concurrency[1]); + } + + return containerDef; + } + + private bool IndicatesPubSub(AbstractObjectDefinition configDef) + { + return (bool) configDef.PropertyValues.GetPropertyValue("PubSubDomain").Value; + } + + private void ParseListenerConfiguration(XmlElement ele, ParserContext parserContext, + ObjectDefinitionBuilder containerDef) + { + string destination = ele.GetAttribute(DESTINATION_ATTRIBUTE); + if (!StringUtils.HasText(destination)) + { + parserContext.ReaderContext.ReportException(ele, LISTENER_ELEMENT, + "Listener '" + DESTINATION_ATTRIBUTE + + "' attribute contains empty value."); + } + containerDef.AddPropertyValue("DestinationName", destination); + + if (ele.HasAttribute(SUBSCRIPTION_ATTRIBUTE)) + { + string subscription = ele.GetAttribute(SUBSCRIPTION_ATTRIBUTE); + if (!StringUtils.HasText(subscription)) + { + parserContext.ReaderContext.ReportException(ele, SUBSCRIPTION_ATTRIBUTE, + "Listener '" + SUBSCRIPTION_ATTRIBUTE + + "' attribute contains empty value."); + } + containerDef.AddPropertyValue("DurableSubscriptionName", subscription); + } + + if (ele.HasAttribute(SELECTOR_ATTRIBUTE)) + { + string selector = ele.GetAttribute(SELECTOR_ATTRIBUTE); + if (!StringUtils.HasText(selector)) + { + parserContext.ReaderContext.ReportException(ele, selector, + "Listener '" + SELECTOR_ATTRIBUTE + + "' attribute contains empty value."); + } + containerDef.AddPropertyValue("MessageSelector", selector); + } + } + + private void ParseContainerConfiguration(XmlElement ele, ParserContext parserContext, + ObjectDefinitionBuilder containerDef) + { + string destinationType = ele.GetAttribute(DESTINATION_TYPE_ATTRIBUTE); + bool pubSubDomain = false; + bool subscriptionDurable = false; + if (DESTINATION_TYPE_DURABLE_TOPIC.Equals(destinationType)) + { + pubSubDomain = true; + subscriptionDurable = true; + } + else if (DESTINATION_TYPE_TOPIC.Equals(destinationType)) + { + pubSubDomain = true; + } + else if ("".Equals(destinationType) || DESTINATION_TYPE_QUEUE.Equals(destinationType)) + { + // the default: queue + } + else + { + parserContext.ReaderContext.ReportException(ele, destinationType, + "Invalid listener container '" + DESTINATION_TYPE_ATTRIBUTE + + "': only 'queue', 'topic' and 'durableTopic' supported"); + } + + containerDef.AddPropertyValue("PubSubDomain", pubSubDomain); + containerDef.AddPropertyValue("SubscriptionDurable", subscriptionDurable); + + if (ele.HasAttribute(CLIENT_ID_ATTRIBUTE)) + { + string clientId = ele.GetAttribute(CLIENT_ID_ATTRIBUTE); + if (!StringUtils.HasText(clientId)) + { + parserContext.ReaderContext.ReportException(ele, clientId, + "Listener '" + CLIENT_ID_ATTRIBUTE + + "' attribute contains empty value."); + } + containerDef.AddPropertyValue("ClientId", clientId); + } + } + + private AcknowledgementMode ParseAcknowledgementMode(XmlElement element, ParserContext parserContext) + { + string acknowledge = element.GetAttribute(ACKNOWLEDGE_ATTRIBUTE); + if (acknowledge.Equals(ACKNOWLEDGE_TRANSACTED)) + { + return AcknowledgementMode.Transactional; + } + else if (acknowledge.Equals(ACKNOWLEDGE_DUPS_OK)) + { + return AcknowledgementMode.DupsOkAcknowledge; + } + else if (acknowledge.Equals(ACKNOWLEDGE_CLIENT)) + { + return AcknowledgementMode.ClientAcknowledge; + } + else if (!acknowledge.Equals(ACKNOWLEDGE_AUTO)) + { + parserContext.ReaderContext.ReportException(element, ACKNOWLEDGE_ATTRIBUTE, + "Invalid listener container 'acknowledge' setting ['" + + acknowledge + + "]: only \"auto\", \"client\", \"dups-ok\" and \"transacted\" supported."); + } + return AcknowledgementMode.AutoAcknowledge; + } + + private int[] ParseConcurrency(XmlElement ele, ParserContext parserContext) + { + String concurrency = ele.GetAttribute(CONCURRENCY_ATTRIBUTE); + if (!StringUtils.HasText(concurrency)) + { + return null; + } + try + { + return new int[] {1, Int32.Parse(concurrency)}; + } + catch (FormatException ex) + { + parserContext.ReaderContext.ReportException(ele, CONCURRENCY_ATTRIBUTE, + "Invalid concurrency value [" + concurrency + "]: only " + + "integer (e.g. \"5\") values upported.", ex); + return null; + } + } + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Config/NmsNamespaceParser.cs b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Config/NmsNamespaceParser.cs new file mode 100644 index 00000000..bb816be4 --- /dev/null +++ b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Config/NmsNamespaceParser.cs @@ -0,0 +1,49 @@ +#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 Spring.Objects.Factory.Xml; + +namespace Spring.Messaging.Nms.Config +{ + /// + /// Namespace parser for the nms namespace. + /// + /// Mark Fisher + /// Juergen Hoeller + /// Mark Pollack (.NET) + [ + NamespaceParser( + Namespace = "http://www.springframework.net/nms", + SchemaLocationAssemblyHint = typeof (NmsNamespaceParser), + SchemaLocation = "/Spring.Messaging.Nms.Config/spring-nms-1.2.xsd" + ) + ] + public class NmsNamespaceParser : NamespaceParserSupport + { + /// + /// Register a MessageListenerContainer for the 'listener-container' tag. + /// + public override void Init() + { + RegisterObjectDefinitionParser("listener-container", new MessageListenerContainerObjectDefinitionParser()); + } + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Config/spring-nms-1.2.xsd b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Config/spring-nms-1.2.xsd new file mode 100644 index 00000000..135f13f4 --- /dev/null +++ b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Config/spring-nms-1.2.xsd @@ -0,0 +1,183 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Connections/SingleConnectionFactory.cs b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Connections/SingleConnectionFactory.cs index bddc8616..db145f65 100644 --- a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Connections/SingleConnectionFactory.cs +++ b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Connections/SingleConnectionFactory.cs @@ -382,10 +382,10 @@ namespace Spring.Messaging.Nms.Connections /// the wrapped connection protected virtual IConnection GetSharedConnection(IConnection target) { - lock (connectionMonitor) + lock (connectionMonitor) { - return new CloseSupressingConnection(this, target); - } + return new CloseSupressingConnection(this, target); + } } } @@ -461,7 +461,7 @@ namespace Spring.Messaging.Nms.Connections { return session; } - return target.CreateSession(); + return target.CreateSession(acknowledgementMode); } #region Pass through implementations to the target connection diff --git a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/IMessageOperations.cs b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/IMessageOperations.cs index 68831c83..0547cd36 100644 --- a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/IMessageOperations.cs +++ b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/IMessageOperations.cs @@ -52,6 +52,20 @@ namespace Spring.Messaging.Nms /// /// NMSException if there is any problem object Execute(ISessionCallback action); + + /// Execute the action specified by the given action object within + /// a NMS Session. + /// + /// + /// Note that the value of PubSubDomain affects the behavior of this method. + /// If PubSubDomain equals true, then a Session is passed to the callback. + /// If false, then a ISession is passed to the callback.b + /// + /// delegate that exposes the session + /// the result object from working with the session + /// + /// NMSException if there is any problem + object Execute(SessionDelegate del); /// Send a message to a NMS destination. The callback gives access to /// the NMS session and MessageProducer in order to do more complex diff --git a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/ISessionCallback.cs b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/ISessionCallback.cs index 447e1f89..e9510531 100644 --- a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/ISessionCallback.cs +++ b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/ISessionCallback.cs @@ -26,8 +26,9 @@ namespace Spring.Messaging.Nms /// Session /// /// - ///

To be used with the MessageTemplate.Execute(ISessionCallback)} - /// method, often implemented as an anonymous inner class.

+ /// To be used with the MessageTemplate.Execute(ISessionCallback)} + /// method. See for the equivalent callback + /// that can be used as a (anonymous) delegate. ///
/// Mark Pollack /// diff --git a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Listener/AbstractListenerContainer.cs b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Listener/AbstractListenerContainer.cs index ae3c7855..ec093e5a 100644 --- a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Listener/AbstractListenerContainer.cs +++ b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Listener/AbstractListenerContainer.cs @@ -24,7 +24,7 @@ using Common.Logging; using Spring.Context; using Spring.Messaging.Nms.Connections; using Spring.Messaging.Nms.Support; -using Spring.Messaging.Nms.Support.IDestinations; +using Spring.Messaging.Nms.Support.Destinations; using Spring.Objects.Factory; namespace Spring.Messaging.Nms.Listener diff --git a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Listener/Adapter/ListenerExecutionFailedException.cs b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Listener/Adapter/ListenerExecutionFailedException.cs new file mode 100644 index 00000000..d114aea7 --- /dev/null +++ b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Listener/Adapter/ListenerExecutionFailedException.cs @@ -0,0 +1,53 @@ +#region License + +/* + * Copyright 2002-2008 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 Apache.NMS; + +namespace Spring.Messaging.Nms.Listener.Adapter +{ + /// + /// Exception to be thrown when the execution of a listener method failed. + /// + /// Juergen Hoeller + /// Mark Pollack (.NET) + public class ListenerExecutionFailedException : NMSException + { + + /// + /// Initializes a new instance of the class, with the specified message + /// + /// The message. + public ListenerExecutionFailedException(string message) : base(message) + { + } + + /// + /// Initializes a new instance of the class, with the specified message + /// and root cause exception + /// + /// The message. + /// The inner exception. + public ListenerExecutionFailedException(string message, Exception innerException) + : base(message, innerException) + { + } + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Listener/Adapter/MessageListenerAdapter.cs b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Listener/Adapter/MessageListenerAdapter.cs index fe8b18a6..506958ed 100644 --- a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Listener/Adapter/MessageListenerAdapter.cs +++ b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Listener/Adapter/MessageListenerAdapter.cs @@ -1,11 +1,33 @@ +#region License + +/* + * Copyright 2002-2008 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 Common.Logging; using Spring.Expressions; using Spring.Messaging.Nms.Listener; using Spring.Messaging.Nms.Support; using Spring.Messaging.Nms.Support.Converter; -using Spring.Messaging.Nms.Support.IDestinations; +using Spring.Messaging.Nms.Support.Destinations; using Spring.Util; using Apache.NMS; @@ -146,7 +168,7 @@ namespace Spring.Messaging.Nms.Listener.Adapter /// Alternatively, specify a JMS Destination object as "defaultResponseDestination". /// /// The name of the default response destination queue. - public string DefaultResponseDestinationQueueName + public string DefaultResponseQueueName { set { defaultResponseDestination = new DestinationNameHolder(value, false); } } @@ -158,7 +180,7 @@ namespace Spring.Messaging.Nms.Listener.Adapter /// Alternatively, specify a JMS Destination object as "defaultResponseDestination". /// /// The name of the default response destination topic. - public string DefaultResponseDestinationTopicName + public string DefaultResponseTopicName { set { defaultResponseDestination = new DestinationNameHolder(value, true); } } @@ -238,6 +260,29 @@ namespace Spring.Messaging.Nms.Listener.Adapter /// The session to operate on. public void OnMessage(IMessage message, ISession session) { + if (handlerObject != this) + { + if (typeof(ISessionAwareMessageListener).IsInstanceOfType(handlerObject)) + { + if (session != null) + { + ((ISessionAwareMessageListener) handlerObject).OnMessage(message, session); + return; + } + else if (!typeof(IMessageListener).IsInstanceOfType(handlerObject)) + { + throw new InvalidOperationException("MessageListenerAdapter cannot handle a " + + "SessionAwareMessageListener delegate if it hasn't been invoked with a Session itself"); + } + } + if (typeof(IMessageListener).IsInstanceOfType(handlerObject)) + { + ((IMessageListener)handlerObject).OnMessage(message); + return; + } + } + + // Regular case: find a handler method reflectively. object convertedMessage = ExtractMessage(message); @@ -250,7 +295,34 @@ namespace Spring.Messaging.Nms.Listener.Adapter processingExpression = Expression.Parse(defaultHandlerMethod + "(#convertedObject)"); //Invoke message handler method and get result. - object result = processingExpression.GetValue(handlerObject, vars); + object result; + try + { + result = processingExpression.GetValue(handlerObject, vars); + } + catch (NMSException) + { + throw; + } + // Will only happen if dynamic method invocation falls back to standard reflection. + catch (TargetInvocationException ex) + { + Exception targetEx = ex.InnerException; + if (ObjectUtils.IsAssignable(typeof(NMSException), targetEx)) + { + throw ReflectionUtils.UnwrapTargetInvocationException(ex); + } + else + { + throw new ListenerExecutionFailedException("Listener method '" + defaultHandlerMethod + "' threw exception", targetEx); + } + } + catch (Exception ex) + { + throw new ListenerExecutionFailedException("Failed to invoke target method '" + defaultHandlerMethod + + "' with argument " + convertedMessage, ex); + } + if (result != null) { HandleResult(result, message, session); @@ -478,9 +550,9 @@ namespace Spring.Messaging.Nms.Listener.Adapter /// internal class DestinationNameHolder { - private string name; + private readonly string name; - private bool isTopic; + private readonly bool isTopic; public DestinationNameHolder(string name, bool isTopic) { diff --git a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Listener/SimpleMessageListenerContainer.cs b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Listener/SimpleMessageListenerContainer.cs index 6b93ce32..bb36b694 100644 --- a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Listener/SimpleMessageListenerContainer.cs +++ b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Listener/SimpleMessageListenerContainer.cs @@ -223,6 +223,7 @@ namespace Spring.Messaging.Nms.Listener SimpleMessageListener listener = new SimpleMessageListener(this, session); + // put in explicit registration with 'new' for compilation on .NET 1.1 consumer.Listener += new Apache.NMS.MessageListener(listener.OnMessage); return consumer; } diff --git a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/MessageTemplate.cs b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/MessageTemplate.cs index b8d687a0..9f29a2f8 100644 --- a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/MessageTemplate.cs +++ b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/MessageTemplate.cs @@ -23,7 +23,7 @@ using Common.Logging; using Spring.Messaging.Nms.Connections; using Spring.Messaging.Nms.Support; using Spring.Messaging.Nms.Support.Converter; -using Spring.Messaging.Nms.Support.IDestinations; +using Spring.Messaging.Nms.Support.Destinations; using Spring.Transaction.Support; using Spring.Util; using Apache.NMS; @@ -581,6 +581,25 @@ namespace Spring.Messaging.Nms #region IMessageOperations Implementation + /// + /// Execute the action specified by the given action object within + /// a NMS Session. + /// + /// delegate that exposes the session + /// + /// the result object from working with the session + /// + /// + /// Note that the value of PubSubDomain affects the behavior of this method. + /// If PubSubDomain equals true, then a Session is passed to the callback. + /// If false, then a ISession is passed to the callback.b + /// + /// NMSException if there is any problem + public object Execute(SessionDelegate del) + { + return Execute(new ExecuteSessionCallbackUsingDelegate(del)); + } + /// Execute the action specified by the given action object within /// a NMS Session. ///

Note: The value of PubSubDomain affects the behavior of this method. @@ -1272,8 +1291,24 @@ namespace Spring.Messaging.Nms } #endregion + + private class ExecuteSessionCallbackUsingDelegate : ISessionCallback + { + private readonly SessionDelegate del; + public ExecuteSessionCallbackUsingDelegate(SessionDelegate del) + { + this.del = del; + } + + public object DoInNms(ISession session) + { + return del(session); + } + } } + + internal class SimpleMessageCreator : IMessageCreator { private MessageTemplate jmsTemplate; diff --git a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/MessageGatewaySupport.cs b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/MessagingGatewaySupport.cs similarity index 100% rename from src/Spring/Spring.Messaging.Nms/Messaging/Nms/MessageGatewaySupport.cs rename to src/Spring/Spring.Messaging.Nms/Messaging/Nms/MessagingGatewaySupport.cs diff --git a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/SessionDelegate.cs b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/SessionDelegate.cs new file mode 100644 index 00000000..25c1a3f9 --- /dev/null +++ b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/SessionDelegate.cs @@ -0,0 +1,39 @@ +#region License + +/* + * Copyright © 2002-2006 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 Apache.NMS; + +namespace Spring.Messaging.Nms +{ + ///

+ /// Callback delegate for code that operates on a Session. + /// + /// The NMS ISession object. + /// + /// Allows you to execute any number of operations + /// on a single ISession, possibly returning a result a result. + /// + /// + /// A result object from working with the Session, if any (so can be null) + /// + /// NMSException if there is any problem + /// Mark Pollack + public delegate object SessionDelegate(ISession session); +} diff --git a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Support/Destinations/DynamicDestinationResolver.cs b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Support/Destinations/DynamicDestinationResolver.cs index 393ff70b..337ffcf6 100644 --- a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Support/Destinations/DynamicDestinationResolver.cs +++ b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Support/Destinations/DynamicDestinationResolver.cs @@ -21,7 +21,7 @@ using Spring.Util; using Apache.NMS; -namespace Spring.Messaging.Nms.Support.IDestinations +namespace Spring.Messaging.Nms.Support.Destinations { /// Simple DestinationResolver implementation resolving destination names /// as dynamic destinations. diff --git a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Support/Destinations/IDestinationResolver.cs b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Support/Destinations/IDestinationResolver.cs index d904b2a3..a48fc74c 100644 --- a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Support/Destinations/IDestinationResolver.cs +++ b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Support/Destinations/IDestinationResolver.cs @@ -20,7 +20,7 @@ using Apache.NMS; -namespace Spring.Messaging.Nms.Support.IDestinations +namespace Spring.Messaging.Nms.Support.Destinations { /// Strategy interface for resolving NMS destinations. /// diff --git a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Support/Destinations/MessageDestinationAccessor.cs b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Support/Destinations/MessageDestinationAccessor.cs index 96504911..81ccb6c2 100644 --- a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Support/Destinations/MessageDestinationAccessor.cs +++ b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Support/Destinations/MessageDestinationAccessor.cs @@ -23,7 +23,7 @@ using Spring.Objects.Factory; using Spring.Util; using Apache.NMS; -namespace Spring.Messaging.Nms.Support.IDestinations +namespace Spring.Messaging.Nms.Support.Destinations { /// Base class for MessageTemplate} and other /// NMS-accessing gateway helpers, adding destination-related properties to diff --git a/src/Spring/Spring.Messaging.Nms/Spring.Messaging.Nms.2005.csproj b/src/Spring/Spring.Messaging.Nms/Spring.Messaging.Nms.2005.csproj index 386f67e7..c7cf04cb 100644 --- a/src/Spring/Spring.Messaging.Nms/Spring.Messaging.Nms.2005.csproj +++ b/src/Spring/Spring.Messaging.Nms/Spring.Messaging.Nms.2005.csproj @@ -47,6 +47,9 @@ + + + @@ -67,13 +70,14 @@ + - + @@ -97,6 +101,11 @@ Spring.Data.2005 + + + Designer + + diff --git a/src/Spring/Spring.Messaging.Nms/Spring.Messaging.Nms.build b/src/Spring/Spring.Messaging.Nms/Spring.Messaging.Nms.build index 02564930..a90c1ea0 100644 --- a/src/Spring/Spring.Messaging.Nms/Spring.Messaging.Nms.build +++ b/src/Spring/Spring.Messaging.Nms/Spring.Messaging.Nms.build @@ -21,11 +21,16 @@ - - - - - + + + + + + + + + + diff --git a/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Config/NmsNamespaceHandlerTests.cs b/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Config/NmsNamespaceHandlerTests.cs new file mode 100644 index 00000000..29de3d73 --- /dev/null +++ b/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Config/NmsNamespaceHandlerTests.cs @@ -0,0 +1,156 @@ +#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 + +#region Imports + +using System.Collections; +using Apache.NMS; +using NUnit.Framework; +using Rhino.Mocks; +using Spring.Context; +using Spring.Context.Support; +using Spring.Messaging.Nms.Connections; +using Spring.Messaging.Nms.Listener; +using Spring.Objects; +using Spring.Objects.Factory.Xml; +using Spring.Testing.NUnit; + +#endregion + +namespace Spring.Messaging.Nms.Config +{ + /// + /// This class contains tests for + /// + /// Mark Pollack + /// $Id:$ + [TestFixture] + public class NmsNamespaceHandlerTests + { + + private static string DEFAULT_CONNECTION_FACTORY = "connectionFactory"; + + private static string EXPLICIT_CONNECTION_FACTORY = "testConnectionFactory"; + + + private IApplicationContext ctx; + + private MockRepository mocks; + + [SetUp] + public void Setup() + { + NamespaceParserRegistry.RegisterParser(typeof(NmsNamespaceParser)); + ctx = new XmlApplicationContext(ReadOnlyXmlTestResource.GetFilePath("NmsNamespaceHandlerTests.xml", GetType())); + mocks = new MockRepository(); + } + + [Test] + public void Registered() + { + Assert.IsNotNull(NamespaceParserRegistry.GetParser("http://www.springframework.net/nms")); + } + + [Test] + public void ObjectsCreated() + { + IDictionary containers = ctx.GetObjectsOfType(typeof(SimpleMessageListenerContainer)); + Assert.AreEqual(3, containers.Count); + } + + [Test] + public void ContainerConfiguration() + { + IDictionary containers = ctx.GetObjectsOfType(typeof (SimpleMessageListenerContainer)); + IConnectionFactory defaultConnectionFactory = (IConnectionFactory) ctx.GetObject(DEFAULT_CONNECTION_FACTORY); + IConnectionFactory explicitConnectionFactory = (IConnectionFactory) ctx.GetObject(EXPLICIT_CONNECTION_FACTORY); + + + int defaultConnectionFactoryCount = 0; + int explicitConnectionFactoryCount = 0; + foreach (DictionaryEntry dictionaryEntry in containers) + { + SimpleMessageListenerContainer container = (SimpleMessageListenerContainer) dictionaryEntry.Value; + if (container.ConnectionFactory.Equals(defaultConnectionFactory)) + { + defaultConnectionFactoryCount++; + } + else if (container.ConnectionFactory.Equals(explicitConnectionFactory)) + { + explicitConnectionFactoryCount++; + } + } + + Assert.AreEqual(1, defaultConnectionFactoryCount, "1 container should have the default connectionFactory"); + Assert.AreEqual(2, explicitConnectionFactoryCount, "2 containers should have the explicit connectionFactory"); + + } + + [Test] + public void Listeners() + { + TestObject testObject1 = (TestObject) ctx.GetObject("testObject1"); + TestObject testObject2 = (TestObject) ctx.GetObject("testObject2"); + TestMessageListener testObject3 = (TestMessageListener) ctx.GetObject("testObject3"); + + Assert.IsNull(testObject1.Name); + Assert.IsNull(testObject2.Name); + Assert.IsNull(testObject3.Message); + + + ITextMessage message1 = (ITextMessage) mocks.CreateMock(typeof (ITextMessage)); + Expect.Call(message1.Text).Return("Test1"); + mocks.Replay(message1); + + IMessageListener listener1 = GetListener("listener1"); + listener1.OnMessage(message1); + Assert.AreEqual("Test1", testObject1.Name); + mocks.Verify(message1); + + + ITextMessage message2 = (ITextMessage)mocks.CreateMock(typeof(ITextMessage)); + Expect.Call(message2.Text).Return("Test1"); + mocks.Replay(message2); + + IMessageListener listener2 = GetListener("listener2"); + listener2.OnMessage(message2); + mocks.Verify(message2); + + + ITextMessage message3 = (ITextMessage)mocks.CreateMock(typeof(ITextMessage)); + mocks.Replay(message3); + + //Default naming strategy is to use full type name. + IMessageListener listener3 = GetListener(typeof (SimpleMessageListenerContainer).FullName); + listener3.OnMessage(message3); + Assert.AreSame(message3, testObject3.Message); + mocks.Verify(message3); + + + } + + private IMessageListener GetListener(string containerObjectName) + { + SimpleMessageListenerContainer container = + (SimpleMessageListenerContainer) ctx.GetObject(containerObjectName); + return (IMessageListener) container.MessageListener; + } + } +} \ No newline at end of file diff --git a/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Config/NmsNamespaceHandlerTests.xml b/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Config/NmsNamespaceHandlerTests.xml new file mode 100644 index 00000000..905a91ea --- /dev/null +++ b/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Config/NmsNamespaceHandlerTests.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Connections/TestConnectionFactory.cs b/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Connections/TestConnectionFactory.cs new file mode 100644 index 00000000..3227d3be --- /dev/null +++ b/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Connections/TestConnectionFactory.cs @@ -0,0 +1,23 @@ + + +using Apache.NMS; + +namespace Spring.Messaging.Nms.Connections +{ + public class TestConnectionFactory : IConnectionFactory + { + #region IConnectionFactory Members + + public IConnection CreateConnection() + { + return new TestConnection(); + } + + public IConnection CreateConnection(string userName, string password) + { + return new TestConnection(); + } + + #endregion + } +} \ No newline at end of file diff --git a/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Connections/TestMessageListener.cs b/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Connections/TestMessageListener.cs new file mode 100644 index 00000000..23c1d058 --- /dev/null +++ b/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Connections/TestMessageListener.cs @@ -0,0 +1,52 @@ +#region License + +/* + * Copyright 2002-2008 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 Apache.NMS; + +namespace Spring.Messaging.Nms.Connections +{ + /// + /// + /// + /// + /// + /// + /// Mark Pollack + public class TestMessageListener : IMessageListener + { + private IMessage message; + + + public IMessage Message + { + get { return message; } + set { message = value; } + } + + #region IMessageListener Members + + public void OnMessage(IMessage message) + { + this.message = message; + } + + #endregion + } +} \ No newline at end of file diff --git a/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Connections/TestSession.cs b/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Connections/TestSession.cs index c056b019..ea9bcdba 100644 --- a/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Connections/TestSession.cs +++ b/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Connections/TestSession.cs @@ -20,12 +20,16 @@ using System; using Apache.NMS; +using Apache.NMS.ActiveMQ; +using Apache.NMS.ActiveMQ.Commands; +using Rhino.Mocks; namespace Spring.Messaging.Nms.Connections { public class TestSession : ISession { + private MockRepository mocks = new MockRepository(); private int closeCount; private int createdCount; @@ -63,7 +67,9 @@ namespace Spring.Messaging.Nms.Connections public IMessageConsumer CreateConsumer(IDestination destination, string selector) { - throw new NotImplementedException(); + //IConnectionFactory connectionFactory = (IConnectionFactory)mocks.CreateMock(typeof(IConnectionFactory)); + IMessageConsumer msgConsumer = (IMessageConsumer) mocks.CreateMock(typeof (IMessageConsumer)); + return msgConsumer; } public IMessageConsumer CreateConsumer(IDestination destination, string selector, bool noLocal) @@ -78,7 +84,7 @@ namespace Spring.Messaging.Nms.Connections public IQueue GetQueue(string name) { - throw new NotImplementedException(); + return new ActiveMQQueue(name); } public ITopic GetTopic(string name) diff --git a/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/MessageTemplateTests.cs b/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/MessageTemplateTests.cs new file mode 100644 index 00000000..f49e9a0b --- /dev/null +++ b/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/MessageTemplateTests.cs @@ -0,0 +1,198 @@ +#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 + +#region Imports + +using System.Collections; +using Apache.NMS; +using NUnit.Framework; +using Rhino.Mocks; +using Spring.Messaging.Nms.Connections; +using Spring.Messaging.Nms.Support.Destinations; +using Spring.Transaction; +using Spring.Transaction.Support; + +#endregion + +namespace Spring.Messaging.Nms +{ + /// + /// This class contains tests for + /// + /// Mark Pollack + /// $Id:$ + [TestFixture] + public class MessageTemplateTests + { + private MockRepository mocks; + private IDestinationResolver mockDestinationResolver; + private IConnectionFactory mockConnectionFactory; + private IConnection mockConnection; + + private ISession mockSession; + + [SetUp] + public void Setup() + { + mocks = new MockRepository(); + CreateMocks(); + } + + private MessageTemplate CreateTemplate() + { + MessageTemplate template = new MessageTemplate(); + template.DestinationResolver = mockDestinationResolver; + template.SessionTransacted = UseTransactedTemplate; + return template; + } + + protected virtual bool UseTransactedSession + { + get { return false; } + } + + protected virtual bool UseTransactedTemplate + { + get { return false; } + } + + private void CreateMocks() + { + mockConnectionFactory = (IConnectionFactory) mocks.CreateMock(typeof (IConnectionFactory)); + mockConnection = (IConnection) mocks.CreateMock(typeof (IConnection)); + mockSession = (ISession) mocks.CreateMock(typeof (ISession)); + + IQueue queue = (IQueue) mocks.CreateMock(typeof (IQueue)); + + Expect.Call(mockConnectionFactory.CreateConnection()).Return(mockConnection).Repeat.Once(); + if (UseTransactedTemplate) + { + Expect.Call(mockConnection.CreateSession(AcknowledgementMode.Transactional)).Return(mockSession).Repeat. + Once(); + } + else + { + Expect.Call(mockConnection.CreateSession(AcknowledgementMode.AutoAcknowledge)).Return(mockSession). + Repeat. + Once(); + } + Expect.Call(mockSession.Transacted).Return(true); + + mockDestinationResolver = + (IDestinationResolver) mocks.CreateMock(typeof (IDestinationResolver)); + mockDestinationResolver.ResolveDestinationName(mockSession, "testDestination", false); + LastCall.Return(queue).Repeat.Any(); + } + + [Test] + public void SessionCallback() + { + MessageTemplate template = CreateTemplate(); + template.ConnectionFactory = mockConnectionFactory; + mockSession.Close(); + LastCall.On(mockSession).Repeat.Once(); + mockConnection.Close(); + LastCall.On(mockConnection).Repeat.Once(); + + mocks.ReplayAll(); + + template.Execute(delegate(ISession session) + { + bool b = session.Transacted; + return null; + }); + mocks.VerifyAll(); + } + + [Test] + public void SessionCallbackWithinSynchronizedTransaction() + { + SingleConnectionFactory scf = new SingleConnectionFactory(mockConnectionFactory); + MessageTemplate template = CreateTemplate(); + template.ConnectionFactory = scf; + + mockConnection.Start(); + LastCall.On(mockConnection).Repeat.Times(2); + Expect.Call(mockSession.Transacted).Return(UseTransactedSession).Repeat.Twice(); + + if (UseTransactedTemplate) + { + mockSession.Commit(); + LastCall.On(mockSession).Repeat.Once(); + } + + mockSession.Close(); + LastCall.On(mockSession).Repeat.Once(); + mockConnection.Stop(); + LastCall.On(mockConnection).Repeat.Once(); + mockConnection.Close(); + LastCall.On(mockConnection).Repeat.Once(); + + mocks.ReplayAll(); + + + TransactionSynchronizationManager.InitSynchronization(); + + try + { + template.Execute(delegate(ISession session) + { + bool b = session.Transacted; + return null; + }); + template.Execute(delegate(ISession session) + { + bool b = session.Transacted; + return null; + }); + + Assert.AreSame(mockSession, ConnectionFactoryUtils.GetTransactionalSession(scf, null, false)); + Assert.AreSame(mockSession, ConnectionFactoryUtils.GetTransactionalSession(scf, scf.CreateConnection(), false)); + + //In Java this test was doing 'double-duty' and testing TransactionAwareConnectionFactoryProxy, which has + //not been implemented in .NET + + template.Execute(delegate(ISession session) + { + bool b = session.Transacted; + return null; + }); + + IList synchs = TransactionSynchronizationManager.Synchronizations; + Assert.AreEqual(1, synchs.Count); + ITransactionSynchronization synch = (ITransactionSynchronization)synchs[0]; + synch.BeforeCommit(false); + synch.BeforeCompletion(); + synch.AfterCommit(); + synch.AfterCompletion(TransactionSynchronizationStatus.Unknown); + + } + finally + { + TransactionSynchronizationManager.ClearSynchronization(); + //Assert.IsTrue(TransactionSynchronizationManager.ResourceDictionary.Count == 0); + //Assert.IsFalse(TransactionSynchronizationManager.SynchronizationActive); + scf.Dispose(); + } + Assert.IsTrue(TransactionSynchronizationManager.ResourceDictionary.Count == 0); + mocks.VerifyAll(); + } + } +} \ No newline at end of file diff --git a/test/Spring/Spring.Messaging.Nms.Tests/Spring.Messaging.Nms.Tests.2005.csproj b/test/Spring/Spring.Messaging.Nms.Tests/Spring.Messaging.Nms.Tests.2005.csproj index 218bbade..76f86021 100644 --- a/test/Spring/Spring.Messaging.Nms.Tests/Spring.Messaging.Nms.Tests.2005.csproj +++ b/test/Spring/Spring.Messaging.Nms.Tests/Spring.Messaging.Nms.Tests.2005.csproj @@ -88,19 +88,24 @@ + - + + + + + Always