Skip to content

Commit

Permalink
Merge pull request #204 from aamirmousavi/main
Browse files Browse the repository at this point in the history
add feature - Inclusive Gateway element
  • Loading branch information
nitram509 authored May 22, 2024
2 parents 0aec32e + 3f1e6ea commit 91ca79a
Show file tree
Hide file tree
Showing 9 changed files with 326 additions and 1 deletion.
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

0 comments on commit 91ca79a

Please sign in to comment.