File: System\Activities\WorkflowInvoker.cs
Project: ndp\cdf\src\NetFx40\System.Activities\System.Activities.csproj (System.Activities)
//-----------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//-----------------------------------------------------------------------------
 
namespace System.Activities
{
    using System;
    using System.Activities.Hosting;
    using System.Collections;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Diagnostics.CodeAnalysis;
    using System.Runtime;
    using System.Threading;
 
    [Fx.Tag.XamlVisible(false)]
    public sealed class WorkflowInvoker
    {
        static AsyncCallback cancelCallback;
        static AsyncCallback invokeCallback;
        WorkflowInstanceExtensionManager extensions;
        Dictionary<object, AsyncInvokeContext> pendingInvokes;
        SendOrPostCallback raiseInvokeCompletedCallback;
        object thisLock;
        Activity workflow;
 
        public WorkflowInvoker(Activity workflow)
        {
            if (workflow == null)
            {
                throw FxTrace.Exception.ArgumentNull("workflow");
            }
 
            this.workflow = workflow;
            this.thisLock = new object();
        }
 
        public event EventHandler<InvokeCompletedEventArgs> InvokeCompleted;
 
        public WorkflowInstanceExtensionManager Extensions
        {
            get
            {
                if (this.extensions == null)
                {
                    this.extensions = new WorkflowInstanceExtensionManager();
                }
 
                return this.extensions;
            }
        }
 
        Dictionary<object, AsyncInvokeContext> PendingInvokes
        {
            get
            {
                if (this.pendingInvokes == null)
                {
                    this.pendingInvokes = new Dictionary<object, AsyncInvokeContext>();
                }
                return this.pendingInvokes;
            }
        }
 
        SendOrPostCallback RaiseInvokeCompletedCallback
        {
            get
            {
                if (this.raiseInvokeCompletedCallback == null)
                {
                    this.raiseInvokeCompletedCallback =
                        Fx.ThunkCallback(new SendOrPostCallback(this.RaiseInvokeCompleted));
                }
                return this.raiseInvokeCompletedCallback;
            }
        }
 
        object ThisLock
        {
            get
            {
                return this.thisLock;
            }
        }
 
        [Fx.Tag.Throws.Timeout("A timeout occurred when invoking the workflow")]
        public static IDictionary<string, object> Invoke(Activity workflow)
        {
            return Invoke(workflow, ActivityDefaults.InvokeTimeout);
        }
 
        [Fx.Tag.InheritThrows(From = "Invoke")]
        public static IDictionary<string, object> Invoke(Activity workflow, TimeSpan timeout)
        {
            return Invoke(workflow, timeout, null);
        }
 
        [Fx.Tag.InheritThrows(From = "Invoke")]
        public static IDictionary<string, object> Invoke(Activity workflow, IDictionary<string, object> inputs)
        {
            return Invoke(workflow, inputs, ActivityDefaults.InvokeTimeout, null);
        }
 
        [Fx.Tag.InheritThrows(From = "Invoke")]
        public static IDictionary<string, object> Invoke(Activity workflow, IDictionary<string, object> inputs, TimeSpan timeout)
        {
            return Invoke(workflow, inputs, timeout, null);
        }
 
        [Fx.Tag.InheritThrows(From = "Invoke")]
        [SuppressMessage(FxCop.Category.Design, FxCop.Rule.ConsiderPassingBaseTypesAsParameters,
            Justification = "Generic needed for type inference")]
        public static TResult Invoke<TResult>(Activity<TResult> workflow)
        {
            return Invoke(workflow, null);
        }
 
        [Fx.Tag.InheritThrows(From = "Invoke")]
        [SuppressMessage(FxCop.Category.Design, FxCop.Rule.ConsiderPassingBaseTypesAsParameters,
            Justification = "Generic needed for type inference")]
        public static TResult Invoke<TResult>(Activity<TResult> workflow, IDictionary<string, object> inputs)
        {
            return Invoke(workflow, inputs, ActivityDefaults.InvokeTimeout);
        }
 
        [Fx.Tag.InheritThrows(From = "Invoke")]
        [SuppressMessage(FxCop.Category.Design, FxCop.Rule.ConsiderPassingBaseTypesAsParameters,
            Justification = "Generic needed for type inference")]
        public static TResult Invoke<TResult>(Activity<TResult> workflow, IDictionary<string, object> inputs, TimeSpan timeout)
        {
            IDictionary<string, object> dummyOutputs;
            return Invoke(workflow, inputs, out dummyOutputs, timeout);
        }
 
