#region License

/*
 * Copyright © 2002-2005 the original author or authors.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#endregion

#region Imports

using System;
using System.Collections;
using System.Data;
using Common.Logging;
using NHibernate;
using Spring.Collections;
using Spring.Transaction.Support;
using Spring.Util;

#endregion

namespace Spring.Data.NHibernate
{
	/// <summary>
    /// Session holder, wrapping a NHibernate ISession and a NHibernate Transaction.
    /// HibernateTransactionManager binds instances of this class
    /// to the thread, for a given ISessionFactory.
	/// </summary>
	/// <remarks>
	/// Note: This is an SPI class, not intended to be used by applications.
	/// </remarks>
	/// <author>Mark Pollack (.NET)</author>
	public class SessionHolder : ResourceHolderSupport
	{
		#region Fields
	    
        private static readonly object DEFAULT_KEY = new object();

        private readonly Hashtable sessionDictionary = new Hashtable(1);
               
        private IDbConnection connection;

        private ITransaction transaction;

        private FlushMode flushMode;

	    //needed to see if we actually assigned the enum value...
        private bool assignedPreviousFlushMode = false;

		#endregion

	    #region Logging Definition

	    private static readonly ILog log = LogManager.GetLogger(typeof (SessionHolder));

	    #endregion

	    #region Constructor (s)

	    /// <summary>
	    /// May be used by derived classes to create an empty SessionHolder.
	    /// </summary>
	    /// <remarks>
	    /// When using this ctor in your derived class, you MUST override <see cref="EnsureInitialized"/>!
	    /// </remarks>
	    protected SessionHolder()
	    {	        
	    }
	    
        /// <summary>
        /// Initializes a new instance of the <see cref="SessionHolder"/> class.
        /// </summary>
        /// <param name="session">The session.</param>
        public SessionHolder(ISession session)
        {
            AddSession(session);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="SessionHolder"/> class.
        /// </summary>
        /// <param name="key">The key to store the session under.</param>
        /// <param name="session">The hibernate session.</param>
        public SessionHolder(object key, ISession session)
        {
            AddSession(key, session);
        }
	    
	    /// <summary>
	    /// May be overridden in a derived class to e.g. lazily create a session
	    /// </summary>
	    protected virtual void EnsureInitialized()
	    {	        
	        // noop here - but may be overridden to lazily create a session
	    }
	    
		#endregion

		#region Properties

        /// <summary>
        /// Gets the session using the default key
        /// </summary>
        /// <value>The hibernate session.</value>
        public ISession Session
        {
            get
            {
                lock (sessionDictionary.SyncRoot)
                {
                    EnsureInitialized();
                    return sessionDictionary[DEFAULT_KEY] as ISession;
                }
            }
        }

        /// <summary>
        /// Gets the first session based on iteration over
        /// the IDictionary storage.
        /// </summary>
        /// <value>Any hibernate session.</value>
        public ISession AnySession
        {
            get
            {
                lock(sessionDictionary.SyncRoot)
                {
                    EnsureInitialized();                    
                    if (sessionDictionary.Count > 0)
                    {
                        IEnumerator enumerator = sessionDictionary.Values.GetEnumerator();
                        enumerator.MoveNext();
                        return (ISession)enumerator.Current;
                    }
                    return null;
                }
            }
        }

        /// <summary>
        /// Gets a value indicating whether dictionary of
        /// hibernate sessions is empty.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this session holder is empty; otherwise, <c>false</c>.
        /// </value>
        public bool IsEmpty
        {
            get
            {
                lock(sessionDictionary.SyncRoot)
                {
                    EnsureInitialized();                    
                    return (sessionDictionary.Count > 0 ? false : true);
                }
            }
        }

        /// <summary>
        /// Gets a value indicating whether this SessionHolder
        /// does not hold non default session.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if does not hold non default session; otherwise, <c>false</c>.
        /// </value>
        public bool DoesNotHoldNonDefaultSession
        {
            get
            {
                lock (sessionDictionary.SyncRoot)
                {
                    EnsureInitialized();                    
                    return ( 
                        sessionDictionary.Count == 0 ||
                        (sessionDictionary.Count == 1 && sessionDictionary.Contains(DEFAULT_KEY)) 
                        );

                }
            }
        }

        /// <summary>
        /// Gets or sets the hibernate transaction.
        /// </summary>
        /// <value>The transaction.</value>
	    public ITransaction Transaction
	    {
	        get { return transaction; }
	        set { transaction = value; }
	    }

        /// <summary>
        /// Gets or sets the ADO.NET Connection used to create the session.
        /// </summary>
        /// <value>The ADO.NET connection.</value>
	    public IDbConnection Connection
	    {
	        get { return connection; }
	        set { connection = value; }
	    }

	    /// <summary>
        /// Gets or sets the previous flush mode.
        /// </summary>
        /// <value>The previous flush mode.</value>
	    public FlushMode PreviousFlushMode
	    {
	        get { return flushMode; }
	        set
	        {
	            flushMode = value;
                assignedPreviousFlushMode = true;
	        }
	    }

        /// <summary>
        /// Gets a value indicating whether the PreviousFlushMode property
        /// was set.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if assigned PreviousFlushMode property; otherwise, <c>false</c>.
        /// </value>
        public bool AssignedPreviousFlushMode
        {
            get
            {
                return assignedPreviousFlushMode;
            }
        }

        /// <summary>
        /// Gets the validated session.
        /// </summary>
        /// <value>The validated session.</value>
        public ISession ValidatedSession
        {
            get
            {
                return GetValidatedSession(DEFAULT_KEY);
            }
        }

	    #endregion

		#region Methods

        /// <summary>
        /// Gets the session given key identifier
        /// </summary>
        /// <param name="key">The key.</param>
        /// <returns>A hibernate session</returns>
        public ISession GetSession(object key)
        {
            lock (sessionDictionary.SyncRoot)
            {
                EnsureInitialized();                
                return sessionDictionary[key] as ISession;
            }
        }

        
        /// <summary>
        /// Gets the session given the key and removes the session from
        /// the dictionary storage.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <returns>A hibernate session</returns>
        public ISession GetValidatedSession(object key)
        {
            lock (sessionDictionary.SyncRoot)
            {
                EnsureInitialized();                
                ISession session = sessionDictionary[key] as ISession;
                // Check for dangling Session that's around but already closed.
                // Effectively an assertion: that should never happen in practice.
                // We'll seamlessly remove the Session here, to not let it cause
                // any side effects.
                if (session != null && !session.IsOpen) 
                {
                    sessionDictionary.Remove(key);
                    session = null;
                }
                return session;
            }
        }
        /// <summary>
        /// Adds the session to the dictionary storage using the default key.
        /// </summary>
        /// <param name="session">The hibernate session.</param>
        public void AddSession(ISession session)
        {
            AddSession(DEFAULT_KEY, session);
        }

        /// <summary>
        /// Adds the session to the dictionary storage using the supplied key.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <param name="session">The hibernate session.</param>
        public void AddSession(object key, ISession session)
        {
            AssertUtils.ArgumentNotNull(key, "key", "Key must not be null");
            AssertUtils.ArgumentNotNull(session, "session", "Session must not be null");

            lock (sessionDictionary.SyncRoot)
            {
                if (sessionDictionary.ContainsKey(key))
                {
                    log.Debug("Overwriting Session in SessionHolder with key = "+ key);
                }

                sessionDictionary[key] =  session;
            }
        }

        /// <summary>
        /// Removes the session from the dictionary storage for the given key.
        /// </summary>
        /// <param name="key">The key.</param>
        /// <returns>The session that was previously contained in the
        /// dictionary storage.</returns>
        public ISession RemoveSession(object key)
        {
            lock(sessionDictionary.SyncRoot)
            {
                ISession oldSession = sessionDictionary[key] as ISession;
                sessionDictionary.Remove(key);
                return oldSession;
            }
        }

        /// <summary>
        /// Determines whether the holder the specified session.
        /// </summary>
        /// <param name="session">The session.</param>
        /// <returns>
        /// 	<c>true</c> if the holder contains the specified session; otherwise, <c>false</c>.
        /// </returns>
        public bool ContainsSession(object session)
        {
            lock (sessionDictionary.SyncRoot)
            {
                EnsureInitialized();                
                return sessionDictionary.ContainsValue(session);
            }
        }

        /// <summary>
        /// Clear the transaction state of this resource holder.
        /// </summary>
        public override void Clear()
        {
            base.Clear();
            Transaction = null;
            assignedPreviousFlushMode = false;
            Connection = null;
        }

		#endregion

	}
}
