Skip to content

Commit

Permalink
merge: #9747
Browse files Browse the repository at this point in the history
9747: Support diverging inclusive gateway r=korthout a=skayliu

## Description

<!-- Please explain the changes you made here. -->
This PR adds support for the diverging (i.e. splitting, forking) inclusive gateway.
<img width="50%" alt="Screen Shot 2022-08-05 at 19 59 33" src="https://user-images.githubusercontent.com/3511026/183134257-7b62393c-eb51-4b06-9651-de834ebc2741.png">

When the inclusive gateway is entered:
- The condition of each outgoing sequence flow (of the inclusive gateway) is evaluated, and
  - if `true` that sequence flow will be taken. 
  - if none of the conditions evaluates to `true`, the _default_ flow is taken.
  - if none of the conditions evaluates to `true`, and no flow is set as _default_, then an incident is raised.
- Depending on the conditions, any combination of the flows can be taken.
- If the inclusive gateway only has one outgoing sequence flow, then it does not need to have a condition.

For example, when the conditions of flows `a` and `b` evaluate to `true`, then flows `a` and `b` are taken and their targets activated.

<img width="50%" alt="Screen Shot 2022-08-05 at 20 14 55" src="https://user-images.githubusercontent.com/3511026/183137053-a02a6694-3217-465a-8b87-41fb048cb2f2.png">

If none of the conditions evaluates to `true`, then the _default_ flow is taken.

<img width="50%" alt="Screen Shot 2022-08-05 at 20 17 20" src="https://user-images.githubusercontent.com/3511026/183137419-3ea72405-59bf-4c2f-beb3-ef1e26369e0c.png">


Related docs:
- [conditions](https://docs.camunda.io/docs/components/modeler/bpmn/exclusive-gateways/#conditions) (for exclusive gateway)
- [incidents](https://docs.camunda.io/docs/components/concepts/incidents/)

## Out of scope

This PR does not add support for the converging (i.e. merging, joining) inclusive gateway.
- The diverging inclusive gateway adds a lot of value, even without the converging behavior. 
- In addition, a combination of parallel and exclusive gateways can be used to merge the flows again.

## Related issues

<!-- Which issues are closed by this PR or are related -->
 
closes #6018 



Co-authored-by: skayliu <skay463@163.com>
  • Loading branch information
zeebe-bors-camunda[bot] and skayliu committed Aug 8, 2022
2 parents 26c8cca + f175b10 commit df1727f
Show file tree
Hide file tree
Showing 19 changed files with 1,049 additions and 23 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,10 @@ public AbstractExclusiveGatewayBuilder<?> moveToLastExclusiveGateway() {
return findLastGateway(ExclusiveGateway.class).builder();
}

public AbstractInclusiveGatewayBuilder<?> moveToLastInclusiveGateway() {
return findLastGateway(InclusiveGateway.class).builder();
}

public AbstractFlowNodeBuilder<?, ?> moveToNode(final String identifier) {
final ModelElementInstance instance = modelInstance.getModelElementById(identifier);
if (instance instanceof FlowNode) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,13 @@ public B defaultFlow(final SequenceFlow sequenceFlow) {
element.setDefault(sequenceFlow);
return myself;
}
/**
* Sets the current flow as the default flow.
*
* @return the builder
*/
public B defaultFlow() {
final SequenceFlow sequenceFlow = getCurrentSequenceFlowBuilder().getElement();
return defaultFlow(sequenceFlow);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import io.camunda.zeebe.model.bpmn.instance.EventBasedGateway;
import io.camunda.zeebe.model.bpmn.instance.ExclusiveGateway;
import io.camunda.zeebe.model.bpmn.instance.FlowElement;
import io.camunda.zeebe.model.bpmn.instance.InclusiveGateway;
import io.camunda.zeebe.model.bpmn.instance.IntermediateCatchEvent;
import io.camunda.zeebe.model.bpmn.instance.IntermediateThrowEvent;
import io.camunda.zeebe.model.bpmn.instance.ManualTask;
Expand Down Expand Up @@ -54,6 +55,7 @@ public class FlowElementValidator implements ModelElementValidator<FlowElement>
SUPPORTED_ELEMENT_TYPES.add(EndEvent.class);
SUPPORTED_ELEMENT_TYPES.add(EventBasedGateway.class);
SUPPORTED_ELEMENT_TYPES.add(ExclusiveGateway.class);
SUPPORTED_ELEMENT_TYPES.add(InclusiveGateway.class);
SUPPORTED_ELEMENT_TYPES.add(IntermediateCatchEvent.class);
SUPPORTED_ELEMENT_TYPES.add(ParallelGateway.class);
SUPPORTED_ELEMENT_TYPES.add(ReceiveTask.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import io.camunda.zeebe.model.bpmn.instance.ExclusiveGateway;
import io.camunda.zeebe.model.bpmn.instance.FlowNode;
import io.camunda.zeebe.model.bpmn.instance.InclusiveGateway;
import org.camunda.bpm.model.xml.validation.ModelElementValidator;
import org.camunda.bpm.model.xml.validation.ValidationResultCollector;

Expand All @@ -30,17 +31,16 @@ public Class<FlowNode> getElementType() {
@Override
public void validate(
final FlowNode element, final ValidationResultCollector validationResultCollector) {
if (!(element instanceof ExclusiveGateway)) {
final boolean hasAnyConditionalFlow =
element.getOutgoing().stream()
.filter(s -> s.getConditionExpression() != null)
.findAny()
.isPresent();
if (element instanceof ExclusiveGateway || element instanceof InclusiveGateway) {
return;
}

final boolean hasAnyConditionalFlow =
element.getOutgoing().stream().anyMatch(s -> s.getConditionExpression() != null);

if (hasAnyConditionalFlow) {
validationResultCollector.addError(
0, "Conditional sequence flows are only supported at exclusive gateway");
}
if (hasAnyConditionalFlow) {
validationResultCollector.addError(
0, "Conditional sequence flows are only supported at exclusive or inclusive gateway");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright © 2017 camunda services GmbH (info@camunda.com)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.camunda.zeebe.model.bpmn.validation.zeebe;

import io.camunda.zeebe.model.bpmn.instance.InclusiveGateway;
import io.camunda.zeebe.model.bpmn.instance.SequenceFlow;
import org.camunda.bpm.model.xml.validation.ModelElementValidator;
import org.camunda.bpm.model.xml.validation.ValidationResultCollector;

public class InclusiveGatewayValidator implements ModelElementValidator<InclusiveGateway> {

@Override
public Class<InclusiveGateway> getElementType() {
return InclusiveGateway.class;
}

@Override
public void validate(
final InclusiveGateway element, final ValidationResultCollector validationResultCollector) {

final SequenceFlow defaultFlow = element.getDefault();
final int size = element.getIncoming().size();
if (defaultFlow != null) {
if (defaultFlow.getConditionExpression() == null && size > 1) {
validationResultCollector.addError(
0, "Must have a condition even if it's marked as the default flow");
}
if (defaultFlow.getSource() != element) {
validationResultCollector.addError(0, "Default flow must start at gateway");
}
}
if (size > 1) {
validationResultCollector.addError(
0, "Currently the inclusive gateway can only have one incoming sequence flow");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@
package io.camunda.zeebe.model.bpmn.validation.zeebe;

import io.camunda.zeebe.model.bpmn.instance.ExclusiveGateway;
import io.camunda.zeebe.model.bpmn.instance.InclusiveGateway;
import io.camunda.zeebe.model.bpmn.instance.SequenceFlow;
import java.util.Optional;
import org.camunda.bpm.model.xml.validation.ModelElementValidator;
import org.camunda.bpm.model.xml.validation.ValidationResultCollector;

Expand All @@ -39,5 +41,25 @@ public void validate(
validationResultCollector.addError(0, "Must have a condition or be default flow");
}
}

if (element.getSource() instanceof InclusiveGateway) {
final InclusiveGateway gateway = (InclusiveGateway) element.getSource();
if (gateway.getOutgoing().size() > 1) {
final Optional<SequenceFlow> sequenceFlow =
gateway.getOutgoing().stream()
.filter(x -> x.getConditionExpression() == null && x == element)
.findFirst();

sequenceFlow.ifPresent(
out -> {
if (gateway.getDefault() != element) {
validationResultCollector.addError(0, "Must have a condition");
} else {
validationResultCollector.addError(
0, "Must have a condition even if it's marked as the default flow");
}
});
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public final class ZeebeDesignTimeValidators {
validators.add(new EventBasedGatewayValidator());
validators.add(new ErrorEventDefinitionValidator());
validators.add(new ExclusiveGatewayValidator());
validators.add(new InclusiveGatewayValidator());
validators.add(new FlowElementValidator());
validators.add(new FlowNodeValidator());
validators.add(new IntermediateCatchEventValidator());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,54 @@ public static Object[][] parameters() {
.done(),
singletonList(expect("flow1", "Must have a condition or be default flow"))
},
{
Bpmn.createExecutableProcess("process")
.startEvent()
.inclusiveGateway("gateway")
.sequenceFlowId("flow1")
.endEvent()
.moveToLastInclusiveGateway()
.sequenceFlowId("flow2")
.conditionExpression("condition")
.endEvent()
.done(),
singletonList(expect("flow1", "Must have a condition"))
},
{
Bpmn.createExecutableProcess("process")
.startEvent()
.inclusiveGateway("gateway")
.defaultFlow()
.sequenceFlowId("flow1")
.endEvent()
.moveToLastInclusiveGateway()
.sequenceFlowId("flow2")
.conditionExpression("condition")
.endEvent()
.done(),
singletonList(
expect("flow1", "Must have a condition even if it's marked as the default flow"))
},
{
Bpmn.createExecutableProcess("process")
.startEvent("start")
.inclusiveGateway("fork")
.defaultFlow()
.sequenceFlowId("flow1")
.conditionExpression("= contains(str,\"a\")")
.serviceTask("task1", b -> b.zeebeJobType("type1"))
.inclusiveGateway("join")
.endEvent("end")
.moveToNode("fork")
.sequenceFlowId("flow2")
.conditionExpression("= contains(str,\"b\")")
.serviceTask("task2", b -> b.zeebeJobType("type2"))
.connectTo("join")
.done(),
singletonList(
expect(
"join", "Currently the inclusive gateway can only have one incoming sequence flow"))
},
{
Bpmn.createExecutableProcess("process")
.startEvent()
Expand All @@ -51,6 +99,10 @@ public static Object[][] parameters() {
singletonList(expect("gateway", "Default flow must not have a condition"))
},
{"default-flow.bpmn", singletonList(expect("gateway", "Default flow must start at gateway"))},
{
"default-flow-inclusive-gateway.bpmn",
singletonList(expect("inclusiveGateway", "Default flow must start at gateway"))
},
{
Bpmn.createExecutableProcess("process")
.startEvent("start")
Expand All @@ -59,7 +111,9 @@ public static Object[][] parameters() {
.endEvent()
.done(),
singletonList(
expect("task", "Conditional sequence flows are only supported at exclusive gateway"))
expect(
"task",
"Conditional sequence flows are only supported at exclusive or inclusive gateway"))
},
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?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:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" id="Definitions_1" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="1.11.2">
<bpmn:process id="Process_1" isExecutable="true">
<bpmn:startEvent id="StartEvent_1">
<bpmn:outgoing>SequenceFlow_0s7tk8n</bpmn:outgoing>
</bpmn:startEvent>
<bpmn:inclusiveGateway id="inclusiveGateway" default="SequenceFlow_0s7tk8n">
<bpmn:incoming>SequenceFlow_0s7tk8n</bpmn:incoming>
<bpmn:outgoing>SequenceFlow_1hlr3xw</bpmn:outgoing>
</bpmn:inclusiveGateway>
<bpmn:endEvent id="EndEvent_1lg4ndn">
<bpmn:incoming>SequenceFlow_1hlr3xw</bpmn:incoming>
</bpmn:endEvent>
<bpmn:sequenceFlow id="SequenceFlow_1hlr3xw" sourceRef="inclusiveGateway" targetRef="EndEvent_1lg4ndn">
<bpmn:conditionExpression xsi:type="bpmn:tFormalExpression">$.foo == true</bpmn:conditionExpression>
</bpmn:sequenceFlow>
<bpmn:sequenceFlow id="SequenceFlow_0s7tk8n" sourceRef="StartEvent_1" targetRef="inclusiveGateway" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
<dc:Bounds x="173" y="102" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="InclusiveGateway_0dqu8hv_di" bpmnElement="inclusiveGateway" isMarkerVisible="true">
<dc:Bounds x="277" y="95" width="50" height="50" />
<bpmndi:BPMNLabel>
<dc:Bounds x="302" y="149" width="0" height="12" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="EndEvent_1lg4ndn_di" bpmnElement="EndEvent_1lg4ndn">
<dc:Bounds x="396" y="102" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="414" y="142" width="0" height="12" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNShape>
<bpmndi:BPMNEdge id="SequenceFlow_1hlr3xw_di" bpmnElement="SequenceFlow_1hlr3xw">
<di:waypoint xsi:type="dc:Point" x="327" y="120" />
<di:waypoint xsi:type="dc:Point" x="396" y="120" />
<bpmndi:BPMNLabel>
<dc:Bounds x="361.5" y="99" width="0" height="12" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
<bpmndi:BPMNEdge id="SequenceFlow_0s7tk8n_di" bpmnElement="SequenceFlow_0s7tk8n">
<di:waypoint xsi:type="dc:Point" x="209" y="120" />
<di:waypoint xsi:type="dc:Point" x="277" y="120" />
<bpmndi:BPMNLabel>
<dc:Bounds x="243" y="99" width="0" height="12" />
</bpmndi:BPMNLabel>
</bpmndi:BPMNEdge>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
</bpmn:definitions>
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import io.camunda.zeebe.engine.processing.bpmn.event.StartEventProcessor;
import io.camunda.zeebe.engine.processing.bpmn.gateway.EventBasedGatewayProcessor;
import io.camunda.zeebe.engine.processing.bpmn.gateway.ExclusiveGatewayProcessor;
import io.camunda.zeebe.engine.processing.bpmn.gateway.InclusiveGatewayProcessor;
import io.camunda.zeebe.engine.processing.bpmn.gateway.ParallelGatewayProcessor;
import io.camunda.zeebe.engine.processing.bpmn.task.BusinessRuleTaskProcessor;
import io.camunda.zeebe.engine.processing.bpmn.task.JobWorkerTaskProcessor;
Expand Down Expand Up @@ -51,6 +52,7 @@ public BpmnElementProcessors(final BpmnBehaviors bpmnBehaviors) {
processors.put(BpmnElementType.PARALLEL_GATEWAY, new ParallelGatewayProcessor(bpmnBehaviors));
processors.put(
BpmnElementType.EVENT_BASED_GATEWAY, new EventBasedGatewayProcessor(bpmnBehaviors));
processors.put(BpmnElementType.INCLUSIVE_GATEWAY, new InclusiveGatewayProcessor(bpmnBehaviors));

// containers
processors.put(BpmnElementType.PROCESS, new ProcessProcessor(bpmnBehaviors));
Expand Down

0 comments on commit df1727f

Please sign in to comment.