private static bool ValidateDUInConditionTransition(NativeActivityUpdateMapMetadata metadata, InternalTransition updatedTransition, InternalTransition originalTransition, out string errorMessage) { Fx.Assert(!originalTransition.IsUnconditional, "Transition should be conditional in the original definition."); errorMessage = string.Empty; foreach (TransitionData updatedTData in updatedTransition.TransitionDataList) { if (metadata.IsReferenceToImportedChild(updatedTData.Condition)) { // if the trigger is referenced, it might have another save values already. errorMessage = SR.TriggerOrConditionIsReferenced; return false; } Fx.Assert(null != updatedTData.Condition, "Must be a condition transition."); Activity conditionMatch = metadata.GetMatch(updatedTData.Condition); if (null == conditionMatch && null != metadata.GetMatch(updatedTData.Action)) { // new Transition.Condition with an Transition.Action moved from within the InternalState. errorMessage = SR.MovingActivitiesInStateBlockDU; return false; } else if (null != conditionMatch) { bool foundMatchingOriginalCondition = false; for (int transitionIndex = 0; transitionIndex < originalTransition.TransitionDataList.Count; transitionIndex++) { if (object.ReferenceEquals(originalTransition.TransitionDataList[transitionIndex].Condition, conditionMatch)) { foundMatchingOriginalCondition = true; // found the original matching condition in updated transition definition. TransitionData originalTData = originalTransition.TransitionDataList[transitionIndex]; Activity originalAction = originalTData.Action; // NOTE: Transition.Action is allowed to be removed, because it doesn't change the execution semantics of SM // if this removed activity was executing, WF runtime would disallow the update. Activity actionMatch = metadata.GetMatch(updatedTData.Action); if (null != actionMatch && !object.ReferenceEquals(originalAction, actionMatch)) { // Transition.Action is an activity moved from elsewhere within the InternalState errorMessage = SR.MovingActivitiesInStateBlockDU; return false; } metadata.SaveOriginalValue(updatedTransition.Trigger, originalTransition.InternalTransitionIndex); metadata.SaveOriginalValue(updatedTData.Condition, transitionIndex); } } if (!foundMatchingOriginalCondition) { // another child activity is move to the Transition.Condition. errorMessage = SR.DUDisallowIfCannotFindingMatchingCondition; return false; } } } return true; }
protected override void OnCreateDynamicUpdateMap(NativeActivityUpdateMapMetadata metadata, Activity originalActivity) { Flowchart originalFlowchart = (Flowchart)originalActivity; Dictionary <Activity, int> originalActivities = new Dictionary <Activity, int>(); foreach (FlowNode node in originalFlowchart.reachableNodes) { if (node.ChildActivity == null) { continue; } if (metadata.IsReferenceToImportedChild(node.ChildActivity)) { // We can't save original values for referenced children. Also, we can't // reliably combine implementation changes with changes to referenced children. // For now, we just disable this scenario altogether; if we want to support it, // we'll need deeper runtime support. metadata.DisallowUpdateInsideThisActivity(SR.FlowchartContainsReferences); return; } if (originalActivities.ContainsKey(node.ChildActivity)) { metadata.DisallowUpdateInsideThisActivity(SR.MultipleFlowNodesSharingSameChildBlockDU); return; } originalActivities[node.ChildActivity] = node.Index; } HashSet <Activity> updatedActivities = new HashSet <Activity>(); foreach (FlowNode node in this.reachableNodes) { if (node.ChildActivity != null) { if (metadata.IsReferenceToImportedChild(node.ChildActivity)) { metadata.DisallowUpdateInsideThisActivity(SR.FlowchartContainsReferences); return; } if (updatedActivities.Contains(node.ChildActivity)) { metadata.DisallowUpdateInsideThisActivity(SR.MultipleFlowNodesSharingSameChildBlockDU); return; } else { updatedActivities.Add(node.ChildActivity); } Activity originalChild = metadata.GetMatch(node.ChildActivity); int originalIndex; if (originalChild != null && originalActivities.TryGetValue(originalChild, out originalIndex)) { if (originalFlowchart.reachableNodes[originalIndex].GetType() != node.GetType()) { metadata.DisallowUpdateInsideThisActivity(SR.CannotMoveChildAcrossDifferentFlowNodeTypes); return; } if (originalIndex != node.Index) { metadata.SaveOriginalValue(node.ChildActivity, originalIndex); } } } } }
protected override void OnCreateDynamicUpdateMap(NativeActivityUpdateMapMetadata metadata, Activity originalActivity) { InternalState originalInternalState = (InternalState)originalActivity; // NOTE: State.Entry/Exit are allowed to be removed, because it doesn't change the execution semantics of SM // if this removed activity was executing, WF runtime would disallow the update. Activity entryActivityMatch = metadata.GetMatch(this.Entry); Activity exitActivityMatch = metadata.GetMatch(this.Exit); if ((null != entryActivityMatch && !object.ReferenceEquals(entryActivityMatch, originalInternalState.Entry)) || (null != exitActivityMatch && !object.ReferenceEquals(exitActivityMatch, originalInternalState.Exit))) { // original State.Entry/Exit is replaced with another child activities with InternalState // new State.Entry/Exit is moved from another child activities within InternalState. metadata.DisallowUpdateInsideThisActivity(SR.MovingActivitiesInStateBlockDU); return; } int originalTriggerInUpdatedDefinition = 0; foreach (InternalTransition originalTransition in originalInternalState.internalTransitions) { if (metadata.IsReferenceToImportedChild(originalTransition.Trigger)) { metadata.DisallowUpdateInsideThisActivity(SR.TriggerOrConditionIsReferenced); return; } if (!originalTransition.IsUnconditional) { // new Trigger activity foreach (TransitionData transitionData in originalTransition.TransitionDataList) { if (metadata.IsReferenceToImportedChild(transitionData.Condition)) { metadata.DisallowUpdateInsideThisActivity(SR.TriggerOrConditionIsReferenced); return; } } } } foreach (InternalTransition updatedTransition in this.internalTransitions) { if (metadata.IsReferenceToImportedChild(updatedTransition.Trigger)) { // if the trigger is referenced, it might have another save values already. metadata.DisallowUpdateInsideThisActivity(SR.TriggerOrConditionIsReferenced); return; } Activity triggerMatch = metadata.GetMatch(updatedTransition.Trigger); if (null != triggerMatch) { InternalTransition originalTransition; if (originalInternalState.triggerInternalTransitionMapping.TryGetValue(triggerMatch, out originalTransition)) { originalTriggerInUpdatedDefinition++; if (originalTransition.IsUnconditional) { string errorMessage; bool canTransitionBeUpdated = ValidateDUInUnconditionalTransition(metadata, updatedTransition, originalTransition, out errorMessage); if (!canTransitionBeUpdated) { metadata.DisallowUpdateInsideThisActivity(errorMessage); return; } } else { if (updatedTransition.IsUnconditional) { // cannot change the transition from condition to unconditional. metadata.DisallowUpdateInsideThisActivity(SR.ChangeConditionalTransitionToUnconditionalBlockDU); return; } else { string errorMessage; bool canTransitionBeUpdated = ValidateDUInConditionTransition(metadata, updatedTransition, originalTransition, out errorMessage); if (!canTransitionBeUpdated) { metadata.DisallowUpdateInsideThisActivity(errorMessage); return; } } } } else { // the trigger is an child activity moved from elsewhere within the state metadata.DisallowUpdateInsideThisActivity(SR.MovingActivitiesInStateBlockDU); return; } } else { // new Trigger activity foreach (TransitionData transitionData in updatedTransition.TransitionDataList) { if ((null != transitionData.Condition && null != metadata.GetMatch(transitionData.Condition)) || (null != transitionData.Action && null != metadata.GetMatch(transitionData.Action))) { // if a new transition is added, it is expected that the Condition/Action // are newly created. metadata.DisallowUpdateInsideThisActivity(SR.ChangingTriggerOrUseOriginalConditionActionBlockDU); return; } } } } if (originalTriggerInUpdatedDefinition != originalInternalState.internalTransitions.Count) { // NOTE: in general, if the transition is removed when there are pending triggers, // runtime would be able to detect the missing child activities. However, in cases, // where the transition is happening already (in between completion of Transition.Action // callback but before InternalState is completed), the workflow definition can be unloaded // and updated. The InternalState is unable to trace the original transition that set the // destination state index. In that case, the update would fail at UpdateInstance. // To simplify the model, it is more convenient to disallow removing existing transitions // from an executing InternalState. The only extra restriction it brings, is that it disables // update even if the InternalState is uploaded at State.Entry. This scenario, however, is uncommon. metadata.DisallowUpdateInsideThisActivity(SR.RemovingTransitionsBlockDU); } }