Initial MSMQ support

This commit is contained in:
markpollack
2008-07-16 19:12:51 +00:00
parent 325250f358
commit 90379aced9
62 changed files with 6887 additions and 12 deletions

View File

@@ -17,6 +17,7 @@
<!ENTITY psa-intro SYSTEM "psa-intro.xml">
<!ENTITY remoting SYSTEM "remoting.xml">
<!ENTITY nms SYSTEM "nms.xml">
<!ENTITY msmq SYSTEM "msmq.xml">
<!ENTITY web SYSTEM "web.xml">
<!ENTITY ajax SYSTEM "ajax.xml">
<!ENTITY services SYSTEM "services.xml">
@@ -53,8 +54,8 @@
<bookinfo>
<title>The Spring.NET Framework</title>
<subtitle>Reference Documentation</subtitle>
<releaseinfo>Version 1.1.2</releaseinfo>
<pubdate>Last Updated June 12, 2008</pubdate>
<releaseinfo>Version 1.2.0 M1</releaseinfo>
<pubdate>Last Updated July 16, 2008</pubdate>
<authorgroup>
<author>
<firstname>Mark</firstname>
@@ -288,7 +289,7 @@
&services;
&webservices;
</part>
<!--
<part id="spring-integration">
<title>Integration</title>
<partintro id="spring-integration-intro">
@@ -301,15 +302,23 @@
<listitem>
<para><xref linkend="nms"/></para>
</listitem>
<listitem>
<para><xref linkend="msmq"/></para>
</listitem>
<!--
<listitem>
<para><xref linkend="scheduling"/></para>
</listitem>
-->
</itemizedlist>
</partintro>
&nms;
&msmq;
<!--
&scheduling;
-->
</part>
-->
<part id="index-vsnet">
<title>VS.NET Integration</title>
<partintro>

View File

@@ -71,7 +71,7 @@
poison-message handling policies. The message listener container leverages
Spring's support for managing transactions. Both DTC, local messaging
transactions, and local database transactions are supported. In
particular, you can easily coordinating the commit and rollback of a local
particular, you can easily coordinate the commit and rollback of a local
MessageQueueTransaction and a local database transaction when they are
used together.</para>
@@ -170,7 +170,10 @@
<classname>MessageQueueTransactionManager</classname> an implementation of
Spring's <classname>IPlatformTransactionManager</classname> abstraction
that provides a uniform API on top of various transaction manager
(ADO.NET,NHibernate, MSMQ, etc). </para>
(ADO.NET,NHibernate, MSMQ, etc). Spring's
<classname>MessageQueueTransactionManager</classname> is responsible for
createing, committing, and rolling back a MSMQ
<classname>MessageQueueTransaction</classname>.</para>
<para>While you can create the message listener container
programmatically, we will show the declarative configuration approach
@@ -283,11 +286,13 @@
queue questionTxQueue for redelivery). If the same message causes an
exception in processing 5 times ,then it will be sent transactionally to
the retryQuestionTxQueue and the message transaction will commit (removing
it from the queue questionTxQueue). The SendToQueueExceptionHandler
implements the interface
<classname>IMessageTransactionExceptionHandler</classname> (discussed
below) so you can write your own implementations should the provided ones
not meet your needs.</para>
it from the queue questionTxQueue). You can also specify that certain
exceptions should commit the transaction (remove from the queue) but this
is not shown here ,see below for more informatio non this functionality
The <classname>SendToQueueExceptionHandler</classname> implements the
interface <classname>IMessageTransactionExceptionHandler</classname>
(discussed below) so you can write your own implementations should the
provided ones not meet your needs.</para>
<para>That's the quick tour folks. Hopefully you got a general feel for
how things work, what requires configuration, and what is the code you

View File

@@ -179,7 +179,6 @@ namespace Spring.Messaging.Nms.Listener.Adapter
}
else
{
//TODO ' use as '
IMessage msg = result as IMessage;
if (msg == null)
{

View File

@@ -0,0 +1,4 @@
using System.Reflection;
[assembly: AssemblyTitle("Spring.Net MSMQ Messaging support")]
[assembly: AssemblyDescription("Interfaces and classes that provide MSMQ 3.0 support in Spring.Net")]

View File

@@ -0,0 +1,94 @@
#region License
/*
* Copyright <20> 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.Collections;
using System.Messaging;
using Spring.Context;
using Spring.Messaging.Support.Converters;
using Spring.Threading;
using Spring.Util;
namespace Spring.Messaging.Core
{
/// <summary>
/// A <see cref="IMessageQueueFactory"/> implementation that caches MessageQueue and IMessageConverter
/// instances.
/// </summary>
/// <author>Mark Pollack</author>
public class DefaultMessageQueueFactory : IMessageQueueFactory, IApplicationContextAware
{
private static readonly string QUEUE_DICTIONARY_SLOTNAME =
UniqueKey.GetTypeScopedString(typeof (DefaultMessageQueueFactory), "Queue");
private static readonly string CONVERTER_DICTIONARY_SLOTNAME =
UniqueKey.GetTypeScopedString(typeof (DefaultMessageQueueFactory), "Converter");
private IApplicationContext applicationContext;
#region IMessageQueueFactory Members
public MessageQueue CreateMessageQueue(string messageQueueObjectName)
{
AssertUtils.ArgumentHasText(messageQueueObjectName, "DefaultMessageQueueObjectName");
IDictionary queues = LogicalThreadContext.GetData(QUEUE_DICTIONARY_SLOTNAME) as IDictionary;
if (queues == null)
{
queues = new Hashtable();
LogicalThreadContext.SetData(QUEUE_DICTIONARY_SLOTNAME, queues);
}
if (!queues.Contains(messageQueueObjectName))
{
MessageQueue mq = ApplicationContext.GetObject(messageQueueObjectName) as MessageQueue;
queues.Add(messageQueueObjectName, mq);
}
return queues[messageQueueObjectName] as MessageQueue;
}
public IMessageConverter CreateMessageConverter(string messgaeConverterObjectName)
{
AssertUtils.ArgumentHasText(messgaeConverterObjectName, "MessgaeFormatterObjectName");
IDictionary converters = LogicalThreadContext.GetData(CONVERTER_DICTIONARY_SLOTNAME) as IDictionary;
if (converters == null)
{
converters = new Hashtable();
LogicalThreadContext.SetData(CONVERTER_DICTIONARY_SLOTNAME, converters);
}
if (!converters.Contains(messgaeConverterObjectName))
{
IMessageConverter mc = ApplicationContext.GetObject(messgaeConverterObjectName) as IMessageConverter;
converters.Add(messgaeConverterObjectName, mc);
}
return converters[messgaeConverterObjectName] as IMessageConverter;
}
#endregion
#region IApplicationContextAware Members
public IApplicationContext ApplicationContext
{
get { return applicationContext; }
set { applicationContext = value; }
}
#endregion
}
}

View File

@@ -0,0 +1,41 @@
#region License
/*
* Copyright <20> 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.Messaging;
using Spring.Messaging.Support.Converters;
namespace Spring.Messaging.Core
{
/// <summary>
/// An interface for creating MessageQueue and IMessageConverter objects.
/// </summary>
/// <remarks>
/// These objects have methods that are generally not thread safe, (IMessageConverter classes
/// rely on IMessageFormatter objects that are not thread safe). A major reason to
/// for this interface is to provide thread-local instances such that appliation code need
/// not be concerned with these resource management issues.
/// </remarks>
public interface IMessageQueueFactory
{
MessageQueue CreateMessageQueue(string messageQueueObjectName);
IMessageConverter CreateMessageConverter(string messageConverterObjectName);
}
}

View File

@@ -0,0 +1,160 @@
#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.Messaging;
using Spring.Messaging.Support.Converters;
namespace Spring.Messaging.Core
{
/// <summary>
/// Specifies a basic set of helper MSMQ opertions.
/// </summary>
/// <remarks>
/// <para>Implemented by <see cref="MessageQueueTemplate"/>. Not often used but a useful option
/// to enhance testability, as it can easily be mocked or stubbed.
/// </para>
/// <para>
/// Provides <code>MessageQueueTemplate's</code> <code>
/// Send(..)</code> and <code>receive(..)</code> methods that mirror various MSMQ MessageQueue
/// API methods.
/// </para>
/// </remarks>
/// <author>Mark Pollack</author>
public interface IMessageQueueOperations
{
/// <summary>
/// Send the given object to the default destination, converting the object
/// to a MSMQ message with a configured IMessageConverter.
/// </summary>
/// <remarks>This will only work with a default destination queue specified!</remarks>
/// <param name="obj">The obj.</param>
void ConvertAndSend(object obj);
/// <summary> Send the given object to the default destination, converting the object
/// to a MSMQ message with a configured IMessageConverter. The IMessagePostProcessor
/// callback allows for modification of the message after conversion.
/// <p>This will only work with a default destination specified!</p>
/// </summary>
/// <param name="obj">the object to convert to a message
/// </param>
/// <param name="messagePostProcessorDelegate">the callback to modify the message
/// </param>
/// <exception cref="MessagingException">if thrown by MSMQ API methods</exception>
void ConvertAndSend(object obj, MessagePostProcessorDelegate messagePostProcessorDelegate);
/// <summary> Send the given object to the specified destination, converting the object
/// to a MSMQ message with a configured <see cref="IMessageConverter"/> and resolving the
/// destination name to a <see cref="MessageQueue"/> using a <see cref="IMessageQueueFactory"/>
/// </summary>
/// <param name="messageQueueObjectName">the name of the destination queue
/// to send this message to (to be resolved to an actual MessageQueue
/// by a IMessageQueueFactory)
/// </param>
/// <param name="obj">the object to convert to a message
/// </param>
/// <throws>NMSException if there is any problem</throws>
void ConvertAndSend(string messageQueueObjectName, object obj);
/// <summary> Send the given object to the specified destination, converting the object
/// to a MSMQ message with a configured <see cref="IMessageConverter"/> and resolving the
/// destination name to a <see cref="MessageQueue"/> with an <see cref="IMessageQueueFactory"/>
/// The <see cref="MessagePostProcessorDelegate"/> callback allows for modification of the message after conversion.
/// </summary>
/// <param name="messageQueueObjectName">the name of the destination queue
/// to send this message to (to be resolved to an actual MessageQueue
/// by a IMessageQueueFactory)
/// </param>
/// <param name="obj">the object to convert to a message
/// </param>
/// <param name="messagePostProcessorDelegate">the callback to modify the message
/// </param>
/// <exception cref="MessagingException">if thrown by MSMQ API methods</exception>
void ConvertAndSend(string messageQueueObjectName, object obj, MessagePostProcessorDelegate messagePostProcessorDelegate);
/// <summary>
/// Receive and convert a message synchronously from the default message queue.
/// </summary>
/// <returns>The converted object</returns>
/// <exception cref="MessageQueueException">if thrown by MSMQ API methods. Note an
/// exception will be thrown if the timeout of the syncrhonous recieve operation expires.
/// </exception>
object ReceiveAndConvert();
/// <summary>
/// Receives and convert a message synchronously from the specified message queue.
/// </summary>
/// <param name="messageQueueObjectName">Name of the message queue object.</param>
/// <returns>the converted object</returns>
/// <exception cref="MessageQueueException">if thrown by MSMQ API methods. Note an
/// exception will be thrown if the timeout of the syncrhonous recieve operation expires.
/// </exception>
object ReceiveAndConvert(string messageQueueObjectName);
/// <summary>
/// Receives a message on the default message queue using the transactional settings as dicted by MessageQueue's Transactional property and
/// the current Spring managed ambient transaction.
/// </summary>
/// <returns>A message.</returns>
Message Receive();
/// <summary>
/// Receives a message on the specified queue using the transactional settings as dicted by MessageQueue's Transactional property and
/// the current Spring managed ambient transaction.
/// </summary>
/// <param name="messageQueueObjectName">Name of the message queue object.</param>
/// <returns></returns>
Message Receive(string messageQueueObjectName);
/// <summary>
/// Sends the specified message to the default message queue using the
/// transactional settings as dicted by MessageQueue's Transactional property and
/// the current Spring managed ambient transaction.
/// </summary>
/// <param name="message">The message to send</param>
void Send(Message message);
/// <summary>
/// Sends the specified message to the message queue using the
/// transactional settings as dicted by MessageQueue's Transactional property and
/// the current Spring managed ambient transaction.
/// </summary>
/// <param name="messageQueueObjectName">Name of the message queue object.</param>
/// <param name="message">The message.</param>
void Send(string messageQueueObjectName, Message message);
/// <summary>
/// Sends the specified message on the provided MessageQueue using the
/// transactional settings as dicted by MessageQueue's Transactional property and
/// the current Spring managed ambient transaction.
/// </summary>
/// <para>
/// Note that it is the callers responsibility to ensure that the MessageQueue instance
/// passed into this not being access simultaneously by other threads.
/// </para>
/// <remarks>A transactional send (either local or DTC transaction) will be
/// attempted for a transacitonal queue, falling back to a single-transaction send
/// to a transactional queue if there is not ambient Spring managed transaction.</remarks>
/// <param name="messageQueue">The DefaultMessageQueue to send a message to.</param>
/// <param name="message">The message to send</param>
void Send(MessageQueue messageQueue, Message message);
}
}

View File

@@ -0,0 +1,37 @@
#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.Messaging;
namespace Spring.Messaging.Core
{
/// <summary>
/// MessageQueueResourceHolder marker subclass that indicates local exposure,
/// i.e. that does not indicate an externally managed transaction.
/// </summary>
/// <author>Mark Pollack</author>
public class LocallyExposedMessageQueueResourceHolder : MessageQueueResourceHolder
{
public LocallyExposedMessageQueueResourceHolder(MessageQueueTransaction messageQueueTransaction)
: base(messageQueueTransaction)
{
}
}
}

View File

@@ -0,0 +1,36 @@
#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.Messaging;
namespace Spring.Messaging.Core
{
/// <summary>
/// To be used with MessageQueueTemplate's send method that
/// convert an object to a message.
/// </summary>
/// <remarks>
/// It allows for further modification of the message after it has been processed
/// by the converter. This is useful for setting of Message properties (e.g.
/// CorrelationId, AppSpecific, TimeToReachQueue).
/// </remarks>
/// <author>Mark Pollack</author>
public delegate Message MessagePostProcessorDelegate(Message message);
}

View File

@@ -0,0 +1,59 @@
#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.Messaging;
using Spring.Transaction.Support;
namespace Spring.Messaging.Core
{
/// <summary>
/// MessageQueue resource holder, wrapping a MessageQueueTransaction.
/// MessageQueueTransactionManager binds instances of this class to the thread.
/// </summary>
/// <remarks>
/// This is an SPI class, not intended to be used by applications.
/// </remarks>
/// <author>Mark Pollack</author>
public class MessageQueueResourceHolder : ResourceHolderSupport
{
private MessageQueueTransaction messageQueueTransaction;
/// <summary>
/// Initializes a new instance of the <see cref="MessageQueueResourceHolder"/> class.
/// </summary>
/// <param name="messageQueueTransaction">The message queue transaction.</param>
public MessageQueueResourceHolder(MessageQueueTransaction messageQueueTransaction)
{
this.messageQueueTransaction = messageQueueTransaction;
}
/// <summary>
/// Gets or sets the message queue transaction.
/// </summary>
/// <value>The message queue transaction.</value>
public MessageQueueTransaction MessageQueueTransaction
{
get { return messageQueueTransaction; }
set { messageQueueTransaction = value; }
}
}
}

View File

@@ -0,0 +1,409 @@
#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.Messaging;
using Common.Logging;
using Spring.Context;
using Spring.Messaging.Support;
using Spring.Messaging.Support.Converters;
using Spring.Objects.Factory;
namespace Spring.Messaging.Core
{
/// <summary>
/// Helper class that simplifies MSMQ access code.
/// </summary>
/// <remarks>
/// <para>
/// Using the System.Messaging.MessageQueue class directly in application code has a number of
/// shortcomings, namely that most operations are not thread safe (in particular Send) and
/// IMessageFormatter classes are not thread safe either.
/// </para>
/// <para>
/// The MessageQueueTemplate class overcomes these limitations letting you use a single instance
/// of MessageQueueTemplate across multiple threads to perform standard MessageQueue opertations.
/// Classes that are not thread safe are obtained and cached in thread local storage via an
/// implementation of the <see cref="IMessageQueueFactory"/> interface, specifically
/// <see cref="DefaultMessageQueueFactory"/>.
/// </para>
/// <para>
/// You can access the thread local instance of the MessageQueue associated with this template
/// via the Property DefaultMessageQueue.
/// </para>
/// <para>
/// The template's Send methods will select an appropriate transaction delivery settings so
/// calling code does not need to explicitly manage this responsibility themselves and thus
/// allowing for greater portability of code across different, but common, transactional usage scenarios.
/// </para>
/// <para>A transactional send (either local or DTC transaction) will be
/// attempted for a transacitonal queue, falling back to a single-transaction send
/// to a transactional queue if there is not ambient Spring managed transaction.
/// </para>
/// <para>The overloaded ConvertAndSend and ReceiveAndConvert methods inherit the transactional
/// semantics of the previously described Send method but more importantly, they help to ensure
/// that thread safe access to <see cref="System.Messaging.IMessageFormatter"/> instances are
/// used as well as providing additional central location to put programmic logic that translates
/// between the MSMQ Message object and the your business objects. This for example is useful if you
/// need to perform additional translation operations after calling a IMessageFormatter instance or
/// want to directly extract and process the Message body contents.
/// </para>
/// </remarks>
public class MessageQueueTemplate : IMessageQueueOperations, IInitializingObject, IApplicationContextAware
{
#region Logging Definition
private static readonly ILog LOG = LogManager.GetLogger(typeof (MessageQueueTemplate));
#endregion
#region Fields
private string defaultMessageQueueObjectName;
private string messageConverterObjectName;
private IMessageQueueFactory messageQueueFactory;
private IApplicationContext applicationContext;
private TimeSpan timeout = MessageQueue.InfiniteTimeout;
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="MessageQueueTemplate"/> class.
/// </summary>
public MessageQueueTemplate()
{
}
/// <summary>
/// Initializes a new instance of the <see cref="MessageQueueTemplate"/> class.
/// </summary>
/// <param name="messageQueueName">Name of the message queue as registered in the Spring container.</param>
public MessageQueueTemplate(string messageQueueName)
{
defaultMessageQueueObjectName = messageQueueName;
}
#endregion
#region Properties
/// <summary>
/// Gets or sets the message queue factory to use for creating MessageQueue and IMessageConverters.
/// Default value is one that support thread local instances.
/// </summary>
/// <value>The message queue factory.</value>
public IMessageQueueFactory MessageQueueFactory
{
get { return messageQueueFactory; }
set { messageQueueFactory = value; }
}
/// <summary>
/// Gets or sets the name of the default message queue as identified in the Spring container.
/// </summary>
/// <value>The name of the message queue as identified in the Spring container.</value>
public string DefaultMessageQueueObjectName
{
get { return defaultMessageQueueObjectName; }
set { defaultMessageQueueObjectName = value; }
}
/// <summary>
/// Gets or sets the name of the message converter object. The name will be passed to
/// the <see cref="IMessageQueueFactory"/> class to resolve it to an actual MessageQueue
/// instance.
/// </summary>
/// <remarks>The default name is internally generated and will register an XmlMessageConverter
/// that uses an <see cref="XmlMessageFormatter"/> and a simple System.String as its TargetType.</remarks>
/// <value>The name of the message converter object.</value>
public string MessageConverterObjectName
{
get { return messageConverterObjectName; }
set { messageConverterObjectName = value; }
}
/// <summary>
/// Gets the default message queue to be used on send/receive operations that do not
/// have a destination parameter. The MessageQueue instance is resolved using
/// the template's <see cref="IMessageQueueFactory"/>, the default implementaion
/// <see cref="DefaultMessageQueueFactory"/> will return an unique instance per thread.
/// </summary>
/// <value>The default message queue.</value>
public MessageQueue DefaultMessageQueue
{
get
{
return MessageQueueFactory.CreateMessageQueue(DefaultMessageQueueObjectName);
}
}
/// <summary>
/// Gets the message converter to use for this template. Used to resolve
/// object parameters to ConvertAndSend methods and object results
/// from ReceiveAndConvert methods.
/// </summary>
/// <remarks>
/// The default
/// </remarks>
/// <value>The message converter.</value>
public IMessageConverter MessageConverter
{
get
{
if (messageConverterObjectName == null)
{
throw new InvalidOperationException(
"No MessageConverter registered. Check configuration of MessageQueueTemplate.");
}
return messageQueueFactory.CreateMessageConverter(MessageConverterObjectName);
}
}
/// <summary>
/// Gets or sets the receive timeout to be used on recieve operations. Default value is
/// MessageQueue.InfiniteTimeout (which is actually ~3 months).
/// </summary>
/// <value>The receive timeout.</value>
public TimeSpan ReceiveTimeout
{
get { return timeout; }
set { timeout = value; }
}
#region IApplicationContextAware Members
/// <summary>
/// Set the <see cref="Spring.Context.IApplicationContext"/> that this
/// object runs in.
/// </summary>
public IApplicationContext ApplicationContext
{
get { return applicationContext; }
set { applicationContext = value; }
}
#endregion
#endregion
#region IInitializingObject Members
/// <summary>
/// Invoked by an <see cref="Spring.Objects.Factory.IObjectFactory"/>
/// after it has injected all of an object's dependencies.
/// </summary>
/// <remarks>
/// Ensure that the DefaultMessageQueueObjectName property is set, creates
/// a default implementation of the <see cref="IMessageQueueFactory"/> interface
/// (<see cref="DefaultMessageQueueFactory"/>) that retrieves instances on a per-thread
/// basis, and registers in the Spring container a default implementation of
/// <see cref="IMessageConverter"/> (<see cref="XmlMessageConverter"/>) with a
/// simple System.String as its TargetType. <see cref="QueueUtils.RegisterDefaultMessageConverter"/>
/// </remarks>
public void AfterPropertiesSet()
{
if (DefaultMessageQueueObjectName == null)
{
throw new ArgumentException("DefaultMessageQueueObjectName is required.");
}
if (MessageQueueFactory == null)
{
DefaultMessageQueueFactory mqf = new DefaultMessageQueueFactory();
mqf.ApplicationContext = applicationContext;
messageQueueFactory = mqf;
}
if (messageConverterObjectName == null)
{
messageConverterObjectName = QueueUtils.RegisterDefaultMessageConverter(applicationContext);
}
}
#endregion
#region IMessageQueueOperations Members
public void ConvertAndSend(object obj)
{
CheckDefaultMessageQueue();
ConvertAndSend(DefaultMessageQueueObjectName, obj);
}
public void ConvertAndSend(object obj, MessagePostProcessorDelegate messagePostProcessorDelegate)
{
CheckDefaultMessageQueue();
ConvertAndSend(DefaultMessageQueueObjectName, obj, messagePostProcessorDelegate);
}
public void ConvertAndSend(string messageQueueObjectName, object obj)
{
Message msg = MessageConverter.ToMessage(obj);
Send(MessageQueueFactory.CreateMessageQueue(messageQueueObjectName), msg);
}
public void ConvertAndSend(string messageQueueObjectName, object obj,
MessagePostProcessorDelegate messagePostProcessorDelegate)
{
Message msg = MessageConverter.ToMessage(obj);
Message msgToSend = messagePostProcessorDelegate(msg);
Send(MessageQueueFactory.CreateMessageQueue(messageQueueObjectName), msgToSend);
}
public object ReceiveAndConvert()
{
MessageQueue mq = DefaultMessageQueue;
Message m = mq.Receive(ReceiveTimeout);
return DoConvertMessage(m);
}
public object ReceiveAndConvert(string messageQueueObjectName)
{
MessageQueue mq = MessageQueueFactory.CreateMessageQueue(messageQueueObjectName);
Message m = mq.Receive(ReceiveTimeout);
return DoConvertMessage(m);
}
public Message Receive()
{
return DefaultMessageQueue.Receive(ReceiveTimeout);
}
public Message Receive(string messageQueueObjectName)
{
return MessageQueueFactory.CreateMessageQueue(messageQueueObjectName).Receive(ReceiveTimeout);
}
public void Send(Message message)
{
Send(DefaultMessageQueue, message);
}
public void Send(string messageQueueObjectName, Message message)
{
Send(MessageQueueFactory.CreateMessageQueue(messageQueueObjectName), message);
}
public virtual void Send(MessageQueue messageQueue, Message message)
{
DoSend(messageQueue, message);
}
#endregion
#region Protected Methods
protected virtual void DoSend(MessageQueue messageQueue, Message message)
{
if (System.Transactions.Transaction.Current == null)
{
DoSendMessageQueueTransactional(messageQueue, message);
}
else
{
DoSendTxScope(messageQueue, message);
}
}
protected virtual void DoSendMessageQueueTransactional(MessageQueue mq, Message msg)
{
MessageQueueTransaction transactionToUse = QueueUtils.GetMessageQueueTransaction(null);
if (mq.Transactional)
{
// DefaultMessageQueue transaction is externally managed.
if (transactionToUse != null)
{
if (LOG.IsDebugEnabled)
{
LOG.Debug(
"Sending messsage using externally managed MessageQueueTransction to transactional queue [" +
mq.QueueName + "].");
}
mq.Send(msg, transactionToUse);
}
else
{
/* From MSDN documentation
* If a non-transactional message is sent to a transactional queue,
* this component creates a single-message transaction for it,
* except in the case of referencing a queue on a remote computer
* using a direct format name. In this situation, if you do not specify a
* transaction context when sending a message, one is not created for you
* and the message will be sent to the dead-letter queue.*/
LOG.Warn("Sending message using implicit single-message transaction to transactional queue [" +
mq.QueueName + "].");
mq.Send(msg, MessageQueueTransactionType.Single);
}
}
else
{
if (transactionToUse != null)
{
LOG.Warn("Thread local message transaction ignored for sending to non-transactional queue.");
mq.Send(msg);
}
else
{
if (LOG.IsDebugEnabled)
{
LOG.Debug("Sending messsage without MSMQ transaction to non-TX-QUEUE.");
}
//Typical case, non TLS transaction, non-tx queue.
mq.Send(msg);
}
}
}
protected virtual void DoSendTxScope(MessageQueue mq, Message msg)
{
mq.Send(msg, MessageQueueTransactionType.Automatic);
}
protected virtual object DoConvertMessage(Message m)
{
if (m != null)
{
return MessageConverter.FromMessage(m);
}
else
{
return null;
}
}
protected virtual void CheckDefaultMessageQueue()
{
if (DefaultMessageQueueObjectName == null)
{
throw new SystemException("No DefaultMessageQueueObjectName specified. Check configuration of MessageQueueTemplate.");
}
}
#endregion
}
}

