e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
835 lines
33 KiB
C#
835 lines
33 KiB
C#
namespace System.Workflow.Activities
|
|
{
|
|
#region Imports
|
|
|
|
using System;
|
|
using System.Diagnostics;
|
|
using System.Xml;
|
|
using System.CodeDom;
|
|
using System.Drawing;
|
|
using System.Collections;
|
|
using System.Windows.Forms;
|
|
using System.ComponentModel;
|
|
using System.Drawing.Drawing2D;
|
|
using System.Collections.Generic;
|
|
using System.Windows.Forms.Design;
|
|
using System.ComponentModel.Design;
|
|
using System.Workflow.ComponentModel;
|
|
using System.Workflow.ComponentModel.Design;
|
|
using System.ComponentModel.Design.Serialization;
|
|
using System.Workflow.ComponentModel.Compiler;
|
|
using System.Workflow.Runtime.DebugEngine;
|
|
using System.Workflow.Activities.Common;
|
|
|
|
#endregion
|
|
|
|
[SRDescription(SR.ConstrainedGroupActivityDescription)]
|
|
[ToolboxItem(typeof(ActivityToolboxItem))]
|
|
[Designer(typeof(ConditionedActivityGroupDesigner), typeof(IDesigner))]
|
|
[ToolboxBitmap(typeof(ConditionedActivityGroup), "Resources.cag.png")]
|
|
[ActivityValidator(typeof(ConditionedActivityGroupValidator))]
|
|
[SRCategory(SR.Standard)]
|
|
[WorkflowDebuggerSteppingAttribute(WorkflowDebuggerSteppingOption.Concurrent)]
|
|
[Obsolete("The System.Workflow.* types are deprecated. Instead, please use the new types from System.Activities.*")]
|
|
public sealed class ConditionedActivityGroup : CompositeActivity, IActivityEventListener<ActivityExecutionStatusChangedEventArgs>
|
|
{
|
|
//Attached properties provided to the children
|
|
public static readonly DependencyProperty WhenConditionProperty = DependencyProperty.RegisterAttached("WhenCondition", typeof(ActivityCondition), typeof(ConditionedActivityGroup), new PropertyMetadata(DependencyPropertyOptions.Metadata), typeof(WhenUnlessConditionDynamicPropertyValidator));
|
|
|
|
// metadata properties go here
|
|
public static readonly DependencyProperty UntilConditionProperty = DependencyProperty.Register("UntilCondition", typeof(ActivityCondition), typeof(ConditionedActivityGroup), new PropertyMetadata(DependencyPropertyOptions.Metadata));
|
|
|
|
#region Constructors
|
|
|
|
public ConditionedActivityGroup()
|
|
{
|
|
}
|
|
|
|
public ConditionedActivityGroup(string name)
|
|
: base(name)
|
|
{
|
|
}
|
|
|
|
#endregion
|
|
|
|
// WhenConditionProperty Get and Set Accessors
|
|
public static object GetWhenCondition(object dependencyObject)
|
|
{
|
|
if (dependencyObject == null)
|
|
throw new ArgumentNullException("dependencyObject");
|
|
if (!(dependencyObject is DependencyObject))
|
|
throw new ArgumentException(SR.GetString(SR.Error_UnexpectedArgumentType, typeof(DependencyObject).FullName), "dependencyObject");
|
|
|
|
return (dependencyObject as DependencyObject).GetValue(WhenConditionProperty);
|
|
}
|
|
|
|
public static void SetWhenCondition(object dependencyObject, object value)
|
|
{
|
|
if (dependencyObject == null)
|
|
throw new ArgumentNullException("dependencyObject");
|
|
if (!(dependencyObject is DependencyObject))
|
|
throw new ArgumentException(SR.GetString(SR.Error_UnexpectedArgumentType, typeof(DependencyObject).FullName), "dependencyObject");
|
|
|
|
(dependencyObject as DependencyObject).SetValue(WhenConditionProperty, value);
|
|
}
|
|
|
|
[SRCategory(SR.Conditions)]
|
|
[SRDescription(SR.UntilConditionDescr)]
|
|
[DefaultValue(null)]
|
|
public ActivityCondition UntilCondition
|
|
{
|
|
get
|
|
{
|
|
return base.GetValue(UntilConditionProperty) as ActivityCondition;
|
|
}
|
|
set
|
|
{
|
|
base.SetValue(UntilConditionProperty, value);
|
|
}
|
|
}
|
|
|
|
[Browsable(false)]
|
|
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
|
|
private Activity GetDynamicActivity(Activity childActivity)
|
|
{
|
|
if (childActivity == null)
|
|
throw new ArgumentNullException("childActivity");
|
|
|
|
if (!this.EnabledActivities.Contains(childActivity))
|
|
throw new ArgumentException(SR.GetString(SR.Error_CAGChildNotFound, childActivity.QualifiedName, this.QualifiedName), "childActivity");
|
|
else
|
|
{
|
|
Activity[] dynamicChildActivity = this.GetDynamicActivities(childActivity);
|
|
|
|
if (dynamicChildActivity.Length != 0)
|
|
return dynamicChildActivity[0];
|
|
else
|
|
return null;
|
|
}
|
|
}
|
|
|
|
public Activity GetDynamicActivity(String childActivityName)
|
|
{
|
|
if (childActivityName == null)
|
|
throw new ArgumentNullException("childActivityName");
|
|
|
|
Activity childActivity = null;
|
|
|
|
for (int i = 0; i < this.EnabledActivities.Count; ++i)
|
|
{
|
|
if (this.EnabledActivities[i].QualifiedName.Equals(childActivityName))
|
|
{
|
|
childActivity = this.EnabledActivities[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (childActivity != null)
|
|
return GetDynamicActivity(childActivity);
|
|
|
|
throw new ArgumentException(SR.GetString(SR.Error_CAGChildNotFound, childActivityName, this.QualifiedName), "childActivityName");
|
|
}
|
|
|
|
public int GetChildActivityExecutedCount(Activity child)
|
|
{
|
|
if (child == null)
|
|
throw new ArgumentNullException("child");
|
|
|
|
ConditionedActivityGroupStateInfo conditionedInfo = this.CAGState;
|
|
|
|
if (conditionedInfo == null)
|
|
{
|
|
throw new InvalidOperationException(SR.GetString(SR.Error_CAGNotExecuting, this.QualifiedName));
|
|
}
|
|
|
|
if (!conditionedInfo.ChildrenStats.ContainsKey(child.QualifiedName))
|
|
{
|
|
throw new ArgumentException(SR.GetString(SR.Error_CAGChildNotFound, child.QualifiedName, this.QualifiedName), "child");
|
|
}
|
|
else
|
|
{
|
|
return conditionedInfo.ChildrenStats[child.QualifiedName].ExecutedCount;
|
|
}
|
|
}
|
|
|
|
private sealed class WhenUnlessConditionDynamicPropertyValidator : Validator
|
|
{
|
|
public override ValidationErrorCollection Validate(ValidationManager manager, object obj)
|
|
{
|
|
ValidationErrorCollection validationErrors = ValidationHelpers.ValidateObject(manager, obj);
|
|
|
|
if (validationErrors.Count == 0)
|
|
{
|
|
Activity activity = manager.Context[typeof(Activity)] as Activity;
|
|
if (activity == null)
|
|
throw new InvalidOperationException(SR.GetString(SR.Error_ContextStackItemMissing, typeof(Activity).Name));
|
|
|
|
CodeCondition codeCondition = obj as CodeCondition;
|
|
if (codeCondition != null && codeCondition.IsBindingSet(CodeCondition.ConditionEvent))
|
|
{
|
|
ActivityBind activityBind = codeCondition.GetBinding(CodeCondition.ConditionEvent) as ActivityBind;
|
|
if (activityBind != null)
|
|
{
|
|
Activity contextActivity = Helpers.ParseActivityForBind(activity, activityBind.Name);
|
|
if (contextActivity != null && Helpers.IsChildActivity(activity.Parent, contextActivity))
|
|
{
|
|
string propertyName = GetFullPropertyName(manager);
|
|
ValidationError error = new ValidationError(SR.GetString(SR.Error_NestedConstrainedGroupConditions, propertyName), ErrorNumbers.Error_NestedConstrainedGroupConditions);
|
|
error.PropertyName = propertyName;
|
|
validationErrors.Add(error);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return validationErrors;
|
|
}
|
|
}
|
|
|
|
#region Runtime Internal Dependency Property
|
|
static DependencyProperty CAGStateProperty = DependencyProperty.Register("CAGState", typeof(ConditionedActivityGroupStateInfo), typeof(ConditionedActivityGroup));
|
|
|
|
internal ConditionedActivityGroupStateInfo CAGState
|
|
{
|
|
get
|
|
{
|
|
return (ConditionedActivityGroupStateInfo)base.GetValue(CAGStateProperty);
|
|
}
|
|
set
|
|
{
|
|
base.SetValue(CAGStateProperty, value);
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
protected override void OnClosed(IServiceProvider provider)
|
|
{
|
|
base.RemoveProperty(ConditionedActivityGroup.CAGStateProperty);
|
|
}
|
|
|
|
#region Workflow Changes Overrides
|
|
protected override void OnActivityChangeAdd(ActivityExecutionContext executionContext, Activity addedActivity)
|
|
{
|
|
if (executionContext == null)
|
|
throw new ArgumentNullException("executionContext");
|
|
if (addedActivity == null)
|
|
throw new ArgumentNullException("addedActivity");
|
|
|
|
if (!addedActivity.Enabled)
|
|
return;
|
|
|
|
ConditionedActivityGroup currentActivity = executionContext.Activity as ConditionedActivityGroup;
|
|
Debug.Assert(currentActivity != null);
|
|
|
|
ConditionedActivityGroupStateInfo state = currentActivity.CAGState;
|
|
if (currentActivity.ExecutionStatus == ActivityExecutionStatus.Executing && state != null)
|
|
{
|
|
Debug.Assert(currentActivity == addedActivity.Parent, "Attempting to add wrong activity to CAG");
|
|
state.ChildrenStats[addedActivity.QualifiedName] = new CAGChildStats();
|
|
}
|
|
}
|
|
|
|
protected override void OnActivityChangeRemove(ActivityExecutionContext executionContext, Activity removedActivity)
|
|
{
|
|
if (executionContext == null)
|
|
throw new ArgumentNullException("executionContext");
|
|
if (removedActivity == null)
|
|
throw new ArgumentNullException("removedActivity");
|
|
|
|
if (!removedActivity.Enabled)
|
|
return;
|
|
|
|
ConditionedActivityGroup cag = executionContext.Activity as ConditionedActivityGroup;
|
|
Debug.Assert(cag != null);
|
|
|
|
// find out the status of the cag
|
|
|
|
ConditionedActivityGroupStateInfo state = cag.CAGState;
|
|
if ((cag.ExecutionStatus == ActivityExecutionStatus.Executing) && (state != null))
|
|
{
|
|
state.ChildrenStats.Remove(removedActivity.QualifiedName);
|
|
}
|
|
}
|
|
|
|
protected override void OnWorkflowChangesCompleted(ActivityExecutionContext executionContext)
|
|
{
|
|
if (executionContext == null)
|
|
throw new ArgumentNullException("executionContext");
|
|
|
|
// find out the status of the cag
|
|
ConditionedActivityGroup currentActivity = executionContext.Activity as ConditionedActivityGroup;
|
|
|
|
// if CAG is executing... fire the conditions on the net result
|
|
if (currentActivity.ExecutionStatus == ActivityExecutionStatus.Executing)
|
|
{
|
|
// but hold on, a derived cag could be applying model changes before it
|
|
// "really" starts executing the activities. In that case it will evaluate
|
|
// the conditions later, at the appropriate time.
|
|
ConditionedActivityGroupStateInfo state = currentActivity.CAGState;
|
|
if ((state != null) && (!state.Testing))
|
|
{
|
|
// fire away... fire away... said the CAG
|
|
if (this.EvaluateConditions(currentActivity, executionContext))
|
|
{
|
|
// CAG until indicates we are done, so no children execute
|
|
this.Cleanup(currentActivity, executionContext);
|
|
}
|
|
else
|
|
{
|
|
// start any pending activity required
|
|
this.TriggerChildren(currentActivity, executionContext);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Execution Implementation
|
|
#if LOG
|
|
private static void Log(string message)
|
|
{
|
|
Trace.WriteLine(message);
|
|
}
|
|
#endif
|
|
|
|
protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext)
|
|
{
|
|
if (executionContext == null)
|
|
throw new ArgumentNullException("executionContext");
|
|
#if LOG
|
|
Log("Execute on " + this.QualifiedName);
|
|
#endif
|
|
|
|
// go figure out what the CAG needs to do
|
|
this.CAGState = new ConditionedActivityGroupStateInfo(this);
|
|
|
|
if (EvaluateConditions(this, executionContext))
|
|
{
|
|
// CAG until indicates we are done, so no children execute
|
|
return ActivityExecutionStatus.Closed;
|
|
}
|
|
|
|
// start any pending activity required
|
|
TriggerChildren(this, executionContext);
|
|
return this.ExecutionStatus;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Evaluate the conditions on the CAG
|
|
/// </summary>
|
|
/// <param name="cag"></param>
|
|
/// <param name="context"></param>
|
|
/// <returns>True if CAG is complete (UNTIL == true, or no UNTIL and no children execute), false otherwise</returns>
|
|
internal bool EvaluateConditions(ConditionedActivityGroup cag, ActivityExecutionContext context)
|
|
{
|
|
Debug.Assert(cag != null);
|
|
Debug.Assert(context != null);
|
|
#if LOG
|
|
Log("EvaluateConditions on " + cag.QualifiedName);
|
|
cag.CAGState.DumpState("Before EvaluateConditions");
|
|
#endif
|
|
// if we've already decided to quit this CAG, don't do anything
|
|
if (cag.CAGState.Completed)
|
|
return false;
|
|
|
|
// if the cag has an UNTIL condition, execute it
|
|
if ((cag.UntilCondition != null) && cag.UntilCondition.Evaluate(cag, context))
|
|
{
|
|
// UNTIL condition says we're done, no need to look at children
|
|
#if LOG
|
|
Log("Until condition is true");
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
// until condition is false, so let's look at all children
|
|
int childExecuting = 0; // keep track of children executing
|
|
Dictionary<string, CAGChildStats> childrenStats = cag.CAGState.ChildrenStats;
|
|
foreach (Activity act in cag.EnabledActivities)
|
|
{
|
|
// if we think the child is executing, do nothing
|
|
if (childrenStats[act.QualifiedName].State == CAGChildState.Excuting)
|
|
{
|
|
++childExecuting;
|
|
continue;
|
|
}
|
|
|
|
// find the run-time activity
|
|
Activity activity = GetRuntimeInitializedActivity(context, act);
|
|
// should it execute?
|
|
if (EvaluateChildConditions(cag, activity, context))
|
|
{
|
|
++childExecuting;
|
|
childrenStats[act.QualifiedName].State = CAGChildState.Pending;
|
|
}
|
|
}
|
|
#if LOG
|
|
cag.CAGState.DumpState("After EvaluateConditions");
|
|
#endif
|
|
|
|
// if any work to do, CAG not yet done
|
|
if (childExecuting > 0)
|
|
return false;
|
|
|
|
// CAG is quiet (nothing more to do)
|
|
// if specified an UNTIL condition but we have nothing to do
|
|
if (cag.UntilCondition != null)
|
|
{
|
|
#if LOG
|
|
Log("CAG quiet, but UNTIL condition is false, so error time");
|
|
#endif
|
|
throw new InvalidOperationException(SR.GetString(SR.Error_CAGQuiet, cag.QualifiedName));
|
|
}
|
|
#if LOG
|
|
Log("CAG quiet");
|
|
#endif
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Evaluate the While condition for a particular child of the CAG
|
|
/// If no While condition, it becomes "execute once"
|
|
/// </summary>
|
|
/// <param name="cag"></param>
|
|
/// <param name="child"></param>
|
|
/// <param name="context"></param>
|
|
/// <returns></returns>
|
|
private bool EvaluateChildConditions(ConditionedActivityGroup cag, Activity child, ActivityExecutionContext context)
|
|
{
|
|
#if LOG
|
|
Log("EvaluateChildConditions on activity " + child.QualifiedName + " inside " + cag.QualifiedName);
|
|
#endif
|
|
// determine the result of the when condition (evaluate once if not specified)
|
|
ConditionedActivityGroupStateInfo state = cag.CAGState;
|
|
try
|
|
{
|
|
state.Testing = true;
|
|
ActivityCondition whenCondition = (ActivityCondition)child.GetValue(ConditionedActivityGroup.WhenConditionProperty);
|
|
return (whenCondition != null)
|
|
? whenCondition.Evaluate(child, context)
|
|
: (state.ChildrenStats[child.QualifiedName].ExecutedCount == 0);
|
|
}
|
|
finally
|
|
{
|
|
state.Testing = false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Start any child activities that need to be run
|
|
/// </summary>
|
|
/// <param name="cag"></param>
|
|
/// <param name="context"></param>
|
|
internal void TriggerChildren(ConditionedActivityGroup cag, ActivityExecutionContext context)
|
|
{
|
|
Debug.Assert(cag != null);
|
|
Debug.Assert(context != null);
|
|
#if LOG
|
|
Log("TriggerChildren on " + cag.QualifiedName);
|
|
cag.CAGState.DumpState("Before TriggerChildren");
|
|
#endif
|
|
|
|
Dictionary<string, CAGChildStats> childrenStats = cag.CAGState.ChildrenStats;
|
|
// until condition is false, so let's look at all children
|
|
foreach (Activity act in cag.EnabledActivities)
|
|
{
|
|
// do we think this child needs to run?
|
|
if (childrenStats[act.QualifiedName].State != CAGChildState.Pending)
|
|
continue;
|
|
|
|
// find the run-time activity
|
|
Activity activity = GetRuntimeInitializedActivity(context, act);
|
|
if (activity.ExecutionStatus == ActivityExecutionStatus.Initialized)
|
|
ExecuteChild(cag, activity, context);
|
|
}
|
|
#if LOG
|
|
cag.CAGState.DumpState("After TriggerChildren");
|
|
#endif
|
|
}
|
|
|
|
private void ExecuteChild(ConditionedActivityGroup cag, Activity childActivity, ActivityExecutionContext context)
|
|
{
|
|
Debug.Assert(cag != null);
|
|
Debug.Assert(childActivity != null);
|
|
Debug.Assert(context != null);
|
|
Debug.Assert(childActivity.ExecutionStatus == ActivityExecutionStatus.Initialized);
|
|
#if LOG
|
|
Log("ExecuteChild " + childActivity.QualifiedName + " inside " + cag.QualifiedName);
|
|
#endif
|
|
ActivityExecutionContext childContext = GetChildExecutionContext(context, childActivity, true);
|
|
cag.CAGState.ChildrenStats[childActivity.QualifiedName].State = CAGChildState.Excuting;
|
|
|
|
// subscribe for child closure
|
|
childContext.Activity.RegisterForStatusChange(Activity.ClosedEvent, this);
|
|
|
|
// execute child in inner context
|
|
childContext.ExecuteActivity(childContext.Activity);
|
|
}
|
|
|
|
protected override ActivityExecutionStatus Cancel(ActivityExecutionContext executionContext)
|
|
{
|
|
if (executionContext == null)
|
|
throw new ArgumentNullException("executionContext");
|
|
|
|
// child activities are cancelled and could complete asynchronously.
|
|
// if there was no asynchronous stuff then lets do the cag level cleanup
|
|
|
|
// if we are already done (or never started), then we are already closed
|
|
if (this.CAGState == null)
|
|
return ActivityExecutionStatus.Closed;
|
|
|
|
return Cleanup(this, executionContext) ? ActivityExecutionStatus.Closed : ActivityExecutionStatus.Canceling;
|
|
}
|
|
|
|
#region IActivityEventListener<ActivityExecutionStatusChangedEventArgs> Members
|
|
void IActivityEventListener<ActivityExecutionStatusChangedEventArgs>.OnEvent(object sender, ActivityExecutionStatusChangedEventArgs e)
|
|
{
|
|
this.HandleEvent(sender as ActivityExecutionContext, new SubscriptionEventArg(e, EventType.StatusChange));
|
|
}
|
|
#endregion
|
|
|
|
internal void HandleEvent(ActivityExecutionContext context, SubscriptionEventArg e)
|
|
{
|
|
if (context == null)
|
|
throw new ArgumentNullException("context");
|
|
if (e == null)
|
|
throw new ArgumentNullException("e");
|
|
|
|
ConditionedActivityGroup cag = context.Activity as ConditionedActivityGroup;
|
|
if (cag == null)
|
|
throw new ArgumentException(SR.GetString(SR.Error_InvalidCAGActivityType), "activity");
|
|
|
|
// Already done the cleanup from another child's signalling
|
|
if (cag.ExecutionStatus == ActivityExecutionStatus.Closed)
|
|
return;
|
|
|
|
if (e.SubscriptionType != EventType.StatusChange)
|
|
{
|
|
// split into seperate test to keep FxCop happy (only place SubscriptionType used)
|
|
Debug.Assert(false, "This CAG activity handler does not handle this event");
|
|
}
|
|
ActivityExecutionStatusChangedEventArgs args1 = (ActivityExecutionStatusChangedEventArgs)e.Args;
|
|
#if LOG
|
|
Log("HandleEvent for " + cag.QualifiedName);
|
|
Log("event = " + e.ToString());
|
|
Log("activity = " + args1.Activity.QualifiedName);
|
|
#endif
|
|
|
|
bool timeToQuit = false;
|
|
|
|
// is this event is for an immediate child?
|
|
Debug.Assert(cag == args1.Activity.Parent, "Received event for non-child of CAG");
|
|
Dictionary<string, CAGChildStats> childrenStats = cag.CAGState.ChildrenStats;
|
|
|
|
// it is possible that dynamic update has removed the child before we get the closed event
|
|
// if that is the case, we don't need to update it's stats since it's not there
|
|
if (childrenStats.ContainsKey(args1.Activity.QualifiedName))
|
|
{
|
|
// update our state about the child
|
|
if (args1.ExecutionStatus != ActivityExecutionStatus.Executing)
|
|
childrenStats[args1.Activity.QualifiedName].State = CAGChildState.Idle;
|
|
|
|
// @undone: this will break if scopes move to "Delayed" closing after Completed.
|
|
if (args1.ExecutionStatus == ActivityExecutionStatus.Closed)
|
|
childrenStats[args1.Activity.QualifiedName].ExecutedCount++;
|
|
|
|
try
|
|
{
|
|
// re-evaluate the conditions on any status change, as long as the CAG is still executing
|
|
if (cag.ExecutionStatus == ActivityExecutionStatus.Executing)
|
|
timeToQuit = EvaluateConditions(cag, context);
|
|
}
|
|
finally
|
|
{
|
|
// get rid of the child that just completed
|
|
// do this in the finally so that the child is cleaned up,
|
|
// even if EvaluateConditions throws beause the CAG is stalled
|
|
CleanupChildAtClosure(context, args1.Activity);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// child has been removed
|
|
// we still need to see if the CAG is done, provided we are still executing
|
|
if (cag.ExecutionStatus == ActivityExecutionStatus.Executing)
|
|
timeToQuit = EvaluateConditions(cag, context);
|
|
}
|
|
|
|
// is the CAG just completed?
|
|
if (timeToQuit)
|
|
{
|
|
Cleanup(cag, context);
|
|
}
|
|
else if (cag.CAGState.Completed)
|
|
{
|
|
// if the CAG is simply waiting for all children to complete, see if this is the last one
|
|
if (AllChildrenQuiet(cag, context))
|
|
{
|
|
// Mark the CAG as closed, if it hasn't already been marked so.
|
|
context.CloseActivity();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// CAG not done, so see if any children need to start
|
|
TriggerChildren(cag, context);
|
|
}
|
|
}
|
|
|
|
internal bool Cleanup(ConditionedActivityGroup cag, ActivityExecutionContext context)
|
|
{
|
|
// the completion condition has fired, or we are canceling
|
|
// either way, we want to cleanup
|
|
ConditionedActivityGroupStateInfo state = cag.CAGState;
|
|
state.Completed = true;
|
|
|
|
// cancel any children currently running
|
|
bool childrenActive = false;
|
|
Dictionary<string, CAGChildStats> childrenStats = state.ChildrenStats;
|
|
foreach (Activity act in cag.EnabledActivities)
|
|
{
|
|
// reset any Pending Execution for all child activity
|
|
if (childrenStats[act.QualifiedName].State == CAGChildState.Pending)
|
|
childrenStats[act.QualifiedName].State = CAGChildState.Idle;
|
|
|
|
// find the run-time activity
|
|
ActivityExecutionContext childContext = GetChildExecutionContext(context, act, false);
|
|
if (childContext != null)
|
|
{
|
|
// child must be running somewhere
|
|
Activity activity = GetRuntimeInitializedActivity(context, act);
|
|
switch (activity.ExecutionStatus)
|
|
{
|
|
case ActivityExecutionStatus.Executing:
|
|
// schedule cancellation on child
|
|
childContext.CancelActivity(activity);
|
|
childrenActive = true;
|
|
break;
|
|
|
|
case ActivityExecutionStatus.Canceling:
|
|
case ActivityExecutionStatus.Faulting:
|
|
childrenActive = true;
|
|
break;
|
|
|
|
case ActivityExecutionStatus.Closed:
|
|
CleanupChildAtClosure(context, activity);
|
|
break;
|
|
default:
|
|
// unhook our handler
|
|
// others will be removed when we get the complete/cancel notification
|
|
act.UnregisterForStatusChange(Activity.ClosedEvent, this);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// if the CAG is quiet, we are all done
|
|
if (!childrenActive)
|
|
context.CloseActivity();
|
|
return !childrenActive;
|
|
}
|
|
|
|
private void CleanupChildAtClosure(ActivityExecutionContext context, Activity childActivity)
|
|
{
|
|
Debug.Assert(context != null);
|
|
Debug.Assert(childActivity != null);
|
|
Debug.Assert(childActivity.ExecutionStatus == ActivityExecutionStatus.Closed);
|
|
|
|
//UnSubsribe child closure of completed activity.
|
|
childActivity.UnregisterForStatusChange(Activity.ClosedEvent, this);
|
|
|
|
//Dispose the execution context;
|
|
ActivityExecutionContext childContext = GetChildExecutionContext(context, childActivity, false);
|
|
ActivityExecutionContextManager contextManager = context.ExecutionContextManager;
|
|
contextManager.CompleteExecutionContext(childContext);
|
|
}
|
|
|
|
private Activity GetRuntimeInitializedActivity(ActivityExecutionContext context, Activity childActivity)
|
|
{
|
|
ActivityExecutionContext childContext = GetChildExecutionContext(context, childActivity, false);
|
|
|
|
if (childContext == null)
|
|
return childActivity;
|
|
|
|
return childContext.Activity;
|
|
}
|
|
|
|
private static ActivityExecutionContext GetChildExecutionContext(ActivityExecutionContext context, Activity childActivity, bool createIfNotExists)
|
|
{
|
|
ActivityExecutionContextManager contextManager = context.ExecutionContextManager;
|
|
ActivityExecutionContext childContext = contextManager.GetExecutionContext(childActivity);
|
|
if (childContext != null)
|
|
return childContext;
|
|
|
|
if (createIfNotExists)
|
|
childContext = contextManager.CreateExecutionContext(childActivity);
|
|
|
|
return childContext;
|
|
}
|
|
|
|
bool AllChildrenQuiet(ConditionedActivityGroup cag, ActivityExecutionContext context)
|
|
{
|
|
if (context == null)
|
|
throw new ArgumentNullException("context");
|
|
|
|
// if there are any execution contexts, 1 or more children still doing something
|
|
foreach (ActivityExecutionContext activeContext in context.ExecutionContextManager.ExecutionContexts)
|
|
{
|
|
if (cag.GetActivityByName(activeContext.Activity.QualifiedName, true) != null)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// no children
|
|
return true;
|
|
}
|
|
}
|
|
|
|
internal sealed class SubscriptionEventArg : EventArgs
|
|
{
|
|
private EventArgs _args;
|
|
private EventType _subscriptionType;
|
|
|
|
internal EventArgs Args
|
|
{
|
|
get { return _args; }
|
|
}
|
|
|
|
internal EventType SubscriptionType
|
|
{
|
|
get { return _subscriptionType; }
|
|
}
|
|
|
|
public override string ToString()
|
|
{
|
|
return "SubscriptionEventArg(" + (_args == null ? "null" : _args.ToString()) + ")";
|
|
}
|
|
|
|
internal SubscriptionEventArg(EventArgs args, EventType subType)
|
|
{
|
|
_args = args;
|
|
_subscriptionType = subType;
|
|
}
|
|
}
|
|
|
|
[Serializable]
|
|
internal enum EventType : byte
|
|
{
|
|
Timer = 0,
|
|
DataChange = 1,
|
|
StatusChange = 2,
|
|
MessageArrival = 3,
|
|
LockAcquisition = 4,
|
|
InterActivity = 6,
|
|
}
|
|
|
|
#region ConditionedActivityGroupStateInfo
|
|
[Serializable]
|
|
internal sealed class ConditionedActivityGroupStateInfo
|
|
{
|
|
private bool completed;
|
|
private bool testing;
|
|
private Dictionary<string, CAGChildStats> childActivityStats;
|
|
|
|
#region Accessors
|
|
|
|
internal bool Completed
|
|
{
|
|
get { return this.completed; }
|
|
set { this.completed = value; }
|
|
}
|
|
|
|
internal bool Testing
|
|
{
|
|
get { return testing; }
|
|
set { testing = value; }
|
|
}
|
|
|
|
internal Dictionary<string, CAGChildStats> ChildrenStats
|
|
{
|
|
get { return this.childActivityStats; }
|
|
}
|
|
|
|
#endregion Accessors
|
|
|
|
internal ConditionedActivityGroupStateInfo(ConditionedActivityGroup cag)
|
|
{
|
|
int len = cag.EnabledActivities.Count;
|
|
this.childActivityStats = new Dictionary<string, CAGChildStats>(len);
|
|
foreach (Activity act in cag.EnabledActivities)
|
|
this.childActivityStats[act.QualifiedName] = new CAGChildStats();
|
|
}
|
|
|
|
#if LOG
|
|
internal void DumpState(string message)
|
|
{
|
|
Trace.WriteLine(message + " completed = " + Completed.ToString());
|
|
foreach (string key in this.childActivityStats.Keys)
|
|
{
|
|
Trace.WriteLine(key + ": state = " + this.childActivityStats[key].State + ", performed = " + this.childActivityStats[key].ExecutedCount);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
}
|
|
|
|
[Serializable]
|
|
internal enum CAGChildState : byte
|
|
{
|
|
Idle,
|
|
Pending,
|
|
Excuting
|
|
}
|
|
|
|
[Serializable]
|
|
internal class CAGChildStats
|
|
{
|
|
internal int ExecutedCount = 0;
|
|
internal CAGChildState State = CAGChildState.Idle;
|
|
internal CAGChildStats()
|
|
{ }
|
|
}
|
|
#endregion
|
|
|
|
#endregion
|
|
|
|
|
|
#region Validator
|
|
internal sealed class ConditionedActivityGroupValidator : CompositeActivityValidator
|
|
{
|
|
public override ValidationErrorCollection Validate(ValidationManager manager, object obj)
|
|
{
|
|
ValidationErrorCollection validationErrors = base.Validate(manager, obj);
|
|
|
|
ConditionedActivityGroup conditionedActivityGroup = obj as ConditionedActivityGroup;
|
|
if (conditionedActivityGroup == null)
|
|
throw new ArgumentException(SR.GetString(SR.Error_UnexpectedArgumentType, typeof(ConditionedActivityGroup).FullName), "obj");
|
|
|
|
return validationErrors;
|
|
}
|
|
|
|
public override ValidationError ValidateActivityChange(Activity activity, ActivityChangeAction action)
|
|
{
|
|
if (activity == null)
|
|
throw new ArgumentNullException("activity");
|
|
if (action == null)
|
|
throw new ArgumentNullException("action");
|
|
|
|
if (activity.ExecutionStatus != ActivityExecutionStatus.Initialized &&
|
|
activity.ExecutionStatus != ActivityExecutionStatus.Executing &&
|
|
activity.ExecutionStatus != ActivityExecutionStatus.Closed)
|
|
{
|
|
return new ValidationError(SR.GetString(SR.Error_DynamicActivity2, activity.QualifiedName, activity.ExecutionStatus, activity.GetType().FullName), ErrorNumbers.Error_DynamicActivity2);
|
|
}
|
|
|
|
// if we are currently executing, make sure that we are not changing something already running
|
|
// removed since changes mean that the child activity is going to get validated anyway
|
|
|
|
return null;
|
|
}
|
|
}
|
|
#endregion
|
|
}
|