File: System\ServiceModel\ComIntegration\TransactionProxy.cs
Project: ndp\cdf\src\WCF\ServiceModel\System.ServiceModel.csproj (System.ServiceModel)
//-----------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//-----------------------------------------------------------------------------
namespace System.ServiceModel.ComIntegration
{
    using System;
    using System.ServiceModel;
    using System.Transactions;
    using System.Diagnostics;
    using System.ServiceModel.Diagnostics;
    using System.Runtime.InteropServices;
    using SR = System.ServiceModel.SR;
 
    class TransactionProxyBuilder : IProxyCreator
    {
        ComProxy comProxy = null;
        TransactionProxy txProxy = null;
 
        private TransactionProxyBuilder(TransactionProxy proxy)
        {
            this.txProxy = proxy;
        }
        void IDisposable.Dispose()
        {
 
        }
 
        ComProxy IProxyCreator.CreateProxy(IntPtr outer, ref Guid riid)
        {
            if ((riid != typeof(ITransactionProxy).GUID))
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidCastException(SR.GetString(SR.NoInterface, riid)));
            if (outer == IntPtr.Zero)
            {
                // transactions require failfasts to prevent corruption
                DiagnosticUtility.FailFast("OuterProxy cannot be null");
            }
 
            if (comProxy == null)
            {
                comProxy = ComProxy.Create(outer, txProxy, null);
                return comProxy;
 
            }
            else
                return comProxy.Clone();
        }
 
        bool IProxyCreator.SupportsErrorInfo(ref Guid riid)
        {
            if ((riid != typeof(ITransactionProxy).GUID))
                return false;
            else
                return true;
 
        }
 
        bool IProxyCreator.SupportsDispatch()
        {
            return false;
        }
 
        bool IProxyCreator.SupportsIntrinsics()
        {
            return false;
        }
 