View File

@@ -0,0 +1,192 @@
#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.Messaging;
using Common.Logging;
using Spring.Data.Core;
using Spring.Transaction;
using Spring.Transaction.Support;
using Spring.Util;
namespace Spring.Messaging.Core
{
/// <summary>
/// <see cref="IPlatformTransactionManager"/> implementation for MSMQ. Binds a
/// MessageQueueTransaction to the thread.
/// </summary>
/// <remarks>
/// <para>
/// This local strategy is an alternative to executing MSMQ operations within
/// DTC transactions. Its advantage is that multiple MSMQ operations can
/// easily participate within the same local MessagingTransaction transparently when
/// using the <see cref="MessageQueueTemplate"/> class for send and recieve operations
/// and not pay the overhead of a DTC transaction.
/// </para>
/// <para>Transaction synchronization is turned off by default, as this manager might
/// be used alongside a IDbProvider-based Spring transaction manager such as the
/// ADO.NET <see cref="AdoPlatformTransactionManager"/>.
/// which has stronger needs for synchronization.</para>
/// </remarks>
/// <author>Mark Pollack</author>
public class MessageQueueTransactionManager : AbstractPlatformTransactionManager
{
public static readonly string CURRENT_TRANSACTION_SLOTNAME =
UniqueKey.GetTypeScopedString(typeof (MessageQueueTransaction), "Current");
#region Logging Definition
private static readonly ILog LOG = LogManager.GetLogger(typeof (MessageQueueTransactionManager));
#endregion
/// <summary>
/// Initializes a new instance of the <see cref="MessageQueueTransactionManager"/> class.
/// </summary>
/// <remarks>
/// Turns off transaction synchronization by default, as this manager might
/// be used alongside a DbProvider-based Spring transaction manager like
/// AdoPlatformTransactionManager, which has stronger needs for synchronization.
/// Only one manager is allowed to drive synchronization at any point of time.
/// </remarks>
public MessageQueueTransactionManager()
{
TransactionSynchronization = TransactionSynchronizationState.Never;
}
protected override object DoGetTransaction()
{
MessageQueueTransactionObject txObject = new MessageQueueTransactionObject();
txObject.ResourceHolder =
(MessageQueueResourceHolder) TransactionSynchronizationManager.GetResource(CURRENT_TRANSACTION_SLOTNAME);
return txObject;
}
protected override bool IsExistingTransaction(object transaction)
{
MessageQueueTransactionObject txObject = (MessageQueueTransactionObject) transaction;
return (txObject.ResourceHolder != null);
}
protected override void DoBegin(object transaction, ITransactionDefinition definition)
{
//TODO check isolation level is different than default value?
MessageQueueTransactionObject txObject = (MessageQueueTransactionObject) transaction;
MessageQueueTransaction mqt = new MessageQueueTransaction();
mqt.Begin();
txObject.ResourceHolder = new MessageQueueResourceHolder(mqt);
txObject.ResourceHolder.SynchronizedWithTransaction = true;
int timeout = DetermineTimeout(definition);
if (timeout != DefaultTransactionDefinition.TIMEOUT_DEFAULT)
{
txObject.ResourceHolder.TimeoutInSeconds = timeout;
}
TransactionSynchronizationManager.BindResource(CURRENT_TRANSACTION_SLOTNAME, txObject.ResourceHolder);
}
protected override object DoSuspend(object transaction)
{
MessageQueueTransactionObject txObject = (MessageQueueTransactionObject) transaction;
txObject.ResourceHolder = null;
return TransactionSynchronizationManager.UnbindResource(CURRENT_TRANSACTION_SLOTNAME);
}
protected override void DoResume(object transaction, object suspendedResources)
{
MessageQueueResourceHolder queueHolder = (MessageQueueResourceHolder) suspendedResources;
TransactionSynchronizationManager.BindResource(CURRENT_TRANSACTION_SLOTNAME, queueHolder);
}
protected override void DoCommit(DefaultTransactionStatus status)
{
MessageQueueTransactionObject txObject = (MessageQueueTransactionObject) status.Transaction;
MessageQueueTransaction transaction = txObject.ResourceHolder.MessageQueueTransaction;
try
{
if (LOG.IsDebugEnabled)
{
LOG.Debug("Committing MessageQueueTransaction");
}
transaction.Commit();
}
catch (MessageQueueException ex)
{
throw new TransactionSystemException("Could not commit DefaultMessageQueue transaction", ex);
}
}
protected override void DoRollback(DefaultTransactionStatus status)
{
MessageQueueTransactionObject txObject = (MessageQueueTransactionObject) status.Transaction;
MessageQueueTransaction transaction = txObject.ResourceHolder.MessageQueueTransaction;
try
{
if (LOG.IsDebugEnabled)
{
LOG.Debug("Committing MessageQueueTransaction");
}
transaction.Abort();
}
catch (MessageQueueException ex)
{
throw new TransactionSystemException("Could not roll back DefaultMessageQueue transaction", ex);
}
}
protected override void DoSetRollbackOnly(DefaultTransactionStatus status)
{
MessageQueueTransactionObject txObject = (MessageQueueTransactionObject) status.Transaction;
txObject.ResourceHolder.RollbackOnly = true;
}
protected override void DoCleanupAfterCompletion(object transaction)
{
MessageQueueTransactionObject txObject = (MessageQueueTransactionObject) transaction;
TransactionSynchronizationManager.UnbindResource(CURRENT_TRANSACTION_SLOTNAME);
txObject.ResourceHolder.Clear();
}
private class MessageQueueTransactionObject : ISmartTransactionObject
{
private MessageQueueResourceHolder resourceHolder;
public MessageQueueResourceHolder ResourceHolder
{
get { return resourceHolder; }
set { resourceHolder = value; }
}
#region ISmartTransactionObject Members
public bool RollbackOnly
{
get { return resourceHolder.RollbackOnly; }
}
#endregion
}
}
}

View File

@@ -0,0 +1,244 @@
#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.Threading;
using Common.Logging;
using Spring.Objects.Factory;
namespace Spring.Messaging.Listener
{
/// <summary>
/// Provides basic lifecyle management methods for implementing a message listener container.
/// </summary>
/// <remarks>
/// This base class does not assume any specific listener programming model
/// or listener invoker mechanism. It just provides the general runtime
/// lifecycle management needed for any kind of message-based listening mechanism.
/// <para>
/// For a concrete listener programming model, check out the
/// <see cref="AbstractMessageListenerContainer"/> subclass. For a concrete listener
/// invoker mechanism, check out the <see cref="NonTransactionalMessageListenerContainer"/>,
/// <see cref="TransactionalMessageListenerContainer"/>, or
/// <see cref="DistributedTxMessageListenerContainer"/> classes.
/// </para>
/// </remarks>
/// <author>Mark Pollack</author>
public abstract class AbstractListenerContainer : IInitializingObject, IObjectNameAware, IDisposable
{
#region Logging Definition
private static readonly ILog LOG = LogManager.GetLogger(typeof (AbstractListenerContainer));
#endregion
private bool autoStartup = true;
private string objectName;
private bool active;
private bool running;
private object lifecycleMonitor = new object();
#region Properties
/// <summary>
/// Sets a value indicating whether to automatically start the container after initialization.
/// Default is "true"; set this to "false" to allow for manual startup though the
/// <see cref="Start"/> method.
/// </summary>
/// <value><c>true</c> if autostartup; otherwise, <c>false</c>.</value>
public bool AutoStartup
{
set { autoStartup = value; }
}
/// <summary>
/// Gets a value indicating whether this Container is active,
/// that is, whether it has been set up but not shut down yet.
/// </summary>
/// <value><c>true</c> if active; otherwise, <c>false</c>.</value>
public bool Active
{
get
{
lock (lifecycleMonitor)
{
return active;
}
}
}
/// <summary>
/// Gets a value indicating whether this Container is running,
/// that is whether it has been started and not stopped yet.
/// </summary>
/// <value><c>true</c> if running; otherwise, <c>false</c>.</value>
public bool Running
{
get
{
lock (lifecycleMonitor)
{
return (running && RunningAllowed());
}
}
}
#region IObjectNameAware Members
/// <summary>
/// Return the object name that this listener container has been assigned
/// in its containing object factory, if any.
/// </summary>
public string ObjectName
{
set { objectName = value; }
get { return objectName; }
}
#endregion
#endregion
#region IInitializingObject Members
/// <summary>
/// Delegates to <see cref="ValidateConfiguration"/> and <see cref="Initialize"/>
/// </summary>
public void AfterPropertiesSet()
{
ValidateConfiguration();
Initialize();
}
#endregion
/// <summary>
/// Validates the configuration of this container
/// The default implementation is empty. To be overridden in subclasses.
/// </summary>
protected virtual void ValidateConfiguration()
{
}
#region IDisposable Members
/// <summary>
/// Calls <see cref="Shutdown"/> when the application context destroys the container instance.
/// </summary>
public void Dispose()
{
Shutdown();
}
#endregion
public virtual void Initialize()
{
lock (lifecycleMonitor)
{
active = true;
Monitor.PulseAll(lifecycleMonitor);
}
if (autoStartup)
{
DoStart();
}
DoInitialize();
}
public virtual void Shutdown()
{
LOG.Debug("Shutting down MessageListenerContainer");
lock (lifecycleMonitor)
{
running = false;
active = false;
Monitor.PulseAll(lifecycleMonitor);
}
DoShutdown();
}
public virtual void Start()
{
DoStart();
}
protected virtual void DoStart()
{
lock (lifecycleMonitor)
{
running = true;
Monitor.PulseAll(lifecycleMonitor);
}
}
public virtual void Stop()
{
DoStop();
}
public virtual void DoStop()
{
lock (lifecycleMonitor)
{
running = false;
Monitor.PulseAll(lifecycleMonitor);
}
}
/// <summary>
/// Check whether this container's listeners are generally allowed to run.
/// </summary>
/// <remarks>
/// This implementation always returns <code>true</code>; the default 'running'
/// state is purely determined by <see cref="Start"/> and <see cref="Stop"/>
/// <para>
/// Subclasses may override this method to check against temporary
/// conditions that prevent listeners from actually running. In other words,
/// they may apply further restrictions to the 'running' state, returning
/// <code>false</code> if such a restriction prevents listeners from running.
/// </para>
/// </remarks>
/// <returns><code>false</code> if such a restriction prevents listeners from running.</returns>
protected virtual bool RunningAllowed()
{
return true;
}
#region Abstract Methods
/// <summary>
/// Subclasses need to implement this method for their specific
/// listener management process.
/// </summary>
protected abstract void DoInitialize();
/// <summary>
/// Subclasses need to implement this method for their specific
/// listener management process.
/// </summary>
protected abstract void DoShutdown();
#endregion
}
}

View File

@@ -0,0 +1,197 @@
#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.Messaging;
using Common.Logging;
using Spring.Context;
using Spring.Messaging.Core;
using Spring.Messaging.Support.Converters;
using Spring.Util;
namespace Spring.Messaging.Listener
{
/// <summary>
/// Defines a minimal programming model for message listener containers. They are expected to
/// invoke a <see cref="IMessageListener"/> upon asynchronous receives of a MSMQ message. Access to
/// obtain MessageQueue and <see cref="IMessageConverter"/> instances is available through the
/// <see cref="IMessageQueueFactory"/> property, the default implementation
/// <see cref="DefaultMessageQueueFactory"/> provides per-thread instances of these classes.
/// </summary>
/// <author>Mark Pollack</author>
public abstract class AbstractMessageListenerContainer : AbstractListenerContainer, IApplicationContextAware
{
#region Logging Definition
private static readonly ILog LOG = LogManager.GetLogger(typeof (AbstractMessageListenerContainer));
#endregion
#region Fields
private string messageQueueObjectName;
private IMessageQueueFactory messageQueueFactory;
private IApplicationContext applicationContext;
/// <summary>
/// Most operations within the MessageListener container hierarchy use methods on the
/// MessageQueue instance which are thread safe (BeginPeek, BeginReceive,
/// EndPeek, EndReceive, GetAllMessages, Peek, and Receive). When using another
/// method on the shared MessageQueue instance, wrap calls with a lock on this object.
/// </summary>
protected object messageQueueMonitor = new object();
private IMessageListener messageListener;
private TimeSpan recoveryTimeSpan = new TimeSpan(0, 0, 0, 1, 0);
#endregion
#region Properties
/// <summary>
/// Gets or sets the message queue factory.
/// </summary>
/// <value>The message queue factory.</value>
public IMessageQueueFactory MessageQueueFactory
{
get { return messageQueueFactory; }
set { messageQueueFactory = value; }
}
/// <summary>
/// Gets or sets the name of the message queue object, as refered to in the
/// Spring configuration, that will be used to create a DefaultMessageQueue instance
/// for consuming messages in the container.
/// </summary>
/// <value>The name of the message queue object.</value>
public string MessageQueueObjectName
{
get { return messageQueueObjectName; }
set
{
AssertUtils.ArgumentNotNull(value, "MessageQueueObjectName");
messageQueueObjectName = value;
}
}
/// <summary>
/// Gets or sets the message listener.
/// </summary>
/// <value>The message listener.</value>
public IMessageListener MessageListener
{
get { return messageListener; }
set
{
AssertUtils.ArgumentNotNull(value, "MessageListener");
messageListener = value;
}
}
/// <summary>
/// Gets or sets the recovery time span, how long to sleep after an exception in processing occured
/// to avoid excessive redelivery attempts. Default value is 1 second.
/// </summary>
/// <value>The recovery time span.</value>
public TimeSpan RecoveryTimeSpan
{
get { return recoveryTimeSpan; }
set { recoveryTimeSpan = value; }
}
#endregion
#region IApplicationContextAware Members
public IApplicationContext ApplicationContext
{
get { return applicationContext; }
set { applicationContext = value; }
}
#endregion
#region Protected Methods
protected override void ValidateConfiguration()
{
if (MessageQueueObjectName == null)
{
throw new ArgumentException("Property 'DefaultMessageQueueObjectName' is required");
}
if (MessageQueueFactory == null)
{
DefaultMessageQueueFactory qf = new DefaultMessageQueueFactory();
qf.ApplicationContext = applicationContext;
MessageQueueFactory = qf;
}
}
protected virtual void DoExecuteListener(Message message)
{
if (!Running)
{
if (LOG.IsWarnEnabled)
{
LOG.Warn("Not processing recieved message because of the listener container " +
"having been stopped in the meantime: " + message);
}
}
InvokeListener(message);
}
protected virtual void InvokeListener(Message message)
{
if (MessageListener != null)
{
DoInvokeListener(MessageListener, message);
}
else
{
throw new InvalidOperationException("No message listener specified - see property 'MessageListener'");
}
}
protected virtual void DoInvokeListener(IMessageListener listener, Message message)
{
listener.OnMessage(message);
}
/// <summary>
/// Closes the queue handle. Cancel pending receive operation by closing the queue handle
/// To dispose of the queue handle, set EnableConnectionCache=false and call Close/Dispose.
/// </summary>
protected void CloseQueueHandle(MessageQueue mq)
{
lock (messageQueueMonitor)
{
MessageQueue.EnableConnectionCache = false;
mq.Close();
mq.Dispose();
}
}
#endregion
}
}

View File

