File: System\Transactions\TransactionManager.cs
Project: ndp\cdf\src\NetFx20\System.Transactions\System.Transactions.csproj (System.Transactions)
//-----------------------------------------------------------------------------
// <copyright file="TransactionManager.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//-----------------------------------------------------------------------------
 
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Configuration;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Security;
using System.Security.Permissions;
using System.Threading;
using System.Transactions.Configuration;
using System.Transactions.Diagnostics;
using System.Xml;
using System.Xml.Serialization;
 
[assembly:BestFitMapping(false)]
#pragma warning disable 618
[assembly:SecurityPermissionAttribute(SecurityAction.RequestMinimum)]
#pragma warning restore 618
 
// No external data is ever passed to PrivilegedConfigurationManager.GetSection
[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2106:SecureAsserts", 
            Scope = "member", 
            Target = "System.Configuration.PrivilegedConfigurationManager.GetSection(System.String):System.Object")]
 
namespace System.Transactions
{
    public delegate Transaction HostCurrentTransactionCallback();
 
    public delegate void TransactionStartedEventHandler(object sender, TransactionEventArgs e);
 
    public static class TransactionManager
    {
        // Revovery Information Version
        private const Int32 recoveryInformationVersion1 = 1;
        private const Int32 currentRecoveryVersion = recoveryInformationVersion1;
        
        // Record if the platform has been validated.
        internal static bool _platformValidated;
 
        // Hashtable of promoted transactions, keyed by identifier guid.  This is used by
        // FindPromotedTransaction to support transaction equivalence when a transaction is
        // serialized and then deserialized back in this app-domain.
        // Double-checked locking pattern requires volatile for read/write synchronization
        private static volatile Hashtable promotedTransactionTable;
 
        // Sorted Table of transaction timeouts
        // Double-checked locking pattern requires volatile for read/write synchronization
        private static volatile TransactionTable transactionTable;
 
        private static TransactionStartedEventHandler distributedTransactionStartedDelegate;
        public static event TransactionStartedEventHandler DistributedTransactionStarted
        {
            [System.Security.Permissions.PermissionSetAttribute(System.Security.Permissions.SecurityAction.LinkDemand, Name = "FullTrust")]
            add
            {
                if ( !TransactionManager._platformValidated ) TransactionManager.ValidatePlatform();
 
                lock ( ClassSyncObject )
                {
                    TransactionManager.distributedTransactionStartedDelegate = (TransactionStartedEventHandler)System.Delegate.Combine(TransactionManager.distributedTransactionStartedDelegate, value);
                    if ( value != null )
                    {
                        ProcessExistingTransactions( value );
                    }
                }
            }
 
            [System.Security.Permissions.PermissionSetAttribute(System.Security.Permissions.SecurityAction.LinkDemand, Name = "FullTrust")]
            remove
            {
                if ( !TransactionManager._platformValidated ) TransactionManager.ValidatePlatform();
 
                lock ( ClassSyncObject )
                {
                    TransactionManager.distributedTransactionStartedDelegate = (TransactionStartedEventHandler)System.Delegate.Remove (TransactionManager.distributedTransactionStartedDelegate, value);
                }
            }
        }
 
 
        internal static void ProcessExistingTransactions( TransactionStartedEventHandler eventHandler )
        {
            lock ( PromotedTransactionTable )
            {
                foreach ( DictionaryEntry entry in PromotedTransactionTable )
                {
                    WeakReference weakRef = (WeakReference)entry.Value;
                    Transaction tx = (Transaction)weakRef.Target;
                    if ( tx != null )
                    {
                        TransactionEventArgs args = new TransactionEventArgs();
                        args.transaction = tx.InternalClone();
                        eventHandler( args.transaction, args );
                    }
                }
            }
        }
 
 
        internal static void FireDistributedTransactionStarted( Transaction transaction )
        {
            TransactionStartedEventHandler localStartedEventHandler = null;
            lock ( ClassSyncObject )
            {
                localStartedEventHandler = TransactionManager.distributedTransactionStartedDelegate;
            }
 
            if ( null != localStartedEventHandler )
            {
                TransactionEventArgs args = new TransactionEventArgs();
                args.transaction = transaction.InternalClone();
                localStartedEventHandler(args.transaction, args);
            }
        }
 
 
        // Data storage for current delegate
        internal static HostCurrentTransactionCallback currentDelegate = null;
        internal static bool currentDelegateSet = false;
 