        public static IntPtr CreateTransactionProxyTearOff(TransactionProxy txProxy)
        {
            IProxyCreator txProxyBuilder = new TransactionProxyBuilder(txProxy);
            IProxyManager proxyManager = new ProxyManager(txProxyBuilder);
            Guid iid = typeof(ITransactionProxy).GUID;
            return OuterProxyWrapper.CreateOuterProxyInstance(proxyManager, ref iid);
        }
 
    }
 
    class TransactionProxy : ITransactionProxy,
                             IExtension<InstanceContext>
    {
        Transaction currentTransaction;
        VoterBallot currentVoter;
        object syncRoot;
        Guid appid;
        Guid clsid;
        int instanceID = 0;
 
        public TransactionProxy(Guid appid, Guid clsid)
        {
            this.syncRoot = new object();
            this.appid = appid;
            this.clsid = clsid;
        }
 
        public Transaction CurrentTransaction
        {
            get
            {
                return this.currentTransaction;
            }
        }
        public Guid AppId
        {
            get
            {
                return this.appid;
            }
        }
        public Guid Clsid
        {
            get
            {
                return this.clsid;
            }
        }
        public int InstanceID
        {
            get
            {
                return this.instanceID;
            }
            set
            {
                this.instanceID = value;
            }
        }
        public void SetTransaction(Transaction transaction)
        {
            lock (this.syncRoot)
            {
                if (transaction == null)
                {
                    // transactions require failfasts to prevent corruption
                    DiagnosticUtility.FailFast("Attempting to set transaction to NULL");
                }
 
                if (this.currentTransaction == null)
                {
                    ProxyEnlistment enlistment;
                    enlistment = new ProxyEnlistment(this, transaction);
                    transaction.EnlistVolatile(enlistment, EnlistmentOptions.None);
                    this.currentTransaction = transaction;
                    if (this.currentVoter != null)
                    {
                        this.currentVoter.SetTransaction(this.currentTransaction);
                    }
                }
                else if (this.currentTransaction != transaction)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(Error.TransactionMismatch());
                }
            }
        }
 
        // IExtension<ServiceInstance>
        public void Attach(InstanceContext owner) { }
        public void Detach(InstanceContext owner) { }
 
        // ITransactionProxy
        public void Commit(Guid guid)
        {
            // transactions require failfasts to prevent corruption
            DiagnosticUtility.FailFast("Commit not supported: BYOT only!");
        }
 
        public void Abort()
        {
            if (this.currentTransaction != null)
            {
                this.currentTransaction.Rollback();
            }
        }
 
        public IDtcTransaction Promote()
        {
            EnsureTransaction();
            return TransactionInterop.GetDtcTransaction(
                this.currentTransaction);
        }
 
        public void CreateVoter(
            ITransactionVoterNotifyAsync2 voterNotification,
            IntPtr voterBallot)
        {
            if (IntPtr.Zero == voterBallot)
                throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("voterBallot");
 
            lock (this.syncRoot)
            {
                if (this.currentVoter != null)
                {
                    // transactions require failfasts to prevent corruption
                    DiagnosticUtility.FailFast("Assumption: proxy only needs one voter");
                }
 
                VoterBallot voter = new VoterBallot(voterNotification, this);
                if (this.currentTransaction != null)
                {
                    voter.SetTransaction(this.currentTransaction);
                }
 
                this.currentVoter = voter;
 
                IntPtr ppv = InterfaceHelper.GetInterfacePtrForObject(typeof(ITransactionVoterBallotAsync2).GUID, this.currentVoter);
 
                Marshal.WriteIntPtr(voterBallot, ppv);
            }
        }
 
        public DtcIsolationLevel GetIsolationLevel()
        {
            DtcIsolationLevel retVal;
            switch (this.currentTransaction.IsolationLevel)
            {
                case IsolationLevel.Serializable:
                    retVal = DtcIsolationLevel.ISOLATIONLEVEL_SERIALIZABLE;
                    break;
                case IsolationLevel.RepeatableRead:
                    retVal = DtcIsolationLevel.ISOLATIONLEVEL_REPEATABLEREAD;
                    break;
                case IsolationLevel.ReadCommitted:
                    retVal = DtcIsolationLevel.ISOLATIONLEVEL_READCOMMITTED;
                    break;
                case IsolationLevel.ReadUncommitted:
                    retVal = DtcIsolationLevel.ISOLATIONLEVEL_READUNCOMMITTED;
                    break;
                default:
                    retVal = DtcIsolationLevel.ISOLATIONLEVEL_SERIALIZABLE;
                    break;
            }
            return retVal;
        }
 
        public Guid GetIdentifier()
        {
            return this.currentTransaction.TransactionInformation.DistributedIdentifier;
        }
 
        // ITransactionProxy2
        public bool IsReusable()
        {
            return true;
        }
 
        void ClearTransaction(ProxyEnlistment enlistment)
        {
            lock (this.syncRoot)
            {
                if (this.currentTransaction == null)
                {
                    // transactions require failfasts to prevent corruption
                    DiagnosticUtility.FailFast("Clearing inactive TransactionProxy");
                }
                if (enlistment.Transaction != this.currentTransaction)
                {
                    // transactions require failfasts to prevent corruption
                    DiagnosticUtility.FailFast("Incorrectly working on multiple transactions");
                }
                this.currentTransaction = null;
                this.currentVoter = null;
            }
        }
 
        void EnsureTransaction()
        {
            lock (this.syncRoot)
            {
                if (this.currentTransaction == null)
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new COMException(null, HR.CONTEXT_E_NOTRANSACTION));
 
            }
        }
 
        class ProxyEnlistment : IEnlistmentNotification
        {
            TransactionProxy proxy;
            Transaction transaction;
 
            public ProxyEnlistment(TransactionProxy proxy,
                                   Transaction transaction)
            {
                this.proxy = proxy;
                this.transaction = transaction;
            }
 
            public Transaction Transaction
            {
                get { return this.transaction; }
            }
 
            public void Prepare(PreparingEnlistment preparingEnlistment)
            {
                this.proxy.ClearTransaction(this);
                this.proxy = null;
                preparingEnlistment.Done();
 
            }
 
            public void Commit(Enlistment enlistment)
            {
                // transactions require failfasts to prevent corruption
                DiagnosticUtility.FailFast("Should have voted read only");
            }
 
            public void Rollback(Enlistment enlistment)
            {
                this.proxy.ClearTransaction(this);
                this.proxy = null;
                enlistment.Done();
            }
 
            public void InDoubt(Enlistment enlistment)
            {
                // transactions require failfasts to prevent corruption
                DiagnosticUtility.FailFast("Should have voted read only");
            }
        }
 
        class VoterBallot : ITransactionVoterBallotAsync2, IEnlistmentNotification
        {
            const int S_OK = 0;
 
            ITransactionVoterNotifyAsync2 notification;
            Transaction transaction;
            Enlistment enlistment;
            PreparingEnlistment preparingEnlistment;
            TransactionProxy proxy;
 
            public VoterBallot(ITransactionVoterNotifyAsync2 notification, TransactionProxy proxy)
            {
                this.notification = notification;
                this.proxy = proxy;
            }
 
            public void SetTransaction(Transaction transaction)
            {
                if (this.transaction != null)
                {
                    // transactions require failfasts to prevent corruption
                    DiagnosticUtility.FailFast("Already have a transaction in the ballot!");
                }
                this.transaction = transaction;
                this.enlistment = transaction.EnlistVolatile(
                    this,
                    EnlistmentOptions.None);
            }
 
 
            public void Prepare(PreparingEnlistment enlistment)
            {
                this.preparingEnlistment = enlistment;
                this.notification.VoteRequest();
            }
 
            public void Rollback(Enlistment enlistment)
            {
                enlistment.Done();
                this.notification.Aborted(0, false, 0, S_OK);
                ComPlusTxProxyTrace.Trace(TraceEventType.Verbose, TraceCode.ComIntegrationTxProxyTxAbortedByTM,
                        SR.TraceCodeComIntegrationTxProxyTxAbortedByTM, proxy.AppId, proxy.Clsid, transaction.TransactionInformation.DistributedIdentifier, proxy.InstanceID);
                Marshal.ReleaseComObject(this.notification);
                this.notification = null;
            }
 
            public void Commit(Enlistment enlistment)
            {
                enlistment.Done();
                this.notification.Committed(false, 0, S_OK);
                ComPlusTxProxyTrace.Trace(TraceEventType.Verbose, TraceCode.ComIntegrationTxProxyTxCommitted,
                            SR.TraceCodeComIntegrationTxProxyTxCommitted, proxy.AppId, proxy.Clsid, transaction.TransactionInformation.DistributedIdentifier, proxy.InstanceID);
                Marshal.ReleaseComObject(this.notification);
                this.notification = null;
            }
 
            public void InDoubt(Enlistment enlistment)
            {
                enlistment.Done();
                this.notification.InDoubt();
                Marshal.ReleaseComObject(this.notification);
                this.notification = null;
            }
 
            public void VoteRequestDone(int hr, int reason)
            {
                if (this.preparingEnlistment == null)
                {
                    throw DiagnosticUtility.ExceptionUtility.ThrowHelperError(new InvalidOperationException(
                        SR.GetString(SR.NoVoteIssued)));
                }
 
                if (S_OK == hr)
                {
                    this.preparingEnlistment.Prepared();
                }
                else
                {
 
                    this.preparingEnlistment.ForceRollback();
                    ComPlusTxProxyTrace.Trace(TraceEventType.Verbose, TraceCode.ComIntegrationTxProxyTxAbortedByContext,
                            SR.TraceCodeComIntegrationTxProxyTxAbortedByContext, proxy.AppId, proxy.Clsid, transaction.TransactionInformation.DistributedIdentifier, proxy.InstanceID);
                }
            }
        }
    }
}