Skip to content

Commit 87f4b29

Browse files
committed
Fix NPE when having two sentries, and one doesn't have an ifPart
1 parent 4d16f91 commit 87f4b29

File tree

3 files changed

+89
-0
lines changed

3 files changed

+89
-0
lines changed

modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/agenda/operation/AbstractEvaluationCriteriaOperation.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,7 @@ protected Criterion evaluateCriteria(EntityWithSentryPartInstances entityWithSen
517517
satisfiedSentryOnPartIds.add(sentryPartInstanceEntity.getOnPartId());
518518

519519
} else if (sentryPartInstanceEntity.getIfPartId() != null
520+
&& sentry.getSentryIfPart() != null
520521
&& sentryPartInstanceEntity.getIfPartId().equals(sentry.getSentryIfPart().getId())) {
521522

522523
sentryIfPartSatisfied = true;

modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/EntryCriteriaTest.java

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,4 +395,48 @@ public void testRepeatingCrossBoundary3() {
395395

396396
}
397397

398+
@Test
399+
@CmmnDeployment
400+
public void testMultipleEntryCriteriaWithAndWithoutIfPart() {
401+
402+
// A plan item (Stage C) has two entry criteria:
403+
// - sentry1: two onParts (A complete + B complete), no ifPart
404+
// - sentry2: one onPart (B complete) + ifPart (myVar == true), default trigger mode (event deferred)
405+
//
406+
// The ifPart of sentry2 is evaluated proactively and stored as a
407+
// SentryPartInstanceEntity. When later evaluating sentry1 (which has no ifPart), the code iterates
408+
// through all satisfied sentry part instances for the plan item and must not NPE on the ifPart
409+
// instance from sentry2.
410+
411+
CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder()
412+
.caseDefinitionKey("myCase")
413+
.variable("myVar", true)
414+
.start();
415+
416+
List<Task> tasks = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).orderByTaskName().asc().list();
417+
assertThat(tasks)
418+
.extracting(Task::getName)
419+
.containsExactly("A", "B");
420+
421+
// Completing A triggers evaluation of sentry1 (no ifPart) while a satisfied ifPart instance
422+
// from sentry2 exists. Before the fix, this caused a NullPointerException.
423+
cmmnTaskService.complete(tasks.get(0).getId());
424+
425+
PlanItemInstance stageCInstance = cmmnRuntimeService.createPlanItemInstanceQuery()
426+
.planItemInstanceName("Stage C").singleResult();
427+
assertThat(stageCInstance.getState()).isEqualTo(PlanItemInstanceState.AVAILABLE);
428+
429+
// Completing B satisfies both sentries, activating Stage C
430+
Task taskB = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).singleResult();
431+
assertThat(taskB.getName()).isEqualTo("B");
432+
cmmnTaskService.complete(taskB.getId());
433+
434+
stageCInstance = cmmnRuntimeService.createPlanItemInstanceQuery()
435+
.planItemInstanceName("Stage C").singleResult();
436+
assertThat(stageCInstance.getState()).isEqualTo(PlanItemInstanceState.ACTIVE);
437+
438+
Task taskC = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).singleResult();
439+
assertThat(taskC.getName()).isEqualTo("C");
440+
}
441+
398442
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<definitions xmlns="http://www.omg.org/spec/CMMN/20151109/MODEL" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:flowable="http://flowable.org/cmmn"
3+
xmlns:cmmndi="http://www.omg.org/spec/CMMN/20151109/CMMNDI" xmlns:dc="http://www.omg.org/spec/CMMN/20151109/DC"
4+
xmlns:di="http://www.omg.org/spec/CMMN/20151109/DI" targetNamespace="http://www.flowable.org/casedef">
5+
<case id="myCase">
6+
<casePlanModel id="myPlanModel" name="My CasePlanModel">
7+
8+
<planItem id="planItem1" name="A" definitionRef="taskA"/>
9+
<planItem id="planItem2" name="B" definitionRef="taskB"/>
10+
<planItem id="planItem3" name="Stage C" definitionRef="stageC">
11+
<entryCriterion id="entry1" sentryRef="sentry1"/>
12+
<entryCriterion id="entry2" sentryRef="sentry2"/>
13+
</planItem>
14+
15+
<!-- Sentry with 2 onParts, NO ifPart -->
16+
<sentry id="sentry1">
17+
<planItemOnPart id="onPart1" sourceRef="planItem1">
18+
<standardEvent>complete</standardEvent>
19+
</planItemOnPart>
20+
<planItemOnPart id="onPart2" sourceRef="planItem2">
21+
<standardEvent>complete</standardEvent>
22+
</planItemOnPart>
23+
</sentry>
24+
25+
<!-- Sentry with 1 onPart + ifPart event deferred -->
26+
<sentry id="sentry2">
27+
<planItemOnPart id="onPart3" sourceRef="planItem2">
28+
<standardEvent>complete</standardEvent>
29+
</planItemOnPart>
30+
<ifPart>
31+
<condition><![CDATA[${myVar}]]></condition>
32+
</ifPart>
33+
</sentry>
34+
35+
<humanTask id="taskA" name="A"/>
36+
<humanTask id="taskB" name="B"/>
37+
<stage id="stageC" name="Stage C">
38+
<planItem id="planItem4" name="C" definitionRef="taskC"/>
39+
<humanTask id="taskC" name="C"/>
40+
</stage>
41+
42+
</casePlanModel>
43+
</case>
44+
</definitions>

0 commit comments

Comments
 (0)