File: Tracking.cs
Project: ndp\cdf\src\WF\RunTime\System.Workflow.Runtime.csproj (System.Workflow.Runtime)
using System;
using System.Text;
using System.Reflection;
using System.ComponentModel;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Xml;
using System.Xml.XPath;
using System.Xml.Schema;
using System.IO;
using System.Runtime.Serialization.Formatters.Binary;
using System.Threading;
using System.Timers;
using System.Security.Permissions;
using System.Security.Cryptography;
 
using System.Workflow.Runtime;
using System.Workflow.ComponentModel;
using System.Workflow.Runtime.Hosting;
using System.Workflow.Runtime.Tracking;
 
namespace System.Workflow.Runtime
{
    /// <summary>
    /// Creates TrackingListener instances
    /// </summary>
    internal class TrackingListenerFactory
    {
        private List<TrackingService> _services = null;
        private bool _initialized = false;
        private Dictionary<Guid, WeakReference> _listeners = new Dictionary<Guid, WeakReference>();
        private volatile object _listenerLock = new object();
 
        private System.Timers.Timer _timer = null;
        private double _interval = 60000;
 
        private TrackingProfileManager _profileManager = new TrackingProfileManager();
 
        internal TrackingListenerFactory()
        {
        }
 
        internal TrackingProfileManager TrackingProfileManager
        {
            get { return _profileManager; }
        }
        /// <summary>
        /// Must be called 
        /// </summary>
        /// <param name="skedExec"></param>
        internal void Initialize(WorkflowRuntime runtime)
        {
            lock (this)
            {
                _services = runtime.TrackingServices;
                _profileManager.Initialize(runtime);
                runtime.WorkflowExecutorInitializing += WorkflowExecutorInitializing;
 
                _timer = new System.Timers.Timer();
                _timer.Interval = _interval;
                _timer.AutoReset = false; // ensure that only one timer thread at a time
                _timer.Elapsed += new ElapsedEventHandler(Cleanup);
                _timer.Start();
 
                _initialized = true;
            }
        }
 
        /// <summary>
        /// Clean up static state created in Initialize
        /// </summary>
        internal void Uninitialize(WorkflowRuntime runtime)
        {
            lock (this)
            {
                _profileManager.Uninitialize();
                runtime.WorkflowExecutorInitializing -= WorkflowExecutorInitializing;
                _timer.Elapsed -= new ElapsedEventHandler(Cleanup);
                _timer.Stop();
                _services = null;
                _initialized = false;
 
                _timer.Dispose();
                _timer = null;
            }
        }
        /// <summary>
        /// Callback for associating tracking listeners to in memory instances.  Fires for new and loading instances.
        /// </summary>
        /// <param name="sender">WorkflowExecutor</param>
        /// <param name="e"></param>
        void WorkflowExecutorInitializing(object sender, WorkflowRuntime.WorkflowExecutorInitializingEventArgs e)
        {
            if (null == sender)
                throw new ArgumentNullException("sender");
 
            if (null == e)
                throw new ArgumentNullException("e");
 
            if (!typeof(WorkflowExecutor).IsInstanceOfType(sender))
                throw new ArgumentException("sender");
 
            WorkflowExecutor exec = (WorkflowExecutor)sender;
            //
            // Add an event to clean up the WeakRef entry
            exec.WorkflowExecutionEvent += new EventHandler<WorkflowExecutor.WorkflowExecutionEventArgs>(WorkflowExecutionEvent);
            TrackingCallingState trackingCallingState = exec.TrackingCallingState;
            TrackingListenerBroker listenerBroker = (TrackingListenerBroker)exec.RootActivity.GetValue(WorkflowExecutor.TrackingListenerBrokerProperty);
            if (listenerBroker != null)
            {
                listenerBroker.ReplaceServices(exec.WorkflowRuntime.TrackingServiceReplacement);
            }
            TrackingListener listener = null;
            //
            // Check if we still have a weakref to the listener for this instance
            WeakReference weakref = null;
            if (e.Loading)
            {
                bool found = false;
                lock (_listenerLock)
                {
                    found = _listeners.TryGetValue(exec.InstanceId, out weakref);
                }
                if (found)
                {
                    try
                    {
                        // 
                        // Instead of checking IsAlive take a ref to the Target
                        //  so that it isn't GC'd underneath us.
                        listener = weakref.Target as TrackingListener;
                    }
                    catch (InvalidOperationException)
                    {
                        //
                        // This seems weird but according to msdn 
                        // accessing Target can throw ???
                        // Ignore because it's the same as a null target.
                    }
                }
                //
                // If listener is null because we didn't find the wr in the cache 
                // or because the Target has been GC'd create a new listener
                if (null != listener)
                {
                    listener.Broker = listenerBroker;
                }
                else
                {
                    Debug.Assert(null != listenerBroker, "TrackingListenerBroker should not be null during loading");
                    listener = GetTrackingListener(exec.WorkflowDefinition, exec, listenerBroker);
                    if (null != listener)
                    {
                        if (null != weakref)
                            weakref.Target = listener;
                        else
                        {
                            lock (_listenerLock)
                            {
                                _listeners.Add(exec.ID, new WeakReference(listener));
                            }
                        }
                    }
                }
            }
            else
            {
                //
                // New instance is being created
                listener = GetTrackingListener(exec.WorkflowDefinition, exec);
 
                if (null != listener)
                {
                    exec.RootActivity.SetValue(WorkflowExecutor.TrackingListenerBrokerProperty, listener.Broker);
                    lock (_listenerLock)
                    {
                        _listeners.Add(exec.ID, new WeakReference(listener));
                    }
                }
                else
                    exec.RootActivity.SetValue(WorkflowExecutor.TrackingListenerBrokerProperty, new TrackingListenerBroker());
            }
 
            if (null != listener)
            {
                exec.WorkflowExecutionEvent += new EventHandler<WorkflowExecutor.WorkflowExecutionEventArgs>(listener.WorkflowExecutionEvent);
            }
 
        }
 
        void WorkflowExecutionEvent(object sender, WorkflowExecutor.WorkflowExecutionEventArgs e)
        {
            switch (e.EventType)
            {
                case WorkflowEventInternal.Aborted:
                case WorkflowEventInternal.Completed:
                case WorkflowEventInternal.Terminated:
                    //
                    // The instance is done - remove 
                    // the WeakRef from our list
                    WorkflowExecutor exec = (WorkflowExecutor)sender;
                    lock (_listenerLock)
                    {
                        _listeners.Remove(exec.ID);
                    }
                    break;
                default:
                    return;
            }
        }
 
        void Cleanup(object sender, ElapsedEventArgs e)
        {
            List<Guid> _toRemove = new List<Guid>();
            if ((null != _listeners) || (_listeners.Count > 0))
            {
                lock (_listenerLock)
                {
                    foreach (KeyValuePair<Guid, WeakReference> kvp in _listeners)
                    {
                        if (null == kvp.Value.Target)
                            _toRemove.Add(kvp.Key);
                    }
                    if (_toRemove.Count > 0)
                    {
                        foreach (Guid g in _toRemove)
                            _listeners.Remove(g);
                    }
                }
            }
 
            lock (this)
            {
                if (_timer != null)
                {
                    _timer.Start();
                }
            }
        }
 
        /// <summary>
        /// Return a tracking listener for a new instance
        /// </summary>
        /// <param name="sked">SequentialWorkflow for which the tracking listener will be associated</param>
        /// <param name="skedExec">ScheduleExecutor for the schedule instance</param>
        /// <returns>New TrackingListener instance</returns>
        internal TrackingListener GetTrackingListener(Activity sked, WorkflowExecutor skedExec)
        {
            if (!_initialized)
                Initialize(skedExec.WorkflowRuntime);
 
            return GetListener(sked, skedExec, null);
        }
 
