//------------------------------------------------------------ // 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 activityScheduledSubscriptions; List faultPropagationSubscriptions; List cancelRequestedSubscriptions; Dictionary> activitySubscriptions; List customTrackingQuerySubscriptions; Dictionary bookmarkSubscriptions; Dictionary workflowEventSubscriptions; TrackingProfile associatedProfile; TrackingRecordPreFilter trackingRecordPreFilter; List 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 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(); } 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>(); } HybridCollection subscription; if (!this.activitySubscriptions.TryGetValue(query.ActivityName, out subscription)) { subscription = new HybridCollection(); 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(); } this.activityScheduledSubscriptions.Add(activityScheduledQuery); } void AddCancelRequestedSubscription(CancelRequestedQuery cancelQuery) { this.trackingRecordPreFilter.TrackCancelRequestedRecords = true; if (this.cancelRequestedSubscriptions == null) { this.cancelRequestedSubscriptions = new List(); } this.cancelRequestedSubscriptions.Add(cancelQuery); } void AddFaultPropagationSubscription(FaultPropagationQuery faultQuery) { this.trackingRecordPreFilter.TrackFaultPropagationRecords = true; if (this.faultPropagationSubscriptions == null) { this.faultPropagationSubscriptions = new List(); } this.faultPropagationSubscriptions.Add(faultQuery); } void AddBookmarkSubscription(BookmarkResumptionQuery bookmarkTrackingQuery) { this.trackingRecordPreFilter.TrackBookmarkResumptionRecords = true; if (this.bookmarkSubscriptions == null) { this.bookmarkSubscriptions = new Dictionary(); } //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(); } this.customTrackingQuerySubscriptions.Add(customQuery); } void AddWorkflowSubscription(WorkflowInstanceQuery workflowTrackingQuery) { this.trackingRecordPreFilter.TrackWorkflowInstanceRecords = true; if (this.workflowEventSubscriptions == null) { this.workflowEventSubscriptions = new Dictionary(); } 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 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 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(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> cache; public RuntimeTrackingProfileCache() { this.cache = new ConditionalWeakTable>(); } public RuntimeTrackingProfile GetRuntimeTrackingProfile(TrackingProfile profile, Activity rootElement) { Fx.Assert(rootElement != null, "Root element must be valid"); RuntimeTrackingProfile foundRuntimeProfile = null; HybridCollection runtimeProfileList = null; lock (this.cache) { if (!this.cache.TryGetValue(rootElement, out runtimeProfileList)) { foundRuntimeProfile = new RuntimeTrackingProfile(profile, rootElement); runtimeProfileList = new HybridCollection(); runtimeProfileList.Add(foundRuntimeProfile); this.cache.Add(rootElement, runtimeProfileList); } else { ReadOnlyCollection 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; } } } }