@@ -0,0 +1,440 @@
#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.Messaging;
using System.Threading;
using Common.Logging;
namespace Spring.Messaging.Listener
{
/// <summary>
/// Base class for listener container implementations which are based on Peeking for messages on
/// a MessageQueue. Peeking is the only resource efficient approach that can be used in
/// order to have MessageQueue receipt in conjunction with transactions, either local MSMQ transactions,
/// local ADO.NET based transactions, or DTC transactions. See SimpleMessageListenerContainer for
/// an implementation based on a synchronous receives and you do not require transactional support.
/// </summary>
/// <remarks>
/// The number of threads that will be created for processing messages after the Peek occurs
/// is set via the property MaxConcurrentListeners. Each processing thread will continue to listen
/// for messages up until the the timeout value specified by ListenerTimeLimit or until
/// there are no more messages on the queue (which ver comes first).
/// <para>
/// The default value of
/// ListenerTimeLimit is TimeSpan.Zero, meaning that only one attempt to recieve a message from the
/// queue will be performed by each listener thread.
/// </para>
/// <para>
/// The current implementation uses the standard .NET thread pool. Future implementations will
/// use a custom (and pluggable) thread pool.
/// </para>
/// </remarks>
public abstract class AbstractPeekingMessageListenerContainer : AbstractMessageListenerContainer
{
#region Logging Definition
private static readonly ILog LOG = LogManager.GetLogger(typeof (AbstractPeekingMessageListenerContainer));
#endregion
#region Fields
private Thread dispatcherThread;
private ManualResetEvent stopEvent = new ManualResetEvent(false);
private MessageQueue messageQueue;
private int maxConcurrentListeners = 1;
private bool setMaxConcurrentListenersCalled = false;
private int activeListenerCount;
private int scheduledListenerCount;
private object activeListenerMonitor = new object();
private TimeSpan listenerTimeLimit = TimeSpan.Zero;
#endregion
#region Properties
/// <summary>
/// Gets or sets the listener time limit to continuously receive messages.
/// The value is specified in milliseconds. The default value is TimeSpan.Zero,
/// indicating to only perform one Receive operation per Peek trigger.
/// </summary>
/// <value>The listener time limit in millis.</value>
public TimeSpan ListenerTimeLimit
{
get { return listenerTimeLimit; }
set { listenerTimeLimit = value; }
}
/// <summary>
/// Gets or sets the max concurrent listeners to receive messages.
/// </summary>
/// <value>The max concurrent listeners.</value>
public int MaxConcurrentListeners
{
get { return maxConcurrentListeners; }
set
{
if (!setMaxConcurrentListenersCalled)
{
setMaxConcurrentListenersCalled = true;
maxConcurrentListeners = value;
}
else
{
LOG.Info("Ignoring resetting of MaxConcurrentListeners. Using previous value of " +
maxConcurrentListeners);
}
}
}
/// <summary>
/// Gets or sets the message queue used for Peeking.
/// </summary>
/// <value>The message queue.</value>
public MessageQueue MessageQueue
{
get { return messageQueue; }
}
#endregion
#region Protected Container Lifecycle Methods
/// <summary>
/// Retrieves a MessageQueue instance given the MessageQueueObjectName
/// </summary>
protected override void DoInitialize()
{
messageQueue = MessageQueueFactory.CreateMessageQueue(MessageQueueObjectName);
//TODO would initialize resources for a seperate thread pool here.
}
/// <summary>
/// Wait for all listener threads to exit and closes the DefaultMessageQueue.
/// <see cref="AbstractMessageListenerContainer.CloseQueueHandle"/>
/// </summary>
protected override void DoShutdown()
{
WaitForListenerThreadsToExit();
CloseQueueHandle(MessageQueue);
if (dispatcherThread != null)
{
LOG.Debug("Waiting to join dispatcher thread.");
dispatcherThread.Join();
dispatcherThread = null;
LOG.Debug("Dispatcher thread terminated.");
}
}
/// <summary>
/// Starts peeking on the DefaultMessageQueue.
/// </summary>
protected override void DoStart()
{
base.DoStart();
stopEvent = new ManualResetEvent(false);
dispatcherThread = new Thread(new ThreadStart(StartPeeking));
ConfigureInitialPeekThread(dispatcherThread);
dispatcherThread.Start();
}
/// <summary>
/// Stops peeking on the message queue.
/// </summary>
public override void DoStop()
{
base.DoStop();
stopEvent.Set();
CloseQueueHandle(MessageQueue);
if (dispatcherThread != null)
{
LOG.Debug("Waiting to join dispatcher thread.");
dispatcherThread.Join();
dispatcherThread = null;
LOG.Debug("Dispatcher thread terminated.");
}
}
#endregion
#region Protected Methods
/// <summary>
/// Starts peeking on the DefaultMessageQueue. This is the method that must be called
/// again at the end of message procesing to continue the peeking process.
/// </summary>
protected virtual void StartPeeking()
{
if (Running)
{
try
{
IAsyncResult asynchResult = MessageQueue.BeginPeek();
LOG.Debug("Waiting on Peek AsyncWaitHandle");
int firedWaitHandle = WaitHandle.WaitAny(new WaitHandle[] {asynchResult.AsyncWaitHandle, stopEvent});
if (firedWaitHandle == 0)
{
PeekCompleted(asynchResult);
}
else
{
//Stopping processing.
return;
}
}
catch (Exception ex)
{
LOG.Error(
"Exception executing DefaultMessageQueue.BeginPeek. Reinvoking after recovery interval [" +
RecoveryTimeSpan + "]", ex);
Thread.Sleep(RecoveryTimeSpan);
StartPeeking();
}
}
}
/// <summary>
/// The callback when the peek has completed. Schedule up to the maximum number of
/// concurrent listeners to receive messages off the queue. Delegates to the abstract
/// method DoReceiveAndExecute so that subclasses may customize the receiving process,
/// for example to surround the receive operation with transactional semantics.
/// </summary>
/// <param name="asyncResult">The async result.</param>
protected virtual void PeekCompleted(IAsyncResult asyncResult)
{
bool listenerThreadWillCallStartPeek = false;
try
{
LOG.Debug("Peek Completed called.");
MessageQueue.EndPeek(asyncResult);
int numberOfListenersToSchedule = 0;
// lock also prevents listeners that are about to exit from invoking
// StartPeeking while new listeners are being scheduled.
lock (activeListenerMonitor)
{
numberOfListenersToSchedule = maxConcurrentListeners -
(activeListenerCount + scheduledListenerCount);
LOG.Debug("Submitting " + numberOfListenersToSchedule + " listener work items");
#region Submit to thread pool up to max number of concurrent listeners
for (int i = 1; i <= numberOfListenersToSchedule; i++)
{
bool wasQueued = ThreadPool.QueueUserWorkItem(new WaitCallback(ReceiveAndExecute), MessageQueue);
if (wasQueued)
{
scheduledListenerCount++;
listenerThreadWillCallStartPeek = true;
LOG.Debug("Queued ReceiveAndExecute listener # " + i);
}
else
{
LOG.Error("Could not submit ReceiveAndExecute work item for listener # " + i);
}
}
Monitor.PulseAll(activeListenerMonitor);
}
#endregion
}
catch (MessageQueueException mex)
{
switch ((int) mex.MessageQueueErrorCode)
{
case -1073741536: // = 0xc0000120 "STATUS_CANCELLED".
LOG.Info("Asynchronous Peek Thread sent STATUS_CANCELLED.");
break;
default:
LOG.Error("MessageQueueException Peeking Message", mex);
break;
}
}
catch (Exception e)
{
LOG.Error("Exception Peeking Message", e);
}
finally
{
if (listenerThreadWillCallStartPeek == false && Running)
{
LOG.Warn(
"Could not queue any listeners onto the thread pool. Calling BeginPeek again after delay of " +
RecoveryTimeSpan);
Thread.Sleep(RecoveryTimeSpan);
StartPeeking();
}
}
}
/// <summary>
/// Execute the listener for a message received from the given queue
/// wrapping the entire operation in an external transaction if demanded.
/// </summary>
/// <param name="state">The DefaultMessageQueue upon which the call to receive should be
/// called.</param>
protected virtual void ReceiveAndExecute(object state)
{
bool messageRecieved = true;
bool listenerTimeOut = false;
MessageQueue mq = state as MessageQueue;
if (mq == null)
{
throw new ArgumentException("Expected asynchronous state object to be of the type DefaultMessageQueue");
}
try
{
LOG.Debug("Executing ReceiveAndExecute");
#region Increment Active Listener Count
lock (activeListenerMonitor)
{
activeListenerCount++;
scheduledListenerCount--;
LOG.Debug("ActiveListenerCount = " + activeListenerCount);
LOG.Debug("ScheduledListenerCount = " + scheduledListenerCount);
Monitor.PulseAll(activeListenerMonitor);
}
#endregion
DateTime expirationTime = DateTime.Now.Add(ListenerTimeLimit);
while (!listenerTimeOut && messageRecieved)
{
//Subclasses to perform receive operation
messageRecieved = DoReceiveAndExecute(mq);
if (ListenerTimeLimit == TimeSpan.Zero)
{
listenerTimeOut = true;
LOG.Trace("No listener timelimit specified, exiting recieve loop after one iteration.");
}
else if (DateTime.Now >= expirationTime)
{
listenerTimeOut = true;
LOG.Trace("Listener timeout, exiting receive loop.");
}
else
{
LOG.Trace("Continuing receive loop.");
}
}
}
catch (Exception ex)
{
messageRecieved = false;
LOG.Error("Error receiving message from DefaultMessageQueue = [" + mq.QueueName + "]", ex);
}
finally
{
LOG.Debug("Exiting ReceiveAndExecute");
#region Decrementing Listener Count and call StartPeeking if last listener or there are still messages to process
lock (activeListenerMonitor)
{
activeListenerCount--;
LOG.Debug("ActiveListenerCount = " + activeListenerCount);
LOG.Trace("ListenerTimeout = " + listenerTimeOut + ", MessageRecieved = " + messageRecieved);
if (activeListenerCount == 0)
{
LOG.Debug("All processing threads ended - calling StartPeek again.");
//last active worker thread needs to restart the peeking process
StartPeeking();
}
else if (listenerTimeOut && messageRecieved)
{
LOG.Debug(
"Processing thread ended due to timeout and last recieve operation was successfull, calling StartPeek again.");
StartPeeking();
}
Monitor.PulseAll(activeListenerMonitor);
}
#endregion
}
}
/// <summary>
/// Subclasses perform a receive opertion on the message queue and execute the
/// message listener
/// </summary>
/// <param name="mq">The DefaultMessageQueue.</param>
/// <returns>true if received a message, false otherwise</returns>
protected abstract bool DoReceiveAndExecute(MessageQueue mq);
/// <summary>
/// Waits for listener threads to exit.
/// </summary>
protected virtual void WaitForListenerThreadsToExit()
{
try
{
lock (activeListenerMonitor)
{
if (activeListenerCount > 0)
{
while (activeListenerCount > 0)
{
LOG.Debug("Waiting for termination of " + activeListenerCount + " listener threads.");
Monitor.Wait(activeListenerMonitor);
}
}
}
}
catch (ThreadInterruptedException)
{
Thread.CurrentThread.Interrupt();
}
}
protected virtual void ConfigureInitialPeekThread(Thread thread)
{
thread.IsBackground = true;
}
/// <summary>
/// Template method that gets called right when a new message has been received,
/// before attempting to process it. Allows subclasses to react to the event
/// of an actual incoming message, for example adapting their consumer count.
/// </summary>
/// <param name="message">The message.</param>
protected virtual void MessageReceived(Message message)
{
}
#endregion
}
}

View File

@@ -0,0 +1,99 @@
using System;
using System.Collections;
using System.Messaging;
using Spring.Context;
using Spring.Messaging.Core;
using Spring.Objects.Factory;
namespace Spring.Messaging.Listener
{
public class AbstractSendToQueueExceptionHandler : IInitializingObject, IApplicationContextAware
{
private int maxRetry = 5;
private IMessageQueueFactory messageQueueFactory;
private string messageQueueObjectName;
private IApplicationContext applicationContext;
protected object messageMapMonitor = new object();
protected IDictionary messageMap = new Hashtable();
/// <summary>
/// Gets or sets the maximum retry count to reattempt processing of a message that has thrown
/// an exception
/// </summary>
/// <value>The max retry count.</value>
public int MaxRetry
{
get { return maxRetry; }
set { maxRetry = value; }
}
/// <summary>
/// Gets or sets the message queue factory.
/// </summary>
/// <value>The message queue factory.</value>
public IMessageQueueFactory MessageQueueFactory
{
get { return messageQueueFactory; }
set { messageQueueFactory = value; }
}
/// <summary>
/// Gets or sets the name of the message queue object to send the message that cannot be
/// processed successfully after MaxRetry delivery attempts.
/// </summary>
/// <value>The name of the message queue object.</value>
public string MessageQueueObjectName
{
get { return messageQueueObjectName; }
set { messageQueueObjectName = value; }
}
#region IApplicationContextAware Members
/// <summary>
/// Set the <see cref="Spring.Context.IApplicationContext"/> that this
/// object runs in.
/// </summary>
public IApplicationContext ApplicationContext
{
get { return applicationContext; }
set { applicationContext = value; }
}
#endregion
#region IInitializingObject Members
/// <summary>
/// Ensure that the MessageQueueObject name is set and creates a
/// <see cref="DefaultMessageQueueFactory"/> if no <see cref="IMessageQueueFactory"/>
/// is specified.
/// </summary>
/// <remarks>Will attempt to create an instance of the DefaultMessageQueue to detect early
/// any configuraiton errors.</remarks>
/// <exception cref="System.Exception">
/// In the event of misconfiguration (such as the failure to set a
/// required property) or if initialization fails.
/// </exception>
public virtual void AfterPropertiesSet()
{
if (MessageQueueObjectName == null)
{
throw new ArgumentException("The DefaultMessageQueueObjectName property has not been set.");
}
if (messageQueueFactory == null)
{
DefaultMessageQueueFactory mqf = new DefaultMessageQueueFactory();
mqf.ApplicationContext = applicationContext;
messageQueueFactory = mqf;
}
//Create an instance so we can 'fail-fast' if there isn't an DefaultMessageQueue unde
MessageQueue mq = MessageQueueFactory.CreateMessageQueue(messageQueueObjectName);
}
#endregion
}
}

View File

@@ -0,0 +1,120 @@
#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.Messaging;
using System.Threading;
using Common.Logging;
using Spring.Data.Core;
using Spring.Messaging.Core;
using Spring.Transaction;
using Spring.Transaction.Support;
namespace Spring.Messaging.Listener
{
/// <summary>
/// An implementation of a Peeking based MessageListener container that starts a transaction
/// before recieving a message. The <see cref="IPlatformTransactionManager"/> implementation determines
/// the type of transaction that will be started. An exception while processing the message will
/// result in a rollback, otherwise a transaction commit will be performed.
/// </summary>
/// <remarks>
/// The type of transaction that can be started can either be local transaction,
/// (e.g. <see cref="AdoPlatformTransactionManager"/>, a local messaging transaction
/// (e.g. <see cref="MessageQueueTransactionManager"/> or a DTC based transaction,
/// (eg. <see cref="TxScopeTransactionManager"/>.
/// <para>
/// Transaction properties can be set using the property <see cref="TransactionDefinition"/>
/// and the transaction timeout via the property <see cref="TransactionTimeout"/>.
/// </para>
/// </remarks>
public abstract class AbstractTransactionalMessageListenerContainer : AbstractPeekingMessageListenerContainer
{
#region Logging Definition
private static readonly ILog LOG = LogManager.GetLogger(typeof (AbstractTransactionalMessageListenerContainer));
#endregion
private IPlatformTransactionManager platformTransactionManager;
private DefaultTransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
public IPlatformTransactionManager PlatformTransactionManager
{
get { return platformTransactionManager; }
set { platformTransactionManager = value; }
}
public DefaultTransactionDefinition TransactionDefinition
{
get { return transactionDefinition; }
set { transactionDefinition = value; }
}
/// <summary>
/// Sets the transaction timeout to use for transactional wrapping, in <b>seconds</b>.
/// Default is none, using the transaction manager's default timeout.
/// </summary>
/// <value>The transaction timeout.</value>
public int TransactionTimeout
{
set { transactionDefinition.TransactionTimeout = value; }
}
protected override bool DoReceiveAndExecute(MessageQueue mq)
{
bool messageReceived = false;
// Execute receive within transaction.
ITransactionStatus status = PlatformTransactionManager.GetTransaction(TransactionDefinition);
try
{
messageReceived = DoReceiveAndExecuteUsingPlatformTransactionManager(mq, status);
}
catch (Exception ex)
{
RollbackOnException(status, ex);
Thread.Sleep(RecoveryTimeSpan);
throw;
}
//if status has indicated rollback only, will rollback.
PlatformTransactionManager.Commit(status);
return messageReceived;
}
protected abstract bool DoReceiveAndExecuteUsingPlatformTransactionManager(MessageQueue mq,
ITransactionStatus status);
protected void RollbackOnException(ITransactionStatus status, Exception ex)
{
LOG.Debug("Initiating transaction rollback on listener exception", ex);
try
{
PlatformTransactionManager.Rollback(status);
}
catch (Exception ex2)
{
LOG.Error("Listener exception overridden by rollback error", ex2);
throw;
}
}
}
}

View File

@@ -0,0 +1,198 @@
#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.Messaging;
using System.Transactions;
using Common.Logging;
using Spring.Transaction;
namespace Spring.Messaging.Listener
{
/// <summary>
/// A MessageListenerContainer that uses distributed (DTC) based transactions. Exceptions are
/// handled by instances of <see cref="IDistributedTransactionExceptionHandler"/>.
/// </summary>
/// <remarks>
/// <para>
/// Starts a DTC based transaction before receiving the message. The transaction is
/// automaticaly promoted to 2PC to avoid the default behaivor of transactional promotion.
/// Database and messaging operations will commit or rollback together.
/// </para>
/// <para>
/// If you only want local message based transactions use the
/// <see cref="TransactionalMessageListenerContainer"/>. With some simple programming
/// you may also achieve 'exactly once' processing using the
/// <see cref="TransactionalMessageListenerContainer"/>.
/// </para>
/// <para>
/// Poison messages can be detected and sent to another queue using Spring's
/// <see cref="SendToQueueDistributedTransactionExceptionHandler"/>.
/// </para>
/// </remarks>
public class DistributedTxMessageListenerContainer : AbstractTransactionalMessageListenerContainer
{
#region Logging Definition
private static readonly ILog LOG = LogManager.GetLogger(typeof (DistributedTxMessageListenerContainer));
#endregion
private IDistributedTransactionExceptionHandler distributedTransactionExceptionHandler;
/// <summary>
/// Gets or sets the distributed transaction exception handler.
/// </summary>
/// <value>The distributed transaction exception handler.</value>
public IDistributedTransactionExceptionHandler DistributedTransactionExceptionHandler
{
get { return distributedTransactionExceptionHandler; }
set { distributedTransactionExceptionHandler = value; }
}
/// <summary>
/// Set the transaction name to be the spring object name.
/// Call base class Initialize() functionality.
/// </summary>
public override void Initialize()
{
// Use object name as default transaction name.
if (TransactionDefinition.Name == null)
{
TransactionDefinition.Name = ObjectName;
}
// Proceed with superclass initialization.
base.Initialize();
}
protected override bool DoReceiveAndExecuteUsingPlatformTransactionManager(MessageQueue mq,
ITransactionStatus status)
{
#region Logging
if (LOG.IsDebugEnabled)
{
LOG.Debug("Executing DoReceiveAndExecuteUsingTxScopeTransactionManager");
}
#endregion Logging
//We are sure to be talking to a second resource manager, so avoid going through
//the promotable transaction and force a distributed transaction right from the start.
TransactionInterop.GetTransmitterPropagationToken(System.Transactions.Transaction.Current);
Message message;
try
{
message = mq.Receive(TimeSpan.Zero, MessageQueueTransactionType.Automatic);
}
catch (MessageQueueException ex)
{
if (ex.MessageQueueErrorCode != MessageQueueErrorCode.IOTimeout)
{
//expected to occur occasionally
#region Logging
if (LOG.IsTraceEnabled)
{
LOG.Trace(
"MessageQueueErrorCode.IOTimeout: No message available to receive. May have been processed by another thread.");
}
#endregion
status.RollbackOnly = true;
return false; // no more peeking unless this is the last listener thread
}
else
{
// A real issue in receiving the message
lock (messageQueueMonitor)
{
mq.Close();
MessageQueue.ClearConnectionCache();
}
throw; // will cause rollback in surrounding platform transaction manager and log exception
}
}
if (message == null)
{
#region Logging
if (LOG.IsTraceEnabled)
{
LOG.Trace("Message recieved is null from Queue = [" + mq.QueueName + "]");
}
#endregion
status.RollbackOnly = true;
return false; // no more peeking unless this is the last listener thread
}
try
{
#region Logging
if (LOG.IsDebugEnabled)
{
LOG.Debug("Received message [" + message.Id + "] on queue [" + mq.QueueName + "]");
}
#endregion
MessageReceived(message);
if (DistributedTransactionExceptionHandler != null)
{
if (DistributedTransactionExceptionHandler.IsPoisonMessage(message))
{
DistributedTransactionExceptionHandler.HandlePoisonMessage(message);
return true; // will remove from queue and continue receive loop.
}
}
DoExecuteListener(message);
}
catch (Exception ex)
{
HandleDistributedTransactionListenerException(ex, message);
throw; // will rollback and keep message on the queue.
}
finally
{
message.Dispose();
}
return true;
}
protected virtual void HandleDistributedTransactionListenerException(Exception exception, Message message)
{
IDistributedTransactionExceptionHandler exceptionHandler = DistributedTransactionExceptionHandler;
if (exceptionHandler != null)
{
exceptionHandler.OnException(exception, message);
}
}
}
}

View File

@@ -0,0 +1,66 @@
#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.Messaging;
namespace Spring.Messaging.Listener
{
/// <summary>
/// Exception handler for use with DTC based message listener container.
/// such as <see cref="DistributedTxMessageListenerContainer"/>.
/// See <see cref="SendToQueueDistributedTransactionExceptionHandler"/> for
/// an implementation that detects poison messages and send them to another queue.
/// </summary>
public interface IDistributedTransactionExceptionHandler
{
/// <summary>
/// Determines whether the incoming message is a poison message. This method is
/// called before the <see cref="IMessageListener"/> is invoked.
/// </summary>
/// <remarks>
/// The <see cref="DistributedTxMessageListenerContainer"/> will call
/// <see cref="HandlePoisonMessage"/> if this method returns true and will
/// then commit the distibuted transaction (removing the message from the queue).
/// </remarks>
/// <param name="message">The incoming message.</param>
/// <returns>
/// <c>true</c> if it is a poison message; otherwise, <c>false</c>.
/// </returns>
bool IsPoisonMessage(Message message);
/// <summary>
/// Handles the poison message.
/// </summary>
/// <remarks>Typical implementations will move the message to another queue.
/// The <see cref="DistributedTxMessageListenerContainer"/> will call this
/// method while still within a DTC-based transaction.
/// </remarks>
/// <param name="poisonMessage">The poison message.</param>
void HandlePoisonMessage(Message poisonMessage);
/// <summary>
/// Called when an exception is thrown in listener processing.
/// </summary>
/// <param name="exception">The exception.</param>
/// <param name="message">The message.</param>
void OnException(Exception exception, Message message);
}
}

View File

@@ -0,0 +1,47 @@
#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.Messaging;
namespace Spring.Messaging.Listener
{
/// <summary>
/// Exception handler called when an exception occurs during
/// non-transactional receive processing.
/// </summary>
/// <remarks>
/// A non-transactional receive will remove the message from the queue. Non-transactional
/// receivers do not suffer from poison messages since there is no redelivery by MSMQ.
/// Typical actions to perform are to log the message or place it in another queue.
/// If placed in another queue, another message listener container can be used to
/// process the message later, assuming the root cause of the original exception is
/// transient in nature.
/// </remarks>
public interface IExceptionHandler
{
/// <summary>
/// Called when an exception is thrown in listener processing.
/// </summary>
/// <param name="exception">The exception.</param>
/// <param name="message">The message.</param>
void OnException(Exception exception, Message message);
}
}

View File