        /// <summary>
        /// Return a tracking listener for an existing instance (normally used during loading)
        /// </summary>
        /// <param name="sked">SequentialWorkflow for which the tracking listener will be associated</param>
        /// <param name="skedExec">ScheduleExecutor for the schedule instance</param>
        /// <param name="broker">TrackingListenerBroker</param>
        /// <returns>New TrackingListener instance</returns>
        internal TrackingListener GetTrackingListener(Activity sked, WorkflowExecutor skedExec, TrackingListenerBroker broker)
        {
            if (!_initialized)
                Initialize(skedExec.WorkflowRuntime);
 
            if (null == broker)
            {
                WorkflowTrace.Tracking.TraceEvent(TraceEventType.Error, 0, ExecutionStringManager.NullTrackingBroker);
                return null;
            }
 
            return GetListener(sked, skedExec, broker);
        }
 
        private TrackingListener GetListenerFromWRCache(Guid instanceId)
        {
            WeakReference wr = null;
            TrackingListener listener = null;
            lock (_listenerLock)
            {
                if (!_listeners.TryGetValue(instanceId, out wr))
                    throw new InvalidOperationException(string.Format(System.Globalization.CultureInfo.InvariantCulture, ExecutionStringManager.ListenerNotInCache, instanceId));
 
                listener = wr.Target as TrackingListener;
 
                if (null == listener)
                    throw new ObjectDisposedException(string.Format(System.Globalization.CultureInfo.InvariantCulture, ExecutionStringManager.ListenerNotInCacheDisposed, instanceId));
            }
 
            return listener;
        }
 
        internal void ReloadProfiles(WorkflowExecutor exec)
        {
            // Keep control events from other threads out
            using (new ServiceEnvironment(exec.RootActivity))
            {
                using (exec.ExecutorLock.Enter())
                {
                    // check if this is a valid in-memory instance
                    if (!exec.IsInstanceValid)
                        throw new InvalidOperationException(ExecutionStringManager.WorkflowNotValid);
 
                    // suspend the instance
                    bool localSuspend = exec.Suspend(ExecutionStringManager.TrackingProfileUpdate);
                    try
                    {
                        //
                        // Get new profiles
                        TrackingListener listener = GetListenerFromWRCache(exec.InstanceId);
                        listener.ReloadProfiles(exec, exec.InstanceId);
                    }
                    finally
                    {
                        if (localSuspend)
                        {
                            // @undone: for now this will not return till the instance is done
                            // Once Kumar has fixed 4335, we can enable this.
                            exec.Resume();
                        }
                    }
                }
            }
        }
 
        internal void ReloadProfiles(WorkflowExecutor exec, Guid instanceId, ref TrackingListenerBroker broker, ref List<TrackingChannelWrapper> channels)
        {
            Type workflowType = exec.WorkflowDefinition.GetType();
            //
            // Ask every tracking service if they want to reload
            // even if they originally returned null for a profile
            foreach (TrackingService service in _services)
            {
                TrackingProfile profile = null;
                TrackingChannelWrapper w = null;
                //
                // Check if the service wants to reload a profile
                if (service.TryReloadProfile(workflowType, instanceId, out profile))
                {
                    bool found = false;
                    int i;
                    for (i = 0; i < channels.Count; i++)
                    {
                        if (service.GetType() == channels[i].TrackingServiceType)
                        {
                            w = channels[i];
                            found = true;
                            break;
                        }
                    }
                    //
                    // If we don't have a profile, remove what we had for this service type (if anything)
                    if (null == profile)
                    {
                        if (found)
                        {
                            broker.RemoveService(w.TrackingServiceType);
                            channels.RemoveAt(i);
                        }
                        continue;
                    }
                    //
                    // Parse the new profile - instance only, the cache is not involved
                    RTTrackingProfile rtp = new RTTrackingProfile(profile, exec.WorkflowDefinition, workflowType);
                    rtp.IsPrivate = true;
 
                    if (!found)
                    {
                        //
                        // This is a new profile, create new channel, channelwrapper and broker item
                        List<string> activityCallPath = null;
                        Guid callerInstanceId = Guid.Empty;
                        TrackingCallingState trackingCallingState = exec.TrackingCallingState;
                        Debug.Assert((null != trackingCallingState), "WorkflowState is null");
                        IList<string> path = null;
                        Guid context = GetContext(exec.RootActivity), callerContext = Guid.Empty, callerParentContext = Guid.Empty;
                        //
                        // Use CallerActivityPathProxy to determine if this is an invoked instance
                        if (trackingCallingState != null)
                        {
                            path = trackingCallingState.CallerActivityPathProxy;
                            if ((null != path) && (path.Count > 0))
                            {
                                activityCallPath = new List<string>(path);
 
                                Debug.Assert(Guid.Empty != trackingCallingState.CallerWorkflowInstanceId, "Instance has an ActivityCallPath but CallerInstanceId is empty");
                                callerInstanceId = trackingCallingState.CallerWorkflowInstanceId;
 
                                callerContext = trackingCallingState.CallerContextGuid;
                                callerParentContext = trackingCallingState.CallerParentContextGuid;
                            }
                        }
 
                        TrackingParameters tp = new TrackingParameters(instanceId, workflowType, exec.WorkflowDefinition, activityCallPath, callerInstanceId, context, callerContext, callerParentContext);
                        TrackingChannel channel = service.GetTrackingChannel(tp);
 
                        TrackingChannelWrapper wrapper = new TrackingChannelWrapper(channel, service.GetType(), workflowType, rtp);
                        channels.Add(wrapper);
 
                        Type t = service.GetType();
                        broker.AddService(t, rtp.Version);
                        broker.MakeProfileInstance(t);
                    }
                    else
                    {
                        //
                        // Don't need to call MakeProfilePrivate on the wrapper
                        // because we've already marked it as private and we already
                        // have a private copy of it.
                        //w.MakeProfilePrivate( exec );
                        w.SetTrackingProfile(rtp);
                        broker.MakeProfileInstance(w.TrackingServiceType);
                    }
                }
            }
        }
 
        internal Guid GetContext(Activity activity)
        {
            return ((ActivityExecutionContextInfo)ContextActivityUtils.ContextActivity(activity).GetValue(Activity.ActivityExecutionContextInfoProperty)).ContextGuid;
        }
 
        private TrackingListener GetListener(Activity sked, WorkflowExecutor skedExec, TrackingListenerBroker broker)
        {
            if ((null == sked) || (null == skedExec))
            {
                WorkflowTrace.Tracking.TraceEvent(TraceEventType.Error, 0, ExecutionStringManager.NullParameters);
                return null;
            }
 
            if ((null == _services) || (_services.Count <= 0))
                return null;
 
            bool load = (null != broker);
 
            List<TrackingChannelWrapper> channels = GetChannels(sked, skedExec, skedExec.InstanceId, sked.GetType(), ref broker);
 
            if ((null == channels) || (0 == channels.Count))
                return null;
 
            return new TrackingListener(this, sked, skedExec, channels, broker, load);
        }
 
