File: System\Activities\Tracking\RuntimeTrackingProfile.cs
Project: ndp\cdf\src\NetFx40\System.Activities\System.Activities.csproj (System.Activities)
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//------------------------------------------------------------
 
namespace System.Activities.Tracking
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Collections.Specialized;
    using System.Collections.ObjectModel;
    using System.Runtime;
    using System.Runtime.CompilerServices;
 
    class RuntimeTrackingProfile
    {
        static RuntimeTrackingProfileCache profileCache;
 
        List<ActivityScheduledQuery> activityScheduledSubscriptions;
        List<FaultPropagationQuery> faultPropagationSubscriptions;
        List<CancelRequestedQuery> cancelRequestedSubscriptions;
        Dictionary<string, HybridCollection<ActivityStateQuery>> activitySubscriptions;
        List<CustomTrackingQuery> customTrackingQuerySubscriptions;
        Dictionary<string, BookmarkResumptionQuery> bookmarkSubscriptions;
        Dictionary<string, WorkflowInstanceQuery> workflowEventSubscriptions;
 
        TrackingProfile associatedProfile;
        TrackingRecordPreFilter trackingRecordPreFilter;
        List<string> activityNames;
 
        bool isRootNativeActivity;
 
        internal RuntimeTrackingProfile(TrackingProfile profile, Activity rootElement)
        {
            this.associatedProfile = profile;
            this.isRootNativeActivity = rootElement is NativeActivity;
            this.trackingRecordPreFilter = new TrackingRecordPreFilter();
 
            foreach (TrackingQuery query in this.associatedProfile.Queries)
            {
                if (query is ActivityStateQuery)
                {
                    AddActivitySubscription((ActivityStateQuery)query);
                }
                else if (query is WorkflowInstanceQuery)
                {
                    AddWorkflowSubscription((WorkflowInstanceQuery)query);
                }
                else if (query is BookmarkResumptionQuery)
                {
                    AddBookmarkSubscription((BookmarkResumptionQuery)query);
                }
                else if (query is CustomTrackingQuery)
                {
                    AddCustomTrackingSubscription((CustomTrackingQuery)query);
                }
                else if (query is ActivityScheduledQuery)
                {
                    AddActivityScheduledSubscription((ActivityScheduledQuery)query);
                }
                else if (query is CancelRequestedQuery)
                {
                    AddCancelRequestedSubscription((CancelRequestedQuery)query);
                }
                else if (query is FaultPropagationQuery)
                {
                    AddFaultPropagationSubscription((FaultPropagationQuery)query);
                }
            }
        }
 
        static RuntimeTrackingProfileCache Cache
        {
            get
            {
                // We do not take a lock here because a true singleton is not required.
                if (profileCache == null)
                {
                    profileCache = new RuntimeTrackingProfileCache();
                }
                return profileCache;
            }
        }
 
        internal TrackingRecordPreFilter Filter
        {
            get
            {
                return this.trackingRecordPreFilter;
            }
        }
 
        internal IEnumerable<string> GetSubscribedActivityNames()
        {
            return this.activityNames;
        }
 
        bool ShouldTrackActivity(ActivityInfo activityInfo, string queryName)
        {
            if (activityInfo != null && queryName == "*")
            {
                if (this.isRootNativeActivity)
                {
                    if (activityInfo.Activity.MemberOf.ParentId != 0)
                    {
                        return false;
                    }
                }
                else
                {
                    if ((activityInfo.Activity.MemberOf.ParentId != 0)
                        && (activityInfo.Activity.MemberOf.Parent.ParentId != 0))
                    {
                        return false;
                    }
                }
            }
 
            return true;
        }
 
        void AddActivityName(string name)
        {
            if (this.activityNames == null)
            {
                this.activityNames = new List<string>();
            }
            this.activityNames.Add(name);
        }
 
        internal static RuntimeTrackingProfile GetRuntimeTrackingProfile(TrackingProfile profile, Activity rootElement)
        {
            return RuntimeTrackingProfile.Cache.GetRuntimeTrackingProfile(profile, rootElement);
        }
 
        void AddActivitySubscription(ActivityStateQuery query)
        {
            this.trackingRecordPreFilter.TrackActivityStateRecords = true;
 
            foreach (string state in query.States)
            {
                if (string.CompareOrdinal(state, "*") == 0)
                {
                    this.trackingRecordPreFilter.TrackActivityStateRecordsClosedState = true;
                    this.trackingRecordPreFilter.TrackActivityStateRecordsExecutingState = true;
                    break;
                }
                if (string.CompareOrdinal(state, ActivityStates.Closed) == 0)
                {
                    this.trackingRecordPreFilter.TrackActivityStateRecordsClosedState = true;
                }
                else if (string.CompareOrdinal(state, ActivityStates.Executing) == 0)
                {
                    this.trackingRecordPreFilter.TrackActivityStateRecordsExecutingState = true;
                }
            }
 
            if (this.activitySubscriptions == null)
            {
                this.activitySubscriptions = new Dictionary<string, HybridCollection<ActivityStateQuery>>();
            }
 
            HybridCollection<ActivityStateQuery> subscription;
            if (!this.activitySubscriptions.TryGetValue(query.ActivityName, out subscription))
            {
                subscription = new HybridCollection<ActivityStateQuery>();
                this.activitySubscriptions[query.ActivityName] = subscription;
            }
            subscription.Add((ActivityStateQuery)query);
            AddActivityName(query.ActivityName);
        }
 
        void AddActivityScheduledSubscription(ActivityScheduledQuery activityScheduledQuery)
        {
            this.trackingRecordPreFilter.TrackActivityScheduledRecords = true;
            if (this.activityScheduledSubscriptions == null)
            {
                this.activityScheduledSubscriptions = new List<ActivityScheduledQuery>();
            }
            this.activityScheduledSubscriptions.Add(activityScheduledQuery);
        }
 
        void AddCancelRequestedSubscription(CancelRequestedQuery cancelQuery)
        {
            this.trackingRecordPreFilter.TrackCancelRequestedRecords = true;
            if (this.cancelRequestedSubscriptions == null)
            {
                this.cancelRequestedSubscriptions = new List<CancelRequestedQuery>();
            }
            this.cancelRequestedSubscriptions.Add(cancelQuery);
        }
 
        void AddFaultPropagationSubscription(FaultPropagationQuery faultQuery)
        {
            this.trackingRecordPreFilter.TrackFaultPropagationRecords = true;
            if (this.faultPropagationSubscriptions == null)
            {
                this.faultPropagationSubscriptions = new List<FaultPropagationQuery>();
            }
            this.faultPropagationSubscriptions.Add(faultQuery);
        }
 
        void AddBookmarkSubscription(BookmarkResumptionQuery bookmarkTrackingQuery)
        {
            this.trackingRecordPreFilter.TrackBookmarkResumptionRecords = true;
            if (this.bookmarkSubscriptions == null)
            {
                this.bookmarkSubscriptions = new Dictionary<string, BookmarkResumptionQuery>();
            }
            //if duplicates are found, use only the first subscription for a given bookmark name.
            if (!this.bookmarkSubscriptions.ContainsKey(bookmarkTrackingQuery.Name))
            {
                this.bookmarkSubscriptions.Add(bookmarkTrackingQuery.Name, bookmarkTrackingQuery);
            }
        }
 
        void AddCustomTrackingSubscription(CustomTrackingQuery customQuery)
        {
            if (this.customTrackingQuerySubscriptions == null)
            {
                this.customTrackingQuerySubscriptions = new List<CustomTrackingQuery>();
            }
            this.customTrackingQuerySubscriptions.Add(customQuery);
        }
 
        void AddWorkflowSubscription(WorkflowInstanceQuery workflowTrackingQuery)
        {
            this.trackingRecordPreFilter.TrackWorkflowInstanceRecords = true;
 
            if (this.workflowEventSubscriptions == null)
            {
                this.workflowEventSubscriptions = new Dictionary<string, WorkflowInstanceQuery>();
            }
            if (workflowTrackingQuery.HasStates)
            {
                foreach (string state in workflowTrackingQuery.States)
                {
                    //if duplicates are found, use only the first subscription for a given state.
                    if (!this.workflowEventSubscriptions.ContainsKey(state))
                    {
                        this.workflowEventSubscriptions.Add(state, workflowTrackingQuery);
                    }
                }
            }
        }
 
        internal TrackingRecord Match(TrackingRecord record, bool shouldClone)
        {
            TrackingQuery resultQuery = null;
            if (record is WorkflowInstanceRecord)
            {
                resultQuery = Match((WorkflowInstanceRecord)record);
            }
            else if (record is ActivityStateRecord)
            {
                resultQuery = Match((ActivityStateRecord)record);
            }
            else if (record is BookmarkResumptionRecord)
            {
                resultQuery = Match((BookmarkResumptionRecord)record);
            }
            else if (record is CustomTrackingRecord)
            {
                resultQuery = Match((CustomTrackingRecord)record);
            }
            else if (record is ActivityScheduledRecord)
            {
                resultQuery = Match((ActivityScheduledRecord)record);
            }
            else if (record is CancelRequestedRecord)
            {
                resultQuery = Match((CancelRequestedRecord)record);
            }
            else if (record is FaultPropagationRecord)
            {
                resultQuery = Match((FaultPropagationRecord)record);
            }
            
            return resultQuery == null ? null : PrepareRecord(record, resultQuery, shouldClone);
        }
 
        ActivityStateQuery Match(ActivityStateRecord activityStateRecord)
        {
            ActivityStateQuery query = null;
            if (this.activitySubscriptions != null)
            {
                HybridCollection<ActivityStateQuery> eventSubscriptions;
                //first look for a specific match, if not found, look for a generic match.
                if (this.activitySubscriptions.TryGetValue(activityStateRecord.Activity.Name, out eventSubscriptions))
                {
                    query = MatchActivityState(activityStateRecord, eventSubscriptions.AsReadOnly());
                }
 
                if (query == null && this.activitySubscriptions.TryGetValue("*", out eventSubscriptions))
                {
                    query = MatchActivityState(activityStateRecord, eventSubscriptions.AsReadOnly());
 
                    if ((query != null) && (this.associatedProfile.ImplementationVisibility == ImplementationVisibility.RootScope))
                    {
                        if (!ShouldTrackActivity(activityStateRecord.Activity, "*"))
                        {
                            return null;
                        }
                    }
                }
            }
 
            return query;
        }
 
        static ActivityStateQuery MatchActivityState(ActivityStateRecord activityRecord, ReadOnlyCollection<ActivityStateQuery> subscriptions)
        {
            ActivityStateQuery genericMatch = null;
            for (int i = 0; i < subscriptions.Count; i++)
            {
                if (subscriptions[i].States.Contains(activityRecord.State))
                {
                    return subscriptions[i];
                }
                else if (subscriptions[i].States.Contains("*"))
                {
                    if (genericMatch == null)
                    {
                        genericMatch = subscriptions[i];
                    }
                }
            }
            return genericMatch;
        }
 
        WorkflowInstanceQuery Match(WorkflowInstanceRecord workflowRecord)
        {
            WorkflowInstanceQuery trackingQuery = null;
            if (this.workflowEventSubscriptions != null)
            {
                if (!this.workflowEventSubscriptions.TryGetValue(workflowRecord.State, out trackingQuery))
                {
                    this.workflowEventSubscriptions.TryGetValue("*", out trackingQuery);
                }
            }
            return trackingQuery;
        }
 
        BookmarkResumptionQuery Match(BookmarkResumptionRecord bookmarkRecord)
        {
            BookmarkResumptionQuery trackingQuery = null;
            if (this.bookmarkSubscriptions != null)
            {
                if (bookmarkRecord.BookmarkName != null)
                {
                    this.bookmarkSubscriptions.TryGetValue(bookmarkRecord.BookmarkName, out trackingQuery);
                }
                if (trackingQuery == null)
                {
                    this.bookmarkSubscriptions.TryGetValue("*", out trackingQuery);
                }
            }
            return trackingQuery;
        }
 
        ActivityScheduledQuery Match(ActivityScheduledRecord activityScheduledRecord)
        {
            ActivityScheduledQuery query = null;
            if (this.activityScheduledSubscriptions != null)
            {
                for (int i = 0; i < this.activityScheduledSubscriptions.Count; i++)
                {
                    //check specific and then generic
                    string activityName = activityScheduledRecord.Activity == null ? null : activityScheduledRecord.Activity.Name;
                    if (string.CompareOrdinal(this.activityScheduledSubscriptions[i].ActivityName, activityName) == 0)
                    {
                        if (CheckSubscription(this.activityScheduledSubscriptions[i].ChildActivityName, activityScheduledRecord.Child.Name))
                        {
                            query = this.activityScheduledSubscriptions[i];
                            break;
                        }
 
                    }
                    else if (string.CompareOrdinal(this.activityScheduledSubscriptions[i].ActivityName, "*") == 0)
                    {
                        if (CheckSubscription(this.activityScheduledSubscriptions[i].ChildActivityName, activityScheduledRecord.Child.Name))
                        {
                            query = this.activityScheduledSubscriptions[i];
                            break;
                        }
                    }
                }
            }
 
            if ((query != null) && (this.associatedProfile.ImplementationVisibility == ImplementationVisibility.RootScope))
            {
                if ((!ShouldTrackActivity(activityScheduledRecord.Activity, query.ActivityName)) ||
                        (!ShouldTrackActivity(activityScheduledRecord.Child, query.ChildActivityName)))
                {
                    return null;
                }
            }
 
            return query;
        }
 
        FaultPropagationQuery Match(FaultPropagationRecord faultRecord)
        {
            FaultPropagationQuery query = null;
            if (this.faultPropagationSubscriptions != null)
            {
                for (int i = 0; i < this.faultPropagationSubscriptions.Count; i++)
                {
                    //check specific and then generic
                    string faultHandlerName = faultRecord.FaultHandler == null ? null : faultRecord.FaultHandler.Name;
                    if (string.CompareOrdinal(this.faultPropagationSubscriptions[i].FaultSourceActivityName, faultRecord.FaultSource.Name) == 0)
                    {
                        if (CheckSubscription(this.faultPropagationSubscriptions[i].FaultHandlerActivityName, faultHandlerName))
                        {
                            query = this.faultPropagationSubscriptions[i];
                            break;
                        }
                    }
                    else if (string.CompareOrdinal(this.faultPropagationSubscriptions[i].FaultSourceActivityName, "*") == 0)
                    {
                        if (CheckSubscription(this.faultPropagationSubscriptions[i].FaultHandlerActivityName, faultHandlerName))
                        {
                            query = this.faultPropagationSubscriptions[i];
                            break;
                        }
                    }
                }
            }
 
            if ((query != null) && (this.associatedProfile.ImplementationVisibility == ImplementationVisibility.RootScope))
            {
                if ((!ShouldTrackActivity(faultRecord.FaultHandler, query.FaultHandlerActivityName)) ||
                    (!ShouldTrackActivity(faultRecord.FaultSource, query.FaultSourceActivityName)))
                {
                    return null;
                }
            }
 
            return query;
        }
 
        CancelRequestedQuery Match(CancelRequestedRecord cancelRecord)
        {
            CancelRequestedQuery query = null;
 
            if (this.cancelRequestedSubscriptions != null)
            {
                for (int i = 0; i < this.cancelRequestedSubscriptions.Count; i++)
                {
                    //check specific and then generic
                    string activityName = cancelRecord.Activity == null ? null : cancelRecord.Activity.Name;
                    if (string.CompareOrdinal(this.cancelRequestedSubscriptions[i].ActivityName, activityName) == 0)
                    {
                        if (CheckSubscription(this.cancelRequestedSubscriptions[i].ChildActivityName, cancelRecord.Child.Name))
                        {
                            query = this.cancelRequestedSubscriptions[i];
                            break;
                        }
                    }
                    else if (string.CompareOrdinal(this.cancelRequestedSubscriptions[i].ActivityName, "*") == 0)
                    {
                        if (CheckSubscription(this.cancelRequestedSubscriptions[i].ChildActivityName, cancelRecord.Child.Name))
                        {
                            query = this.cancelRequestedSubscriptions[i];
                            break;
                        }
                    }
                }
            }
 
            if ((query != null) && (this.associatedProfile.ImplementationVisibility == ImplementationVisibility.RootScope))
            {
                if ((!ShouldTrackActivity(cancelRecord.Activity, query.ActivityName)) ||
                    (!ShouldTrackActivity(cancelRecord.Child, query.ChildActivityName)))
                {
                    return null;
                }
            }
 
            return query;
        }
 
        CustomTrackingQuery Match(CustomTrackingRecord customRecord)
        {
            CustomTrackingQuery query = null;
 
            if (this.customTrackingQuerySubscriptions != null)
            {
                for (int i = 0; i < this.customTrackingQuerySubscriptions.Count; i++)
                {
                    //check specific and then generic
                    if (string.CompareOrdinal(this.customTrackingQuerySubscriptions[i].Name, customRecord.Name) == 0)
                    {
                        if (CheckSubscription(this.customTrackingQuerySubscriptions[i].ActivityName, customRecord.Activity.Name))
                        {
                            query = this.customTrackingQuerySubscriptions[i];
                            break;
                        }
                    }
                    else if (string.CompareOrdinal(this.customTrackingQuerySubscriptions[i].Name, "*") == 0)
                    {
                        if (CheckSubscription(this.customTrackingQuerySubscriptions[i].ActivityName, customRecord.Activity.Name))
                        {
                            query = this.customTrackingQuerySubscriptions[i];
                            break;
                        }
                    }
                }
            }
            return query;
        }
 
        static bool CheckSubscription(string name, string value)
        {
            //check specific and then generic
            return (string.CompareOrdinal(name, value) == 0 ||
                string.CompareOrdinal(name, "*") == 0);
        }
 
        static void ExtractVariables(ActivityStateRecord activityStateRecord, ActivityStateQuery activityStateQuery)
        {
            if (activityStateQuery.HasVariables)
            {
                activityStateRecord.Variables = activityStateRecord.GetVariables(activityStateQuery.Variables);
            }
            else
            {
                activityStateRecord.Variables = ActivityUtilities.EmptyParameters;
            }
        }
 
        static void ExtractArguments(ActivityStateRecord activityStateRecord, ActivityStateQuery activityStateQuery)
        {
            if (activityStateQuery.HasArguments)
            {
                activityStateRecord.Arguments = activityStateRecord.GetArguments(activityStateQuery.Arguments);
            }
            else
            {
                activityStateRecord.Arguments = ActivityUtilities.EmptyParameters;
            }
        }
 
        static TrackingRecord PrepareRecord(TrackingRecord record, TrackingQuery query, bool shouldClone)
        {
            TrackingRecord preparedRecord = shouldClone ? record.Clone() : record;
 
            if (query.HasAnnotations)
            {
                preparedRecord.Annotations = new ReadOnlyDictionaryInternal<string, string>(query.QueryAnnotations);
            }
 
            if (query is ActivityStateQuery)
            {
                ExtractArguments((ActivityStateRecord)preparedRecord, (ActivityStateQuery)query);
                ExtractVariables((ActivityStateRecord)preparedRecord, (ActivityStateQuery)query);                
            }
            return preparedRecord;
        }
 
 
        class RuntimeTrackingProfileCache
        {
            [Fx.Tag.Cache(typeof(RuntimeTrackingProfile), Fx.Tag.CacheAttrition.PartialPurgeOnEachAccess)]
            ConditionalWeakTable<Activity, HybridCollection<RuntimeTrackingProfile>> cache;
 
            public RuntimeTrackingProfileCache()
            {
                this.cache = new ConditionalWeakTable<Activity, HybridCollection<RuntimeTrackingProfile>>();
            }
 
            public RuntimeTrackingProfile GetRuntimeTrackingProfile(TrackingProfile profile, Activity rootElement)
            {
                Fx.Assert(rootElement != null, "Root element must be valid");
 
                RuntimeTrackingProfile foundRuntimeProfile = null;
                HybridCollection<RuntimeTrackingProfile> runtimeProfileList = null;
 
                lock (this.cache)
                {
                    if (!this.cache.TryGetValue(rootElement, out runtimeProfileList))
                    {
                        foundRuntimeProfile = new RuntimeTrackingProfile(profile, rootElement);
                        runtimeProfileList = new HybridCollection<RuntimeTrackingProfile>();
                        runtimeProfileList.Add(foundRuntimeProfile);
 
                        this.cache.Add(rootElement, runtimeProfileList);
                    }
                    else
                    {
                        ReadOnlyCollection<RuntimeTrackingProfile> runtimeProfileCollection = runtimeProfileList.AsReadOnly();
                        foreach (RuntimeTrackingProfile runtimeProfile in runtimeProfileCollection)
                        {
                            if (string.CompareOrdinal(profile.Name, runtimeProfile.associatedProfile.Name) == 0 &&
                                string.CompareOrdinal(profile.ActivityDefinitionId, runtimeProfile.associatedProfile.ActivityDefinitionId) == 0)
                            {
                                foundRuntimeProfile = runtimeProfile;
                                break;
                            }
                        }
 
                        if (foundRuntimeProfile == null)
                        {
                            foundRuntimeProfile = new RuntimeTrackingProfile(profile, rootElement);
                            runtimeProfileList.Add(foundRuntimeProfile);
                        }
                    }
                }
                return foundRuntimeProfile;
            }
        }
    }
}