        [Fx.Tag.InheritThrows(From = "Invoke")]
        [SuppressMessage(FxCop.Category.Design, FxCop.Rule.ConsiderPassingBaseTypesAsParameters,
            Justification = "Generic needed for type inference")]
        [SuppressMessage(FxCop.Category.Design, FxCop.Rule.AvoidOutParameters,
            Justification = "Arch approved design. Requires the out argument for extra information provided")]
        public static TResult Invoke<TResult>(Activity<TResult> workflow, IDictionary<string, object> inputs, out IDictionary<string, object> additionalOutputs, TimeSpan timeout)
        {
            TimeoutHelper.ThrowIfNegativeArgument(timeout);
            if (inputs != null)
            {
                additionalOutputs = Invoke(workflow, inputs, timeout, null);
            }
            else
            {
                additionalOutputs = Invoke(workflow, timeout, null);
            }
            object untypedResult;
            if (additionalOutputs.TryGetValue("Result", out untypedResult))
            {
                additionalOutputs.Remove("Result");
                return (TResult)untypedResult;
            }
            else
            {
                throw Fx.AssertAndThrow("Activity<TResult> should always have a output named \"Result\"");
            }
        }
 
        [Fx.Tag.InheritThrows(From = "Invoke")]
        public IAsyncResult BeginInvoke(AsyncCallback callback, object state)
        {
            return BeginInvoke(this.workflow, ActivityDefaults.InvokeTimeout, this.extensions, callback, state);
        }
 
        [Fx.Tag.InheritThrows(From = "Invoke")]
        public IAsyncResult BeginInvoke(TimeSpan timeout, AsyncCallback callback, object state)
        {
            TimeoutHelper.ThrowIfNegativeArgument(timeout);
 
            return BeginInvoke(this.workflow, timeout, this.extensions, callback, state);
        }
 
        [Fx.Tag.InheritThrows(From = "Invoke")]
        public IAsyncResult BeginInvoke(IDictionary<string, object> inputs, AsyncCallback callback, object state)
        {
            return BeginInvoke(this.workflow, inputs, ActivityDefaults.InvokeTimeout, this.extensions, callback, state);
        }
 
        [Fx.Tag.InheritThrows(From = "Invoke")]
        public IAsyncResult BeginInvoke(IDictionary<string, object> inputs, TimeSpan timeout, AsyncCallback callback, object state)
        {
            TimeoutHelper.ThrowIfNegativeArgument(timeout);
 
            return BeginInvoke(this.workflow, inputs, timeout, this.extensions, callback, state);
        }
 
        public void CancelAsync(object userState)
        {
            if (userState == null)
            {
                throw FxTrace.Exception.ArgumentNull("userState");
            }
 
            AsyncInvokeContext context = this.RemoveFromPendingInvokes(userState);
            if (context != null)
            {
                // cancel does not need a timeout since it's bounded by the invoke timeout
                if (cancelCallback == null)
                {
                    cancelCallback = Fx.ThunkCallback(new AsyncCallback(CancelCallback));
                }
                // cancel only throws TimeoutException and shouldnt throw at all if timeout is infinite
                // cancel does not need to raise InvokeCompleted since the InvokeAsync invocation would raise it
                IAsyncResult result = context.WorkflowApplication.BeginCancel(TimeSpan.MaxValue, cancelCallback, context);
                if (result.CompletedSynchronously)
                {
                    context.WorkflowApplication.EndCancel(result);
                }
            }
        }
 
        [Fx.Tag.InheritThrows(From = "Invoke")]
        public IDictionary<string, object> EndInvoke(IAsyncResult result)
        {
            return WorkflowApplication.EndInvoke(result);
        }
 
        [Fx.Tag.Throws.Timeout("A timeout occurred when invoking the workflow")]
        public IDictionary<string, object> Invoke()
        {
            return WorkflowInvoker.Invoke(this.workflow, ActivityDefaults.InvokeTimeout, this.extensions);
        }
 
        [Fx.Tag.Throws.Timeout("A timeout occurred when invoking the workflow")]
        public IDictionary<string, object> Invoke(TimeSpan timeout)
        {
            TimeoutHelper.ThrowIfNegativeArgument(timeout);
 
            return WorkflowInvoker.Invoke(this.workflow, timeout, this.extensions);
        }
 
