diff --git a/lib/Net/2.0/Apache.NMS.dll b/lib/Net/2.0/Apache.NMS.dll index b2369c8f..b5640841 100644 Binary files a/lib/Net/2.0/Apache.NMS.dll and b/lib/Net/2.0/Apache.NMS.dll differ diff --git a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Config/MessageListenerContainerObjectDefinitionParser.cs b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Config/MessageListenerContainerObjectDefinitionParser.cs new file mode 100644 index 00000000..b208587b --- /dev/null +++ b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Config/MessageListenerContainerObjectDefinitionParser.cs @@ -0,0 +1,367 @@ +#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.Xml; +using Apache.NMS; +using Spring.Messaging.Nms.Listener; +using Spring.Messaging.Nms.Listener.Adapter; +using Spring.Objects.Factory.Config; +using Spring.Objects.Factory.Support; +using Spring.Objects.Factory.Xml; +using Spring.Util; + +namespace Spring.Messaging.Nms.Config +{ + /// + /// 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