diff --git a/src/Spring/Spring.ConversationWA.NH32/AssemblyInfo.cs b/src/Spring/Spring.ConversationWA.NH32/AssemblyInfo.cs
new file mode 100644
index 00000000..d82e84c0
--- /dev/null
+++ b/src/Spring/Spring.ConversationWA.NH32/AssemblyInfo.cs
@@ -0,0 +1,5 @@
+using System;
+using System.Reflection;
+
+[assembly: AssemblyTitle("Spring.ConversationWA.NH32. NHibernate 3.2 support.")]
+[assembly: AssemblyDescription("Interfaces and classes that provide 'Conversation Workaround' support in Spring.Net")]
\ No newline at end of file
diff --git a/src/Spring/Spring.ConversationWA.NH32/ConversationWA/HttpModule/ConversationModule.cs b/src/Spring/Spring.ConversationWA.NH32/ConversationWA/HttpModule/ConversationModule.cs
new file mode 100644
index 00000000..4804eeb6
--- /dev/null
+++ b/src/Spring/Spring.ConversationWA.NH32/ConversationWA/HttpModule/ConversationModule.cs
@@ -0,0 +1,125 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Web;
+using Spring.Context.Support;
+using Common.Logging;
+using Spring.Data.NHibernate.Support;
+using System.Web.UI;
+using Spring.Context;
+
+namespace Spring.ConversationWA.HttpModule
+{
+ ///
+ /// HttpModule for end Conversation with Timeout exceeded.
+ ///
+ /// Hailton de Castro
+ public class ConversationModule : IHttpModule, IApplicationContextAware
+ {
+ private static readonly ILog LOG = LogManager.GetLogger(typeof(ConversationModule));
+
+ ///
+ /// Default constructor.
+ ///
+ public ConversationModule(){ }
+
+ private IList conversationManagerName;
+ ///
+ /// Name for the IConversationManager on the spring context.
+ ///
+ public IList ConversationManagerNameList
+ {
+ get { return conversationManagerName; }
+ set { conversationManagerName = value; }
+ }
+
+ #region IHttpModule Members
+
+ ///
+ /// Add PostRequestHandlerExecute event to clear conversations with timeout exceeded.
+ ///
+ ///
+ public void Init(HttpApplication context)
+ {
+ context.PreRequestHandlerExecute += new EventHandler(context_PreRequestHandlerExecute);
+ context.PostRequestHandlerExecute += new EventHandler(context_PostRequestHandlerExecute);
+ context.EndRequest += new EventHandler(context_EndRequest);
+ }
+
+ ///
+ /// NOOP.
+ ///
+ public void Dispose()
+ {
+ //noop
+ }
+
+ void context_PreRequestHandlerExecute(object sender, EventArgs e)
+ {
+ if (HttpContext.Current.Handler is Page)
+ {
+ Page page = (Page)HttpContext.Current.Handler;
+ page.Unload += new EventHandler(page_Unload);
+
+ if (HttpContext.Current.Session != null)
+ {
+ if (LOG.IsDebugEnabled) LOG.Debug("context_PreRequestHandlerExecute: HttpContext.Current.Session is NOT null");
+ foreach (String convMngName in this.ConversationManagerNameList)
+ {
+ IConversationManager convMng = (IConversationManager)this.applicationContext.GetObject(convMngName);
+ convMng.EndOnTimeOut();
+ convMng.FreeEnded();
+ }
+ }
+ else
+ {
+ if (LOG.IsDebugEnabled) LOG.Debug("context_PreRequestHandlerExecute: HttpContext.Current.Session IS null");
+ }
+ }
+ }
+
+ ///
+ /// Necessary for Redirect or Abort for some reason.
+ ///
+ ///
+ ///
+ void page_Unload(object sender, EventArgs e)
+ {
+ if (LOG.IsDebugEnabled) LOG.Debug("page_Unload HttpContext.Current.Session is null: " + (HttpContext.Current.Session == null));
+ foreach (String convMngName in this.ConversationManagerNameList)
+ {
+ IConversationManager convMng = (IConversationManager)this.applicationContext.GetObject(convMngName);
+ convMng.EndOnTimeOut();
+ convMng.FreeEnded();
+ convMng.PauseConversations();
+ }
+ }
+
+ void context_EndRequest(object sender, EventArgs e)
+ {
+ if (LOG.IsDebugEnabled) LOG.Debug("context_EndRequest HttpContext.Current.Session is null: " + (HttpContext.Current.Session == null));
+ }
+
+ void context_PostRequestHandlerExecute(object sender, EventArgs e)
+ {
+ if (LOG.IsDebugEnabled) LOG.Debug("context_PostRequestHandlerExecute HttpContext.Current.Session is null: " + (HttpContext.Current.Session == null));
+ if (HttpContext.Current.Session != null)
+ {
+ }
+ }
+
+ #endregion
+
+ #region IApplicationContextAware Members
+ private IApplicationContext applicationContext;
+ ///
+ /// Used to obtain the instances of "IConversationManager".
+ ///
+ public IApplicationContext ApplicationContext
+ {
+ set { this.applicationContext = value; }
+ }
+
+ #endregion
+ }
+}
diff --git a/src/Spring/Spring.ConversationWA.NH32/ConversationWA/IConversationManager.cs b/src/Spring/Spring.ConversationWA.NH32/ConversationWA/IConversationManager.cs
new file mode 100644
index 00000000..5ec3c961
--- /dev/null
+++ b/src/Spring/Spring.ConversationWA.NH32/ConversationWA/IConversationManager.cs
@@ -0,0 +1,92 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using NHibernate;
+
+namespace Spring.ConversationWA
+{
+ ///
+ /// manager for Conversations.
+ ///
+ /// Hailton de Castro
+ public interface IConversationManager
+ {
+ ///
+ /// Returns the conversation if it is still alive, otherwise it returns null.
+ ///
+ ///
+ ///
+ IConversationState GetConversationById(String id);
+
+ ///
+ /// Ends all conversations with the timeout exceeded.
+ ///
+ void EndOnTimeOut();
+
+ ///
+ /// Close IDbConnection's for that
+ /// use 'session-per-conversation'. It calls
+ /// in all conversations.
+ ///
+ void PauseConversations();
+
+ ///
+ /// Release the ended conversatons And remove it.
+ /// If the conversation support 'session-per-conversation' close the session.
+ ///
+ void FreeEnded();
+
+ ///
+ /// Add conversation. If is null
+ /// it is setted with 'this'.
+ ///
+ ///
+ ///
+ /// If already has another manager.
+ ///
+ void AddConversation(IConversationState conversation);
+
+ ///
+ /// Makes the 'root conversation' of
+ /// the current active conversation and open/reopen the
+ /// if
+ /// the conversation supports 'session-per-conversation'. Close all
+ /// the connection for all session before.
+ /// If is true will end all
+ /// paused conversations.
+ ///
+ void SetActiveConversation(IConversationState conversation);
+
+ ///
+ /// Returns the active conversation if exists, otherwise returns null.
+ /// It depends on
+ ///
+ ///
+ IConversationState ActiveConversation{ get; }
+
+ ///
+ /// If this is non-null run pattern 'session-per-conversation'.
+ /// Must be the same SessionFactory of the managed conversations.
+ ///
+ ///
+ ISessionFactory SessionFactory { get; set; }
+
+ ///
+ /// Ends the "paused conversations" in call to .
+ /// Important: Unexpected behavior may occur if there are nested conversations,
+ /// as in 'StartResumeConversation' only the own conversation and their parents
+ /// are started, the 'conversations children' remain paused, so these will be ended.
+ /// Defaul value: false.
+ ///
+ ///
+ /// When it is true, "start/resume a conversation" will cause the other to be
+ /// ended and cleaned up.
+ ///
+ /// This is useful to avoid memory leak where there are many conversations.
+ /// This leak can be very considerable, as the conversation may keep a "NHibernate session"
+ /// that can contain many objects in its cache from the database queries.
+ ///
+ ///
+ bool EndPaused { get; set; }
+ }
+}
diff --git a/src/Spring/Spring.ConversationWA.NH32/ConversationWA/IConversationState.cs b/src/Spring/Spring.ConversationWA.NH32/ConversationWA/IConversationState.cs
new file mode 100644
index 00000000..2338a910
--- /dev/null
+++ b/src/Spring/Spring.ConversationWA.NH32/ConversationWA/IConversationState.cs
@@ -0,0 +1,172 @@
+using System;
+using System.Data;
+using System.Configuration;
+using System.Web;
+using System.Web.Security;
+using System.Web.UI;
+using System.Web.UI.WebControls;
+using System.Web.UI.WebControls.WebParts;
+using System.Web.UI.HtmlControls;
+using System.Collections.Generic;
+using System.Collections;
+using NHibernate;
+using Spring.Data.Common;
+
+namespace Spring.ConversationWA
+{
+ ///
+ /// Port to conversation. If the object is not found in the current
+ /// conversation, will be tried on the parent if the parent is
+ /// different not null.
+ ///
+ ///
+ /// If is different from spring name for this instance.
+ ///
+ /// Hailton de Castro
+ public interface IConversationState: IDictionary
+ {
+ ///
+ /// Conversation id.
+ ///
+ String Id { get; set; }
+
+ ///
+ /// Starts or resumes the conversation and the .
+ /// If is not null, so
+ /// .GetCurrentSession() is called to
+ /// Raise SessionHolder for make the reconnection.
+ ///
+ /// Make return false.
+ ///
+ /// Update the .
+ ///
+ ///
+ ///
+ ///
+ /// - If this conversation is ended.
+ ///
+ /// - If is not null and
+ /// different from
+ /// .GetCurrentSession()
+ ///
+ ///
+ ///
+ void StartResumeConversation();
+
+ ///
+ /// Return true until is called.
+ ///
+ bool IsNew { get; }
+
+ ///
+ /// Ends the conversation. End each the 'inner conversation' in
+ /// . Returns false if the
+ /// conversation and all of
+ /// has already been ended.
+ ///
+ ///
+ ///
+ ///
+ /// - If .
+ /// Session["spring.objects"]
+ /// is null.
+ ///
+ /// - The 'spring session scopes' are not located in the key
+ /// 'spring.objects' of HttpSessionState.
+ ///
+ ///
+ ///
+ void EndConversation();
+
+ ///
+ /// Return true if this conversation is ended.
+ ///
+ bool Ended { get; }
+
+ ///
+ /// Inner conversation. After added if the
+ /// is null it will be setted with 'this'.
+ ///
+ /// at
+ /// ,
+ /// ,
+ ///
+ /// if Circular Dependence is detected.
+ IList InnerConversations { get; }
+
+ ///
+ /// Conversation parent.
+ ///
+ ///
+ ///
+ /// - If this conversation already has a different parent.
+ ///
+ /// - If this Conversation is not new.
+ ///
+ /// - If Circular Dependence is detected.
+ ///
+ /// - The Parent conversation is not new.
+ ///
+ ///
+ ///
+ IConversationState ParenteConversation { get; set;}
+
+ ///
+ /// TimeOut for the conversation in milliseconds.
+ /// If 0 means it will be ignored.
+ ///
+ Int32 TimeOut { get; set; }
+
+ ///
+ /// Last acces for a value into this Conversation or Inner Conversation.
+ /// It is reseted for DateTime.Now each time
+ /// is called.
+ ///
+ DateTime LastAccess { get; set; }
+
+ ///
+ /// Conversation Manager. When this is setted if
+ ///
+ /// returns null so AddConversation is called.
+ ///
+ IConversationManager ConversationManager { get; set; }
+
+ ///
+ /// that is stored in the root conversation.
+ ///
+ ///
+ /// must support 'session-per-conversation'.
+ ///
+ ///
+ ISession RootSessionPerConversation { get; set; }
+
+ ///
+ /// If this is non-null run pattern c.
+ /// It also depends on and .
+ /// must support ConversationManager.
+ ///
+ ///
+ ISessionFactory SessionFactory { get; set; }
+
+ ///
+ /// If this is non-null run pattern 'session-per-conversation'.
+ /// It also depends on and .
+ /// must support ConversationManager.
+ ///
+ ///
+ IDbProvider DbProvider { get; set; }
+
+ ///
+ /// Indicates that the conversation is paused.
+ ///
+ bool IsPaused { get; }
+
+ ///
+ /// Starts or resumes the conversation and each 'inner conversation' in
+ /// .
+ /// It is not about 'Session-per-conversation' because it is done by
+ /// .
+ ///
+ void PauseConversation();
+ }
+}
diff --git a/src/Spring/Spring.ConversationWA.NH32/ConversationWA/Imple/InnerConversationList.cs b/src/Spring/Spring.ConversationWA.NH32/ConversationWA/Imple/InnerConversationList.cs
new file mode 100644
index 00000000..83ae531b
--- /dev/null
+++ b/src/Spring/Spring.ConversationWA.NH32/ConversationWA/Imple/InnerConversationList.cs
@@ -0,0 +1,354 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Collections;
+using Common.Logging;
+using Iesi.Collections.Generic;
+
+namespace Spring.ConversationWA.Imple
+{
+ ///
+ /// List that make validation for Circular Dependence for
+ ///
+ /// Hailton de Castro
+ public class InnerConversationList: IList, IList
+ {
+ private static readonly ILog LOG = LogManager.GetLogger(typeof(InnerConversationList));
+
+ private IConversationState conversationOwner;
+ ///
+ /// Contructor.
+ ///
+ /// The IConversation that owns this InnerConversationList.
+ public InnerConversationList(IConversationState conversationOwner)
+ {
+ if (conversationOwner == null)
+ {
+ String exMsgStr = "'conversationOwner' can not be null";
+
+ LOG.Error(exMsgStr);
+ throw new InvalidOperationException(exMsgStr);
+ }
+ if (LOG.IsDebugEnabled) LOG.Error(String.Format("Creating InnerConversationList for '{0}'", conversationOwner.Id));
+ this.conversationOwner = conversationOwner;
+ }
+
+ private IList innerList = new List();
+
+ ///
+ /// Common Helper to be run before insert.
+ ///
+ ///
+ private void addPreAddHelper(IConversationState itemAdded)
+ {
+ if (LOG.IsDebugEnabled) LOG.Debug(String.Format("addPreAddHelper: added={0} into {1}", itemAdded, this.conversationOwner));
+ this.validateCircularDependence(itemAdded);
+ if (itemAdded.ParenteConversation != null && itemAdded.ParenteConversation != this.conversationOwner)
+ {
+ throw new InvalidOperationException(
+ String.Format(WebConversationSpringState.MSG_CONVERSATION_ALREADY_HAS_PARENT, itemAdded.ParenteConversation.Id, this.conversationOwner.Id));
+ }
+ }
+
+ ///
+ /// Common Helper to be run after insert.
+ ///
+ private void addPostAddHelper(IConversationState itemAdded)
+ {
+ if (LOG.IsDebugEnabled) LOG.Debug(String.Format("addPreAddHelper: added={0} into {1}", itemAdded, this.conversationOwner));
+ if (itemAdded.ParenteConversation == null)
+ {
+ itemAdded.ParenteConversation = this.conversationOwner;
+ }
+ }
+
+ private void validateCircularDependence(IConversationState itemAdded)
+ {
+ if (LOG.IsDebugEnabled) LOG.Debug(String.Format("Validating Circular Dependence: added={0} into {1}", itemAdded, this.conversationOwner));
+
+ ICollection visitedColl = new HashedSet();
+ visitedColl.Add(conversationOwner);
+
+ IConversationState parentAncestor = this.conversationOwner;
+ String path = this.conversationOwner.Id;
+ //string conectorPath = "";
+
+ this.validateCircularDependenceRecursive(itemAdded, visitedColl, path + "->" + itemAdded.Id);
+ }
+
+ private void validateCircularDependenceRecursive(IConversationState currentConv, ICollection visitedColl, String path)
+ {
+ foreach (IConversationState convItem in currentConv.InnerConversations)
+ {
+ if (visitedColl.Contains(convItem))
+ {
+ String exMsgStr =
+ "ConversationState Circular Dependence detected: " +
+ path + "->" + convItem.Id;
+
+ LOG.Error(exMsgStr);
+ throw new InvalidOperationException(exMsgStr);
+ }
+
+ visitedColl.Add(convItem);
+
+ this.validateCircularDependenceRecursive(convItem, visitedColl, path + "->" + convItem.Id);
+ }
+ }
+
+ #region IList Members
+ ///
+ ///
+ ///
+ ///
+ ///
+ public int IndexOf(IConversationState item)
+ {
+ return this.innerList.IndexOf(item);
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void Insert(int index, IConversationState item)
+ {
+ this.addPreAddHelper(item);
+ this.innerList.Insert(index, item);
+ this.addPostAddHelper(item);
+ }
+
+ ///
+ ///
+ ///
+ ///
+ public void RemoveAt(int index)
+ {
+ this.innerList.RemoveAt(index);
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public IConversationState this[int index]
+ {
+ get
+ {
+ return this.innerList[index];
+ }
+ set
+ {
+ this.addPreAddHelper(value);
+ this.innerList[index] = value;
+ this.addPostAddHelper(value);
+ }
+ }
+
+ #endregion
+
+ #region ICollection Members
+ ///
+ ///
+ ///
+ ///
+ public void Add(IConversationState item)
+ {
+ this.addPreAddHelper(item);
+ this.innerList.Add(item);
+ this.addPostAddHelper(item);
+ }
+
+ ///
+ ///
+ ///
+ public void Clear()
+ {
+ this.innerList.Clear();
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public bool Contains(IConversationState item)
+ {
+ return this.innerList.Contains(item);
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void CopyTo(IConversationState[] array, int arrayIndex)
+ {
+ this.innerList.CopyTo(array, arrayIndex);
+ }
+
+ ///
+ ///
+ ///
+ public int Count
+ {
+ get { return this.innerList.Count; }
+ }
+
+ ///
+ ///
+ ///
+ public bool IsReadOnly
+ {
+ get { return this.innerList.IsReadOnly; }
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public bool Remove(IConversationState item)
+ {
+ return this.innerList.Remove(item);
+ }
+
+ #endregion
+
+ #region IEnumerable Members
+ ///
+ ///
+ ///
+ ///
+ public IEnumerator GetEnumerator()
+ {
+ return this.innerList.GetEnumerator();
+ }
+
+ #endregion
+
+ #region IEnumerable Members
+
+ ///
+ ///
+ ///
+ ///
+ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
+ {
+ return this.innerList.GetEnumerator();
+ }
+
+ #endregion
+
+ #region IList Members
+ ///
+ ///
+ ///
+ ///
+ ///
+ public int Add(object value)
+ {
+ ((IList)this).Add((IConversationState)value);
+ return this.innerList.Count - 1;
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public bool Contains(object value)
+ {
+ return ((IList)this).Contains((IConversationState)value);
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public int IndexOf(object value)
+ {
+ return ((IList)this).IndexOf((IConversationState)value);
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void Insert(int index, object value)
+ {
+ ((IList)this).Insert(index, (IConversationState)value);
+ }
+
+ ///
+ ///
+ ///
+ public bool IsFixedSize
+ {
+ get { return false; }
+ }
+
+ ///
+ ///
+ ///
+ ///
+ public void Remove(object value)
+ {
+ ((IList)this).Add((IConversationState)value);
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ object IList.this[int index]
+ {
+ get
+ {
+ return ((IList)this)[index];
+ }
+ set
+ {
+ ((IList)this)[index] = (IConversationState)value;
+ }
+ }
+
+ #endregion
+
+ #region ICollection Members
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void CopyTo(Array array, int index)
+ {
+ IConversationState[] convArr = new IConversationState[array.Length];
+ ((IList)this).CopyTo(convArr, index);
+ convArr.CopyTo(array, index);
+ }
+
+ ///
+ ///
+ ///
+ public bool IsSynchronized
+ {
+ get { return false; }
+ }
+
+ ///
+ ///
+ ///
+ public object SyncRoot
+ {
+ get { return this; }
+ }
+
+ #endregion
+ }
+}
diff --git a/src/Spring/Spring.ConversationWA.NH32/ConversationWA/Imple/WebConversationManager.cs b/src/Spring/Spring.ConversationWA.NH32/ConversationWA/Imple/WebConversationManager.cs
new file mode 100644
index 00000000..8fe66e79
--- /dev/null
+++ b/src/Spring/Spring.ConversationWA.NH32/ConversationWA/Imple/WebConversationManager.cs
@@ -0,0 +1,295 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Threading;
+using System.Web;
+using Common.Logging;
+using Spring.Data.NHibernate.Support;
+using NHibernate;
+
+namespace Spring.ConversationWA.Imple
+{
+ ///
+ /// This was made to stay under session scope.
+ ///
+ /// Hailton de Castro
+ public class WebConversationManager : SessionPerConversationScope, IConversationManager
+ {
+ private static readonly ILog LOG = LogManager.GetLogger(typeof(WebConversationManager));
+
+ private static readonly String CONVERSATION_COOKIE_ID = "WebConversationManager.activeConversationId";
+
+ ///
+ /// Semaphore to synchronize writes to the dictionary.
+ ///
+ private Mutex mutexEditDic = new Mutex();
+ private IDictionary conversations = new Dictionary();
+ private IConversationState activeConversation = null;
+
+ #region IConversationManager Members
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public IConversationState GetConversationById(string id)
+ {
+ if (this.conversations.ContainsKey(id))
+ {
+ return this.conversations[id];
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ ///
+ ///
+ ///
+ public void EndOnTimeOut()
+ {
+ try
+ {
+ if (LOG.IsDebugEnabled) LOG.Debug("EndOnTimeOut");
+ this.mutexEditDic.WaitOne(5000);
+ foreach (String keyItem in this.conversations.Keys)
+ {
+ IConversationState conversationItem = this.conversations[keyItem];
+ if (conversationItem.TimeOut > 0)
+ {
+ if (DateTime.Now.Subtract(conversationItem.LastAccess).TotalMilliseconds > conversationItem.TimeOut)
+ {
+ if (LOG.IsDebugEnabled) LOG.Debug(String.Format("Timeout for conversation '{0}'", conversationItem.Id));
+ conversationItem.EndConversation();
+ }
+ }
+ }
+ }
+ finally
+ {
+ this.mutexEditDic.ReleaseMutex();
+ }
+ }
+
+ ///
+ ///
+ ///
+ public void PauseConversations()
+ {
+ foreach (IConversationState convItem in this.conversations.Values)
+ {
+ convItem.PauseConversation();
+ }
+ this.Close(this.SessionFactory, this.conversations.Values);
+ }
+
+ ///
+ ///
+ ///
+ public void FreeEnded()
+ {
+ try
+ {
+ if (LOG.IsDebugEnabled) LOG.Debug("EndOnTimeOut");
+ this.mutexEditDic.WaitOne(5000);
+ List removeList = new List();
+ foreach (String keyItem in this.conversations.Keys)
+ {
+ IConversationState conversationItem = this.conversations[keyItem];
+ if (conversationItem.Ended)
+ {
+ if (LOG.IsDebugEnabled) LOG.Debug(String.Format("FreeEnded: Release conversation '{0}'", conversationItem.Id));
+ removeList.Add(conversationItem);
+ }
+ }
+
+ if (removeList.Count > 0)
+ {
+ this.Close(this.sessionFactory, removeList);
+ }
+
+ foreach (IConversationState conversationItem in removeList)
+ {
+ if (LOG.IsDebugEnabled) LOG.Debug(String.Format("FreeEnded: Remove conversation '{0}'", conversationItem.Id));
+ conversationItem.EndConversation();
+ this.RemoveConversation(conversationItem);
+ }
+ }
+ finally
+ {
+ this.mutexEditDic.ReleaseMutex();
+ }
+ }
+
+ ///
+ ///
+ ///
+ ///
+ public void AddConversation(IConversationState conversation)
+ {
+ try
+ {
+ this.mutexEditDic.WaitOne(5000);
+ this.conversations.Add(conversation.Id, conversation);
+ }
+ finally
+ {
+ this.mutexEditDic.ReleaseMutex();
+ }
+
+ if (conversation.ConversationManager != null && conversation.ConversationManager != this)
+ {
+ throw new InvalidOperationException(String.Format("Conversation already has another manager. conversation='{0}'", conversation.Id));
+ }
+
+ if (conversation.ConversationManager == null)
+ {
+ conversation.ConversationManager = this;
+ }
+ }
+
+ ///
+ ///
+ ///
+ ///
+ public void SetActiveConversation(IConversationState conversation)
+ {
+ if (LOG.IsDebugEnabled) LOG.Debug(String.Format("SetActiveConversation('{0}')", conversation.Id));
+
+ //Close connection for the last conversation, if it is open.
+ if (this.activeConversation != null && this.activeConversation != conversation)
+ {
+ this.Close(this.SessionFactory, this.conversations.Values);
+ }
+ this.Open(conversation, this.conversations.Values);
+
+ this.activeConversation = conversation;
+
+ //Ending the paused conversations.
+ if (this.EndPaused)
+ {
+ foreach (IConversationState convItem in this.conversations.Values)
+ {
+ if (convItem.IsPaused)
+ {
+ convItem.EndConversation();
+ }
+ }
+ }
+ }
+
+ ///
+ ///
+ ///
+ [Obsolete("Not used, the active conversation is defined by call 'IConversationManager.SetActiveConversation' on 'IConversationState.StartResumeConversation'")]
+ public void LoadActiveConversation()
+ {
+ //reset this.activeConversation
+ this.activeConversation = null;
+
+ if (LOG.IsDebugEnabled) LOG.Debug("LoadActiveConversation");
+
+ HttpCookie activeConveCookie = HttpContext.Current.Request.Cookies[CONVERSATION_COOKIE_ID];
+ if (activeConveCookie != null && !String.IsNullOrEmpty(activeConveCookie.Value))
+ {
+ if (LOG.IsDebugEnabled) LOG.Debug(String.Format("LoadActiveConversation: cooking found for current active conversation: [{0}]", activeConveCookie.ToString()));
+ if (this.conversations.ContainsKey(activeConveCookie.Value))
+ {
+ if (LOG.IsDebugEnabled) LOG.Debug(String.Format("LoadActiveConversation: active conversation found for id: '{0}'", activeConveCookie.Value));
+ IConversationState conversation = this.conversations[activeConveCookie.Value];
+ if (conversation != null)
+ {
+ if (LOG.IsDebugEnabled) LOG.Debug(String.Format("LoadActiveConversation: conversation found: '{0}'", conversation.Id));
+ //find root conversation.
+ IConversationState rootConversation = conversation;
+ while (rootConversation.ParenteConversation != null)
+ {
+ rootConversation = rootConversation.ParenteConversation;
+ }
+ rootConversation.StartResumeConversation();
+ this.SetActiveConversation(rootConversation);
+ }
+ }
+ else
+ {
+ if (LOG.IsDebugEnabled) LOG.Debug(String.Format("LoadActiveConversation: conversation NOT found for id on the cookie: '{0}'", activeConveCookie.Value));
+ HttpContext.Current.Response.Cookies.Remove(CONVERSATION_COOKIE_ID);
+ }
+ }
+ }
+
+ ///
+ ///
+ ///
+ public IConversationState ActiveConversation
+ {
+ get
+ {
+ return this.activeConversation;
+ }
+ }
+
+ private ISessionFactory sessionFactory;
+ ///
+ ///
+ ///
+ public ISessionFactory SessionFactory
+ {
+ get { return sessionFactory; }
+ set { sessionFactory = value; }
+ }
+
+ private bool endPaused = false;
+
+ ///
+ ///
+ ///
+ public bool EndPaused
+ {
+ get { return endPaused; }
+ set { endPaused = value; }
+ }
+ #endregion
+
+ #region SessionPerConversationScope Members
+ ///
+ /// Ends all conversations and Closes all their Session.
+ ///
+ public override void Dispose()
+ {
+ if (LOG.IsDebugEnabled) LOG.Debug("Dispose. End all Conversations");
+ foreach (String conversationId in this.conversations.Keys)
+ {
+ this.conversations[conversationId].EndConversation();
+ }
+
+ this.Close(this.SessionFactory, this.conversations.Values);
+
+ this.conversations.Clear();
+ this.sessionFactory = null;
+ }
+ #endregion
+
+ ///
+ /// Remove conversation.
+ ///
+ ///
+ private void RemoveConversation(IConversationState conversation)
+ {
+ try
+ {
+ this.mutexEditDic.WaitOne(5000);
+ if (!this.conversations.Remove(conversation.Id))
+ {
+ throw new InvalidOperationException(String.Format("Conversation '{0}' not exists on this manager", conversation.Id));
+ }
+ }
+ finally
+ {
+ this.mutexEditDic.ReleaseMutex();
+ }
+ }
+ }
+}
diff --git a/src/Spring/Spring.ConversationWA.NH32/ConversationWA/Imple/WebConversationSpringState.cs b/src/Spring/Spring.ConversationWA.NH32/ConversationWA/Imple/WebConversationSpringState.cs
new file mode 100644
index 00000000..5c91e414
--- /dev/null
+++ b/src/Spring/Spring.ConversationWA.NH32/ConversationWA/Imple/WebConversationSpringState.cs
@@ -0,0 +1,601 @@
+using System;
+using System.Data;
+using System.Configuration;
+using System.Web;
+using System.Web.Security;
+using System.Web.UI;
+using System.Web.UI.WebControls;
+using System.Web.UI.WebControls.WebParts;
+using System.Web.UI.HtmlControls;
+using Spring.Objects.Factory;
+using Common.Logging;
+using System.Collections.Generic;
+using System.Collections;
+using Spring.Data.Common;
+using NHibernate;
+using Spring.Data.NHibernate.Support;
+using Spring.Data.NHibernate;
+
+namespace Spring.ConversationWA.Imple
+{
+ ///
+ /// Implementation of conversation in the infrastructure of Spring.
+ /// It avoid Circular Dependence.
+ ///
+ /// Hailton de Castro
+ public class WebConversationSpringState : IConversationState, IObjectNameAware
+ {
+ private static readonly ILog LOG = LogManager.GetLogger(typeof(WebConversationSpringState));
+
+ ///
+ /// Default message for "CONVERSATION ALREADY HAS A PARENT" error.
+ ///
+ public static readonly String MSG_CONVERSATION_ALREADY_HAS_PARENT =
+ "This conversation already has a different parent." +
+ " Current: '{0}'. Tried: '{1}'";
+
+ private IDictionary state = new Dictionary();
+
+ #region IConversationState Members
+
+ private String id;
+ ///
+ ///
+ ///
+ public string Id
+ {
+ get
+ {
+ return this.id;
+ }
+ set
+ {
+ this.id = value;
+ }
+ }
+
+ ///
+ ///
+ ///
+ ///
+ public void EndConversation()
+ {
+ if (LOG.IsDebugEnabled) LOG.Debug(String.Format("End of Conversation '{0}'", this.id));
+
+ IDictionary springSessionScope = (IDictionary)HttpContext.Current.Session["spring.objects"];
+ if (springSessionScope == null)
+ {
+ throw new InvalidOperationException("The 'spring session scope' are not located in the key 'spring.objects' of HttpSessionState");
+ }
+
+ if (springSessionScope.Contains(this.Id))
+ {
+ if (LOG.IsDebugEnabled) LOG.Debug(String.Format("EndConversation: Id='{0}' Was Found on 'spring session scope'!", this.id));
+ springSessionScope.Remove(this.id);
+ }
+ else
+ {
+ if (LOG.IsDebugEnabled) LOG.Debug(String.Format("EndConversation: Id='{0}' Was NOT Found on 'spring session scope!", this.id));
+ }
+
+ List innerConversationsListTemp = new List(this.InnerConversations);
+ foreach (IConversationState innerConversationItem in innerConversationsListTemp)
+ {
+ innerConversationItem.EndConversation();
+ }
+
+ if (this.parenteConversation != null)
+ {
+ this.parenteConversation.InnerConversations.Remove(this);
+ }
+
+ this.ended = true;
+ }
+
+ private bool ended = false;
+ ///
+ ///
+ ///
+ public bool Ended
+ {
+ get { return ended; }
+ }
+
+ private int timeOut = 180000;
+ ///
+ /// Default 180000.
+ ///
+ public int TimeOut
+ {
+ get { return timeOut; }
+ set { timeOut = value; }
+ }
+
+ private IList innerConversations;
+ ///
+ ///
+ ///
+ public IList InnerConversations
+ {
+ get
+ {
+ if (this.innerConversations == null)
+ {
+ this.innerConversations = new InnerConversationList(this);
+ }
+ return this.innerConversations;
+ }
+ }
+
+ private IConversationState parenteConversation;
+ ///
+ ///
+ ///
+ public IConversationState ParenteConversation
+ {
+ get
+ {
+ return this.parenteConversation;
+ }
+ set
+ {
+ if (this.parenteConversation != null && this.parenteConversation != value)
+ {
+ throw new InvalidOperationException(
+ String.Format(MSG_CONVERSATION_ALREADY_HAS_PARENT, this.parenteConversation.Id, value.Id));
+ }
+ if (!this.IsNew)
+ {
+ throw new InvalidOperationException(
+ String.Format("This Conversation is not new." +
+ " Conversation.Id: '{0}'. Parent Tried: '{1}'", this.Id, value.Id));
+ }
+ //Perhaps the father need not be new, only the child needs to be.
+ //if (!value.IsNew)
+ //{
+ // throw new InvalidOperationException(
+ // String.Format("The Parent conversation is not new." +
+ // " Conversation.Id: '{0}'. Parent Tried: '{1}'", this.Id, value.Id));
+ //}
+
+ this.parenteConversation = value;
+ if (!this.parenteConversation.InnerConversations.Contains(this))
+ {
+ this.parenteConversation.InnerConversations.Add(this);
+ }
+ }
+ }
+
+ private DateTime lastAccess = DateTime.Now;
+ ///
+ /// .
+ ///
+ public DateTime LastAccess
+ {
+ get { return lastAccess; }
+ set { lastAccess = value; }
+ }
+
+ private IConversationManager conversationManager;
+ ///
+ ///
+ ///
+ public IConversationManager ConversationManager
+ {
+ get
+ {
+ return conversationManager;
+ }
+ set
+ {
+ conversationManager = value;
+ if (this.conversationManager.GetConversationById(this.Id) == null)
+ {
+ this.conversationManager.AddConversation(this);
+ }
+ }
+ }
+
+ ///
+ ///
+ ///
+ public void StartResumeConversation()
+ {
+ this.isNew = false;
+ this.isPaused = false;
+ this.lastAccess = DateTime.Now;
+
+ if (this.Ended)
+ {
+ throw new InvalidOperationException(
+ String.Format(
+ "StartResumeConversation: this conversation is ended." +
+ " Conversation.Id '{0}'", this.Id));
+ }
+ if (this.ConversationManager != null)
+ {
+ if (LOG.IsDebugEnabled) LOG.Debug(String.Format("StartResumeConversation('{0}'): ConversationManager is not null.", this.Id));
+ //if this is the root conversation.
+ if (this.ParenteConversation == null)
+ {
+ if (LOG.IsDebugEnabled) LOG.Debug(String.Format("SetActiveConversation('{0}'): ConversationManager is not null.", this.Id));
+ //if this is the root conversation.
+ this.ConversationManager.SetActiveConversation(this);
+
+ //makes SessionHolder to reopen this session
+ if (this.RootSessionPerConversation != null)
+ {
+ ISession session = this.SessionFactory.GetCurrentSession();
+ if (this.RootSessionPerConversation != session)
+ {
+ //How it is implemented it will never happen, because of the sequence of previous calls:
+ // ConversationManager.SetActiveConversation -> SessionPerConversationScope.Open
+ // 'new InvalidOperationException("Participating in existing Hibernate SessionFactory IS NOT ALOWED.")' happen first.
+ throw new InvalidOperationException(
+ String.Format(
+ "StartResumeConversation: this.SessionFactory.GetCurrentSession()" +
+ " have a different instance than 'RootSessionPerConversation'" +
+ " from conversation '{0}'", this.Id));
+ }
+ }
+ }
+ else
+ {
+ this.ParenteConversation.StartResumeConversation();
+ }
+ }
+ }
+
+ private ISession rootSessionPerConversation;
+ ///
+ ///
+ ///
+ public ISession RootSessionPerConversation
+ {
+ get
+ {
+ if (this.ParenteConversation != null)
+ {
+ return this.ParenteConversation.RootSessionPerConversation;
+ }
+ else
+ {
+ return rootSessionPerConversation;
+ }
+ }
+ set
+ {
+ if (this.ParenteConversation != null)
+ {
+ this.ParenteConversation.RootSessionPerConversation = value;
+ }
+ else
+ {
+ rootSessionPerConversation = value;
+ }
+ }
+ }
+
+ private ISessionFactory sessionFactory;
+ ///
+ ///
+ ///
+ public ISessionFactory SessionFactory
+ {
+ get { return sessionFactory; }
+ set { sessionFactory = value; }
+ }
+
+ IDbProvider dbProvider;
+ ///
+ ///
+ ///
+ public IDbProvider DbProvider
+ {
+ get { return dbProvider; }
+ set { dbProvider = value; }
+ }
+
+ private bool isNew = true;
+ ///
+ ///
+ ///
+ public bool IsNew
+ {
+ get { return this.isNew; }
+ }
+
+ private bool isPaused = true;
+ ///
+ ///
+ ///
+ public bool IsPaused
+ {
+ get { return isPaused; }
+ }
+
+ ///
+ ///
+ ///
+ public void PauseConversation()
+ {
+ this.isPaused = true;
+ foreach (IConversationState innerConv in this.InnerConversations)
+ {
+ innerConv.PauseConversation();
+ }
+ }
+ #endregion
+
+ #region IObjectNameAware Members
+ ///
+ /// . It is used to valddate
+ ///
+ public string ObjectName
+ {
+ set
+ {
+ if (this.id != value)
+ {
+ throw new InvalidOperationException(String.Format("Id is different from spring name for this instance.. Currents='{0}', springName='{1}'", this.id, value));
+ }
+ if (LOG.IsDebugEnabled) LOG.Debug(String.Format("Begin of Conversation '{0}'", this.id));
+ }
+ }
+
+ #endregion
+
+ #region IDictionary Members
+ ///
+ ///
+ ///
+ ///
+ ///
+ public void Add(string key, object value)
+ {
+ this.state.Add(key, value);
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public bool ContainsKey(string key)
+ {
+ if (this.state.ContainsKey(key))
+ {
+ return this.state.ContainsKey(key);
+ }
+ else if (this.parenteConversation != null)
+ {
+ return this.parenteConversation.ContainsKey(key);
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ ///
+ ///
+ ///
+ public ICollection Keys
+ {
+ get
+ {
+ return this.state.Keys;
+ }
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ public bool Remove(string key)
+ {
+ return this.state.Remove(key);
+ }
+
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public bool TryGetValue(string key, out object value)
+ {
+ if (this.state.TryGetValue(key, out value))
+ {
+ return true;
+ }
+ else if (this.parenteConversation != null && this.parenteConversation.TryGetValue(key, out value))
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ ///
+ ///
+ ///
+ public ICollection