Skip to content

Commit b1e219a

Browse files
committed
Add FEEL support to timers
1 parent bf154b2 commit b1e219a

File tree

3 files changed

+167
-2
lines changed

3 files changed

+167
-2
lines changed

pkg/bpmn_engine/timer.go

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package bpmn_engine
22

33
import (
4+
"errors"
45
"fmt"
56
"strings"
67
"time"
@@ -104,7 +105,8 @@ func (state *BpmnEngineState) handleIntermediateTimerCatchEvent(instance *proces
104105
}
105106

106107
func (state *BpmnEngineState) createTimer(instance *processInstanceInfo, ice BPMN20.TIntermediateCatchEvent, originActivity activity) (*Timer, error) {
107-
durationVal, err := findDurationValue(ice)
108+
variableContext := instance.VariableHolder.Variables()
109+
durationVal, err := findDurationValue(ice, variableContext)
108110
if err != nil {
109111
return nil, &BpmnEngineError{Msg: fmt.Sprintf("Error parsing 'timeDuration' value "+
110112
"from element with ID=%s. Error:%s", ice.Id, err.Error())}
@@ -138,8 +140,28 @@ func findExistingTimerNotYetTriggered(state *BpmnEngineState, id string, instanc
138140
return t
139141
}
140142

141-
func findDurationValue(ice BPMN20.TIntermediateCatchEvent) (duration.Duration, error) {
143+
func findDurationValue(ice BPMN20.TIntermediateCatchEvent, variableContext map[string]interface{}) (duration.Duration, error) {
142144
durationStr := ice.TimerEventDefinition.TimeDuration.XMLText
145+
146+
// Check if it is expression
147+
if strings.HasPrefix(durationStr, "=") {
148+
v, err := evaluateExpression(durationStr, variableContext)
149+
if err != nil {
150+
return duration.Duration{}, &ExpressionEvaluationError{
151+
Msg: fmt.Sprintf("Error evaluating expression for timer id='%s' name='%s'", ice.Id, ice.Name),
152+
Err: err,
153+
}
154+
}
155+
if dur, ok := v.(string); ok {
156+
durationStr = dur
157+
} else {
158+
return duration.Duration{}, &ExpressionEvaluationError{
159+
Msg: fmt.Sprintf("Expression \"%s\" evaluated to a an invalid value for timer id='%s' name='%s'", durationStr, ice.Id, ice.Name),
160+
Err: errors.New("expression evaluated to an invalid type"),
161+
}
162+
}
163+
}
164+
143165
if len(strings.TrimSpace(durationStr)) == 0 {
144166
return duration.Duration{}, newEngineErrorf("Can't find 'timeDuration' value for INTERMEDIATE_CATCH_EVENT with id=%s", ice.Id)
145167
}

pkg/bpmn_engine/timer_test.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,29 @@ func Test_InvalidTimer_will_stop_execution_and_return_err(t *testing.T) {
4646
then.AssertThat(t, cp.CallPath, is.EqualTo(""))
4747
}
4848

49+
func Test_EventBasedGateway_selects_path_where_timer_occurs_from_expression(t *testing.T) {
50+
// setup
51+
bpmnEngine := New()
52+
cp := CallPath{}
53+
54+
// given
55+
process, _ := bpmnEngine.LoadFromFile("../../test-cases/message-intermediate-timer-event-expression.bpmn")
56+
bpmnEngine.NewTaskHandler().Id("task-for-message").Handler(cp.TaskHandler)
57+
bpmnEngine.NewTaskHandler().Id("task-for-timer").Handler(cp.TaskHandler)
58+
59+
variableContext := make(map[string]interface{})
60+
variableContext["timeoutValue"] = "PT1S"
61+
instance, _ := bpmnEngine.CreateAndRunInstance(process.ProcessKey, variableContext)
62+
63+
// when
64+
time.Sleep((1 * time.Second) + (1 * time.Millisecond))
65+
_, err := bpmnEngine.RunOrContinueInstance(instance.GetInstanceKey())
66+
then.AssertThat(t, err, is.Nil())
67+
68+
// then
69+
then.AssertThat(t, cp.CallPath, is.EqualTo("task-for-timer"))
70+
}
71+
4972
func Test_EventBasedGateway_selects_path_where_message_received(t *testing.T) {
5073
// setup
5174
bpmnEngine := New()
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<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_0igr2e0" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="5.33.1" modeler:executionPlatform="Camunda Cloud" modeler:executionPlatformVersion="1.1.0">
3+
<bpmn:process id="message-intermediate-timer-event" name="message-intermediate-timer-event" isExecutable="true">
4+
<bpmn:startEvent id="StartEvent_1">
5+
<bpmn:outgoing>Flow_18cznvu</bpmn:outgoing>
6+
</bpmn:startEvent>
7+
<bpmn:sequenceFlow id="Flow_18cznvu" sourceRef="StartEvent_1" targetRef="event-based-gateway" />
8+
<bpmn:endEvent id="Event_1nquajk">
9+
<bpmn:incoming>Flow_0o4yo3c</bpmn:incoming>
10+
<bpmn:incoming>Flow_065ojmy</bpmn:incoming>
11+
</bpmn:endEvent>
12+
<bpmn:sequenceFlow id="Flow_0o4yo3c" sourceRef="task-for-message" targetRef="Event_1nquajk" />
13+
<bpmn:sequenceFlow id="Flow_timer" sourceRef="event-based-gateway" targetRef="timer1" />
14+
<bpmn:intermediateCatchEvent id="timer1" name="1s">
15+
<bpmn:incoming>Flow_timer</bpmn:incoming>
16+
<bpmn:outgoing>Flow_1i9q20i</bpmn:outgoing>
17+
<bpmn:timerEventDefinition id="TimerEventDefinition_0r7onmv">
18+
<bpmn:timeDuration xsi:type="bpmn:tFormalExpression">=timeoutValue</bpmn:timeDuration>
19+
</bpmn:timerEventDefinition>
20+
</bpmn:intermediateCatchEvent>
21+
<bpmn:eventBasedGateway id="event-based-gateway" name="event-based-gateway">
22+
<bpmn:extensionElements />
23+
<bpmn:incoming>Flow_18cznvu</bpmn:incoming>
24+
<bpmn:outgoing>Flow_timer</bpmn:outgoing>
25+
<bpmn:outgoing>Flow_message</bpmn:outgoing>
26+
</bpmn:eventBasedGateway>
27+
<bpmn:intermediateCatchEvent id="message" name="message">
28+
<bpmn:incoming>Flow_message</bpmn:incoming>
29+
<bpmn:outgoing>Flow_1hqp7b9</bpmn:outgoing>
30+
<bpmn:messageEventDefinition id="MessageEventDefinition_1gyy1p0" messageRef="Message_1ah4t8u" />
31+
</bpmn:intermediateCatchEvent>
32+
<bpmn:sequenceFlow id="Flow_message" sourceRef="event-based-gateway" targetRef="message" />
33+
<bpmn:sequenceFlow id="Flow_1hqp7b9" sourceRef="message" targetRef="task-for-message" />
34+
<bpmn:sequenceFlow id="Flow_1i9q20i" sourceRef="timer1" targetRef="task-for-timer" />
35+
<bpmn:sequenceFlow id="Flow_065ojmy" sourceRef="task-for-timer" targetRef="Event_1nquajk" />
36+
<bpmn:serviceTask id="task-for-timer" name="task-for-timer">
37+
<bpmn:extensionElements>
38+
<zeebe:taskDefinition type="task-for-timer" />
39+
</bpmn:extensionElements>
40+
<bpmn:incoming>Flow_1i9q20i</bpmn:incoming>
41+
<bpmn:outgoing>Flow_065ojmy</bpmn:outgoing>
42+
</bpmn:serviceTask>
43+
<bpmn:serviceTask id="task-for-message" name="task-for-message">
44+
<bpmn:extensionElements>
45+
<zeebe:taskDefinition type="task-for-message" />
46+
</bpmn:extensionElements>
47+
<bpmn:incoming>Flow_1hqp7b9</bpmn:incoming>
48+
<bpmn:outgoing>Flow_0o4yo3c</bpmn:outgoing>
49+
</bpmn:serviceTask>
50+
</bpmn:process>
51+
<bpmn:message id="Message_1ah4t8u" name="message">
52+
<bpmn:extensionElements>
53+
<zeebe:subscription correlationKey="=message" />
54+
</bpmn:extensionElements>
55+
</bpmn:message>
56+
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
57+
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="message-intermediate-timer-event">
58+
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
59+
<dc:Bounds x="179" y="99" width="36" height="36" />
60+
</bpmndi:BPMNShape>
61+
<bpmndi:BPMNShape id="Event_1nquajk_di" bpmnElement="Event_1nquajk">
62+
<dc:Bounds x="582" y="99" width="36" height="36" />
63+
</bpmndi:BPMNShape>
64+
<bpmndi:BPMNShape id="Event_0mmdh8g_di" bpmnElement="timer1">
65+
<dc:Bounds x="352" y="212" width="36" height="36" />
66+
<bpmndi:BPMNLabel>
67+
<dc:Bounds x="365" y="255" width="13" height="14" />
68+
</bpmndi:BPMNLabel>
69+
</bpmndi:BPMNShape>
70+
<bpmndi:BPMNShape id="Gateway_1vqo74e_di" bpmnElement="event-based-gateway">
71+
<dc:Bounds x="265" y="92" width="50" height="50" />
72+
<bpmndi:BPMNLabel>
73+
<dc:Bounds x="259" y="62" width="65" height="27" />
74+
</bpmndi:BPMNLabel>
75+
</bpmndi:BPMNShape>
76+
<bpmndi:BPMNShape id="Event_0m8tkc5_di" bpmnElement="message">
77+
<dc:Bounds x="352" y="99" width="36" height="36" />
78+
<bpmndi:BPMNLabel>
79+
<dc:Bounds x="348" y="142" width="45" height="14" />
80+
</bpmndi:BPMNLabel>
81+
</bpmndi:BPMNShape>
82+
<bpmndi:BPMNShape id="Activity_02sw2a3_di" bpmnElement="task-for-timer">
83+
<dc:Bounds x="430" y="190" width="100" height="80" />
84+
</bpmndi:BPMNShape>
85+
<bpmndi:BPMNShape id="Activity_0bb82tt_di" bpmnElement="task-for-message">
86+
<dc:Bounds x="430" y="77" width="100" height="80" />
87+
</bpmndi:BPMNShape>
88+
<bpmndi:BPMNEdge id="Flow_18cznvu_di" bpmnElement="Flow_18cznvu">
89+
<di:waypoint x="215" y="117" />
90+
<di:waypoint x="265" y="117" />
91+
</bpmndi:BPMNEdge>
92+
<bpmndi:BPMNEdge id="Flow_0o4yo3c_di" bpmnElement="Flow_0o4yo3c">
93+
<di:waypoint x="530" y="117" />
94+
<di:waypoint x="582" y="117" />
95+
</bpmndi:BPMNEdge>
96+
<bpmndi:BPMNEdge id="Flow_1rjxdq2_di" bpmnElement="Flow_timer">
97+
<di:waypoint x="290" y="142" />
98+
<di:waypoint x="290" y="230" />
99+
<di:waypoint x="352" y="230" />
100+
</bpmndi:BPMNEdge>
101+
<bpmndi:BPMNEdge id="Flow_0dy38oj_di" bpmnElement="Flow_message">
102+
<di:waypoint x="315" y="117" />
103+
<di:waypoint x="352" y="117" />
104+
</bpmndi:BPMNEdge>
105+
<bpmndi:BPMNEdge id="Flow_1hqp7b9_di" bpmnElement="Flow_1hqp7b9">
106+
<di:waypoint x="388" y="117" />
107+
<di:waypoint x="430" y="117" />
108+
</bpmndi:BPMNEdge>
109+
<bpmndi:BPMNEdge id="Flow_1i9q20i_di" bpmnElement="Flow_1i9q20i">
110+
<di:waypoint x="388" y="230" />
111+
<di:waypoint x="430" y="230" />
112+
</bpmndi:BPMNEdge>
113+
<bpmndi:BPMNEdge id="Flow_065ojmy_di" bpmnElement="Flow_065ojmy">
114+
<di:waypoint x="530" y="230" />
115+
<di:waypoint x="600" y="230" />
116+
<di:waypoint x="600" y="135" />
117+
</bpmndi:BPMNEdge>
118+
</bpmndi:BPMNPlane>
119+
</bpmndi:BPMNDiagram>
120+
</bpmn:definitions>

0 commit comments

Comments
 (0)