        // CurrentDelegate
        //
        // Store a delegate to be used to query for an external current transaction.
        public static HostCurrentTransactionCallback HostCurrentCallback
        {
            // get_HostCurrentCallback is used from get_CurrentTransaction, which doesn't have any permission requirements.
            // We don't expose what is returned from this property in that case.  But we don't want just anybody being able
            // to retrieve the value.
            [System.Security.Permissions.PermissionSetAttribute(System.Security.Permissions.SecurityAction.LinkDemand, Name = "FullTrust")]
            get
            {
                if ( !TransactionManager._platformValidated ) TransactionManager.ValidatePlatform();
 
                // Note do not add trace notifications to this method.  It is called
                // at the startup of SQLCLR and tracing has too much working set overhead.
                return currentDelegate;
            }
 
            [System.Security.Permissions.PermissionSetAttribute(System.Security.Permissions.SecurityAction.LinkDemand, Name = "FullTrust")]
            set
            {
                if ( !TransactionManager._platformValidated ) TransactionManager.ValidatePlatform();
 
                // Note do not add trace notifications to this method.  It is called
                // at the startup of SQLCLR and tracing has too much working set overhead.
                if ( value == null )
                {
                    throw new ArgumentNullException( "value" );
                }
 
                lock ( ClassSyncObject )
                {
                    if ( currentDelegateSet )
                    {
                        throw new InvalidOperationException( SR.GetString( SR.CurrentDelegateSet ));
                    }
                    currentDelegateSet = true;
                }
 
                currentDelegate = value;
            }
        }
 
 
        [System.Security.Permissions.PermissionSetAttribute(System.Security.Permissions.SecurityAction.LinkDemand, Name = "FullTrust")]
        static public Enlistment Reenlist(
            Guid resourceManagerIdentifier,
            byte[] recoveryInformation,
            IEnlistmentNotification enlistmentNotification
            )
        {
            if ( resourceManagerIdentifier == Guid.Empty )
            {
                throw new ArgumentException( SR.GetString( SR.BadResourceManagerId ), "resourceManagerIdentifier" );
            }
 
            if ( null == recoveryInformation )
            {
                throw new ArgumentNullException( "recoveryInformation" );
            }
 
            if ( null == enlistmentNotification )
            {
                throw new ArgumentNullException( "enlistmentNotification" );
            }
 
            if ( !TransactionManager._platformValidated ) TransactionManager.ValidatePlatform();
 
            if ( DiagnosticTrace.Verbose )
            {
                MethodEnteredTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
                    "TransactionManager.Reenlist"
                    );
            }
 
            if ( DiagnosticTrace.Information )
            {
                ReenlistTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
                    resourceManagerIdentifier
                    );
            }
 
            // Put the recovery information into a stream.
            MemoryStream stream = new MemoryStream( recoveryInformation );
            int recoveryInformationVersion = 0;
            string nodeName = null;
            byte[] resourceManagerRecoveryInformation = null;
            