        private List<TrackingChannelWrapper> GetChannels(Activity schedule, WorkflowExecutor exec, Guid instanceID, Type workflowType, ref TrackingListenerBroker broker)
        {
            if (null == _services)
                return null;
 
            bool initBroker = false;
            if (null == broker)
            {
                broker = new TrackingListenerBroker();
                initBroker = true;
            }
 
            List<TrackingChannelWrapper> channels = new List<TrackingChannelWrapper>();
 
            List<string> activityCallPath = null;
            Guid callerInstanceId = Guid.Empty;
            Guid context = GetContext(exec.RootActivity), callerContext = Guid.Empty, callerParentContext = Guid.Empty;
 
            Debug.Assert(exec is WorkflowExecutor, "Executor is not WorkflowExecutor");
            TrackingCallingState trackingCallingState = exec.TrackingCallingState;
            TrackingListenerBroker trackingListenerBroker = (TrackingListenerBroker)exec.RootActivity.GetValue(WorkflowExecutor.TrackingListenerBrokerProperty);
            IList<string> path = trackingCallingState != null ? trackingCallingState.CallerActivityPathProxy : null;
            //
            // Use CallerActivityPathProxy to determine if this is an invoked instance
            if ((null != path) && (path.Count > 0))
            {
                activityCallPath = new List<string>(path);
 
                Debug.Assert(Guid.Empty != trackingCallingState.CallerWorkflowInstanceId, "Instance has an ActivityCallPath but CallerInstanceId is empty");
                callerInstanceId = trackingCallingState.CallerWorkflowInstanceId;
 
                callerContext = trackingCallingState.CallerContextGuid;
                callerParentContext = trackingCallingState.CallerParentContextGuid;
            }
 
            TrackingParameters parameters = new TrackingParameters(instanceID, workflowType, exec.WorkflowDefinition, activityCallPath, callerInstanceId, context, callerContext, callerParentContext);
 
            for (int i = 0; i < _services.Count; i++)
            {
                TrackingChannel channel = null;
                Type serviceType = _services[i].GetType();
 
                //
                // See if the service has a profile for this schedule type
                // If not we don't do any tracking for the service
                // 
                RTTrackingProfile profile = null;
 
                //
                // If we've created the broker get the current version of the profile
                if (initBroker)
                {
                    profile = _profileManager.GetProfile(_services[i], schedule);
 
                    if (null == profile)
                        continue;
 
                    broker.AddService(serviceType, profile.Version);
                }
                else
                {
                    //
                    // Only reload the services that are in the broker
                    // If services that weren't originally associated to an instance
                    // wish to join that instance they should call ReloadTrackingProfiles
                    if (!broker.ContainsService(serviceType))
                        continue;
 
                    if (broker.IsProfileInstance(serviceType))
                    {
                        profile = _profileManager.GetProfile(_services[i], schedule, instanceID);
 
                        if (null == profile)
                            throw new InvalidOperationException(ExecutionStringManager.MissingProfileForService + serviceType.ToString());
 
                        profile.IsPrivate = true;
                    }
                    else
                    {
                        Version versionId;
                        if (broker.TryGetProfileVersionId(serviceType, out versionId))
                        {
                            profile = _profileManager.GetProfile(_services[i], schedule, versionId);
 
                            if (null == profile)
                                throw new InvalidOperationException(ExecutionStringManager.MissingProfileForService + serviceType.ToString() + ExecutionStringManager.MissingProfileForVersion + versionId.ToString());
                            //
                            // If the profile is marked as private clone the instance we got from the cache
                            // The cloned instance is marked as private during the cloning
                            if (broker.IsProfilePrivate(serviceType))
                            {
                                profile = profile.Clone();
                                profile.IsPrivate = true;
                            }
                        }
                        else
                            continue;
                    }
                }
 
                //
                // If profile is not null get a channel
                channel = _services[i].GetTrackingChannel(parameters);
 
                if (null == channel)
                    throw new InvalidOperationException(ExecutionStringManager.NullChannel);
 
                channels.Add(new TrackingChannelWrapper(channel, _services[i].GetType(), workflowType, profile));
            }
 
            return channels;
        }
    }
 
    /// <summary>
    /// Handles subscribing to status change events and receiving event notifications.
    /// </summary>
    internal class TrackingListener
    {
        private List<TrackingChannelWrapper> _channels = null;
        private TrackingListenerBroker _broker = null;
        private TrackingListenerFactory _factory = null;
 
        protected TrackingListener()
        {
        }
 
        internal TrackingListener(TrackingListenerFactory factory, Activity sked, WorkflowExecutor exec, List<TrackingChannelWrapper> channels, TrackingListenerBroker broker, bool load)
        {
            if ((null == sked) || (null == broker))
            {
                WorkflowTrace.Tracking.TraceEvent(TraceEventType.Error, 0, ExecutionStringManager.NullParameters);
                return;
            }
            _factory = factory;
            _channels = channels;
            //
            // Keep a reference to the broker so that we can hand it out when adding subscriptions
            _broker = broker;
            //
            // Give the broker our reference so that it can call us back on behalf of subscriptions
            _broker.TrackingListener = this;
        }
 
        internal TrackingListenerBroker Broker
        {
            get { return _broker; }
            set { _broker = value; }
        }
 
        internal void ReloadProfiles(WorkflowExecutor exec, Guid instanceId)
        {
            //
            // Ask the factory to redo the channels and broker
            _factory.ReloadProfiles(exec, instanceId, ref _broker, ref _channels);
        }
 
 
        #region Event Handlers
 
        internal void ActivityStatusChange(object sender, WorkflowExecutor.ActivityStatusChangeEventArgs e)
        {
            WorkflowTrace.Tracking.TraceInformation("TrackingListener::ActivityStatusChange - Received Activity Status Change Event for activity {0}", e.Activity.QualifiedName);
 
            if (null == sender)
                throw new ArgumentNullException("sender");
 
            if (!typeof(WorkflowExecutor).IsInstanceOfType(sender))
                throw new ArgumentException("sender");
 
            if (null == e)
                throw new ArgumentNullException("e");
 
            WorkflowExecutor exec = (WorkflowExecutor)sender;
 
            if ((null == _channels) || (_channels.Count <= 0))
            {
                WorkflowTrace.Tracking.TraceEvent(TraceEventType.Error, 0, ExecutionStringManager.NoChannels);
                return;
            }
 
            Activity activity = e.Activity;
 
            if (!SubscriptionRequired(activity, exec))
                return;
            //
            // Get the shared data that is the same for each tracking channel that gets a record
            Guid parentContextGuid = Guid.Empty, contextGuid = Guid.Empty;
            GetContext(activity, exec, out contextGuid, out parentContextGuid);
 
            DateTime dt = DateTime.UtcNow;
            int eventOrderId = _broker.GetNextEventOrderId();
 
            foreach (TrackingChannelWrapper wrapper in _channels)
            {
                //
                // Create a record for each tracking channel
                // Each channel gets a distinct record because extract data will almost always be different.
                ActivityTrackingRecord record = new ActivityTrackingRecord(activity.GetType(), activity.QualifiedName, contextGuid, parentContextGuid, activity.ExecutionStatus, dt, eventOrderId, null);
 
                bool extracted = wrapper.GetTrackingProfile(exec).TryTrackActivityEvent(activity, activity.ExecutionStatus, exec, record);
                //
                // Only send the record to the channel if the profile indicates that it is interested
                // This doesn't mean that the Body will always have data in it, 
                // it may be an empty extraction (just header info)
                if (extracted)
                    wrapper.TrackingChannel.Send(record);
            }
        }
 
        internal void UserTrackPoint(object sender, WorkflowExecutor.UserTrackPointEventArgs e)
        {
            if (!typeof(WorkflowExecutor).IsInstanceOfType(sender))
                throw new ArgumentException("sender is not WorkflowExecutor");
 
            WorkflowExecutor exec = (WorkflowExecutor)sender;
            Activity activity = e.Activity;
 
            DateTime dt = DateTime.UtcNow;
            int eventOrderId = _broker.GetNextEventOrderId();
 
            Guid parentContextGuid, contextGuid;
            GetContext(activity, exec, out contextGuid, out parentContextGuid);
 
            foreach (TrackingChannelWrapper wrapper in _channels)
            {
                UserTrackingRecord record = new UserTrackingRecord(activity.GetType(), activity.QualifiedName, contextGuid, parentContextGuid, dt, eventOrderId, e.Key, e.Args);
 
                if (wrapper.GetTrackingProfile(exec).TryTrackUserEvent(activity, e.Key, e.Args, exec, record))
                    wrapper.TrackingChannel.Send(record);
            }
        }
 