        [Fx.Tag.Throws.Timeout("A timeout occurred when invoking the workflow")]
        public IDictionary<string, object> Invoke(IDictionary<string, object> inputs)
        {
            return WorkflowInvoker.Invoke(this.workflow, inputs, ActivityDefaults.InvokeTimeout, this.extensions);
        }
 
        [Fx.Tag.Throws.Timeout("A timeout occurred when invoking the workflow")]
        public IDictionary<string, object> Invoke(IDictionary<string, object> inputs, TimeSpan timeout)
        {
            TimeoutHelper.ThrowIfNegativeArgument(timeout);
 
            return WorkflowInvoker.Invoke(this.workflow, inputs, timeout, this.extensions);
        }
 
        public void InvokeAsync()
        {
            InvokeAsync(ActivityDefaults.InvokeTimeout, null);
        }
 
        public void InvokeAsync(TimeSpan timeout)
        {
            InvokeAsync(timeout, null);
        }
 
        public void InvokeAsync(object userState)
        {
            InvokeAsync(ActivityDefaults.InvokeTimeout, userState);
        }
 
        public void InvokeAsync(TimeSpan timeout, object userState)
        {
            TimeoutHelper.ThrowIfNegativeArgument(timeout);
 
            InternalInvokeAsync(null, timeout, userState);
        }
 
        public void InvokeAsync(IDictionary<string, object> inputs)
        {
            InvokeAsync(inputs, null);
        }
 
        public void InvokeAsync(IDictionary<string, object> inputs, TimeSpan timeout)
        {
            InvokeAsync(inputs, timeout, null);
        }
 
        public void InvokeAsync(IDictionary<string, object> inputs, object userState)
        {
            InvokeAsync(inputs, ActivityDefaults.InvokeTimeout, userState);
        }
 
        public void InvokeAsync(IDictionary<string, object> inputs, TimeSpan timeout, object userState)
        {
            if (inputs == null)
            {
                throw FxTrace.Exception.ArgumentNull("inputs");
            }
            TimeoutHelper.ThrowIfNegativeArgument(timeout);
 
            InternalInvokeAsync(inputs, timeout, userState);
        }
 
        [Fx.Tag.Throws.Timeout("A timeout occurred when invoking the workflow")]
        static IDictionary<string, object> Invoke(Activity workflow, TimeSpan timeout, WorkflowInstanceExtensionManager extensions)
        {
            if (workflow == null)
            {
                throw FxTrace.Exception.ArgumentNull("workflow");
            }
 
            TimeoutHelper.ThrowIfNegativeArgument(timeout);
 
            IDictionary<string, object> outputs = WorkflowApplication.Invoke(workflow, null, extensions, timeout);
 
            if (outputs == null)
            {
                return ActivityUtilities.EmptyParameters;
            }
            else
            {
                return outputs;
            }
        }
 
        [Fx.Tag.Throws.Timeout("A timeout occurred when invoking the workflow")]
        static IDictionary<string, object> Invoke(Activity workflow, IDictionary<string, object> inputs, TimeSpan timeout, WorkflowInstanceExtensionManager extensions)
        {
            if (workflow == null)
            {
                throw FxTrace.Exception.ArgumentNull("workflow");
            }
 
            if (inputs == null)
            {
                throw FxTrace.Exception.ArgumentNull("inputs");
            }
 
            TimeoutHelper.ThrowIfNegativeArgument(timeout);
 
            IDictionary<string, object> outputs = WorkflowApplication.Invoke(workflow, inputs, extensions, timeout);
 
            if (outputs == null)
            {
                return ActivityUtilities.EmptyParameters;
            }
            else
            {
                return outputs;
            }
        }
 
        void AddToPendingInvokes(AsyncInvokeContext context)
        {
            lock (ThisLock)
            {
                if (this.PendingInvokes.ContainsKey(context.UserState))
                {
                    throw FxTrace.Exception.AsError(new InvalidOperationException(SR.SameUserStateUsedForMultipleInvokes));
                }
                this.PendingInvokes.Add(context.UserState, context);
            }
        }
 
