Skip to content

Commit

Permalink
merge: #9747 #10023
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 



10023: Add note about community channels to general issue template r=oleschoenburg a=menski

## Description

Try to guide people for general questions to the community channel, to reduce the amount of questions in github issues.


Co-authored-by: skayliu <skay463@163.com>
Co-authored-by: Sebastian Menski <sebastian.menski@camunda.com>
  • Loading branch information
3 people committed Aug 8, 2022
3 parents 155de10 + f175b10 + a9e1ac2 commit 75c4aa4
Show file tree
Hide file tree
Showing 20 changed files with 1,059 additions and 23 deletions.
10 changes: 10 additions & 0 deletions .github/ISSUE_TEMPLATE/general_issue.md
Expand Up @@ -8,6 +8,16 @@ assignees: ''

---

<!--
In case you have questions about our software we encourage everyone to participate in our community via the
- Camunda Platform community forum https://forum.camunda.io/ or
- Slack https://camunda-cloud.slack.com/ (For invite: https://camunda-slack-invite.herokuapp.com/)
There you can exchange ideas with other Zeebe and Camunda Platform 8 users, as well as the product developers, and use the search to find answer to similar questions.
This issue template is used by the Zeebe engineers to create general tasks.
-->

**Description**

A clear and concise description of what this issue is about.
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
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);
}
}
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
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");
}
}
}
@@ -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");
}
}
}
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");
}
});
}
}
}
}
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
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
@@ -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>
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 75c4aa4

Please sign in to comment.