diff --git a/BPMNEngine/.vs/BPMNEngine/DesignTimeBuild/.dtbcache.v2 b/BPMNEngine/.vs/BPMNEngine/DesignTimeBuild/.dtbcache.v2 index 53296ba..7718f08 100644 Binary files a/BPMNEngine/.vs/BPMNEngine/DesignTimeBuild/.dtbcache.v2 and b/BPMNEngine/.vs/BPMNEngine/DesignTimeBuild/.dtbcache.v2 differ diff --git a/BPMNEngine/.vs/BPMNEngine/v17/.futdcache.v2 b/BPMNEngine/.vs/BPMNEngine/v17/.futdcache.v2 index 0beb67a..d43800f 100644 Binary files a/BPMNEngine/.vs/BPMNEngine/v17/.futdcache.v2 and b/BPMNEngine/.vs/BPMNEngine/v17/.futdcache.v2 differ diff --git a/BPMNEngine/.vs/BPMNEngine/v17/.suo b/BPMNEngine/.vs/BPMNEngine/v17/.suo index c290388..3c899f6 100644 Binary files a/BPMNEngine/.vs/BPMNEngine/v17/.suo and b/BPMNEngine/.vs/BPMNEngine/v17/.suo differ diff --git a/BPMNEngine/.vs/BPMNEngine/v17/TestStore/0/testlog.manifest b/BPMNEngine/.vs/BPMNEngine/v17/TestStore/0/testlog.manifest index bde5c72..96dcbf4 100644 Binary files a/BPMNEngine/.vs/BPMNEngine/v17/TestStore/0/testlog.manifest and b/BPMNEngine/.vs/BPMNEngine/v17/TestStore/0/testlog.manifest differ diff --git a/BPMNEngine/.vs/ProjectEvaluation/bpmnengine.metadata.v7.bin b/BPMNEngine/.vs/ProjectEvaluation/bpmnengine.metadata.v7.bin index 03c72a3..561a207 100644 Binary files a/BPMNEngine/.vs/ProjectEvaluation/bpmnengine.metadata.v7.bin and b/BPMNEngine/.vs/ProjectEvaluation/bpmnengine.metadata.v7.bin differ diff --git a/BPMNEngine/.vs/ProjectEvaluation/bpmnengine.projects.v7.bin b/BPMNEngine/.vs/ProjectEvaluation/bpmnengine.projects.v7.bin index 3d43513..55c8d3e 100644 Binary files a/BPMNEngine/.vs/ProjectEvaluation/bpmnengine.projects.v7.bin and b/BPMNEngine/.vs/ProjectEvaluation/bpmnengine.projects.v7.bin differ diff --git a/BPMNEngine/BusinessProcess.Actions.cs b/BPMNEngine/BusinessProcess.Actions.cs index eaf4a11..e1a3a52 100644 --- a/BPMNEngine/BusinessProcess.Actions.cs +++ b/BPMNEngine/BusinessProcess.Actions.cs @@ -57,6 +57,13 @@ internal void ProcessStepComplete(ProcessInstance instance, string sourceID, str } }); } + if (elem is SubProcess subProcess) + { + ReadOnlyProcessVariablesContainer vars = new(sourceID, instance); + subProcess.Children + .Where(child=>instance.State.Path.AbortableSteps.Contains(child.ID)) + .ForEach(child=>AbortStep(instance, sourceID, child, vars)); + } } WriteLogLine(sourceID, LogLevel.Debug, new StackFrame(1, true), DateTime.Now, string.Format("Process Step[{0}] has been completed", sourceID)); if (outgoingID != null) @@ -322,41 +329,48 @@ internal void ProcessEvent(ProcessInstance instance, string sourceID, AEvent evn evnt, new ReadOnlyProcessVariablesContainer(evnt.ID, instance) ); - if (evnt is EndEvent event1 && event1.IsProcessEnd) + if (evnt is EndEvent endEvent) { - if (!event1.IsTermination) + var sp = endEvent.SubProcess as SubProcess; + if (sp!=null && + ( + !endEvent.IsProcessEnd + ||(endEvent.IsProcessEnd && !endEvent.IsTermination) + ) + ){ + instance.State.Path.SucceedFlowNode(sp); + BusinessProcess.TriggerDelegateAsync( + instance.Delegates.Events.SubProcesses.Completed, + sp, + new ReadOnlyProcessVariablesContainer(sp.ID, instance) + ); + } + else if (endEvent.IsProcessEnd) { - SubProcess sp = (SubProcess)event1.SubProcess; - if (sp != null) + if (!endEvent.IsTermination) { - instance.State.Path.SucceedFlowNode(sp); - BusinessProcess.TriggerDelegateAsync( - instance.Delegates.Events.SubProcesses.Completed, - sp, - new ReadOnlyProcessVariablesContainer(sp.ID, instance) - ); + if (sp==null) + { + BusinessProcess.TriggerDelegateAsync( + instance.Delegates.Events.Processes.Completed, + endEvent.Process, + new ReadOnlyProcessVariablesContainer(evnt.ID, instance) + ); + instance.CompleteProcess(); + } } else { + ReadOnlyProcessVariablesContainer vars = new(evnt.ID, instance); + instance.State.AbortableSteps.ForEach(str => { AbortStep(instance, evnt.ID, GetElement(str), vars); }); BusinessProcess.TriggerDelegateAsync( - instance.Delegates.Events.Processes.Completed, - event1.Process, + instance.Delegates.Events.Processes.Completed, + endEvent.Process, new ReadOnlyProcessVariablesContainer(evnt.ID, instance) ); instance.CompleteProcess(); } } - else - { - ReadOnlyProcessVariablesContainer vars = new(evnt.ID, instance); - instance.State.AbortableSteps.ForEach(str => { AbortStep(instance, evnt.ID, GetElement(str), vars); }); - BusinessProcess.TriggerDelegateAsync( - instance.Delegates.Events.Processes.Completed, - event1.Process, - new ReadOnlyProcessVariablesContainer(evnt.ID, instance) - ); - instance.CompleteProcess(); - } } } } diff --git a/BPMNEngine/BusinessProcess.cs b/BPMNEngine/BusinessProcess.cs index 390e19e..7cf1dba 100644 --- a/BPMNEngine/BusinessProcess.cs +++ b/BPMNEngine/BusinessProcess.cs @@ -291,7 +291,7 @@ public IProcessInstance LoadState(Utf8JsonReader reader, /// Used to set the logging level for the process state document /// a process instance if the process was successfully started public IProcessInstance BeginProcess( - Dictionary pars, + Dictionary pars=null, ProcessEvents events = null, StepValidations validations = null, ProcessTasks tasks = null, diff --git a/BPMNEngine/Elements/Diagrams/Edge.cs b/BPMNEngine/Elements/Diagrams/Edge.cs index dcad88b..bf474a4 100644 --- a/BPMNEngine/Elements/Diagrams/Edge.cs +++ b/BPMNEngine/Elements/Diagrams/Edge.cs @@ -38,7 +38,7 @@ public override RectF Rectangle }); Label l = Label; _rectangle = new RectF(_rectangle.Value.X-3.5f, _rectangle.Value.Y-3.5f, _rectangle.Value.Width+6.5f, _rectangle.Value.Height+6.5f); - if (l != null) + if (l != null && l.Bounds!=null) _rectangle = MergeRectangle(_rectangle.Value,l.Bounds.Rectangle); } return _rectangle.Value; @@ -122,7 +122,7 @@ public void Render(ICanvas surface, ProcessPath path,Definition definition) } } - if (Label!=null) + if (Label!=null && Label.Bounds!=null) { surface.FontColor = color; surface.DrawString(elem.ToString(), Label.Bounds.Rectangle, HorizontalAlignment.Center, VerticalAlignment.Center); diff --git a/BPMNEngine/Elements/Diagrams/Label.cs b/BPMNEngine/Elements/Diagrams/Label.cs index b1d3a17..2bbb885 100644 --- a/BPMNEngine/Elements/Diagrams/Label.cs +++ b/BPMNEngine/Elements/Diagrams/Label.cs @@ -7,21 +7,10 @@ namespace BPMNEngine.Elements.Diagrams [ValidParent(typeof(Shape))] internal class Label : AParentElement { - public Bounds Bounds - => Children.OfType().FirstOrDefault(); + public Bounds? Bounds + => Children.OfType().FirstOrDefault(); public Label(XmlElement elem, XmlPrefixMap map, AElement parent) : base(elem, map, parent) { } - - public override bool IsValid(out IEnumerable err) - { - var res = base.IsValid(out err); - if (Bounds == null) - { - err = (err??Array.Empty()).Concat(new string[] { "No bounds for the label are specified." }); - return false; - } - return res; - } } } diff --git a/BPMNEngine/Elements/SubProcess.cs b/BPMNEngine/Elements/SubProcess.cs index 71437b9..93fb1f9 100644 --- a/BPMNEngine/Elements/SubProcess.cs +++ b/BPMNEngine/Elements/SubProcess.cs @@ -32,7 +32,7 @@ public override bool IsValid(out IEnumerable err) var res = base.IsValid(out err); bool hasStart = Children.Any(elem => elem is StartEvent || (elem is IntermediateCatchEvent ice && ice.SubType.HasValue)); bool hasEnd = Children.Any(elem=>elem is EndEvent); - bool hasIncoming = this.Incoming!=null || Children.Any(elem=>elem is IntermediateCatchEvent ice && ice.SubType.HasValue); + bool hasIncoming = Incoming.Any() || Children.Any(elem=>elem is IntermediateCatchEvent ice && ice.SubType.HasValue); var terr = new List(); if (!(hasStart && hasEnd && hasIncoming)) { diff --git a/UnitTest/AssemblyInfo.cs b/UnitTest/AssemblyInfo.cs index 574539f..1b6cd92 100644 --- a/UnitTest/AssemblyInfo.cs +++ b/UnitTest/AssemblyInfo.cs @@ -1,3 +1,3 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -[assembly: Parallelize(Workers = 50, Scope = ExecutionScope.MethodLevel)] \ No newline at end of file +[assembly: Parallelize(Workers = 25, Scope = ExecutionScope.MethodLevel)] \ No newline at end of file diff --git a/UnitTest/DiagramLoading.cs b/UnitTest/DiagramLoading.cs index e4d04c2..0576a89 100644 --- a/UnitTest/DiagramLoading.cs +++ b/UnitTest/DiagramLoading.cs @@ -120,13 +120,15 @@ public void LoadInvalidDiagram() Assert.IsTrue(res.Contains("No bounds specified for the shape.")); Assert.IsTrue(res.Contains("End Events cannot have an outgoing path.")); Assert.IsTrue(res.Contains("End Events must have an incoming path.")); - Assert.IsTrue(res.Contains("No bounds for the label are specified.")); Assert.IsTrue(res.Contains("No child elements found in the definition.")); Assert.IsTrue(res.Contains("Collaboration requires at least 1 child element.")); Assert.IsTrue(res.Contains("Right value specified more than once.")); Assert.IsTrue(res.Contains("Left value specified more than once.")); Assert.IsTrue(res.Contains("Right and Left value missing.")); Assert.IsTrue(res.Contains("Left value missing.")); + Assert.IsTrue(res.Contains("A Sub Process Must have a StartEvent or valid IntermediateCatchEvent")); + Assert.IsTrue(res.Contains("A Sub Process Must have a valid Incoming path, achieved through an incoming flow or IntermediateCatchEvent")); + Assert.IsTrue(res.Contains("A Sub Process Must have an EndEvent")); } [TestMethod] diff --git a/UnitTest/SubProcesses.cs b/UnitTest/SubProcesses.cs new file mode 100644 index 0000000..ecef22b --- /dev/null +++ b/UnitTest/SubProcesses.cs @@ -0,0 +1,122 @@ +using BPMNEngine; +using BPMNEngine.Interfaces.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace UnitTest +{ + [TestClass] + public class SubProcesses + { + private const string SIGNAL = "interupted"; + + private const string SUB_PROCESS_ID = "Activity_01eulv2"; + private const string SUB_SUB_PROCESS_ID = "Activity_099a0io"; + + private const string MAIN_SIGNAL_ID = "Event_167oagf"; + private const string SUB_SIGNAL_ID = "Event_1etf3o8"; + private const string SUB_SUB_SIGNAL_ID = "Event_0s8bx7i"; + + private const string MAIN_TASK_ID = "Activity_1hle9w5"; + private const string SUB_TASK_ID = "Activity_1ifnuk7"; + private const string SUB_SUB_TASK_ID = "Activity_1e0arlx"; + + private static BusinessProcess _process; + + [ClassInitialize()] + public static void Initialize(TestContext testContext) + { + _process = new BusinessProcess(Utility.LoadResourceDocument("SubProcesses/subprocesses.bpmn")); + } + + [ClassCleanup] + public static void Cleanup() + { + _process.Dispose(); + } + + [TestMethod] + public void TestSubProcessCompletion() + { + var instance = _process.BeginProcess(); + Assert.IsNotNull(instance); + Assert.IsTrue(instance.WaitForManualTask(SUB_TASK_ID, out IManualTask task)); + Assert.IsNotNull(task); + task.MarkComplete(); + Assert.IsTrue(Utility.WaitForCompletion(instance)); + var state = instance.CurrentState; + Assert.IsTrue(Utility.StepCompleted(state, SUB_PROCESS_ID)); + Assert.IsTrue(Utility.StepAborted(state, SUB_SUB_PROCESS_ID)); + Assert.IsTrue(Utility.StepAborted(state, SUB_SUB_TASK_ID)); + } + + [TestMethod] + public void TestSubTaskSignal() + { + var instance = _process.BeginProcess(); + Assert.IsNotNull(instance); + Assert.IsTrue(instance.WaitForManualTask(SUB_TASK_ID, out IManualTask task)); + Assert.IsNotNull(task); + task.Signal(SIGNAL, out bool isAborted); + Assert.IsTrue(isAborted); + Assert.IsTrue(Utility.WaitForCompletion(instance)); + var state = instance.CurrentState; + Assert.IsTrue(Utility.StepAborted(state, SUB_PROCESS_ID)); + Assert.IsTrue(Utility.StepCompleted(state, SUB_SIGNAL_ID)); + Assert.IsFalse(Utility.StepCompleted(state, SUB_SUB_SIGNAL_ID)); + Assert.IsFalse(Utility.StepCompleted(state, MAIN_SIGNAL_ID)); + Assert.IsTrue(Utility.StepAborted(state, SUB_SUB_PROCESS_ID)); + Assert.IsTrue(Utility.StepAborted(state, SUB_PROCESS_ID)); + } + + [TestMethod] + public void TestSubSubProcessCompletion() + { + var instance = _process.BeginProcess(); + Assert.IsNotNull(instance); + Assert.IsTrue(instance.WaitForManualTask(SUB_SUB_TASK_ID, out IManualTask task)); + Assert.IsNotNull(task); + task.MarkComplete(); + Assert.IsTrue(Utility.WaitForCompletion(instance)); + var state = instance.CurrentState; + Assert.IsTrue(Utility.StepCompleted(state, SUB_PROCESS_ID)); + Assert.IsTrue(Utility.StepCompleted(state, SUB_SUB_PROCESS_ID)); + Assert.IsTrue(Utility.StepAborted(state, SUB_TASK_ID)); + } + + [TestMethod] + public void TestSubSubTaskSignal() + { + var instance = _process.BeginProcess(); + Assert.IsNotNull(instance); + Assert.IsTrue(instance.WaitForManualTask(SUB_SUB_TASK_ID, out IManualTask task)); + Assert.IsNotNull(task); + task.Signal(SIGNAL,out bool isAborted); + Assert.IsTrue(isAborted); + Assert.IsTrue(Utility.WaitForCompletion(instance)); + var state = instance.CurrentState; + Assert.IsTrue(Utility.StepCompleted(state, SUB_PROCESS_ID)); + Assert.IsTrue(Utility.StepCompleted(state, SUB_SUB_SIGNAL_ID)); + Assert.IsFalse(Utility.StepCompleted(state, SUB_SIGNAL_ID)); + Assert.IsFalse(Utility.StepCompleted(state, MAIN_SIGNAL_ID)); + Assert.IsTrue(Utility.StepAborted(state, SUB_SUB_PROCESS_ID)); + Assert.IsTrue(Utility.StepAborted(state, SUB_SUB_TASK_ID)); + } + + [TestMethod] + public void TestMainTaskSignal() + { + var instance = _process.BeginProcess(); + Assert.IsNotNull(instance); + Assert.IsTrue(instance.WaitForManualTask(MAIN_TASK_ID, out IManualTask task)); + Assert.IsNotNull(task); + task.Signal(SIGNAL, out bool isAborted); + Assert.IsFalse(isAborted); + Assert.IsTrue(Utility.WaitForCompletion(instance)); + var state = instance.CurrentState; + Assert.IsFalse(Utility.StepCompleted(state, SUB_PROCESS_ID)); + Assert.IsFalse(Utility.StepCompleted(state, SUB_SUB_SIGNAL_ID)); + Assert.IsFalse(Utility.StepCompleted(state, SUB_SIGNAL_ID)); + Assert.IsTrue(Utility.StepCompleted(state, MAIN_SIGNAL_ID)); + } + } +} diff --git a/UnitTest/UnitTest.csproj b/UnitTest/UnitTest.csproj index 3910735..7f49806 100644 --- a/UnitTest/UnitTest.csproj +++ b/UnitTest/UnitTest.csproj @@ -44,6 +44,7 @@ + @@ -89,6 +90,7 @@ + diff --git a/UnitTest/diagrams/DiagramLoading/invalid.bpmn b/UnitTest/diagrams/DiagramLoading/invalid.bpmn index ec98081..2c350a1 100644 --- a/UnitTest/diagrams/DiagramLoading/invalid.bpmn +++ b/UnitTest/diagrams/DiagramLoading/invalid.bpmn @@ -192,6 +192,8 @@ + + diff --git a/UnitTest/diagrams/SubProcesses/subprocesses.bpmn b/UnitTest/diagrams/SubProcesses/subprocesses.bpmn new file mode 100644 index 0000000..221970d --- /dev/null +++ b/UnitTest/diagrams/SubProcesses/subprocesses.bpmn @@ -0,0 +1,260 @@ + + + + + Flow_0ybu4zs + + + Flow_1iuw80e + Flow_1v689k6 + + Flow_1ogm67q + + + Flow_0sg21d8 + Flow_0a6kp3f + + Flow_0wg2h5i + + + + Flow_0wg2h5i + Flow_0ey8aqt + + + Flow_0ey8aqt + + + + + + Flow_1ogm67q + Flow_0sg21d8 + Flow_06ya3yo + + + + + Flow_06ya3yo + Flow_1cury9n + + + Flow_1cury9n + Flow_0a6kp3f + Flow_06vs1x4 + Flow_0122jm8 + + + + Flow_0122jm8 + + + + + + Flow_06vs1x4 + + + + + + + + + + + + Flow_1q3m6i8 + Flow_1v689k6 + Flow_1j6wmqe + Flow_0hvbkiq + Flow_081edq1 + + + + Flow_0ybu4zs + Flow_1iuw80e + Flow_1lu5lzt + + + Flow_081edq1 + + + + + Flow_1lu5lzt + Flow_1q3m6i8 + + + Flow_1j6wmqe + + + + + + + + + Flow_0hvbkiq + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +