diff --git a/pkg/bpmn_engine/conditions.go b/pkg/bpmn_engine/conditions.go
index 3995c8cc..8a22c35d 100644
--- a/pkg/bpmn_engine/conditions.go
+++ b/pkg/bpmn_engine/conditions.go
@@ -29,6 +29,28 @@ func exclusivelyFilterByConditionExpression(flows []BPMN20.TSequenceFlow, variab
return ret, nil
}
+func inclusivelyFilterByConditionExpression(flows []BPMN20.TSequenceFlow, variableContext map[string]interface{}) ([]BPMN20.TSequenceFlow, error) {
+ var ret []BPMN20.TSequenceFlow
+ for _, flow := range flows {
+ if flow.HasConditionExpression() {
+ expression := flow.GetConditionExpression()
+ out, err := evaluateExpression(expression, variableContext)
+ if err != nil {
+ return nil, &ExpressionEvaluationError{
+ Msg: fmt.Sprintf("Error evaluating expression in flow element id='%s' name='%s'", flow.Id, flow.Name),
+ Err: err,
+ }
+ }
+ if out == true {
+ ret = append(ret, flow)
+ return ret, nil
+ }
+ }
+ }
+ ret = append(ret, findDefaultFlow(flows)...)
+ return ret, nil
+}
+
func findDefaultFlow(flows []BPMN20.TSequenceFlow) (ret []BPMN20.TSequenceFlow) {
for _, flow := range flows {
if !flow.HasConditionExpression() {
diff --git a/pkg/bpmn_engine/conditions_test.go b/pkg/bpmn_engine/conditions_test.go
index f23ad3c8..9ab0cc89 100644
--- a/pkg/bpmn_engine/conditions_test.go
+++ b/pkg/bpmn_engine/conditions_test.go
@@ -101,3 +101,45 @@ func Test_evaluation_error_percolates_up(t *testing.T) {
then.AssertThat(t, err, is.Not(is.Nil()))
then.AssertThat(t, err.Error(), has.Prefix("Error evaluating expression in flow element id="))
}
+
+func Test_inclusive_gateway_with_expressions_selects_one_and_not_the_other(t *testing.T) {
+ // setup
+ bpmnEngine := New()
+ cp := CallPath{}
+
+ // given
+ process, _ := bpmnEngine.LoadFromFile("../../test-cases/inclusive-gateway-with-condition.bpmn")
+ bpmnEngine.NewTaskHandler().Id("task-a").Handler(cp.TaskHandler)
+ bpmnEngine.NewTaskHandler().Id("task-b").Handler(cp.TaskHandler)
+ variables := map[string]interface{}{
+ "price": -50,
+ }
+
+ // when
+ _, err := bpmnEngine.CreateAndRunInstance(process.ProcessKey, variables)
+ then.AssertThat(t, err, is.Nil())
+
+ // then
+ then.AssertThat(t, cp.CallPath, is.EqualTo("task-b"))
+}
+
+func Test_inclusive_gateway_with_expressions_selects_default(t *testing.T) {
+ // setup
+ bpmnEngine := New()
+ cp := CallPath{}
+
+ // given
+ process, _ := bpmnEngine.LoadFromFile("../../test-cases/inclusive-gateway-with-condition-and-default.bpmn")
+ bpmnEngine.NewTaskHandler().Id("task-a").Handler(cp.TaskHandler)
+ bpmnEngine.NewTaskHandler().Id("task-b").Handler(cp.TaskHandler)
+ variables := map[string]interface{}{
+ "price": -1,
+ }
+
+ // when
+ _, err := bpmnEngine.CreateAndRunInstance(process.ProcessKey, variables)
+ then.AssertThat(t, err, is.Nil())
+
+ // then
+ then.AssertThat(t, cp.CallPath, is.EqualTo("task-b"))
+}
diff --git a/pkg/bpmn_engine/engine.go b/pkg/bpmn_engine/engine.go
index 114a1b74..609e9a49 100644
--- a/pkg/bpmn_engine/engine.go
+++ b/pkg/bpmn_engine/engine.go
@@ -291,6 +291,13 @@ func (state *BpmnEngineState) handleElement(process *ProcessInfo, instance *proc
}
instance.appendActivity(activity)
createFlowTransitions = true
+ case BPMN20.InclusiveGateway:
+ activity = elementActivity{
+ key: state.generateKey(),
+ state: Active,
+ element: element,
+ }
+ createFlowTransitions = true
default:
panic(fmt.Sprintf("[invariant check] unsupported element: id=%s, type=%s", (*element).GetId(), (*element).GetType()))
}
@@ -313,7 +320,8 @@ func createCheckExclusiveGatewayDoneCommand(originActivity activity) (cmds []com
func createNextCommands(process *ProcessInfo, instance *processInstanceInfo, element *BPMN20.BaseElement, activity activity) (cmds []command) {
nextFlows := BPMN20.FindSequenceFlows(&process.definitions.Process.SequenceFlows, (*element).GetOutgoingAssociation())
var err error
- if (*element).GetType() == BPMN20.ExclusiveGateway {
+ switch (*element).GetType() {
+ case BPMN20.ExclusiveGateway:
nextFlows, err = exclusivelyFilterByConditionExpression(nextFlows, instance.VariableHolder.Variables())
if err != nil {
instance.State = Failed
@@ -324,6 +332,18 @@ func createNextCommands(process *ProcessInfo, instance *processInstanceInfo, ele
})
return cmds
}
+ case BPMN20.InclusiveGateway:
+ nextFlows, err = inclusivelyFilterByConditionExpression(nextFlows, instance.VariableHolder.Variables())
+ if err != nil {
+ instance.State = Failed
+ return []command{
+ errorCommand{
+ elementId: (*element).GetId(),
+ elementName: (*element).GetName(),
+ err: err,
+ },
+ }
+ }
}
for _, flow := range nextFlows {
cmds = append(cmds, flowTransitionCommand{
diff --git a/pkg/spec/BPMN20/bpmn_structs.go b/pkg/spec/BPMN20/bpmn_structs.go
index 865c9040..1a02f664 100644
--- a/pkg/spec/BPMN20/bpmn_structs.go
+++ b/pkg/spec/BPMN20/bpmn_structs.go
@@ -31,6 +31,7 @@ type TProcess struct {
IntermediateCatchEvent []TIntermediateCatchEvent `xml:"intermediateCatchEvent"`
IntermediateTrowEvent []TIntermediateThrowEvent `xml:"intermediateThrowEvent"`
EventBasedGateway []TEventBasedGateway `xml:"eventBasedGateway"`
+ InclusiveGateway []TInclusiveGateway `xml:"inclusiveGateway"`
}
type TSequenceFlow struct {
@@ -150,3 +151,10 @@ type TMessage struct {
type TTimeDuration struct {
XMLText string `xml:",innerxml"`
}
+
+type TInclusiveGateway struct {
+ Id string `xml:"id,attr"`
+ Name string `xml:"name,attr"`
+ IncomingAssociation []string `xml:"incoming"`
+ OutgoingAssociation []string `xml:"outgoing"`
+}
diff --git a/pkg/spec/BPMN20/elements.go b/pkg/spec/BPMN20/elements.go
index 87169105..8dd91156 100644
--- a/pkg/spec/BPMN20/elements.go
+++ b/pkg/spec/BPMN20/elements.go
@@ -15,6 +15,7 @@ const (
IntermediateCatchEvent ElementType = "INTERMEDIATE_CATCH_EVENT"
IntermediateThrowEvent ElementType = "INTERMEDIATE_THROW_EVENT"
EventBasedGateway ElementType = "EVENT_BASED_GATEWAY"
+ InclusiveGateway ElementType = "INCLUSIVE_GATEWAY"
SequenceFlow ElementType = "SEQUENCE_FLOW"
@@ -45,6 +46,7 @@ type GatewayElement interface {
BaseElement
IsParallel() bool
IsExclusive() bool
+ IsInclusive() bool
}
func (startEvent TStartEvent) GetId() string {
@@ -194,6 +196,10 @@ func (parallelGateway TParallelGateway) IsExclusive() bool {
return false
}
+func (parallelGateway TParallelGateway) IsInclusive() bool {
+ return false
+}
+
func (exclusiveGateway TExclusiveGateway) GetId() string {
return exclusiveGateway.Id
}
@@ -221,6 +227,10 @@ func (exclusiveGateway TExclusiveGateway) IsExclusive() bool {
return true
}
+func (exclusiveGateway TExclusiveGateway) IsInclusive() bool {
+ return false
+}
+
func (intermediateCatchEvent TIntermediateCatchEvent) GetId() string {
return intermediateCatchEvent.Id
}
@@ -271,6 +281,10 @@ func (eventBasedGateway TEventBasedGateway) IsExclusive() bool {
return true
}
+func (eventBasedGateway TEventBasedGateway) IsInclusive() bool {
+ return false
+}
+
// -------------------------------------------------------------------------
func (intermediateThrowEvent TIntermediateThrowEvent) GetId() string {
@@ -293,3 +307,35 @@ func (intermediateThrowEvent TIntermediateThrowEvent) GetOutgoingAssociation() [
func (intermediateThrowEvent TIntermediateThrowEvent) GetType() ElementType {
return IntermediateThrowEvent
}
+
+func (inclusiveGateway TInclusiveGateway) GetId() string {
+ return inclusiveGateway.Id
+}
+
+func (inclusiveGateway TInclusiveGateway) GetName() string {
+ return inclusiveGateway.Name
+}
+
+func (inclusiveGateway TInclusiveGateway) GetIncomingAssociation() []string {
+ return inclusiveGateway.IncomingAssociation
+}
+
+func (inclusiveGateway TInclusiveGateway) GetOutgoingAssociation() []string {
+ return inclusiveGateway.OutgoingAssociation
+}
+
+func (inclusiveGateway TInclusiveGateway) GetType() ElementType {
+ return InclusiveGateway
+}
+
+func (inclusiveGateway TInclusiveGateway) IsParallel() bool {
+ return false
+}
+
+func (inclusiveGateway TInclusiveGateway) IsExclusive() bool {
+ return false
+}
+
+func (inclusiveGateway TInclusiveGateway) IsInclusive() bool {
+ return true
+}
diff --git a/pkg/spec/BPMN20/elements_test.go b/pkg/spec/BPMN20/elements_test.go
index 38b6085b..e3b1e08e 100644
--- a/pkg/spec/BPMN20/elements_test.go
+++ b/pkg/spec/BPMN20/elements_test.go
@@ -17,4 +17,5 @@ func Test_all_interfaces_implemented(t *testing.T) {
var _ BaseElement = &TIntermediateCatchEvent{}
var _ BaseElement = &TIntermediateThrowEvent{}
var _ BaseElement = &TEventBasedGateway{}
+ var _ BaseElement = &TInclusiveGateway{}
}
diff --git a/pkg/spec/BPMN20/helper.go b/pkg/spec/BPMN20/helper.go
index 8e576274..bacf503d 100644
--- a/pkg/spec/BPMN20/helper.go
+++ b/pkg/spec/BPMN20/helper.go
@@ -69,6 +69,10 @@ func FindBaseElementsById(definitions *TDefinitions, id string) (elements []*Bas
var be BaseElement = intermediateCatchEvent
appender(&be)
}
+ for _, inclusiveGateway := range definitions.Process.InclusiveGateway {
+ var be BaseElement = inclusiveGateway
+ appender(&be)
+ }
return elements
}
diff --git a/test-cases/inclusive-gateway-with-condition-and-default.bpmn b/test-cases/inclusive-gateway-with-condition-and-default.bpmn
new file mode 100644
index 00000000..94bd92cd
--- /dev/null
+++ b/test-cases/inclusive-gateway-with-condition-and-default.bpmn
@@ -0,0 +1,90 @@
+
+
+
+
+ Flow_1y8jegt
+
+
+
+ price > 0
+
+
+
+
+
+ price-gt-zero
+ Flow_1moyr7v
+
+
+
+
+
+
+ default
+ Flow_1dekydz
+
+
+ Flow_1dekydz
+
+
+
+ Flow_1moyr7v
+
+
+
+ Flow_1y8jegt
+ price-gt-zero
+ default
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/test-cases/inclusive-gateway-with-condition.bpmn b/test-cases/inclusive-gateway-with-condition.bpmn
new file mode 100644
index 00000000..5b414e58
--- /dev/null
+++ b/test-cases/inclusive-gateway-with-condition.bpmn
@@ -0,0 +1,92 @@
+
+
+
+
+ Flow_1y8jegt
+
+
+
+ price > 0
+
+
+
+
+
+ price-gt-zero
+ Flow_1moyr7v
+
+
+ price < 0
+
+
+
+
+
+ price-lt-zero
+ Flow_1dekydz
+
+
+ Flow_1dekydz
+
+
+
+ Flow_1moyr7v
+
+
+
+ Flow_1y8jegt
+ price-gt-zero
+ price-lt-zero
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+