From 864acecb6b86eb23c49f26b41fa38451266c1a91 Mon Sep 17 00:00:00 2001 From: Tijs Rademakers Date: Fri, 17 Nov 2023 12:03:14 +0100 Subject: [PATCH] Add option to not create a repetitionCounter variable for repetition elements in CMMN --- .../cmmn/converter/CmmnXmlConstants.java | 1 + .../converter/RepetitionRuleXmlConverter.java | 6 ++ .../CreatePlanItemInstanceOperation.java | 5 +- ...temInstanceWithoutEvaluationOperation.java | 5 +- .../AbstractCmmnDynamicStateManager.java | 8 ++- .../impl/util/PlanItemInstanceUtil.java | 21 ++++-- .../test/itemcontrol/RepetitionRuleTest.java | 65 +++++++++++++++++++ .../RepetitionVariableAggregationTest.java | 54 +++++++++++++++ ...stPlanItemLocalVariablesIgnoreCounter.cmmn | 25 +++++++ ...lVariablesWithCollectionIgnoreCounter.cmmn | 25 +++++++ ...uentialRepeatingUserTaskIgnoreCounter.cmmn | 34 ++++++++++ .../flowable/cmmn/model/RepetitionRule.java | 9 +++ 12 files changed, 249 insertions(+), 9 deletions(-) create mode 100644 modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/itemcontrol/RepetitionRuleTest.testPlanItemLocalVariablesIgnoreCounter.cmmn create mode 100644 modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/itemcontrol/RepetitionRuleTest.testPlanItemLocalVariablesWithCollectionIgnoreCounter.cmmn create mode 100644 modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/itemcontrol/RepetitionVariableAggregationTest.testSequentialRepeatingUserTaskIgnoreCounter.cmmn diff --git a/modules/flowable-cmmn-converter/src/main/java/org/flowable/cmmn/converter/CmmnXmlConstants.java b/modules/flowable-cmmn-converter/src/main/java/org/flowable/cmmn/converter/CmmnXmlConstants.java index 24c6b48cd5a..4af92a96e7c 100644 --- a/modules/flowable-cmmn-converter/src/main/java/org/flowable/cmmn/converter/CmmnXmlConstants.java +++ b/modules/flowable-cmmn-converter/src/main/java/org/flowable/cmmn/converter/CmmnXmlConstants.java @@ -199,6 +199,7 @@ public interface CmmnXmlConstants { String ATTRIBUTE_TASK_COMPLETER_VARIABLE_NAME = "taskCompleterVariableName"; String ATTRIBUTE_REPETITION_COUNTER_VARIABLE_NAME = "counterVariable"; + String ATTRIBUTE_IGNORE_REPETITION_COUNTER_VARIABLE = "ignoreCounterVariable"; String ATTRIBUTE_REPETITION_MAX_INSTANCE_COUNT_NAME = "maxInstanceCount"; String ATTRIBUTE_REPETITION_COLLECTION_VARIABLE_NAME = "collectionVariable"; String ATTRIBUTE_REPETITION_ELEMENT_VARIABLE_NAME = "elementVariable"; diff --git a/modules/flowable-cmmn-converter/src/main/java/org/flowable/cmmn/converter/RepetitionRuleXmlConverter.java b/modules/flowable-cmmn-converter/src/main/java/org/flowable/cmmn/converter/RepetitionRuleXmlConverter.java index 921d9b993ce..2b2835971de 100644 --- a/modules/flowable-cmmn-converter/src/main/java/org/flowable/cmmn/converter/RepetitionRuleXmlConverter.java +++ b/modules/flowable-cmmn-converter/src/main/java/org/flowable/cmmn/converter/RepetitionRuleXmlConverter.java @@ -44,6 +44,12 @@ protected CmmnElement convert(XMLStreamReader xtr, ConversionHelper conversionHe repetitionRule.setRepetitionCounterVariableName(xtr.getAttributeValue(CmmnXmlConstants.FLOWABLE_EXTENSIONS_NAMESPACE, CmmnXmlConstants.ATTRIBUTE_REPETITION_COUNTER_VARIABLE_NAME)); + + + String ignoreRepetitionCounterVariableValue = xtr.getAttributeValue(CmmnXmlConstants.FLOWABLE_EXTENSIONS_NAMESPACE, CmmnXmlConstants.ATTRIBUTE_IGNORE_REPETITION_COUNTER_VARIABLE); + if ("true".equalsIgnoreCase(ignoreRepetitionCounterVariableValue)) { + repetitionRule.setIgnoreRepetitionCounterVariable(true); + } String maxInstanceCountValue = xtr.getAttributeValue(CmmnXmlConstants.FLOWABLE_EXTENSIONS_NAMESPACE, CmmnXmlConstants.ATTRIBUTE_REPETITION_MAX_INSTANCE_COUNT_NAME); if (maxInstanceCountValue == null) { diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/agenda/operation/CreatePlanItemInstanceOperation.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/agenda/operation/CreatePlanItemInstanceOperation.java index 90b75845106..98b1d7e88d8 100644 --- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/agenda/operation/CreatePlanItemInstanceOperation.java +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/agenda/operation/CreatePlanItemInstanceOperation.java @@ -56,7 +56,10 @@ protected void internalExecute() { planItemInstanceEntity.getParentVariableScope().setVariable(variableName, bpmnAggregation); } } - setRepetitionCounter(planItemInstanceEntity, repetitionCounter + 1); + + if (repetitionRule.getAggregations() != null || !PlanItemInstanceUtil.hasIgnoreCounterVariable(planItemInstanceEntity)) { + setRepetitionCounter(planItemInstanceEntity, repetitionCounter + 1); + } } CmmnHistoryManager cmmnHistoryManager = CommandContextUtil.getCmmnHistoryManager(commandContext); diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/agenda/operation/CreatePlanItemInstanceWithoutEvaluationOperation.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/agenda/operation/CreatePlanItemInstanceWithoutEvaluationOperation.java index 37820354b24..eee261686e7 100644 --- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/agenda/operation/CreatePlanItemInstanceWithoutEvaluationOperation.java +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/agenda/operation/CreatePlanItemInstanceWithoutEvaluationOperation.java @@ -58,7 +58,10 @@ public void run() { planItemInstanceEntity.getParentVariableScope().setVariable(variableName, bpmnAggregation); } } - setRepetitionCounter(planItemInstanceEntity, repetitionCounter + 1); + + if (repetitionRule.getAggregations() != null || !PlanItemInstanceUtil.hasIgnoreCounterVariable(planItemInstanceEntity)) { + setRepetitionCounter(planItemInstanceEntity, repetitionCounter + 1); + } } CmmnHistoryManager cmmnHistoryManager = CommandContextUtil.getCmmnHistoryManager(commandContext); diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/runtime/AbstractCmmnDynamicStateManager.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/runtime/AbstractCmmnDynamicStateManager.java index a300dfd0401..a14fe0594b2 100644 --- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/runtime/AbstractCmmnDynamicStateManager.java +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/runtime/AbstractCmmnDynamicStateManager.java @@ -67,6 +67,7 @@ import org.flowable.cmmn.model.PlanItem; import org.flowable.cmmn.model.PlanItemDefinition; import org.flowable.cmmn.model.ProcessTask; +import org.flowable.cmmn.model.RepetitionRule; import org.flowable.cmmn.model.Sentry; import org.flowable.cmmn.model.SentryIfPart; import org.flowable.cmmn.model.SentryOnPart; @@ -1139,8 +1140,11 @@ protected PlanItemInstanceEntity copyAndInsertPlanItemInstance(CommandContext co .create(); if (hasRepetitionRule(planItemInstanceEntityToCopy)) { - int counter = getRepetitionCounter(planItemInstanceEntityToCopy); - setRepetitionCounter(planItemInstanceEntity, counter); + RepetitionRule repetitionRule = planItemInstanceEntity.getPlanItem().getItemControl().getRepetitionRule(); + if (repetitionRule.getAggregations() != null || !repetitionRule.isIgnoreRepetitionCounterVariable()) { + int counter = getRepetitionCounter(planItemInstanceEntityToCopy); + setRepetitionCounter(planItemInstanceEntity, counter); + } } return planItemInstanceEntity; diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/util/PlanItemInstanceUtil.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/util/PlanItemInstanceUtil.java index e1256e56790..318a564f0cc 100644 --- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/util/PlanItemInstanceUtil.java +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/util/PlanItemInstanceUtil.java @@ -36,11 +36,16 @@ public static PlanItemInstanceEntity copyAndInsertPlanItemInstance(CommandContex Map localVariables, boolean addToParent, boolean silentNameExpressionEvaluation) { if (ExpressionUtil.hasRepetitionRule(planItemInstanceEntityToCopy)) { - int counter = getRepetitionCounter(planItemInstanceEntityToCopy); - if (localVariables == null) { - localVariables = new HashMap<>(0); + RepetitionRule repetitionRule = planItemInstanceEntityToCopy.getPlanItem().getItemControl().getRepetitionRule(); + + if (repetitionRule.getAggregations() != null || !repetitionRule.isIgnoreRepetitionCounterVariable()) { + int counter = getRepetitionCounter(planItemInstanceEntityToCopy); + if (localVariables == null) { + localVariables = new HashMap<>(0); + } + + localVariables.put(getCounterVariable(planItemInstanceEntityToCopy), counter); } - localVariables.put(getCounterVariable(planItemInstanceEntityToCopy), counter); } PlanItemInstance stagePlanItem = planItemInstanceEntityToCopy.getStagePlanItemInstanceEntity(); @@ -160,6 +165,10 @@ public static int getRepetitionCounter(PlanItemInstanceEntity repeatingPlanItemI return counter.intValue(); } } + + public static boolean hasIgnoreCounterVariable(PlanItemInstanceEntity repeatingPlanItemInstanceEntity) { + return repeatingPlanItemInstanceEntity.getPlanItem().getItemControl().getRepetitionRule().isIgnoreRepetitionCounterVariable(); + } public static String getCounterVariable(PlanItemInstanceEntity repeatingPlanItemInstanceEntity) { String repetitionCounterVariableName = repeatingPlanItemInstanceEntity.getPlanItem().getItemControl().getRepetitionRule().getRepetitionCounterVariableName(); @@ -184,7 +193,9 @@ protected static PlanItemInstanceEntity createPlanItemInstanceDuplicateForCollec CommandContextUtil.getAgenda(commandContext).planCreateRepeatedPlanItemInstanceOperation(childPlanItemInstanceEntity); // The repetition counter is 1 based - childPlanItemInstanceEntity.setVariableLocal(PlanItemInstanceUtil.getCounterVariable(childPlanItemInstanceEntity), index + 1); + if (repetitionRule.getAggregations() != null || !repetitionRule.isIgnoreRepetitionCounterVariable()) { + childPlanItemInstanceEntity.setVariableLocal(PlanItemInstanceUtil.getCounterVariable(childPlanItemInstanceEntity), index + 1); + } // createPlanItemInstance operations will also sync planItemInstance history CommandContextUtil.getAgenda(commandContext).planActivatePlanItemInstanceOperation(childPlanItemInstanceEntity, entryCriterionId); diff --git a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/itemcontrol/RepetitionRuleTest.java b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/itemcontrol/RepetitionRuleTest.java index 6ee5ae50dd2..945deb170e1 100644 --- a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/itemcontrol/RepetitionRuleTest.java +++ b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/itemcontrol/RepetitionRuleTest.java @@ -492,6 +492,7 @@ public void testPlanItemLocalVariablesWithCollection() { .start(); List tasks = cmmnTaskService.createTaskQuery() + .caseInstanceId(caseInstance.getId()) .orderByTaskPriority().asc() .list(); @@ -521,6 +522,43 @@ public void testPlanItemLocalVariablesWithCollection() { entry("initiator", null) ); } + + @Test + @CmmnDeployment + public void testPlanItemLocalVariablesWithCollectionIgnoreCounter() { + CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder() + .caseDefinitionKey("repeatingTask") + .transientVariable("myCollection", Arrays.asList("one", "two", "three")) + .start(); + + List tasks = cmmnTaskService.createTaskQuery() + .caseInstanceId(caseInstance.getId()) + .orderByTaskPriority().asc() + .list(); + + assertThat(tasks).hasSize(3); + + assertThat(cmmnTaskService.getVariables(tasks.get(0).getId())) + .containsOnly( + entry("item", "one"), + entry("itemIndex", 0), + entry("initiator", null) + ); + + assertThat(cmmnTaskService.getVariables(tasks.get(1).getId())) + .containsOnly( + entry("item", "two"), + entry("itemIndex", 1), + entry("initiator", null) + ); + + assertThat(cmmnTaskService.getVariables(tasks.get(2).getId())) + .containsOnly( + entry("item", "three"), + entry("itemIndex", 2), + entry("initiator", null) + ); + } @Test @CmmnDeployment @@ -564,6 +602,33 @@ public void testPlanItemLocalVariables() { assertCaseInstanceEnded(caseInstance); } + + @Test + @CmmnDeployment + public void testPlanItemLocalVariablesIgnoreCounter() { + CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder() + .caseDefinitionKey("repeatingTask") + .variable("initiator", "johndoe") + .start(); + + Task task = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).singleResult(); + assertThat(task).isNotNull(); + + assertThat(cmmnTaskService.getVariables(task.getId())) + .containsOnly( + entry("initiator", "johndoe") + ); + + cmmnTaskService.complete(task.getId()); + + task = cmmnTaskService.createTaskQuery().singleResult(); + assertThat(task).isNotNull(); + + assertThat(cmmnTaskService.getVariables(task.getId())) + .containsOnly( + entry("initiator", "johndoe") + ); + } @Test @CmmnDeployment diff --git a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/itemcontrol/RepetitionVariableAggregationTest.java b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/itemcontrol/RepetitionVariableAggregationTest.java index f4fbec75b69..8da00989b7d 100644 --- a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/itemcontrol/RepetitionVariableAggregationTest.java +++ b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/itemcontrol/RepetitionVariableAggregationTest.java @@ -75,6 +75,7 @@ public void testSequentialRepeatingUserTask() { Task task = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).taskName("My Task").singleResult(); + assertThat(cmmnTaskService.getVariable(task.getId(), "repetitionCounter")).isEqualTo(1); Map variables = new HashMap<>(); variables.put("approved", false); variables.put("description", "description task 0"); @@ -125,12 +126,14 @@ public void testSequentialRepeatingUserTask() { } task = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).taskName("My Task").singleResult(); + assertThat(cmmnTaskService.getVariable(task.getId(), "repetitionCounter")).isEqualTo(2); variables.put("approved", true); variables.put("description", "description task 1"); cmmnTaskService.setAssignee(task.getId(), "userTwo"); cmmnTaskService.complete(task.getId(), variables); task = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).taskName("My Task").singleResult(); + assertThat(cmmnTaskService.getVariable(task.getId(), "repetitionCounter")).isEqualTo(3); variables.put("approved", false); variables.put("description", "description task 2"); cmmnTaskService.setAssignee(task.getId(), "userThree"); @@ -167,6 +170,57 @@ public void testSequentialRepeatingUserTask() { } } + + @Test + @CmmnDeployment + public void testSequentialRepeatingUserTaskIgnoreCounter() { + CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder() + .caseDefinitionKey("repeatingTask") + .variable("nrOfLoops", 3) + .variable("otherVariable", "Hello World") + .start(); + + ArrayNode reviews = (ArrayNode) cmmnRuntimeService.getVariable(caseInstance.getId(), "reviews"); + + assertThatJson(reviews) + .isEqualTo("[" + + "{ userId: null }" + + "]"); + + Task task = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).taskName("My Task").singleResult(); + + assertThat(cmmnTaskService.getVariable(task.getId(), "repetitionCounter")).isEqualTo(1); + Map variables = new HashMap<>(); + variables.put("approved", false); + variables.put("description", "description task 0"); + cmmnTaskService.setAssignee(task.getId(), "userOne"); + + reviews = (ArrayNode) cmmnRuntimeService.getVariable(caseInstance.getId(), "reviews"); + + assertThatJson(reviews) + .isEqualTo("[" + + "{ userId: 'userOne' }" + + "]"); + + cmmnTaskService.complete(task.getId(), variables); + + reviews = (ArrayNode) cmmnRuntimeService.getVariable(caseInstance.getId(), "reviews"); + + assertThatJson(reviews) + .isEqualTo("[" + + "{ userId: 'userOne', approved : false, description : 'description task 0' }," + + "{ userId: null }" + + "]"); + + assertVariablesNotVisible(caseInstance); + + task = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).taskName("My Task").singleResult(); + assertThat(cmmnTaskService.getVariable(task.getId(), "repetitionCounter")).isEqualTo(2); + variables.put("approved", true); + variables.put("description", "description task 1"); + cmmnTaskService.setAssignee(task.getId(), "userTwo"); + cmmnTaskService.complete(task.getId(), variables); + } @Test @CmmnDeployment diff --git a/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/itemcontrol/RepetitionRuleTest.testPlanItemLocalVariablesIgnoreCounter.cmmn b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/itemcontrol/RepetitionRuleTest.testPlanItemLocalVariablesIgnoreCounter.cmmn new file mode 100644 index 00000000000..a42cb092767 --- /dev/null +++ b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/itemcontrol/RepetitionRuleTest.testPlanItemLocalVariablesIgnoreCounter.cmmn @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/itemcontrol/RepetitionRuleTest.testPlanItemLocalVariablesWithCollectionIgnoreCounter.cmmn b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/itemcontrol/RepetitionRuleTest.testPlanItemLocalVariablesWithCollectionIgnoreCounter.cmmn new file mode 100644 index 00000000000..af91862ae28 --- /dev/null +++ b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/itemcontrol/RepetitionRuleTest.testPlanItemLocalVariablesWithCollectionIgnoreCounter.cmmn @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/itemcontrol/RepetitionVariableAggregationTest.testSequentialRepeatingUserTaskIgnoreCounter.cmmn b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/itemcontrol/RepetitionVariableAggregationTest.testSequentialRepeatingUserTaskIgnoreCounter.cmmn new file mode 100644 index 00000000000..3e4ec0cd297 --- /dev/null +++ b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/itemcontrol/RepetitionVariableAggregationTest.testSequentialRepeatingUserTaskIgnoreCounter.cmmn @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/flowable-cmmn-model/src/main/java/org/flowable/cmmn/model/RepetitionRule.java b/modules/flowable-cmmn-model/src/main/java/org/flowable/cmmn/model/RepetitionRule.java index ff2545cbb02..75f17e97260 100644 --- a/modules/flowable-cmmn-model/src/main/java/org/flowable/cmmn/model/RepetitionRule.java +++ b/modules/flowable-cmmn-model/src/main/java/org/flowable/cmmn/model/RepetitionRule.java @@ -27,6 +27,7 @@ public class RepetitionRule extends PlanItemRule { public static final String DEFAULT_REPETITION_COUNTER_VARIABLE_NAME = "repetitionCounter"; protected String repetitionCounterVariableName; + protected boolean ignoreRepetitionCounterVariable; protected String collectionVariableName; protected String elementVariableName; protected String elementIndexVariableName; @@ -45,6 +46,14 @@ public void setRepetitionCounterVariableName(String repetitionCounterVariableNam this.repetitionCounterVariableName = repetitionCounterVariableName; } + public boolean isIgnoreRepetitionCounterVariable() { + return ignoreRepetitionCounterVariable; + } + + public void setIgnoreRepetitionCounterVariable(boolean ignoreRepetitionCounterVariable) { + this.ignoreRepetitionCounterVariable = ignoreRepetitionCounterVariable; + } + public String getCollectionVariableName() { return collectionVariableName; }