@@ -0,0 +1,37 @@
#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.Messaging;
namespace Spring.Messaging.Listener
{
/// <summary>
/// The callback invoked when a message is received.
/// </summary>
/// <author>Mark Pollack</author>
public interface IMessageListener
{
/// <summary>
/// Called when message is received.
/// </summary>
/// <param name="message">The message.</param>
void OnMessage(Message message);
}
}

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 System;
using System.Messaging;
namespace Spring.Messaging.Listener
{
/// <summary>
/// The exception handler within a transactional context.
/// </summary>
/// <remarks>
/// The return value indicates to the invoker (typically a
/// <see cref="TransactionalMessageListenerContainer"/>) whether the
/// MessageTransaction that is associated with the delivery of this message
/// should be rolled back (placing the message back on the transactional queue
/// for redelivery) or commited (removing the message from the transactional queue)
/// </remarks>
/// <author>Mark Pollack</author>
public interface IMessageTransactionExceptionHandler
{
/// <summary>
/// Called when an exception is thrown during listener processing under the
/// scope of a <see cref="MessageQueueTransaction"/>.
/// </summary>
/// <param name="exception">The exception.</param>
/// <param name="message">The message.</param>
/// <param name="messageQueueTransaction">The message queue transaction.</param>
/// <returns>An action indicating if the caller should commit or rollback the
/// <see cref="MessageQueueTransaction"/>
/// </returns>
TransactionAction OnException(Exception exception, Message message,
MessageQueueTransaction messageQueueTransaction);
}
}

View File

@@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<ClassDiagram MajorVersion="1" MinorVersion="1">
<Font Name="Tahoma" Size="10" />
<Class Name="Spring.Messaging.Listener.AbstractListenerContainer" Collapsed="true">
<Position X="3.75" Y="1" Width="2.5" />
<TypeIdentifier>
<FileName>Messaging\Listener\AbstractListenerContainer.cs</FileName>
<HashCode>AAAAAAAgACgAAQAAEAEAIxAAAAgAAAYpIABAIAgAAAA=</HashCode>
</TypeIdentifier>
<Lollipop Position="0.2" />
</Class>
<Class Name="Spring.Messaging.Listener.AbstractMessageListenerContainer" Collapsed="true">
<Position X="3.75" Y="2.25" Width="2.5" />
<TypeIdentifier>
<FileName>Messaging\Listener\AbstractMessageListenerContainer.cs</FileName>
<HashCode>AABAMYAgABgAABIAAAAIAACAAAAAAAAAAAEAAAhAAAA=</HashCode>
</TypeIdentifier>
<Lollipop Position="0.2" />
</Class>
<Class Name="Spring.Messaging.Listener.AbstractPeekingMessageListenerContainer" Collapsed="true">
<Position X="3.5" Y="3.5" Width="3" />
<TypeIdentifier>
<FileName>Messaging\Listener\AbstractPeekingMessageListenerContainer.cs</FileName>
<HashCode>AAAAAAAiwAghGCBAABEAAhSEAABAQAQIAAAAAIAAAAA=</HashCode>
</TypeIdentifier>
</Class>
<Class Name="Spring.Messaging.Listener.DistributedTxMessageListenerContainer" Collapsed="true">
<Position X="6" Y="6" Width="3.75" />
<TypeIdentifier>
<FileName>Messaging\Listener\DistributedTxMessageListenerContainer.cs</FileName>
<HashCode>AAABAAAAAAgAAQAAAAAIAAAAAAQAAAAAAAAAAAAAAQA=</HashCode>
</TypeIdentifier>
</Class>
<Class Name="Spring.Messaging.Listener.AbstractTransactionalMessageListenerContainer" Collapsed="true">
<Position X="5.5" Y="4.75" Width="2.75" />
<TypeIdentifier>
<FileName>Messaging\Listener\AbstractTransactionalMessageListenerContainer.cs</FileName>
<HashCode>AAAAAIAAAAggEACAAAAIAAAAEAAAAAAAAAAAAAAAAAI=</HashCode>
</TypeIdentifier>
</Class>
<Class Name="Spring.Messaging.Listener.TransactionalMessageListenerContainer" Collapsed="true">
<Position X="2" Y="6" Width="3.75" />
<TypeIdentifier>
<FileName>Messaging\Listener\TransactionalMessageListenerContainer.cs</FileName>
<HashCode>AAhAAAAQAAgAASAIAAAKAAAAAAEAAgAAAAQAAACAAAg=</HashCode>
</TypeIdentifier>
</Class>
<Class Name="Spring.Messaging.Listener.NonTransactionalMessageListenerContainer" Collapsed="true">
<Position X="1.25" Y="4.75" Width="3.75" />
<TypeIdentifier>
<FileName>Messaging\Listener\NonTransactionalMessageListenerContainer.cs</FileName>
<HashCode>AAAAIAAAAAggAABAAAAAIAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
</TypeIdentifier>
</Class>
</ClassDiagram>

View File

@@ -0,0 +1,362 @@
#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.Messaging;
using Common.Logging;
using Spring.Context;
using Spring.Expressions;
using Spring.Messaging.Core;
using Spring.Messaging.Support;
using Spring.Messaging.Support.Converters;
using Spring.Objects.Factory;
using Spring.Objects.Factory.Support;
using Spring.Reflection.Dynamic;
namespace Spring.Messaging.Listener
{
/// <summary>
/// Message listener adapter that delegates the handling of messages to target
/// listener methods via reflection <see cref="DynamicReflectionManager"/>,
/// with flexible message type conversion.
/// Allows listener methods to operate on message content types, completely
/// independent from the MSMQ API.
/// </summary>
/// <remarks>
/// <para>
/// By default, the content of incoming MSMQ messages gets extracted before
/// being passed into the target handler method, to let the target method
/// operate on message content types such as String or business object instead of
/// <see cref="Message"/>. Message type conversion is delegated to a Spring
/// <see cref="IMessageConverter"/> By default, an <see cref="XmlMessageConverter"/>
/// with TargetType set to System.String is used. If you do not want such automatic
/// message conversion taking place, then be sure to set the
/// MessageConverter property to null.
/// </para>
/// <para>
/// If a target handler method returns a non-null object (for example, with a
/// message content type such as <code>String</code>), it will get
/// wrapped in a MSMQ <code>Message</code> and sent to the response destination
/// (either using the MSMQ Message.ResponseQueue property or
/// <see cref="DefaultResponseQueue"/>) specified default response queue
/// destination).
/// </para>
/// <para>
/// Find below some examples of method signatures compliant with this adapter class.
/// This first example uses the default <see cref="XmlMessageConverter"/> that can
/// marhsall/unmarshall string values from the MSMQ Message.
/// </para>
/// <example>
/// public interface IMyHandler
/// {
/// void HandleMessage(string text);
/// }
/// </example>
/// <para>
/// The next example indicates a similar method signature but the name of the
/// handler method name has been changed to "DoWork", using the property
/// <see cref="DefaultHandlerMethod"/>
/// </para>
/// <example>
/// public interface IMyHandler
/// {
/// void DoWork(string text);
/// }
/// </example>
/// <para>If your <see cref="IMessageConverter"/> implementation will return multiple object
/// types, overloading the handler method is perfectly acceptible, the most specific matching
/// method will be used. A method with an object signature would be consider a
/// 'catch-all' method
/// </para>
/// <example>
/// public interface IMyHandler
/// {
/// void DoWork(string text);
/// void DoWork(OrderRequest orderRequest);
/// void DoWork(InvoiceRequest invoiceRequest);
/// void DoWork(object obj);
/// }
/// </example>
/// <para>
/// The last example shows how to send a message to the ResponseQueue for those
/// methods that do not return void.
/// <example>
/// public interface MyHandler
/// {
/// string DoWork(string text);
/// OrderResponse DoWork(OrderRequest orderRequest);
/// InvoiceResponse DoWork(InvoiceRequest invoiceRequest);
/// void DoWork(object obj);
/// }
/// </example>
/// </para>
/// </remarks>
/// <author>Mark Pollack</author>
public class MessageListenerAdapter : IMessageListener, IApplicationContextAware, IInitializingObject
{
#region Logging
private static readonly ILog logger = LogManager.GetLogger(typeof (MessageListenerAdapter));
#endregion
private IApplicationContext applicationContext;
private object handlerObject;
private string defaultHandlerMethod = "HandleMessage";
private IExpression processingExpression;
private string defaultResponseQueueName;
private string messageConverterObjectName;
private MessageQueueTemplate messageQueueTemplate;
private IMessageQueueFactory messageQueueFactory;
public MessageListenerAdapter()
{
handlerObject = this;
processingExpression = Expression.Parse(defaultHandlerMethod + "(#convertedObject)");
messageQueueTemplate = new MessageQueueTemplate();
}
public MessageListenerAdapter(object handlerObject)
{
this.handlerObject = handlerObject;
}
public object HandlerObject
{
get { return handlerObject; }
set { handlerObject = value; }
}
public string DefaultHandlerMethod
{
get { return defaultHandlerMethod; }
set { defaultHandlerMethod = value; }
}
#region IInitializingObject Members
public void AfterPropertiesSet()
{
if (messageQueueFactory == null)
{
DefaultMessageQueueFactory mqf = new DefaultMessageQueueFactory();
mqf.ApplicationContext = applicationContext;
messageQueueFactory = mqf;
}
if (messageConverterObjectName == null)
{
messageConverterObjectName = QueueUtils.RegisterDefaultMessageConverter(applicationContext);
}
}
#endregion
#region IApplicationContextAware Members
public IApplicationContext ApplicationContext
{
get { return applicationContext; }
set { applicationContext = value; }
}
#endregion
public IMessageQueueFactory MessageQueueFactory
{
get { return messageQueueFactory; }
set { messageQueueFactory = value; }
}
public string DefaultResponseQueueName
{
get { return defaultResponseQueueName; }
set { defaultResponseQueueName = value; }
}
public MessageQueue DefaultResponseQueue
{
get
{
if (DefaultResponseQueueName != null)
{
return messageQueueFactory.CreateMessageQueue(DefaultResponseQueueName);
}
else
{
return null;
}
/*
DefaultMessageQueue mq = LogicalThreadContext.GetData(CURRENT_RESPONSEQUEUE_SLOTNAME) as DefaultMessageQueue;
if (mq == null)
{
mq = ApplicationContext.GetObject(DefaultResponseQueueName) as DefaultMessageQueue;
LogicalThreadContext.SetData(CURRENT_RESPONSEQUEUE_SLOTNAME, mq);
}
return mq;
*/
}
}
public string MessageConverterObjectName
{
get { return messageConverterObjectName; }
set { messageConverterObjectName = value; }
}
public IMessageConverter MessageConverter
{
get
{
return messageQueueFactory.CreateMessageConverter(MessageConverterObjectName);
/*
if (messageConverter == null)
{
throw new InvalidOperationException("No MessageConverter registered. Check configuration of MessageQueueTemplate.");
}
IMessageConverter mc = LogicalThreadContext.GetData(CURRENT_CONVERTER_SLOTNAME) as IMessageConverter;
if (mc == null)
{
mc = messageConverter.Clone() as IMessageConverter;
LogicalThreadContext.SetData(CURRENT_CONVERTER_SLOTNAME, mc);
}
return mc;*/
}
}
protected virtual string GetListenerMethodName(Message originalMessage, object extractedMessage)
{
return DefaultHandlerMethod;
}
#region IMessageListener Members
public virtual void OnMessage(Message message)
{
object convertedMessage = ExtractMessage(message);
IDictionary vars = new Hashtable();
vars["convertedObject"] = convertedMessage;
//Need to parse each time since have overloaded methods and
//expression processor caches target of first invocation.
//TODO - use regular reflection.
processingExpression = Expression.Parse(defaultHandlerMethod + "(#convertedObject)");
//Invoke message handler method and get result.
object result = processingExpression.GetValue(handlerObject, vars);
if (result != null)
{
HandleResult(result, message);
}
else
{
logger.Debug("No result object given - no result to handle");
}
}
#endregion
protected virtual object ExtractMessage(Message message)
{
IMessageConverter converter = MessageConverter;
if (converter != null)
{
return converter.FromMessage(message);
}
return message;
}
private void HandleResult(object result, Message request)
{
if (logger.IsDebugEnabled)
{
logger.Debug("Listener method returned result [" + result +
"] - generating response message for it");
}
Message response = BuildMessage(result);
PostProcessResponse(request, response);
MessageQueue destination = GetResponseDestination(request, response);
SendResponse(destination, response);
}
protected virtual void SendResponse(MessageQueue destination, Message response)
{
//Will send with appropriate transaction semantics
messageQueueTemplate.Send(destination, response);
}
protected virtual Message BuildMessage(object result)
{
IMessageConverter converter = MessageConverter;
if (converter != null)
{
if (messageConverterObjectName.Equals("__XmlMessageConverter__"))
{
return converter.ToMessage(result.ToString());
}
else
{
return converter.ToMessage(result);
}
}
else
{
Message msg = result as Message;
if (msg == null)
{
throw new MessagingException("No MessageConverter specified - cannot handle message [" + result +
"]");
}
return msg;
}
}
protected virtual void PostProcessResponse(Message request, Message response)
{
response.CorrelationId = request.CorrelationId;
}
protected virtual MessageQueue GetResponseDestination(Message request, Message response)
{
MessageQueue replyTo = request.ResponseQueue;
if (replyTo == null)
{
replyTo = DefaultResponseQueue;
if (replyTo == null)
{
throw new MessagingException("Cannot determine response destination: " +
"Request message does not contain ResponseQueue destination, and no default response queue set.");
}
}
return replyTo;
}
}
}

View File

@@ -0,0 +1,158 @@
#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.Messaging;
using Common.Logging;
namespace Spring.Messaging.Listener
{
/// <summary>
/// An implementation of a Peeking based MessageListener container that does not surround the
/// receive operation with a transaction.
/// </summary>
/// <remarks>
/// Exceptions that occur during message processing are handled by an instance
/// of <see cref="IExceptionHandler"/>.
/// </remarks>
public class NonTransactionalMessageListenerContainer : AbstractPeekingMessageListenerContainer
{
#region Logging Definition
private static readonly ILog LOG = LogManager.GetLogger(typeof (NonTransactionalMessageListenerContainer));
#endregion
private IExceptionHandler exceptionHandler;
public IExceptionHandler ExceptionHandler
{
get { return exceptionHandler; }
set { exceptionHandler = value; }
}
protected virtual void HandleListenerException(Exception e, Message message)
{
IExceptionHandler exceptionHandler = ExceptionHandler;
if (exceptionHandler != null)
{
exceptionHandler.OnException(e, message);
}
}
protected override bool DoReceiveAndExecute(MessageQueue mq)
{
Message message = null;
try
{
#region Logging
if (LOG.IsTraceEnabled)
{
LOG.Trace("Receiving message with zero timeout for queue = [" + mq.QueueName + "]");
}
#endregion
message = mq.Receive(TimeSpan.Zero);
}
catch (MessageQueueException ex)
{
if (ex.MessageQueueErrorCode == MessageQueueErrorCode.IOTimeout)
{
//expected to occur occasionally
#region Logging
if (LOG.IsTraceEnabled)
{
LOG.Trace(
"MessageQueueErrorCode.IOTimeout: No message available to receive. May have been processed by another thread.");
}
#endregion
return false; // no more peeking unless this is the last listener thread
}
else
{
// A real issue in receiving the message
#region Logging
if (LOG.IsErrorEnabled)
{
LOG.Error("Error receiving message from DefaultMessageQueue [" + mq.QueueName +
"], closing queue and clearing connection cache.");
}
#endregion
lock (messageQueueMonitor)
{
mq.Close();
MessageQueue.ClearConnectionCache();
}
throw; // will log exception.
}
}
if (message == null)
{
#region Logging
if (LOG.IsTraceEnabled)
{
LOG.Trace("Message recieved is null from Queue = [" + mq.QueueName + "]");
}
#endregion
return false; // no more peeking unless this is the last listener thread
}
try
{
#region Logging
if (LOG.IsDebugEnabled)
{
LOG.Debug("Received message [" + message.Id + "] on queue [" + mq.QueueName + "]");
}
#endregion
MessageReceived(message);
DoExecuteListener(message);
}
catch (Exception ex)
{
HandleListenerException(ex, message);
}
finally
{
message.Dispose();
}
return true;
}
}
}

View File

@@ -0,0 +1,117 @@
#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.Messaging;
using Common.Logging;
namespace Spring.Messaging.Listener
{
public class SendToQueueDistributedTransactionExceptionHandler : AbstractSendToQueueExceptionHandler,
IDistributedTransactionExceptionHandler
{
#region Logging Definition
private static readonly ILog LOG =
LogManager.GetLogger(typeof (SendToQueueDistributedTransactionExceptionHandler));
#endregion
#region IDistributedTransactionExceptionHandler Members
public bool IsPoisonMessage(Message message)
{
string messageId = message.Id;
lock (messageMapMonitor)
{
MessageStats messageStats = null;
if (messageMap.Contains(messageId))
{
messageStats = (MessageStats) messageMap[messageId];
if (messageStats.Count > MaxRetry)
{
LOG.Warn("Message with id = [" + message.Id + "] detected as poison message.");
return true;
}
}
return false;
}
}
public void HandlePoisonMessage(Message message)
{
SendMessageToQueue(message);
}
public void OnException(Exception exception, Message message)
{
string messageId = message.Id;
lock (messageMapMonitor)
{
MessageStats messageStats = null;
if (messageMap.Contains(messageId))
{
messageStats = (MessageStats) messageMap[messageId];
}
else
{
messageStats = new MessageStats();
messageMap[messageId] = messageStats;
}
messageStats.Count++;
LOG.Warn("Message Error Count = [" + messageStats.Count + "] for message id = [" + messageId +
"]");
}
}
#endregion
protected virtual void SendMessageToQueue(Message message)
{
MessageQueue mq = MessageQueueFactory.CreateMessageQueue(MessageQueueObjectName);
try
{
#region Logging
if (LOG.IsInfoEnabled)
{
LOG.Info("Sending message with id = [" + message.Id + "] to queue [" + mq.QueueName + "].");
}
#endregion
mq.Send(message, MessageQueueTransactionType.Automatic);
}
catch (Exception e)
{
#region Logging
if (LOG.IsErrorEnabled)
{
LOG.Error("Could not send message with id = [" + message.Id + "] to queue [" + mq.QueueName + "].", e);
LOG.Error("Message will not be processed. Message Body = " + message.Body);
}
#endregion
}
}
}
}

View File

@@ -0,0 +1,182 @@
#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.Messaging;
using Common.Logging;
namespace Spring.Messaging.Listener
{
public class SendToQueueExceptionHandler : AbstractSendToQueueExceptionHandler, IMessageTransactionExceptionHandler
{
#region Logging Definition
private static readonly ILog LOG = LogManager.GetLogger(typeof (SendToQueueExceptionHandler));
#endregion
#region Fields
private string[] messageAlreadyProcessedExceptionNames;
#endregion
#region Properties
/// <summary>
/// Gets or sets the exception anmes that indicate the message has already
/// been processed. If the exception thrown matches one of these names then
/// the returned TransactionAction is Commit to remove it from the queue.
/// </summary>
/// <remarks>The name test is thrownException.GetType().Name.IndexOf(exceptionName) >= 0</remarks>
/// <value>The message already processed exception types.</value>
public string[] MessageAlreadyProcessedExceptionNames
{
set { messageAlreadyProcessedExceptionNames = value; }
get { return messageAlreadyProcessedExceptionNames; }
}
#endregion
#region IMessageTransactionExceptionHandler Members
public TransactionAction OnException(Exception exception, Message message,
MessageQueueTransaction messageQueueTransaction)
{
if (IsMessageAlreadyProcessedException(exception))
{
return TransactionAction.Commit;
}
string messageId = message.Id;
lock (messageMapMonitor)
{
MessageStats messageStats = null;
if (messageMap.Contains(messageId))
{
messageStats = (MessageStats) messageMap[messageId];
}
else
{
messageStats = new MessageStats();
messageMap[messageId] = messageStats;
}
messageStats.Count++;
LOG.Warn("Message Error Count = [" + messageStats.Count + "] for message id = [" + messageId + "]");
if (messageStats.Count > MaxRetry)
{
LOG.Info("Maximum number of redelivery attempts exceeded for message id = [" + messageId + "]");
messageMap.Remove(messageId);
return SendMessageToQueue(message, messageQueueTransaction);
}
else
{
LOG.Warn("Rolling back delivery of message id [" + messageId + "]");
return TransactionAction.Rollback;
}
}
}
#endregion
#region Protected Methods
protected virtual bool IsMessageAlreadyProcessedException(Exception exception)
{
if (MessageAlreadyProcessedExceptionNames != null)
{
foreach (string exceptionName in MessageAlreadyProcessedExceptionNames)
{
if (exception.GetType().Name.IndexOf(exceptionName) >= 0)
{
return true;
}
}
}
return false;
}
protected virtual TransactionAction SendMessageToQueue(Message message,
MessageQueueTransaction messageQueueTransaction)
{
MessageQueue mq = MessageQueueFactory.CreateMessageQueue(MessageQueueObjectName);
try
{
#region Logging
if (LOG.IsInfoEnabled)
{
LOG.Info("Sending message with id = [" + message.Id + "] to queue [" + mq.QueueName + "].");
}
#endregion
ProcessExceptionalMessage(message);
mq.Send(message, messageQueueTransaction);
}
catch (Exception e)
{
#region Logging
if (LOG.IsErrorEnabled)
{
LOG.Error("Could not send message with id = [" + message.Id + "] to queue [" + mq.QueueName + "].",e);
LOG.Error("Message will not be processed. Message Body = " + message.Body);
}
#endregion
}
return TransactionAction.Commit;
}
/// <summary>
/// Template method called before the message that caused the exception is
/// send to another queue. The default behavior is to set the CorrelationId
/// to the current message's Id value for tracking purposes. Subclasses
/// can use other means, perhaps using the AppSpecific field or modifying the
/// body of the message to a known shared format that keeps track of
/// the full 'lifecycle' of the message as it goes from queue-to-queue.
/// </summary>
/// <param name="message">The message.</param>
protected virtual void ProcessExceptionalMessage(Message message)
{
if (message.CorrelationId == null)
{
message.CorrelationId = message.Id;
}
}
#endregion
}
internal class MessageStats
{
private int count;
public int Count
{
get { return count; }
set { count = value; }
}
}
}

View File

