Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

add feature - Inclusive Gateway element #204

Merged
merged 1 commit into from
May 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions pkg/bpmn_engine/conditions.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
42 changes: 42 additions & 0 deletions pkg/bpmn_engine/conditions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
}
22 changes: 21 additions & 1 deletion pkg/bpmn_engine/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()))
}
Expand All @@ -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
Expand All @@ -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{
Expand Down
8 changes: 8 additions & 0 deletions pkg/spec/BPMN20/bpmn_structs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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"`
}
46 changes: 46 additions & 0 deletions pkg/spec/BPMN20/elements.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -45,6 +46,7 @@ type GatewayElement interface {
BaseElement
IsParallel() bool
IsExclusive() bool
IsInclusive() bool
}

func (startEvent TStartEvent) GetId() string {
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -271,6 +281,10 @@ func (eventBasedGateway TEventBasedGateway) IsExclusive() bool {
return true
}

func (eventBasedGateway TEventBasedGateway) IsInclusive() bool {
return false
}

// -------------------------------------------------------------------------

func (intermediateThrowEvent TIntermediateThrowEvent) GetId() string {
Expand All @@ -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
}
1 change: 1 addition & 0 deletions pkg/spec/BPMN20/elements_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ func Test_all_interfaces_implemented(t *testing.T) {
var _ BaseElement = &TIntermediateCatchEvent{}
var _ BaseElement = &TIntermediateThrowEvent{}
var _ BaseElement = &TEventBasedGateway{}
var _ BaseElement = &TInclusiveGateway{}
}
4 changes: 4 additions & 0 deletions pkg/spec/BPMN20/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
90 changes: 90 additions & 0 deletions test-cases/inclusive-gateway-with-condition-and-default.bpmn
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:zeebe="http://camunda.org/schema/zeebe/1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:modeler="http://camunda.org/schema/modeler/1.0" id="Definitions_12fuprs" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="4.12.0" modeler:executionPlatform="Camunda Cloud" modeler:executionPlatformVersion="1.1.0">
<bpmn:process id="exclusive-gateway-with-condition-and-default" name="exclusive-gateway-with-condition-and-default" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>Flow_1y8jegt</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:sequenceFlow id="Flow_1y8jegt" sourceRef="StartEvent_1" targetRef="Gateway_01wr5g0" />
<bpmn:sequenceFlow id="price-gt-zero" name="price &#62; 0" sourceRef="Gateway_01wr5g0" targetRef="task-a">
<bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">price &gt; 0</bpmn:conditionExpression>
</bpmn:sequenceFlow>
<bpmn:serviceTask id="task-a" name="task-a">
<bpmn:extensionElements>
<zeebe:taskDefinition type="task-a" />
</bpmn:extensionElements>
<bpmn:incoming>price-gt-zero</bpmn:incoming>
<bpmn:outgoing>Flow_1moyr7v</bpmn:outgoing>
</bpmn:serviceTask>
<bpmn:sequenceFlow id="default" name="default" sourceRef="Gateway_01wr5g0" targetRef="task-b" />
<bpmn:serviceTask id="task-b" name="task-b">
<bpmn:extensionElements>
<zeebe:taskDefinition type="task-b" />
</bpmn:extensionElements>
<bpmn:incoming>default</bpmn:incoming>
<bpmn:outgoing>Flow_1dekydz</bpmn:outgoing>
</bpmn:serviceTask>
<bpmn:endEvent id="Event_196zxhe">
<bpmn:incoming>Flow_1dekydz</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_1dekydz" sourceRef="task-b" targetRef="Event_196zxhe" />
<bpmn:endEvent id="Event_1g3ipua">
<bpmn:incoming>Flow_1moyr7v</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="Flow_1moyr7v" sourceRef="task-a" targetRef="Event_1g3ipua" />
<bpmn:inclusiveGateway id="Gateway_01wr5g0">
<bpmn:incoming>Flow_1y8jegt</bpmn:incoming>
<bpmn:outgoing>price-gt-zero</bpmn:outgoing>
<bpmn:outgoing>default</bpmn:outgoing>
</bpmn:inclusiveGateway>
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="exclusive-gateway-with-condition-and-default">
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="179" y="152" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1y23oc8_di" bpmnElement="task-a">
<dc:Bounds x="360" y="40" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Activity_1wgex28_di" bpmnElement="task-b">
<dc:Bounds x="360" y="200" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_196zxhe_di" bpmnElement="Event_196zxhe">
<dc:Bounds x="512" y="222" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Event_1g3ipua_di" bpmnElement="Event_1g3ipua">
<dc:Bounds x="512" y="62" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Gateway_0nmf0kq_di" bpmnElement="Gateway_01wr5g0">
<dc:Bounds x="285" y="145" width="50" height="50" />
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="Flow_1y8jegt_di" bpmnElement="Flow_1y8jegt">
<di:waypoint x="215" y="170" />
<di:waypoint x="285" y="170" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_0gf8oc6_di" bpmnElement="price-gt-zero">
<di:waypoint x="310" y="145" />
<di:waypoint x="310" y="80" />
<di:waypoint x="360" y="80" />
<bpmndi:BPMNLabel>
<dc:Bounds x="305" y="110" width="43" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1cjigq1_di" bpmnElement="default">
<di:waypoint x="310" y="195" />
<di:waypoint x="310" y="240" />
<di:waypoint x="360" y="240" />
<bpmndi:BPMNLabel>
<dc:Bounds x="310" y="215" width="34" height="14" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1dekydz_di" bpmnElement="Flow_1dekydz">
<di:waypoint x="460" y="240" />
<di:waypoint x="512" y="240" />
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="Flow_1moyr7v_di" bpmnElement="Flow_1moyr7v">
<di:waypoint x="460" y="80" />
<di:waypoint x="512" y="80" />
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>
Loading
Loading