diff --git a/src/Spring/Spring.Core/Util/AssertUtils.cs b/src/Spring/Spring.Core/Util/AssertUtils.cs index e4e2e78c..c5394745 100644 --- a/src/Spring/Spring.Core/Util/AssertUtils.cs +++ b/src/Spring/Spring.Core/Util/AssertUtils.cs @@ -191,6 +191,37 @@ namespace Spring.Util { throw new ArgumentException(message, argumentName); } + } + + + /// + /// Assert a boolean expression, throwing ArgumentException + /// if the test result is false. + /// + /// a boolean expression. + /// The exception message to use if the assertion fails. + /// + /// if expression is false + /// + public static void IsTrue(bool expression, string message) + { + if (!expression) + { + throw new ArgumentException(message); + } + } + + /// + /// Assert a boolean expression, throwing ArgumentException + /// if the test result is false. + /// + /// a boolean expression. + /// + /// if expression is false + /// + public static void IsTrue(bool expression) + { + IsTrue(expression, "[Assertion failed] - this expression must be true"); } /// diff --git a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Connections/CachedMessageProducer.cs b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Connections/CachedMessageProducer.cs new file mode 100644 index 00000000..b9238b47 --- /dev/null +++ b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Connections/CachedMessageProducer.cs @@ -0,0 +1,187 @@ +#region License + +/* + * Copyright 2002-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using Apache.NMS; + +namespace Spring.Messaging.Nms.Connection +{ + /// + /// MessageProducer decorator that adapts specific settings + /// to a shared MessageProducer instance underneath. + /// + /// Juergen Hoeller + /// Mark Pollack (.NET) + public class CachedMessageProducer : IMessageProducer + { + private IMessageProducer target; + + private bool disableMessageID; + + private object originalDisableMessageID = null; + + private bool disableMessageTimestamp; + + private object originalDisableMessageTimestamp = null; + + //Not part of NMS spce + //private int deliveryMode; + + private bool persistent; + + private byte priority; + + private TimeSpan timeToLive; + + + public CachedMessageProducer(IMessageProducer target) + { + this.target = target; + } + + + public IMessageProducer Target + { + get { return target; } + } + + public void Send(IMessage message) + { + target.Send(message); + } + + public void Send(IMessage message, bool persistent, byte priority, TimeSpan timeToLive) + { + target.Send(message, persistent, priority, timeToLive); + } + + public void Send(IDestination destination, IMessage message) + { + target.Send(destination, message); + } + + public void Send(IDestination destination, IMessage message, bool persistent, byte priority, TimeSpan timeToLive) + { + target.Send(destination, message, persistent, priority, timeToLive); + } + + #region Odd Message Creationg Methods on IMessageProducer - not in-line with JMS APIs. + public IMessage CreateMessage() + { + return target.CreateMessage(); + } + + public ITextMessage CreateTextMessage() + { + return target.CreateTextMessage(); + } + + public ITextMessage CreateTextMessage(string text) + { + return target.CreateTextMessage(text); + } + + public IMapMessage CreateMapMessage() + { + return target.CreateMapMessage(); + } + + public IObjectMessage CreateObjectMessage(object body) + { + return target.CreateObjectMessage(body); + } + + public IBytesMessage CreateBytesMessage() + { + return target.CreateBytesMessage(); + } + + public IBytesMessage CreateBytesMessage(byte[] body) + { + return target.CreateBytesMessage(body); + } + #endregion + + public bool Persistent + { + get { return persistent; } + set { persistent = value; } + } + + public TimeSpan TimeToLive + { + get { return timeToLive; } + set { timeToLive = value; } + } + + public byte Priority + { + get { return priority; } + set { priority = value;} + } + + public bool DisableMessageID + { + get + { + return disableMessageID; + } + set + { + if (originalDisableMessageID == null) + { + originalDisableMessageID = value; + } + disableMessageID = value; + } + } + + public bool DisableMessageTimestamp + { + get + { + return disableMessageTimestamp; + } + set + { + if (originalDisableMessageTimestamp == null) + { + originalDisableMessageTimestamp = value; + } + disableMessageTimestamp = value; + } + } + + public void Dispose() + { + // It's a cached MessageProducer... reset properties only. + if (originalDisableMessageID != null) + { + target.DisableMessageID = (bool) originalDisableMessageID; + originalDisableMessageID = null; + } + if (originalDisableMessageTimestamp != null) + { + target.DisableMessageTimestamp = (bool) originalDisableMessageTimestamp; + originalDisableMessageTimestamp = null; + } + } + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Connections/CachedSession.cs b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Connections/CachedSession.cs new file mode 100644 index 00000000..6c67e40f --- /dev/null +++ b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Connections/CachedSession.cs @@ -0,0 +1,296 @@ +#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.Collections; +using Apache.NMS; +using Common.Logging; +using Spring.Collections; +using IQueue=Apache.NMS.IQueue; + +namespace Spring.Messaging.Nms.Connection +{ + /// + /// Wrapper for ISession that caches producers and registers itself as available + /// to the session cache when being closed. Generally used for testing purposes or + /// if need to get at the wrapped Session object via the TargetSession property (for + /// vendor specific methods). + /// + /// Juergen Hoeller + /// Mark Pollack + public class CachedSession : IDecoratorSession + { + #region Logging Definition + + private static readonly ILog LOG = LogManager.GetLogger(typeof(CachedSession)); + + #endregion + + private ISession target; + private LinkedList sessionList; + private int sessionCacheSize; + private IDictionary cachedProducers = new Hashtable(); + private IMessageProducer cachedUnspecifiedDestinationMessageProducer; + private bool shouldCacheProducers; + + /// + /// Initializes a new instance of the class. + /// + /// The target session. + /// The session list. + /// Size of the session cache. + /// if set to true to cache message producers. + public CachedSession(ISession targetSession, LinkedList sessionList, int sessionCacheSize, bool cacheProducers) + { + target = targetSession; + this.sessionList = sessionList; + this.sessionCacheSize = sessionCacheSize; + shouldCacheProducers = cacheProducers; + } + + + /// + /// Gets the target, for testing purposes. + /// + /// The target. + public ISession TargetSession + { + get { return target; } + } + + /// + /// Creates the producer, potentially returning a cached instance. + /// + /// A message producer, potentially cached. + public IMessageProducer CreateProducer() + { + if (shouldCacheProducers) + { + if (cachedUnspecifiedDestinationMessageProducer != null) + { + #region Logging + + if (LOG.IsDebugEnabled) + { + LOG.Debug("Found cached MessageProducer for unspecified destination"); + } + + #endregion + } + else + { + cachedUnspecifiedDestinationMessageProducer = target.CreateProducer(); + #region Logging + + if (LOG.IsDebugEnabled) + { + LOG.Debug("Created cached MessageProducer for unspecified destination"); + } + + #endregion + } + return new CachedMessageProducer(cachedUnspecifiedDestinationMessageProducer); + } + else + { + return target.CreateProducer(); + } + } + + /// + /// Creates the producer, potentially returning a cached instance. + /// + /// The destination. + /// + public IMessageProducer CreateProducer(IDestination destination) + { + if (shouldCacheProducers) + { + IMessageProducer producer = (IMessageProducer)cachedProducers[destination]; + if (producer != null) + { + #region Logging + + if (LOG.IsDebugEnabled) + { + LOG.Debug("Found cached MessageProducer for destination [" + destination + "]"); + } + + #endregion + } + else + { + producer = target.CreateProducer(destination); + cachedProducers.Add(destination, producer); + #region Logging + + if (LOG.IsDebugEnabled) + { + LOG.Debug("Created cached MessageProducer for destination [" + destination + "]"); + } + + #endregion + } + return new CachedMessageProducer(producer); + } + else + { + return target.CreateProducer(destination); + } + } + + /// + /// If have not yet reached session cache size, cache the session, otherwise + /// dispose of all cached message producers and close the session. + /// + public void Close() + { + lock (sessionList) + { + if (sessionList.Count < sessionCacheSize) + { //don't pass the call to the underlying target. + if (!sessionList.Contains(this)) + { + sessionList.Add(this); //add to end of linked list. + #region Logging + if (LOG.IsDebugEnabled) + { + LOG.Debug("Returned cached Session: " + target); + } + #endregion + } + } + else + { + foreach (DictionaryEntry entry in cachedProducers) + { + ((IMessageProducer)entry.Value).Dispose(); + } + target.Close(); + if (LOG.IsDebugEnabled) + { + LOG.Debug("Closed cached Session: " + target); + } + } + } + } + + #region Pass through implementations + public IMessageConsumer CreateConsumer(IDestination destination) + { + return target.CreateConsumer(destination); + } + + public IMessageConsumer CreateConsumer(IDestination destination, string selector) + { + return target.CreateConsumer(destination, selector); + } + + public IMessageConsumer CreateConsumer(IDestination destination, string selector, bool noLocal) + { + return target.CreateConsumer(destination, selector, noLocal); + } + + public IMessageConsumer CreateDurableConsumer(ITopic destination, string name, string selector, bool noLocal) + { + return target.CreateDurableConsumer(destination, name, selector, noLocal); + } + + public IQueue GetQueue(string name) + { + return target.GetQueue(name); + } + + public ITopic GetTopic(string name) + { + return target.GetTopic(name); + } + + public ITemporaryQueue CreateTemporaryQueue() + { + return target.CreateTemporaryQueue(); + } + + public ITemporaryTopic CreateTemporaryTopic() + { + return target.CreateTemporaryTopic(); + } + + public IMessage CreateMessage() + { + return target.CreateMessage(); + } + + public ITextMessage CreateTextMessage() + { + return target.CreateTextMessage(); + } + + public ITextMessage CreateTextMessage(string text) + { + return target.CreateTextMessage(text); + } + + public IMapMessage CreateMapMessage() + { + return target.CreateMapMessage(); + } + + public IObjectMessage CreateObjectMessage(object body) + { + return target.CreateObjectMessage(body); + } + + public IBytesMessage CreateBytesMessage() + { + return target.CreateBytesMessage(); + } + + public IBytesMessage CreateBytesMessage(byte[] body) + { + return target.CreateBytesMessage(body); + } + + public void Commit() + { + target.Commit(); + } + + public void Rollback() + { + target.Rollback(); + } + + public bool Transacted + { + get { return target.Transacted; } + } + + public AcknowledgementMode AcknowledgementMode + { + get { return target.AcknowledgementMode; } + } + + public void Dispose() + { + target.Dispose(); + } + #endregion + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Connections/CachingConnectionFactory.cs b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Connections/CachingConnectionFactory.cs new file mode 100644 index 00000000..b27ec9d4 --- /dev/null +++ b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Connections/CachingConnectionFactory.cs @@ -0,0 +1,179 @@ +#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.Collections; +using Apache.NMS; +using Common.Logging; +using Spring.Collections; +using Spring.Util; +using IQueue=Apache.NMS.IQueue; + +namespace Spring.Messaging.Nms.Connection +{ + /// + /// subclass that adds + /// ISession and IMessageProducer caching. This ConnectionFactory + /// also switches the ReconnectOnException property to true + /// by default, allowing for automatic recovery of the underlying + /// Connection. + /// + /// + /// By default, only one single Session will be cached, with further requested + /// Sessions being created and disposed on demand. Consider raising the + /// SessionCacheSize property in case of a high-concurrency environment. + /// + /// Juergen Hoeller + /// Mark Pollack + public class CachingConnectionFactory : SingleConnectionFactory + { + #region Logging Definition + + private static readonly ILog LOG = LogManager.GetLogger(typeof(CachingConnectionFactory)); + + #endregion + + private int sessionCacheSize = 1; + + private bool cacheProducers = true; + + private IDictionary cachedSessions = new Hashtable(); + + + /// + /// Initializes a new instance of the class. + /// and sets the ReconnectOnException to true + /// + public CachingConnectionFactory() + { + ReconnectOnException = true; + } + + + /// + /// Gets or sets the size of the session cache. + /// + /// + /// This cache size is the maximum limit for the number of cached Sessions + /// per session acknowledgement type (auto, client, dups_ok, transacted). + /// As a consequence, the actual number of cached Sessions may be up to + /// four times as high as the specified value - in the unlikely case + /// of mixing and matching different acknowledgement types. + /// + /// Default is 1: caching a single Session, (re-)creating further ones on + /// demand. Specify a number like 10 if you'd like to raise the number of cached + /// Sessions; that said, 1 may be sufficient for low-concurrency scenarios. + /// + /// + /// The size of the session cache. + public int SessionCacheSize + { + get { return sessionCacheSize; } + set + { + AssertUtils.IsTrue(value >= 1, "Session cache size must be 1 or higher"); + sessionCacheSize = value; + } + } + + + /// + /// Gets or sets a value indicating whether to cache MessageProducers per + /// Session instance. (more specifically: one MessageProducer per Destination + /// and Session). + /// + /// + /// Default is "true". Switch this to "false" in order to recreate, + /// MessageProducers on demand. + /// + /// + /// true if should cache message producers; otherwise, false. + public bool CacheProducers + { + get { return cacheProducers; } + set { cacheProducers = value; } + } + + /// + /// Resets the Session cache as well as resetting the connection. + /// + public override void ResetConnection() + { + lock (cachedSessions) + { + cachedSessions.Clear(); + } + base.ResetConnection(); + } + + /// + /// Obtaining a cached Session. + /// + /// The connection to operate on. + /// The session ack mode. + /// The Session to use + /// + public override ISession GetSession(IConnection con, AcknowledgementMode mode) + { + LinkedList sessionList; + lock (cachedSessions) + { + sessionList = (LinkedList) cachedSessions[mode]; + if (sessionList == null) + { + sessionList = new LinkedList(); + cachedSessions.Add(mode, sessionList); + } + } + + ISession session = null; + lock (sessionList) + { + if (sessionList.Count > 0) + { + session = (ISession) sessionList[0]; + sessionList.RemoveAt(0); + } + } + if (session != null) + { + if (LOG.IsDebugEnabled) + { + LOG.Debug("Found cached Session for mode " + mode + ": " + session); + } + } else + { + ISession targetSession = con.CreateSession(mode); + session = GetCachedSessionWrapper(targetSession, sessionList); + if (LOG.IsDebugEnabled) + { + LOG.Debug("Created cached Session for mode " + mode + ": " + session); + } + } + return session; + } + + protected virtual ISession GetCachedSessionWrapper(ISession targetSession, LinkedList sessionList) + { + return new CachedSession(targetSession, sessionList, SessionCacheSize, CacheProducers); + } + } + + +} \ No newline at end of file diff --git a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Connections/ChainedExceptionListener.cs b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Connections/ChainedExceptionListener.cs new file mode 100644 index 00000000..30889c66 --- /dev/null +++ b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Connections/ChainedExceptionListener.cs @@ -0,0 +1,72 @@ +#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 Spring.Util; + +namespace Spring.Messaging.Nms.Connection +{ + /// + /// Implementation of Spring IExceptionListener interface that supports + /// chaining allowing the addition of multiple ExceptionListener instances in order. + /// + /// Juergen Hoeller + /// Mark Pollack (.NET) + public class ChainedExceptionListener : IExceptionListener + { + private ArrayList listeners = new ArrayList(2); + + /// + /// Adds the exception listener to the chain + /// + /// The listener. + public void AddListener(IExceptionListener listener) + { + AssertUtils.ArgumentNotNull(listener, "listener", "ExceptionListener must not be null"); + listeners.Add(listener); + } + + /// + /// Called when an exception occurs in message processing. + /// + /// The exception. + public void OnException(Exception exception) + { + foreach (IExceptionListener listener in listeners) + { + listener.OnException(exception); + } + } + + /// + /// Gets the exception listeners as an array. + /// + /// The exception listeners. + public IExceptionListener[] Listeners + { + get + { + return (IExceptionListener[]) listeners.ToArray(typeof (IExceptionListener)); + } + } + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Connections/ConnectionFactoryUtils.cs b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Connections/ConnectionFactoryUtils.cs index c231b730..b14b04df 100644 --- a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Connections/ConnectionFactoryUtils.cs +++ b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Connections/ConnectionFactoryUtils.cs @@ -24,7 +24,7 @@ using Common.Logging; using Spring.Transaction.Support; using Spring.Util; -namespace Spring.Messaging.Nms.IConnections +namespace Spring.Messaging.Nms.Connection { /// Helper class for obtaining transactional NMS resources /// for a given IConnectionFactory. @@ -340,7 +340,6 @@ namespace Spring.Messaging.Nms.IConnections } } - //TODO bring in new Spring.Data library to Integration project which has this method in interface. public override void AfterCommit() { if (transacted) diff --git a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Connections/IDecoratorSession.cs b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Connections/IDecoratorSession.cs new file mode 100644 index 00000000..8e705824 --- /dev/null +++ b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Connections/IDecoratorSession.cs @@ -0,0 +1,40 @@ +#region License + +/* + * Copyright 2002-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using Apache.NMS; + +namespace Spring.Messaging.Nms.Connection +{ + /// + /// Subinterface of ISession to be implemented by + /// implementations that wrap an ISession to provide added + /// functionality. Allows access to the the underlying target Session. + /// + /// Mark Pollack + public interface IDecoratorSession : ISession + { + /// + /// Gets the target session of the decorator. + /// This will typically be the native provider Session or a wrapper from a session pool. + /// + /// The underlying session, never null + ISession TargetSession { get; } + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Connections/ISmartConnectionFactory.cs b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Connections/ISmartConnectionFactory.cs index 67427edd..2911c003 100644 --- a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Connections/ISmartConnectionFactory.cs +++ b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Connections/ISmartConnectionFactory.cs @@ -22,7 +22,7 @@ using Apache.NMS; -namespace Spring.Messaging.Nms.IConnections +namespace Spring.Messaging.Nms.Connection { /// /// Extension of the IConnectionFactory interface, diff --git a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Connections/NmsResourceHolder.cs b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Connections/NmsResourceHolder.cs index fcf816af..fa0b9006 100644 --- a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Connections/NmsResourceHolder.cs +++ b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Connections/NmsResourceHolder.cs @@ -26,7 +26,7 @@ using Spring.Transaction.Support; using Spring.Util; using Apache.NMS; -namespace Spring.Messaging.Nms.IConnections +namespace Spring.Messaging.Nms.Connection { /// IConnection holder, wrapping a NMS IConnection and a NMS ISession. /// NmsTransactionManager binds instances of this class to the thread, diff --git a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Connections/NmsTransactionManager.cs b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Connections/NmsTransactionManager.cs index c1fcb32c..b3959c08 100644 --- a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Connections/NmsTransactionManager.cs +++ b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Connections/NmsTransactionManager.cs @@ -8,7 +8,7 @@ using Spring.Objects.Factory; using Spring.Transaction; using Spring.Transaction.Support; -namespace Spring.Messaging.Nms.IConnections +namespace Spring.Messaging.Nms.Connection { /// /// A implementation diff --git a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Connections/SingleConnectionFactory.cs b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Connections/SingleConnectionFactory.cs index 0cbe336b..6b602a71 100644 --- a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Connections/SingleConnectionFactory.cs +++ b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Connections/SingleConnectionFactory.cs @@ -24,9 +24,9 @@ using Common.Logging; using Spring.Objects.Factory; using Spring.Util; -namespace Spring.Messaging.Nms.IConnections +namespace Spring.Messaging.Nms.Connection { - public class SingleConnectionFactory : IConnectionFactory, IInitializingObject, IDisposable + public class SingleConnectionFactory : IConnectionFactory, IExceptionListener, IInitializingObject, IDisposable { #region Logging Definition @@ -34,11 +34,13 @@ namespace Spring.Messaging.Nms.IConnections #endregion + #region Fields + private IConnectionFactory targetConnectionFactory; private string clientId; - private ExceptionListener exceptionListenerDelegate; + private IExceptionListener exceptionListener; private bool reconnectOnException = false; @@ -57,6 +59,9 @@ namespace Spring.Messaging.Nms.IConnections /// private object connectionMonitor = new object(); + #endregion + + #region Constructors /// /// Initializes a new instance of the class. @@ -73,9 +78,9 @@ namespace Spring.Messaging.Nms.IConnections /// The single Connection. public SingleConnectionFactory(IConnection target) { - AssertUtils.ArgumentNotNull(target, "connection", "Target Connection must not be null"); + AssertUtils.ArgumentNotNull(target, "connection", "TargetSession Connection must not be null"); this.target = target; - connection = GetSharedConnection(target); + connection = GetSharedConnection(this, target); } @@ -87,10 +92,14 @@ namespace Spring.Messaging.Nms.IConnections public SingleConnectionFactory(IConnectionFactory targetConnectionFactory) { AssertUtils.ArgumentNotNull(targetConnectionFactory, "targetConnectionFactory", - "Target ConnectionFactory must not be null"); + "TargetSession ConnectionFactory must not be null"); this.targetConnectionFactory = targetConnectionFactory; } + #endregion + + #region Properties + /// /// Gets or sets the target connection factory which will be used to create a single /// connection. @@ -118,18 +127,12 @@ namespace Spring.Messaging.Nms.IConnections } - /// - /// Gets or sets the exception listener delegate that should be registered with - /// the single connection created by this factory. - /// - /// The exception listener delegate. - public ExceptionListener ExceptionListenerDelegate + public IExceptionListener ExceptionListener { - get { return exceptionListenerDelegate; } - set { exceptionListenerDelegate = value; } + get { return exceptionListener; } + set { exceptionListener = value; } } - /// /// Gets or sets a value indicating whether the single Connection /// should be reset (to be subsequently renewed) when a NMSException @@ -155,6 +158,8 @@ namespace Spring.Messaging.Nms.IConnections set { reconnectOnException = value; } } + #endregion + #region IConnectionFactory Members public IConnection CreateConnection() @@ -169,6 +174,13 @@ namespace Spring.Messaging.Nms.IConnections } } + public IConnection CreateConnection(string userName, string password) + { + throw new InvalidOperationException("SingleConnectionFactory does not support custom username and password."); + } + + #endregion + public void InitConnection() { if (TargetConnectionFactory == null) @@ -188,34 +200,61 @@ namespace Spring.Messaging.Nms.IConnections { LOG.Info("Established shared NMS Connection: " + this.target); } - this.connection = GetSharedConnection(this.target); + this.connection = GetSharedConnection(this, target); } } + /// + /// Exception listener callback that renews the underlying single Connection. + /// + /// The exception from the messaging infrastructure. + public void OnException(Exception exception) + { + ResetConnection(); + } + protected virtual void PrepareConnection(IConnection con) { if (ClientId != null) { con.ClientId = ClientId; } - if (ExceptionListenerDelegate != null || ReconnectOnException) + if (ExceptionListener != null || ReconnectOnException) { - ExceptionListener listenerToUse = ExceptionListenerDelegate; + IExceptionListener listenerToUse = ExceptionListener; if (ReconnectOnException) { - InternalChainedExceptionListenerSupport chained = new InternalChainedExceptionListenerSupport(this, listenerToUse); + InternalChainedExceptionListener chained = new InternalChainedExceptionListener(this, listenerToUse); con.ExceptionListener += chained.OnException; } - + else + { + if (ExceptionListener != null) + { + con.ExceptionListener += ExceptionListener.OnException; + } + } } } + /// + /// Template method for obtaining a (potentially cached) Session. + /// + /// The connection to operate on. + /// The session ack mode. + /// the Session to use, or null to indicate + /// creation of a default Session + public virtual ISession GetSession(IConnection con, AcknowledgementMode mode) + { + return null; + } + protected virtual IConnection DoCreateConnection() { return TargetConnectionFactory.CreateConnection(); } - private void CloseConnection(IConnection con) + protected virtual void CloseConnection(IConnection con) { try { @@ -232,13 +271,6 @@ namespace Spring.Messaging.Nms.IConnections } } - public IConnection CreateConnection(string userName, string password) - { - throw new NotImplementedException(); - } - - #endregion - #region IInitializingObject Members public void AfterPropertiesSet() @@ -251,14 +283,12 @@ namespace Spring.Messaging.Nms.IConnections #endregion - #region IDisposable Members - public void Dispose() { ResetConnection(); } - public void ResetConnection() + public virtual void ResetConnection() { lock (connectionMonitor) { @@ -271,62 +301,79 @@ namespace Spring.Messaging.Nms.IConnections } } - #endregion - - protected virtual IConnection GetSharedConnection(IConnection target) + protected virtual IConnection GetSharedConnection(SingleConnectionFactory singleConnectionFactory, IConnection target) { lock (connectionMonitor) { - return new CloseSupressingConnection(target); + return new CloseSupressingConnection(singleConnectionFactory, target); } } } - internal class InternalChainedExceptionListenerSupport + internal class InternalChainedExceptionListener : ChainedExceptionListener, IExceptionListener { - private SingleConnectionFactory factory; - private ExceptionListener listenerToUse; - public InternalChainedExceptionListenerSupport(SingleConnectionFactory factory, ExceptionListener listenerToUse) + private IExceptionListener userListener; + public InternalChainedExceptionListener(IExceptionListener internalListener, IExceptionListener userListener) { - this.factory = factory; - this.listenerToUse = listenerToUse; + AddListener(internalListener); + if (userListener != null) + { + AddListener(userListener); + this.userListener = userListener; + } } - public void OnException(Exception exception) + public IExceptionListener UserListener { - //TODO exception mgmt. + get { return userListener; } } } internal class CloseSupressingConnection : IConnection { private IConnection target; + private SingleConnectionFactory singleConnectionFactory; - public CloseSupressingConnection(IConnection target) + public CloseSupressingConnection(SingleConnectionFactory singleConnectionFactory, IConnection target) { this.target = target; + this.singleConnectionFactory = singleConnectionFactory; } + public void Close() + { + // don't pass the call to the target. + } + + public void Stop() + { + //don't pass the call to the target. + } + + public ISession CreateSession() + { + return CreateSession(AcknowledgementMode.AutoAcknowledge); + } + + public ISession CreateSession(AcknowledgementMode acknowledgementMode) + { + ISession session = singleConnectionFactory.GetSession(target, acknowledgementMode); + if (session != null) + { + return session; + } + return target.CreateSession(); + } + + #region Pass through implementations to the target connection + + public event ExceptionListener ExceptionListener { add { target.ExceptionListener += value; } remove { target.ExceptionListener -= value; } } - public ISession CreateSession() - { - return target.CreateSession(); - } - - public ISession CreateSession(AcknowledgementMode acknowledgementMode) - { - return target.CreateSession(acknowledgementMode); - } - - public void Close() - { - // don't pass the call to the target. - } public AcknowledgementMode AcknowledgementMode { @@ -354,10 +401,7 @@ namespace Spring.Messaging.Nms.IConnections { get { return target.IsStarted; } } + #endregion - public void Stop() - { - //don't pass the call to the target. - } } } \ No newline at end of file diff --git a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Connections/SynchedLocalTransactionFailedException.cs b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Connections/SynchedLocalTransactionFailedException.cs index fe74651c..ce3ebcf4 100644 --- a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Connections/SynchedLocalTransactionFailedException.cs +++ b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Connections/SynchedLocalTransactionFailedException.cs @@ -19,25 +19,19 @@ #endregion using System; -using System.Runtime.Serialization; +using Apache.NMS; -namespace Spring.Messaging.Nms.IConnections -{ - //TODO should we have a generic spring exception for NMS? - +namespace Spring.Messaging.Nms.Connection +{ /// Exception thrown when a synchronized local transaction failed to complete /// (after the main transaction has already completed). /// /// Jergen Hoeller /// Mark Pollack (.NET) [Serializable] - public class SynchedLocalTransactionFailedException : ApplicationException + public class SynchedLocalTransactionFailedException : NMSException { #region Constructor (s) / Destructor - /// Creates a new instance of the SynchedLocalTransactionFailedException class. - public SynchedLocalTransactionFailedException() - { - } /// /// Creates a new instance of the SynchedLocalTransactionFailedException class. with the specified message. @@ -64,22 +58,6 @@ namespace Spring.Messaging.Nms.IConnections { } - /// - /// Creates a new instance of the SynchedLocalTransactionFailedException class. - /// - /// - /// The - /// that holds the serialized object data about the exception being thrown. - /// - /// - /// The - /// that contains contextual information about the source or destination. - /// - protected SynchedLocalTransactionFailedException( - SerializationInfo info, StreamingContext context) - : base (info, context) - { - } #endregion } } diff --git a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/IExceptionListener.cs b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/IExceptionListener.cs new file mode 100644 index 00000000..35150487 --- /dev/null +++ b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/IExceptionListener.cs @@ -0,0 +1,33 @@ +#region License + +/* + * Copyright 2002-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; + +namespace Spring.Messaging.Nms +{ + /// + /// Exception handler for exceptions from the messaging infrastrcture. + /// + /// Mark Pollack + public interface IExceptionListener + { + void OnException(Exception exception); + } +} \ No newline at end of file diff --git a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Listener/AbstractMessageListenerContainer.cs b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Listener/AbstractMessageListenerContainer.cs index 2a645f77..318fa1b0 100644 --- a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Listener/AbstractMessageListenerContainer.cs +++ b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/Listener/AbstractMessageListenerContainer.cs @@ -1,7 +1,7 @@ using System; using System.Collections; using Spring.Context; -using Spring.Messaging.Nms.IConnections; +using Spring.Messaging.Nms.Connection; using Spring.Messaging.Nms.Support; using Spring.Messaging.Nms.Support.IDestinations; using Spring.Util; diff --git a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/NmsTemplate.cs b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/NmsTemplate.cs index 9b6afca8..94c989b0 100644 --- a/src/Spring/Spring.Messaging.Nms/Messaging/Nms/NmsTemplate.cs +++ b/src/Spring/Spring.Messaging.Nms/Messaging/Nms/NmsTemplate.cs @@ -19,7 +19,7 @@ #endregion using System; -using Spring.Messaging.Nms.IConnections; +using Spring.Messaging.Nms.Connection; using Spring.Messaging.Nms.Support; using Spring.Messaging.Nms.Support.Converter; using Spring.Messaging.Nms.Support.IDestinations; diff --git a/src/Spring/Spring.Messaging.Nms/Spring.Messaging.Nms.2005.csproj b/src/Spring/Spring.Messaging.Nms/Spring.Messaging.Nms.2005.csproj index e61716dd..ced3dbbf 100644 --- a/src/Spring/Spring.Messaging.Nms/Spring.Messaging.Nms.2005.csproj +++ b/src/Spring/Spring.Messaging.Nms/Spring.Messaging.Nms.2005.csproj @@ -47,12 +47,18 @@ + + + + + + diff --git a/test/Spring/Spring.Core.Tests/Util/AssertUtilsTests.cs b/test/Spring/Spring.Core.Tests/Util/AssertUtilsTests.cs index cdfdb5d1..129a4b51 100644 --- a/test/Spring/Spring.Core.Tests/Util/AssertUtilsTests.cs +++ b/test/Spring/Spring.Core.Tests/Util/AssertUtilsTests.cs @@ -35,6 +35,32 @@ namespace Spring.Util [TestFixture] public sealed class AssertUtilsTests { + [Test] + [ExpectedException(typeof(ArgumentException),ExpectedMessage = "foo")] + public void IsTrueWithMesssage() + { + AssertUtils.IsTrue(false,"foo"); + } + + [Test] + public void IsTrueWithMessageValidExpression() + { + AssertUtils.IsTrue(true, "foo"); + } + + [Test] + [ExpectedException(typeof(ArgumentException), ExpectedMessage = "[Assertion failed] - this expression must be true")] + public void IsTrue() + { + AssertUtils.IsTrue(false); + } + + [Test] + public void IsTrueValidExpression() + { + AssertUtils.IsTrue(true); + } + [Test] [ExpectedException(typeof(InvalidOperationException))] public void StateTrue() diff --git a/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Connections/CachingConnectionFactoryTests.cs b/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Connections/CachingConnectionFactoryTests.cs new file mode 100644 index 00000000..3d1d65cb --- /dev/null +++ b/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Connections/CachingConnectionFactoryTests.cs @@ -0,0 +1,221 @@ +#region License + +/* + * Copyright © 2002-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +#region Imports + +using Apache.NMS; +using NUnit.Framework; +using Rhino.Mocks; + +#endregion + +namespace Spring.Messaging.Nms.Connection +{ + /// + /// This class contains tests for + /// + /// Mark Pollack + /// $Id:$ + [TestFixture] + public class CachingConnectionFactoryTests + { + private MockRepository mocks; + + [SetUp] + public void Setup() + { + mocks = new MockRepository(); + } + + [Test] + public void CachedSession() + { + IConnectionFactory connectionFactory = (IConnectionFactory)mocks.CreateMock(typeof(IConnectionFactory)); + IConnection connection = new TestConnection(); + + Expect.Call(connectionFactory.CreateConnection()).Return(connection).Repeat.Once(); + + mocks.ReplayAll(); + + CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory(); + cachingConnectionFactory.TargetConnectionFactory = connectionFactory; + + IConnection con1 = cachingConnectionFactory.CreateConnection(); + + ISession session1 = con1.CreateSession(AcknowledgementMode.Transactional); + TestSession testSession = GetTestSession(session1); + Assert.AreEqual(1, testSession.CreatedCount); + Assert.AreEqual(0, testSession.CloseCount); + + + session1.Close(); // won't close, will put in session cache. + Assert.AreEqual(0, testSession.CloseCount); + + ISession session2 = con1.CreateSession(AcknowledgementMode.Transactional); + + + TestSession testSession2 = GetTestSession(session2); + + + Assert.AreSame(testSession, testSession2); + + Assert.AreEqual(1, testSession.CreatedCount); + Assert.AreEqual(0, testSession.CloseCount); + + mocks.VerifyAll(); + + //don't explicitly call close on + } + + private static TestSession GetTestSession(ISession session1) + { + CachedSession cachedSession = session1 as CachedSession; + Assert.IsNotNull(cachedSession); + TestSession testSession = cachedSession.TargetSession as TestSession; + Assert.IsNotNull(testSession); + return testSession; + } + + [Test] + public void CachedSessionTwoRequests() + { + IConnectionFactory connectionFactory = (IConnectionFactory)mocks.CreateMock(typeof(IConnectionFactory)); + IConnection connection = new TestConnection(); + + Expect.Call(connectionFactory.CreateConnection()).Return(connection).Repeat.Once(); + + mocks.ReplayAll(); + + CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory(); + cachingConnectionFactory.TargetConnectionFactory = connectionFactory; + IConnection con1 = cachingConnectionFactory.CreateConnection(); + + ISession session1 = con1.CreateSession(AcknowledgementMode.Transactional); + TestSession testSession1 = GetTestSession(session1); + Assert.AreEqual(1, testSession1.CreatedCount); + Assert.AreEqual(0, testSession1.CloseCount); + + + //will create a new one, not in the cache. + ISession session2 = con1.CreateSession(AcknowledgementMode.Transactional); + TestSession testSession2 = GetTestSession(session2); + Assert.AreEqual(1, testSession2.CreatedCount); + Assert.AreEqual(0, testSession2.CloseCount); + + Assert.AreNotSame(testSession1, testSession2); + + Assert.AreNotSame(session1, session2); + + session1.Close(); // will be put in the cache + + ISession session3 = con1.CreateSession(AcknowledgementMode.Transactional); + TestSession testSession3 = GetTestSession(session3); + Assert.AreSame(testSession1, testSession3); + Assert.AreSame(session1, session3); + Assert.AreEqual(1, testSession1.CreatedCount); + Assert.AreEqual(0, testSession1.CloseCount); + + mocks.VerifyAll(); + + + } + + /// + /// Tests that the same underlying instance of the message producer is returned after + /// creating a session, creating the producer (A), closing the session, and creating another + /// producer (B). Assert that (A)=(B). + /// + [Test] + public void CachedMessageProducer() + { + IConnectionFactory connectionFactory = (IConnectionFactory)mocks.CreateMock(typeof(IConnectionFactory)); + IConnection connection = new TestConnection(); + + Expect.Call(connectionFactory.CreateConnection()).Return(connection).Repeat.Once(); + + mocks.ReplayAll(); + + + CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory(); + cachingConnectionFactory.TargetConnectionFactory = connectionFactory; + IConnection con1 = cachingConnectionFactory.CreateConnection(); + + ISession sessionA = con1.CreateSession(AcknowledgementMode.Transactional); + IMessageProducer producerA = sessionA.CreateProducer(); + TestMessageProducer tmpA = GetTestMessageProducer(producerA); + + sessionA.Close(); + + ISession sessionB = con1.CreateSession(AcknowledgementMode.Transactional); + IMessageProducer producerB = sessionB.CreateProducer(); + TestMessageProducer tmpB = GetTestMessageProducer(producerB); + + Assert.AreSame(tmpA, tmpB); + + mocks.VerifyAll(); + } + + [Test] + public void CachedMessageProducerTwoRequests() + { + IConnectionFactory connectionFactory = (IConnectionFactory)mocks.CreateMock(typeof(IConnectionFactory)); + IConnection connection = new TestConnection(); + + Expect.Call(connectionFactory.CreateConnection()).Return(connection).Repeat.Once(); + + mocks.ReplayAll(); + + + CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory(); + cachingConnectionFactory.TargetConnectionFactory = connectionFactory; + IConnection con1 = cachingConnectionFactory.CreateConnection(); + + ISession sessionA = con1.CreateSession(AcknowledgementMode.Transactional); + IMessageProducer producerA = sessionA.CreateProducer(); + TestMessageProducer tmpA = GetTestMessageProducer(producerA); + + + ISession sessionB = con1.CreateSession(AcknowledgementMode.Transactional); + IMessageProducer producerB = sessionB.CreateProducer(); + TestMessageProducer tmpB = GetTestMessageProducer(producerB); + + Assert.AreNotSame(tmpA, tmpB); + + sessionA.Close(); + + ISession sessionC = con1.CreateSession(AcknowledgementMode.Transactional); + IMessageProducer producerC = sessionC.CreateProducer(); + TestMessageProducer tmpC = GetTestMessageProducer(producerC); + + Assert.AreSame(tmpA, tmpC); + + mocks.VerifyAll(); + } + + private static TestMessageProducer GetTestMessageProducer(IMessageProducer producer1) + { + CachedMessageProducer cmp1 = producer1 as CachedMessageProducer; + Assert.IsNotNull(cmp1); + TestMessageProducer tmp1 = cmp1.Target as TestMessageProducer; + Assert.IsNotNull(tmp1); + return tmp1; + } + } +} \ No newline at end of file diff --git a/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Connections/NmsTransactionManagerTests.cs b/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Connections/NmsTransactionManagerTests.cs index 6633e7e9..026f1683 100644 --- a/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Connections/NmsTransactionManagerTests.cs +++ b/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Connections/NmsTransactionManagerTests.cs @@ -24,13 +24,13 @@ using System; using Apache.NMS; using NUnit.Framework; using Rhino.Mocks; -using Spring.Messaging.Nms.IConnections; +using Spring.Messaging.Nms.Connection; using Spring.Transaction; using Spring.Transaction.Support; #endregion -namespace Spring.Messaging.Nms.Connections +namespace Spring.Messaging.Nms.Connection { /// /// This class contains tests for diff --git a/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Connections/SingleConnectionFactoryTests.cs b/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Connections/SingleConnectionFactoryTests.cs new file mode 100644 index 00000000..e2da9214 --- /dev/null +++ b/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Connections/SingleConnectionFactoryTests.cs @@ -0,0 +1,235 @@ +#region License + +/* + * Copyright © 2002-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +#region Imports + +using System; +using Apache.NMS; +using NUnit.Framework; +using Rhino.Mocks; +using Spring.Messaging.Nms.Connection; + +#endregion + +namespace Spring.Messaging.Nms.Connection +{ + /// + /// This class contains tests for the SingleConnectionFactory + /// + /// Mark Pollack + [TestFixture] + public class SingleConnectionFactoryTests + { + private MockRepository mocks; + + [SetUp] + public void Setup() + { + mocks = new MockRepository(); + } + + + [Test] + public void UsingConnection() + { + IConnection connection = (IConnection)mocks.CreateMock(typeof(IConnection)); + + connection.Start(); + LastCall.On(connection).Repeat.Twice(); + connection.Stop(); + LastCall.On(connection).Repeat.Once(); + connection.Close(); + LastCall.On(connection).Repeat.Once(); + + mocks.ReplayAll(); + + SingleConnectionFactory scf = new SingleConnectionFactory(connection); + IConnection con1 = scf.CreateConnection(); + con1.Start(); + con1.Stop(); // should be ignored + con1.Close(); // should be ignored + IConnection con2 = scf.CreateConnection(); + con2.Start(); + con2.Stop(); // should be ignored + con2.Close(); // should be ignored. + scf.Dispose(); + + mocks.VerifyAll(); + } + + [Test] + public void UsingConnectionFactory() + { + IConnectionFactory connectionFactory = (IConnectionFactory)mocks.CreateMock(typeof(IConnectionFactory)); + IConnection connection = (IConnection)mocks.CreateMock(typeof(IConnection)); + + Expect.Call(connectionFactory.CreateConnection()).Return(connection).Repeat.Once(); + connection.Start(); + LastCall.On(connection).Repeat.Twice(); + connection.Stop(); + LastCall.On(connection).Repeat.Once(); + connection.Close(); + LastCall.On(connection).Repeat.Once(); + + + mocks.ReplayAll(); + + SingleConnectionFactory scf = new SingleConnectionFactory(connectionFactory); + IConnection con1 = scf.CreateConnection(); + con1.Start(); + con1.Close(); // should be ignored + IConnection con2 = scf.CreateConnection(); + con2.Start(); + con2.Close(); //should be ignored + scf.Dispose(); //should trigger actual close + + mocks.VerifyAll(); + + } + + [Test] + public void UsingConnectionFactoryAndClientId() + { + IConnectionFactory connectionFactory = (IConnectionFactory)mocks.CreateMock(typeof(IConnectionFactory)); + IConnection connection = (IConnection)mocks.CreateMock(typeof(IConnection)); + + Expect.Call(connectionFactory.CreateConnection()).Return(connection).Repeat.Once(); + connection.ClientId = "MyId"; + LastCall.On(connection).Repeat.Once(); + connection.Start(); + LastCall.On(connection).Repeat.Twice(); + connection.Stop(); + LastCall.On(connection).Repeat.Once(); + connection.Close(); + LastCall.On(connection).Repeat.Once(); + + mocks.ReplayAll(); + + SingleConnectionFactory scf = new SingleConnectionFactory(connectionFactory); + scf.ClientId = "MyId"; + IConnection con1 = scf.CreateConnection(); + con1.Start(); + con1.Close(); // should be ignored + IConnection con2 = scf.CreateConnection(); + con2.Start(); + con2.Close(); // should be ignored + scf.Dispose(); // should trigger actual close + + mocks.VerifyAll(); + + + } + + + [Test] + public void UsingConnectionFactoryAndExceptionListener() + { + IConnectionFactory connectionFactory = (IConnectionFactory)mocks.CreateMock(typeof(IConnectionFactory)); + IConnection connection = (IConnection)mocks.CreateMock(typeof(IConnection)); + + + IExceptionListener listener = new ChainedExceptionListener(); + Expect.Call(connectionFactory.CreateConnection()).Return(connection).Repeat.Once(); + connection.ExceptionListener += listener.OnException; + LastCall.On(connection).IgnoreArguments(); + + connection.Start(); + LastCall.On(connection).Repeat.Twice(); + connection.Stop(); + LastCall.On(connection).Repeat.Once(); + connection.Close(); + LastCall.On(connection).Repeat.Once(); + + mocks.ReplayAll(); + + SingleConnectionFactory scf = new SingleConnectionFactory(connectionFactory); + scf.ExceptionListener = listener; + IConnection con1 = scf.CreateConnection(); + + //can't look at invocation list on event ...grrr. + + con1.Start(); + con1.Stop(); // should be ignored + con1.Close(); // should be ignored + IConnection con2 = scf.CreateConnection(); + con2.Start(); + con2.Stop(); + con2.Close(); + scf.Dispose(); + + mocks.VerifyAll(); + } + + [Test] + public void UsingConnectionFactoryAndReconnectOnException() + { + IConnectionFactory connectionFactory = (IConnectionFactory)mocks.CreateMock(typeof(IConnectionFactory)); + TestConnection con = new TestConnection(); + + Expect.Call(connectionFactory.CreateConnection()).Return(con).Repeat.Twice(); + + mocks.ReplayAll(); + + SingleConnectionFactory scf = new SingleConnectionFactory(connectionFactory); + scf.ReconnectOnException = true; + IConnection con1 = scf.CreateConnection(); + + con1.Start(); + con.FireExcpetionEvent(new NMSException("")); + IConnection con2 = scf.CreateConnection(); + con2.Start(); + scf.Dispose(); + + mocks.VerifyAll(); + + Assert.AreEqual(2, con.StartCount); + Assert.AreEqual(2, con.CloseCount); + } + + [Test] + public void UsingConnectionFactoryAndExceptionListenerAndReconnectOnException() + { + IConnectionFactory connectionFactory = (IConnectionFactory)mocks.CreateMock(typeof(IConnectionFactory)); + TestConnection con = new TestConnection(); + TestExceptionListener listener = new TestExceptionListener(); + + Expect.Call(connectionFactory.CreateConnection()).Return(con).Repeat.Twice(); + + mocks.ReplayAll(); + + SingleConnectionFactory scf = new SingleConnectionFactory(connectionFactory); + scf.ExceptionListener = listener; + scf.ReconnectOnException = true; + IConnection con1 = scf.CreateConnection(); + //Assert.AreSame(listener, ); + con1.Start(); + con.FireExcpetionEvent(new NMSException("")); + IConnection con2 = scf.CreateConnection(); + con2.Start(); + scf.Dispose(); + + mocks.VerifyAll(); + + Assert.AreEqual(2, con.StartCount); + Assert.AreEqual(2, con.CloseCount); + Assert.AreEqual(1, listener.Count); + } + } +} \ No newline at end of file diff --git a/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Connections/TestConnection.cs b/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Connections/TestConnection.cs new file mode 100644 index 00000000..98ecd5e8 --- /dev/null +++ b/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Connections/TestConnection.cs @@ -0,0 +1,82 @@ +using System; +using Apache.NMS; + +namespace Spring.Messaging.Nms.Connection +{ + public class TestConnection : IConnection + { + private int startCount; + private int closeCount; + private int createSessionCount; + private int closeSessionCount; + + + public int StartCount + { + get { return startCount; } + } + + public int CloseCount + { + get { return closeCount; } + } + + public event ExceptionListener ExceptionListener; + + public ISession CreateSession() + { + createSessionCount++; + return new TestSession(); + } + + public ISession CreateSession(AcknowledgementMode acknowledgementMode) + { + createSessionCount++; + return new TestSession(); + } + + public void Close() + { + closeCount++; + } + + public AcknowledgementMode AcknowledgementMode + { + get { return AcknowledgementMode.ClientAcknowledge; } + set { } + } + + public string ClientId + { + get { return null; } + set { } + } + + public void Dispose() + { + } + + public void Start() + { + startCount++; + } + + public bool IsStarted + { + get + { + if (startCount > 0) return true; + return false; + } + } + + public void Stop() + { + } + + public void FireExcpetionEvent(Exception e) + { + ExceptionListener(e); + } + } +} \ No newline at end of file diff --git a/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Connections/TestExceptionListener.cs b/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Connections/TestExceptionListener.cs new file mode 100644 index 00000000..52ae92e1 --- /dev/null +++ b/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Connections/TestExceptionListener.cs @@ -0,0 +1,40 @@ +#region License + +/* + * Copyright © 2002-2007 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; + +namespace Spring.Messaging.Nms.Connection +{ + public class TestExceptionListener : IExceptionListener + { + private int count = 0; + + public void OnException(Exception exception) + { + count++; + } + + + public int Count + { + get { return count; } + } + } +} \ No newline at end of file diff --git a/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Connections/TestMessageProducer.cs b/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Connections/TestMessageProducer.cs new file mode 100644 index 00000000..be9a97f0 --- /dev/null +++ b/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Connections/TestMessageProducer.cs @@ -0,0 +1,119 @@ +#region License + +/* + * Copyright 2002-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using Apache.NMS; + +namespace Spring.Messaging.Nms.Connection +{ + + public class TestMessageProducer : IMessageProducer + { + public void Send(IMessage message) + { + throw new NotImplementedException(); + } + + public void Send(IMessage message, bool persistent, byte priority, TimeSpan timeToLive) + { + throw new NotImplementedException(); + } + + public void Send(IDestination destination, IMessage message) + { + throw new NotImplementedException(); + } + + public void Send(IDestination destination, IMessage message, bool persistent, byte priority, TimeSpan timeToLive) + { + throw new NotImplementedException(); + } + + public IMessage CreateMessage() + { + throw new NotImplementedException(); + } + + public ITextMessage CreateTextMessage() + { + throw new NotImplementedException(); + } + + public ITextMessage CreateTextMessage(string text) + { + throw new NotImplementedException(); + } + + public IMapMessage CreateMapMessage() + { + throw new NotImplementedException(); + } + + public IObjectMessage CreateObjectMessage(object body) + { + throw new NotImplementedException(); + } + + public IBytesMessage CreateBytesMessage() + { + throw new NotImplementedException(); + } + + public IBytesMessage CreateBytesMessage(byte[] body) + { + throw new NotImplementedException(); + } + + public bool Persistent + { + get { throw new NotImplementedException(); } + set { throw new NotImplementedException(); } + } + + public TimeSpan TimeToLive + { + get { throw new NotImplementedException(); } + set { throw new NotImplementedException(); } + } + + public byte Priority + { + get { throw new NotImplementedException(); } + set { throw new NotImplementedException(); } + } + + public bool DisableMessageID + { + get { throw new NotImplementedException(); } + set { throw new NotImplementedException(); } + } + + public bool DisableMessageTimestamp + { + get { throw new NotImplementedException(); } + set { throw new NotImplementedException(); } + } + + public void Dispose() + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Connections/TestSession.cs b/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Connections/TestSession.cs new file mode 100644 index 00000000..3d6eeb7b --- /dev/null +++ b/test/Spring/Spring.Messaging.Nms.Tests/Messaging/Nms/Connections/TestSession.cs @@ -0,0 +1,164 @@ +#region License + +/* + * Copyright 2002-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#endregion + +using System; +using Apache.NMS; + +namespace Spring.Messaging.Nms.Connection +{ + + public class TestSession : ISession + { + private int closeCount; + private int createdCount; + + + public TestSession() + { + createdCount++; + } + + public int CloseCount + { + get { return closeCount; } + } + + + public int CreatedCount + { + get { return createdCount; } + } + + public IMessageProducer CreateProducer() + { + return new TestMessageProducer(); + } + + public IMessageProducer CreateProducer(IDestination destination) + { + return new TestMessageProducer(); + } + + public IMessageConsumer CreateConsumer(IDestination destination) + { + throw new NotImplementedException(); + } + + public IMessageConsumer CreateConsumer(IDestination destination, string selector) + { + throw new NotImplementedException(); + } + + public IMessageConsumer CreateConsumer(IDestination destination, string selector, bool noLocal) + { + throw new NotImplementedException(); + } + + public IMessageConsumer CreateDurableConsumer(ITopic destination, string name, string selector, bool noLocal) + { + throw new NotImplementedException(); + } + + public IQueue GetQueue(string name) + { + throw new NotImplementedException(); + } + + public ITopic GetTopic(string name) + { + throw new NotImplementedException(); + } + + public ITemporaryQueue CreateTemporaryQueue() + { + throw new NotImplementedException(); + } + + public ITemporaryTopic CreateTemporaryTopic() + { + throw new NotImplementedException(); + } + + public IMessage CreateMessage() + { + throw new NotImplementedException(); + } + + public ITextMessage CreateTextMessage() + { + throw new NotImplementedException(); + } + + public ITextMessage CreateTextMessage(string text) + { + throw new NotImplementedException(); + } + + public IMapMessage CreateMapMessage() + { + throw new NotImplementedException(); + } + + public IObjectMessage CreateObjectMessage(object body) + { + throw new NotImplementedException(); + } + + public IBytesMessage CreateBytesMessage() + { + throw new NotImplementedException(); + } + + public IBytesMessage CreateBytesMessage(byte[] body) + { + throw new NotImplementedException(); + } + + public void Close() + { + closeCount++; + } + + public void Commit() + { + throw new NotImplementedException(); + } + + public void Rollback() + { + throw new NotImplementedException(); + } + + public bool Transacted + { + get { throw new NotImplementedException(); } + } + + public AcknowledgementMode AcknowledgementMode + { + get { throw new NotImplementedException(); } + } + + public void Dispose() + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/test/Spring/Spring.Messaging.Nms.Tests/Spring.Messaging.Nms.Tests.2005.csproj b/test/Spring/Spring.Messaging.Nms.Tests/Spring.Messaging.Nms.Tests.2005.csproj index 7c5acc53..e9cea6ca 100644 --- a/test/Spring/Spring.Messaging.Nms.Tests/Spring.Messaging.Nms.Tests.2005.csproj +++ b/test/Spring/Spring.Messaging.Nms.Tests/Spring.Messaging.Nms.Tests.2005.csproj @@ -74,9 +74,19 @@ {AEB1578C-9018-4D49-B440-789F38DD2F29} Spring.Messaging.Nms.2005 + + {44B16BAA-6DF8-447C-9D7F-3AD3D854D904} + Spring.Core.Tests.2005 + + + + + + +