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
{
///
/// RTTrackingProfile contains functionality specific to the runtime such as
/// trackpoint and location matching and caching, cloning, handling dynamic updates...
///
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> _activities = new Dictionary>();
private List _activitiesIgnore = new List();
private Dictionary> _user = new Dictionary>();
private List _userIgnore = new List();
//
// 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 _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> _dynamicActivities = null;
private List _dynamicActivitiesIgnore = null;
private Dictionary> _dynamicUser = null;
private List _dynamicUserIgnore = null;
#endregion
#region Constructors
///
/// Default constructor
///
protected RTTrackingProfile()
{
}
///
/// Primary constructor
///
///
///
///
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);
}
///
/// Constructor used for cloning.
///
/// RTTrackingProfile to clone
/// All members are shallow copied! Use MakePrivate to deep copy after cloning.
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>(runtimeProfile._activities.Count);
foreach (KeyValuePair> kvp in runtimeProfile._activities)
_activities.Add(kvp.Key, runtimeProfile._activities[kvp.Key]);
_activitiesIgnore = new List(runtimeProfile._activitiesIgnore);
//
// Pending dynamic update activity match and ignore cache
if (null != runtimeProfile._dynamicActivities)
{
_dynamicActivities = new Dictionary>(runtimeProfile._dynamicActivities.Count);
foreach (KeyValuePair> kvp in runtimeProfile._dynamicActivities)
_dynamicActivities.Add(kvp.Key, runtimeProfile._dynamicActivities[kvp.Key]);
}
if (null != runtimeProfile._dynamicActivitiesIgnore)
_dynamicActivitiesIgnore = new List(runtimeProfile._dynamicActivitiesIgnore);
//
// User event match and ignore cache
_user = new Dictionary>(runtimeProfile._user.Count);
foreach (KeyValuePair> kvp in runtimeProfile._user)
_user.Add(kvp.Key, runtimeProfile._user[kvp.Key]);
_userIgnore = new List(runtimeProfile._userIgnore);
//
// Pending dynamic update activity match and ignore cache
if (null != runtimeProfile._dynamicUser)
{
_dynamicUser = new Dictionary>(runtimeProfile._dynamicUser.Count);
foreach (KeyValuePair> kvp in runtimeProfile._dynamicUser)
_dynamicUser.Add(kvp.Key, kvp.Value);
}
if (null != runtimeProfile._dynamicUserIgnore)
_dynamicUserIgnore = new List(runtimeProfile._dynamicUserIgnore);
}
#endregion
#region Properties
///
/// Indicates if the profile is specific to an individual instance.
///
internal bool IsPrivate
{
get { return _isPrivate; }
set
{
if (!(value) && (_isPrivate))
throw new InvalidOperationException(ExecutionStringManager.CannotResetIsPrivate);
_isPrivate = value;
}
}
///
/// Type of workflow to which this profile is associated
///
internal Type WorkflowType
{
get { return _workflowType; }
}
///
/// Version of the profile
///
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 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 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;
}
///
/// 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.
///
///
///
///
internal bool ActivitySubscriptionNeeded(Activity activity)
{
List 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 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 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>();
_dynamicActivitiesIgnore = new List();
_dynamicUser = new Dictionary>();
_dynamicUserIgnore = new List();
_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> 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> 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
///
/// Create the static qualifiedid to trackpoint map
///
///
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);
}
///
/// Recursively walk the activity tree and find all track points that match each activity
///
///
private void CheckActivity(Activity activity)
{
//
// Build caches of activity status change events
string qId = activity.QualifiedName;
List activities = null;
if (CreateCacheItems(activity, out activities))
CacheInsert(qId, activities);
else
_activitiesIgnore.Add(qId);
//
// Build caches of user events
List user = null;
if (CreateCacheItems(activity, out user))
CacheInsert(qId, user);
else
_userIgnore.Add(qId);
}
///
/// Find all trackpoints that match an activity.
///
/// Activity for which to determine subscription needs
/// List to be populated with matching track points
/// true if a subscription is needed; false if not
private bool CreateCacheItems(Activity activity, out List includes)
{
includes = new List();
//
// Check if we have any trackpoints that match this activity
foreach (ActivityTrackPoint point in _profile.ActivityTrackPoints)
{
List events;
bool hasCondition = false;
if (point.IsMatch(activity, out events, out hasCondition))
includes.Add(new ActivityTrackPointCacheItem(point, events, hasCondition));
}
return (includes.Count > 0);
}
///
/// Find all trackpoints that match user events for an activity.
///
/// Activity for which to determine subscription needs
/// List to be populated with matching track points
/// true if a subscription is needed; false if not
private bool CreateCacheItems(Activity activity, out List includes)
{
includes = new List();
//
// 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 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 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 points = null;
if (!_activities.TryGetValue(qualifiedID, out points))
{
points = new List();
_activities.Add(qualifiedID, points);
}
points.Add(point);
}
private void CacheInsert(string qualifiedID, UserTrackPoint point)
{
List points = null;
if (!_user.TryGetValue(qualifiedID, out points))
{
points = new List();
_user.Add(qualifiedID, points);
}
points.Add(point);
}
private void CacheInsertUpdatePending(string qualifiedID, List 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 tmp = null;
if (!_dynamicActivities.TryGetValue(qualifiedID, out tmp))
{
tmp = new List();
_dynamicActivities.Add(qualifiedID, tmp);
}
foreach (ActivityTrackPointCacheItem point in points)
tmp.Add(point);
}
private bool TryGetCacheItems(Activity activity, out List 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 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 tmp = null;
if (!_dynamicUser.TryGetValue(qualifiedID, out tmp))
{
tmp = new List();
_dynamicUser.Add(qualifiedID, tmp);
}
foreach (UserTrackPoint point in points)
tmp.Add(point);
}
private bool TryGetCacheItems(Activity activity, out List 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 GetAllEnabledActivities(CompositeActivity compositeActivity)
{
if (compositeActivity == null)
throw new ArgumentNullException("compositeActivity");
List allActivities = new List(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 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 Events;
internal bool HasLocationConditions;
}
#endregion
}
}