NMS development

This commit is contained in:
markpollack
2008-08-01 06:22:54 +00:00
parent 7e3b716350
commit 1eed54d668
26 changed files with 1334 additions and 26 deletions

Binary file not shown.

View File

@@ -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
{
/// <summary>
/// Parser for the NMS <code>&lt;listener-container&gt;</code> element.
/// </summary>
/// <author>Mark Fisher</author>
/// <author>Juergen Hoeller</author>
/// <author>Mark Pollack (.NET)</author>
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
/// <summary>
/// Parse the specified XmlElement and register the resulting
/// ObjectDefinitions with the <see cref="ParserContext.Registry"/> IObjectDefinitionRegistry
/// embedded in the supplied <see cref="ParserContext"/>
/// </summary>
/// <param name="element">The element to be parsed.</param>
/// <param name="parserContext">TThe object encapsulating the current state of the parsing process.
/// Provides access to a IObjectDefinitionRegistry</param>
/// <returns>The primary object definition.</returns>
/// <remarks>
/// <p>
/// This method is never invoked if the parser is namespace aware
/// and was called to process the root node.
/// </p>
/// </remarks>
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;
}
}
}
}

View File

@@ -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
{
/// <summary>
/// Namespace parser for the nms namespace.
/// </summary>
/// <author>Mark Fisher</author>
/// <author>Juergen Hoeller</author>
/// <author>Mark Pollack (.NET)</author>
[
NamespaceParser(
Namespace = "http://www.springframework.net/nms",
SchemaLocationAssemblyHint = typeof (NmsNamespaceParser),
SchemaLocation = "/Spring.Messaging.Nms.Config/spring-nms-1.2.xsd"
)
]
public class NmsNamespaceParser : NamespaceParserSupport
{
/// <summary>
/// Register a MessageListenerContainer for the '<code>listener-container</code>' tag.
/// </summary>
public override void Init()
{
RegisterObjectDefinitionParser("listener-container", new MessageListenerContainerObjectDefinitionParser());
}
}
}

View File