@@ -0,0 +1,229 @@
#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.Messaging;
using System.Threading;
using Common.Logging;
namespace Spring.Messaging.Listener
{
public class SimpleMessageListenerContainer : AbstractMessageListenerContainer
{
#region Logging Definition
private static readonly ILog LOG = LogManager.GetLogger(typeof (SimpleMessageListenerContainer));
private Thread dispatcherThread;
protected ManualResetEvent stopEvent = new ManualResetEvent(false);
private IExceptionHandler exceptionHandler;
private MessageQueue messageQueue;
public MessageQueue MessageQueue
{
get { return messageQueue; }
set { messageQueue = value; }
}
public IExceptionHandler ExceptionHandler
{
get { return exceptionHandler; }
set { exceptionHandler = value; }
}
#endregion
protected override void DoInitialize()
{
messageQueue = ApplicationContext.GetObject(MessageQueueObjectName, typeof (MessageQueue)) as MessageQueue;
}
/// <summary>
/// Unsubscribe for messaging events and closethe queue
/// </summary>
protected override void DoShutdown()
{
CloseQueueHandle(MessageQueue);
if (dispatcherThread != null)
{
LOG.Debug("Waiting to join dispatcher thread.");
dispatcherThread.Join();
dispatcherThread = null;
LOG.Debug("Dispatcher thread terminated.");
}
}
/// <summary>
/// Re-initializes this container's consumers, if not initialized already.
/// </summary>
protected override void DoStart()
{
base.DoStart();
stopEvent = new ManualResetEvent(false);
dispatcherThread = new Thread(new ThreadStart(StartListening));
dispatcherThread.Start();
}
/// <summary>
/// Stops the container from listening to message events.
/// </summary>
public override void DoStop()
{
base.DoStop();
CloseQueueHandle(MessageQueue);
stopEvent.Set();
if (dispatcherThread != null)
{
LOG.Debug("Waiting to join dispatcher thread.");
dispatcherThread.Join();
dispatcherThread = null;
LOG.Debug("Dispatcher thread terminated.");
}
}
/// <summary>
/// Starts listening off the queue.
/// </summary>
protected virtual void StartListening()
{
if (Running)
{
try
{
IAsyncResult asynchResult = MessageQueue.BeginReceive();
LOG.Debug("WaitAny");
int firedWaitHandle = WaitHandle.WaitAny(new WaitHandle[] {asynchResult.AsyncWaitHandle, stopEvent});
if (firedWaitHandle == 0)
{
ReceiveCompleted(asynchResult);
}
else
{
//Do the endreceive?
return;
}
}
catch (Exception ex)
{
LOG.Error(
"Exception executing DefaultMessageQueue.BeginReceive. Reinvoking after recovery interval [" +
RecoveryTimeSpan + "]", ex);
Thread.Sleep(RecoveryTimeSpan);
StartListening();
}
}
}
protected virtual void ReceiveCompleted(IAsyncResult asyncResult)
{
Message message;
#region Receive Message
try
{
LOG.Debug("ReceiveCompleted called.");
// Get reference to the queue.
// End the asynchronous receive operation.
message = MessageQueue.EndReceive(asyncResult);
}
catch (MessageQueueException ex)
{
if (ex.MessageQueueErrorCode != MessageQueueErrorCode.IOTimeout)
{
if (LOG.IsTraceEnabled)
{
LOG.Trace("IOTimeout: Message to receive was already processed by another thread.");
}
}
else
{
// A real issue in receiving the message
LOG.Error("Error receiving message from DefaultMessageQueue = [" + MessageQueue.QueueName + "]");
Thread.Sleep(RecoveryTimeSpan);
//InvokeReceiveExceptionHandler(ex);?
}
MessageQueue.Close();
StartListening();
return;
}
#endregion
if (message == null)
{
LOG.Error("Message recieved is null");
StartListening();
return;
}
try
{
bool queued = ThreadPool.QueueUserWorkItem(new WaitCallback(ExecuteListener), message);
#region Could not enqueue
if (!queued)
{
LOG.Warn("Could not queue work item into thread pool. Retrying.");
Thread.Sleep(RecoveryTimeSpan);
}
#endregion
}
catch (Exception e)
{
LOG.Error("Error enqueue message in thread pool DefaultMessageQueue = [" + MessageQueue.QueueName + "]", e);
Thread.Sleep(RecoveryTimeSpan);
}
finally
{
StartListening();
}
}
protected virtual void ExecuteListener(object state)
{
Message message = state as Message;
try
{
DoExecuteListener(message);
}
catch (Exception e)
{
HandleListenerException(e, message);
}
}
private void HandleListenerException(Exception e, Message message)
{
IExceptionHandler exceptionHandler = ExceptionHandler;
if (exceptionHandler != null)
{
exceptionHandler.OnException(e, message);
}
}
}
}

View File

@@ -0,0 +1,38 @@
#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
namespace Spring.Messaging.Listener
{
/// <summary>
/// Action to perform on the MessageQueueTransaction when handling message listener exceptions.
/// </summary>
public enum TransactionAction
{
/// <summary>
/// Rollback the MessageQueueTransaction, returning the recieved message back onto the queue.
/// </summary>
Rollback,
/// <summary>
/// Commit the MessageQueueTransaction, removing the message from the queue.
/// </summary>
Commit
} ;
}

View File

@@ -0,0 +1,649 @@
#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.Messaging;
using Common.Logging;
using Spring.Data.Core;
using Spring.Messaging.Core;
using Spring.Messaging.Support;
using Spring.Transaction;
using Spring.Transaction.Support;
namespace Spring.Messaging.Listener
{
/// <summary>
/// A MessageListenerContainer that uses local (non-DTC) based transactions. Exceptions are
/// handled by instances of <see cref="IMessageTransactionExceptionHandler"/>.
/// </summary>
/// <remarks>
/// <para>
/// This container distinguishes between two types of <see cref="IPlatformTransactionManager"/>
/// implementations.
/// </para>
/// <para>If you specify a <see cref="MessageQueueTransactionManager"/> then
/// a MSMQ <see cref="MessageQueueTransaction"/> will be started
/// before receiving the message and used as part of the container's recieve operation. The
/// <see cref="MessageQueueTransactionManager"/> binds the <see cref="MessageQueueTransaction"/>
/// to thread local storage and as such will implicitly be used by
/// <see cref="MessageQueueTemplate"/> send and receive operations to a transactional queue.
/// </para>
/// <para>
/// Service layer operations that are called inside the message listener will typically
/// be transactional based on using standard Spring declarative transaction management
/// functionality. In case of exceptions in the service layer, the database operation
/// will have been rolled back and the <see cref="IMessageTransactionExceptionHandler"/>
/// that is later invoked should decide to either commit the surrounding local
/// MSMQ based transaction (removing the message from the queue) or to rollback
/// (placing the message back on the queue for redelivery).
/// </para>
/// <para>
/// The use of a transactional service layer in combination with
/// a container managed <see cref="MessageQueueTransaction"/> is a powerful combination
/// that can be used to achieve "exactly one" transaction message processing with
/// database operations that are commonly associated with using transactional messaging and
/// distributed transactions (i.e. both the messaging and database operation commit or rollback
/// together).
/// </para>
/// <para>
/// The additional programming logic needed to achieve this is to keep track of the Message.Id
/// that has been processed successfully within the transactional service layer.
/// This is needed as there may be a system failure (e.g. power goes off)
/// between the 'inner' database commit and the 'outer' messaging commit, resulting
/// in message redelivery. The transactional service layer needs logic to detect if incoming
/// message was processed successfully. It can do this by checking the database for an
/// indication of successfull processing, perhaps by recording the Message.Id itself in a
/// status table. If the transactional service layer determines that the message has
/// already been processed, it can throw a specific exception for thise case. The
/// container's exception handler will recognize this exception type and vote to commit
/// (remove from the queue) the 'outer' messaging transaction.
/// Spring provides an exception handler with this functionality,
/// see <see cref="SendToQueueExceptionHandler"/> for more information.
/// </para>
/// <para>If you specify an implementation of <see cref="IResourceTransactionManager"/>
/// (e.g. <see cref="AdoPlatformTransactionManager"/> or HibernateTransactionManager) then
/// an local database transaction will be started before receiving the message. By default,
/// the container will also start a local <see cref="MessageQueueTransaction"/>
/// after the local database transaction has started, but before the receiving the message.
/// The <see cref="MessageQueueTransaction"/> will be used to receive the message.
/// If you do not want his behavior set <see cref="UseContainerManagedMessageQueueTransaction"/>
/// to false. Also by default, the <see cref="MessageQueueTransaction"/>
/// will be bound to thread local storage such that any <see cref="MessageQueueTemplate"/>
/// send or recieve operations will participate transparently in the same
/// <see cref="MessageQueueTransaction"/>. If you do not want this behavior
/// set the property <see cref="ExposeContainerManagedMessageQueueTransaction"/> to false.
/// </para>
/// <para>In case of exceptions during <see cref="IMessageListener"/> processing
/// when using an implementation of
/// <see cref="IResourceTransactionManager"/> (e.g. and starting a container managed
/// <see cref="MessageQueueTransaction"/>) the container's
/// <see cref="IMessageTransactionExceptionHandler"/> will determine if the
/// <see cref="MessageQueueTransaction"/> should commit (removing it from the queue)
/// or rollback (placing it back on the queue for redelivery). The listener
/// exception will always
/// trigger a rollback in the 'outer' (e.g. <see cref="AdoPlatformTransactionManager"/>
/// or HibernateTransactionManager) based transaction.
/// </para>
/// <para>
/// PoisonMessage handing, that is endless redelivery of a message due to exceptions
/// during processing, can be detected using implementatons of the
/// <see cref="IMessageTransactionExceptionHandler"/> interface. A specific implementation
/// is provided that will move the poison message to another queue after a maximum number
/// of redelivery attempts. See <see cref="SendToQueueExceptionHandler"/> for more information.
/// </para>
/// </remarks>
public class TransactionalMessageListenerContainer : AbstractTransactionalMessageListenerContainer
{
#region Logging Definition
private static readonly ILog LOG = LogManager.GetLogger(typeof (TransactionalMessageListenerContainer));
#endregion
#region Fields
private bool useContainerManagedMessageQueueTransaction = false;
private bool useMessageQueueTransactionManagerCalled = false;
private bool exposeContainerManagedMessageQueueTransaction = true;
private IMessageTransactionExceptionHandler messageTransactionExceptionHandler;
#endregion
#region Properties
/// <summary>
/// Gets or sets a value indicating whether the MessageListenerContainer should be
/// responsible for creating a MessageQueueTransaction
/// when receiving a message.
/// </summary>
/// <remarks>
/// <para>
/// Creating MessageQueueTransactions is usually the responsibility of the
/// IPlatformTransactionManager, e.g. TxScopePlatformTransactionManager (when using DTC)
/// or MessageQueueTransactionManager (when using local messaging transactions).
/// </para>
/// <para>
/// For all other IPlatformTransactionManager implementations, including when none is
/// specified, the MessageListenerContainer will itself create a MessageQueueTransaction
/// (assuming the container is consuming from a transactional queue).
/// </para>
/// <para>
/// Set the ExposeContainerManagedMessageQueueTransaction property to true if you want
/// the MessageQueueTransaction to be exposed to Spring's MessageQueueTemplate class
/// </para>
/// </remarks>
/// <value>
/// <c>true</c> to use a container managed MessageQueueTransaction; otherwise, <c>false</c>.
/// </value>
public bool UseContainerManagedMessageQueueTransaction
{
get { return useContainerManagedMessageQueueTransaction; }
set
{
useContainerManagedMessageQueueTransaction = value;
useMessageQueueTransactionManagerCalled = true;
}
}
/// <summary>
/// Gets or sets a value indicating whether expose the
/// container managed <see cref="MessageQueueTransaction"/> to thread local storage
/// where it will be automatically used by <see cref="MessageQueueTemplate"/> send
/// and receive operations.
/// </summary>
/// <remarks>
/// Using an <see cref="MessageQueueTransactionManager"/> will always exposes a
/// <see cref="MessageQueueTransaction"/> to thread local storage. This property
/// only has effect when using a non-DTC based
/// </remarks>
/// <value>
/// <c>true</c> if [expose container managed message queue transaction]; otherwise, <c>false</c>.
/// </value>
public bool ExposeContainerManagedMessageQueueTransaction
{
get { return exposeContainerManagedMessageQueueTransaction; }
set { exposeContainerManagedMessageQueueTransaction = value; }
}
/// <summary>
/// Gets or sets the message transaction exception handler.
/// </summary>
/// <value>The message transaction exception handler.</value>
public IMessageTransactionExceptionHandler MessageTransactionExceptionHandler
{
get { return messageTransactionExceptionHandler; }
set { messageTransactionExceptionHandler = value; }
}
#endregion
#region Public Methods
/// <summary>
/// Determine if the container should create its own
/// MessageQueueTransaction when a IResourceTransactionManager is specified.
/// Set the transaction name to the name of the spring object.
/// Call base class Initialize() funtionality
/// </summary>
public override void Initialize()
{
//using non-DTC based transaction manager?
bool isRtm = PlatformTransactionManager is IResourceTransactionManager;
//using MessageQueueTransactionManager?
bool isQtm = PlatformTransactionManager is MessageQueueTransactionManager;
if (!isRtm && !isQtm)
{
throw new ArgumentException("Can not use the provied IPlatformTransactionManager of type "
+ PlatformTransactionManager.GetType()
+ ". It must implement IResourceTransactionManager or be a MessageQueueTransactionManager.");
}
//Set useMessageQueueTransactionManager = true when using
// 1. non-DTC based transaction manager
// 2. not the MessageQueueTransactionManager.
if (!useMessageQueueTransactionManagerCalled && isRtm && !isQtm)
{
useContainerManagedMessageQueueTransaction = true;
}
// Use object name as default transaction name.
if (TransactionDefinition.Name == null)
{
TransactionDefinition.Name = ObjectName;
}
// Proceed with superclass initialization.
base.Initialize();
}
#endregion
#region Protected Methods
protected override bool DoReceiveAndExecuteUsingPlatformTransactionManager(MessageQueue mq,
ITransactionStatus status)
{
if (PlatformTransactionManager is MessageQueueTransactionManager)
{
return DoRecieveAndExecuteUsingMessageQueueTransactionManager(mq, status);
}
else if (PlatformTransactionManager is IResourceTransactionManager)
{
if (UseContainerManagedMessageQueueTransaction)
{
return DoRecieveAndExecuteUsingResourceTransactionManagerWithTxQueue(mq, status);
}
else
{
//recieve non-transactionally from transactional queue but
//use ResourceBasedTransactionManagement.
DoRecieveAndExecuteUsingResourceTransactionManager();
}
}
return false;
}
private void DoRecieveAndExecuteUsingResourceTransactionManager()
{
//This is a bit of an odd case since really one is better off using
//NonTransactionalMessageListenerContainer and having the database
//transaction done in the service tier.
throw new NotImplementedException("Try using NonTransactionalMessageListenerContainer instead.");
}
protected virtual bool DoRecieveAndExecuteUsingMessageQueueTransactionManager(MessageQueue mq,
ITransactionStatus status)
{
#region Logging
if (LOG.IsDebugEnabled)
{
LOG.Debug("Executing DoRecieveAndExecuteUsingMessageQueueTransactionManager");
}
#endregion Logging
Message message;
#region Receive message
try
{
//TODO check that GetMessageQueueTransction doesn't return null.
message = mq.Receive(TimeSpan.Zero, QueueUtils.GetMessageQueueTransaction(null));
}
catch (MessageQueueException ex)
{
if (ex.MessageQueueErrorCode != MessageQueueErrorCode.IOTimeout)
{
//expected to occur occasionally
if (LOG.IsTraceEnabled)
{
LOG.Trace("IOTimeout: Message to receive was already processed by another thread.");
}
status.RollbackOnly = true;
return false; // no more peeking unless this is the last listener thread
}
else
{
// A real issue in receiving the message
#region Logging
if (LOG.IsErrorEnabled)
{
LOG.Error("Error receiving message from DefaultMessageQueue [" + mq.QueueName +
"], closing queue and clearing connection cache.");
}
#endregion
lock (messageQueueMonitor)
{
mq.Close();
MessageQueue.ClearConnectionCache();
}
throw; // will cause rollback in MessageQueueTransactionManager and log exception
}
}
#endregion
if (message == null)
{
#region Logging
if (LOG.IsTraceEnabled)
{
LOG.Trace("Message recieved is null from Queue = [" + mq.QueueName + "]");
}
#endregion
status.RollbackOnly = true;
return false; // no more peeking unless this is the last listener thread
}
try
{
#region Logging
if (LOG.IsDebugEnabled)
{
LOG.Debug("Received message [" + message.Id + "] on queue [" + mq.QueueName + "]");
}
#endregion
MessageReceived(message);
DoExecuteListener(message);
#region Logging
if (LOG.IsTraceEnabled)
{
LOG.Trace("MessageListener executed");
}
#endregion
}
catch (Exception ex)
{
//Exception may indicate rollback of database transaction in service layer.
//Let the handler determine if the message should be removed from the queue.
TransactionAction action =
HandleTransactionalListenerException(ex, message, QueueUtils.GetMessageQueueTransaction(null));
if (action == TransactionAction.Rollback)
{
#region Logging
if (LOG.IsDebugEnabled)
{
LOG.Debug(
"Exception handler's TransactionAction has rolled back MessageQueueTransaction for queue [" +
mq.QueueName + "]");
}
#endregion
status.RollbackOnly = true;
return false; // no more peeking unless this is the last listener thread
}
else
{
LOG.Info("Committing MessageQueueTransaction due to explicit commit request by exception handler.");
}
}
finally
{
message.Dispose();
}
return true;
}
protected virtual bool DoRecieveAndExecuteUsingResourceTransactionManagerWithTxQueue(MessageQueue mq,
ITransactionStatus status)
{
#region Logging
if (LOG.IsDebugEnabled)
{
LOG.Debug("Executing DoRecieveAndExecuteUsingResourceTransactionManagerWithTxQueue");
}
#endregion Logging
using (MessageQueueTransaction messageQueueTransaction = new MessageQueueTransaction())
{
messageQueueTransaction.Begin();
#region Logging
if (LOG.IsTraceEnabled)
{
LOG.Trace("Started MessageQueueTransaction for queue = [" + mq.QueueName + "]");
}
#endregion
Message message;
#region ReceiveMessage
try
{
#region Logging
if (LOG.IsTraceEnabled)
{
LOG.Trace("Receiving message with zero timeout for queue = [" + mq.QueueName + "]");
}
#endregion
message = mq.Receive(TimeSpan.Zero, messageQueueTransaction);
}
catch (MessageQueueException ex)
{
if (ex.MessageQueueErrorCode == MessageQueueErrorCode.IOTimeout)
{
//expected to occur occasionally
#region Logging
if (LOG.IsTraceEnabled)
{
LOG.Trace(
"MessageQueueErrorCode.IOTimeout: No message available to receive. May have been processed by another thread.");
}
#endregion
status.RollbackOnly = true;
return false; // no more peeking unless this is the last listener thread
}
else
{
// A real issue in receiving the message
#region Logging
if (LOG.IsErrorEnabled)
{
LOG.Error("Error receiving message from DefaultMessageQueue [" + mq.QueueName +
"], closing queue and clearing connection cache.");
}
#endregion
lock (messageQueueMonitor)
{
mq.Close();
MessageQueue.ClearConnectionCache();
}
throw; // will cause rollback in surrounding platform transaction manager and log exception
}
}
#endregion
if (message == null)
{
#region Logging
if (LOG.IsTraceEnabled)
{
LOG.Trace("Message recieved is null from Queue = [" + mq.QueueName + "]");
}
#endregion
status.RollbackOnly = true;
return false; // no more peeking unless this is the last listener thread
}
try
{
#region Logging
if (LOG.IsDebugEnabled)
{
LOG.Debug("Received message [" + message.Id + "] on queue [" + mq.QueueName + "]");
}
#endregion
MessageReceived(message);
if (ExposeContainerManagedMessageQueueTransaction)
{
TransactionSynchronizationManager.BindResource(
MessageQueueTransactionManager.CURRENT_TRANSACTION_SLOTNAME,
new LocallyExposedMessageQueueResourceHolder(messageQueueTransaction));
}
DoExecuteListener(message);
#region Logging
if (LOG.IsTraceEnabled)
{
LOG.Trace("MessageListener executed");
}
#endregion
messageQueueTransaction.Commit();
#region Logging
if (LOG.IsTraceEnabled)
{
LOG.Trace("Committed MessageQueueTransaction for queue [" + mq.QueueName + "]");
}
#endregion
}
catch (Exception ex)
{
TransactionAction action =
HandleTransactionalListenerException(ex, message, messageQueueTransaction);
if (action == TransactionAction.Rollback)
{
messageQueueTransaction.Abort();
#region Logging
if (LOG.IsDebugEnabled)
{
LOG.Debug(
"Exception handler's TransactionAction has rolled back MessageQueueTransaction for queue [" +
mq.QueueName + "]");
}
#endregion
}
else
{
// Will remove from the message queue
messageQueueTransaction.Commit();
#region Logging
if (LOG.IsDebugEnabled)
{
LOG.Debug(
"Exception handler's TransactionAction has committed MessageQueueTransaction for queue [" +
mq.QueueName + "]");
}
#endregion
}
//Outer db-tx will rollback
throw;
}
finally
{
if (ExposeContainerManagedMessageQueueTransaction)
{
TransactionSynchronizationManager.UnbindResource(
MessageQueueTransactionManager.CURRENT_TRANSACTION_SLOTNAME);
}
message.Dispose();
}
return true;
}
}
protected virtual TransactionAction HandleTransactionalListenerException(Exception e, Message message,
MessageQueueTransaction
messageQueueTransaction)
{
try
{
TransactionAction transactionAction =
InvokeTransactionalExceptionListener(e, message, messageQueueTransaction);
if (Active)
{
// Regular case: failed while active.
// Log at error level.
LOG.Error("Execution of message listener failed", e);
}
else
{
// Rare case: listener thread failed after container shutdown.
// Log at debug level, to avoid spamming the shutdown log.
LOG.Debug("Listener exception after container shutdown", e);
}
return transactionAction;
}
catch (Exception ex)
{
LOG.Error("Exception invoking MessageTransactionExceptionHandler. Rolling back transaction.", ex);
return TransactionAction.Rollback;
}
}
protected virtual TransactionAction InvokeTransactionalExceptionListener(Exception e, Message message,
MessageQueueTransaction
messageQueueTransaction)
{
IMessageTransactionExceptionHandler exMessageTransaction = MessageTransactionExceptionHandler;
if (exMessageTransaction != null)
{
return exMessageTransaction.OnException(e, message, messageQueueTransaction);
}
else
{
LOG.Warn("No MessageTransactionExceptionHandler defined. Defaulting to TransactionAction.Rollback.");
return TransactionAction.Rollback;
}
}
#endregion
}
}