        internal void WorkflowExecutionEvent(object sender, WorkflowExecutor.WorkflowExecutionEventArgs e)
        {
            if (null == sender)
                throw new ArgumentNullException("sender");
 
            WorkflowExecutor exec = sender as WorkflowExecutor;
            if (null == exec)
                throw new ArgumentException(ExecutionStringManager.InvalidSenderWorkflowExecutor);
            //
            // Many events are mapped "forward" and sent to tracking services 
            // (Persisting->Persisted, SchedulerEmpty->Idle)
            // This is so that a batch is always available when a tracking service gets an event.  
            // Without this tracking data could be inconsistent with the state of the instance. 
            switch (e.EventType)
            {
                case WorkflowEventInternal.Creating:
                    NotifyChannels(TrackingWorkflowEvent.Created, e, exec);
                    return;
                case WorkflowEventInternal.Starting:
                    NotifyChannels(TrackingWorkflowEvent.Started, e, exec);
                    return;
                case WorkflowEventInternal.Suspending:
                    NotifyChannels(TrackingWorkflowEvent.Suspended, e, exec);
                    return;
                case WorkflowEventInternal.Resuming:
                    NotifyChannels(TrackingWorkflowEvent.Resumed, e, exec);
                    return;
                case WorkflowEventInternal.Persisting:
                    NotifyChannels(TrackingWorkflowEvent.Persisted, e, exec);
                    return;
                case WorkflowEventInternal.Unloading:
                    NotifyChannels(TrackingWorkflowEvent.Unloaded, e, exec);
                    return;
                case WorkflowEventInternal.Loading:
                    NotifyChannels(TrackingWorkflowEvent.Loaded, e, exec);
                    return;
                case WorkflowEventInternal.Completing:
                    NotifyChannels(TrackingWorkflowEvent.Completed, e, exec);
                    NotifyChannelsOfCompletionOrTermination();
                    return;
                case WorkflowEventInternal.Aborting:
                    NotifyChannels(TrackingWorkflowEvent.Aborted, e, exec);
                    return;
                case WorkflowEventInternal.Terminating:
                    NotifyChannels(TrackingWorkflowEvent.Terminated, e, exec);
                    NotifyChannelsOfCompletionOrTermination();
                    return;
                case WorkflowEventInternal.Exception:
                    NotifyChannels(TrackingWorkflowEvent.Exception, e, exec);
                    return;
                case WorkflowEventInternal.SchedulerEmpty:
                    NotifyChannels(TrackingWorkflowEvent.Idle, e, exec);
                    return;
                case WorkflowEventInternal.UserTrackPoint:
                    UserTrackPoint(exec, (WorkflowExecutor.UserTrackPointEventArgs)e);
                    return;
                case WorkflowEventInternal.ActivityStatusChange:
                    ActivityStatusChange(exec, (WorkflowExecutor.ActivityStatusChangeEventArgs)e);
                    return;
                case WorkflowEventInternal.DynamicChangeBegin:
                    DynamicUpdateBegin(exec, (WorkflowExecutor.DynamicUpdateEventArgs)e);
                    return;
                case WorkflowEventInternal.DynamicChangeRollback:
                    DynamicUpdateRollback(exec, (WorkflowExecutor.DynamicUpdateEventArgs)e);
                    return;
                case WorkflowEventInternal.DynamicChangeCommit:
                    DynamicUpdateCommit(exec, (WorkflowExecutor.DynamicUpdateEventArgs)e);
                    return;
                default:
                    return;
            }
        }
 
        internal void DynamicUpdateBegin(object sender, WorkflowExecutor.DynamicUpdateEventArgs e)
        {
            if (null == sender)
                throw new ArgumentNullException("sender");
 
            if (!typeof(WorkflowExecutor).IsInstanceOfType(sender))
                throw new ArgumentException("sender");
 
            WorkflowExecutor exec = (WorkflowExecutor)sender;
            //
            // WorkflowChangeEventArgs may be null or the WorkflowChanges may be null or empty
            // If so there's no work to do here
            if (null == e.ChangeActions)
                return;
            //
            // Clone the profiles to create instance specific copies (if they aren't already)
            MakeProfilesPrivate(exec);
            //
            // Give the profiles the changes.  At this point we are in a volatile state.
            // Profiles must act as if the changes will succeed but roll back any internal changes if they do not.
            foreach (TrackingChannelWrapper channel in _channels)
            {
                channel.GetTrackingProfile(exec).WorkflowChangeBegin(e.ChangeActions);
            }
        }
 
        internal void DynamicUpdateRollback(object sender, WorkflowExecutor.DynamicUpdateEventArgs e)
        {
            if (null == sender)
                throw new ArgumentNullException("sender");
 
            if (!typeof(WorkflowExecutor).IsInstanceOfType(sender))
                throw new ArgumentException("sender");
 
            WorkflowExecutor exec = (WorkflowExecutor)sender;
 
            foreach (TrackingChannelWrapper channel in _channels)
            {
                channel.GetTrackingProfile(exec).WorkflowChangeRollback();
            }
        }
 
        internal void DynamicUpdateCommit(object sender, WorkflowExecutor.DynamicUpdateEventArgs e)
        {
            if (null == sender)
                throw new ArgumentNullException("sender");
 
            if (!typeof(WorkflowExecutor).IsInstanceOfType(sender))
                throw new ArgumentException("sender");
 
            WorkflowExecutor exec = (WorkflowExecutor)sender;
 
            DateTime dt = DateTime.UtcNow;
            foreach (TrackingChannelWrapper channel in _channels)
            {
                channel.GetTrackingProfile(exec).WorkflowChangeCommit();
            }
            //
            // Notify tracking channels of changes
            int eventOrderId = _broker.GetNextEventOrderId();
 
            foreach (TrackingChannelWrapper wrapper in _channels)
            {
                WorkflowTrackingRecord rec = new WorkflowTrackingRecord(TrackingWorkflowEvent.Changed, dt, eventOrderId, new TrackingWorkflowChangedEventArgs(e.ChangeActions, exec.WorkflowDefinition));
                if (wrapper.GetTrackingProfile(exec).TryTrackInstanceEvent(TrackingWorkflowEvent.Changed, rec))
                    wrapper.TrackingChannel.Send(rec);
            }
        }
 
        #endregion Event Handlers
 
        #region Private Methods
 
        private void NotifyChannels(TrackingWorkflowEvent evt, WorkflowExecutor.WorkflowExecutionEventArgs e, WorkflowExecutor exec)
        {
            DateTime dt = DateTime.UtcNow;
            int eventOrderId = _broker.GetNextEventOrderId();
 
            foreach (TrackingChannelWrapper wrapper in _channels)
            {
                EventArgs args = null;
                switch (evt)
                {
                    case TrackingWorkflowEvent.Suspended:
                        args = new TrackingWorkflowSuspendedEventArgs(((WorkflowExecutor.WorkflowExecutionSuspendingEventArgs)e).Error);
                        break;
                    case TrackingWorkflowEvent.Terminated:
                        WorkflowExecutor.WorkflowExecutionTerminatingEventArgs wtea = (WorkflowExecutor.WorkflowExecutionTerminatingEventArgs)e;
                        if (null != wtea.Exception)
                            args = new TrackingWorkflowTerminatedEventArgs(wtea.Exception);
                        else
                            args = new TrackingWorkflowTerminatedEventArgs(wtea.Error);
                        break;
                    case TrackingWorkflowEvent.Exception:
                        WorkflowExecutor.WorkflowExecutionExceptionEventArgs weea = (WorkflowExecutor.WorkflowExecutionExceptionEventArgs)e;
                        args = new TrackingWorkflowExceptionEventArgs(weea.Exception, weea.CurrentPath, weea.OriginalPath, weea.ContextGuid, weea.ParentContextGuid);
                        break;
                }
                WorkflowTrackingRecord rec = new WorkflowTrackingRecord(evt, dt, eventOrderId, args);
                if (wrapper.GetTrackingProfile(exec).TryTrackInstanceEvent(evt, rec))
                    wrapper.TrackingChannel.Send(rec);
            }
        }
 
