749 lines
30 KiB
C#
749 lines
30 KiB
C#
|
|
|||
|
using System;
|
|||
|
using System.Collections;
|
|||
|
using System.Collections.Generic;
|
|||
|
using System.Collections.ObjectModel;
|
|||
|
using System.Text;
|
|||
|
using System.Xml;
|
|||
|
using System.Xml.Schema;
|
|||
|
using System.IO;
|
|||
|
using System.Reflection;
|
|||
|
using System.Diagnostics;
|
|||
|
using System.Runtime.Serialization;
|
|||
|
using System.Security.Permissions;
|
|||
|
using System.Globalization;
|
|||
|
|
|||
|
//using System.Workflow.Activities;
|
|||
|
using System.Workflow.ComponentModel;
|
|||
|
using System.Workflow.Runtime;
|
|||
|
using System.Workflow.Runtime.Hosting;
|
|||
|
using Hosting = System.Workflow.Runtime.Hosting;
|
|||
|
using System.Workflow.Runtime.Tracking;
|
|||
|
|
|||
|
|
|||
|
namespace System.Workflow.Runtime
|
|||
|
{
|
|||
|
/// <summary>
|
|||
|
/// RTTrackingProfile contains functionality specific to the runtime such as
|
|||
|
/// trackpoint and location matching and caching, cloning, handling dynamic updates...
|
|||
|
/// </summary>
|
|||
|
internal class RTTrackingProfile : ICloneable // ICloneable is deprecated
|
|||
|
{
|
|||
|
#region Private Data Members
|
|||
|
//
|
|||
|
// Client defined profile
|
|||
|
private TrackingProfile _profile = null;
|
|||
|
//
|
|||
|
// Type of the workflow that this profile is associated to
|
|||
|
private Type _workflowType = null;
|
|||
|
private Type _serviceType = null;
|
|||
|
//
|
|||
|
// List of qualified ids and the trackpoints that declared themselves as matches during static examination
|
|||
|
private Dictionary<string, List<ActivityTrackPointCacheItem>> _activities = new Dictionary<string, List<ActivityTrackPointCacheItem>>();
|
|||
|
private List<string> _activitiesIgnore = new List<string>();
|
|||
|
|
|||
|
private Dictionary<string, List<UserTrackPoint>> _user = new Dictionary<string, List<UserTrackPoint>>();
|
|||
|
private List<string> _userIgnore = new List<string>();
|
|||
|
//
|
|||
|
// Indicates that the RTTrackingProfile instance is private and is safe to modify for a specific instance
|
|||
|
private bool _isPrivate = false;
|
|||
|
//
|
|||
|
// Indicates if a dynamic update is in-flight
|
|||
|
private bool _pendingWorkflowChange = false;
|
|||
|
//
|
|||
|
// The changes for a dynamic update
|
|||
|
private IList<WorkflowChangeAction> _pendingChanges = null;
|
|||
|
//
|
|||
|
// Activities (including those that are being added) can start executing while a dynamic update is pending
|
|||
|
// These cannot be added to the main cache until the update succeeds because the update might roll back.
|
|||
|
// However since we have to search for matching track points we might as well save that work.
|
|||
|
// This list will be copied into the main cache if the dynamic update completes successfully
|
|||
|
private Dictionary<string, List<ActivityTrackPointCacheItem>> _dynamicActivities = null;
|
|||
|
private List<string> _dynamicActivitiesIgnore = null;
|
|||
|
|
|||
|
private Dictionary<string, List<UserTrackPoint>> _dynamicUser = null;
|
|||
|
private List<string> _dynamicUserIgnore = null;
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Constructors
|
|||
|
/// <summary>
|
|||
|
/// Default constructor
|
|||
|
/// </summary>
|
|||
|
protected RTTrackingProfile()
|
|||
|
{
|
|||
|
}
|
|||
|
/// <summary>
|
|||
|
/// Primary constructor
|
|||
|
/// </summary>
|
|||
|
/// <param name="root"></param>
|
|||
|
/// <param name="workflowType"></param>
|
|||
|
/// <param name="profile"></param>
|
|||
|
internal RTTrackingProfile(TrackingProfile profile, Activity root, Type serviceType)
|
|||
|
{
|
|||
|
if (null == profile)
|
|||
|
throw new ArgumentNullException("profile");
|
|||
|
if (null == root)
|
|||
|
throw new ArgumentNullException("root");
|
|||
|
if (null == serviceType)
|
|||
|
throw new ArgumentNullException("serviceType");
|
|||
|
|
|||
|
_workflowType = root.GetType();
|
|||
|
_serviceType = serviceType;
|
|||
|
//
|
|||
|
// "Clone" a private copy in case the tracking service holds a reference to
|
|||
|
// the profile it gave us and attempts to modify it at a later point
|
|||
|
TrackingProfileSerializer tps = new TrackingProfileSerializer();
|
|||
|
|
|||
|
StringWriter writer = new StringWriter(System.Globalization.CultureInfo.InvariantCulture);
|
|||
|
StringReader reader = null;
|
|||
|
|
|||
|
TrackingProfile privateProfile = null;
|
|||
|
|
|||
|
try
|
|||
|
{
|
|||
|
//
|
|||
|
// Let exceptions bubble back to the tracking service -
|
|||
|
// the profile must be valid per the schema.
|
|||
|
tps.Serialize(writer, profile);
|
|||
|
reader = new StringReader(writer.ToString());
|
|||
|
privateProfile = tps.Deserialize(reader);
|
|||
|
}
|
|||
|
finally
|
|||
|
{
|
|||
|
if (null != reader)
|
|||
|
reader.Close();
|
|||
|
|
|||
|
if (null != writer)
|
|||
|
writer.Close();
|
|||
|
}
|
|||
|
_profile = privateProfile;
|
|||
|
|
|||
|
CheckAllActivities((Activity)root);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Constructor used for cloning.
|
|||
|
/// </summary>
|
|||
|
/// <param name="runtimeProfile">RTTrackingProfile to clone</param>
|
|||
|
/// <remarks>All members are shallow copied! Use MakePrivate to deep copy after cloning.</remarks>
|
|||
|
private RTTrackingProfile(RTTrackingProfile runtimeProfile)
|
|||
|
{
|
|||
|
//
|
|||
|
// Shallow copy
|
|||
|
_profile = runtimeProfile._profile;
|
|||
|
_isPrivate = runtimeProfile._isPrivate;
|
|||
|
_pendingChanges = runtimeProfile._pendingChanges;
|
|||
|
_pendingWorkflowChange = runtimeProfile._pendingWorkflowChange;
|
|||
|
_workflowType = runtimeProfile._workflowType;
|
|||
|
//
|
|||
|
// Deep copy the cache. Items in the cache can
|
|||
|
// be shared but the cache themselves cannot as they may be modified
|
|||
|
//
|
|||
|
// Activity match and ignore cache
|
|||
|
_activities = new Dictionary<string, List<ActivityTrackPointCacheItem>>(runtimeProfile._activities.Count);
|
|||
|
foreach (KeyValuePair<string, List<ActivityTrackPointCacheItem>> kvp in runtimeProfile._activities)
|
|||
|
_activities.Add(kvp.Key, runtimeProfile._activities[kvp.Key]);
|
|||
|
|
|||
|
_activitiesIgnore = new List<string>(runtimeProfile._activitiesIgnore);
|
|||
|
//
|
|||
|
// Pending dynamic update activity match and ignore cache
|
|||
|
if (null != runtimeProfile._dynamicActivities)
|
|||
|
{
|
|||
|
_dynamicActivities = new Dictionary<string, List<ActivityTrackPointCacheItem>>(runtimeProfile._dynamicActivities.Count);
|
|||
|
foreach (KeyValuePair<string, List<ActivityTrackPointCacheItem>> kvp in runtimeProfile._dynamicActivities)
|
|||
|
_dynamicActivities.Add(kvp.Key, runtimeProfile._dynamicActivities[kvp.Key]);
|
|||
|
}
|
|||
|
|
|||
|
if (null != runtimeProfile._dynamicActivitiesIgnore)
|
|||
|
_dynamicActivitiesIgnore = new List<string>(runtimeProfile._dynamicActivitiesIgnore);
|
|||
|
//
|
|||
|
// User event match and ignore cache
|
|||
|
_user = new Dictionary<string, List<UserTrackPoint>>(runtimeProfile._user.Count);
|
|||
|
foreach (KeyValuePair<string, List<UserTrackPoint>> kvp in runtimeProfile._user)
|
|||
|
_user.Add(kvp.Key, runtimeProfile._user[kvp.Key]);
|
|||
|
|
|||
|
_userIgnore = new List<string>(runtimeProfile._userIgnore);
|
|||
|
//
|
|||
|
// Pending dynamic update activity match and ignore cache
|
|||
|
if (null != runtimeProfile._dynamicUser)
|
|||
|
{
|
|||
|
_dynamicUser = new Dictionary<string, List<UserTrackPoint>>(runtimeProfile._dynamicUser.Count);
|
|||
|
foreach (KeyValuePair<string, List<UserTrackPoint>> kvp in runtimeProfile._dynamicUser)
|
|||
|
_dynamicUser.Add(kvp.Key, kvp.Value);
|
|||
|
}
|
|||
|
|
|||
|
if (null != runtimeProfile._dynamicUserIgnore)
|
|||
|
_dynamicUserIgnore = new List<string>(runtimeProfile._dynamicUserIgnore);
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Properties
|
|||
|
/// <summary>
|
|||
|
/// Indicates if the profile is specific to an individual instance.
|
|||
|
/// </summary>
|
|||
|
internal bool IsPrivate
|
|||
|
{
|
|||
|
get { return _isPrivate; }
|
|||
|
set
|
|||
|
{
|
|||
|
if (!(value) && (_isPrivate))
|
|||
|
throw new InvalidOperationException(ExecutionStringManager.CannotResetIsPrivate);
|
|||
|
|
|||
|
_isPrivate = value;
|
|||
|
}
|
|||
|
}
|
|||
|
/// <summary>
|
|||
|
/// Type of workflow to which this profile is associated
|
|||
|
/// </summary>
|
|||
|
internal Type WorkflowType
|
|||
|
{
|
|||
|
get { return _workflowType; }
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Version of the profile
|
|||
|
/// </summary>
|
|||
|
internal Version Version
|
|||
|
{
|
|||
|
get { return _profile.Version; }
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Internal Methods for Listeners
|
|||
|
|
|||
|
internal bool TryTrackActivityEvent(Activity activity, ActivityExecutionStatus status, IServiceProvider provider, ActivityTrackingRecord record)
|
|||
|
{
|
|||
|
List<ActivityTrackPointCacheItem> points;
|
|||
|
//
|
|||
|
// Check the match caches.
|
|||
|
if (TryGetCacheItems(activity, out points))
|
|||
|
{
|
|||
|
bool ret = false;
|
|||
|
foreach (ActivityTrackPointCacheItem item in points)
|
|||
|
{
|
|||
|
if (item.HasLocationConditions)
|
|||
|
{
|
|||
|
if (!item.Point.IsMatch(activity, status))
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
if (item.Events.Contains(status))
|
|||
|
{
|
|||
|
ret = true;
|
|||
|
item.Point.Track(activity, provider, record.Body);
|
|||
|
record.Annotations.AddRange(item.Point.Annotations);
|
|||
|
}
|
|||
|
}
|
|||
|
return ret;
|
|||
|
}
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
internal bool TryTrackUserEvent(Activity activity, string keyName, object argument, WorkflowExecutor exec, UserTrackingRecord record)
|
|||
|
{
|
|||
|
List<UserTrackPoint> points;
|
|||
|
if (TryGetCacheItems(activity, out points))
|
|||
|
{
|
|||
|
bool ret = false;
|
|||
|
foreach (UserTrackPoint point in points)
|
|||
|
{
|
|||
|
if (point.IsMatch(activity, keyName, argument))
|
|||
|
{
|
|||
|
ret = true;
|
|||
|
point.Track(activity, argument, exec, record.Body);
|
|||
|
record.Annotations.AddRange(point.Annotations);
|
|||
|
}
|
|||
|
}
|
|||
|
return ret;
|
|||
|
}
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
internal bool TryTrackInstanceEvent(TrackingWorkflowEvent status, WorkflowTrackingRecord record)
|
|||
|
{
|
|||
|
bool track = false;
|
|||
|
foreach (WorkflowTrackPoint point in _profile.WorkflowTrackPoints)
|
|||
|
{
|
|||
|
if (point.IsMatch(status))
|
|||
|
{
|
|||
|
record.Annotations.AddRange(point.Annotations);
|
|||
|
track = true;
|
|||
|
}
|
|||
|
}
|
|||
|
return track;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Called by TrackingListener to determine if a subscription is needed for an activity.
|
|||
|
/// Also used as an entry point for dynamically building cache entries for dynamically added activities.
|
|||
|
/// </summary>
|
|||
|
/// <param name="activity"></param>
|
|||
|
/// <param name="exec"></param>
|
|||
|
/// <returns></returns>
|
|||
|
internal bool ActivitySubscriptionNeeded(Activity activity)
|
|||
|
{
|
|||
|
List<ActivityTrackPointCacheItem> points = null;
|
|||
|
if ((!_pendingWorkflowChange) || ((_pendingWorkflowChange) && (!IsPendingUpdateActivity(activity, true))))
|
|||
|
{
|
|||
|
//
|
|||
|
// A dynamic update is not in progress or
|
|||
|
// the activity is not part of the dynamic update.
|
|||
|
// The main cache has all matching track points
|
|||
|
//
|
|||
|
//
|
|||
|
bool retry = true;
|
|||
|
while (retry)
|
|||
|
{
|
|||
|
if (_activitiesIgnore.Contains(activity.QualifiedName))
|
|||
|
return false;
|
|||
|
|
|||
|
if (_activities.TryGetValue(activity.QualifiedName, out points))
|
|||
|
return true;
|
|||
|
else
|
|||
|
//
|
|||
|
// This activity isn't in either cache, look it up in the profile and add to cache
|
|||
|
CheckActivity(activity);
|
|||
|
}
|
|||
|
return false;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
//
|
|||
|
// Dynamic update is in progress and this activity is being added as part of the update
|
|||
|
// Search the profile for matching track points and add them to the dynamic cache
|
|||
|
// (copied to the main cache at the successful completion of the update)
|
|||
|
// Don't go through CheckActivity because that adds to the main cache
|
|||
|
List<UserTrackPoint> user = null;
|
|||
|
if (CreateCacheItems(activity, out user))
|
|||
|
CacheInsertUpdatePending(activity.QualifiedName, user);
|
|||
|
else
|
|||
|
_dynamicUserIgnore.Add(activity.QualifiedName);
|
|||
|
|
|||
|
if (CreateCacheItems(activity, out points))
|
|||
|
{
|
|||
|
CacheInsertUpdatePending(activity.QualifiedName, points);
|
|||
|
return true;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
_dynamicActivitiesIgnore.Add(activity.QualifiedName);
|
|||
|
return false;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
public void WorkflowChangeBegin(IList<WorkflowChangeAction> changeActions)
|
|||
|
{
|
|||
|
Debug.Assert(!_pendingWorkflowChange, "_pendingWorkflowChange should be false.");
|
|||
|
if (_pendingWorkflowChange)
|
|||
|
throw new InvalidOperationException(ExecutionStringManager.DynamicUpdateIsNotPending);
|
|||
|
|
|||
|
if (!_isPrivate)
|
|||
|
throw new InvalidOperationException(ExecutionStringManager.ProfileIsNotPrivate);
|
|||
|
//
|
|||
|
// Initialize the temp dictionary for activities that are spun up during the update process
|
|||
|
// If the update succeeds we'll copy these to the main _subscriptions dictionary.
|
|||
|
_dynamicActivities = new Dictionary<string, List<ActivityTrackPointCacheItem>>();
|
|||
|
_dynamicActivitiesIgnore = new List<string>();
|
|||
|
|
|||
|
_dynamicUser = new Dictionary<string, List<UserTrackPoint>>();
|
|||
|
_dynamicUserIgnore = new List<string>();
|
|||
|
|
|||
|
_pendingChanges = changeActions;
|
|||
|
_pendingWorkflowChange = true;
|
|||
|
}
|
|||
|
|
|||
|
public void WorkflowChangeCommit()
|
|||
|
{
|
|||
|
Debug.Assert(_pendingWorkflowChange, "Workflow change is not pending - no change to commit");
|
|||
|
|
|||
|
if (!_pendingWorkflowChange)
|
|||
|
return;
|
|||
|
|
|||
|
if (!_isPrivate)
|
|||
|
throw new InvalidOperationException(ExecutionStringManager.ProfileIsNotPrivate);
|
|||
|
//
|
|||
|
// Remove items that have been deleted by this update
|
|||
|
// Must do all removes first as there may be a new action
|
|||
|
// with the same qid as a previous action that is being removed
|
|||
|
if (null != _pendingChanges)
|
|||
|
{
|
|||
|
foreach (WorkflowChangeAction action in _pendingChanges)
|
|||
|
{
|
|||
|
if (action is RemovedActivityAction)
|
|||
|
{
|
|||
|
//
|
|||
|
// Remove all references to this activity that might exist in our caches
|
|||
|
string qId = ((RemovedActivityAction)action).OriginalRemovedActivity.QualifiedName;
|
|||
|
_activities.Remove(qId);
|
|||
|
_activitiesIgnore.Remove(qId);
|
|||
|
_user.Remove(qId);
|
|||
|
_userIgnore.Remove(qId);
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
//
|
|||
|
// Copy any pending cache items to the regular activity track point cache
|
|||
|
if ((null != _dynamicActivities) && (_dynamicActivities.Count > 0))
|
|||
|
foreach (KeyValuePair<string, List<ActivityTrackPointCacheItem>> kvp in _dynamicActivities)
|
|||
|
_activities.Add(kvp.Key, kvp.Value);
|
|||
|
|
|||
|
if ((null != _dynamicActivitiesIgnore) && (_dynamicActivitiesIgnore.Count > 0))
|
|||
|
_activitiesIgnore.AddRange(_dynamicActivitiesIgnore);
|
|||
|
|
|||
|
if ((null != _dynamicUser) && (_dynamicUser.Count > 0))
|
|||
|
foreach (KeyValuePair<string, List<UserTrackPoint>> kvp in _dynamicUser)
|
|||
|
_user.Add(kvp.Key, kvp.Value);
|
|||
|
|
|||
|
if ((null != _dynamicUserIgnore) && (_dynamicUserIgnore.Count > 0))
|
|||
|
_userIgnore.AddRange(_dynamicUserIgnore);
|
|||
|
//
|
|||
|
// All done, clean up
|
|||
|
_dynamicActivities = null;
|
|||
|
_dynamicActivitiesIgnore = null;
|
|||
|
_dynamicUser = null;
|
|||
|
_dynamicUserIgnore = null;
|
|||
|
_pendingChanges = null;
|
|||
|
_pendingWorkflowChange = false;
|
|||
|
}
|
|||
|
|
|||
|
public void WorkflowChangeRollback()
|
|||
|
{
|
|||
|
//
|
|||
|
// Just clean up, there isn't any work to rollback because
|
|||
|
// any subscriptions that may have been added for a pending add activity
|
|||
|
// won't ever be hit as the activities haven't been added to the tree.
|
|||
|
_dynamicActivities = null;
|
|||
|
_dynamicActivitiesIgnore = null;
|
|||
|
_dynamicUser = null;
|
|||
|
_dynamicUserIgnore = null;
|
|||
|
_pendingChanges = null;
|
|||
|
_pendingWorkflowChange = false;
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Private Cache Methods
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Create the static qualifiedid to trackpoint map
|
|||
|
/// </summary>
|
|||
|
/// <param name="root"></param>
|
|||
|
private void CheckAllActivities(Activity activity)
|
|||
|
{
|
|||
|
CheckActivity((Activity)activity);
|
|||
|
//
|
|||
|
// Walk down the activity tree
|
|||
|
// Use EnabledActivities to get invisible activities
|
|||
|
// EnabledActivities will not return commented activities
|
|||
|
if (activity is CompositeActivity)
|
|||
|
foreach (Activity a in GetAllEnabledActivities((CompositeActivity)activity))
|
|||
|
CheckAllActivities(a);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Recursively walk the activity tree and find all track points that match each activity
|
|||
|
/// </summary>
|
|||
|
/// <param name="activity"></param>
|
|||
|
private void CheckActivity(Activity activity)
|
|||
|
{
|
|||
|
//
|
|||
|
// Build caches of activity status change events
|
|||
|
string qId = activity.QualifiedName;
|
|||
|
List<ActivityTrackPointCacheItem> activities = null;
|
|||
|
if (CreateCacheItems(activity, out activities))
|
|||
|
CacheInsert(qId, activities);
|
|||
|
else
|
|||
|
_activitiesIgnore.Add(qId);
|
|||
|
|
|||
|
//
|
|||
|
// Build caches of user events
|
|||
|
List<UserTrackPoint> user = null;
|
|||
|
if (CreateCacheItems(activity, out user))
|
|||
|
CacheInsert(qId, user);
|
|||
|
else
|
|||
|
_userIgnore.Add(qId);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Find all trackpoints that match an activity.
|
|||
|
/// </summary>
|
|||
|
/// <param name="activity">Activity for which to determine subscription needs</param>
|
|||
|
/// <param name="includes">List to be populated with matching track points</param>
|
|||
|
/// <returns>true if a subscription is needed; false if not</returns>
|
|||
|
private bool CreateCacheItems(Activity activity, out List<ActivityTrackPointCacheItem> includes)
|
|||
|
{
|
|||
|
includes = new List<ActivityTrackPointCacheItem>();
|
|||
|
//
|
|||
|
// Check if we have any trackpoints that match this activity
|
|||
|
foreach (ActivityTrackPoint point in _profile.ActivityTrackPoints)
|
|||
|
{
|
|||
|
List<ActivityExecutionStatus> events;
|
|||
|
bool hasCondition = false;
|
|||
|
if (point.IsMatch(activity, out events, out hasCondition))
|
|||
|
includes.Add(new ActivityTrackPointCacheItem(point, events, hasCondition));
|
|||
|
}
|
|||
|
|
|||
|
return (includes.Count > 0);
|
|||
|
}
|
|||
|
|
|||
|
/// <summary>
|
|||
|
/// Find all trackpoints that match user events for an activity.
|
|||
|
/// </summary>
|
|||
|
/// <param name="activity">Activity for which to determine subscription needs</param>
|
|||
|
/// <param name="includes">List to be populated with matching track points</param>
|
|||
|
/// <returns>true if a subscription is needed; false if not</returns>
|
|||
|
private bool CreateCacheItems(Activity activity, out List<UserTrackPoint> includes)
|
|||
|
{
|
|||
|
includes = new List<UserTrackPoint>();
|
|||
|
//
|
|||
|
// Check if we have any trackpoints that match this activity
|
|||
|
foreach (UserTrackPoint point in _profile.UserTrackPoints)
|
|||
|
{
|
|||
|
if (point.IsMatch(activity))
|
|||
|
includes.Add(point);
|
|||
|
}
|
|||
|
|
|||
|
return (includes.Count > 0);
|
|||
|
}
|
|||
|
|
|||
|
private void CacheInsert(string qualifiedID, List<ActivityTrackPointCacheItem> points)
|
|||
|
{
|
|||
|
//
|
|||
|
// Check to make sure the item isn't in the dictionary
|
|||
|
// If not add all track points
|
|||
|
Debug.Assert(!_activities.ContainsKey(qualifiedID), "QualifiedName is already in the activities cache");
|
|||
|
if (_activities.ContainsKey(qualifiedID))
|
|||
|
throw new InvalidOperationException(ExecutionStringManager.RTProfileActCacheDupKey);
|
|||
|
|
|||
|
foreach (ActivityTrackPointCacheItem point in points)
|
|||
|
CacheInsert(qualifiedID, point);
|
|||
|
}
|
|||
|
|
|||
|
private void CacheInsert(string qualifiedID, List<UserTrackPoint> points)
|
|||
|
{
|
|||
|
//
|
|||
|
// Check to make sure the item isn't in the dictionary
|
|||
|
// If not add all track points
|
|||
|
Debug.Assert(!_user.ContainsKey(qualifiedID), "QualifiedName is already in the user cache");
|
|||
|
if (_user.ContainsKey(qualifiedID))
|
|||
|
throw new InvalidOperationException(ExecutionStringManager.RTProfileActCacheDupKey);
|
|||
|
|
|||
|
foreach (UserTrackPoint point in points)
|
|||
|
CacheInsert(qualifiedID, point);
|
|||
|
}
|
|||
|
|
|||
|
private void CacheInsert(string qualifiedID, ActivityTrackPointCacheItem point)
|
|||
|
{
|
|||
|
List<ActivityTrackPointCacheItem> points = null;
|
|||
|
|
|||
|
if (!_activities.TryGetValue(qualifiedID, out points))
|
|||
|
{
|
|||
|
points = new List<ActivityTrackPointCacheItem>();
|
|||
|
_activities.Add(qualifiedID, points);
|
|||
|
}
|
|||
|
points.Add(point);
|
|||
|
}
|
|||
|
|
|||
|
private void CacheInsert(string qualifiedID, UserTrackPoint point)
|
|||
|
{
|
|||
|
List<UserTrackPoint> points = null;
|
|||
|
|
|||
|
if (!_user.TryGetValue(qualifiedID, out points))
|
|||
|
{
|
|||
|
points = new List<UserTrackPoint>();
|
|||
|
_user.Add(qualifiedID, points);
|
|||
|
}
|
|||
|
points.Add(point);
|
|||
|
}
|
|||
|
|
|||
|
private void CacheInsertUpdatePending(string qualifiedID, List<ActivityTrackPointCacheItem> points)
|
|||
|
{
|
|||
|
//
|
|||
|
// The activity has been added during a pending dynamic change
|
|||
|
// add it to a temporary lookup which will be copied to real cache
|
|||
|
// when the dynamic update commits.
|
|||
|
if ((!_isPrivate) || (!_pendingWorkflowChange))
|
|||
|
throw new InvalidOperationException(ExecutionStringManager.ProfileIsNotPrivate);
|
|||
|
|
|||
|
if (null == _dynamicActivities)
|
|||
|
throw new InvalidOperationException(ExecutionStringManager.RTProfileDynamicActCacheIsNull);
|
|||
|
|
|||
|
List<ActivityTrackPointCacheItem> tmp = null;
|
|||
|
if (!_dynamicActivities.TryGetValue(qualifiedID, out tmp))
|
|||
|
{
|
|||
|
tmp = new List<ActivityTrackPointCacheItem>();
|
|||
|
_dynamicActivities.Add(qualifiedID, tmp);
|
|||
|
}
|
|||
|
|
|||
|
foreach (ActivityTrackPointCacheItem point in points)
|
|||
|
tmp.Add(point);
|
|||
|
}
|
|||
|
|
|||
|
private bool TryGetCacheItems(Activity activity, out List<ActivityTrackPointCacheItem> points)
|
|||
|
{
|
|||
|
points = null;
|
|||
|
if ((!_pendingWorkflowChange) || ((_pendingWorkflowChange) && (!IsPendingUpdateActivity(activity, true))))
|
|||
|
{
|
|||
|
//
|
|||
|
// A dynamic update is not in progress or this activity
|
|||
|
// is not being added by the current dynamic update.
|
|||
|
// The main cache holds all matching track points
|
|||
|
return _activities.TryGetValue(activity.QualifiedName, out points);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
//
|
|||
|
// Dynamic update is in progress
|
|||
|
return _dynamicActivities.TryGetValue(activity.QualifiedName, out points);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void CacheInsertUpdatePending(string qualifiedID, List<UserTrackPoint> points)
|
|||
|
{
|
|||
|
//
|
|||
|
// The activity has been added during a pending dynamic change
|
|||
|
// add it to a temporary lookup which will be copied to real cache
|
|||
|
// when the dynamic update commits.
|
|||
|
if ((!_isPrivate) || (!_pendingWorkflowChange))
|
|||
|
throw new InvalidOperationException(ExecutionStringManager.ProfileIsNotPrivate);
|
|||
|
|
|||
|
if (null == _dynamicUser)
|
|||
|
throw new InvalidOperationException(ExecutionStringManager.RTProfileDynamicActCacheIsNull);
|
|||
|
|
|||
|
List<UserTrackPoint> tmp = null;
|
|||
|
if (!_dynamicUser.TryGetValue(qualifiedID, out tmp))
|
|||
|
{
|
|||
|
tmp = new List<UserTrackPoint>();
|
|||
|
_dynamicUser.Add(qualifiedID, tmp);
|
|||
|
}
|
|||
|
|
|||
|
foreach (UserTrackPoint point in points)
|
|||
|
tmp.Add(point);
|
|||
|
}
|
|||
|
|
|||
|
private bool TryGetCacheItems(Activity activity, out List<UserTrackPoint> points)
|
|||
|
{
|
|||
|
points = null;
|
|||
|
if ((!_pendingWorkflowChange) || ((_pendingWorkflowChange) && (!IsPendingUpdateActivity(activity, true))))
|
|||
|
{
|
|||
|
//
|
|||
|
// A dynamic update is not in progress or this activity
|
|||
|
// is not being added by the current dynamic update.
|
|||
|
// The main cache holds all matching track points
|
|||
|
return _user.TryGetValue(activity.QualifiedName, out points);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
//
|
|||
|
// Dynamic update is in progress
|
|||
|
return _dynamicUser.TryGetValue(activity.QualifiedName, out points);
|
|||
|
}
|
|||
|
}
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Private Methods
|
|||
|
|
|||
|
|
|||
|
// This function returns all the executable activities including secondary flow activities.
|
|||
|
public IList<Activity> GetAllEnabledActivities(CompositeActivity compositeActivity)
|
|||
|
{
|
|||
|
if (compositeActivity == null)
|
|||
|
throw new ArgumentNullException("compositeActivity");
|
|||
|
|
|||
|
List<Activity> allActivities = new List<Activity>(compositeActivity.EnabledActivities);
|
|||
|
|
|||
|
foreach (Activity secondaryFlowActivity in ((ISupportAlternateFlow)compositeActivity).AlternateFlowActivities)
|
|||
|
{
|
|||
|
if (!allActivities.Contains(secondaryFlowActivity))
|
|||
|
allActivities.Add(secondaryFlowActivity);
|
|||
|
}
|
|||
|
|
|||
|
return allActivities;
|
|||
|
}
|
|||
|
|
|||
|
private bool IsPendingUpdateActivity(Activity activity, bool addedOnly)
|
|||
|
{
|
|||
|
//
|
|||
|
// If we don't have an update going on this method isn't valid
|
|||
|
if ((!_isPrivate) || (!_pendingWorkflowChange))
|
|||
|
throw new InvalidOperationException(ExecutionStringManager.ProfileIsNotPrivate);
|
|||
|
//
|
|||
|
// if we don't have any changes we're done
|
|||
|
if ((null == _pendingChanges || _pendingChanges.Count <= 0))
|
|||
|
return false;
|
|||
|
|
|||
|
foreach (WorkflowChangeAction action in _pendingChanges)
|
|||
|
{
|
|||
|
string qualifiedId = null;
|
|||
|
if (action is ActivityChangeAction)
|
|||
|
{
|
|||
|
if (action is AddedActivityAction)
|
|||
|
{
|
|||
|
qualifiedId = ((AddedActivityAction)action).AddedActivity.QualifiedName;
|
|||
|
}
|
|||
|
else if (action is RemovedActivityAction)
|
|||
|
{
|
|||
|
if (!addedOnly)
|
|||
|
qualifiedId = ((RemovedActivityAction)action).OriginalRemovedActivity.QualifiedName;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
Debug.Assert(false, ExecutionStringManager.UnknownActivityActionType);
|
|||
|
}
|
|||
|
|
|||
|
if ((null != qualifiedId)
|
|||
|
&& (0 == String.Compare(activity.QualifiedName, qualifiedId, StringComparison.Ordinal)))
|
|||
|
{
|
|||
|
return true;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
return false;
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region ICloneable Members
|
|||
|
|
|||
|
object ICloneable.Clone()
|
|||
|
{
|
|||
|
return this.Clone();
|
|||
|
}
|
|||
|
|
|||
|
internal RTTrackingProfile Clone()
|
|||
|
{
|
|||
|
return new RTTrackingProfile(this);
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
|
|||
|
#region Contained Types
|
|||
|
|
|||
|
private struct ActivityTrackPointCacheItem
|
|||
|
{
|
|||
|
internal ActivityTrackPointCacheItem(ActivityTrackPoint point, List<ActivityExecutionStatus> events, bool hasConditions)
|
|||
|
{
|
|||
|
if (null == point)
|
|||
|
throw new ArgumentNullException("point");
|
|||
|
if (null == events)
|
|||
|
throw new ArgumentNullException("events");
|
|||
|
|
|||
|
Point = point;
|
|||
|
Events = events;
|
|||
|
HasLocationConditions = hasConditions;
|
|||
|
}
|
|||
|
|
|||
|
internal ActivityTrackPoint Point;
|
|||
|
internal List<ActivityExecutionStatus> Events;
|
|||
|
internal bool HasLocationConditions;
|
|||
|
}
|
|||
|
|
|||
|
#endregion
|
|||
|
}
|
|||
|
}
|