            try
            {
                BinaryReader reader = new BinaryReader( stream );
                recoveryInformationVersion = reader.ReadInt32();
 
                if ( recoveryInformationVersion == TransactionManager.recoveryInformationVersion1 )
                {
                    nodeName = reader.ReadString();
 
                    resourceManagerRecoveryInformation = reader.ReadBytes( recoveryInformation.Length - checked((int)stream.Position) );
                }
                else
                {
                    if ( DiagnosticTrace.Error )
                    {
                        TransactionExceptionTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
                            SR.GetString( SR.UnrecognizedRecoveryInformation )
                            );
                    }
                    throw new ArgumentException( SR.GetString( SR.UnrecognizedRecoveryInformation ), "recoveryInformation" );
                }
            }
            catch ( System.IO.EndOfStreamException e )
            {
                if ( DiagnosticTrace.Error )
                {
                    TransactionExceptionTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
                        SR.GetString( SR.UnrecognizedRecoveryInformation )
                        );
                }
                throw new ArgumentException( SR.GetString( SR.UnrecognizedRecoveryInformation ), "recoveryInformation", e);
            }
            catch ( System.FormatException e )
            {
                if ( DiagnosticTrace.Error )
                {
                    TransactionExceptionTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
                        SR.GetString( SR.UnrecognizedRecoveryInformation )
                        );
                }
                throw new ArgumentException( SR.GetString( SR.UnrecognizedRecoveryInformation ), "recoveryInformation", e);
            }
            finally
            {
                stream.Close();
            }
 
            Oletx.OletxTransactionManager transactionManager = CheckTransactionManager(nodeName);
 
            // Now ask the Transaction Manager to reenlist.
            object syncRoot = new object();
            Enlistment returnValue = new Enlistment( enlistmentNotification, syncRoot );
            EnlistmentState._EnlistmentStatePromoted.EnterState( returnValue.InternalEnlistment );
 
            returnValue.InternalEnlistment.PromotedEnlistment = 
                transactionManager.ReenlistTransaction(
                    resourceManagerIdentifier,
                    resourceManagerRecoveryInformation,
                    (RecoveringInternalEnlistment)returnValue.InternalEnlistment
                    );
 
            if ( DiagnosticTrace.Verbose )
            {
                MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
                    "TransactionManager.Reenlist"
                    );
            }
 
            return returnValue;
        }
 
 
        private static Oletx.OletxTransactionManager CheckTransactionManager(string nodeName)
        {
            Oletx.OletxTransactionManager tm = DistributedTransactionManager;
            if ( !((tm.NodeName == null && (nodeName == null || nodeName.Length == 0)) || 
                  (tm.NodeName != null && tm.NodeName.Equals(nodeName))) )
            {
                throw new ArgumentException( SR.GetString(SR.InvalidRecoveryInformation), "recoveryInformation" );
            }
            return tm;
        }
 
 
        [System.Security.Permissions.PermissionSetAttribute(System.Security.Permissions.SecurityAction.LinkDemand, Name = "FullTrust")]
        static public void RecoveryComplete(
            Guid resourceManagerIdentifier
            )
        {
            if ( resourceManagerIdentifier == Guid.Empty )
            {
                throw new ArgumentException( SR.GetString( SR.BadResourceManagerId ), "resourceManagerIdentifier" );
            }
 
            if ( DiagnosticTrace.Verbose )
            {
                MethodEnteredTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
                    "TransactionManager.RecoveryComplete"
                    );
            }
 
            if ( DiagnosticTrace.Information )
            {
                RecoveryCompleteTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
                    resourceManagerIdentifier
                    );
            }
 
            DistributedTransactionManager.ResourceManagerRecoveryComplete( resourceManagerIdentifier );
 
            if ( DiagnosticTrace.Verbose )
            {
                MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
                    "TransactionManager.RecoveryComplete"
                    );
            }
        }
 
        
        // Object for synchronizing access to the entire class( avoiding lock( typeof( ... )) )
        private static object classSyncObject;
        
        // Helper object for static synchronization
        static object ClassSyncObject
        {
            get
            {
                if ( classSyncObject == null )
                {
                    object o = new object();
                    Interlocked.CompareExchange( ref classSyncObject, o, null );
                }
                return classSyncObject;
            }
        }
 
 
        internal static System.Transactions.IsolationLevel DefaultIsolationLevel
        {
            get
            {
                if ( DiagnosticTrace.Verbose )
                {
                    MethodEnteredTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
                        "TransactionManager.get_DefaultIsolationLevel"
                        );
                    MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
                        "TransactionManager.get_DefaultIsolationLevel"
                        );
                }
 
                return IsolationLevel.Serializable;
            }
        }
 
 
        static DefaultSettingsSection defaultSettings;
        static DefaultSettingsSection DefaultSettings
        {
            get
            {
                if ( defaultSettings == null )
                {
                    defaultSettings = DefaultSettingsSection.GetSection();
                }
 
                return defaultSettings;
            }
        }
 
 
        static MachineSettingsSection machineSettings;
        static MachineSettingsSection MachineSettings
        {
            get
            {
                if ( machineSettings == null )
                {
                    machineSettings = MachineSettingsSection.GetSection();
                }
 
                return machineSettings;
            }
        }
 
 
        private static bool _defaultTimeoutValidated;
        private static TimeSpan _defaultTimeout;
        public static TimeSpan DefaultTimeout
        {
            get
            {
                if ( !TransactionManager._platformValidated ) TransactionManager.ValidatePlatform();
 
                if ( DiagnosticTrace.Verbose )
                {
                    MethodEnteredTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
                        "TransactionManager.get_DefaultTimeout"
                        );
                }
 
                if ( !_defaultTimeoutValidated )
                {
                    _defaultTimeout = ValidateTimeout( DefaultSettings.Timeout );
                    // If the timeout value got adjusted, it must have been greater than MaximumTimeout.
                    if ( _defaultTimeout != DefaultSettings.Timeout )
                    {
                        if ( DiagnosticTrace.Warning )
                        {
                            ConfiguredDefaultTimeoutAdjustedTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ) );
                        }
                    }
                    _defaultTimeoutValidated = true;
                }
 
                if ( DiagnosticTrace.Verbose )
                {
                    MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
                        "TransactionManager.get_DefaultTimeout"
                        );
                }
                return _defaultTimeout;
            }
        }
 
 
        // Double-checked locking pattern requires volatile for read/write synchronization
        private static volatile bool _cachedMaxTimeout;
        private static TimeSpan _maximumTimeout;
        public static TimeSpan MaximumTimeout
        {
            get
            {
                if ( !TransactionManager._platformValidated ) TransactionManager.ValidatePlatform();
 
                if ( DiagnosticTrace.Verbose )
                {
                    MethodEnteredTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
                        "TransactionManager.get_DefaultMaximumTimeout"
                        );
                }
 
                if ( !_cachedMaxTimeout )
                {
                    lock ( ClassSyncObject )
                    {
                        if ( !_cachedMaxTimeout )
                        {
                            TimeSpan temp = MachineSettings.MaxTimeout;
                            _maximumTimeout = temp;
                            _cachedMaxTimeout = true;
                        }
                    }
                }
 
                if ( DiagnosticTrace.Verbose )
                {
                    MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
                        "TransactionManager.get_DefaultMaximumTimeout"
                        );
                }
 
                return _maximumTimeout;
            }
        }
 
 
        // This routine writes the "header" for the recovery information, based on the
        // type of the calling object and its provided parameter collection.  This information
        // we be read back by the static Reenlist method to create the necessary transaction
        // manager object with the right parameters in order to do a ReenlistTransaction call.
        internal static byte[] GetRecoveryInformation(
            string startupInfo,
            byte[] resourceManagerRecoveryInformation
            )
        {
            if ( DiagnosticTrace.Verbose )
            {
                MethodEnteredTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
                    "TransactionManager.GetRecoveryInformation"
                    );
            }
 
            MemoryStream stream = new MemoryStream();
            byte[] returnValue = null;
 
            try
            {
                // Manually write the recovery information
                BinaryWriter writer = new BinaryWriter( stream );
 
                writer.Write( TransactionManager.currentRecoveryVersion );
                if ( startupInfo != null )
                {
                    writer.Write( startupInfo );
                }
                else
                {
                    writer.Write( "" );
                }
                writer.Write( resourceManagerRecoveryInformation );
                writer.Flush();
                returnValue = stream.ToArray();
            }
            finally
            {
                stream.Close();
            }
 
            if ( DiagnosticTrace.Verbose )
            {
                MethodExitedTraceRecord.Trace( SR.GetString( SR.TraceSourceBase ),
                    "TransactionManager.GetRecoveryInformation"
                    );
            }
 
            return returnValue;
        }
 
        internal static byte[] ConvertToByteArray( object thingToConvert )
        {
            MemoryStream streamToWrite = new MemoryStream();
            byte[] returnValue = null;
            
            try
            {
                // First seralize the type to the stream.
                IFormatter formatter = new BinaryFormatter();
                formatter.Serialize( streamToWrite, thingToConvert );
    
                returnValue = new byte[streamToWrite.Length];
    
                streamToWrite.Position = 0;
                streamToWrite.Read( returnValue, 0, Convert.ToInt32( streamToWrite.Length, CultureInfo.InvariantCulture ) );
            }
            finally
            {
                streamToWrite.Close();
            }
 
            return returnValue;
       }
        
        /// <summary>
        /// This static function throws an ArgumentOutOfRange if the specified IsolationLevel is not within
        /// the range of valid values.
        /// </summary>
        /// <param name="transactionIsolationLevel">
        /// The IsolationLevel value to validate.
        /// </param>
        internal static void ValidateIsolationLevel( IsolationLevel transactionIsolationLevel )
        {
            switch ( transactionIsolationLevel )
            {
                case IsolationLevel.Serializable:
                case IsolationLevel.RepeatableRead:
                case IsolationLevel.ReadCommitted:
                case IsolationLevel.ReadUncommitted:
                case IsolationLevel.Unspecified:
                case IsolationLevel.Chaos:
                case IsolationLevel.Snapshot:
                    break;
                default:
                {
                    throw new ArgumentOutOfRangeException( "transactionIsolationLevel" );
                }
            }
        }
 
 
        /// <summary>
        /// This static function throws an ArgumentOutOfRange if the specified TimeSpan does not meet
        /// requirements of a valid transaction timeout.  Timeout values must be positive.
        /// </summary>
        /// <param name="transactionTimeout">
        /// The TimeSpan value to validate.
        /// </param>
        internal static TimeSpan ValidateTimeout( TimeSpan transactionTimeout )
        {
            if ( transactionTimeout < TimeSpan.Zero )
            {
                throw new ArgumentOutOfRangeException( "transactionTimeout" );
            }
 
            if ( TransactionManager.MaximumTimeout != TimeSpan.Zero )
            {
                if ( transactionTimeout > TransactionManager.MaximumTimeout || transactionTimeout == TimeSpan.Zero )
                {
                    return TransactionManager.MaximumTimeout;
                }
            }
 
            return transactionTimeout;
        }
 
 
        internal static Transaction FindPromotedTransaction(
            Guid transactionIdentifier
            )
        {
            Hashtable promotedTransactionTable = PromotedTransactionTable;
                WeakReference weakRef = (WeakReference) promotedTransactionTable[transactionIdentifier];
                if ( null != weakRef )
                {
                Transaction tx = weakRef.Target as Transaction;
                    if ( null != tx )
                    {
                        return tx.InternalClone();
                    }
                    else  // an old, moldy weak reference.  Let's get rid of it.
                    {
                        lock ( promotedTransactionTable )
                        {
                            promotedTransactionTable.Remove( transactionIdentifier );
                        }
                    }
                }
 
            return null;
        }
 
 
        internal static Transaction FindOrCreatePromotedTransaction(
            Guid transactionIdentifier,
            Oletx.OletxTransaction oletx
            )
        {
            Transaction tx = null;
            Hashtable promotedTransactionTable = PromotedTransactionTable;
            lock (promotedTransactionTable)
            {
                WeakReference weakRef = (WeakReference) promotedTransactionTable[transactionIdentifier];
                if ( null != weakRef )
                {
                    tx = weakRef.Target as Transaction;
                    if ( null != tx )
                    {
                        // If we found a transaction then dispose the oletx
                        oletx.Dispose();
                        return tx.InternalClone();
                    }
                    else  // an old, moldy weak reference.  Let's get rid of it.
                    {
                        lock ( promotedTransactionTable )
                        {
                            promotedTransactionTable.Remove( transactionIdentifier );
                        }
                    }
                }
 
                tx = new Transaction( oletx );
 
                // Since we are adding this reference to the table create an object that will clean that
                // entry up.
                tx.internalTransaction.finalizedObject = new FinalizedObject( tx.internalTransaction, oletx.Identifier );
 
                weakRef = new WeakReference( tx, false );
                promotedTransactionTable[oletx.Identifier] = weakRef;
            }
            oletx.savedLtmPromotedTransaction = tx;
 
            TransactionManager.FireDistributedTransactionStarted( tx );
 
            return tx;
        }
 
 
        // Table for promoted transactions
        internal static Hashtable PromotedTransactionTable
        {
            get
            {
                if ( promotedTransactionTable == null )
                {
                    lock ( ClassSyncObject )
                    {
                        if ( promotedTransactionTable == null )
                        {
                            Hashtable temp = new Hashtable( 100 );
                            promotedTransactionTable = temp;
                        }
                    }
                }
 
                return promotedTransactionTable;
            }
        }
 
 
        // Table for transaction timeouts
        internal static TransactionTable TransactionTable
        {
            get
            {
                if ( transactionTable == null )
                {
                    lock ( ClassSyncObject )
                    {
                        if ( transactionTable == null )
                        {
                            TransactionTable temp = new TransactionTable();
                            transactionTable = temp;
                        }
                    }
                }
 
                return transactionTable;
            }
        }
 
 
        // Fault in a DistributedTransactionManager if one has not already been created.
        // Double-checked locking pattern requires volatile for read/write synchronization
        internal static volatile Oletx.OletxTransactionManager distributedTransactionManager;
        internal static Oletx.OletxTransactionManager DistributedTransactionManager
        {
            get
            {
                // If the distributed transaction manager is not configured, throw an exception
                if ( distributedTransactionManager == null )
                {
                    lock ( ClassSyncObject )
                    {
                        if ( distributedTransactionManager == null )
                        {
                            Oletx.OletxTransactionManager temp = new Oletx.OletxTransactionManager( 
                                DefaultSettings.DistributedTransactionManagerName);
                            distributedTransactionManager = temp;
                        }
                    }
                }
 
                return distributedTransactionManager;
            }
        }
 
 
        internal static void ValidatePlatform()
        {
            if ( PlatformID.Win32NT != Environment.OSVersion.Platform )
            {
                throw new PlatformNotSupportedException( SR.GetString( SR.OnlySupportedOnWinNT ) );
            }
 
            // Note that this is purposly not synchronized because who cares if there is a ---- to set it.
            _platformValidated = true;       
        }
 
    }
}