View File

@@ -0,0 +1,59 @@
using System;
using System.Runtime.Serialization;
namespace Spring.Messaging
{
public class MessagingException : ApplicationException
{
#region Constructor (s) / Destructor
/// <summary>Creates a new instance of the MessagingException class.</summary>
public MessagingException()
{
}
/// <summary>
/// Creates a new instance of the MessagingException class. with the specified message.
/// </summary>
/// <param name="message">
/// A message about the exception.
/// </param>
public MessagingException(string message) : base(message)
{
}
/// <summary>
/// Creates a new instance of the MessagingException class with the specified message
/// and root cause.
/// </summary>
/// <param name="message">
/// A message about the exception.
/// </param>
/// <param name="rootCause">
/// The root exception that is being wrapped.
/// </param>
public MessagingException(string message, Exception rootCause)
: base(message, rootCause)
{
}
/// <summary>
/// Creates a new instance of the MessagingException class.
/// </summary>
/// <param name="info">
/// The <see cref="System.Runtime.Serialization.SerializationInfo"/>
/// that holds the serialized object data about the exception being thrown.
/// </param>
/// <param name="context">
/// The <see cref="System.Runtime.Serialization.StreamingContext"/>
/// that contains contextual information about the source or destination.
/// </param>
protected MessagingException(
SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
#endregion
}
}

View File

@@ -0,0 +1,70 @@
#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.Messaging;
namespace Spring.Messaging.Support.Converters
{
public class ActiveXMessageConverter : IMessageConverter
{
private ActiveXMessageFormatter messageFormatter;
public ActiveXMessageConverter()
{
messageFormatter = new ActiveXMessageFormatter();
}
public ActiveXMessageConverter(ActiveXMessageFormatter messageFormatter)
{
this.messageFormatter = messageFormatter;
}
#region IMessageConverter Members
public Message ToMessage(object obj)
{
Message m = new Message();
m.Body = obj;
m.Formatter = messageFormatter;
return m;
}
public object FromMessage(Message message)
{
message.Formatter = messageFormatter;
return message.Body;
}
#endregion
#region ICloneable Members
public object Clone()
{
ActiveXMessageConverter mc = new ActiveXMessageConverter(messageFormatter.Clone() as ActiveXMessageFormatter);
return mc;
}
#endregion
}
}

View File

@@ -0,0 +1,85 @@
#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.Messaging;
using System.Runtime.Serialization.Formatters;
namespace Spring.Messaging.Support.Converters
{
public class BinaryMessageConverter : IMessageConverter
{
private BinaryMessageFormatter binaryMessageFormatter;
private FormatterTypeStyle typeFormat;
private FormatterAssemblyStyle topObjectFormat;
public BinaryMessageConverter()
{
binaryMessageFormatter = new BinaryMessageFormatter();
}
public BinaryMessageConverter(BinaryMessageFormatter binaryMessageFormatter)
{
this.binaryMessageFormatter = binaryMessageFormatter;
}
public FormatterTypeStyle TypeFormat
{
get { return typeFormat; }
set { typeFormat = value; }
}
public FormatterAssemblyStyle TopObjectFormat
{
get { return topObjectFormat; }
set { topObjectFormat = value; }
}
#region IMessageConverter Members
public Message ToMessage(object obj)
{
Message m = new Message();
m.Body = obj;
m.Formatter = binaryMessageFormatter;
return m;
}
public object FromMessage(Message message)
{
message.Formatter = binaryMessageFormatter;
return message.Body;
}
#endregion
#region ICloneable Members
public object Clone()
{
BinaryMessageConverter mc = new BinaryMessageConverter(binaryMessageFormatter.Clone() as BinaryMessageFormatter);
return mc;
}
#endregion
}
}

View File

@@ -0,0 +1,42 @@
#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.Messaging;
namespace Spring.Messaging.Support.Converters
{
public interface IMessageConverter : ICloneable
{
/// <summary>
/// Convert the given object to a Message.
/// </summary>
/// <param name="obj">The object to send.</param>
/// <returns>Message to send</returns>
Message ToMessage(object obj);
/// <summary>
/// Convert the given message to a object.
/// </summary>
/// <param name="message">The message.</param>
/// <returns>the object</returns>
object FromMessage(Message message);
}
}

View File

@@ -0,0 +1,46 @@
using System;
using System.Messaging;
using System.Xml;
namespace Spring.Messaging.Support.Converters
{
public class XmlDocumentConverter : IMessageConverter
{
#region IMessageConverter Members
public Message ToMessage(object obj)
{
XmlDocument doc = obj as XmlDocument;
if (doc != null)
{
Message m = new Message();
doc.Save(m.BodyStream);
return m;
}
else
{
throw new MessagingException("Expected object to be of type System.Xml.XmlDocument");
}
}
public object FromMessage(Message message)
{
XmlDocument doc = new XmlDocument();
doc.Load(message.BodyStream);
return doc;
}
#endregion
#region ICloneable Members
public object Clone()
{
throw new NotImplementedException();
}
#endregion
}
}

View File

@@ -0,0 +1,90 @@
#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.Messaging;
using Spring.Util;
namespace Spring.Messaging.Support.Converters
{
public class XmlMessageConverter : IMessageConverter
{
private XmlMessageFormatter messageFormatter;
public XmlMessageConverter()
{
messageFormatter = new XmlMessageFormatter();
}
public XmlMessageConverter(XmlMessageFormatter messageFormatter)
{
this.messageFormatter = messageFormatter;
}
public Type[] TargetTypes
{
set
{
AssertUtils.ArgumentNotNull(value, "TargetTypes");
messageFormatter.TargetTypes = value;
}
get { return messageFormatter.TargetTypes; }
}
public string[] TargetTypeNames
{
set
{
AssertUtils.ArgumentNotNull(value, "TargetTypeNames");
messageFormatter.TargetTypeNames = value;
}
get { return messageFormatter.TargetTypeNames; }
}
#region IMessageConverter Members
public Message ToMessage(object obj)
{
Message m = new Message();
m.Body = obj;
m.Formatter = messageFormatter;
return m;
}
public object FromMessage(Message message)
{
message.Formatter = messageFormatter;
return message.Body;
}
#endregion
#region ICloneable Members
public object Clone()
{
XmlMessageConverter mc = new XmlMessageConverter(messageFormatter.Clone() as XmlMessageFormatter);
return mc;
}
#endregion
}
}

View File

@@ -0,0 +1,188 @@
#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.Messaging;
using Spring.Objects.Factory.Config;
namespace Spring.Messaging.Support
{
/// <summary>
/// Factory for creating MessageQueues
/// </summary>
/// <author>Mark Pollack</author>
public class MessageQueueFactoryObject : IConfigurableFactoryObject
{
// fields used in constructor
private string path = string.Empty;
private bool modeDenySharedReceive;
private bool enableCache;
private QueueAccessMode accessMode = QueueAccessMode.SendAndReceive;
// To avoid stale handles when MSMQ restarts and when stopping async recieve/peek operations
private bool enableConnectionCache = false;
private IObjectDefinition productTemplate;
private bool messageReadPropertyFilterSetAll = true;
private bool messageReadPropertyFilterSetDefaults = false;
//myQueue.MessageReadPropertyFilter.SetAll();
/// <summary>
/// Gets or sets the path used to creat DefaultMessageQueue instance.
/// </summary>
/// <value>The location of the queue referenced by the DefaultMessageQueue.</value>
public string Path
{
get { return path; }
set { path = value; }
}
/// <summary>
/// Gets or sets a value indicating whether to create the DefaultMessageQueue instance with
/// exclusive read access to the first application that accesses the queue
/// </summary>
/// <value>
/// <c>true</c> to grant exclusive read access to the first application that accesses the queue; otherwise, <c>false</c>.
/// </value>
public bool DenySharedReceive
{
get { return modeDenySharedReceive; }
set { modeDenySharedReceive = value; }
}
/// <summary>
/// Gets or sets the queue access mode.
/// </summary>
/// <value>The queue access mode.</value>
/// <see cref="AccessMode"/>
public QueueAccessMode AccessMode
{
get { return accessMode; }
set { accessMode = value; }
}
/// <summary>
/// Gets or sets a value indicating whether [enable cache].
/// </summary>
/// <value><c>true</c> to create and use a connection cache; otherwise <c>false</c>.</value>
public bool EnableCache
{
get { return enableCache; }
set { enableCache = value; }
}
/// <summary>
/// Sets a value indicating whether to enable connection cache. The default is false, which
/// is different than the default value when creating a DefaultMessageQueue object.
/// </summary>
/// <value>
/// <c>true</c> if enable connection cache; otherwise, <c>false</c>.
/// </value>
public bool EnableConnectionCache
{
set { enableConnectionCache = value; }
}
/// <summary>
/// Sets a value indicating whether to retrieve all message properties when receiving a message.
/// </summary>
/// <value>
/// <c>true</c> if should etrieve all message properties when receiving a message; otherwise, <c>false</c>.
/// </value>
public bool MessageReadPropertyFilterSetAll
{
set { messageReadPropertyFilterSetAll = value; }
}
/// <summary>
/// Sets a value indicating whether to set the filter values of common Message Queuing properties
/// to true and the integer-valued properties to their default values..
/// </summary>
/// <value>
/// <c>true</c> if should set the filter values of common Message Queuing properties; otherwise, <c>false</c>.
/// </value>
public bool MessageReadPropertyFilterSetDefaults
{
set { messageReadPropertyFilterSetDefaults = value; }
}
#region IFactoryObject Members
public object GetObject()
{
MessageQueue.EnableConnectionCache = enableConnectionCache;
MessageQueue mq = new MessageQueue(Path, DenySharedReceive, EnableCache, AccessMode);
if (messageReadPropertyFilterSetDefaults)
{
mq.MessageReadPropertyFilter.SetDefaults();
}
if (messageReadPropertyFilterSetAll)
{
mq.MessageReadPropertyFilter.SetAll();
}
return mq;
}
/// <summary>
/// Return the <see cref="System.Type"/> of object that this
/// <see cref="Spring.Objects.Factory.IFactoryObject"/> creates, or
/// <see langword="null"/> if not known in advance.
/// </summary>
/// <value>The type DefaultMessageQueue</value>
public Type ObjectType
{
get { return typeof (MessageQueue); }
}
/// <summary>
/// Is the object managed by this factory a singleton or a prototype?
/// </summary>
/// <value>return false, a new object will be created for each request of the object</value>
public bool IsSingleton
{
get { return false; }
}
#region IConfigurableFactoryObject Members
/// <summary>
/// Gets the template object definition that should be used
/// to configure the instance of the object managed by this factory.
/// </summary>
/// <value>The object definition to configure the factory's product</value>
public IObjectDefinition ProductTemplate
{
get { return productTemplate; }
set { productTemplate = value; }
}
#endregion
#endregion
}
}

View File

@@ -0,0 +1,151 @@
#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.Messaging;
using Spring.Context;
using Spring.Messaging.Core;
using Spring.Messaging.Support.Converters;
using Spring.Objects.Factory.Support;
using Spring.Transaction.Support;
namespace Spring.Messaging.Support
{
public class QueueUtils
{
/// <summary>
/// Registers the default message converter with the application context.
/// </summary>
/// <param name="applicationContext">The application context.</param>
/// <returns>The name of the message converter to use for lookups with
/// <see cref="DefaultMessageQueueFactory"/>.
/// </returns>
public static string RegisterDefaultMessageConverter(IApplicationContext applicationContext)
{
//Create a default message converter to use.
RootObjectDefinition rod = new RootObjectDefinition(typeof(XmlMessageConverter));
rod.PropertyValues.Add("TargetTypes", new Type[] { typeof(String) });
rod.IsSingleton = false;
IConfigurableApplicationContext ctx = (IConfigurableApplicationContext)applicationContext;
DefaultListableObjectFactory of = (DefaultListableObjectFactory)ctx.ObjectFactory;
string messageConverterObjectName = "__XmlMessageConverter__";
if (!applicationContext.ContainsObjectDefinition(messageConverterObjectName))
{
of.RegisterObjectDefinition(messageConverterObjectName, rod);
}
return messageConverterObjectName;
}
public static MessageQueueTransaction GetMessageQueueTransaction(IResourceFactory resourceFactory)
{
MessageQueueResourceHolder resourceHolder =
(MessageQueueResourceHolder)
TransactionSynchronizationManager.GetResource(
MessageQueueTransactionManager.CURRENT_TRANSACTION_SLOTNAME);
if (resourceHolder != null)
{
return resourceHolder.MessageQueueTransaction;
}
if (!TransactionSynchronizationManager.SynchronizationActive)
{
return null;
}
throw new NotImplementedException();
/*
MessageQueueResourceHolder resourceHolderToUse = resourceHolder;
if (resourceHolderToUse == null)
{
resourceHolderToUse = new MessageQueueResourceHolder(new MessageQueueTransaction());
}
if (resourceHolderToUse != resourceHolder)
{
TransactionSynchronizationManager.RegisterSynchronization(
new MessageQueueResourceSynchronization(resourceHolderToUse, resourceFactory.SynchedLocalTransactionAllowed));
resourceHolderToUse.SynchronizedWithTransaction = true;
TransactionSynchronizationManager.BindResource(MessageQueueTransactionManager.CURRENT_TRANSACTION_SLOTNAME, resourceHolderToUse);
}
return resourceHolderToUse.MessageQueueTransaction;*/
}
}
internal class MessageQueueResourceSynchronization : ITransactionSynchronization
{
private object resourceKey;
private MessageQueueResourceHolder resourceHolder;
private bool holderActive = true;
public MessageQueueResourceSynchronization(MessageQueueResourceHolder resourceHolder, object resourceKey)
{
this.resourceHolder = resourceHolder;
this.resourceKey = resourceKey;
}
#region ITransactionSynchronization Members
public void Suspend()
{
if (holderActive)
{
TransactionSynchronizationManager.UnbindResource(resourceKey);
}
}
public void Resume()
{
if (holderActive)
{
TransactionSynchronizationManager.BindResource(resourceKey, resourceHolder);
}
}
public void BeforeCommit(bool readOnly)
{
throw new NotImplementedException();
}
public void AfterCommit()
{
throw new NotImplementedException();
}
public void BeforeCompletion()
{
TransactionSynchronizationManager.UnbindResource(resourceKey);
holderActive = false;
//this.resourceHolder.closeAll();
throw new NotImplementedException();
}
public void AfterCompletion(TransactionSynchronizationStatus status)
{
throw new NotImplementedException();
}
#endregion
}
public interface IResourceFactory
{
bool SynchedLocalTransactionAllowed { get; }
}
}

View File

@@ -0,0 +1,109 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.50727</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{0E23AE41-D8D8-41C2-84A2-D35564049F0D}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Spring</RootNamespace>
<AssemblyName>Spring.Messaging</AssemblyName>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>..\..\..\build\VS.Net.2005\Spring.Messaging\Debug\</OutputPath>
<DefineConstants>TRACE;DEBUG;NET_2_0</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<DocumentationFile>Spring.Messaging.xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>..\..\..\build\VS.Net.2005\Spring.Messaging\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="antlr.runtime, Version=2.7.6.2, Culture=neutral, PublicKeyToken=65e474d141e25e07, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\lib\Net\2.0\antlr.runtime.dll</HintPath>
</Reference>
<Reference Include="Common.Logging, Version=1.2.0.0, Culture=neutral, PublicKeyToken=af08829b84f0328e">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\lib\Net\2.0\Common.Logging.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Messaging" />
<Reference Include="System.Transactions" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\CommonAssemblyInfo.cs">
<Link>CommonAssemblyInfo.cs</Link>
</Compile>
<Compile Include="AssemblyInfo.cs" />
<Compile Include="Messaging\Core\DefaultMessageQueueFactory.cs" />
<Compile Include="Messaging\Core\IMessageQueueFactory.cs" />
<Compile Include="Messaging\Core\MessagePostProcessorDelegate.cs" />
<Compile Include="Messaging\Listener\AbstractSendToQueueExceptionHandler.cs" />
<Compile Include="Messaging\Listener\AbstractTransactionalMessageListenerContainer.cs" />
<Compile Include="Messaging\Listener\DistributedTxMessageListenerContainer.cs" />
<Compile Include="Messaging\Listener\MessageListenerAdapter.cs" />
<Compile Include="Messaging\Listener\SendToQueueDistributedTransactionExceptionHandler.cs" />
<Compile Include="Messaging\Listener\TransactionalMessageListenerContainer.cs" />
<Compile Include="Messaging\Listener\NonTransactionalMessageListenerContainer.cs" />
<Compile Include="Messaging\Core\IMessageQueueOperations.cs" />
<Compile Include="Messaging\Core\LocallyExposedMessageQueueResourceHolder.cs" />
<Compile Include="Messaging\Core\MessageQueueResourceHolder.cs" />
<Compile Include="Messaging\Core\MessageQueueTransactionManager.cs" />
<Compile Include="Messaging\Core\MessageQueueTemplate.cs" />
<Compile Include="Messaging\Listener\AbstractListenerContainer.cs" />
<Compile Include="Messaging\Listener\AbstractMessageListenerContainer.cs" />
<Compile Include="Messaging\Listener\IDistributedTransactionExceptionHandler.cs" />
<Compile Include="Messaging\Listener\IMessageTransactionExceptionHandler.cs" />
<Compile Include="Messaging\Listener\AbstractPeekingMessageListenerContainer.cs" />
<Compile Include="Messaging\Listener\SendToQueueExceptionHandler.cs" />
<Compile Include="Messaging\Listener\TransactionAction.cs" />
<Compile Include="Messaging\Listener\IExceptionHandler.cs" />
<Compile Include="Messaging\Listener\IMessageListener.cs" />
<Compile Include="Messaging\Listener\SimpleMessageListenerContainer.cs" />
<Compile Include="Messaging\MessagingException.cs" />
<Compile Include="Messaging\Support\Converters\ActiveXMessageConverter.cs" />
<Compile Include="Messaging\Support\Converters\BinaryMessageConverter.cs" />
<Compile Include="Messaging\Support\Converters\IMessageConverter.cs" />
<Compile Include="Messaging\Support\Converters\XmlDocumentConverter.cs" />
<Compile Include="Messaging\Support\Converters\XmlMessageConverter.cs" />
<Compile Include="Messaging\Support\MessageQueueFactoryObject.cs" />
<Compile Include="Messaging\Support\QueueUtils.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Spring.Core\Spring.Core.2005.csproj">
<Project>{710961A3-0DF4-49E4-A26E-F5B9C044AC84}</Project>
<Name>Spring.Core.2005</Name>
</ProjectReference>
<ProjectReference Include="..\Spring.Data\Spring.Data.2005.csproj">
<Project>{AE00E5AB-C39A-436F-86D2-33BFE33E2E40}</Project>
<Name>Spring.Data.2005</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<None Include="Messaging\Listener\ListenerClassDiagram.cd" />
</ItemGroup>
<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" ?>
<project name="Spring.Messaging" default="build" xmlns="http://nant.sf.net/schemas/nant.xsd">
<!--
Required properties:
* current.bin.dir - (path) root level to build to
* current.build.debug - (true|false) debug build?
* current.build.defines.csc - framework-specific build defines for C# compiler
-->
<target name="build">
<csc target="library" define="${current.build.defines.csc}"
warnaserror="false"
debug="${current.build.debug}"
output="${current.bin.dir}/${project::get-name()}.dll"
doc="${current.bin.dir}/${project::get-name()}.xml"
>
<arg line="${compiler.args}"/>
<nowarn>
<warning number="1591" />
</nowarn>
<sources failonempty="true">
<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>
</csc>
</target>
</project>

View File

@@ -0,0 +1,182 @@
#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;
using System.Transactions;
using NUnit.Framework;
using Spring.Data.Core;
using Spring.Messaging.Support.Converters;
using Spring.Testing.NUnit;
using Spring.Threading;
using Spring.Transaction;
using Spring.Transaction.Support;
using Spring.Util;
#endregion
namespace Spring.Messaging.Core
{
/// <summary>
/// This class contains tests for
/// </summary>
/// <author>Mark Pollack</author>
/// <version>$Id:$</version>
[TestFixture]
public class MessageQueueTemplateTests : AbstractDependencyInjectionSpringContextTests
{
[Test]
[ExpectedException(typeof (ArgumentException), ExpectedMessage = "DefaultMessageQueueObjectName is required.")]
public void NoMessageQueueNameSpecified()
{
MessageQueueTemplate mqt = new MessageQueueTemplate();
mqt.AfterPropertiesSet();
}
[Test]
[ExpectedException(typeof (ArgumentException),
ExpectedMessage = "No object named noqueuename is defined in the Spring container")]
public void MessageQueueNameNotInContext()
{
MessageQueueTemplate q = new MessageQueueTemplate("noqueuename");
q.ApplicationContext = applicationContext;
q.AfterPropertiesSet();
}
[Test]
public void MessageQueueCreatedinThreadLocalStorage()
{
MessageQueueTemplate q = applicationContext["queue"] as MessageQueueTemplate;
Assert.IsNotNull(q);
Assert.AreEqual(q.DefaultMessageQueue, q.MessageQueueFactory.CreateMessageQueue(q.DefaultMessageQueueObjectName));
}
[Test]
[ExpectedException(typeof (InvalidOperationException),
ExpectedMessage = "No MessageConverter registered. Check configuration of MessageQueueTemplate.")]
public void MessageConverterNotRegistered()
{
MessageQueueTemplate q = applicationContext["queue-noconverter"] as MessageQueueTemplate;
Assert.IsNotNull(q);
IMessageConverter c = q.MessageConverter;
}
#region Integration Tests - to be moved to another test assembly
[Test]
public void SendAndReceiveNonTransactional()
{
MessageQueueTemplate q = applicationContext["queue"] as MessageQueueTemplate;
Assert.IsNotNull(q);
ReceiveHelloWorld(q,1);
}
private static void ReceiveHelloWorld(MessageQueueTemplate q, int index)
{
object o = q.ReceiveAndConvert();
Assert.IsNotNull(o);
string data = o as string;
Assert.IsNotNull(data);
Assert.AreEqual("Hello World " + index, data);
}
[Test]
public void SendNonTxMessageQueueUsingMessageTx()
{
MessageQueueTemplate q = applicationContext["queue"] as MessageQueueTemplate;
Assert.IsNotNull(q);
SendAndRecieve(q);
}
[Test]
public void SendTxMessageQueueUsingMessageTx()
{
MessageQueueTemplate q = applicationContext["txqueue"] as MessageQueueTemplate;
Assert.IsNotNull(q);
SendAndRecieve(q);
}
[Test]
public void SendTxMessageQueueUsingTxScope()
{
MessageQueueTemplate q = applicationContext["txqueue"] as MessageQueueTemplate;
Assert.IsNotNull(q);
SendUsingMessageTxScope(q);
Receive(q);
}
private static void SendAndRecieve(MessageQueueTemplate q)
{
SendUsingMessageTx(q);
Receive(q);
}
private static void Receive(MessageQueueTemplate q)
{
ReceiveHelloWorld(q,1);
ReceiveHelloWorld(q,2);
ReceiveHelloWorld(q,3);
}
private static void SendUsingMessageTx(MessageQueueTemplate q)
{
IPlatformTransactionManager txManager = new MessageQueueTransactionManager();
TransactionTemplate transactionTemplate = new TransactionTemplate(txManager);
transactionTemplate.Execute(delegate(ITransactionStatus status)
{
q.ConvertAndSend("Hello World 1");
q.ConvertAndSend("Hello World 2");
q.ConvertAndSend("Hello World 3");
return null;
});
}
private static void SendUsingMessageTxScope(MessageQueueTemplate q)
{
IPlatformTransactionManager txManager = new TxScopeTransactionManager();
TransactionTemplate transactionTemplate = new TransactionTemplate(txManager);
transactionTemplate.Execute(delegate(ITransactionStatus status)
{
q.ConvertAndSend("Hello World 1");
q.ConvertAndSend("Hello World 2");
q.ConvertAndSend("Hello World 3");
return null;
});
}
#endregion
[Test]
public void GetAllFromQueue()
{
MessageQueueTemplate q = applicationContext["queue"] as MessageQueueTemplate;
Assert.IsNotNull(q);
for (int i = 0; i < 5; i++)
{
Console.WriteLine(q.ReceiveAndConvert());
}
}
protected override string[] ConfigLocations
{
get { return new string[] {"assembly://Spring.Messaging.Tests/Spring.Messaging.Core/MessageQueueTemplateTests.xml"}; }
}
}
}

View File

@@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8" ?>
<objects xmlns="http://www.springframework.net">
<object id='testqueue' type='Spring.Messaging.Support.MessageQueueFactoryObject, Spring.Messaging'>
<property name='Path' value='.\Private$\testqueue'/>
<property name='DenySharedReceive' value='true'/>
<property name='AccessMode' value='Receive'/>
<property name='EnableCache' value='true'/>
<property name='ProductTemplate'>
<object>
<property name='Label' value='MyLabel'/>
</object>
</property>
</object>
<object id='testtxqueue' type='Spring.Messaging.Support.MessageQueueFactoryObject, Spring.Messaging'>
<property name='Path' value='.\Private$\testtxqueue'/>
<property name='ProductTemplate'>
<object>
<property name='Label' value='MyTxLabel'/>
</object>
</property>
</object>
<object id='msqueue' type='Spring.Messaging.Support.MessageQueueFactoryObject, Spring.Messaging'>
<property name='Path' value='.\Private$\testqueue'/>
<property name='ProductTemplate'>
<object>
<property name='Label' value='MyLabel'/>
</object>
</property>
</object>
<object id="queue" type="Spring.Messaging.Core.MessageQueueTemplate, Spring.Messaging">
<property name="MessageQueueObjectName" value="testtxqueue"/>
<property name="MessageConverterObjectName" value="messageConverter"/>
</object>
<object id="queue-noconverter" type="Spring.Messaging.Core.MessageQueueTemplate, Spring.Messaging">
<property name="MessageQueueObjectName" value="testtxqueue"/>
</object>
<object id="txqueue" type="Spring.Messaging.Core.MessageQueueTemplate, Spring.Messaging">
<property name="MessageQueueObjectName" value="testtxqueue"/>
<property name="MessageConverterObjectName" value="messageConverter"/>
</object>
<object id="messageConverter" type="Spring.Messaging.Support.Converters.XmlMessageConverter, Spring.Messaging"
singleton="false">
<property name="TargetTypes" value="System.String"/>
</object>
</objects>

View File

@@ -0,0 +1,56 @@
#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.Threading;
using NUnit.Framework;
#endregion
namespace Spring.Messaging.Core
{
/// <summary>
/// This class contains tests for
/// </summary>
/// <author>Mark Pollack</author>
/// <version>$Id:$</version>
[TestFixture]
public class ThreadingTests
{
private int activeListenerCount = 0;
[SetUp]
public void Setup()
{
}
[Test]
public void Test()
{
Interlocked.Increment(ref activeListenerCount);
// just gets the current value...
int count = Interlocked.CompareExchange(ref activeListenerCount, -1, -1);
Assert.AreEqual(1, count);
}
}
}

View File

@@ -0,0 +1,116 @@
#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.Threading;
using NUnit.Framework;
using Spring.Messaging.Core;
using Spring.Testing.NUnit;
#endregion
namespace Spring.Messaging.Listener
{
/// <summary>
/// This class contains tests for DistributedTxMessageListenerContainer
/// </summary>
/// <author>Mark Pollack</author>
[TestFixture]
public class DistributedTxMessageListenerContainerTests : AbstractDependencyInjectionSpringContextTests
{
private int waitInMillis = 20000;
private DistributedTxMessageListenerContainer distributedTxMessageListenerContainer;
private SimpleHandler listener;
public DistributedTxMessageListenerContainer DistributedTxMessageListenerContainer
{
set { distributedTxMessageListenerContainer = value; }
}
public SimpleHandler Listener
{
set { listener = value; }
}
[Test]
public void SendAndAsyncReceiveWithExceptionHandling()
{
MessageQueueTemplate q = applicationContext["queueTemplate"] as MessageQueueTemplate;
Assert.IsNotNull(q);
MessageQueueTemplate retryQ = applicationContext["retryQueueTemplate"] as MessageQueueTemplate;
Assert.IsNotNull(retryQ);
q.ConvertAndSend("Goodbye World 1");
Assert.AreEqual(0, listener.MessageCount);
distributedTxMessageListenerContainer.Start();
Thread.Sleep(waitInMillis);
distributedTxMessageListenerContainer.Stop();
distributedTxMessageListenerContainer.Shutdown();
Thread.Sleep(2500);
object msg = retryQ.ReceiveAndConvert();
Assert.IsNotNull(msg);
string textMsg = msg as string;
Assert.IsNotNull(textMsg);
Assert.AreEqual("Goodbye World 1", textMsg);
}
[Test]
public void SendAndAsyncReceive()
{
MessageQueueTemplate q = applicationContext["queueTemplate"] as MessageQueueTemplate;
Assert.IsNotNull(q);
q.ConvertAndSend("Hello World 1");
q.ConvertAndSend("Hello World 2");
q.ConvertAndSend("Hello World 3");
q.ConvertAndSend("Hello World 4");
q.ConvertAndSend("Hello World 5");
Assert.AreEqual(0, listener.MessageCount);
distributedTxMessageListenerContainer.Start();
Thread.Sleep(waitInMillis);
Assert.AreEqual(5, listener.MessageCount);
distributedTxMessageListenerContainer.Stop();
distributedTxMessageListenerContainer.Shutdown();
Thread.Sleep(2500);
}
protected override string[] ConfigLocations
{
get { return new string[] { "assembly://Spring.Messaging.Tests/Spring.Messaging.Listener/DistributedTxMessageListenerContainerTests.xml" }; }
}
}
}

View File

@@ -0,0 +1,86 @@
<?xml version="1.0" encoding="utf-8" ?>
<objects xmlns="http://www.springframework.net"
xmlns:db="http://www.springframework.net/database">
<db:provider id="DbProvider"
provider="System.Data.SqlClient"
connectionString="Data Source=MARKT60\SQL2005;Initial Catalog=Spring;Persist Security Info=True;User ID=springqa;Password=springqa"/>
<object id="txScopeTransactionManager"
type="Spring.Data.Core.TxScopeTransactionManager, Spring.Data">
</object>
<object id='msmqtxqueue' type='Spring.Messaging.Support.MessageQueueFactoryObject, Spring.Messaging'>
<property name='Path' value='.\Private$\testtxqueue'/>
<property name='MessageReadPropertyFilterSetAll' value='true'/>
<property name='ProductTemplate'>
<object>
<property name='Label' value='MyTxLabel'/>
</object>
</property>
</object>
<object id='msmqtxresponsequeue' type='Spring.Messaging.Support.MessageQueueFactoryObject, Spring.Messaging'>
<property name='Path' value='.\Private$\testtxresponsequeue'/>
<property name='MessageReadPropertyFilterSetAll' value='true'/>
<property name='ProductTemplate'>
<object>
<property name='Label' value='MyTxResponseLabel'/>
</object>
</property>
</object>
<object id='msmqtxretryqueue' type='Spring.Messaging.Support.MessageQueueFactoryObject, Spring.Messaging'>
<property name='Path' value='.\Private$\testtxretryqueue'/>
<property name='MessageReadPropertyFilterSetAll' value='true'/>
<property name='ProductTemplate'>
<object>
<property name='Label' value='MyTxRetryLabel'/>
</object>
</property>
</object>
<object id="queueTemplate" type="Spring.Messaging.Core.MessageQueueTemplate, Spring.Messaging">
<property name="MessageQueueObjectName" value="msmqtxqueue"/>
<property name="MessageConverterObjectName" value="messageConverter"/>
</object>
<object id="retryQueueTemplate" type="Spring.Messaging.Core.MessageQueueTemplate, Spring.Messaging">
<property name="MessageQueueObjectName" value="msmqtxretryqueue"/>
<property name="MessageConverterObjectName" value="messageConverter"/>
</object>
<object id="messageConverter" type="Spring.Messaging.Support.Converters.XmlMessageConverter, Spring.Messaging"
singleton="false">
<property name="TargetTypes" value="System.String"/>
</object>
<object id="transactionalMessageListenerContainer" type="Spring.Messaging.Listener.DistributedTxMessageListenerContainer, Spring.Messaging">
<property name="MessageQueueObjectName" value="msmqtxqueue"/>
<property name="PlatformTransactionManager" ref="txScopeTransactionManager"/>
<property name="MaxConcurrentListeners" value="1"/>
<property name="ListenerTimeLimitInMillis" value="20000"/>
<property name="MessageListener" ref="messageListenerAdapter"/>
<property name="DistributedTransactionExceptionHandler" ref="distributedTransactionExceptionHandler"/>
<property name="AutoStartup" value="false"/>
</object>
<object id="messageListenerAdapter" type="Spring.Messaging.Listener.MessageListenerAdapter, Spring.Messaging">
<property name="DefaultResponseQueueName" value="msmqtxresponsequeue"/>
<property name="MessageConverterObjectName" value="messageConverter"/>
<property name="HandlerObject" ref="simpleHandler"/>
</object>
<object id="simpleHandler" type="Spring.Messaging.Listener.SimpleHandler, Spring.Messaging.Tests">
</object>
<object id="distributedTransactionExceptionHandler" type="Spring.Messaging.Listener.SendToQueueDistributedTransactionExceptionHandler, Spring.Messaging">
<property name="MaxRetry" value="2"/>
<property name="MessageQueueObjectName" value="msmqtxretryqueue"/>
</object>
<object id="simpleMessageListener" type="Spring.Messaging.Listener.SimpleMessageListener, Spring.Messaging.Tests"/>
</objects>

View File

@@ -0,0 +1,68 @@
using System;
using System.Messaging;
using System.Threading;
using Common.Logging;
namespace Spring.Messaging.Listener
{
/// <summary>
///
/// </summary>
public class LoggingExceptionHandler : IExceptionHandler
{
private TimeSpan recoveryTimeSpan;
#region Logging Definition
private static readonly ILog LOG = LogManager.GetLogger(typeof (LoggingExceptionHandler));
#endregion
/// <summary>
/// Initializes a new instance of the <see cref="LoggingExceptionHandler"/> class with
/// a default recovery time span of 5 seconds.
/// </summary>
public LoggingExceptionHandler()
{
recoveryTimeSpan = new TimeSpan(0, 0, 0, 5);
}
public LoggingExceptionHandler(TimeSpan recoveryTimeSpan)
{
this.recoveryTimeSpan = recoveryTimeSpan;
}
public TimeSpan RecoveryTimeSpan
{
set { recoveryTimeSpan = value; }
}
#region IExceptionListener Members
public void OnException(Exception exception, Message message)
{
//TODO other exception handling
MessageQueueException e = exception as MessageQueueException;
if (e != null)
{
switch ((int) e.MessageQueueErrorCode)
{
case (int) MessageQueueErrorCode.IOTimeout:
case -1073741536:
Console.WriteLine("Msmq Error -1073741536 or IOTimeout : Sleeping, and then ReListening");
Thread.Sleep(recoveryTimeSpan);
break;
default:
LOG.Error("Exception Receiving Message", e);
break;
}
}
else
{
LOG.Error("got exception", exception);
}
}
#endregion
}
}

View File

@@ -0,0 +1,114 @@
#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.Threading;
using NUnit.Framework;
using Spring.Messaging.Core;
using Spring.Testing.NUnit;
#endregion
namespace Spring.Messaging.Listener
{
/// <summary>
/// This class contains tests for
/// </summary>
/// <author>Mark Pollack</author>
/// <version>$Id:$</version>
[TestFixture]
public class NonTransactionalMessageListenerContainerTests : AbstractDependencyInjectionSpringContextTests
{
private int waitInMillis = 20000;
private NonTransactionalMessageListenerContainer container;
private SimpleHandler listener;
private SimpleExceptionHandler exceptionHandler;
[Test]
public void Test()
{
}
public SimpleExceptionHandler ExceptionHandler
{
set { exceptionHandler = value; }
}
public NonTransactionalMessageListenerContainer Container
{
get { return container; }
set { container = value; }
}
public SimpleHandler Listener
{
get { return listener; }
set { listener = value; }
}
[Test]
public void SendAndAsyncReceiveWithExceptionHandling()
{
MessageQueueTemplate q = applicationContext["testQueueTemplate"] as MessageQueueTemplate;
Assert.IsNotNull(q);
q.ConvertAndSend("Goodbye World 1");
Assert.AreEqual(0, listener.MessageCount);
container.Start();
Thread.Sleep(waitInMillis);
Assert.AreEqual(0, listener.MessageCount);
Assert.AreEqual(1, exceptionHandler.MessageCount);
container.Stop();
container.Shutdown();
Thread.Sleep(2500);
}
[Test]
public void SendAndAsyncReceive()
{
MessageQueueTemplate q = applicationContext["testQueueTemplate"] as MessageQueueTemplate;
Assert.IsNotNull(q);
q.ConvertAndSend("Hello World 1");
q.ConvertAndSend("Hello World 2");
q.ConvertAndSend("Hello World 3");
q.ConvertAndSend("Hello World 4");
q.ConvertAndSend("Hello World 5");
Assert.AreEqual(0, listener.MessageCount);
container.Start();
Thread.Sleep(waitInMillis);
Assert.AreEqual(5, listener.MessageCount);
Assert.AreEqual(0, exceptionHandler.MessageCount);
container.Stop();
container.Shutdown();
Thread.Sleep(2500);
}
protected override string[] ConfigLocations
{
get { return new string[] { "assembly://Spring.Messaging.Tests/Spring.Messaging.Listener/NonTransactionalMessageListenerContainerTests.xml" }; }
}
}
}

View File

@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8" ?>
<objects xmlns="http://www.springframework.net"
xmlns:db="http://www.springframework.net/database">
<db:provider id="DbProvider"
provider="System.Data.SqlClient"
connectionString="Data Source=MARKT60\SQL2005;Initial Catalog=Spring;Persist Security Info=True;User ID=springqa;Password=springqa"/>
<object id="adoTransactionManager"
type="Spring.Data.Core.AdoPlatformTransactionManager, Spring.Data">
<property name="DbProvider" ref="DbProvider"/>
</object>
<object id='msmqTestQueue' type='Spring.Messaging.Support.MessageQueueFactoryObject, Spring.Messaging'>
<property name='Path' value='.\Private$\testqueue'/>
<property name='MessageReadPropertyFilterSetAll' value='true'/>
<property name='ProductTemplate'>
<object>
<property name='Label' value='MyTestQueueLabel'/>
</object>
</property>
</object>
<object id='msmqTestResponseQueue' type='Spring.Messaging.Support.MessageQueueFactoryObject, Spring.Messaging'>
<property name='Path' value='.\Private$\testresponsequeue'/>
<property name='MessageReadPropertyFilterSetAll' value='true'/>
<property name='ProductTemplate'>
<object>
<property name='Label' value='MyTestResponseQueueLabel'/>
</object>
</property>
</object>
<object id="testQueueTemplate" type="Spring.Messaging.Core.MessageQueueTemplate, Spring.Messaging">
<property name="MessageQueueObjectName" value="msmqTestQueue"/>
<property name="MessageConverterObjectName" value="messageConverter"/>
</object>
<object id="messageConverter" type="Spring.Messaging.Support.Converters.XmlMessageConverter, Spring.Messaging"
singleton="false">
<property name="TargetTypes" value="System.String"/>
</object>
<object id="nonTransactionalMessageListenerContainer" type="Spring.Messaging.Listener.NonTransactionalMessageListenerContainer, Spring.Messaging">
<property name="MessageQueueObjectName" value="msmqTestQueue"/>
<property name="MaxConcurrentListeners" value="2"/>
<property name="ListenerTimeLimit" value="20s"/>
<property name="MessageListener" ref="messageListenerAdapter"/>
<property name="ExceptionHandler" ref="exceptionHandler"/>
<property name="AutoStartup" value="false"/>
</object>
<object id="messageListenerAdapter" type="Spring.Messaging.Listener.MessageListenerAdapter, Spring.Messaging">
<property name="DefaultResponseQueueName" value="msmqTestResponseQueue"/>
<property name="MessageConverterObjectName" value="messageConverter"/>
<property name="HandlerObject" ref="simpleHandler"/>
</object>
<object id="simpleHandler" type="Spring.Messaging.Listener.SimpleHandler, Spring.Messaging.Tests">
</object>
<object id="exceptionHandler" type="Spring.Messaging.Listener.SimpleExceptionHandler, Spring.Messaging.Tests">
</object>
</objects>

View File

@@ -0,0 +1,36 @@
using System;
using System.Messaging;
using Common.Logging;
namespace Spring.Messaging.Listener
{
public class SimpleExceptionHandler : IExceptionHandler
{
#region Logging
private static readonly ILog LOG = LogManager.GetLogger(typeof(SimpleExceptionHandler));
#endregion
private int messageCount;
public int MessageCount
{
get { return messageCount; }
set { messageCount = value; }
}
#region IExceptionHandler Members
public void OnException(Exception exception, Message message)
{
LOG.Error("Exception Handler processing message id = [" + message.Id + "]");
messageCount++;
}
#endregion
}
}

View File

@@ -0,0 +1,37 @@
using System;
using Common.Logging;
namespace Spring.Messaging.Listener
{
public class SimpleHandler
{
#region Logging
private static readonly ILog LOG = LogManager.GetLogger(typeof(SimpleHandler));
#endregion
private int messageCount;
public int MessageCount
{
get { return messageCount; }
set { messageCount = value; }
}
public string HandleMessage(string msgTxt)
{
LOG.Debug("Received text = [" + msgTxt + "]");
if (msgTxt.Contains("Goodbye"))
{
throw new ArgumentException("Don't like saying goodbye!");
}
messageCount++;
LOG.Debug("Message listener count = " + messageCount);
return msgTxt + " - processed!";
}
}
}

View File

@@ -0,0 +1,40 @@
using System.Messaging;
using Common.Logging;
namespace Spring.Messaging.Listener
{
public class SimpleMessageListener : IMessageListener
{
#region Logging Definition
private static readonly ILog LOG = LogManager.GetLogger(typeof(SimpleMessageListener));
#endregion
private Message lastReceivedMessage;
private int messageCount;
public Message LastReceivedMessage
{
get { return lastReceivedMessage; }
}
public int MessageCount
{
get { return messageCount; }
}
#region IMessageListener Members
public void OnMessage(Message message)
{
lastReceivedMessage = message;
messageCount++;
LOG.Debug("Message listener count = " + messageCount);
}
#endregion
}
}

View File

@@ -0,0 +1,106 @@
#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;
using System.Messaging;
using System.Threading;
using NUnit.Framework;
using Spring.Messaging.Core;
using Spring.Testing.NUnit;
#endregion
namespace Spring.Messaging.Listener
{
/// <summary>
/// This class contains tests for SimpleMessageListenerContainer
/// </summary>
/// <author>Mark Pollack</author>
/// <version>$Id:$</version>
[TestFixture]
public class SimpleMessageListenerContainerTests : AbstractDependencyInjectionSpringContextTests
{
[Test, ExpectedException(typeof(ArgumentException), ExpectedMessage = "Property 'DefaultMessageQueue' is required")]
public void EnsureMessageQueuePropertyIsSet()
{
SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
container.AfterPropertiesSet();
container.Start();
}
[Test]
public void SendAndAsyncReceive()
{
SimpleMessageListenerContainer container =
applicationContext["simpleMessageListenerContainer"] as SimpleMessageListenerContainer;
SimpleMessageListener listener = applicationContext["simpleMessageListener"] as SimpleMessageListener;
Assert.IsNotNull(container);
Assert.IsNotNull(listener);
MessageQueueTemplate q = applicationContext["queue"] as MessageQueueTemplate;
Assert.IsNotNull(q);
q.ConvertAndSend("Hello World 1");
int waitInMillis = 2000;
Thread.Sleep(waitInMillis);
Assert.AreEqual(0, listener.MessageCount);
container.Start();
//pick up the message that is already in the queue
Thread.Sleep(waitInMillis);
Assert.AreEqual(1, listener.MessageCount);
container.Stop();
q.ConvertAndSend("Hello World 2");
//what happens to this message, we stopped, so no new event is fired.
Thread.Sleep(waitInMillis);
Assert.AreEqual(1, listener.MessageCount);
container.Start();
Thread.Sleep(waitInMillis);
//did we get hello world 2?
Assert.AreEqual(2, listener.MessageCount);
/*
q.ConvertAndSend("Hello World");
Thread.Sleep(waitInMillis);
Assert.AreEqual(2, listener.MessageCount);
*/
container.Stop();
container.Shutdown();
Thread.Sleep(waitInMillis);
}
protected override string[] ConfigLocations
{
get { return new string[] { "assembly://Spring.Messaging.Tests/Spring.Messaging.Listener/SimpleMessageListenerContainerTests.xml" }; }
}
}
}

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8" ?>
<objects xmlns="http://www.springframework.net">
<object id='msqueue' type='Spring.Messaging.Support.MessageQueueFactoryObject, Spring.Messaging'>
<property name='Path' value='.\Private$\testqueue'/>
<property name='ProductTemplate'>
<object>
<property name='Label' value='MyLabel'/>
</object>
</property>
</object>
<object id="queue" type="Spring.Messaging.Core.MessageQueueTemplate, Spring.Messaging">
<property name="MessageQueueName" value="msqueue"/>
<property name="MessageConverter" ref="messageConverter"/>
</object>
<object id="messageConverter" type="Spring.Messaging.Support.Converters.XmlMessageConverter, Spring.Messaging">
<property name="TargetTypes" value="System.String"/>
</object>
<object id="simpleMessageListenerContainer" type="Spring.Messaging.Listener.SimpleMessageListenerContainer, Spring.Messaging">
<property name="MessageQueue" ref="msqueue"/>
<property name="MessageListener" ref="simpleMessageListener"/>
<property name="ExceptionHandler">
<object type="Spring.Messaging.Listener.LoggingExceptionHandler, Spring.Messaging.Tests"/>
</property>
<property name="AutoStartup" value="false"/>
</object>
<object id="simpleMessageListener" type="Spring.Messaging.Listener.SimpleMessageListener, Spring.Messaging.Tests"/>
</objects>

View File

@@ -0,0 +1,135 @@
#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;
using System.Threading;
using NUnit.Framework;
using Spring.Messaging.Core;
using Spring.Testing.NUnit;
#endregion
namespace Spring.Messaging.Listener
{
/// <summary>
/// This class contains integration tests for the TransactionalMessageListenerContainer
/// </summary>
/// <author>Mark Pollack</author>
[TestFixture]
public class TransactionalMessageListenerContainerTests : AbstractDependencyInjectionSpringContextTests
{
private int waitInMillis = 20000;
private TransactionalMessageListenerContainer transactionalMessageListenerContainer;
private SimpleHandler listener;
public TransactionalMessageListenerContainer TransactionalMessageListenerContainer
{
set { transactionalMessageListenerContainer = value; }
}
public SimpleHandler SimpleHandler
{
set { listener = value; }
}
[Test, ExpectedException(typeof(ArgumentException), ExpectedMessage = "Property 'DefaultMessageQueue' is required")]
public void EnsureMessageQueuePropertyIsSet()
{
TransactionalMessageListenerContainer container = new TransactionalMessageListenerContainer();
container.AfterPropertiesSet();
container.Start();
}
[Test]
public void EnsureuseContainerManagedMessageQueueTransactionIsSetCorrectly()
{
TransactionalMessageListenerContainer container = applicationContext["transactionalMessageListenerContainer"] as TransactionalMessageListenerContainer;
Assert.IsNotNull(container);
Assert.AreEqual(true, container.UseContainerManagedMessageQueueTransaction);
}
[Test]
public void SendAndAsyncReceiveWithExceptionHandling()
{
MessageQueueTemplate q = applicationContext["queueTemplate"] as MessageQueueTemplate;
Assert.IsNotNull(q);
MessageQueueTemplate retryQ = applicationContext["retryQueueTemplate"] as MessageQueueTemplate;
Assert.IsNotNull(retryQ);
q.ConvertAndSend("Goodbye World 1");
Assert.AreEqual(0, listener.MessageCount);
transactionalMessageListenerContainer.Start();
Thread.Sleep(waitInMillis);
transactionalMessageListenerContainer.Stop();
transactionalMessageListenerContainer.Shutdown();
Thread.Sleep(2500);
object msg = retryQ.ReceiveAndConvert();
Assert.IsNotNull(msg);
string textMsg = msg as string;
Assert.IsNotNull(textMsg);
Assert.AreEqual("Goodbye World 1", textMsg);
}
[Test]
public void SendAndAsyncReceive()
{
MessageQueueTemplate q = applicationContext["queueTemplate"] as MessageQueueTemplate;
Assert.IsNotNull(q);
q.ConvertAndSend("Hello World 1");
q.ConvertAndSend("Hello World 2");
q.ConvertAndSend("Hello World 3");
q.ConvertAndSend("Hello World 4");
q.ConvertAndSend("Hello World 5");
Assert.AreEqual(0, listener.MessageCount);
transactionalMessageListenerContainer.Start();
Thread.Sleep(waitInMillis);
Assert.AreEqual(5, listener.MessageCount);
transactionalMessageListenerContainer.Stop();
transactionalMessageListenerContainer.Shutdown();
Thread.Sleep(2500);
}
protected override string[] ConfigLocations
{
get { return new string[] { "assembly://Spring.Messaging.Tests/Spring.Messaging.Listener/TransactionalMessageListenerContainerTests.xml" }; }
}
}
}

View File

@@ -0,0 +1,88 @@
<?xml version="1.0" encoding="utf-8" ?>
<objects xmlns="http://www.springframework.net"
xmlns:db="http://www.springframework.net/database">
<db:provider id="DbProvider"
provider="System.Data.SqlClient"
connectionString="Data Source=MARKT60\SQL2005;Initial Catalog=Spring;Persist Security Info=True;User ID=springqa;Password=springqa"/>
<object id="adoTransactionManager"
type="Spring.Data.Core.AdoPlatformTransactionManager, Spring.Data">
<property name="DbProvider" ref="DbProvider"/>
</object>
<object id='msmqtxqueue' type='Spring.Messaging.Support.MessageQueueFactoryObject, Spring.Messaging'>
<property name='Path' value='.\Private$\testtxqueue'/>
<property name='MessageReadPropertyFilterSetAll' value='true'/>
<property name='ProductTemplate'>
<object>
<property name='Label' value='MyTxLabel'/>
</object>
</property>
</object>
<object id='msmqtxresponsequeue' type='Spring.Messaging.Support.MessageQueueFactoryObject, Spring.Messaging'>
<property name='Path' value='.\Private$\testtxresponsequeue'/>
<property name='MessageReadPropertyFilterSetAll' value='true'/>
<property name='ProductTemplate'>
<object>
<property name='Label' value='MyTxResponseLabel'/>
</object>
</property>
</object>
<object id='msmqtxretryqueue' type='Spring.Messaging.Support.MessageQueueFactoryObject, Spring.Messaging'>
<property name='Path' value='.\Private$\testtxretryqueue'/>
<property name='MessageReadPropertyFilterSetAll' value='true'/>
<property name='ProductTemplate'>
<object>
<property name='Label' value='MyTxRetryLabel'/>
</object>
</property>
</object>
<object id="queueTemplate" type="Spring.Messaging.Core.MessageQueueTemplate, Spring.Messaging">
<property name="MessageQueueObjectName" value="msmqtxqueue"/>
<property name="MessageConverterObjectName" value="messageConverter"/>
</object>
<object id="retryQueueTemplate" type="Spring.Messaging.Core.MessageQueueTemplate, Spring.Messaging">
<property name="MessageQueueObjectName" value="msmqtxretryqueue"/>
<property name="MessageConverterObjectName" value="messageConverter"/>
</object>
<object id="messageConverter" type="Spring.Messaging.Support.Converters.XmlMessageConverter, Spring.Messaging"
singleton="false">
<property name="TargetTypes" value="System.String"/>
</object>
<object id="transactionalMessageListenerContainer" type="Spring.Messaging.Listener.TransactionalMessageListenerContainer, Spring.Messaging">
<property name="MessageQueueObjectName" value="msmqtxqueue"/>
<property name="PlatformTransactionManager" ref="adoTransactionManager"/>
<property name="MaxConcurrentListeners" value="1"/>
<property name="ListenerTimeLimit" value="20s"/>
<property name="MessageListener" ref="messageListenerAdapter"/>
<property name="MessageTransactionExceptionHandler" ref="messageTransactionExceptionHandler"/>
<property name="AutoStartup" value="false"/>
</object>
<object id="messageListenerAdapter" type="Spring.Messaging.Listener.MessageListenerAdapter, Spring.Messaging">
<property name="DefaultResponseQueueName" value="msmqtxresponsequeue"/>
<property name="MessageConverterObjectNAme" value="messageConverter"/>
<property name="HandlerObject" ref="simpleHandler"/>
</object>
<object id="simpleHandler" type="Spring.Messaging.Listener.SimpleHandler, Spring.Messaging.Tests">
</object>
<object id="messageTransactionExceptionHandler" type="Spring.Messaging.Listener.SendToQueueExceptionHandler, Spring.Messaging">
<property name="MaxRetry" value="2"/>
<property name="MessageQueueObjectName" value="msmqtxretryqueue"/>
</object>
<object id="simpleMessageListener" type="Spring.Messaging.Listener.SimpleMessageListener, Spring.Messaging.Tests"/>
</objects>

View File

@@ -0,0 +1,65 @@
using System;
using System.Runtime.Serialization;
namespace Spring.Messaging
{
/// <summary>
/// Indicates that the received message has already been processed. Will typically be used
/// in an IMessageTransactionExceptionHandler to commit (i.e. remove from the queue) a
/// message that has been processed by the database but not acknowledged to MSMQ
/// due to an application failure.
/// </summary>
public class MessageAlreadyProcessedException : MessagingException
{
#region Constructor (s) / Destructor
/// <summary>Creates a new instance of the MessageAlreadyProcessedException class.</summary>
public MessageAlreadyProcessedException()
{
}
/// <summary>
/// Creates a new instance of the MessageAlreadyProcessedException class. with the specified message.
/// </summary>
/// <param name="message">
/// A message about the exception.
/// </param>
public MessageAlreadyProcessedException(string message) : base(message)
{
}
/// <summary>
/// Creates a new instance of the MessageAlreadyProcessedException class with the specified message
/// and root cause.
/// </summary>
/// <param name="message">
/// A message about the exception.
/// </param>
/// <param name="rootCause">
/// The root exception that is being wrapped.
/// </param>
public MessageAlreadyProcessedException(string message, Exception rootCause)
: base(message, rootCause)
{
}
/// <summary>
/// Creates a new instance of the MessageAlreadyProcessedException class.
/// </summary>
/// <param name="info">
/// The <see cref="System.Runtime.Serialization.SerializationInfo"/>
/// that holds the serialized object data about the exception being thrown.
/// </param>
/// <param name="context">
/// The <see cref="System.Runtime.Serialization.StreamingContext"/>
/// that contains contextual information about the source or destination.
/// </param>
protected MessageAlreadyProcessedException(
SerializationInfo info, StreamingContext context)
: base(info, context)
{
}
#endregion
}
}

View File

@@ -0,0 +1,89 @@
#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.Messaging;
using NUnit.Framework;
using Spring.Context;
using Spring.Context.Support;
using Spring.Testing.NUnit;
#endregion
namespace Spring.Messaging.Support
{
/// <summary>
/// This class contains tests for the MessageQueueFactory
/// </summary>
/// <author>Mark Pollack</author>
[TestFixture]
public class MessageQueueFactoryObjectTests : AbstractDependencyInjectionSpringContextTests
{
[Test]
public void CheckDefaultConstructorValues()
{
MessageQueueFactoryObject mqFactoryObject = new MessageQueueFactoryObject();
MessageQueue queue = mqFactoryObject.GetObject() as MessageQueue;
Assert.IsNotNull(queue);
Assert.AreEqual(string.Empty, queue.Path);
Assert.AreEqual(false, queue.DenySharedReceive);
Assert.AreEqual(QueueAccessMode.SendAndReceive, queue.AccessMode);
//EnableCache property not on queue.
}
[Test]
public void CheckSimpleProperties()
{
MessageQueueFactoryObject mqFactoryObject = (MessageQueueFactoryObject) applicationContext["&testqueue"];
Assert.AreEqual(@".\Private$\testqueue", mqFactoryObject.Path);
Assert.AreEqual(true, mqFactoryObject.DenySharedReceive);
Assert.AreEqual(QueueAccessMode.Receive, mqFactoryObject.AccessMode);
Assert.AreEqual(true, mqFactoryObject.EnableCache);
MessageQueue mq = (MessageQueue) applicationContext["testqueue"];
Assert.AreEqual("MyLabel", mq.Label);
}
[Test]
public void CheckGetObjectReturnsNewInstance()
{
MessageQueueFactoryObject mqFactoryObject = new MessageQueueFactoryObject();
MessageQueue queue = mqFactoryObject.GetObject() as MessageQueue;
MessageQueue anotherQueue = mqFactoryObject.GetObject() as MessageQueue;
Assert.IsFalse(queue == anotherQueue, "Should be returning new instances");
Assert.IsFalse(mqFactoryObject.IsSingleton,
"The MessageQueueFactoryObject class must be configured to return shared instances.");
}
[Test]
public void ObjectTypePropertyYieldsTheCorrectType()
{
MessageQueueFactoryObject mqFactoryObject = new MessageQueueFactoryObject();
Assert.AreEqual(typeof (MessageQueue), mqFactoryObject.ObjectType,
"The MessageQueueFactoryObject class ain't giving back DefaultMessageQueue types (it must).");
}
protected override string[] ConfigLocations
{
get { return new string[] {"assembly://Spring.Messaging.Tests/Spring.Messaging/queue-context.xml"}; }
}
}
}

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8" ?>
<objects xmlns="http://www.springframework.net">
<object id='testqueue' type='Spring.Messaging.Support.MessageQueueFactoryObject, Spring.Messaging'>
<property name='Path' value='.\Private$\testqueue'/>
<property name='DenySharedReceive' value='true'/>
<property name='AccessMode' value='Receive'/>
<property name='EnableCache' value='true'/>
<property name='ProductTemplate'>
<object>
<property name='Label' value='MyLabel'/>
</object>
</property>
</object>
</objects>

View File

@@ -0,0 +1,35 @@
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("Spring.Messaging.Test")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("Spring.Messaging.Test")]
[assembly: AssemblyCopyright("Copyright © 2008")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("75d6d9a9-9fcf-45ff-9691-1b0cc086e243")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Revision and Build Numbers
// by using the '*' as shown below:
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

View File

@@ -0,0 +1,125 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.50727</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{41BC3AEA-7EB3-48BF-B1EC-84119376AC98}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>Spring</RootNamespace>
<AssemblyName>Spring.Messaging.Tests</AssemblyName>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="Common.Logging, Version=1.2.0.0, Culture=neutral, PublicKeyToken=af08829b84f0328e">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\lib\Net\2.0\Common.Logging.dll</HintPath>
</Reference>
<Reference Include="Common.Logging.Log4Net, Version=1.2.0.2, Culture=neutral, PublicKeyToken=af08829b84f0328e, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\lib\Net\2.0\Common.Logging.Log4Net.dll</HintPath>
</Reference>
<Reference Include="log4net, Version=1.2.10.0, Culture=neutral, PublicKeyToken=1b44e1d426115821, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\lib\Net\2.0\log4net.dll</HintPath>
</Reference>
<Reference Include="nunit.framework, Version=2.4.1.0, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\lib\Net\2.0\nunit.framework.dll</HintPath>
</Reference>
<Reference Include="Rhino.Mocks, Version=2.9.6.40380, Culture=neutral, PublicKeyToken=0b3305902db7183f, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\..\..\lib\Net\2.0\Rhino.Mocks.dll</HintPath>
</Reference>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Messaging" />
<Reference Include="System.Transactions" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Messaging\Core\MessageQueueTemplateTests.cs" />
<Compile Include="Messaging\Support\MessageQueueFactoryObjectTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\Spring\Spring.Core\Spring.Core.2005.csproj">
<Project>{710961A3-0DF4-49E4-A26E-F5B9C044AC84}</Project>
<Name>Spring.Core.2005</Name>
</ProjectReference>
<ProjectReference Include="..\..\..\src\Spring\Spring.Data\Spring.Data.2005.csproj">
<Project>{AE00E5AB-C39A-436F-86D2-33BFE33E2E40}</Project>
<Name>Spring.Data.2005</Name>
</ProjectReference>
<ProjectReference Include="..\..\..\src\Spring\Spring.Messaging\Spring.Messaging.2005.csproj">
<Project>{0E23AE41-D8D8-41C2-84A2-D35564049F0D}</Project>
<Name>Spring.Messaging.2005</Name>
</ProjectReference>
<ProjectReference Include="..\..\..\src\Spring\Spring.Testing.NUnit\Spring.Testing.NUnit.2005.csproj">
<Project>{ED204A7B-832F-44C7-BFE3-504AEBE1BCC8}</Project>
<Name>Spring.Testing.NUnit.2005</Name>
</ProjectReference>
<ProjectReference Include="..\Spring.Core.Tests\Spring.Core.Tests.2005.csproj">
<Project>{44B16BAA-6DF8-447C-9D7F-3AD3D854D904}</Project>
<Name>Spring.Core.Tests.2005</Name>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Messaging\Listener\NonTransactionalMessageListenerContainerTests.xml" />
<EmbeddedResource Include="Messaging\Listener\SimpleMessageListenerContainerTests.xml" />
<EmbeddedResource Include="Messaging\Listener\TransactionalMessageListenerContainerTests.xml" />
<EmbeddedResource Include="Messaging\queue-context.xml" />
<EmbeddedResource Include="Messaging\Core\MessageQueueTemplateTests.xml" />
<EmbeddedResource Include="Messaging\Listener\DistributedTxMessageListenerContainerTests.xml" />
<Content Include="Spring.Messaging.Tests.dll.config">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Compile Include="Messaging\Core\ThreadingTests.cs" />
<Compile Include="Messaging\Listener\DistributedTxMessageListenerContainerTests.cs" />
<Compile Include="Messaging\Listener\LoggingExceptionHandler.cs" />
<Compile Include="Messaging\Listener\NonTransactionalMessageListenerContainerTests.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Messaging\Listener\SimpleExceptionHandler.cs" />
<Compile Include="Messaging\Listener\SimpleHandler.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Messaging\Listener\SimpleMessageListener.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Messaging\Listener\SimpleMessageListenerContainerTests.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Messaging\Listener\TransactionalMessageListenerContainerTests.cs">
<SubType>Code</SubType>
</Compile>
<Compile Include="Messaging\Support\MessageAlreadyProcessedException.cs" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@@ -0,0 +1,44 @@
<?xml version="1.0" ?>
<project name="Spring.Messaging.Nms.Tests" default="test" xmlns="http://nant.sf.net/schemas/nant.xsd">
<!--
Required properties:
* current.bin.dir - (path) root level to build to
* current.build.debug - (true|false) debug build?
* current.build.defines.csc - framework-specific build defines for C# compiler
-->
<target name="build">
<!-- build Spring.Data.Tests -->
<csc target="library" define="${current.build.defines.csc}"
warnaserror="true"
debug="${current.build.debug}"
output="${current.bin.dir}/${project::get-name()}.dll"
doc="${current.bin.dir}/${project::get-name()}.xml">
<nowarn>
<warning number="${nowarn.numbers.test}" />
</nowarn>
<sources failonempty="true">
<include name="**/*.cs" />
<include name="../CommonAssemblyInfo.cs" />
</sources>
<resources prefix="Spring" dynamicprefix="true" failonempty="true">
<include name="**/*.xml" />
</resources>
<references basedir="${current.bin.dir}">
<include name="*.dll" />
<exclude name="${project::get-name()}.dll" />
<exclude name="CloverRuntime.dll" />
</references>
</csc>
<copy file="${project::get-base-directory()}/${project::get-name()}.dll.config"
tofile="${current.bin.dir}/${project::get-name()}.dll.config"/>
</target>
<target name="test" depends="build">
<nunit2outproc>
<formatter type="Plain" />
<formatter type="Xml" usefile="true" extension=".xml"
outputdir="${current.bin.dir}/results" />
<test assemblyname="${current.bin.dir}/${project::get-name()}.dll" />
</nunit2outproc>
</target>
</project>

View File

@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8" ?>
<!--
Copyright 2002-2005 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.
-->
<configuration>
<configSections>
<sectionGroup name="common">
<section name="logging" type="Common.Logging.ConfigurationSectionHandler, Common.Logging" />
</sectionGroup>
<sectionGroup name="spring">
<section name="parsers" type="Spring.Context.Support.NamespaceParsersSectionHandler, Spring.Core" />
</sectionGroup>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler, log4net"/>
</configSections>
<common>
<logging>
<factoryAdapter type="Common.Logging.Log4Net.Log4NetLoggerFactoryAdapter, Common.Logging.Log4Net">
<arg key="configType" value="INLINE" />
</factoryAdapter>
</logging>
</common>
<log4net>
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger %ndc - %message%newline" />
</layout>
</appender>
<root>
<level value="TRACE" />
<appender-ref ref="ConsoleAppender" />
</root>
<logger name="Spring">
<level value="DEBUG" />
</logger>
<logger name="Spring.Messaging">
<level value="TRACE" />
</logger>
</log4net>
<spring>
<parsers>
<parser type="Spring.Data.Config.DatabaseNamespaceParser, Spring.Data" />
</parsers>
</spring>
</configuration>