        [Fx.Tag.InheritThrows(From = "Invoke")]
        IAsyncResult BeginInvoke(Activity workflow, IDictionary<string, object> inputs, TimeSpan timeout, WorkflowInstanceExtensionManager extensions, AsyncCallback callback, object state)
        {
            if (inputs == null)
            {
                throw FxTrace.Exception.ArgumentNull("inputs");
            }
 
            TimeoutHelper.ThrowIfNegativeArgument(timeout);
 
            return WorkflowApplication.BeginInvoke(workflow, inputs, extensions, timeout, null, null, callback, state);
        }
 
        [Fx.Tag.InheritThrows(From = "Invoke")]
        IAsyncResult BeginInvoke(Activity workflow, TimeSpan timeout, WorkflowInstanceExtensionManager extensions, AsyncCallback callback, object state)
        {
            TimeoutHelper.ThrowIfNegativeArgument(timeout);
 
            return WorkflowApplication.BeginInvoke(workflow, null, extensions, timeout, null, null, callback, state);
        }
 
        void CancelCallback(IAsyncResult result)
        {
            if (result.CompletedSynchronously)
            {
                return;
            }
            AsyncInvokeContext context = (AsyncInvokeContext)result.AsyncState;
            // cancel only throws TimeoutException and shouldnt throw at all if timeout is infinite
            context.WorkflowApplication.EndCancel(result);
        }
 
        void InternalInvokeAsync(IDictionary<string, object> inputs, TimeSpan timeout, object userState)
        {
            AsyncInvokeContext context = new AsyncInvokeContext(userState, this);
            if (userState != null)
            {
                AddToPendingInvokes(context);
            }
            Exception error = null;
            bool completedSynchronously = false;
            try
            {
                if (invokeCallback == null)
                {
                    invokeCallback = Fx.ThunkCallback(new AsyncCallback(InvokeCallback));
                }
                context.Operation.OperationStarted();
                IAsyncResult result = WorkflowApplication.BeginInvoke(this.workflow, inputs, this.extensions, timeout, SynchronizationContext.Current, context, invokeCallback, context);
                if (result.CompletedSynchronously)
                {
                    context.Outputs = this.EndInvoke(result);
                    completedSynchronously = true;
                }
            }
            catch (Exception e)
            {
                if (Fx.IsFatal(e))
                {
                    throw;
                }
                error = e;
            }
            if (error != null || completedSynchronously)
            {
                PostInvokeCompletedAndRemove(context, error);
            }
        }
 
        void InvokeCallback(IAsyncResult result)
        {
            if (result.CompletedSynchronously)
            {
                return;
            }
            AsyncInvokeContext context = (AsyncInvokeContext)result.AsyncState;
            WorkflowInvoker thisPtr = context.Invoker;
            Exception error = null;
            try
            {
                context.Outputs = thisPtr.EndInvoke(result);
            }
            catch (Exception e)
            {
                if (Fx.IsFatal(e))
                {
                    throw;
                }
                error = e;
            }
            thisPtr.PostInvokeCompletedAndRemove(context, error);
        }
 
        void PostInvokeCompleted(AsyncInvokeContext context, Exception error)
        {
            bool cancelled;
            if (error == null)
            {
                context.WorkflowApplication.GetCompletionStatus(out error, out cancelled);
            }
            else
            {
                cancelled = false;
            }
            PostInvokeCompleted(context, cancelled, error);
        }
 
        void PostInvokeCompleted(AsyncInvokeContext context, bool cancelled, Exception error)
        {
            InvokeCompletedEventArgs e = new InvokeCompletedEventArgs(error, cancelled, context);
            if (this.InvokeCompleted == null)
            {
                context.Operation.OperationCompleted();
            }
            else
            {
                context.Operation.PostOperationCompleted(this.RaiseInvokeCompletedCallback, e);
            }
        }
 
        void PostInvokeCompletedAndRemove(AsyncInvokeContext context, Exception error)
        {
            if (context.UserState != null)
            {
                RemoveFromPendingInvokes(context.UserState);
            }
            PostInvokeCompleted(context, error);
        }
 
        void RaiseInvokeCompleted(object state)
        {
            EventHandler<InvokeCompletedEventArgs> handler = this.InvokeCompleted;
            if (handler != null)
            {
                handler(this, (InvokeCompletedEventArgs)state);
            }
        }
 
        AsyncInvokeContext RemoveFromPendingInvokes(object userState)
        {
            AsyncInvokeContext context;
            lock (ThisLock)
            {
                if (this.PendingInvokes.TryGetValue(userState, out context))
                {
                    this.PendingInvokes.Remove(userState);
                }
            }
            return context;
        }
    }
}