Initial MSMQ support
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -179,7 +179,6 @@ namespace Spring.Messaging.Nms.Listener.Adapter
|
||||
}
|
||||
else
|
||||
{
|
||||
//TODO ' use as '
|
||||
IMessage msg = result as IMessage;
|
||||
if (msg == null)
|
||||
{
|
||||
|
||||
4
src/Spring/Spring.Messaging/AssemblyInfo.cs
Normal file
4
src/Spring/Spring.Messaging/AssemblyInfo.cs
Normal 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")]
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
} ;
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
59
src/Spring/Spring.Messaging/Messaging/MessagingException.cs
Normal file
59
src/Spring/Spring.Messaging/Messaging/MessagingException.cs
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
151
src/Spring/Spring.Messaging/Messaging/Support/QueueUtils.cs
Normal file
151
src/Spring/Spring.Messaging/Messaging/Support/QueueUtils.cs
Normal 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; }
|
||||
}
|
||||
}
|
||||
109
src/Spring/Spring.Messaging/Spring.Messaging.2005.csproj
Normal file
109
src/Spring/Spring.Messaging/Spring.Messaging.2005.csproj
Normal 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>
|
||||
32
src/Spring/Spring.Messaging/Spring.Messaging.build
Normal file
32
src/Spring/Spring.Messaging/Spring.Messaging.build
Normal 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>
|
||||
@@ -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"}; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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" }; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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" }; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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!";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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" }; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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" }; }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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"}; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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")]
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user