        private void NotifyChannelsOfCompletionOrTermination()
        {
            foreach (TrackingChannelWrapper wrapper in _channels)
                wrapper.TrackingChannel.InstanceCompletedOrTerminated();
        }
 
        private void GetContext(Activity activity, WorkflowExecutor exec, out Guid contextGuid, out Guid parentContextGuid)
        {
            contextGuid = _factory.GetContext(activity);
 
            if (null != activity.Parent)
                parentContextGuid = _factory.GetContext(activity.Parent);
            else
                parentContextGuid = contextGuid;
 
            Debug.Assert(contextGuid != Guid.Empty, "TrackingContext is empty");
            Debug.Assert(parentContextGuid != Guid.Empty, "Parent TrackingContext is empty");
        }
 
        /// <summary>
        /// Clone all profiles to create private versions in order to hold subscriptions for dynamic changes
        /// </summary>
        private void MakeProfilesPrivate(WorkflowExecutor exec)
        {
            foreach (TrackingChannelWrapper channel in _channels)
            {
                channel.MakeProfilePrivate(exec);
                _broker.MakeProfilePrivate(channel.TrackingServiceType);
            }
        }
        /// <summary>
        /// Determine if subscriptions are needed
        /// </summary>
        /// <param name="activity">Activity for which to check subscription needs</param>
        /// <returns></returns>
        private bool SubscriptionRequired(Activity activity, WorkflowExecutor exec)
        {
            //
            // Give each channel a chance to prep itself
            bool needed = false;
 
            foreach (TrackingChannelWrapper channel in _channels)
            {
                if ((channel.GetTrackingProfile(exec).ActivitySubscriptionNeeded(activity)) && (!needed))
                    needed = true;
            }
 
            return needed;
        }
 
        #endregion
    }
 
    /// <summary>
    /// This is a lightweight class that is serialized so that the TrackingListener doesn't have to be.
    /// Every subscription that the listener adds holds a reference to this class.  
    /// When an instance is loaded the broker is given to the listener factory and the listener factory
    /// gives the broker the new listener.  This saves us from having to persist the listener itself which
    /// means that while we do need to persist a list of service types and their profile version we don't
    /// have to persist the channels themselves (and we can't control how heavy channels get as they are host defined).
    /// </summary>
    [Serializable]
    internal class TrackingListenerBroker : System.Runtime.Serialization.ISerializable
    {
        [NonSerialized]
        private TrackingListener _listener = null;
        private int _eventOrderId = 0;
        private Dictionary<Guid, ServiceProfileContainer> _services = new Dictionary<Guid, ServiceProfileContainer>();
 
        internal TrackingListenerBroker()
        {
        }
 
        internal TrackingListenerBroker(TrackingListener listener)
        {
            _listener = listener;
        }
 
        internal TrackingListener TrackingListener
        {
            //
            // FxCops minbar complains because this isn't used.  
            // The Setter is required; seems weird not to have a getter.
            //get { return _listener; } 
            set { _listener = value; }
        }
 
        internal bool ContainsService(Type trackingServiceType)
        {
            return _services.ContainsKey(HashHelper.HashServiceType(trackingServiceType));
        }
 
        internal void AddService(Type trackingServiceType, Version profileVersionId)
        {
            _services.Add(HashHelper.HashServiceType(trackingServiceType), new ServiceProfileContainer(profileVersionId));
        }
 
        internal void ReplaceServices(Dictionary<string, Type> replacements)
        {
            if (replacements != null && replacements.Count > 0)
            {
                ServiceProfileContainer item;
                foreach (KeyValuePair<string, Type> replacement in replacements)
                {
                    Guid previous = HashHelper.HashServiceType(replacement.Key);
                    if (_services.TryGetValue(previous, out item))
                    {
                        _services.Remove(previous);
                        Guid current = HashHelper.HashServiceType(replacement.Value);
                        if (!_services.ContainsKey(current))
                        {
                            _services.Add(current, item);
                        }
                    }
                }
            }
        }
 
        internal void RemoveService(Type trackingServiceType)
        {
            _services.Remove(HashHelper.HashServiceType(trackingServiceType));
        }
 
        internal bool TryGetProfileVersionId(Type trackingServiceType, out Version profileVersionId)
        {
            profileVersionId = new Version(0, 0);
 
            ServiceProfileContainer service = null;
            if (_services.TryGetValue(HashHelper.HashServiceType(trackingServiceType), out service))
            {
                profileVersionId = service.ProfileVersionId;
                return true;
            }
            return false;
        }
 
        internal void MakeProfilePrivate(Type trackingServiceType)
        {
            ServiceProfileContainer service = null;
            if (!_services.TryGetValue(HashHelper.HashServiceType(trackingServiceType), out service))
                throw new ArgumentException(ExecutionStringManager.InvalidTrackingService);
 
            service.IsPrivate = true;
        }
 
        internal bool IsProfilePrivate(Type trackingServiceType)
        {
            ServiceProfileContainer service = null;
            if (!_services.TryGetValue(HashHelper.HashServiceType(trackingServiceType), out service))
                throw new ArgumentException(ExecutionStringManager.InvalidTrackingService);
 
            return service.IsPrivate;
        }
 
        internal void MakeProfileInstance(Type trackingServiceType)
        {
            ServiceProfileContainer service = null;
            if (!_services.TryGetValue(HashHelper.HashServiceType(trackingServiceType), out service))
                throw new ArgumentException(ExecutionStringManager.InvalidTrackingService);
            //
            // Can't be instance without being private
            service.IsPrivate = true;
            service.IsInstance = true;
        }
 
        internal bool IsProfileInstance(Type trackingServiceType)
        {
            ServiceProfileContainer service = null;
            if (!_services.TryGetValue(HashHelper.HashServiceType(trackingServiceType), out service))
                throw new ArgumentException(ExecutionStringManager.InvalidTrackingService);
 
            return service.IsInstance;
        }
 
        internal int GetNextEventOrderId()
        {
            checked
            {
                return ++_eventOrderId;
            }
        }
 
        [Serializable]
        internal class ServiceProfileContainer
        {
            Version _profileVersionId = new Version(0, 0);
            bool _isPrivate = false;
            bool _isInstance = false;
 
            protected ServiceProfileContainer() { }
 
            internal ServiceProfileContainer(Version profileVersionId)
            {
                _profileVersionId = profileVersionId;
            }
 
            internal Version ProfileVersionId
            {
                get { return _profileVersionId; }
            }
 
            internal bool IsPrivate
            {
                get { return _isPrivate; }
                set { _isPrivate = value; }
            }
 
            internal bool IsInstance
            {
                get { return _isInstance; }
                set { _isInstance = value; }
            }
        }
 
        #region ISerializable Members
 