@@ -0,0 +1,183 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsd:schema xmlns="http://www.springframework.net/nms"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:vs="http://schemas.microsoft.com/Visual-Studio-Intellisense"
targetNamespace="http://www.springframework.net/nms"
elementFormDefault="qualified"
attributeFormDefault="unqualified"
vs:friendlyname="Spring.NET NMS Configuration" vs:ishtmlschema="false" vs:iscasesensitive="true" vs:requireattributequotes="true" vs:defaultnamespacequalifier="" vs:defaultnsprefix="">
<xsd:annotation>
<xsd:documentation><![CDATA[
Defines the configuration elements for the Spring Framework's NMS support.
Allows for configuring NMS listener containers in XML 'shortcut' style.
]]></xsd:documentation>
</xsd:annotation>
<xsd:element name="listener-container">
<xsd:annotation>
<xsd:documentation><![CDATA[
Each listener child element will be hosted by a container whose configuration
is determined by this parent element. This variant builds standard NMS
listener containers, operating against a specified NMS ConnectionFactory.
]]></xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:sequence>
<xsd:element name="listener" type="listenerType" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attribute name="container-type" default="simple">
<xsd:annotation>
<xsd:documentation><![CDATA[
The type of this listener container: "simple" is the only option as of Spring.NET 1.2
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:NMTOKEN">
<xsd:enumeration value="simple"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="connection-factory" type="xsd:string" default="connectionFactory">
<xsd:annotation>
<xsd:documentation><![CDATA[
A reference to the NMS ConnectionFactory bean.
Default is "connectionFactory".
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="destination-resolver" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
A reference to the DestinationResolver strategy for resolving destination names.
Default is a DynamicDestinationResolver, using the NMS provider's queue/topic
name resolution.]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="message-converter" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
A reference to the MessageConverter strategy for converting NMS Messages to
listener method arguments. Default is a SimpleMessageConverter.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="destination-type" default="queue">
<xsd:annotation>
<xsd:documentation><![CDATA[
The NMS destination type for this listener: "queue", "topic" or "durableTopic".
The default is "queue".
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:NMTOKEN">
<xsd:enumeration value="queue"/>
<xsd:enumeration value="topic"/>
<xsd:enumeration value="durableTopic"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="client-id" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The NMS client id for this listener container.
Needs to be specified when using durable subscriptions.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="acknowledge" default="auto">
<xsd:annotation>
<xsd:documentation><![CDATA[
The native NMS acknowledge mode: "auto", "client", "dups-ok" or "transacted".
A value of "transacted" effectively activates a locally transacted Session;
as alternative, specify an external "transaction-manager" via the corresponding
attribute. Default is "auto".
]]></xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:NMTOKEN">
<xsd:enumeration value="auto"/>
<xsd:enumeration value="client"/>
<xsd:enumeration value="dups-ok"/>
<xsd:enumeration value="transacted"/>
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
<xsd:attribute name="concurrency" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The number of concurrent sessions/consumers to start for each listener.
Default is 1; keep concurrency limited to 1 in case of a topic listener
or if message ordering is important; consider raising it for general queues.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:complexType name="listenerType">
<xsd:attribute name="id" type="xsd:ID">
<xsd:annotation>
<xsd:documentation><![CDATA[
The unique identifier for a listener.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="destination" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation><![CDATA[
The destination name for this listener, resolved through the
container-wide IDestinationResolver strategy (if any). Required.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="subscription" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name for the durable subscription, if any.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="selector" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The NMS message selector for this listener.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="ref" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation><![CDATA[
The object name of the listener object, implementing
the IMessageListener/ISessionAwareMessageListener interface
or defining the specified listener method. Required.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="method" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of the listener method to invoke. If not specified,
the target object is supposed to implement the IMessageListener
or ISessionAwareMessageListener interface.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="response-destination" type="xsd:string">
<xsd:annotation>
<xsd:documentation><![CDATA[
The name of the default response destination to send response messages to.
This will be applied in case of a request message that does not carry
a "NMSReplyTo" field. The type of this destination will be determined
by the listener-container's "destination-type" attribute.
Note: This only applies to a listener method with a return value,
for which each result object will be converted into a response message.
]]></xsd:documentation>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>
</xsd:schema>

View File

@@ -382,10 +382,10 @@ namespace Spring.Messaging.Nms.Connections
/// <returns>the wrapped connection</returns>
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

View File

@@ -52,6 +52,20 @@ namespace Spring.Messaging.Nms
/// </returns>
/// <throws>NMSException if there is any problem </throws>
object Execute(ISessionCallback action);
/// <summary> Execute the action specified by the given action object within
/// a NMS Session.
/// </summary>
/// <remarks>
/// <para>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.</para>b
/// </remarks>
/// <param name="del">delegate that exposes the session</param>
/// <returns> the result object from working with the session
/// </returns>
/// <throws>NMSException if there is any problem </throws>
object Execute(SessionDelegate del);
/// <summary> Send a message to a NMS destination. The callback gives access to
/// the NMS session and MessageProducer in order to do more complex

View File

@@ -26,8 +26,9 @@ namespace Spring.Messaging.Nms
/// Session
/// </summary>
/// <remarks>
/// <p>To be used with the MessageTemplate.Execute(ISessionCallback)}
/// method, often implemented as an anonymous inner class.</p>
/// <para>To be used with the MessageTemplate.Execute(ISessionCallback)}
/// method. See <see cref="SessionDelegate"/> for the equivalent callback
/// that can be used as a (anonymous) delegate.</para>
/// </remarks>
/// <author>Mark Pollack</author>
/// <seealso cref="MessageTemplate.Execute(ISessionCallback,bool)">

View File

@@ -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

View File

@@ -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
{
/// <summary>
/// Exception to be thrown when the execution of a listener method failed.
/// </summary>
/// <author>Juergen Hoeller</author>
/// <author>Mark Pollack (.NET)</author>
public class ListenerExecutionFailedException : NMSException
{
/// <summary>
/// Initializes a new instance of the <see cref="ListenerExecutionFailedException"/> class, with the specified message
/// </summary>
/// <param name="message">The message.</param>
public ListenerExecutionFailedException(string message) : base(message)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="ListenerExecutionFailedException"/> class, with the specified message
/// and root cause exception
/// </summary>
/// <param name="message">The message.</param>
/// <param name="innerException">The inner exception.</param>
public ListenerExecutionFailedException(string message, Exception innerException)
: base(message, innerException)
{
}
}
}

View File

@@ -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
/// <para>Alternatively, specify a JMS Destination object as "defaultResponseDestination".</para>
/// </summary>
/// <value>The name of the default response destination queue.</value>
public string DefaultResponseDestinationQueueName
public string DefaultResponseQueueName
{
set { defaultResponseDestination = new DestinationNameHolder(value, false); }
}
@@ -158,7 +180,7 @@ namespace Spring.Messaging.Nms.Listener.Adapter
/// <para>Alternatively, specify a JMS Destination object as "defaultResponseDestination".</para>
/// </summary>
/// <value>The name of the default response destination topic.</value>
public string DefaultResponseDestinationTopicName
public string DefaultResponseTopicName
{
set { defaultResponseDestination = new DestinationNameHolder(value, true); }
}
@@ -238,6 +260,29 @@ namespace Spring.Messaging.Nms.Listener.Adapter
/// <param name="session">The session to operate on.</param>
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
/// </summary>
internal class DestinationNameHolder
{
private string name;
private readonly string name;
private bool isTopic;
private readonly bool isTopic;
public DestinationNameHolder(string name, bool isTopic)
{

View File

@@ -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;
}

View File

@@ -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
/// <summary>
/// Execute the action specified by the given action object within
/// a NMS Session.
/// </summary>
/// <param name="del">delegate that exposes the session</param>
/// <returns>
/// the result object from working with the session
/// </returns>
/// <remarks>
/// <para>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.</para>b
/// </remarks>
/// <throws>NMSException if there is any problem </throws>
public object Execute(SessionDelegate del)
{
return Execute(new ExecuteSessionCallbackUsingDelegate(del));
}
/// <summary> Execute the action specified by the given action object within
/// a NMS Session.
/// <p>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;

View File

@@ -0,0 +1,39 @@
#region License
/*
* Copyright <20> 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
{
/// <summary>
/// Callback delegate for code that operates on a Session.
/// </summary>
/// <param name="session">The NMS ISession object.</param>
/// <remarks>
/// <para>Allows you to execute any number of operations
/// on a single ISession, possibly returning a result a result.
/// </para>
/// </remarks>
/// <returns>A result object from working with the <code>Session</code>, if any (so can be <code>null</code>)
/// </returns>
/// <throws>NMSException if there is any problem </throws>
/// <author>Mark Pollack</author>
public delegate object SessionDelegate(ISession session);
}

View File

@@ -21,7 +21,7 @@
using Spring.Util;
using Apache.NMS;
namespace Spring.Messaging.Nms.Support.IDestinations
namespace Spring.Messaging.Nms.Support.Destinations
{
/// <summary> Simple DestinationResolver implementation resolving destination names
/// as dynamic destinations.</summary>

View File

@@ -20,7 +20,7 @@
using Apache.NMS;
namespace Spring.Messaging.Nms.Support.IDestinations
namespace Spring.Messaging.Nms.Support.Destinations
{
/// <summary> Strategy interface for resolving NMS destinations.
/// </summary>

View File

@@ -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
{
/// <summary> Base class for MessageTemplate} and other
/// NMS-accessing gateway helpers, adding destination-related properties to

View File

@@ -47,6 +47,9 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Messaging\Nms\SessionDelegate.cs" />
<Compile Include="Messaging\Nms\Config\MessageListenerContainerObjectDefinitionParser.cs" />
<Compile Include="Messaging\Nms\Config\NmsNamespaceParser.cs" />
<Compile Include="Messaging\Nms\Connections\CachedMessageProducer.cs" />
<Compile Include="Messaging\Nms\Connections\CachedSession.cs" />
<Compile Include="Messaging\Nms\Connections\CachingConnectionFactory.cs" />
@@ -67,13 +70,14 @@
<Compile Include="Messaging\Nms\ISessionCallback.cs" />
<Compile Include="Messaging\Nms\Listener\AbstractListenerContainer.cs" />
<Compile Include="Messaging\Nms\Listener\AbstractMessageListenerContainer.cs" />
<Compile Include="Messaging\Nms\Listener\Adapter\ListenerExecutionFailedException.cs" />
<Compile Include="Messaging\Nms\Listener\Adapter\MessageListenerAdapter.cs" />
<Compile Include="Messaging\Nms\Listener\ISessionAwareMessageListener.cs" />
<Compile Include="Messaging\Nms\Listener\LocallyExposedMessageResourceHolder.cs" />
<Compile Include="Messaging\Nms\Listener\SimpleMessageListenerContainer.cs" />
<Compile Include="Messaging\Nms\MessageCreatorDelegate.cs" />
<Compile Include="Messaging\Nms\MessageGatewaySupport.cs" />
<Compile Include="Messaging\Nms\MessageTemplate.cs" />
<Compile Include="Messaging\Nms\MessagingGatewaySupport.cs" />
<Compile Include="Messaging\Nms\Support\Converter\IMessageConverter.cs" />
<Compile Include="Messaging\Nms\Support\Converter\MessageConversionException.cs" />
<Compile Include="Messaging\Nms\Support\Converter\SimpleMessageConverter.cs" />
@@ -97,6 +101,11 @@
<Name>Spring.Data.2005</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Messaging\Nms\Config\spring-nms-1.2.xsd">
<SubType>Designer</SubType>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>

View File

@@ -21,11 +21,16 @@
<include name="**/*.cs" />
<include name="../GenCommonAssemblyInfo.cs" />
</sources>
<references basedir="${current.bin.dir}">
<include name="*.dll" />
<exclude name="${project::get-name()}.dll" />
<exclude name="CloverRuntime.dll" />
</references>
<resources prefix="Spring" dynamicprefix="true" failonempty="true">
<include name="**/*.keys" />
<include name="**/*.xsd" />
<exclude name="**/obj/**" />
</resources>
<references basedir="${current.bin.dir}">
<include name="*.dll" />
<exclude name="${project::get-name()}.dll" />
<exclude name="CloverRuntime.dll" />
</references>
</csc>
</target>

View File

@@ -0,0 +1,156 @@
#region License
/*
* Copyright <20> 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
{
/// <summary>
/// This class contains tests for
/// </summary>
/// <author>Mark Pollack</author>
/// <version>$Id:$</version>
[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;
}
}
}

View File

@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8" ?>
<objects xmlns="http://www.springframework.net"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:nms="http://www.springframework.net/nms">
<nms:listener-container connection-factory="testConnectionFactory"
destination-resolver="testDestinationResolver" message-converter="testMessageConverter">
<nms:listener id="listener1" destination="testDestination" ref="testObject1" method="SetName"/>
<nms:listener id="listener2" destination="testDestination" ref="testObject2" method="SetName"
response-destination="responseDestination"/>
</nms:listener-container>
<nms:listener-container>
<nms:listener destination="testDestination" ref="testObject3"/>
</nms:listener-container>
<!-- the default ConnectionFactory -->
<object id="connectionFactory" type="Spring.Messaging.Nms.Connections.TestConnectionFactory, Spring.Messaging.Nms.Tests"/>
<object id="testConnectionFactory" type="Spring.Messaging.Nms.Connections.TestConnectionFactory, Spring.Messaging.Nms.Tests"/>
<!--
<bean id="testActivationSpecFactory" class="org.springframework.jms.listener.endpoint.StubJmsActivationSpecFactory"/>
-->
<object id="testDestinationResolver" type="Spring.Messaging.Nms.Support.Destinations.DynamicDestinationResolver, Spring.Messaging.Nms"/>
<object id="testMessageConverter" type="Spring.Messaging.Nms.Support.Converter.SimpleMessageConverter, Spring.Messaging.Nms"/>
<object id="testObject1" type="Spring.Objects.TestObject, Spring.Core.Tests"/>
<object id="testObject2" type="Spring.Objects.TestObject, Spring.Core.Tests"/>
<object id="testObject3" type="Spring.Messaging.Nms.Connections.TestMessageListener, Spring.Messaging.Nms.Tests"/>
</objects>

View File

@@ -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
}
}

View File

@@ -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
{
/// <summary>
///
/// </summary>
/// <remarks>
///
/// </remarks>
/// <author>Mark Pollack</author>
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
}
}

View File

@@ -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)

View File

@@ -0,0 +1,198 @@
#region License
/*
* Copyright <20> 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
{
/// <summary>
/// This class contains tests for
/// </summary>
/// <author>Mark Pollack</author>
/// <version>$Id:$</version>
[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();
}
}
}

View File

@@ -88,19 +88,24 @@
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Compile Include="Messaging\Nms\Config\NmsNamespaceHandlerTests.cs" />
<Compile Include="Messaging\Nms\Connections\CachingConnectionFactoryTests.cs" />
<Compile Include="Messaging\Nms\Connections\NmsTransactionManagerTests.cs" />
<Compile Include="Messaging\Nms\Connections\MessageTransactionManagerTests.cs" />
<Compile Include="Messaging\Nms\Connections\SingleConnectionFactoryTests.cs" />
<Compile Include="Messaging\Nms\Connections\TestConnection.cs" />
<Compile Include="Messaging\Nms\Connections\TestExceptionListener.cs" />
<Compile Include="Messaging\Nms\Connections\TestMessageListener.cs" />
<Compile Include="Messaging\Nms\Connections\TestMessageProducer.cs" />
<Compile Include="Messaging\Nms\Connections\TestSession.cs" />
<Compile Include="Messaging\Nms\Integration\LoggingExceptionHandler.cs" />
<Compile Include="Messaging\Nms\Integration\SimpleMessageListener.cs" />
<Compile Include="Messaging\Nms\Integration\SimpleMessageListenerContainerTests.cs" />
<Compile Include="Messaging\Nms\Connections\TestConnectionFactory.cs" />
<Compile Include="Messaging\Nms\MessageTemplateTests.cs" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Messaging\Nms\Integration\SimpleMessageListenerContainerTests.xml" />
<EmbeddedResource Include="Messaging\Nms\Config\NmsNamespaceHandlerTests.xml" />
<Content Include="Spring.Messaging.Nms.Tests.dll.config">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>