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 { //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; } /// /// Evaluate the conditions on the CAG /// /// /// /// True if CAG is complete (UNTIL == true, or no UNTIL and no children execute), false otherwise 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 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; } /// /// Evaluate the While condition for a particular child of the CAG /// If no While condition, it becomes "execute once" /// /// /// /// /// 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; } } /// /// Start any child activities that need to be run /// /// /// 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 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 Members void IActivityEventListener.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 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 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 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 ChildrenStats { get { return this.childActivityStats; } } #endregion Accessors internal ConditionedActivityGroupStateInfo(ConditionedActivityGroup cag) { int len = cag.EnabledActivities.Count; this.childActivityStats = new Dictionary(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 }