SPRNET-1055 - Provide ability to easily register to MessageQueueFactoryObject definitions with the container at runtime

This commit is contained in:
markpollack
2008-10-12 21:00:43 +00:00
parent b4862ff7f9
commit 6a51ec1417
11 changed files with 350 additions and 45 deletions

View File

@@ -18,10 +18,11 @@
#endregion
using System;
using System.Collections;
using System.Messaging;
using Spring.Context;
using Spring.Messaging.Support;
using Spring.Messaging.Support.Converters;
using Spring.Threading;
using Spring.Util;
@@ -30,7 +31,8 @@ namespace Spring.Messaging.Core
{
/// <summary>
/// A <see cref="IMessageQueueFactory"/> implementation that caches MessageQueue and IMessageConverter
/// instances.
/// instances. The MessageQueue objects are created by retrieving them by-name from the
/// ApplicationContext.
/// </summary>
/// <author>Mark Pollack</author>
public class DefaultMessageQueueFactory : IMessageQueueFactory, IApplicationContextAware
@@ -41,10 +43,18 @@ namespace Spring.Messaging.Core
private static readonly string CONVERTER_DICTIONARY_SLOTNAME =
UniqueKey.GetTypeScopedString(typeof (DefaultMessageQueueFactory), "Converter");
private IApplicationContext applicationContext;
private IConfigurableApplicationContext applicationContext;
#region IMessageQueueFactory Members
public void RegisterMessageQueue(string messageQueueObjectName,
MessageQueueCreatorDelegate messageQueueCreatorDelegate)
{
MessageQueueFactoryObject mqfo = new MessageQueueFactoryObject();
mqfo.MessageCreatorDelegate = messageQueueCreatorDelegate;
applicationContext.ObjectFactory.RegisterSingleton(messageQueueObjectName, mqfo);
}
public MessageQueue CreateMessageQueue(string messageQueueObjectName)
{
AssertUtils.ArgumentHasText(messageQueueObjectName, "DefaultMessageQueueObjectName");
@@ -62,21 +72,40 @@ namespace Spring.Messaging.Core
return queues[messageQueueObjectName] as MessageQueue;
}
public IMessageConverter CreateMessageConverter(string messgaeConverterObjectName)
public bool ContainsMessageQueue(string messageQueueObjectName)
{
AssertUtils.ArgumentHasText(messgaeConverterObjectName, "MessgaeFormatterObjectName");
return applicationContext.ContainsObject(messageQueueObjectName);
}
public void RegisterMessageConverter(string messageConverterName,
MessageConverterCreatorDelegate messageConverterCreatorDelegate)
{
MessageConverterFactoryObject mcfo = new MessageConverterFactoryObject();
mcfo.MessageConverterCreatorDelegate = messageConverterCreatorDelegate;
applicationContext.ObjectFactory.RegisterSingleton(messageConverterName, mcfo);
}
public IMessageConverter CreateMessageConverter(string messageConverterObjectName)
{
AssertUtils.ArgumentHasText(messageConverterObjectName, "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))
if (!converters.Contains(messageConverterObjectName))
{
IMessageConverter mc = applicationContext.GetObject(messgaeConverterObjectName) as IMessageConverter;
converters.Add(messgaeConverterObjectName, mc);
IMessageConverter mc = applicationContext.GetObject(messageConverterObjectName) as IMessageConverter;
converters.Add(messageConverterObjectName, mc);
}
return converters[messgaeConverterObjectName] as IMessageConverter;
return converters[messageConverterObjectName] as IMessageConverter;
}
public bool ContainsMessageConverter(string messageConverterObjectName)
{
return applicationContext.ContainsObject(messageConverterObjectName);
}
#endregion
@@ -112,7 +141,18 @@ namespace Spring.Messaging.Core
public IApplicationContext ApplicationContext
{
get { return applicationContext; }
set { applicationContext = value; }
set
{
AssertUtils.ArgumentNotNull(value, "An ApplicationContext instance is required");
IConfigurableApplicationContext ctx = value as IConfigurableApplicationContext;
if (ctx == null)
{
throw new InvalidOperationException(
"Implementations of IApplicationContext must also implement IConfigurableApplicationContext");
}
applicationContext = ctx;
}
}
#endregion

View File

@@ -19,23 +19,42 @@
#endregion
using System.Messaging;
using Spring.Messaging.Support;
using Spring.Messaging.Support.Converters;
namespace Spring.Messaging.Core
{
/// <summary>
/// An interface for creating MessageQueue and IMessageConverter objects.
/// An interface for creating MessageQueue and IMessageConverter objects from object definitions
/// defined in the application context.
/// </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.
/// MessageQueue and IMessageConverter objects have methods that are generally not thread safe,
/// (IMessageConverter classes rely on IMessageFormatter objects that are not thread safe).
/// As such, 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>
/// <author>Mark Pollack</author>
public interface IMessageQueueFactory
{
void RegisterMessageQueue(string messageQueueObjectName,
MessageQueueCreatorDelegate messageQueueCreatorDelegate);
MessageQueue CreateMessageQueue(string messageQueueObjectName);
bool ContainsMessageQueue(string messageQueueObjectName);
void RegisterMessageConverter(string messageConverterName,
MessageConverterCreatorDelegate MessageConverterCreatorDelegate);
IMessageConverter CreateMessageConverter(string messageConverterObjectName);
bool ContainsMessageConverter(string messageConverterObjectName);
}
}

View File

@@ -88,6 +88,11 @@ namespace Spring.Messaging.Core
/// <exception cref="MessagingException">if thrown by MSMQ API methods</exception>
void ConvertAndSend(string messageQueueObjectName, object obj, MessagePostProcessorDelegate messagePostProcessorDelegate);
/*
void ConvertAndSend(QueueIdentifierType queueIdentifierType, string destinationValue, string messageQueueObjectName, object obj);
void ConvertAndSend(QueueIdentifierType queueIdentifierType, string destinationValue, string messageQueueObjectName, object obj, MessagePostProcessorDelegate messagePostProcessorDelegate);
*/
/// <summary>
/// Receive and convert a message synchronously from the default message queue.
/// </summary>

View File

@@ -0,0 +1,23 @@
namespace Spring.Messaging.Core
{
public enum QueueIdentifierType
{
/// <summary>
/// Use a Label to identify the queue, for example new MessageQueue("Label:TheLabel");
/// </summary>
Label,
/// <summary>
/// Use a FormatName to idenitfy the queue,
/// </summary>
FormatName,
MachineName,
Path,
QueueName,
}
}

View File

@@ -0,0 +1,31 @@
#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 Spring.Messaging.Core;
using Spring.Messaging.Support.Converters;
namespace Spring.Messaging.Support.Converters
{
/// <summary>
/// Delegate for creating IMessageConverter instance. Used by <see cref="DefaultMessageQueueFactory"/>
/// to register a creation function with a given name.
/// </summary>
public delegate IMessageConverter MessageConverterCreatorDelegate();
}

View File

@@ -0,0 +1,80 @@
#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 Spring.Objects.Factory.Config;
namespace Spring.Messaging.Support.Converters
{
/// <summary>
/// Internal class to that users can specify a delegate function to register with the application context that
/// will create a IMessageConverter instance easily at runtime.
/// </summary>
/// <author>Mark Pollack</author>
internal class MessageConverterFactoryObject : IConfigurableFactoryObject
{
private IObjectDefinition productTemplate;
private MessageConverterCreatorDelegate messageConverterCreatorDelegate;
public MessageConverterCreatorDelegate MessageConverterCreatorDelegate
{
get { return messageConverterCreatorDelegate; }
set { messageConverterCreatorDelegate = value; }
}
#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
#region IFactoryObject Members
public object GetObject()
{
return MessageConverterCreatorDelegate();
}
public Type ObjectType
{
get { return typeof(IMessageConverter); }
}
public bool IsSingleton
{
get { return false; }
}
#endregion
}
}

View File

@@ -0,0 +1,13 @@
using System.Messaging;
using Spring.Messaging.Core;
namespace Spring.Messaging.Support
{
/// <summary>
/// Delegate for creating MessageQueue instance. Used by <see cref="DefaultMessageQueueFactory"/>
/// to register a creation function with a given name.
/// </summary>
public delegate MessageQueue MessageQueueCreatorDelegate();
}

View File

@@ -25,8 +25,13 @@ using Spring.Objects.Factory.Config;
namespace Spring.Messaging.Support
{
/// <summary>
/// Factory for creating MessageQueues
/// Factory for creating MessageQueues. This factory will create prototype instances, i.e. every call to GetObject
/// will return a new MessageQueue object.
/// </summary>
/// <remarks>All MessageQueue constructor arguments are exposed as properties of the factory object. As this
/// is a <see cref="IConfigurableFactoryObject"/> use the PropertyTemplate property to specify additional
/// configuration of the MessageQueue.
/// </remarks>
/// <author>Mark Pollack</author>
public class MessageQueueFactoryObject : IConfigurableFactoryObject
{
@@ -45,7 +50,25 @@ namespace Spring.Messaging.Support
private bool messageReadPropertyFilterSetDefaults = false;
//myQueue.MessageReadPropertyFilter.SetAll();
private MessageQueueCreatorDelegate messageCreatorDelegate;
/// <summary>
/// Gets or sets an instance of the MessageQueueCreator delegate that will be used to create the
/// MessageQueue object, instead of using the various public properties on this class such
/// as Path, AccessMode, etc. Not intended for end-users but rather as a means to help
/// register MessageQueueFactoryObject at runtime using convenience method on the IMessageQueueFactory.
/// </summary>
/// <remarks>
/// Can also be specifed using an instance of MessageCreatorDelegate. If both are specifed, the
/// Interface implementation has priority.
/// </remarks>
/// <value>The function that is responsbile for creating a message queue.</value>
public MessageQueueCreatorDelegate MessageCreatorDelegate
{
get { return messageCreatorDelegate; }
set { messageCreatorDelegate = value; }
}
/// <summary>
/// Gets or sets the path used to creat DefaultMessageQueue instance.
@@ -139,17 +162,24 @@ namespace Spring.Messaging.Support
/// <returns>A newly configured MessageQueue object</returns>
public object GetObject()
{
MessageQueue.EnableConnectionCache = enableConnectionCache;
MessageQueue mq = new MessageQueue(Path, DenySharedReceive, EnableCache, AccessMode);
if (messageReadPropertyFilterSetDefaults)
if (MessageCreatorDelegate != null)
{
mq.MessageReadPropertyFilter.SetDefaults();
return MessageCreatorDelegate();
}
if (messageReadPropertyFilterSetAll)
else
{
mq.MessageReadPropertyFilter.SetAll();
MessageQueue.EnableConnectionCache = enableConnectionCache;
MessageQueue mq = new MessageQueue(Path, DenySharedReceive, EnableCache, AccessMode);
if (messageReadPropertyFilterSetDefaults)
{
mq.MessageReadPropertyFilter.SetDefaults();
}
if (messageReadPropertyFilterSetAll)
{
mq.MessageReadPropertyFilter.SetAll();
}
return mq;
}
return mq;
}
/// <summary>

View File

@@ -49,7 +49,10 @@
<Link>CommonAssemblyInfo.cs</Link>
</Compile>
<Compile Include="AssemblyInfo.cs" />
<Compile Include="Messaging\Support\Converters\MessageConverterCreatorDelegate.cs" />
<Compile Include="Messaging\Core\DefaultMessageQueueFactory.cs" />
<Compile Include="Messaging\Support\MessageQueueCreatorDelegate.cs" />
<Compile Include="Messaging\Core\QueueIdentifierType.cs" />
<Compile Include="Messaging\Core\IMessageQueueFactory.cs" />
<Compile Include="Messaging\Core\MessagePostProcessorDelegate.cs" />
<Compile Include="Messaging\Listener\AbstractSendToQueueExceptionHandler.cs" />
@@ -78,6 +81,7 @@
<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\MessageConverterFactoryObject.cs" />
<Compile Include="Messaging\Support\Converters\XmlDocumentConverter.cs" />
<Compile Include="Messaging\Support\Converters\XmlMessageConverter.cs" />
<Compile Include="Messaging\Support\MessageQueueFactoryObject.cs" />

View File

@@ -21,6 +21,7 @@
#region Imports
using System;
using System.Messaging;
using System.Transactions;
using NUnit.Framework;
using Spring.Data.Core;
@@ -43,7 +44,46 @@ namespace Spring.Messaging.Core
[TestFixture]
public class MessageQueueTemplateTests : AbstractDependencyInjectionSpringContextTests
{
[Test]
[Test]
public void MessageCreator()
{
MessageQueueTemplate mqt = applicationContext["txqueue"] as MessageQueueTemplate;
Assert.IsNotNull(mqt);
string path = @".\Private$\mlptestqueue";
if (MessageQueue.Exists(path))
{
MessageQueue.Delete(path);
}
MessageQueue.Create(path, true);
mqt.MessageQueueFactory.RegisterMessageQueue("newQueueDefinition", delegate
{
MessageQueue mq = new MessageQueue();
mq.Path = path;
// other properties
return mq;
});
Assert.IsTrue(mqt.MessageQueueFactory.ContainsMessageQueue("newQueueDefinition"));
SendAndReceive("newQueueDefinition",mqt);
SimpleCreator sc = new SimpleCreator();
mqt.MessageQueueFactory.RegisterMessageQueue("fooQueueDefinition", sc.CreateQueue );
}
public class SimpleCreator
{
public MessageQueue CreateQueue()
{
return new MessageQueue();
}
}
[Test]
[ExpectedException(typeof (ArgumentException), ExpectedMessage = "DefaultMessageQueueObjectName is required.")]
public void NoMessageQueueNameSpecified()
{
@@ -86,12 +126,19 @@ namespace Spring.Messaging.Core
{
MessageQueueTemplate q = applicationContext["queue"] as MessageQueueTemplate;
Assert.IsNotNull(q);
ReceiveHelloWorld(q,1);
ReceiveHelloWorld(null,q,1);
}
private static void ReceiveHelloWorld(MessageQueueTemplate q, int index)
private static void ReceiveHelloWorld(string messageQueueObjectName, MessageQueueTemplate q, int index)
{
object o = q.ReceiveAndConvert();
object o = null;
if (messageQueueObjectName == null)
{
o = q.ReceiveAndConvert();
} else
{
o = q.ReceiveAndConvert(messageQueueObjectName);
}
Assert.IsNotNull(o);
string data = o as string;
Assert.IsNotNull(data);
@@ -103,7 +150,7 @@ namespace Spring.Messaging.Core
{
MessageQueueTemplate q = applicationContext["queue"] as MessageQueueTemplate;
Assert.IsNotNull(q);
SendAndRecieve(q);
SendAndReceive(q);
}
[Test]
@@ -111,7 +158,7 @@ namespace Spring.Messaging.Core
{
MessageQueueTemplate q = applicationContext["txqueue"] as MessageQueueTemplate;
Assert.IsNotNull(q);
SendAndRecieve(q);
SendAndReceive(q);
}
[Test]
@@ -120,31 +167,44 @@ namespace Spring.Messaging.Core
MessageQueueTemplate q = applicationContext["txqueue"] as MessageQueueTemplate;
Assert.IsNotNull(q);
SendUsingMessageTxScope(q);
Receive(q);
Receive(null,q);
}
private static void SendAndRecieve(MessageQueueTemplate q)
private static void SendAndReceive(MessageQueueTemplate q)
{
SendUsingMessageTx(q);
Receive(q);
SendAndReceive(null, q);
}
private static void Receive(MessageQueueTemplate q)
private static void SendAndReceive(string messageQueueObjectName, MessageQueueTemplate q)
{
ReceiveHelloWorld(q,1);
ReceiveHelloWorld(q,2);
ReceiveHelloWorld(q,3);
SendUsingMessageTx(messageQueueObjectName, q);
Receive(messageQueueObjectName, q);
}
private static void SendUsingMessageTx(MessageQueueTemplate q)
private static void Receive(string messageQueueObjectName, MessageQueueTemplate q)
{
ReceiveHelloWorld(messageQueueObjectName, q, 1);
ReceiveHelloWorld(messageQueueObjectName, q, 2);
ReceiveHelloWorld(messageQueueObjectName, q, 3);
}
private static void SendUsingMessageTx(string messageQueueObjectName, 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");
if (messageQueueObjectName == null)
{
q.ConvertAndSend("Hello World 1");
q.ConvertAndSend("Hello World 2");
q.ConvertAndSend("Hello World 3");
} else
{
q.ConvertAndSend(messageQueueObjectName, "Hello World 1");
q.ConvertAndSend(messageQueueObjectName, "Hello World 2");
q.ConvertAndSend(messageQueueObjectName, "Hello World 3");
}
return null;
});
}

View File

@@ -34,18 +34,18 @@
</object>
<object id="queue" type="Spring.Messaging.Core.MessageQueueTemplate, Spring.Messaging">
<property name="MessageQueueObjectName" value="testtxqueue"/>
<property name="DefaultMessageQueueObjectName" value="testtxqueue"/>
<property name="MessageConverterObjectName" value="messageConverter"/>
</object>
<object id="queue-noconverter" type="Spring.Messaging.Core.MessageQueueTemplate, Spring.Messaging">
<property name="MessageQueueObjectName" value="testtxqueue"/>
<property name="DefaultMessageQueueObjectName" value="testtxqueue"/>
</object>
<object id="txqueue" type="Spring.Messaging.Core.MessageQueueTemplate, Spring.Messaging">
<property name="MessageQueueObjectName" value="testtxqueue"/>
<property name="DefaultMessageQueueObjectName" value="testtxqueue"/>
<property name="MessageConverterObjectName" value="messageConverter"/>
</object>