        [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
        public void GetObjectData(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
        {
            info.AddValue("eventOrderId", this._eventOrderId);
            info.AddValue("services", this._services.Count == 0 ? null : this._services);
        }
        private TrackingListenerBroker(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context)
        {
            this._eventOrderId = info.GetInt32("eventOrderId");
            this._services = (Dictionary<Guid, ServiceProfileContainer>)info.GetValue("services", typeof(Dictionary<Guid, ServiceProfileContainer>));
            if (this._services == null)
                this._services = new Dictionary<Guid, ServiceProfileContainer>();
        }
 
        #endregion
    }
 
    /// <summary>
    /// Manages profile requests, caching profiles and creating RTTrackingProfile instances.
    /// </summary>
    internal class TrackingProfileManager
    {
        //
        // This is a dictionary keyed by tracking service type 
        // that returns a dictionary that is key by schedule type
        // that returns a Set of profile versions for that schedule type
        // The set is constrained by VersionId
        private Dictionary<Type, Dictionary<Type, ProfileList>> _cacheLookup;
        //
        // Protects _cacheLookup
        private object _cacheLock = new object();
        //
        // Values assigned in Initialize
        private bool _init = false;
        private List<TrackingService> _services = null;
        private WorkflowRuntime _runtime = null;
 
        internal TrackingProfileManager()
        {
        }
        /// <summary>
        /// Clears all entries from the cache by reinitializing the member
        /// </summary>
        public static void ClearCache()
        {
            WorkflowRuntime.ClearTrackingProfileCache();
        }
 
        internal void ClearCacheImpl()
        {
            lock (_cacheLock)
            {
                _cacheLookup = new Dictionary<Type, Dictionary<Type, ProfileList>>();
            }
        }
        /// <summary>
        /// Create static state
        /// </summary>
        /// <param name="runtime"></param>
        internal void Initialize(WorkflowRuntime runtime)
        {
            lock (_cacheLock)
            {
                if (null == runtime)
                    throw new ArgumentException(ExecutionStringManager.NullEngine);
 
                _runtime = runtime;
                //
                // Initialize the cache
                // Do this every time the runtime starts/stops to make life easier 
                // for IProfileNotification tracking services that might have updated
                // profiles while we were stopped - we'll go get new versions since nothing is cached
                // without them having to fire updated events.
                _cacheLookup = new Dictionary<Type, Dictionary<Type, ProfileList>>();
                if (null != runtime.TrackingServices)
                {
                    _services = runtime.TrackingServices;
                    foreach (TrackingService service in _services)
                    {
                        if (service is IProfileNotification)
                        {
                            ((IProfileNotification)service).ProfileUpdated += new EventHandler<ProfileUpdatedEventArgs>(ProfileUpdated);
                            ((IProfileNotification)service).ProfileRemoved += new EventHandler<ProfileRemovedEventArgs>(ProfileRemoved);
                        }
                    }
                }
                _init = true;
            }
        }
        /// <summary>
        /// Clean up static state
        /// </summary>
        internal void Uninitialize()
        {
            lock (_cacheLock)
            {
                if (null != _runtime)
                {
                    foreach (TrackingService service in _services)
                    {
                        if (service is IProfileNotification)
                        {
                            ((IProfileNotification)service).ProfileUpdated -= new EventHandler<ProfileUpdatedEventArgs>(ProfileUpdated);
                            ((IProfileNotification)service).ProfileRemoved -= new EventHandler<ProfileRemovedEventArgs>(ProfileRemoved);
                        }
                    }
                }
                _runtime = null;
                _services = null;
                _init = false;
            }
        }
        /// <summary>
        /// Retrieves the current version of a profile from the specified service
        /// </summary>
        internal RTTrackingProfile GetProfile(TrackingService service, Activity schedule)
        {
            if (!_init)
                throw new ApplicationException(ExecutionStringManager.TrackingProfileManagerNotInitialized);
 
            if ((null == service) || (null == schedule))
            {
                WorkflowTrace.Tracking.TraceEvent(TraceEventType.Error, 0, ExecutionStringManager.NullParameters);
                return null;
            }
 
            Type workflowType = schedule.GetType();
            RTTrackingProfile tp = null;
            if (service is IProfileNotification)
            {
                //
                // If we found the profile in the cache return it, it may be null, this is OK 
                // (no profile for this service type/schedule type combination)
                if (TryGetFromCache(service.GetType(), workflowType, out tp))
                    return tp;
            }
            //
            // Either we don't have anything in the cache for this schedule/service combination
            // or this is a base TrackingService that doesn't notify of profile updates
            // Get the profile from the service
            TrackingProfile profile = null;
 
            if (!service.TryGetProfile(workflowType, out profile))
            {
                //
                // No profile for this schedule from this service
                // RemoveProfile will just mark this service/schedule as not currently having a profile in the cache
                RemoveProfile(workflowType, service.GetType());
                return null;
            }
            //
            // Check the cache to see if we already have this version
            // For TrackingService types this is necessary.
            // For IProfileNotification types this is a bit redundant 
            // but another threadcould have inserted the profile into the cache 
            // so check again before acquiring the writer lock
            if (TryGetFromCache(service.GetType(), workflowType, profile.Version, out tp))
                return tp;
            //
            // No profile, create it
            string xaml = schedule.GetValue(Activity.WorkflowXamlMarkupProperty) as string;
            if (null != xaml && xaml.Length > 0)
            {
                //
                // Never add xaml only workflows to the cache
                // Each one must be handled distinctly
                return CreateProfile(profile, schedule, service.GetType());
            }
            else
            {
                tp = CreateProfile(profile, workflowType, service.GetType());
            }
 
            lock (_cacheLock)
            {
                //
                // Recheck the cache with exclusive access
                RTTrackingProfile tmp = null;
                if (TryGetFromCache(service.GetType(), workflowType, profile.Version, out tmp))
                    return tmp;
                //
                // Add it to the cache
                if (!AddToCache(tp, service.GetType()))
                    throw new ApplicationException(ExecutionStringManager.ProfileCacheInsertFailure);
 
                return tp;
            }
        }
        /// <summary>
        /// Retrieves the specified version of a profile from the specified service
        /// </summary>
        internal RTTrackingProfile GetProfile(TrackingService service, Activity workflow, Version versionId)
        {
            if (null == service)
                throw new ArgumentNullException("service");
            if (null == workflow)
                throw new ArgumentNullException("workflow");
 
            if (!_init)
                throw new InvalidOperationException(ExecutionStringManager.TrackingProfileManagerNotInitialized);
 
            Type workflowType = workflow.GetType();
            RTTrackingProfile tp = null;
            //
            // Looking for a specific version, see if it is in the cache
            if (TryGetFromCache(service.GetType(), workflowType, versionId, out tp))
                return tp;
 
            TrackingProfile profile = service.GetProfile(workflowType, versionId);
            //
            // No profile, create it
            string xaml = workflow.GetValue(Activity.WorkflowXamlMarkupProperty) as string;
            if (null != xaml && xaml.Length > 0)
            {
                //
                // Never add xaml only workflows to the cache
                // Each one must be handled distinctly
                return CreateProfile(profile, workflow, service.GetType());
            }
            else
            {
                tp = CreateProfile(profile, workflowType, service.GetType());
            }
 
            lock (_cacheLock)
            {
                //
                // Recheck the cache with exclusive access
                RTTrackingProfile tmp = null;
                if (TryGetFromCache(service.GetType(), workflowType, versionId, out tmp))
                    return tmp;
                //
                // Add it to the cache
                if (!AddToCache(tp, service.GetType()))
                    throw new ApplicationException(ExecutionStringManager.ProfileCacheInsertFailure);
 
                return tp;
            }
        }
 
        internal RTTrackingProfile GetProfile(TrackingService service, Activity workflow, Guid instanceId)
        {
            //
            // An instance based profile will never be in the cache
            TrackingProfile profile = service.GetProfile(instanceId);
 
            if (null == profile)
                return null;
 
            return new RTTrackingProfile(profile, workflow, service.GetType());
        }
 
        #region Private Methods
 
        private RTTrackingProfile CreateProfile(TrackingProfile profile, Type workflowType, Type serviceType)
        {
            //
            // Can't use the activity definition that we have here, it may have been updated
            // Get the base definition and use it to create the profile.
            Activity tmpSchedule = _runtime.GetWorkflowDefinition(workflowType);
            return new RTTrackingProfile(profile, tmpSchedule, serviceType);
        }
        private RTTrackingProfile CreateProfile(TrackingProfile profile, Activity schedule, Type serviceType)
        {
            //
            // This is called for Xaml only workflows
            return new RTTrackingProfile(profile, schedule, serviceType);
        }
        /// <summary>
        /// Add a profile to the cache but do not reset the NoProfiles flag for the schedule type
        /// </summary>
        /// <param name="profile">RTTrackingProfile to add</param>
        /// <param name="serviceType">TrackingService type</param>
        /// <returns>True if the profile was successfully added; false if not</returns>
        private bool AddToCache(RTTrackingProfile profile, Type serviceType)
        {
            return AddToCache(profile, serviceType, false);
        }
        /// <summary>
        /// Adds a profile to the cache and optionally resets the NoProfiles flag for the schedule type
        /// </summary>
        /// <param name="profile">RTTrackingProfile to add</param>
        /// <param name="serviceType">TrackingService type</param>
        /// <param name="resetNoProfiles">true will reset NoProfiles (to false); false will leave NoProfiles as is</param>
        /// <returns>True if the profile was successfully added; false if not</returns>
        private bool AddToCache(RTTrackingProfile profile, Type serviceType, bool resetNoProfiles)
        {
            //
            // Profile may be null, serviceType may not
            if (null == serviceType)
                return false;
 
            lock (_cacheLock)
            {
                Dictionary<Type, ProfileList> schedules = null;
                //
                // Get the dictionary for the service type,
                // create it if it doesn't exist
                if (!_cacheLookup.TryGetValue(serviceType, out schedules))
                {
                    schedules = new Dictionary<Type, ProfileList>();
                    _cacheLookup.Add(serviceType, schedules);
                }
                //
                // The the ProfileList for the schedule type,
                // create it if it doesn't exist
                ProfileList profiles = null;
                if (!schedules.TryGetValue(profile.WorkflowType, out profiles))
                {
                    profiles = new ProfileList();
                    schedules.Add(profile.WorkflowType, profiles);
                }
                if (resetNoProfiles)
                    profiles.NoProfile = false;
                return profiles.Profiles.TryAdd(new CacheItem(profile));
            }
        }
        /// <summary>
        /// Gets a profile from the cache
        /// </summary>
        private bool TryGetFromCache(Type serviceType, Type workflowType, out RTTrackingProfile profile)
        {
            return TryGetFromCache(serviceType, workflowType, new Version(0, 0), out profile); // 0 is an internal signal to get the most current
        }
        /// <summary>
        /// Gets a profile from the cache
        /// </summary>
        private bool TryGetFromCache(Type serviceType, Type workflowType, Version versionId, out RTTrackingProfile profile)
        {
            profile = null;
            CacheItem item = null;
            lock (_cacheLock)
            {
                Dictionary<Type, ProfileList> schedules = null;
 
                if (!_cacheLookup.TryGetValue(serviceType, out schedules))
                    return false;
 
                ProfileList profiles = null;
                if (!schedules.TryGetValue(workflowType, out profiles))
                    return false;
 
                //
                // 0 means get the current version
                if (0 == versionId.Major)
                {
                    //
                    // Currently the schedule type doesn't have a profile associated to it
                    if (profiles.NoProfile)
                        return true;
 
                    if ((null == profiles.Profiles) || (0 == profiles.Profiles.Count))
                        return false;
 
                    //
                    // Current version is highest versionId
                    // which means it is at the end of the Set
                    int endPos = profiles.Profiles.Count - 1;
 
                    if (null == profiles.Profiles[endPos])
                        return false;
 
                    profile = profiles.Profiles[endPos].TrackingProfile;
                    return true;
                }
                else
                {
                    if ((null == profiles.Profiles) || (0 == profiles.Profiles.Count))
                        return false;
 
                    if (profiles.Profiles.TryGetValue(new CacheItem(workflowType, versionId), out item))
                    {
                        profile = item.TrackingProfile;
                        return true;
                    }
                    else
                        return false;
                }
            }
        }
 
        #endregion
 
        #region Event Handlers
        /// <summary>
        /// Listens on ProfileUpdated events from IProfileNotification services
        /// </summary>
        /// <param name="sender">Type of the tracking service sending the update</param>
        /// <param name="e">ProfileUpdatedEventArgs containing the new profile and the schedule type</param>
        private void ProfileUpdated(object sender, ProfileUpdatedEventArgs e)
        {
            if (null == sender)
                throw new ArgumentNullException("sender");
 
            Type t = sender.GetType();
 
            if (null == e.WorkflowType)
                throw new ArgumentNullException("e");
 
            if (null == e.TrackingProfile)
            {
                RemoveProfile(e.WorkflowType, t);
                return;
            }
 
            RTTrackingProfile profile = CreateProfile(e.TrackingProfile, e.WorkflowType, t);
            //
            // If AddToCache fails this version is already in the cache and we don't care
            AddToCache(profile, t, true);
        }
 
        private void ProfileRemoved(object sender, ProfileRemovedEventArgs e)
        {
            if (null == sender)
                throw new ArgumentNullException("sender");
 
            if (null == e.WorkflowType)
                throw new ArgumentNullException("e");
 
            RemoveProfile(e.WorkflowType, sender.GetType());
        }
 
        private void RemoveProfile(Type workflowType, Type serviceType)
        {
            lock (_cacheLock)
            {
                Dictionary<Type, ProfileList> schedules = null;
 
                if (!_cacheLookup.TryGetValue(serviceType, out schedules))
                {
                    schedules = new Dictionary<Type, ProfileList>();
                    _cacheLookup.Add(serviceType, schedules);
                }
                ProfileList profiles = null;
                if (!schedules.TryGetValue(workflowType, out profiles))
                {
                    profiles = new ProfileList();
                    schedules.Add(workflowType, profiles);
                }
                //
                // Finally indicate that there isn't a profile for this schedule type
                // Calling UpdateProfile for this type will result in resetting this field
                // regardless of whether the version of the profile passed is in the cache or not
                profiles.NoProfile = true;
            }
        }
        #endregion
 
        #region Private Classes
        private class ProfileList
        {
            internal bool NoProfile = false;
            internal Set<CacheItem> Profiles = new Set<CacheItem>(5);
        }
 
        private class CacheItem : IComparable
        {
            internal RTTrackingProfile TrackingProfile = null;
            internal DateTime LastAccess = DateTime.UtcNow;
            //
            // VersionId and ScheduleType are stored separately from the profile so that they
            // can be used to identify the profile if it has been pushed from the cache.
            internal Version VersionId = new Version(0, 0);
            internal Type ScheduleType = null;
 
            internal CacheItem()
            {
            }
 
            internal CacheItem(RTTrackingProfile profile)
            {
                if (null == profile)
                    throw new ArgumentNullException("profile");
 
                ScheduleType = profile.WorkflowType;
 
                this.TrackingProfile = profile;
                VersionId = profile.Version;
            }
 
            internal CacheItem(Type workflowType, Version versionId)
            {
                VersionId = versionId;
                ScheduleType = workflowType;
            }
 
            #region IComparable Members
 
            public int CompareTo(object obj)
            {
                if (!(obj is CacheItem))
                    throw new ArgumentException(ExecutionStringManager.InvalidCacheItem);
 
                CacheItem item = (CacheItem)obj;
                if ((VersionId == item.VersionId) && (ScheduleType == item.ScheduleType))
                    return 0;
                else
                    return (VersionId > item.VersionId) ? 1 : -1;
            }
 
            #endregion
        }
        #endregion
 
    }
    /// <summary>
    /// Represents a wrapper around a channel and its artifacts, such as its tracking service type and profile
    /// </summary>
    internal class TrackingChannelWrapper
    {
        private Type _serviceType = null, _scheduleType = null;
        private TrackingChannel _channel = null;
        [NonSerialized]
        private RTTrackingProfile _profile = null;
        private Version _profileVersionId;
 
        private TrackingChannelWrapper() { }
 
        public TrackingChannelWrapper(TrackingChannel channel, Type serviceType, Type workflowType, RTTrackingProfile profile)
        {
            _serviceType = serviceType;
            _scheduleType = workflowType;
            _channel = channel;
            _profile = profile;
            _profileVersionId = profile.Version;
        }
 
        internal Type TrackingServiceType
        {
            get { return _serviceType; }
        }
 
        internal TrackingChannel TrackingChannel
        {
            get { return _channel; }
        }
        /// <summary>
        /// Get the tracking profile for the channel
        /// </summary>
        /// <param name="exec">BaseExecutor</param>
        /// <returns>RTTrackingProfile</returns>
        internal RTTrackingProfile GetTrackingProfile(WorkflowExecutor skedExec)
        {
            if (null != _profile)
                return _profile;
            else
                throw new InvalidOperationException(String.Format(System.Globalization.CultureInfo.CurrentCulture, ExecutionStringManager.NullProfileForChannel, this._scheduleType.AssemblyQualifiedName));
        }
 
        internal void SetTrackingProfile(RTTrackingProfile profile)
        {
            _profile = profile;
        }
 
        /// <summary>
        /// Clone the tracking profile stored in the cache and 
        /// </summary>
        /// <param name="exec"></param>
        internal void MakeProfilePrivate(WorkflowExecutor exec)
        {
            if (null != _profile)
            {
                //
                // If the profile is not already a private copy make it so
                if (!_profile.IsPrivate)
                {
                    _profile = _profile.Clone();
                    _profile.IsPrivate = true;
                }
            }
            else
            {
                //
                // We're not holding a reference to a profile
                // so get it from the cache and clone it into a private copy
                RTTrackingProfile tmp = GetTrackingProfile(exec);
                _profile = tmp.Clone();
                _profile.IsPrivate = true;
            }
        }
    }
    internal class Set<T> : IEnumerable<T> where T : IComparable
    {
        List<T> list = null;
 
        public Set()
        {
            list = new List<T>();
        }
 
        public Set(int capacity)
        {
            list = new List<T>(capacity);
        }
 
        public int Count
        {
            get { return list.Count; }
        }
 
        public void Add(T item)
        {
            int pos = -1;
            if (!Search(item, out pos))
                list.Insert(pos, item);
            else
                throw new ArgumentException(ExecutionStringManager.ItemAlreadyExist);
        }
 
        public bool TryAdd(T item)
        {
            int pos = -1;
            if (!Search(item, out pos))
            {
                list.Insert(pos, item);
                return true;
            }
            else
                return false;
        }
 
        public bool Contains(T item)
        {
            int pos = -1;
            return Search(item, out pos);
        }
 
        public IEnumerator<T> GetEnumerator()
        {
            return list.GetEnumerator();
        }
 
        System.Collections.IEnumerator IEnumerable.GetEnumerator()
        {
            return list.GetEnumerator();
        }
 
        public bool TryGetValue(T item, out T value)
        {
            int pos = -1;
            if (Search(item, out pos))
            {
                value = list[pos];
                return true;
            }
            else
            {
                value = default(T);
                return false;
            }
        }
 
        public T this[int index]
        {
            get { return list[index]; }
        }
 
        private bool Search(T item, out int insertPos)
        {
            insertPos = -1;
 
            int pos = 0,
                high = list.Count,
                low = -1,
                diff = 0;
 
            while (high - low > 1)
            {
                pos = (high + low) / 2;
 
                diff = list[pos].CompareTo(item);
 
                if (0 == diff)
                {
                    insertPos = pos;
                    return true;
                }
                else if (diff > 0)
                    high = pos;
                else
                    low = pos;
            }
 
            if (low == -1)
            {
                insertPos = 0;
                return false;
            }
 
            if (0 != diff)
            {
                insertPos = (diff < 0) ? pos + 1 : pos;
                return false;
            }
 
            return true;
        }
    }
 
    /// <summary>
    /// Persisted tracking State pertaining to workflow invoking for an individual schedule.  There could be multiple called schedules under 
    /// an instance.
    /// </summary>
    [Serializable]
    internal class TrackingCallingState
    {
        #region data members
        private IList<string> callerActivityPathProxy;
        private Guid callerInstanceId;
        private Guid callerContextGuid;
        private Guid callerParentContextGuid;
 
        #endregion data members
 
        #region Property accessors
 
        /// <summary>
        /// Activity proxy of the caller/execer activity, if any
        /// //@@Undone for Ashishmi: Hold on to ActivityPath Proxy  in one of your class impl
        /// </summary>
        /// <value></value>
        internal IList<string> CallerActivityPathProxy
        {
            get { return callerActivityPathProxy; }
            set { callerActivityPathProxy = value; }
        }
 
        /// <summary>
        /// Instance ID of the caller/exec'er schedule, if any
        /// </summary>
        /// <value></value>
        public Guid CallerWorkflowInstanceId
        {
            get { return callerInstanceId; }
            set { callerInstanceId = value; }
        }
        /// <summary>
        /// Context of the caller's invoke activity
        /// </summary>
        /// <value>int</value>
        public Guid CallerContextGuid
        {
            get { return callerContextGuid; }
            set { callerContextGuid = value; }
        }
        /// <summary>
        /// ParentContext of the caller's invoke activity
        /// </summary>
        /// <value>int</value>
        public Guid CallerParentContextGuid
        {
            get { return callerParentContextGuid; }
            set { callerParentContextGuid = value; }
        }
 
        #endregion Property accessors
 
    }
 
    internal static class HashHelper
    {
        internal static Guid HashServiceType(Type serviceType)
        {
            return HashServiceType(serviceType.AssemblyQualifiedName);
        }
 
        [SuppressMessage("Microsoft.Cryptographic.Standard", "CA5350:MD5CannotBeUsed",
            Justification = "Design has been approved.  We are not using MD5 for any security or cryptography purposes but rather as a hash.")]
        // Cannot modify this to use LocalAppContextSwitches.UseLegacyHashForSqlTrackingCacheKey because the Guids that are returned from this
        // are used by the TrackingListenerBroker and are serialized with the cache Dictionary.
        internal static Guid HashServiceType(String serviceFullTypeName)
        {
            byte[] data;
            byte[] result;
 
            UnicodeEncoding ue = new UnicodeEncoding();
            data = ue.GetBytes(serviceFullTypeName);
 
            if (AppSettings.FIPSRequired)
            {
                result = MD5PInvokeHelper.CalculateHash(data);
            }
            else
            {
                MD5 md5 = new MD5CryptoServiceProvider();
                result = md5.ComputeHash(data);
            }
 
            return new Guid(result);
        }
 
        [SuppressMessage("Microsoft.Cryptographic.Standard", "CA5350:MD5CannotBeUsed",
            Justification = "Design has been approved.  We are not using MD5 for any security or cryptography purposes but rather as a hash.")]
        // This is used by SqlTrackingService for its cache, but that cache is not serialized so we can move to the more secure SHA256 algorithm.
        internal static Guid HashStringToGuid(String stringToHash)
        {
            byte[] data;
            byte[] result;
 
            UnicodeEncoding ue = new UnicodeEncoding();
            data = ue.GetBytes(stringToHash);
 
            if (LocalAppContextSwitches.UseLegacyHashForSqlTrackingCacheKey)
            {
                if (AppSettings.FIPSRequired)
                {
                    result = MD5PInvokeHelper.CalculateHash(data);
                }
                else
                {
                    MD5 md5 = new MD5CryptoServiceProvider();
                    result = md5.ComputeHash(data);
                }
            }
            else
            {
                SHA256 sha256Provider = new SHA256CryptoServiceProvider();
                result = sha256Provider.ComputeHash(data);
                // We need a byte[16] to create the Guid.
                byte[] result16 = new byte[16];
                Array.Copy(result, result16, Math.Min(result16.Length, result.Length));
                result = result16;
            }
 
            return new Guid(result);
        }
    }
}