#region License

/*
 * Copyright © 2002-2006 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

#if (!NET_1_0)

#region Imports

using System;
using System.EnterpriseServices;
using System.Threading;
using Spring.Data.Support;
using Spring.Objects.Factory;
using Spring.Transaction;
using Spring.Transaction.Support;

#endregion

namespace Spring.Data.Core
{
    /// <summary>
    /// Transaction Manager that uses EnterpriseServices to access the
    /// MS-DTC.  It requires the support of 'Services without Components'
    /// functionality which is available on Win 2003 and Win XP SP2.
    /// </summary>
    /// <author>Mark Pollack (.NET)</author>
    public class ServiceDomainPlatformTransactionManager : AbstractPlatformTransactionManager, IInitializingObject
    {
        private IServiceDomainAdapter txAdapter;

        private bool trackingEnabled = true;
        private string trackingAppName = "Spring.NET";
        private string trackingComponentName = "ServiceDomainPlatformTransactionManager";

        #region Constructor (s)
        /// <summary>
        /// Initializes a new instance of the <see cref="ServiceDomainPlatformTransactionManager"/> class.
        /// </summary>
        public 	ServiceDomainPlatformTransactionManager()
        {

        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ServiceDomainPlatformTransactionManager"/> class.
        /// </summary>
        /// <remarks>This is indented only for unit testing purposes and should not be
        /// called by production application code.</remarks>
        /// <param name="txAdapter">The tx adapter.</param>
        public ServiceDomainPlatformTransactionManager(IServiceDomainAdapter txAdapter)
        {
            this.txAdapter = txAdapter;
        }

        #endregion


        /// <summary>
        /// Gets or sets a value indicating whether tracking is enabled.
        /// </summary>
        /// <value><c>true</c> if tracking is enabled; otherwise, <c>false</c>.</value>
        public bool TrackingEnabled
        {
            get { return trackingEnabled; }
            set { trackingEnabled = value; }
        }

        /// <summary>
        /// Gets or sets a text string that corresponds to the application ID under which tracker information is reported.
        /// The default value is 'Spring.NET'
        /// </summary>
        /// <value>The name of the tracking app.</value>
        public string TrackingAppName
        {
            get { return trackingAppName; }
            set { trackingAppName = value; }
        }

        /// <summary>
        /// Gets or sets a text string that corresponds to the context name under which tracker information is reported. 
        /// </summary>
        /// <value>The name of the tracking component.</value>
        public string TrackingComponentName
        {
            get { return trackingComponentName; }
            set { trackingComponentName = value; }
        }

        public void AfterPropertiesSet()
        {
            //TODO decide what should be validated for advanced configurations.
        }

        protected override object DoGetTransaction()
        {
            ServiceDomainTransactionObject txObject = new ServiceDomainTransactionObject();
            if (txAdapter != null)
            {
                txObject.ServiceDomainAdapter = txAdapter;
            }
            return txObject;
        }



        protected override bool IsExistingTransaction(object transaction)
        {
            ServiceDomainTransactionObject txObject =  (ServiceDomainTransactionObject)transaction;
            if (txObject.ServiceDomainAdapter.IsInTransaction)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        protected override void DoBegin(object transaction, ITransactionDefinition definition)
        {
            try
            {
                ServiceDomainTransactionObject txObject = (ServiceDomainTransactionObject)transaction;

                DoServiceDomainBegin(txObject, definition);
            }
            catch (PlatformNotSupportedException ex)
            {
                throw new TransactionSystemException("ServiceDomain failure on begin of transaction.  Platform does not support EnterpriseServices 'Services without Components'", ex);
            }
            catch (Exception e)
            {
                throw new CannotCreateTransactionException("ServiceDomain failure on begin of transaction", e);
            }
        }

        private void DoServiceDomainBegin(ServiceDomainTransactionObject serviceDomainTxObject, ITransactionDefinition definition)
        {
            SimpleServiceConfig serviceConfig = CreateServiceConfig(definition);
            //The context is created when we call Enter.
            serviceDomainTxObject.ServiceDomainAdapter.Enter(serviceConfig);
            if (log.IsDebugEnabled)
            {
                log.Debug("Context created. TransactionId = " + ContextUtil.TransactionId
                          + ", ActivityId = " + ContextUtil.ActivityId);
            }
        }

        protected override object DoSuspend(object transaction)
        {
            // Passing the current transaction object, literally an 'object' as the 'suspended resource', 
            // even though it is not used just to avoid passing null
            // ServiceDomainPlatformTransactionManager is not binding any resources to the local thread, instead delegating to
            // System.EnterpriseServices to handle thread local resources.
            return transaction;
        }

        protected override void DoResume(object transaction, object suspendedResources)
        {
        }

        private SimpleServiceConfig CreateServiceConfig(ITransactionDefinition definition)
        {
            SimpleServiceConfig serviceConfig = new SimpleServiceConfig();

            //TODO investigate BringYourOwnTransaction and TipUrl properties of ServiceConfig
            serviceConfig.TransactionDescription = definition.Name;

            serviceConfig.TrackingEnabled = TrackingEnabled;
            serviceConfig.TrackingAppName = TrackingAppName;
            serviceConfig.TrackingComponentName = TrackingComponentName;

            ApplyPropagationBehavior(serviceConfig, definition);

            ApplyIsolationLevel(serviceConfig, definition);
            
            // infinite==-1 would cause transactions to be aborted immediately!
            if(definition.TransactionTimeout != Timeout.Infinite)
            {
                serviceConfig.TransactionTimeout = definition.TransactionTimeout;
            }
            return serviceConfig;
        }

        protected void ApplyIsolationLevel(SimpleServiceConfig serviceConfig, ITransactionDefinition definition)
        {
            switch (definition.TransactionIsolationLevel)
            {
                    
                case System.Data.IsolationLevel.Chaos:
                    if (log.IsInfoEnabled)
                    {
                        log.Info("IsolationLevel Chaos does not have a direct counterpart in EnterpriseServices, using Any");
                    }   
                    serviceConfig.IsolationLevel = TransactionIsolationLevel.Any;
                    break;
                case System.Data.IsolationLevel.ReadCommitted:
                    serviceConfig.IsolationLevel = TransactionIsolationLevel.ReadCommitted;
                    break;
                case System.Data.IsolationLevel.ReadUncommitted:
                    serviceConfig.IsolationLevel = TransactionIsolationLevel.ReadUncommitted;
                    break;
                case System.Data.IsolationLevel.RepeatableRead:
                    serviceConfig.IsolationLevel = TransactionIsolationLevel.RepeatableRead;
                    break;
                case System.Data.IsolationLevel.Serializable:
                    serviceConfig.IsolationLevel = TransactionIsolationLevel.Serializable;
                    break;
#if NET_2_0
                case System.Data.IsolationLevel.Snapshot:
                    if (log.IsInfoEnabled)
                    {
                        log.Info("IsolationLevel Snapshot does not have a direct counterpart in EnterpriseServices, using ReadCommitted.  Introduced in SqlServer 2005.  Consider using System.Transactions for transaction management instead."); 
                    }   
                    serviceConfig.IsolationLevel = TransactionIsolationLevel.ReadCommitted;  //err on the side of consistency
                    break;
#endif
                case System.Data.IsolationLevel.Unspecified:
                    serviceConfig.IsolationLevel = TransactionIsolationLevel.Any;
                    break;
            }

        }

        protected void ApplyPropagationBehavior(SimpleServiceConfig serviceConfig, ITransactionDefinition definition)
        {
            if (definition.PropagationBehavior == TransactionPropagation.Required)
            {
                serviceConfig.TransactionOption = TransactionOption.Required;
            }
            else if (definition.PropagationBehavior == TransactionPropagation.RequiresNew)
            {
                serviceConfig.TransactionOption = TransactionOption.RequiresNew;
            }
            else if (definition.PropagationBehavior == TransactionPropagation.Supports)
            {
                serviceConfig.TransactionOption = TransactionOption.Supported;
            }
            else if (definition.PropagationBehavior == TransactionPropagation.NotSupported)
            {
                serviceConfig.TransactionOption = TransactionOption.NotSupported;
            }
            else if (definition.PropagationBehavior == TransactionPropagation.Never)
            {
                //TODO check the validity of this mapping
                serviceConfig.TransactionOption = TransactionOption.Disabled;
            } else
            {
                //TODO Should we throw an exception instead?
                log.Warn("The requested transaction propagation option " +
                         definition.PropagationBehavior + " is not supported.  " +
                         "Defaulting to Never(Disabled) ");
            }
        }




        protected override void DoCommit(DefaultTransactionStatus status)
        {
            ServiceDomainTransactionObject txObject = (ServiceDomainTransactionObject) status.Transaction;
            bool globalRollbackOnly = status.GlobalRollbackOnly;
            try
            {
                if (txObject.ServiceDomainAdapter.IsInTransaction)
                {

                    if (txObject.ServiceDomainAdapter.MyTransactionVote == TransactionVote.Commit)
                    {
                        txObject.ServiceDomainAdapter.SetComplete();
                    }
                    else
                    {
                        txObject.ServiceDomainAdapter.SetAbort();
                    }                                  
                }
                TransactionStatus serviceDomainTxstatus = txObject.ServiceDomainAdapter.Leave();
                if (log.IsDebugEnabled)
                {
                    log.Debug("ServiceDomain Transaction Status upon leaving ServiceDomain = " + serviceDomainTxstatus);
                }
                txObject.TransactionStatus = serviceDomainTxstatus;
                if (!globalRollbackOnly && serviceDomainTxstatus == TransactionStatus.Aborted)
                {
                    throw new UnexpectedRollbackException("Transaction unexpectedly rolled-back (maybe due to a timeout)");
                }
            }
            catch (PlatformNotSupportedException ex)
            {
                throw new TransactionSystemException("Failure on Commit.  Platform does not support EnterpriseServices 'Services without Components'", ex); 
            }
            catch (Exception e)
            {
                throw new TransactionSystemException("Failure upon Leaving ServiceDomain (for Commit)", e);
            }

        }

        protected override void DoRollback(DefaultTransactionStatus status)
        {
            ServiceDomainTransactionObject txObject = (ServiceDomainTransactionObject)status.Transaction;
            if (txObject.ServiceDomainAdapter.IsInTransaction)
            {
                try
                {
                    txObject.ServiceDomainAdapter.SetAbort();
                    txObject.ServiceDomainAdapter.Leave();
                }
                catch (PlatformNotSupportedException ex)
                {
                    throw new TransactionSystemException("Failure on Rollback.  Platform does not support EnterpriseServices 'Services without Components'", ex);
                }
                catch (Exception e)
                {
                    throw new Spring.Transaction.TransactionSystemException("Failure upon Leaving ServiceDomain (for Rollback)", e);
                }
            }
        }

        protected override void DoSetRollbackOnly(DefaultTransactionStatus status)
        {
            ServiceDomainTransactionObject txObject = (ServiceDomainTransactionObject)status.Transaction;
            if (status.Debug)
            {
                log.Debug("Setting transaction rollback-only");
            }
            try
            {
                txObject.ServiceDomainAdapter.MyTransactionVote = TransactionVote.Abort;
            }
            catch (Exception ex)
            {
                throw new TransactionSystemException("Failure on System.Transactions.Transaction.Current.Rollback", ex);
            }
           
        }

        protected override bool ShouldCommitOnGlobalRollbackOnly
        {
            get { return true; }
        }


        public class ServiceDomainTransactionObject : ISmartTransactionObject
        {
            private TransactionStatus transactionStatus;
            private IServiceDomainAdapter serviceDomainAdapter;


            public ServiceDomainTransactionObject()
            {
                serviceDomainAdapter = new DefaultServiceDomainAdapter();
            }

            public IServiceDomainAdapter ServiceDomainAdapter
            {
                get { return serviceDomainAdapter; }
                set { serviceDomainAdapter = value; }
            }

            public TransactionStatus TransactionStatus
            {
                get { return transactionStatus; }
                set { transactionStatus = value; }
            }

            public bool RollbackOnly
            {
                get
                {
                    if ( serviceDomainAdapter.MyTransactionVote == TransactionVote.Abort)
                    {
                        return true;
                    }
                    else
                    {
                        return false;
                    }
                }
            }
        }
    }
}

#endif // (!NET_1_0)
