From 685780c03c6f2470d244d0b37e046cc0a5617ff0 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Thu, 8 Feb 2024 13:59:38 +0100 Subject: [PATCH 01/15] Upgrade MyBatis to 3.15.15 and MyBatis Spring to 3.0.3 --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index d8ee6d15faa..3b55b510bc9 100644 --- a/pom.xml +++ b/pom.xml @@ -293,12 +293,12 @@ org.mybatis mybatis - 3.5.11 + 3.5.15 org.mybatis mybatis-spring - 3.0.0 + 3.0.3 org.mockito From 5f56cc4cd688c20dc38a06a3cd5776763742a9c0 Mon Sep 17 00:00:00 2001 From: Pavel Horal Date: Thu, 8 Feb 2024 09:51:10 +0100 Subject: [PATCH 02/15] Fix broken OSGi wiring (#3844) --- modules/flowable-batch-service/pom.xml | 3 +++ modules/flowable-entitylink-service/pom.xml | 3 +++ modules/flowable-eventsubscription-service/pom.xml | 3 +++ modules/flowable-identitylink-service/pom.xml | 3 +++ modules/flowable-job-service/pom.xml | 7 ++++--- modules/flowable-task-service/pom.xml | 3 +++ modules/flowable-variable-service/pom.xml | 1 + 7 files changed, 20 insertions(+), 3 deletions(-) diff --git a/modules/flowable-batch-service/pom.xml b/modules/flowable-batch-service/pom.xml index ef9d18327e5..c7dd15fe9b3 100755 --- a/modules/flowable-batch-service/pom.xml +++ b/modules/flowable-batch-service/pom.xml @@ -89,6 +89,9 @@ org.flowable.batch.service.db.mapping.entity + + org.springframework*;resolution:=optional + diff --git a/modules/flowable-entitylink-service/pom.xml b/modules/flowable-entitylink-service/pom.xml index 3a179018067..a2e5d3707fe 100755 --- a/modules/flowable-entitylink-service/pom.xml +++ b/modules/flowable-entitylink-service/pom.xml @@ -94,6 +94,9 @@ org.flowable.entitylink.service.db.mapping.entity + + org.springframework*;resolution:=optional + diff --git a/modules/flowable-eventsubscription-service/pom.xml b/modules/flowable-eventsubscription-service/pom.xml index 6622c802bc1..d6bd9a14b4e 100755 --- a/modules/flowable-eventsubscription-service/pom.xml +++ b/modules/flowable-eventsubscription-service/pom.xml @@ -98,6 +98,9 @@ org.flowable.eventsubscription.service.db.mapping.entity + + org.springframework*;resolution:=optional + diff --git a/modules/flowable-identitylink-service/pom.xml b/modules/flowable-identitylink-service/pom.xml index 951fe638235..6aec9a0b812 100755 --- a/modules/flowable-identitylink-service/pom.xml +++ b/modules/flowable-identitylink-service/pom.xml @@ -94,6 +94,9 @@ org.flowable.identitylink.service.db.mapping.entity + + org.springframework*;resolution:=optional + diff --git a/modules/flowable-job-service/pom.xml b/modules/flowable-job-service/pom.xml index 22bad64cdbd..6b9c69dd079 100755 --- a/modules/flowable-job-service/pom.xml +++ b/modules/flowable-job-service/pom.xml @@ -104,9 +104,10 @@ org.flowable.job.service.db.mapping.entity - - jakarta.enterprise.concurrent;resolution:=optional, - + + jakarta.enterprise.concurrent;resolution:=optional, + org.springframework*;resolution:=optional + diff --git a/modules/flowable-task-service/pom.xml b/modules/flowable-task-service/pom.xml index a6e5f184d08..0cd7214d797 100755 --- a/modules/flowable-task-service/pom.xml +++ b/modules/flowable-task-service/pom.xml @@ -98,6 +98,9 @@ org.flowable.task.service.db.mapping.entity + + org.springframework*;resolution:=optional + diff --git a/modules/flowable-variable-service/pom.xml b/modules/flowable-variable-service/pom.xml index 3117f39bd9a..6e8fb357608 100755 --- a/modules/flowable-variable-service/pom.xml +++ b/modules/flowable-variable-service/pom.xml @@ -96,6 +96,7 @@ jakarta.persistence*;resolution:=optional, + org.springframework*;resolution:=optional From 98fde0c00227ffd541906fbb19f9018c882bc32a Mon Sep 17 00:00:00 2001 From: Tijs Rademakers Date: Thu, 8 Feb 2024 21:51:02 +0100 Subject: [PATCH 03/15] Support listening to a variable change event with multiple elements in bpmn and cmmn --- ...aluateVariableEventListenersOperation.java | 10 +-- .../VariableEventListenerTest.java | 68 +++++++++++++++++++ ...TriggerMultipleVariableEventListeners.cmmn | 33 +++++++++ .../VariableListenerSessionData.java | 12 +++- ...ableListenerEventDefinitionsOperation.java | 6 +- .../variable/VariableListenerEventTest.java | 22 ++++++ ....multipleCatchVariableListeners.bpmn20.xml | 51 ++++++++++++++ 7 files changed, 195 insertions(+), 7 deletions(-) create mode 100644 modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/eventlistener/VariableEventListenerTest.testTriggerMultipleVariableEventListeners.cmmn create mode 100644 modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/variable/VariableListenerEventTest.multipleCatchVariableListeners.bpmn20.xml diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/agenda/operation/EvaluateVariableEventListenersOperation.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/agenda/operation/EvaluateVariableEventListenersOperation.java index c5ef2fec146..737b716b092 100644 --- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/agenda/operation/EvaluateVariableEventListenersOperation.java +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/agenda/operation/EvaluateVariableEventListenersOperation.java @@ -93,10 +93,12 @@ public void run() { if (changeTypeValue.equals(variableListenerData.getChangeType()) || VariableListenerEventDefinition.CHANGE_TYPE_ALL.equals(changeTypeValue) || (VariableListenerEventDefinition.CHANGE_TYPE_UPDATE_CREATE.equals(changeTypeValue) && (VariableListenerEventDefinition.CHANGE_TYPE_CREATE.equals(variableListenerData.getChangeType()) || VariableListenerEventDefinition.CHANGE_TYPE_UPDATE.equals(variableListenerData.getChangeType())))) { - - itVariableListener.remove(); - CommandContextUtil.getAgenda().planTriggerPlanItemInstanceOperation(planItemInstance); - triggeredPlanItemInstance = true; + + if (!variableListenerData.containsProcessedElementId(planItemInstance.getPlanItemDefinitionId())) { + CommandContextUtil.getAgenda().planTriggerPlanItemInstanceOperation(planItemInstance); + triggeredPlanItemInstance = true; + variableListenerData.addProcessedElementId(planItemInstance.getPlanItemDefinitionId()); + } } } } diff --git a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/eventlistener/VariableEventListenerTest.java b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/eventlistener/VariableEventListenerTest.java index 1d08e3b7f26..ce4761f5605 100644 --- a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/eventlistener/VariableEventListenerTest.java +++ b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/eventlistener/VariableEventListenerTest.java @@ -196,6 +196,74 @@ public void testTriggerVariableEventListenerInStageOnlyCreate() { assertCaseInstanceEnded(caseInstance); } + + @Test + @CmmnDeployment + public void testTriggerMultipleVariableEventListeners() { + CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder().caseDefinitionKey("variableListener").start(); + + // 5 plan items reachable + assertThat(cmmnRuntimeService.createPlanItemInstanceQuery().count()).isEqualTo(5); + assertThat(cmmnRuntimeService.createPlanItemInstanceQuery().list()) + .extracting(PlanItemInstance::getPlanItemDefinitionType, PlanItemInstance::getPlanItemDefinitionId, PlanItemInstance::getState) + .containsExactlyInAnyOrder( + tuple(PlanItemDefinitionType.VARIABLE_EVENT_LISTENER, "variableEventListener", PlanItemInstanceState.AVAILABLE), + tuple(PlanItemDefinitionType.VARIABLE_EVENT_LISTENER, "variableEventListener2", PlanItemInstanceState.AVAILABLE), + tuple(PlanItemDefinitionType.HUMAN_TASK, "taskA", PlanItemInstanceState.ACTIVE), + tuple(PlanItemDefinitionType.HUMAN_TASK, "taskB", PlanItemInstanceState.AVAILABLE), + tuple(PlanItemDefinitionType.HUMAN_TASK, "taskC", PlanItemInstanceState.AVAILABLE) + ); + + if (CmmnHistoryTestHelper.isHistoryLevelAtLeast(HistoryLevel.ACTIVITY, cmmnEngineConfiguration)) { + assertThat(cmmnHistoryService.createHistoricPlanItemInstanceQuery().list()) + .extracting(HistoricPlanItemInstance::getPlanItemDefinitionType, HistoricPlanItemInstance::getPlanItemDefinitionId, HistoricPlanItemInstance::getState) + .containsExactlyInAnyOrder( + tuple(PlanItemDefinitionType.VARIABLE_EVENT_LISTENER, "variableEventListener", PlanItemInstanceState.AVAILABLE), + tuple(PlanItemDefinitionType.VARIABLE_EVENT_LISTENER, "variableEventListener2", PlanItemInstanceState.AVAILABLE), + tuple(PlanItemDefinitionType.HUMAN_TASK, "taskA", PlanItemInstanceState.ACTIVE), + tuple(PlanItemDefinitionType.HUMAN_TASK, "taskB", PlanItemInstanceState.AVAILABLE), + tuple(PlanItemDefinitionType.HUMAN_TASK, "taskC", PlanItemInstanceState.AVAILABLE) + ); + } + + // create different variable + cmmnRuntimeService.setVariable(caseInstance.getId(), "var2", "test"); + + assertThat(cmmnRuntimeService.createPlanItemInstanceQuery().planItemDefinitionType(PlanItemDefinitionType.VARIABLE_EVENT_LISTENER).count()).isEqualTo(2); + + // create var1 variable to trigger variable event listener + cmmnRuntimeService.setVariable(caseInstance.getId(), "var1", "test"); + + // variable event listener should be completed + assertThat(cmmnRuntimeService.createPlanItemInstanceQuery().planItemDefinitionType(PlanItemDefinitionType.VARIABLE_EVENT_LISTENER).count()).isZero(); + assertThat(cmmnRuntimeService.createPlanItemInstanceQuery().list()) + .extracting(PlanItemInstance::getPlanItemDefinitionType, PlanItemInstance::getPlanItemDefinitionId, PlanItemInstance::getState) + .containsExactlyInAnyOrder( + tuple(PlanItemDefinitionType.HUMAN_TASK, "taskA", PlanItemInstanceState.ACTIVE), + tuple(PlanItemDefinitionType.HUMAN_TASK, "taskB", PlanItemInstanceState.ACTIVE), + tuple(PlanItemDefinitionType.HUMAN_TASK, "taskC", PlanItemInstanceState.ACTIVE) + ); + + assertThat(cmmnRuntimeService.createPlanItemInstanceQuery().planItemDefinitionId("taskB").planItemInstanceStateActive().count()).isEqualTo(1); + assertThat(cmmnRuntimeService.createPlanItemInstanceQuery().planItemDefinitionId("taskC").planItemInstanceStateActive().count()).isEqualTo(1); + + if (CmmnHistoryTestHelper.isHistoryLevelAtLeast(HistoryLevel.ACTIVITY, cmmnEngineConfiguration)) { + assertThat(cmmnHistoryService.createHistoricPlanItemInstanceQuery().list()) + .extracting(HistoricPlanItemInstance::getPlanItemDefinitionType, HistoricPlanItemInstance::getPlanItemDefinitionId, HistoricPlanItemInstance::getState) + .containsExactlyInAnyOrder( + tuple(PlanItemDefinitionType.VARIABLE_EVENT_LISTENER, "variableEventListener", PlanItemInstanceState.COMPLETED), + tuple(PlanItemDefinitionType.VARIABLE_EVENT_LISTENER, "variableEventListener2", PlanItemInstanceState.COMPLETED), + tuple(PlanItemDefinitionType.HUMAN_TASK, "taskA", PlanItemInstanceState.ACTIVE), + tuple(PlanItemDefinitionType.HUMAN_TASK, "taskB", PlanItemInstanceState.ACTIVE), + tuple(PlanItemDefinitionType.HUMAN_TASK, "taskC", PlanItemInstanceState.ACTIVE) + ); + } + + + assertCaseInstanceNotEnded(caseInstance); + cmmnTaskService.createTaskQuery().list().forEach(t -> cmmnTaskService.complete(t.getId())); + assertCaseInstanceEnded(caseInstance); + } @Test @CmmnDeployment diff --git a/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/eventlistener/VariableEventListenerTest.testTriggerMultipleVariableEventListeners.cmmn b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/eventlistener/VariableEventListenerTest.testTriggerMultipleVariableEventListeners.cmmn new file mode 100644 index 00000000000..8b1390ca4cc --- /dev/null +++ b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/eventlistener/VariableEventListenerTest.testTriggerMultipleVariableEventListeners.cmmn @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + occur + + + + + occur + + + + + + + + + + \ No newline at end of file diff --git a/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/variablelistener/VariableListenerSessionData.java b/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/variablelistener/VariableListenerSessionData.java index d21d3d5e916..99049a3af51 100644 --- a/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/variablelistener/VariableListenerSessionData.java +++ b/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/variablelistener/VariableListenerSessionData.java @@ -12,6 +12,9 @@ */ package org.flowable.common.engine.impl.variablelistener; +import java.util.ArrayList; +import java.util.List; + public class VariableListenerSessionData { public static final String VARIABLE_CREATE = "create"; @@ -22,6 +25,7 @@ public class VariableListenerSessionData { protected String scopeId; protected String scopeType; protected String scopeDefinitionId; + protected List processedElementIds = new ArrayList<>(); public VariableListenerSessionData(String changeType, String scopeId, String scopeType, String scopeDefinitionId) { this.changeType = changeType; @@ -57,5 +61,11 @@ public String getScopeDefinitionId() { } public void setScopeDefinitionId(String scopeDefinitionId) { this.scopeDefinitionId = scopeDefinitionId; - } + } + public boolean containsProcessedElementId(String elementId) { + return this.processedElementIds.contains(elementId); + } + public void addProcessedElementId(String elementId) { + this.processedElementIds.add(elementId); + } } diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/agenda/EvaluateVariableListenerEventDefinitionsOperation.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/agenda/EvaluateVariableListenerEventDefinitionsOperation.java index 8691987d497..ac6e2bce53a 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/agenda/EvaluateVariableListenerEventDefinitionsOperation.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/agenda/EvaluateVariableListenerEventDefinitionsOperation.java @@ -106,8 +106,10 @@ public void run() { VariableListenerEventDefinition.CHANGE_TYPE_ALL.equals(changeTypeValue) || (VariableListenerEventDefinition.CHANGE_TYPE_UPDATE_CREATE.equals(changeTypeValue) && (VariableListenerEventDefinition.CHANGE_TYPE_CREATE.equals(variableListenerData.getChangeType()) || VariableListenerEventDefinition.CHANGE_TYPE_UPDATE.equals(variableListenerData.getChangeType())))) { - itVariableListener.remove(); - CommandContextUtil.getAgenda().planTriggerExecutionOperation(execution); + if (!variableListenerData.containsProcessedElementId(execution.getActivityId())) { + CommandContextUtil.getAgenda().planTriggerExecutionOperation(execution); + variableListenerData.addProcessedElementId(execution.getActivityId()); + } } } } diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/variable/VariableListenerEventTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/variable/VariableListenerEventTest.java index 6291fc4fb72..15661b7efc0 100644 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/variable/VariableListenerEventTest.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/bpmn/event/variable/VariableListenerEventTest.java @@ -88,6 +88,28 @@ public void catchVariableListenerUpdate() { assertThat(runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).count()).isEqualTo(0); } + @Test + @Deployment + public void multipleCatchVariableListeners() { + ProcessInstance processInstance = runtimeService.startProcessInstanceByKey("catchVariableListener"); + + assertThat(runtimeService.createEventSubscriptionQuery().processInstanceId(processInstance.getId()).list()).hasSize(2); + + assertThat(runtimeService.createExecutionQuery().processInstanceId(processInstance.getId()).count()).isEqualTo(3); + + runtimeService.setVariable(processInstance.getId(), "var2", "test"); + + assertThat(runtimeService.createEventSubscriptionQuery().processInstanceId(processInstance.getId()).list()).hasSize(2); + + runtimeService.setVariable(processInstance.getId(), "var1", "test"); + + assertThat(runtimeService.createEventSubscriptionQuery().processInstanceId(processInstance.getId()).list()).hasSize(0); + + assertThat(taskService.createTaskQuery().processInstanceId(processInstance.getId()).count()).isEqualTo(2); + assertThat(taskService.createTaskQuery().taskDefinitionKey("aftertask").processInstanceId(processInstance.getId()).count()).isEqualTo(1); + assertThat(taskService.createTaskQuery().taskDefinitionKey("aftertask2").processInstanceId(processInstance.getId()).count()).isEqualTo(1); + } + @Test @Deployment public void boundaryVariableListener() { diff --git a/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/variable/VariableListenerEventTest.multipleCatchVariableListeners.bpmn20.xml b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/variable/VariableListenerEventTest.multipleCatchVariableListeners.bpmn20.xml new file mode 100644 index 00000000000..f3595d15fc9 --- /dev/null +++ b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/bpmn/event/variable/VariableListenerEventTest.multipleCatchVariableListeners.bpmn20.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 5bca8a4a95d6bdb6d2a1e482547ba2168b9b7d24 Mon Sep 17 00:00:00 2001 From: Karel Maxa Date: Fri, 9 Feb 2024 09:01:36 +0100 Subject: [PATCH 04/15] Fix parsing of service provider file for OSGi Fixes #3841 --- modules/flowable-osgi/pom.xml | 5 ++ .../main/java/org/flowable/osgi/Extender.java | 12 +++- .../osgi/BundleScriptEngineResolverTest.java | 67 +++++++++++++++++++ .../services/javax.script.ScriptEngineFactory | 4 ++ 4 files changed, 85 insertions(+), 3 deletions(-) create mode 100644 modules/flowable-osgi/src/test/java/org/flowable/osgi/BundleScriptEngineResolverTest.java create mode 100644 modules/flowable-osgi/src/test/resources/META-INF/services/javax.script.ScriptEngineFactory diff --git a/modules/flowable-osgi/pom.xml b/modules/flowable-osgi/pom.xml index 099ea725c9c..d3691c37edf 100644 --- a/modules/flowable-osgi/pom.xml +++ b/modules/flowable-osgi/pom.xml @@ -201,6 +201,11 @@ junit-jupiter-api test + + org.mockito + mockito-junit-jupiter + test + diff --git a/modules/flowable-osgi/src/main/java/org/flowable/osgi/Extender.java b/modules/flowable-osgi/src/main/java/org/flowable/osgi/Extender.java index c1c5a7b4b4f..11f98a982f5 100644 --- a/modules/flowable-osgi/src/main/java/org/flowable/osgi/Extender.java +++ b/modules/flowable-osgi/src/main/java/org/flowable/osgi/Extender.java @@ -21,11 +21,13 @@ import java.io.InputStreamReader; import java.net.MalformedURLException; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Predicate; import javax.script.ScriptEngine; import javax.script.ScriptEngineFactory; @@ -367,9 +369,13 @@ public void unregister() { @Override public ScriptEngine resolveScriptEngine(String name) { try { - BufferedReader in = new BufferedReader(new InputStreamReader(configFile.openStream())); - String className = in.readLine(); - in.close(); + String className; + try (InputStream input = configFile.openStream()) { + className = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8)).lines() + .map(line -> line.split("#", 2)[0].trim()) + .filter(Predicate.not(String::isBlank)) + .findFirst().orElse(null); + } Class cls = bundle.loadClass(className); if (!ScriptEngineFactory.class.isAssignableFrom(cls)) { throw new IllegalStateException("Invalid ScriptEngineFactory: " + cls.getName()); diff --git a/modules/flowable-osgi/src/test/java/org/flowable/osgi/BundleScriptEngineResolverTest.java b/modules/flowable-osgi/src/test/java/org/flowable/osgi/BundleScriptEngineResolverTest.java new file mode 100644 index 00000000000..093742eccdf --- /dev/null +++ b/modules/flowable-osgi/src/test/java/org/flowable/osgi/BundleScriptEngineResolverTest.java @@ -0,0 +1,67 @@ +/* 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 org.flowable.osgi; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.net.URL; +import java.util.List; + +import javax.script.ScriptEngine; +import javax.script.ScriptEngineFactory; + +import org.flowable.osgi.Extender.BundleScriptEngineResolver; +import org.junit.Test; +import org.osgi.framework.Bundle; + +/** + * Test processing of service provider configuration file. + */ +public class BundleScriptEngineResolverTest { + + @Test + public void testResolveScriptEngine() throws Exception { + ScriptEngineFactory factory = mock(ScriptEngineFactoryMock.class); + Bundle bundle = mock(Bundle.class); + when(bundle.loadClass(eq("org.flowable.ScriptEngineFactoryMock"))).thenAnswer(answer -> factory.getClass()); + URL configFile = getClass().getClassLoader().getResource("META-INF/services/javax.script.ScriptEngineFactory"); + BundleScriptEngineResolver resolver = new BundleScriptEngineResolver(bundle, configFile); + ScriptEngine resolvedEngine = resolver.resolveScriptEngine("mockengine"); + assertNotNull(resolvedEngine); + assertEquals("mockengine", resolvedEngine.get("name")); + } + + /** + * Script engine factory providing mock script engine. + */ + static abstract class ScriptEngineFactoryMock implements ScriptEngineFactory { + + @Override + public List getNames() { + return List.of("mockengine"); + } + + @Override + public ScriptEngine getScriptEngine() { + ScriptEngine engine = mock(ScriptEngine.class); + when(engine.get(eq("name"))).thenReturn("mockengine"); + return engine; + } + + } + +} diff --git a/modules/flowable-osgi/src/test/resources/META-INF/services/javax.script.ScriptEngineFactory b/modules/flowable-osgi/src/test/resources/META-INF/services/javax.script.ScriptEngineFactory new file mode 100644 index 00000000000..165bef5b9b4 --- /dev/null +++ b/modules/flowable-osgi/src/test/resources/META-INF/services/javax.script.ScriptEngineFactory @@ -0,0 +1,4 @@ +# Comments, tabs and spaces should be ignored +# See java.util.ServiceLoader + + org.flowable.ScriptEngineFactoryMock # should be also ignored From 5a3135505585da1bd0358888e81688c0b88a1641 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Fri, 9 Feb 2024 10:27:06 +0100 Subject: [PATCH 05/15] Bump MyBatis version in notice.txt --- distro/src/notice.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/distro/src/notice.txt b/distro/src/notice.txt index 2a91d5baada..c7ee4fddd88 100644 --- a/distro/src/notice.txt +++ b/distro/src/notice.txt @@ -101,8 +101,8 @@ org.apache.groovy groovy 4.0.15 The org.apache.groovy groovy-jsr223 4.0.15 The Apache Software License, Version 2.0 org.eclipse.angus angus-mail 2.0.2 EDL 1.0 / EPL 2.0 org.liquibase liquibase-core 4.5.0 Apache License, Version 2.0 -org.mybatis mybatis 3.5.11 The Apache Software License, Version 2.0 -org.mybatis mybatis-spring 3.0.0 The Apache Software License, Version 2.0 +org.mybatis mybatis 3.5.13 The Apache Software License, Version 2.0 +org.mybatis mybatis-spring 3.0.3 The Apache Software License, Version 2.0 org.mvel mvel2 2.2.6.Final The Apache Software License, Version 2.0 org.slf4j jcl-over-slf4j 2.0.9 MIT License org.slf4j slf4j-api 2.0.9 MIT License From d1d5fdfd1f0358fdab10077560d9613164bb2e32 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Thu, 8 Feb 2024 15:01:23 +0100 Subject: [PATCH 06/15] Change deprecated spring-kafka `FixedDelayStrategy` --- .../kafka/KafkaChannelDefinitionProcessor.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/modules/flowable-event-registry-spring/src/main/java/org/flowable/eventregistry/spring/kafka/KafkaChannelDefinitionProcessor.java b/modules/flowable-event-registry-spring/src/main/java/org/flowable/eventregistry/spring/kafka/KafkaChannelDefinitionProcessor.java index 450ceab01f7..880a260a566 100644 --- a/modules/flowable-event-registry-spring/src/main/java/org/flowable/eventregistry/spring/kafka/KafkaChannelDefinitionProcessor.java +++ b/modules/flowable-event-registry-spring/src/main/java/org/flowable/eventregistry/spring/kafka/KafkaChannelDefinitionProcessor.java @@ -84,11 +84,11 @@ import org.springframework.kafka.retrytopic.DefaultDestinationTopicResolver; import org.springframework.kafka.retrytopic.DestinationTopic; import org.springframework.kafka.retrytopic.DestinationTopicProcessor; -import org.springframework.kafka.retrytopic.FixedDelayStrategy; import org.springframework.kafka.retrytopic.ListenerContainerFactoryConfigurer; import org.springframework.kafka.retrytopic.RetryTopicConfiguration; import org.springframework.kafka.retrytopic.RetryTopicConfigurationBuilder; import org.springframework.kafka.retrytopic.RetryTopicSchedulerWrapper; +import org.springframework.kafka.retrytopic.SameIntervalTopicReuseStrategy; import org.springframework.kafka.retrytopic.TopicSuffixingStrategy; import org.springframework.kafka.support.Suffixer; import org.springframework.kafka.support.TopicPartitionOffset; @@ -1024,13 +1024,14 @@ protected RetryTopicConfiguration createRetryTopicConfiguration(ResolvedRetryCon RetryTopicConfigurationBuilder retryTopicConfigurationBuilder = RetryTopicConfigurationBuilder.newInstance() .autoStartDltHandler(false) .autoCreateTopics(retryConfiguration.autoCreateTopics, retryConfiguration.numPartitions, retryConfiguration.replicationFactor) - .dltSuffix(dltTopicSuffix) .retryTopicSuffix(retryTopicSuffix) - .useSingleTopicForFixedDelays(retryConfiguration.fixedDelayTopicStrategy) + .sameIntervalTopicReuseStrategy(retryConfiguration.sameIntervalTopicReuseStrategy) .setTopicSuffixingStrategy(retryConfiguration.topicSuffixingStrategy); if (dltTopicSuffix == null) { retryTopicConfigurationBuilder.doNotConfigureDlt(); + } else { + retryTopicConfigurationBuilder.dltSuffix(dltTopicSuffix); } if (retryConfiguration.hasRetryTopic()) { @@ -1061,10 +1062,10 @@ protected ResolvedRetryConfiguration resolveRetryConfiguration(KafkaInboundChann resolvedRetryConfiguration.dltTopicSuffix = resolveExpressionAsString(retry.getDltTopicSuffix(), "retry.dltTopicSuffix"); resolvedRetryConfiguration.retryTopicSuffix = resolveExpressionAsString(retry.getRetryTopicSuffix(), "retry.retryTopicSuffix"); - String fixedDelayTopicStrategy = resolveExpressionAsString(retry.getFixedDelayTopicStrategy(), "retry.fixedDelayTopicStrategy"); + String sameIntervalTopicReuseStrategy = resolveExpressionAsString(retry.getFixedDelayTopicStrategy(), "retry.fixedDelayTopicStrategy"); // Different default from Spring Kafka - resolvedRetryConfiguration.fixedDelayTopicStrategy = - fixedDelayTopicStrategy == null ? FixedDelayStrategy.SINGLE_TOPIC : FixedDelayStrategy.valueOf(fixedDelayTopicStrategy); + resolvedRetryConfiguration.sameIntervalTopicReuseStrategy = + sameIntervalTopicReuseStrategy == null ? SameIntervalTopicReuseStrategy.SINGLE_TOPIC : SameIntervalTopicReuseStrategy.valueOf(sameIntervalTopicReuseStrategy); String topicSuffixingStrategy = resolveExpressionAsString(retry.getTopicSuffixingStrategy(), "retry.topicSuffixingStrategy"); // Different default from Spring Kafka @@ -1133,7 +1134,7 @@ protected static class ResolvedRetryConfiguration { protected Integer attempts; protected String dltTopicSuffix; protected String retryTopicSuffix; - protected FixedDelayStrategy fixedDelayTopicStrategy; + protected SameIntervalTopicReuseStrategy sameIntervalTopicReuseStrategy; protected TopicSuffixingStrategy topicSuffixingStrategy; protected SleepingBackOffPolicy nonBlockingBackOff; protected boolean autoCreateTopics; From b75af3810a512fedf5fc4c31ba16d663f5fbd87d Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Thu, 8 Feb 2024 17:35:16 +0100 Subject: [PATCH 07/15] Update dependencies * Spring Boot 3.2.2 * Spring Framework 6.1.3 * Spring Security 6.2.1 * Spring AMQP 3.1.1 * Spring Kafka 3.1.1 * Spring LDAP 3.2.1 * Reactor Netty 1.1.15 * SLF4J 2.0.11 * Groovy 4.0.17 * JUnit Jupiter 5.10.1 * Mockito 5.7.0 * Byte Buddy 1.14.11 * Testcontainer 1.19.3 * Artemis 2.31.2 * Http Client5 5.2.3 * Hibernate Core 6.4.1.Final * Spring Data JPA 3.2.2 Change assertion in `AllEnginesAutoConfigurationTest` to use `containsExactlyInAnyOrder` since the order there isn't important. The `AppEngineConfiguration` is going to order all the available contributors before applying. The configurators are only the ones that have been registered during bean registration. --- distro/src/notice.txt | 42 +++++++++---------- modules/flowable-rest/pom.xml | 2 +- .../boot/AllEnginesAutoConfigurationTest.java | 2 +- pom.xml | 32 +++++++------- 4 files changed, 39 insertions(+), 39 deletions(-) diff --git a/distro/src/notice.txt b/distro/src/notice.txt index c7ee4fddd88..73a45701d33 100644 --- a/distro/src/notice.txt +++ b/distro/src/notice.txt @@ -97,32 +97,32 @@ org.apache.httpcomponents httpclient 4.5.13 Apac org.apache.httpcomponents httpcore 4.4.15 Apache License, Version 2.0 org.apache.httpcomponents httpmime 4.5.13 Apache License, Version 2.0 org.apache.geronimo.bundles json 20090211_1 The Apache Software License, Version 2.0 -org.apache.groovy groovy 4.0.15 The Apache Software License, Version 2.0 -org.apache.groovy groovy-jsr223 4.0.15 The Apache Software License, Version 2.0 +org.apache.groovy groovy 4.0.17 The Apache Software License, Version 2.0 +org.apache.groovy groovy-jsr223 4.0.17 The Apache Software License, Version 2.0 org.eclipse.angus angus-mail 2.0.2 EDL 1.0 / EPL 2.0 org.liquibase liquibase-core 4.5.0 Apache License, Version 2.0 org.mybatis mybatis 3.5.13 The Apache Software License, Version 2.0 org.mybatis mybatis-spring 3.0.3 The Apache Software License, Version 2.0 org.mvel mvel2 2.2.6.Final The Apache Software License, Version 2.0 -org.slf4j jcl-over-slf4j 2.0.9 MIT License -org.slf4j slf4j-api 2.0.9 MIT License -org.slf4j slf4j-log4j12 2.0.9 MIT License -org.springframework spring-beans 6.0.14 The Apache Software License, Version 2.0 -org.springframework spring-core 6.0.14 The Apache Software License, Version 2.0 -org.springframework spring-context 6.0.14 The Apache Software License, Version 2.0 -org.springframework spring-context-support 6.0.14 The Apache Software License, Version 2.0 -org.springframework spring-jdbc 6.0.14 The Apache Software License, Version 2.0 -org.springframework spring-tx 6.0.14 The Apache Software License, Version 2.0 -org.springframework spring-web 6.0.14 The Apache Software License, Version 2.0 -org.springframework spring-webmvc 6.0.14 The Apache Software License, Version 2.0 -org.springframework spring-aop 6.0.14 The Apache Software License, Version 2.0 -org.springframework spring-core 6.0.14 The Apache Software License, Version 2.0 -org.springframework spring-expression 6.0.14 The Apache Software License, Version 2.0 -org.springframework spring-orm 6.0.14 The Apache Software License, Version 2.0 -org.springframework.security spring-security-config 6.1.5 The Apache Software License, Version 2.0 -org.springframework.security spring-security-core 6.1.5 The Apache Software License, Version 2.0 -org.springframework.security spring-security-crypto 6.1.5 The Apache Software License, Version 2.0 -org.springframework.security spring-security-web 6.1.5 The Apache Software License, Version 2.0 +org.slf4j jcl-over-slf4j 2.0.11 MIT License +org.slf4j slf4j-api 2.0.11 MIT License +org.slf4j slf4j-log4j12 2.0.11 MIT License +org.springframework spring-beans 6.1.3 The Apache Software License, Version 2.0 +org.springframework spring-core 6.1.3 The Apache Software License, Version 2.0 +org.springframework spring-context 6.1.3 The Apache Software License, Version 2.0 +org.springframework spring-context-support 6.1.3 The Apache Software License, Version 2.0 +org.springframework spring-jdbc 6.1.3 The Apache Software License, Version 2.0 +org.springframework spring-tx 6.1.3 The Apache Software License, Version 2.0 +org.springframework spring-web 6.1.3 The Apache Software License, Version 2.0 +org.springframework spring-webmvc 6.1.3 The Apache Software License, Version 2.0 +org.springframework spring-aop 6.1.3 The Apache Software License, Version 2.0 +org.springframework spring-core 6.1.3 The Apache Software License, Version 2.0 +org.springframework spring-expression 6.1.3 The Apache Software License, Version 2.0 +org.springframework spring-orm 6.1.3 The Apache Software License, Version 2.0 +org.springframework.security spring-security-config 6.2.1 The Apache Software License, Version 2.0 +org.springframework.security spring-security-core 6.2.1 The Apache Software License, Version 2.0 +org.springframework.security spring-security-crypto 6.2.1 The Apache Software License, Version 2.0 +org.springframework.security spring-security-web 6.2.1 The Apache Software License, Version 2.0 org.tinyjee.jgraphx jgraphx 1.10.4.1 JGraph Ltd - 3 clause BSD license org.yaml snakeyaml 1.17 The Apache Software License, Version 2.0 xerces xercesImpl 2.12.1 The Apache Software License, Version 2.0 diff --git a/modules/flowable-rest/pom.xml b/modules/flowable-rest/pom.xml index 4e23d8a0db9..f35d1a86afc 100644 --- a/modules/flowable-rest/pom.xml +++ b/modules/flowable-rest/pom.xml @@ -154,7 +154,7 @@ org.springframework.data spring-data-jpa - 3.0.0 + 3.2.2 test diff --git a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/test/java/org/flowable/test/spring/boot/AllEnginesAutoConfigurationTest.java b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/test/java/org/flowable/test/spring/boot/AllEnginesAutoConfigurationTest.java index b013d42a606..25b6eea858e 100644 --- a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/test/java/org/flowable/test/spring/boot/AllEnginesAutoConfigurationTest.java +++ b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/test/java/org/flowable/test/spring/boot/AllEnginesAutoConfigurationTest.java @@ -163,7 +163,7 @@ public void usingAllAutoConfigurationsTogetherShouldWorkCorrectly() { SpringProcessEngineConfigurator processConfigurator = context.getBean(SpringProcessEngineConfigurator.class); assertThat(appEngineConfiguration.getConfigurators()) .as("AppEngineConfiguration configurators") - .containsExactly( + .containsExactlyInAnyOrder( processConfigurator, dmnConfigurator, cmmnConfigurator diff --git a/pom.xml b/pom.xml index 3b55b510bc9..dc69cd518ae 100644 --- a/pom.xml +++ b/pom.xml @@ -14,35 +14,35 @@ https://oss.sonatype.org/content/repositories/snapshots/ 17 - 3.1.6 - 6.0.14 - 6.1.5 - 3.0.10 - 3.0.13 - 3.1.1 - 1.1.8 + 3.2.2 + 6.1.3 + 6.2.1 + 3.1.1 + 3.1.1 + 3.2.1 + 1.1.15 2.15.3 3.1.0 4.0.0 4.0.2 - 2.0.9 - 4.0.15 + 2.0.11 + 4.0.17 3.4.0 0.9.27 4.13.2 - 5.9.3 + 5.10.1 5.0.1 3.1.1 3.3.1 - 5.5.0 + 5.7.0 - 1.14.8 - 1.18.3 - 2.28.0 + 1.14.11 + 1.19.3 + 2.31.2 21.9.0.0 ojdbc8 @@ -151,7 +151,7 @@ org.apache.httpcomponents.client5 httpclient5 - 5.1.3 + 5.2.3 com.oracle.database.jdbc @@ -271,7 +271,7 @@ org.hibernate.orm hibernate-core - 6.2.13.Final + 6.4.1.Final org.apache.groovy From 7ff37bcad780f1dd79c7dc752d5a468cb21d8559 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Fri, 9 Feb 2024 10:16:08 +0100 Subject: [PATCH 08/15] Add `spring-context` for testing the `SpringWebClientFlowableHttpClient` in `flowable-http` The `SpringWebClientFlowableHttpClient` is using `ReactorClientHttpConnector` from `spring-web`, which since Spring Framework 6.1 is implementing `SmartLifecycle` and thus needs `spring-context` --- modules/flowable-http/pom.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/modules/flowable-http/pom.xml b/modules/flowable-http/pom.xml index 28a12449c48..529ac72395d 100644 --- a/modules/flowable-http/pom.xml +++ b/modules/flowable-http/pom.xml @@ -224,6 +224,16 @@ spring-webflux test + + + org.springframework + spring-context + test + io.projectreactor.netty reactor-netty From be8b3c87de36f2dd36954b402abb04b4e0be841a Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Mon, 12 Feb 2024 16:10:55 +0100 Subject: [PATCH 09/15] Use BatchQuery#delete instead of deprecated BatchQuery#deleteWithRelateData --- .../cmmn/engine/impl/job/CmmnHistoryCleanupJobHandler.java | 2 +- .../engine/impl/jobexecutor/BpmnHistoryCleanupJobHandler.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/job/CmmnHistoryCleanupJobHandler.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/job/CmmnHistoryCleanupJobHandler.java index ad2d8129999..98d78b2ec2c 100644 --- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/job/CmmnHistoryCleanupJobHandler.java +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/job/CmmnHistoryCleanupJobHandler.java @@ -53,7 +53,7 @@ public void execute(JobEntity job, String configuration, VariableScope variableS BatchQuery batchCleaningQuery = cmmnEngineConfiguration.getCmmnHistoryCleaningManager().createBatchCleaningQuery(); if (batchCleaningQuery != null) { - batchCleaningQuery.deleteWithRelatedData(); + batchCleaningQuery.delete(); } } diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/jobexecutor/BpmnHistoryCleanupJobHandler.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/jobexecutor/BpmnHistoryCleanupJobHandler.java index ef66a25c99d..e39102a61e3 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/jobexecutor/BpmnHistoryCleanupJobHandler.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/jobexecutor/BpmnHistoryCleanupJobHandler.java @@ -53,7 +53,7 @@ public void execute(JobEntity job, String configuration, VariableScope variableS BatchQuery batchCleaningQuery = processEngineConfiguration.getHistoryCleaningManager().createBatchCleaningQuery(); if (batchCleaningQuery != null) { - batchCleaningQuery.deleteWithRelatedData(); + batchCleaningQuery.delete(); } } From 5f65ec9627a67bbd3b20804fd50d353d3c9b76ec Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Mon, 12 Feb 2024 16:12:27 +0100 Subject: [PATCH 10/15] Use AssertJ assertThatThrownBy instead of JUnit 4 ExpectedException rule --- .../cmmn/test/prefix/CmmnPrefixTest.java | 5 -- .../runtime/CaseInstanceInvolvementTest.java | 34 ++++------ .../cmmn/test/runtime/VariablesTest.java | 33 ++++------ .../cmmn/test/task/CmmnTaskServiceTest.java | 21 +++--- .../dmn/engine/test/MixedDeploymentTest.java | 35 ++++------ .../dmn/test/runtime/DecisionTaskTest.java | 66 ++++++++----------- .../DecisionTableExecutionFallBackTest.java | 21 ++---- 7 files changed, 80 insertions(+), 135 deletions(-) diff --git a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/prefix/CmmnPrefixTest.java b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/prefix/CmmnPrefixTest.java index db9c9d75ce3..2577f62a466 100644 --- a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/prefix/CmmnPrefixTest.java +++ b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/prefix/CmmnPrefixTest.java @@ -32,18 +32,13 @@ import org.flowable.task.api.Task; import org.flowable.task.api.history.HistoricTaskInstance; import org.flowable.variable.api.history.HistoricVariableInstance; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; /** * @author Joram Barrez */ public class CmmnPrefixTest { - @Rule - public ExpectedException expectedException = ExpectedException.none(); - @Test public void testPrefixCase() { CmmnEngine cmmnEngine = null; diff --git a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/CaseInstanceInvolvementTest.java b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/CaseInstanceInvolvementTest.java index 66df8a1fe8c..5423e0cfe6e 100644 --- a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/CaseInstanceInvolvementTest.java +++ b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/CaseInstanceInvolvementTest.java @@ -14,6 +14,7 @@ package org.flowable.cmmn.test.runtime; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.util.Collections; import java.util.HashSet; @@ -28,18 +29,13 @@ import org.flowable.common.engine.api.FlowableIllegalArgumentException; import org.flowable.common.engine.impl.AbstractEngineConfiguration; import org.flowable.identitylink.api.IdentityLinkType; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; /** * @author martin.grofcik */ public class CaseInstanceInvolvementTest extends FlowableCmmnTestCase { - @Rule - public ExpectedException expectException = ExpectedException.none(); - @Test @CmmnDeployment(resources = "org/flowable/cmmn/test/runtime/oneTaskCase.cmmn") public void getCaseInstanceWithInvolvedUser() { @@ -94,26 +90,23 @@ public void getCaseInstanceWithNonExistingInvolvedUser() { @Test public void getCaseInstanceWithNullInvolvedUser() { - this.expectException.expect(FlowableIllegalArgumentException.class); - this.expectException.expectMessage("involvedUser is null"); - - cmmnRuntimeService.createCaseInstanceQuery().involvedUser(null); + assertThatThrownBy(() -> cmmnRuntimeService.createCaseInstanceQuery().involvedUser(null)) + .isInstanceOf(FlowableIllegalArgumentException.class) + .hasMessage("involvedUser is null"); } @Test public void getCaseInstanceWithNullInvolvedGroups() { - this.expectException.expect(FlowableIllegalArgumentException.class); - this.expectException.expectMessage("involvedGroups are null"); - - cmmnRuntimeService.createCaseInstanceQuery().involvedGroups(null); + assertThatThrownBy(() -> cmmnRuntimeService.createCaseInstanceQuery().involvedGroups(null)) + .isInstanceOf(FlowableIllegalArgumentException.class) + .hasMessage("involvedGroups are null"); } @Test public void getCaseInstanceWithEmptyInvolvedGroups() { - this.expectException.expect(FlowableIllegalArgumentException.class); - this.expectException.expectMessage("involvedGroups are empty"); - - cmmnRuntimeService.createCaseInstanceQuery().involvedGroups(Collections.emptySet()); + assertThatThrownBy(() -> cmmnRuntimeService.createCaseInstanceQuery().involvedGroups(Collections.emptySet())) + .isInstanceOf(FlowableIllegalArgumentException.class) + .hasMessage("involvedGroups are empty"); } @Test @@ -124,10 +117,9 @@ public void getCaseInstanceWithNonNullInvolvedUser() { start(); cmmnRuntimeService.addUserIdentityLink(caseInstance.getId(), "kermit", IdentityLinkType.PARTICIPANT); - this.expectException.expect(FlowableIllegalArgumentException.class); - this.expectException.expectMessage("involvedUser is null"); - - cmmnRuntimeService.createCaseInstanceQuery().involvedUser(null).count(); + assertThatThrownBy(() -> cmmnRuntimeService.createCaseInstanceQuery().involvedUser(null)) + .isInstanceOf(FlowableIllegalArgumentException.class) + .hasMessage("involvedUser is null"); } @Test diff --git a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/VariablesTest.java b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/VariablesTest.java index c133aae26a6..84644bf60d2 100644 --- a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/VariablesTest.java +++ b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/VariablesTest.java @@ -14,6 +14,7 @@ import static java.util.stream.Collectors.toMap; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.entry; import java.io.Serializable; @@ -54,18 +55,13 @@ import org.flowable.variable.api.types.VariableType; import org.flowable.variable.service.VariableServiceConfiguration; import org.flowable.variable.service.impl.persistence.entity.VariableInstanceEntity; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; /** * @author Joram Barrez */ public class VariablesTest extends FlowableCmmnTestCase { - @Rule - public ExpectedException expectedException = ExpectedException.none(); - @Test @CmmnDeployment public void testGetVariables() { @@ -437,19 +433,17 @@ public void testSetVariableOnRootCase() { @Test @CmmnDeployment(resources = "org/flowable/cmmn/test/task/CmmnTaskServiceTest.testOneHumanTaskCase.cmmn") public void testSetVariableOnNonExistingCase() { - this.expectedException.expect(FlowableObjectNotFoundException.class); - this.expectedException.expectMessage("No case instance found for id NON-EXISTING-CASE"); - - cmmnRuntimeService.setVariable("NON-EXISTING-CASE", "varToUpdate", "newValue"); + assertThatThrownBy(() -> cmmnRuntimeService.setVariable("NON-EXISTING-CASE", "varToUpdate", "newValue")) + .isInstanceOf(FlowableObjectNotFoundException.class) + .hasMessage("No case instance found for id NON-EXISTING-CASE"); } @Test @CmmnDeployment(resources = "org/flowable/cmmn/test/task/CmmnTaskServiceTest.testOneHumanTaskCase.cmmn") public void testSetVariableWithoutName() { - this.expectedException.expect(FlowableIllegalArgumentException.class); - this.expectedException.expectMessage("variable name is null"); - - cmmnRuntimeService.setVariable("NON-EXISTING-CASE", null, "newValue"); + assertThatThrownBy(() -> cmmnRuntimeService.setVariable("NON-EXISTING-CASE", null, "newValue")) + .isInstanceOf(FlowableIllegalArgumentException.class) + .hasMessage("variable name is null"); } @Test @@ -513,13 +507,13 @@ public void testSetVariablesOnRootCase() { @Test @CmmnDeployment(resources = "org/flowable/cmmn/test/task/CmmnTaskServiceTest.testOneHumanTaskCase.cmmn") public void testSetVariablesOnNonExistingCase() { - this.expectedException.expect(FlowableObjectNotFoundException.class); - this.expectedException.expectMessage("No case instance found for id NON-EXISTING-CASE"); Map variables = Stream.of(new ImmutablePair("varToUpdate", "newValue")).collect( toMap(Pair::getKey, Pair::getValue) ); - cmmnRuntimeService.setVariables("NON-EXISTING-CASE", variables); + assertThatThrownBy(() -> cmmnRuntimeService.setVariables("NON-EXISTING-CASE", variables)) + .isInstanceOf(FlowableObjectNotFoundException.class) + .hasMessage("No case instance found for id NON-EXISTING-CASE"); } @SuppressWarnings("unchecked") @@ -531,10 +525,9 @@ public void testSetVariablesWithEmptyMap() { .caseDefinitionKey("oneHumanTaskCase") .start(); - this.expectedException.expect(FlowableIllegalArgumentException.class); - this.expectedException.expectMessage("variables is empty"); - - cmmnRuntimeService.setVariables(caseInstance.getId(), Collections.EMPTY_MAP); + assertThatThrownBy(() -> cmmnRuntimeService.setVariables(caseInstance.getId(), Collections.EMPTY_MAP)) + .isInstanceOf(FlowableIllegalArgumentException.class) + .hasMessage("variables is empty"); } @Test diff --git a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/task/CmmnTaskServiceTest.java b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/task/CmmnTaskServiceTest.java index ff19b7611cc..aaf67f238a5 100644 --- a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/task/CmmnTaskServiceTest.java +++ b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/task/CmmnTaskServiceTest.java @@ -51,9 +51,7 @@ import org.flowable.task.api.Task; import org.flowable.task.api.TaskQuery; import org.flowable.task.api.history.HistoricTaskInstance; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; /** * @author Joram Barrez @@ -61,9 +59,6 @@ */ public class CmmnTaskServiceTest extends FlowableCmmnTestCase { - @Rule - public ExpectedException expectedException = ExpectedException.none(); - @Test @CmmnDeployment public void testOneHumanTaskCase() { @@ -167,12 +162,12 @@ public void testOneHumanTaskVariableScopeExpressionCase() { CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder().caseDefinitionKey("oneHumanTaskCase").start(); Task task = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).singleResult(); - this.expectedException.expect(FlowableException.class); - this.expectedException.expectMessage("Error while evaluating expression: ${caseInstance.name}"); - cmmnTaskService.complete(task.getId(), Collections.singletonMap( + assertThatThrownBy(() -> cmmnTaskService.complete(task.getId(), Collections.singletonMap( "${caseInstance.name}", "newCaseName" ) - ); + )) + .isInstanceOf(FlowableException.class) + .hasMessageContaining("Error while evaluating expression: ${caseInstance.name}"); } @Test @@ -181,12 +176,12 @@ public void testOneHumanTaskCompleteSetCaseName() { CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder().caseDefinitionKey("oneHumanTaskCase").start(); Task task = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).singleResult(); - this.expectedException.expect(FlowableException.class); - this.expectedException.expectMessage("Error while evaluating expression: ${name}"); - cmmnTaskService.complete(task.getId(), Collections.singletonMap( + assertThatThrownBy(() -> cmmnTaskService.complete(task.getId(), Collections.singletonMap( "${name}", "newCaseName" ) - ); + )) + .isInstanceOf(FlowableException.class) + .hasMessageContaining("Error while evaluating expression: ${name}"); } @Test diff --git a/modules/flowable-dmn-engine-configurator/src/test/java/org/flowable/dmn/engine/test/MixedDeploymentTest.java b/modules/flowable-dmn-engine-configurator/src/test/java/org/flowable/dmn/engine/test/MixedDeploymentTest.java index f38805847d3..3c88b5d1d4d 100644 --- a/modules/flowable-dmn-engine-configurator/src/test/java/org/flowable/dmn/engine/test/MixedDeploymentTest.java +++ b/modules/flowable-dmn-engine-configurator/src/test/java/org/flowable/dmn/engine/test/MixedDeploymentTest.java @@ -32,9 +32,7 @@ import org.flowable.engine.runtime.ProcessInstance; import org.flowable.engine.test.Deployment; import org.flowable.variable.api.history.HistoricVariableInstance; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; /** * @author Yvo Swillens @@ -42,9 +40,6 @@ */ public class MixedDeploymentTest extends AbstractFlowableDmnEngineConfiguratorTest { - @Rule - public ExpectedException expectedException = ExpectedException.none(); - @Test @Deployment(resources = { "org/flowable/dmn/engine/test/deployment/oneDecisionTaskProcess.bpmn20.xml", "org/flowable/dmn/engine/test/deployment/simple.dmn" }) @@ -155,10 +150,9 @@ public void testDecisionTaskExecutionWithGlobalTenantFallback() { @Deployment(resources = { "org/flowable/dmn/engine/test/deployment/oneDecisionTaskProcess.bpmn20.xml" } ) public void testDecisionTaskExecutionInAnotherDeploymentAndTenantDefaultBehavior() { - this.expectedException.expect(FlowableObjectNotFoundException.class); - this.expectedException.expectMessage("Process definition with key 'oneDecisionTaskProcess' and tenantId 'flowable' was not found"); - - deployDecisionAndAssertProcessExecuted(); + assertThatThrownBy(this::deployDecisionAndAssertProcessExecuted) + .isInstanceOf(FlowableObjectNotFoundException.class) + .hasMessageContaining("Process definition with key 'oneDecisionTaskProcess' and tenantId 'flowable' was not found"); } @Test @@ -166,11 +160,9 @@ public void testDecisionTaskExecutionInAnotherDeploymentAndTenantDefaultBehavior tenantId = "flowable" ) public void testDecisionTaskExecutionInAnotherDeploymentAndTenantFalse() { - this.expectedException.expect(FlowableException.class); - this.expectedException.expectMessage("No decision found for key: decision1"); - this.expectedException.expectMessage("and tenant id: flowable"); - - deployDecisionAndAssertProcessExecuted(); + assertThatThrownBy(this::deployDecisionAndAssertProcessExecuted) + .isInstanceOf(FlowableObjectNotFoundException.class) + .hasMessageContainingAll("No decision found for key: decision1", "and tenant id: flowable"); } @Test @@ -178,17 +170,15 @@ public void testDecisionTaskExecutionInAnotherDeploymentAndTenantFalse() { tenantId = "flowable" ) public void testDecisionTaskExecutionInAnotherDeploymentAndTenantFallbackFalseWithoutDeployment() { - this.expectedException.expect(FlowableException.class); - this.expectedException.expectMessage("No decision found for key: decision1"); - this.expectedException.expectMessage("and tenant id: flowable"); - deleteAllDmnDeployments(); org.flowable.engine.repository.Deployment deployment = repositoryService.createDeployment(). addClasspathResource("org/flowable/dmn/engine/test/deployment/simple.dmn"). tenantId("anotherTenant"). deploy(); try { - assertDmnProcessExecuted(); + assertThatThrownBy(this::assertDmnProcessExecuted) + .isInstanceOf(FlowableObjectNotFoundException.class) + .hasMessageContainingAll("No decision found for key: decision1", "and tenant id: flowable"); } finally { this.repositoryService.deleteDeployment(deployment.getId(), true); deleteAllDmnDeployments(); @@ -200,15 +190,14 @@ public void testDecisionTaskExecutionInAnotherDeploymentAndTenantFallbackFalseWi tenantId = "flowable" ) public void testDecisionTaskExecutionInAnotherDeploymentAndTenantFallbackTrueWithoutDeployment() { - this.expectedException.expect(FlowableException.class); - this.expectedException.expectMessage("No decision found for key: decision1. There was also no fall back decision table found without tenant."); - org.flowable.engine.repository.Deployment deployment = repositoryService.createDeployment(). addClasspathResource("org/flowable/dmn/engine/test/deployment/simple.dmn"). tenantId("anotherTenant"). deploy(); try { - assertDmnProcessExecuted(); + assertThatThrownBy(this::assertDmnProcessExecuted) + .isInstanceOf(FlowableObjectNotFoundException.class) + .hasMessageContaining("No decision found for key: decision1. There was also no fall back decision table found without tenant."); } finally { this.repositoryService.deleteDeployment(deployment.getId(), true); deleteAllDmnDeployments(); diff --git a/modules/flowable-dmn-engine-configurator/src/test/java/org/flowable/dmn/test/runtime/DecisionTaskTest.java b/modules/flowable-dmn-engine-configurator/src/test/java/org/flowable/dmn/test/runtime/DecisionTaskTest.java index a089ce65dfd..7abd98a0cd5 100644 --- a/modules/flowable-dmn-engine-configurator/src/test/java/org/flowable/dmn/test/runtime/DecisionTaskTest.java +++ b/modules/flowable-dmn-engine-configurator/src/test/java/org/flowable/dmn/test/runtime/DecisionTaskTest.java @@ -13,6 +13,7 @@ package org.flowable.dmn.test.runtime; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.util.List; import java.util.Map; @@ -35,7 +36,6 @@ import org.junit.After; import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -53,9 +53,6 @@ public class DecisionTaskTest { @Rule public FlowableCmmnRule cmmnRule = new FlowableCmmnRule("org/flowable/cmmn/test/runtime/DecisionTaskTest.cfg.xml"); - @Rule - public ExpectedException expectedException = ExpectedException.none(); - @After public void tearDown() { deleteAllDmnDeployments(); @@ -83,14 +80,11 @@ public void testDecisionServiceTask() { } ) public void testUnknowPropertyUsedInDmn() { - this.expectedException.expect(FlowableException.class); - this.expectedException.expectMessage("DMN decision with key decisionTable execution failed. Cause: Unknown property used in expression: #{testVar == \"test2\"}"); - - CaseInstance caseInstance = cmmnRule.getCmmnRuntimeService().createCaseInstanceBuilder() + assertThatThrownBy(() -> cmmnRule.getCmmnRuntimeService().createCaseInstanceBuilder() .caseDefinitionKey("myCase") - .start(); - - assertResultVariable(caseInstance); + .start()) + .isInstanceOf(FlowableException.class) + .hasMessageContaining("DMN decision with key decisionTable execution failed. Cause: Unknown property used in expression: #{testVar == \"test2\"}"); } @Test @@ -163,14 +157,13 @@ public void testThrowErrorOnNoHitWithHit() { } ) public void testThrowErrorOnNoHit() { - this.expectedException.expect(FlowableException.class); - this.expectedException.expectMessage("DMN decision with key decisionTable did not hit any rules for the provided input."); - - cmmnRule.getCmmnRuntimeService().createCaseInstanceBuilder() + assertThatThrownBy(() -> cmmnRule.getCmmnRuntimeService().createCaseInstanceBuilder() .variable("throwErrorOnNoHits", true) .variable("testVar", "noHit") .caseDefinitionKey("myCase") - .start(); + .start()) + .isInstanceOf(FlowableException.class) + .hasMessageContaining("DMN decision with key decisionTable did not hit any rules for the provided input."); } @Test @@ -210,14 +203,13 @@ public void testExpressionReferenceKey() { resources = {"org/flowable/cmmn/test/runtime/DecisionTaskTest.testExpressionReferenceKey.cmmn"} ) public void testNullReferenceKey() { - this.expectedException.expect(FlowableException.class); - this.expectedException.expectMessage("Could not execute decision: no externalRef defined"); - - cmmnRule.getCmmnRuntimeService().createCaseInstanceBuilder() + assertThatThrownBy(() -> cmmnRule.getCmmnRuntimeService().createCaseInstanceBuilder() .caseDefinitionKey("myCase") .variable("testVar", "test2") .variable("referenceKey", null) - .start(); + .start()) + .isInstanceOf(FlowableException.class) + .hasMessageContaining("Could not execute decision: no externalRef defined"); } @Test @@ -225,14 +217,13 @@ public void testNullReferenceKey() { resources = {"org/flowable/cmmn/test/runtime/DecisionTaskTest.testExpressionReferenceKey.cmmn"} ) public void testNonStringReferenceKey() { - this.expectedException.expect(FlowableException.class); - this.expectedException.expectMessage("No decision found for key: 1 and parent deployment id"); - - cmmnRule.getCmmnRuntimeService().createCaseInstanceBuilder() + assertThatThrownBy(() -> cmmnRule.getCmmnRuntimeService().createCaseInstanceBuilder() .caseDefinitionKey("myCase") .variable("testVar", "test2") .variable("referenceKey", 1) - .start(); + .start()) + .isInstanceOf(FlowableException.class) + .hasMessageContaining("No decision found for key: 1 and parent deployment id"); } @Test @@ -240,14 +231,13 @@ public void testNonStringReferenceKey() { resources = {"org/flowable/cmmn/test/runtime/DecisionTaskTest.testExpressionReferenceKey.cmmn"} ) public void testNonExistingReferenceKey() { - this.expectedException.expect(FlowableException.class); - this.expectedException.expectMessage("No decision found for key: NonExistingReferenceKey and parent deployment id"); - - cmmnRule.getCmmnRuntimeService().createCaseInstanceBuilder() + assertThatThrownBy(() -> cmmnRule.getCmmnRuntimeService().createCaseInstanceBuilder() .caseDefinitionKey("myCase") .variable("testVar", "test2") .variable("referenceKey", "NonExistingReferenceKey") - .start(); + .start()) + .isInstanceOf(FlowableException.class) + .hasMessageContaining("No decision found for key: NonExistingReferenceKey and parent deployment id"); } @Test @@ -303,10 +293,9 @@ public void testDecisionServiceTaskWithFallback() { tenantId = "flowable" ) public void testDecisionServiceTaskWithFallbackFalse() { - this.expectedException.expect(FlowableException.class); - this.expectedException.expectMessage("and tenant id: flowable. There was also no fall back decision found without parent deployment id."); - - deployDmnTableAssertCaseStarted(); + assertThatThrownBy(this::deployDmnTableAssertCaseStarted) + .isInstanceOf(FlowableException.class) + .hasMessageContaining("and tenant id: flowable. There was also no fall back decision found without parent deployment id."); } @Test @@ -324,10 +313,9 @@ public void testDecisionServiceTaskWithGlobalTenantFallback() { tenantId = "flowable" ) public void testDecisionServiceTaskWithGlobalTenantFallbackNoDefinition() { - this.expectedException.expect(FlowableException.class); - this.expectedException.expectMessage("There was also no fall back decision found for default tenant defaultFlowable"); - - deployDmnTableWithGlobalTenantFallback("otherTenant"); + assertThatThrownBy(() -> deployDmnTableWithGlobalTenantFallback("otherTenant")) + .isInstanceOf(FlowableException.class) + .hasMessageContaining("There was also no fall back decision found for default tenant defaultFlowable"); } @Test diff --git a/modules/flowable-dmn-engine/src/test/java/org/flowable/dmn/engine/test/runtime/DecisionTableExecutionFallBackTest.java b/modules/flowable-dmn-engine/src/test/java/org/flowable/dmn/engine/test/runtime/DecisionTableExecutionFallBackTest.java index 50d38a03fd7..5f8af34dce9 100644 --- a/modules/flowable-dmn-engine/src/test/java/org/flowable/dmn/engine/test/runtime/DecisionTableExecutionFallBackTest.java +++ b/modules/flowable-dmn-engine/src/test/java/org/flowable/dmn/engine/test/runtime/DecisionTableExecutionFallBackTest.java @@ -13,6 +13,7 @@ package org.flowable.dmn.engine.test.runtime; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.util.HashMap; import java.util.Map; @@ -23,9 +24,7 @@ import org.flowable.dmn.engine.test.AbstractFlowableDmnTest; import org.junit.After; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; -import org.junit.rules.ExpectedException; /** * This class tests fallbacks in {@link org.flowable.dmn.engine.impl.cmd.AbstractExecuteDecisionCmd} @@ -36,9 +35,6 @@ public static final String TEST_TENANT_ID = "testTenantId"; public static final String TEST_PARENT_DEPLOYMENT_ID = "testParentDeploymentId"; - @Rule - public ExpectedException expectedException = ExpectedException.none(); - protected DmnDeployment deployment; @Before @@ -72,19 +68,16 @@ public void fallBackDecisionKeyDeploymentIdTenantIdWrongDeploymentId() { @Test public void decisionKeyDeploymentIdTenantIdWrongTenantIdThrowsException() { - expectedException.expect(FlowableObjectNotFoundException.class); - expectedException.expectMessage("No decision found for key: decision1, parent deployment id testParentDeploymentId and tenant id: WRONG_TENANT_ID."); - - executeDecision("WRONG_TENANT_ID", TEST_PARENT_DEPLOYMENT_ID); + assertThatThrownBy(() -> executeDecision("WRONG_TENANT_ID", TEST_PARENT_DEPLOYMENT_ID)) + .isInstanceOf(FlowableObjectNotFoundException.class) + .hasMessageContaining("No decision found for key: decision1, parent deployment id testParentDeploymentId and tenant id: WRONG_TENANT_ID."); } @Test public void decisionKeyTenantIdWrongTenantIdThrowsException() { - expectedException.expect(FlowableObjectNotFoundException.class); - expectedException.expectMessage("No decision found for key: decision1"); - expectedException.expectMessage("and tenantId: WRONG_TENANT_ID"); - - executeDecision("WRONG_TENANT_ID", null); + assertThatThrownBy(() -> executeDecision("WRONG_TENANT_ID", null)) + .isInstanceOf(FlowableObjectNotFoundException.class) + .hasMessage("No decision found for key: decision1 and tenantId: WRONG_TENANT_ID."); } @Test From f43c3c6a841654c072ccfd3e43a45397e15d43aa Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Mon, 12 Feb 2024 16:12:49 +0100 Subject: [PATCH 11/15] Use AssertJ recursive assertion and replace getCause with cause assertion --- .../CmmnHistoryServiceTaskLogTest.java | 16 +++++++++++---- .../history/HistoryServiceTaskLogTest.java | 12 ++++++++--- .../AbstractProcessInstanceMigrationTest.java | 4 +++- ...cessInstanceMigrationCallActivityTest.java | 12 ++++++++--- .../ProcessInstanceMigrationDocumentTest.java | 8 ++++++-- .../ProcessInstanceMigrationTest.java | 20 ++++++++++++++----- .../http/bpmn/HttpServiceTaskTest.java | 2 +- .../boot/ldap/FlowableLdapPropertiesTest.java | 6 ++++-- .../ProcessEngineAutoConfigurationTest.java | 6 ++++-- 9 files changed, 63 insertions(+), 23 deletions(-) diff --git a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/history/CmmnHistoryServiceTaskLogTest.java b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/history/CmmnHistoryServiceTaskLogTest.java index b3139ade7cd..1efb8608ae8 100644 --- a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/history/CmmnHistoryServiceTaskLogTest.java +++ b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/history/CmmnHistoryServiceTaskLogTest.java @@ -727,7 +727,9 @@ protected void assertThatTaskLogIsFetched(HistoricTaskLogEntryBuilder historicTa List pagedLogEntries = historicTaskLogEntryQuery.listPage(1, 1); assertThat(pagedLogEntries).hasSize(1); - assertThat(pagedLogEntries.get(0)).isEqualToComparingFieldByField(logEntries.get(1)); + assertThat(pagedLogEntries.get(0)) + .usingRecursiveComparison() + .isEqualTo(logEntries.get(1)); } finally { deleteTaskWithLogEntries(anotherTask.getId()); @@ -753,7 +755,9 @@ protected void assertThatAllTaskLogIsFetched(HistoricTaskLogEntryBuilder histori List pagedLogEntries = historicTaskLogEntryQuery.listPage(1, 1); assertThat(pagedLogEntries).hasSize(1); - assertThat(pagedLogEntries.get(0)).isEqualToComparingFieldByField(logEntries.get(1)); + assertThat(pagedLogEntries.get(0)) + .usingRecursiveComparison() + .isEqualTo(logEntries.get(1)); } finally { deleteTaskWithLogEntries(anotherTask.getId()); @@ -855,7 +859,9 @@ public void queryForTaskLogEntriesByToIncludedTimeStamp() { List pagedLogEntries = historicTaskLogEntryQuery.listPage(1, 1); assertThat(pagedLogEntries).hasSize(1); - assertThat(pagedLogEntries.get(0)).isEqualToComparingFieldByField(logEntries.get(1)); + assertThat(pagedLogEntries.get(0)) + .usingRecursiveComparison() + .isEqualTo(logEntries.get(1)); } finally { deleteTaskWithLogEntries(anotherTask.getId()); @@ -934,7 +940,9 @@ public void queryForTaskLogEntriesByLogNumber() { List pagedLogEntries = historicTaskLogEntryQuery.listPage(1, 1); assertThat(pagedLogEntries).hasSize(1); - assertThat(pagedLogEntries.get(0)).isEqualToComparingFieldByField(logEntries.get(1)); + assertThat(pagedLogEntries.get(0)) + .usingRecursiveComparison() + .isEqualTo(logEntries.get(1)); } finally { deleteTaskWithLogEntries(anotherTask.getId()); diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/history/HistoryServiceTaskLogTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/history/HistoryServiceTaskLogTest.java index 9b884fa3997..c68b5214f20 100644 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/history/HistoryServiceTaskLogTest.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/history/HistoryServiceTaskLogTest.java @@ -836,7 +836,9 @@ protected void assertThatTaskLogIsFetched(HistoricTaskLogEntryBuilder historicTa List pagedLogEntries = historicTaskLogEntryQuery.listPage(1, 1); assertThat(pagedLogEntries).hasSize(1); - assertThat(pagedLogEntries.get(0)).isEqualToComparingFieldByField(logEntries.get(1)); + assertThat(pagedLogEntries.get(0)) + .usingRecursiveComparison() + .isEqualTo(logEntries.get(1)); } } finally { @@ -865,7 +867,9 @@ protected void assertThatAllTaskLogIsFetched(HistoricTaskLogEntryBuilder histori List pagedLogEntries = historicTaskLogEntryQuery.listPage(1, 1); assertThat(pagedLogEntries).hasSize(1); - assertThat(pagedLogEntries.get(0)).isEqualToComparingFieldByField(logEntries.get(1)); + assertThat(pagedLogEntries.get(0)) + .usingRecursiveComparison() + .isEqualTo(logEntries.get(1)); } } finally { @@ -940,7 +944,9 @@ public void queryForTaskLogEntriesByToTimeStamp() { List pagedLogEntries = historicTaskLogEntryQuery.listPage(1, 1); assertThat(pagedLogEntries).hasSize(1); - assertThat(pagedLogEntries.get(0)).isEqualToComparingFieldByField(logEntries.get(1)); + assertThat(pagedLogEntries.get(0)) + .usingRecursiveComparison() + .isEqualTo(logEntries.get(1)); } } finally { diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/runtime/migration/AbstractProcessInstanceMigrationTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/runtime/migration/AbstractProcessInstanceMigrationTest.java index 4f3219ff95b..9df357884ba 100644 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/runtime/migration/AbstractProcessInstanceMigrationTest.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/runtime/migration/AbstractProcessInstanceMigrationTest.java @@ -52,7 +52,9 @@ protected void checkActivityInstances(ProcessDefinition processDefinition, Proce activityInstance -> { HistoricActivityInstance historicActivityInstance = historyService.createHistoricActivityInstanceQuery() .activityInstanceId(activityInstance.getId()).singleResult(); - assertThat(activityInstance).isEqualToComparingFieldByField(historicActivityInstance); + assertThat(activityInstance) + .usingRecursiveComparison() + .isEqualTo(historicActivityInstance); } ); } else { diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/runtime/migration/ProcessInstanceMigrationCallActivityTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/runtime/migration/ProcessInstanceMigrationCallActivityTest.java index 455e423640b..10f84d39d40 100644 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/runtime/migration/ProcessInstanceMigrationCallActivityTest.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/runtime/migration/ProcessInstanceMigrationCallActivityTest.java @@ -1796,7 +1796,9 @@ public void testMigrateProcessWithCallActivityWithoutAlteringTheSubProcessDefini .containsOnly(procDefWithCallActivityV2.getId()); ProcessInstance subProcessInstanceAfterMigration = runtimeService.createProcessInstanceQuery().superProcessInstanceId(processInstance.getId()) .singleResult(); - assertThat(subProcessInstanceAfterMigration).isEqualToComparingFieldByField(subProcessInstance); + assertThat(subProcessInstanceAfterMigration) + .usingRecursiveComparison() + .isEqualTo(subProcessInstance); List subProcessExecutionsAfterMigration = runtimeService.createExecutionQuery().processInstanceId(subProcessInstanceAfterMigration.getId()) .onlyChildExecutions().list(); assertThat(subProcessExecutionsAfterMigration) @@ -1807,7 +1809,9 @@ public void testMigrateProcessWithCallActivityWithoutAlteringTheSubProcessDefini .containsOnly(procDefSubProcess.getId()); subProcessExecutions.sort(Comparator.comparing(Execution::getId)); subProcessExecutionsAfterMigration.sort(Comparator.comparing(Execution::getId)); - assertThat(subProcessExecutionsAfterMigration).usingFieldByFieldElementComparator().isEqualTo(subProcessExecutions); + assertThat(subProcessExecutionsAfterMigration) + .usingRecursiveFieldByFieldElementComparator() + .isEqualTo(subProcessExecutions); List subProcessTasksAfterMigration = taskService.createTaskQuery().processInstanceId(subProcessInstance.getId()).list(); assertThat(subProcessTasksAfterMigration) @@ -1816,7 +1820,9 @@ public void testMigrateProcessWithCallActivityWithoutAlteringTheSubProcessDefini subProcessTasks.sort(Comparator.comparing(Task::getId)); subProcessTasksAfterMigration.sort(Comparator.comparing(Task::getId)); - assertThat(subProcessTasksAfterMigration).usingFieldByFieldElementComparator().isEqualTo(subProcessTasks); + assertThat(subProcessTasksAfterMigration) + .usingRecursiveFieldByFieldElementComparator() + .isEqualTo(subProcessTasks); subProcessTasksAfterMigration.forEach(this::completeTask); diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/runtime/migration/ProcessInstanceMigrationDocumentTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/runtime/migration/ProcessInstanceMigrationDocumentTest.java index 2575997001b..615a070ab0f 100644 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/runtime/migration/ProcessInstanceMigrationDocumentTest.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/runtime/migration/ProcessInstanceMigrationDocumentTest.java @@ -105,8 +105,12 @@ public void testDeSerializeCompleteProcessInstanceMigrationDocument() { .containsExactly(oneToOneMapping, oneToManyMapping, manyToOneMapping); assertThat(migrationDocument.getActivitiesLocalVariables()).isEqualTo(activityLocalVariables); assertThat(migrationDocument.getProcessInstanceVariables()).isEqualTo(processInstanceVariables); - assertThat(migrationDocument.getPreUpgradeScript()).isEqualToComparingFieldByField(new Script("groovy", "1+1")); - assertThat(migrationDocument.getPostUpgradeScript()).isEqualToComparingFieldByField(new Script("groovy", "2+2")); + assertThat(migrationDocument.getPreUpgradeScript()) + .usingRecursiveComparison() + .isEqualTo(new Script("groovy", "1+1")); + assertThat(migrationDocument.getPostUpgradeScript()) + .usingRecursiveComparison() + .isEqualTo(new Script("groovy", "2+2")); } @Test diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/runtime/migration/ProcessInstanceMigrationTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/runtime/migration/ProcessInstanceMigrationTest.java index 4e01ff7069d..9cfc17ad194 100644 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/runtime/migration/ProcessInstanceMigrationTest.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/runtime/migration/ProcessInstanceMigrationTest.java @@ -1925,7 +1925,9 @@ public void testMigrateActivityToActivityWithTimerInNewDefinition() { Execution execution = runtimeService.createExecutionQuery().parentId(task.getExecutionId()).singleResult(); Job job = managementService.createTimerJobQuery().executionId(execution.getId()).singleResult(); assertThat(job) - .isEqualToIgnoringGivenFields(timerJob, "originalPersistentState", "customValuesByteArrayRef", "exceptionByteArrayRef"); + .usingRecursiveComparison() + .ignoringFields("originalPersistentState", "customValuesByteArrayRef", "exceptionByteArrayRef") + .isEqualTo(timerJob); // Verify events assertThat(changeStateEventListener.hasEvents()).isTrue(); @@ -1992,7 +1994,9 @@ public void testMigrateActivityWithTimerToActivityWithoutTimerInNewDefinition() Execution execution = runtimeService.createExecutionQuery().parentId(task.getExecutionId()).singleResult(); Job job = managementService.createTimerJobQuery().executionId(execution.getId()).singleResult(); assertThat(job) - .isEqualToIgnoringGivenFields(timerJob, "originalPersistentState", "customValuesByteArrayRef", "exceptionByteArrayRef"); + .usingRecursiveComparison() + .ignoringFields("originalPersistentState", "customValuesByteArrayRef", "exceptionByteArrayRef") + .isEqualTo(timerJob); changeStateEventListener.clear(); @@ -2096,7 +2100,9 @@ public void testMigrateActivityWithTimerToActivityWithTimerInNewDefinition() { Execution execution = runtimeService.createExecutionQuery().parentId(task.getExecutionId()).singleResult(); Job job = managementService.createTimerJobQuery().executionId(execution.getId()).singleResult(); assertThat(job) - .isEqualToIgnoringGivenFields(timerJob2, "originalPersistentState", "customValuesByteArrayRef", "exceptionByteArrayRef"); + .usingRecursiveComparison() + .ignoringFields("originalPersistentState", "customValuesByteArrayRef", "exceptionByteArrayRef") + .isEqualTo(timerJob2); assertThat(timerJob1) .extracting(Job::getExecutionId) .isNotEqualTo(timerJob2); @@ -2530,7 +2536,9 @@ public void testMigrateActivityToActivityWithTimerInsideEmbeddedSubProcessInNewD Execution timerExecution = runtimeService.createExecutionQuery().parentId(task.getExecutionId()).singleResult(); Job timerFromTask = managementService.createTimerJobQuery().executionId(timerExecution.getId()).singleResult(); assertThat(timerJob) - .isEqualToIgnoringGivenFields(timerFromTask, "originalPersistentState", "customValuesByteArrayRef", "exceptionByteArrayRef"); + .usingRecursiveComparison() + .ignoringFields("originalPersistentState", "customValuesByteArrayRef", "exceptionByteArrayRef") + .isEqualTo(timerFromTask); // Verify events assertThat(changeStateEventListener.hasEvents()).isTrue(); @@ -2619,7 +2627,9 @@ public void testMigrateActivityToActivityWithTimerInsideEmbeddedSubProcessInNewD Execution timerExecution = runtimeService.createExecutionQuery().parentId(task.getExecutionId()).singleResult(); Job timerFromTask = managementService.createTimerJobQuery().executionId(timerExecution.getId()).singleResult(); assertThat(timerJob) - .isEqualToIgnoringGivenFields(timerFromTask, "originalPersistentState", "customValuesByteArrayRef", "exceptionByteArrayRef"); + .usingRecursiveComparison() + .ignoringFields("originalPersistentState", "customValuesByteArrayRef", "exceptionByteArrayRef") + .isEqualTo(timerFromTask); // Verify events assertThat(changeStateEventListener.hasEvents()).isTrue(); diff --git a/modules/flowable-http/src/test/java/org/flowable/http/bpmn/HttpServiceTaskTest.java b/modules/flowable-http/src/test/java/org/flowable/http/bpmn/HttpServiceTaskTest.java index 53e1f479418..5f8eeb46275 100644 --- a/modules/flowable-http/src/test/java/org/flowable/http/bpmn/HttpServiceTaskTest.java +++ b/modules/flowable-http/src/test/java/org/flowable/http/bpmn/HttpServiceTaskTest.java @@ -285,7 +285,7 @@ public void testConnectTimeout() { public void testRequestTimeout() { assertThatThrownBy(() -> runtimeService.startProcessInstanceByKey("requestTimeout")) .isExactlyInstanceOf(FlowableException.class) - .getCause().isInstanceOfAny(SocketTimeoutException.class, SocketException.class); + .cause().isInstanceOfAny(SocketTimeoutException.class, SocketException.class); } @Test diff --git a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/test/java/org/flowable/spring/boot/ldap/FlowableLdapPropertiesTest.java b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/test/java/org/flowable/spring/boot/ldap/FlowableLdapPropertiesTest.java index 94d5f76fb46..762da8beae1 100644 --- a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/test/java/org/flowable/spring/boot/ldap/FlowableLdapPropertiesTest.java +++ b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/test/java/org/flowable/spring/boot/ldap/FlowableLdapPropertiesTest.java @@ -69,7 +69,8 @@ public void shouldCorrectlyCustomizeLdapConfiguration() { assertThat(ldapConfiguration) .as("Base Ldap Configuration") - .isEqualToIgnoringGivenFields(properties, + .usingRecursiveComparison() + .ignoringFields( "queryUserByUserId", "queryUserByFullNameLike", "queryAllUsers", @@ -87,7 +88,8 @@ public void shouldCorrectlyCustomizeLdapConfiguration() { "groupCacheExpirationTime", "ldapQueryBuilder", "groupCacheListener" - ); + ) + .isEqualTo(properties); assertThat(ldapConfiguration) .as("Query properties") diff --git a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/test/java/org/flowable/test/spring/boot/process/ProcessEngineAutoConfigurationTest.java b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/test/java/org/flowable/test/spring/boot/process/ProcessEngineAutoConfigurationTest.java index 5c0828aea7f..08b1c1536d8 100644 --- a/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/test/java/org/flowable/test/spring/boot/process/ProcessEngineAutoConfigurationTest.java +++ b/modules/flowable-spring-boot/flowable-spring-boot-starters/flowable-spring-boot-autoconfigure/src/test/java/org/flowable/test/spring/boot/process/ProcessEngineAutoConfigurationTest.java @@ -446,7 +446,8 @@ public void processEngineWithCustomIdGenerator() { assertThat(dbIdGenerator.getIdBlockSize()).isEqualTo(engineConfiguration.getIdBlockSize()); assertThat(dbIdGenerator.getCommandExecutor()).isEqualTo(engineConfiguration.getCommandExecutor()); assertThat(dbIdGenerator.getCommandConfig()) - .isEqualToComparingFieldByField(engineConfiguration.getDefaultCommandConfig().transactionRequiresNew()); + .usingRecursiveComparison() + .isEqualTo(engineConfiguration.getDefaultCommandConfig().transactionRequiresNew()); }); }); } @@ -467,7 +468,8 @@ public void processEngineWithCustomIdGeneratorAsBean() { assertThat(dbIdGenerator.getIdBlockSize()).isEqualTo(engineConfiguration.getIdBlockSize()); assertThat(dbIdGenerator.getCommandExecutor()).isEqualTo(engineConfiguration.getCommandExecutor()); assertThat(dbIdGenerator.getCommandConfig()) - .isEqualToComparingFieldByField(engineConfiguration.getDefaultCommandConfig().transactionRequiresNew()); + .usingRecursiveComparison() + .isEqualTo(engineConfiguration.getDefaultCommandConfig().transactionRequiresNew()); }) .isEqualTo(context.getBean(IdGenerator.class)); }); From b1ab9a717a4c1576f6f43ca40782f07620eaeed1 Mon Sep 17 00:00:00 2001 From: BertMatthys <84137009+BertMatthys@users.noreply.github.com> Date: Mon, 19 Feb 2024 10:41:59 +0100 Subject: [PATCH 12/15] Make sure NotFoundException for decision execution is thrown before possible NPE in restApiInterceptor --- .../api/history/BaseHistoricDecisionExecutionResource.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/flowable-dmn-rest/src/main/java/org/flowable/dmn/rest/service/api/history/BaseHistoricDecisionExecutionResource.java b/modules/flowable-dmn-rest/src/main/java/org/flowable/dmn/rest/service/api/history/BaseHistoricDecisionExecutionResource.java index e6a53797bf2..97680b56100 100644 --- a/modules/flowable-dmn-rest/src/main/java/org/flowable/dmn/rest/service/api/history/BaseHistoricDecisionExecutionResource.java +++ b/modules/flowable-dmn-rest/src/main/java/org/flowable/dmn/rest/service/api/history/BaseHistoricDecisionExecutionResource.java @@ -51,13 +51,14 @@ protected DmnHistoricDecisionExecution getHistoricDecisionExecutionFromRequest(S DmnHistoricDecisionExecution decisionExecution = historicDecisionExecutionQuery.singleResult(); + if (decisionExecution == null) { + throw new FlowableObjectNotFoundException("Could not find a decision execution with id '" + decisionExecutionId + "'"); + } + if (restApiInterceptor != null) { restApiInterceptor.accessDecisionHistoryInfoById(decisionExecution); } - if (decisionExecution == null) { - throw new FlowableObjectNotFoundException("Could not find a decision execution with id '" + decisionExecutionId + "'"); - } return decisionExecution; } From 5928971ae2fe38cd36516d2645070395a83dc93a Mon Sep 17 00:00:00 2001 From: Tijs Rademakers Date: Mon, 19 Feb 2024 12:22:20 +0100 Subject: [PATCH 13/15] Add support for local variables for stages for case instance migration --- ...eToAvailablePlanItemDefinitionMapping.java | 18 ++ .../PlanItemDefinitionMappingBuilder.java | 6 + .../runtime/ChangePlanItemStateBuilder.java | 6 + .../engine/impl/agenda/CmmnEngineAgenda.java | 2 + .../impl/agenda/DefaultCmmnEngineAgenda.java | 8 + .../AbstractEvaluationCriteriaOperation.java | 16 +- .../operation/EvaluateCriteriaOperation.java | 14 +- .../CaseInstanceMigrationManagerImpl.java | 2 +- .../entity/PlanItemInstanceEntity.java | 3 + .../entity/PlanItemInstanceEntityImpl.java | 11 + .../AbstractCmmnDynamicStateManager.java | 12 +- .../ChangePlanItemStateBuilderImpl.java | 6 + .../engine/interceptor/MigrationContext.java | 9 + .../migration/CaseInstanceMigrationTest.java | 263 +++++++++++++++++- .../stage-local-variables-extra-task.cmmn.xml | 27 ++ .../migration/stage-local-variables.cmmn.xml | 25 ++ ...tition-local-variables-extra-task.cmmn.xml | 42 +++ .../stage-repetition-local-variables.cmmn.xml | 45 +++ .../migration/task-local-variables.cmmn.xml | 9 + 19 files changed, 510 insertions(+), 14 deletions(-) create mode 100644 modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/migration/stage-local-variables-extra-task.cmmn.xml create mode 100644 modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/migration/stage-local-variables.cmmn.xml create mode 100644 modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/migration/stage-repetition-local-variables-extra-task.cmmn.xml create mode 100644 modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/migration/stage-repetition-local-variables.cmmn.xml create mode 100644 modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/migration/task-local-variables.cmmn.xml diff --git a/modules/flowable-cmmn-api/src/main/java/org/flowable/cmmn/api/migration/MoveToAvailablePlanItemDefinitionMapping.java b/modules/flowable-cmmn-api/src/main/java/org/flowable/cmmn/api/migration/MoveToAvailablePlanItemDefinitionMapping.java index 90101f14c96..dd8b8e1145c 100644 --- a/modules/flowable-cmmn-api/src/main/java/org/flowable/cmmn/api/migration/MoveToAvailablePlanItemDefinitionMapping.java +++ b/modules/flowable-cmmn-api/src/main/java/org/flowable/cmmn/api/migration/MoveToAvailablePlanItemDefinitionMapping.java @@ -12,9 +12,27 @@ */ package org.flowable.cmmn.api.migration; +import java.util.LinkedHashMap; +import java.util.Map; + public class MoveToAvailablePlanItemDefinitionMapping extends PlanItemDefinitionMapping { + + protected Map withLocalVariables = new LinkedHashMap<>(); public MoveToAvailablePlanItemDefinitionMapping(String planItemDefinitionId) { super(planItemDefinitionId); } + + public MoveToAvailablePlanItemDefinitionMapping(String planItemDefinitionId, Map withLocalVariables) { + super(planItemDefinitionId); + this.withLocalVariables = withLocalVariables; + } + + public Map getWithLocalVariables() { + return withLocalVariables; + } + + public void setWithLocalVariables(Map withLocalVariables) { + this.withLocalVariables = withLocalVariables; + } } diff --git a/modules/flowable-cmmn-api/src/main/java/org/flowable/cmmn/api/migration/PlanItemDefinitionMappingBuilder.java b/modules/flowable-cmmn-api/src/main/java/org/flowable/cmmn/api/migration/PlanItemDefinitionMappingBuilder.java index 190a92c4d0b..0bc49afde71 100644 --- a/modules/flowable-cmmn-api/src/main/java/org/flowable/cmmn/api/migration/PlanItemDefinitionMappingBuilder.java +++ b/modules/flowable-cmmn-api/src/main/java/org/flowable/cmmn/api/migration/PlanItemDefinitionMappingBuilder.java @@ -38,6 +38,12 @@ public static MoveToAvailablePlanItemDefinitionMapping createMoveToAvailablePlan return new MoveToAvailablePlanItemDefinitionMapping(planItemDefinitionId); } + public static MoveToAvailablePlanItemDefinitionMapping createMoveToAvailablePlanItemDefinitionMappingFor( + String planItemDefinitionId, Map withLocalVariables) { + + return new MoveToAvailablePlanItemDefinitionMapping(planItemDefinitionId, withLocalVariables); + } + public static WaitingForRepetitionPlanItemDefinitionMapping createWaitingForRepetitionPlanItemDefinitionMappingFor(String planItemDefinitionId) { return new WaitingForRepetitionPlanItemDefinitionMapping(planItemDefinitionId); } diff --git a/modules/flowable-cmmn-api/src/main/java/org/flowable/cmmn/api/runtime/ChangePlanItemStateBuilder.java b/modules/flowable-cmmn-api/src/main/java/org/flowable/cmmn/api/runtime/ChangePlanItemStateBuilder.java index d361ef8791c..232960515d7 100644 --- a/modules/flowable-cmmn-api/src/main/java/org/flowable/cmmn/api/runtime/ChangePlanItemStateBuilder.java +++ b/modules/flowable-cmmn-api/src/main/java/org/flowable/cmmn/api/runtime/ChangePlanItemStateBuilder.java @@ -16,6 +16,7 @@ import java.util.Map; import org.flowable.cmmn.api.migration.ActivatePlanItemDefinitionMapping; +import org.flowable.cmmn.api.migration.MoveToAvailablePlanItemDefinitionMapping; import org.flowable.common.engine.api.FlowableException; import org.flowable.common.engine.api.FlowableObjectNotFoundException; @@ -63,6 +64,11 @@ public interface ChangePlanItemStateBuilder { */ ChangePlanItemStateBuilder changeToAvailableStateByPlanItemDefinitionIds(List planItemDefinitionIds); + /** + * Set a plan item to available state by definition mapping. + */ + ChangePlanItemStateBuilder changeToAvailableStateByPlanItemDefinition(MoveToAvailablePlanItemDefinitionMapping planItemDefinitionMapping); + /** * Terminate a plan item by definition id without terminating another plan item instance. */ diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/agenda/CmmnEngineAgenda.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/agenda/CmmnEngineAgenda.java index a9f8debf953..5423f6eff63 100644 --- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/agenda/CmmnEngineAgenda.java +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/agenda/CmmnEngineAgenda.java @@ -92,6 +92,8 @@ public interface CmmnEngineAgenda extends Agenda { void planEvaluateCriteriaOperation(String caseInstanceEntityId, PlanItemLifeCycleEvent lifeCycleEvent); + void planEvaluateCriteriaOperation(String caseInstanceEntityId, MigrationContext migrationContext); + void planEvaluateVariableEventListenersOperation(String caseInstanceEntityId); } diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/agenda/DefaultCmmnEngineAgenda.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/agenda/DefaultCmmnEngineAgenda.java index ac9ce880e92..66aae05a5fd 100644 --- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/agenda/DefaultCmmnEngineAgenda.java +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/agenda/DefaultCmmnEngineAgenda.java @@ -144,6 +144,14 @@ public void planEvaluateCriteriaOperation(String caseInstanceEntityId, PlanItemL internalPlanEvaluateCriteria(caseInstanceEntityId, lifeCycleEvent, false); } + @Override + public void planEvaluateCriteriaOperation(String caseInstanceEntityId, MigrationContext migrationContext) { + EvaluateCriteriaOperation evaluateCriteriaOperation = new EvaluateCriteriaOperation(commandContext, caseInstanceEntityId, null); + evaluateCriteriaOperation.setEvaluateStagesAndCaseInstanceCompletion(false); + evaluateCriteriaOperation.setMigrationContext(migrationContext); + addOperation(evaluateCriteriaOperation); + } + protected void internalPlanEvaluateCriteria(String caseInstanceEntityId, PlanItemLifeCycleEvent planItemLifeCycleEvent, boolean evaluateCaseInstanceCompleted) { EvaluateCriteriaOperation evaluateCriteriaOperation = new EvaluateCriteriaOperation(commandContext, caseInstanceEntityId, planItemLifeCycleEvent); evaluateCriteriaOperation.setEvaluateStagesAndCaseInstanceCompletion(evaluateCaseInstanceCompleted); diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/agenda/operation/AbstractEvaluationCriteriaOperation.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/agenda/operation/AbstractEvaluationCriteriaOperation.java index 79eadcd96d2..152be2b31b2 100644 --- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/agenda/operation/AbstractEvaluationCriteriaOperation.java +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/agenda/operation/AbstractEvaluationCriteriaOperation.java @@ -44,6 +44,7 @@ import org.flowable.cmmn.engine.impl.util.ExpressionUtil; import org.flowable.cmmn.engine.impl.util.PlanItemInstanceContainerUtil; import org.flowable.cmmn.engine.impl.util.PlanItemInstanceUtil; +import org.flowable.cmmn.engine.interceptor.MigrationContext; import org.flowable.cmmn.model.Criterion; import org.flowable.cmmn.model.EventListener; import org.flowable.cmmn.model.HasExitCriteria; @@ -107,6 +108,7 @@ public void evaluateForActivation(PlanItemInstanceEntity planItemInstanceEntity, // if we need to activate the plan item, mark the result as some criteria changed and plan the activation of the plan item by adding // this as an operation to the agenda if (activatePlanItemInstance) { + planItemInstanceEntity.setPlannedForActivationInMigration(true); evaluationResult.markCriteriaChanged(); CommandContextUtil.getAgenda(commandContext) .planActivatePlanItemInstanceOperation(planItemInstanceEntity, satisfiedEntryCriterion != null ? satisfiedEntryCriterion.getId() : null); @@ -144,7 +146,7 @@ public boolean evaluateForCompletion(PlanItemInstanceEntity planItemInstanceEnti } else if (planItem.getPlanItemDefinition() instanceof Stage) { if (PlanItemInstanceState.ACTIVE.equals(state)) { - boolean criteriaChangeOrActiveChildrenForStage = evaluatePlanItemsCriteria(planItemInstanceEntity); + boolean criteriaChangeOrActiveChildrenForStage = evaluatePlanItemsCriteria(planItemInstanceEntity, null); if (criteriaChangeOrActiveChildrenForStage) { evaluationResult.markCriteriaChanged(); planItemInstanceEntity.setCompletable(false); // an active child = stage cannot be completed anymore @@ -189,15 +191,21 @@ public boolean evaluateForCompletion(PlanItemInstanceEntity planItemInstanceEnti * Returns false if no sentry changes happened and none of the passed plan item instances are active. * This means that the parent of these plan item instances also now can change its state. */ - protected boolean evaluatePlanItemsCriteria(PlanItemInstanceContainer planItemInstanceContainer) { + protected boolean evaluatePlanItemsCriteria(PlanItemInstanceContainer planItemInstanceContainer, MigrationContext migrationContext) { List planItemInstances = planItemInstanceContainer.getChildPlanItemInstances(); // needed because when doing case instance migration the child plan item instances can be null - if (planItemInstances == null && planItemInstanceContainer instanceof CaseInstanceEntity) { + if ((planItemInstances == null || (migrationContext != null && migrationContext.isFetchPlanItemInstances())) && + planItemInstanceContainer instanceof CaseInstanceEntity) { + PlanItemInstanceEntityManager planItemInstanceEntityManager = CommandContextUtil.getPlanItemInstanceEntityManager(commandContext); CaseInstanceEntity caseInstance = (CaseInstanceEntity) planItemInstanceContainer; planItemInstances = planItemInstanceEntityManager.findByCaseInstanceId(caseInstance.getId()); planItemInstanceContainer.setChildPlanItemInstances(planItemInstances); + + if (migrationContext != null && migrationContext.isFetchPlanItemInstances()) { + migrationContext.setFetchPlanItemInstances(false); + } } // create an evaluation result object, holding all evaluation results as well as a list of newly created child plan items, as to avoid concurrent @@ -212,7 +220,7 @@ protected boolean evaluatePlanItemsCriteria(PlanItemInstanceContainer planItemIn String state = planItemInstanceEntity.getState(); // check, if the plan item is in an evaluation state (e.g. available or waiting for repetition) to check for its activation - if (PlanItemInstanceState.EVALUATE_ENTRY_CRITERIA_STATES.contains(state)) { + if (PlanItemInstanceState.EVALUATE_ENTRY_CRITERIA_STATES.contains(state) && !planItemInstanceEntity.isPlannedForActivationInMigration()) { evaluateForActivation(planItemInstanceEntity, planItemInstanceContainer, evaluationResult); } diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/agenda/operation/EvaluateCriteriaOperation.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/agenda/operation/EvaluateCriteriaOperation.java index ec109c15779..5395af2efa4 100644 --- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/agenda/operation/EvaluateCriteriaOperation.java +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/agenda/operation/EvaluateCriteriaOperation.java @@ -15,6 +15,7 @@ import org.flowable.cmmn.api.runtime.CaseInstanceState; import org.flowable.cmmn.engine.impl.criteria.PlanItemLifeCycleEvent; import org.flowable.cmmn.engine.impl.util.CommandContextUtil; +import org.flowable.cmmn.engine.interceptor.MigrationContext; import org.flowable.cmmn.model.Criterion; import org.flowable.common.engine.impl.interceptor.CommandContext; import org.slf4j.Logger; @@ -27,6 +28,8 @@ public class EvaluateCriteriaOperation extends AbstractEvaluationCriteriaOperation { private static final Logger LOGGER = LoggerFactory.getLogger(EvaluateCriteriaOperation.class); + + protected MigrationContext migrationContext; public EvaluateCriteriaOperation(CommandContext commandContext, String caseInstanceEntityId) { super(commandContext, caseInstanceEntityId, null, null); @@ -53,7 +56,7 @@ public void run() { satisfiedExitCriterion.getExitType(), satisfiedExitCriterion.getExitEventType()); } else { - boolean criteriaChangeOrActiveChildren = evaluatePlanItemsCriteria(caseInstanceEntity); + boolean criteriaChangeOrActiveChildren = evaluatePlanItemsCriteria(caseInstanceEntity, migrationContext); if (evaluateStagesAndCaseInstanceCompletion && evaluatePlanModelComplete() && !criteriaChangeOrActiveChildren @@ -91,5 +94,12 @@ public String toString() { return stringBuilder.toString(); } - + + public MigrationContext getMigrationContext() { + return migrationContext; + } + + public void setMigrationContext(MigrationContext migrationContext) { + this.migrationContext = migrationContext; + } } diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/migration/CaseInstanceMigrationManagerImpl.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/migration/CaseInstanceMigrationManagerImpl.java index 2b1dde9173a..ec189f7ef67 100644 --- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/migration/CaseInstanceMigrationManagerImpl.java +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/migration/CaseInstanceMigrationManagerImpl.java @@ -371,7 +371,7 @@ protected ChangePlanItemStateBuilderImpl prepareChangeStateBuilder(CaseInstance } for (MoveToAvailablePlanItemDefinitionMapping planItemDefinitionMapping : document.getMoveToAvailablePlanItemDefinitionMappings()) { - changePlanItemStateBuilder.changeToAvailableStateByPlanItemDefinitionId(planItemDefinitionMapping.getPlanItemDefinitionId()); + changePlanItemStateBuilder.changeToAvailableStateByPlanItemDefinition(planItemDefinitionMapping); } for (WaitingForRepetitionPlanItemDefinitionMapping planItemDefinitionMapping : document.getWaitingForRepetitionPlanItemDefinitionMappings()) { diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/persistence/entity/PlanItemInstanceEntity.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/persistence/entity/PlanItemInstanceEntity.java index b4dc7a33c03..0ab87907f19 100644 --- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/persistence/entity/PlanItemInstanceEntity.java +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/persistence/entity/PlanItemInstanceEntity.java @@ -26,4 +26,7 @@ public interface PlanItemInstanceEntity extends Entity, HasRevision, DelegatePla VariableScope getParentVariableScope(); + boolean isPlannedForActivationInMigration(); + + void setPlannedForActivationInMigration(boolean plannedForActivationInMigration); } diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/persistence/entity/PlanItemInstanceEntityImpl.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/persistence/entity/PlanItemInstanceEntityImpl.java index c6cb0be4463..94df68d8934 100644 --- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/persistence/entity/PlanItemInstanceEntityImpl.java +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/persistence/entity/PlanItemInstanceEntityImpl.java @@ -92,6 +92,7 @@ public class PlanItemInstanceEntityImpl extends AbstractCmmnEngineVariableScopeE protected PlanItemInstanceLifecycleListener currentLifecycleListener; // Only set when executing an plan item lifecycle listener protected FlowableListener currentFlowableListener; // Only set when executing an plan item lifecycle listener + protected boolean plannedForActivationInMigration; public PlanItemInstanceEntityImpl() { } @@ -646,6 +647,16 @@ public void setLocalizedName(String localizedName) { this.localizedName = localizedName; } + @Override + public boolean isPlannedForActivationInMigration() { + return plannedForActivationInMigration; + } + + @Override + public void setPlannedForActivationInMigration(boolean plannedForActivationInMigration) { + this.plannedForActivationInMigration = plannedForActivationInMigration; + } + @Override public String toString() { StringBuilder stringBuilder = new StringBuilder(); 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 9ad975fb52e..338b632e2dc 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 @@ -147,7 +147,9 @@ protected void doMovePlanItemState(CaseInstanceChangeState caseInstanceChangeSta executeRemoveWaitingForRepetitionPlanItemInstances(caseInstanceChangeState, caseInstance, commandContext); CmmnEngineAgenda agenda = CommandContextUtil.getAgenda(commandContext); - agenda.planEvaluateCriteriaOperation(caseInstance.getId()); + MigrationContext migrationContext = new MigrationContext(); + migrationContext.setFetchPlanItemInstances(true); + agenda.planEvaluateCriteriaOperation(caseInstance.getId(), migrationContext); } protected void executeChangePlanItemIds(CaseInstanceChangeState caseInstanceChangeState, String originalCaseDefinitionId, CommandContext commandContext) { @@ -228,6 +230,10 @@ protected void executeActivatePlanItemInstances(CaseInstanceChangeState caseInst PlanItemInstanceEntity newPlanItemInstance = createStagesAndPlanItemInstances(planItem, caseInstance, caseInstanceChangeState, commandContext); + + if (planItemDefinitionMapping.getWithLocalVariables() != null && !planItemDefinitionMapping.getWithLocalVariables().isEmpty()) { + newPlanItemInstance.setVariablesLocal(planItemDefinitionMapping.getWithLocalVariables()); + } CmmnEngineAgenda agenda = CommandContextUtil.getAgenda(commandContext); if (planItemDefinitionMapping.getNewAssignee() != null && planItem.getPlanItemDefinition() instanceof HumanTask) { @@ -316,6 +322,10 @@ protected void executeChangePlanItemInstancesToAvailableState(CaseInstanceChange caseInstanceChangeState.addCreatedStageInstance(planItemDefinitionMapping.getPlanItemDefinitionId(), availablePlanItemInstance); } + if (planItemDefinitionMapping.getWithLocalVariables() != null && !planItemDefinitionMapping.getWithLocalVariables().isEmpty()) { + availablePlanItemInstance.setVariablesLocal(planItemDefinitionMapping.getWithLocalVariables()); + } + CmmnHistoryManager cmmnHistoryManager = cmmnEngineConfiguration.getCmmnHistoryManager(); cmmnHistoryManager.recordPlanItemInstanceCreated(availablePlanItemInstance); diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/runtime/ChangePlanItemStateBuilderImpl.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/runtime/ChangePlanItemStateBuilderImpl.java index 71e4f04a9ec..72205d50e02 100644 --- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/runtime/ChangePlanItemStateBuilderImpl.java +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/runtime/ChangePlanItemStateBuilderImpl.java @@ -103,6 +103,12 @@ public ChangePlanItemStateBuilder changeToAvailableStateByPlanItemDefinitionIds( return this; } + @Override + public ChangePlanItemStateBuilder changeToAvailableStateByPlanItemDefinition(MoveToAvailablePlanItemDefinitionMapping planItemDefinitionMapping) { + changeToAvailableStatePlanItemDefinitions.add(planItemDefinitionMapping); + return this; + } + @Override public ChangePlanItemStateBuilder terminatePlanItemDefinitionId(String planItemDefinitionId) { terminatePlanItemDefinitions.add(new TerminatePlanItemDefinitionMapping(planItemDefinitionId)); diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/interceptor/MigrationContext.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/interceptor/MigrationContext.java index babe86db66c..531701e69e9 100644 --- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/interceptor/MigrationContext.java +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/interceptor/MigrationContext.java @@ -14,7 +14,16 @@ public class MigrationContext { + protected boolean fetchPlanItemInstances; protected String assignee; + + public boolean isFetchPlanItemInstances() { + return fetchPlanItemInstances; + } + + public void setFetchPlanItemInstances(boolean fetchPlanItemInstances) { + this.fetchPlanItemInstances = fetchPlanItemInstances; + } public String getAssignee() { return assignee; diff --git a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/migration/CaseInstanceMigrationTest.java b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/migration/CaseInstanceMigrationTest.java index a7a4c39ff06..dc258466912 100644 --- a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/migration/CaseInstanceMigrationTest.java +++ b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/migration/CaseInstanceMigrationTest.java @@ -404,18 +404,18 @@ void withTwoTasksIntroducingANewStageAroundSecondTask() { assertThat(planItem2.getCaseDefinitionId()).isEqualTo(destinationDefinition.getId()); assertThat(planItem2.getName()).isEqualTo("Task 2"); assertThat(planItem2.getStageInstanceId()).isNotNull(); - assertThat(planItem2.getState()).isEqualTo(PlanItemInstanceState.AVAILABLE); + assertThat(planItem2.getState()).isEqualTo(PlanItemInstanceState.ACTIVE); PlanItemInstance planItem3 = planItemsByElementId.get("planItem3").get(0); assertThat(planItem3.getCaseDefinitionId()).isEqualTo(destinationDefinition.getId()); assertThat(planItem3.getPlanItemDefinitionId()).isEqualTo("expandedStage1"); assertThat(planItem3.getState()).isEqualTo(PlanItemInstanceState.AVAILABLE); - Task task = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).singleResult(); + Task task = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).taskDefinitionKey("humanTask1").singleResult(); assertThat(task.getTaskDefinitionKey()).isEqualTo("humanTask1"); assertThat(task.getScopeDefinitionId()).isEqualTo(destinationDefinition.getId()); cmmnTaskService.complete(task.getId()); - task = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).singleResult(); + task = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).taskDefinitionKey("humanTask2").singleResult(); assertThat(task.getTaskDefinitionKey()).isEqualTo("humanTask2"); assertThat(task.getScopeDefinitionId()).isEqualTo(destinationDefinition.getId()); cmmnTaskService.complete(task.getId()); @@ -4395,10 +4395,261 @@ void activatePlanItemWithCompletedStage() { assertThat(historicPlanItemInstances.size()).isEqualTo(1); assertThat(historicPlanItemInstances.get(0).getState()).isEqualTo("completed"); } + + @Test + void migrateCaseInstancesWithLocalVariablesForStage() { + // Arrange + deployCaseDefinition("test1", "org/flowable/cmmn/test/migration/stage-local-variables.cmmn.xml"); + CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder().caseDefinitionKey("example-stage-case").start(); + CaseDefinition destinationDefinition = deployCaseDefinition("test1", "org/flowable/cmmn/test/migration/stage-local-variables-extra-task.cmmn.xml"); + + assertThat(cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).count()).isEqualTo(1); + + Map localStageVarMap = new HashMap<>(); + localStageVarMap.put("stageNr", 1); + + // Act + cmmnMigrationService.createCaseInstanceMigrationBuilder() + .migrateToCaseDefinition(destinationDefinition.getId()) + .addActivatePlanItemDefinitionMapping(PlanItemDefinitionMappingBuilder.createActivatePlanItemDefinitionMappingFor("extra-task")) + .addActivatePlanItemDefinitionMapping(PlanItemDefinitionMappingBuilder.createActivatePlanItemDefinitionMappingFor("stage", null, localStageVarMap)) + .migrateCaseInstances(caseInstance.getCaseDefinitionId()); + + // Assert + CaseInstance caseInstanceAfterMigration = cmmnRuntimeService.createCaseInstanceQuery() + .caseInstanceId(caseInstance.getId()) + .singleResult(); + assertThat(caseInstanceAfterMigration.getCaseDefinitionId()).isEqualTo(destinationDefinition.getId()); + List planItemInstances = cmmnRuntimeService.createPlanItemInstanceQuery() + .caseInstanceId(caseInstance.getId()) + .includeEnded() + .list(); + assertThat(planItemInstances).hasSize(4); + assertThat(planItemInstances) + .extracting(PlanItemInstance::getCaseDefinitionId) + .containsOnly(destinationDefinition.getId()); + assertThat(planItemInstances) + .extracting(PlanItemInstance::getName) + .containsExactlyInAnyOrder("Start Task", "Stage", "Stage Task 1", "Extra Task"); + assertThat(planItemInstances) + .filteredOn("name", "Start Task") + .extracting(PlanItemInstance::getState) + .containsOnly(PlanItemInstanceState.ACTIVE); + + assertThat(planItemInstances) + .filteredOn("name", "Stage") + .extracting(PlanItemInstance::getState) + .containsOnly(PlanItemInstanceState.ACTIVE); + + assertThat(planItemInstances) + .filteredOn("name", "Stage Task 1") + .extracting(PlanItemInstance::getState) + .containsOnly(PlanItemInstanceState.ACTIVE); + + assertThat(planItemInstances) + .filteredOn("name", "Extra Task") + .extracting(PlanItemInstance::getState) + .containsOnly(PlanItemInstanceState.ACTIVE); + + assertThat(cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).count()).isEqualTo(3); + + Task task = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).taskDefinitionKey("stage-task").singleResult(); + cmmnTaskService.complete(task.getId()); + + assertThat(cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).count()).isEqualTo(2); + + task = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).taskDefinitionKey("task").singleResult(); + cmmnTaskService.complete(task.getId()); + + assertThat(cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).count()).isEqualTo(1); + + task = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).taskDefinitionKey("extra-task").singleResult(); + cmmnTaskService.complete(task.getId()); + + assertThat(cmmnRuntimeService.createCaseInstanceQuery().caseInstanceId(caseInstance.getId()).count()).isZero(); + + if (CmmnHistoryTestHelper.isHistoryLevelAtLeast(HistoryLevel.ACTIVITY, cmmnEngineConfiguration)) { + assertThat(cmmnHistoryService.createHistoricCaseInstanceQuery().caseInstanceId(caseInstance.getId()).count()).isEqualTo(1); + assertThat(cmmnHistoryService.createHistoricCaseInstanceQuery().caseInstanceId(caseInstance.getId()).singleResult().getCaseDefinitionId()) + .isEqualTo(destinationDefinition.getId()); + + List historicPlanItemInstances = cmmnHistoryService.createHistoricPlanItemInstanceQuery() + .planItemInstanceCaseInstanceId(caseInstance.getId()).list(); + assertThat(historicPlanItemInstances).hasSize(4); + for (HistoricPlanItemInstance historicPlanItemInstance : historicPlanItemInstances) { + assertThat(historicPlanItemInstance.getCaseDefinitionId()).isEqualTo(destinationDefinition.getId()); + } + + List historicTasks = cmmnHistoryService.createHistoricTaskInstanceQuery().caseInstanceId(caseInstance.getId()).list(); + assertThat(historicTasks).hasSize(3); + for (HistoricTaskInstance historicTask : historicTasks) { + assertThat(historicTask.getScopeDefinitionId()).isEqualTo(destinationDefinition.getId()); + } + } + } + + @Test + void migrateCaseInstancesWithLocalVariablesForRepeatingStage() { + // Arrange + deployCaseDefinition("test1", "org/flowable/cmmn/test/migration/stage-repetition-local-variables.cmmn.xml"); + CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder().caseDefinitionKey("example-stage-case").start(); + CaseDefinition destinationDefinition = deployCaseDefinition("test1", "org/flowable/cmmn/test/migration/stage-repetition-local-variables-extra-task.cmmn.xml"); + + assertThat(cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).count()).isEqualTo(2); + + Map localStageVarMap = new HashMap<>(); + localStageVarMap.put("stageNr", 1); + + // Act + cmmnMigrationService.createCaseInstanceMigrationBuilder() + .migrateToCaseDefinition(destinationDefinition.getId()) + .addActivatePlanItemDefinitionMapping(PlanItemDefinitionMappingBuilder.createActivatePlanItemDefinitionMappingFor("extra-task")) + .addActivatePlanItemDefinitionMapping(PlanItemDefinitionMappingBuilder.createActivatePlanItemDefinitionMappingFor("repeating-stage", null, localStageVarMap)) + .migrateCaseInstances(caseInstance.getCaseDefinitionId()); + + // Assert + CaseInstance caseInstanceAfterMigration = cmmnRuntimeService.createCaseInstanceQuery() + .caseInstanceId(caseInstance.getId()) + .singleResult(); + assertThat(caseInstanceAfterMigration.getCaseDefinitionId()).isEqualTo(destinationDefinition.getId()); + List planItemInstances = cmmnRuntimeService.createPlanItemInstanceQuery() + .caseInstanceId(caseInstance.getId()) + .includeEnded() + .list(); + assertThat(planItemInstances).hasSize(6); + assertThat(planItemInstances) + .extracting(PlanItemInstance::getCaseDefinitionId) + .containsOnly(destinationDefinition.getId()); + assertThat(planItemInstances) + .extracting(PlanItemInstance::getName) + .containsExactlyInAnyOrder("Exit Task", "Start Repeating Task", "Repeating Stage", "Repeating Stage", "Stage repeating Task 1", "Extra Task"); + assertThat(planItemInstances) + .filteredOn("name", "Start Repeating Task") + .extracting(PlanItemInstance::getState) + .containsOnly(PlanItemInstanceState.ACTIVE); + + assertThat(planItemInstances) + .filteredOn("name", "Repeating Stage") + .extracting(PlanItemInstance::getState) + .containsOnly(PlanItemInstanceState.ACTIVE, PlanItemInstanceState.WAITING_FOR_REPETITION); + + assertThat(planItemInstances) + .filteredOn("name", "Stage repeating Task 1") + .extracting(PlanItemInstance::getState) + .containsOnly(PlanItemInstanceState.ACTIVE); + + assertThat(planItemInstances) + .filteredOn("name", "Extra Task") + .extracting(PlanItemInstance::getState) + .containsOnly(PlanItemInstanceState.ACTIVE); + + assertThat(cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).count()).isEqualTo(4); + + Task task = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).taskDefinitionKey("stage-repeating-task").singleResult(); + cmmnTaskService.complete(task.getId()); + + assertThat(cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).count()).isEqualTo(3); + + task = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).taskDefinitionKey("exit-task").singleResult(); + cmmnTaskService.complete(task.getId()); + + assertThat(cmmnRuntimeService.createCaseInstanceQuery().caseInstanceId(caseInstance.getId()).count()).isZero(); + + if (CmmnHistoryTestHelper.isHistoryLevelAtLeast(HistoryLevel.ACTIVITY, cmmnEngineConfiguration)) { + assertThat(cmmnHistoryService.createHistoricCaseInstanceQuery().caseInstanceId(caseInstance.getId()).count()).isEqualTo(1); + assertThat(cmmnHistoryService.createHistoricCaseInstanceQuery().caseInstanceId(caseInstance.getId()).singleResult().getCaseDefinitionId()) + .isEqualTo(destinationDefinition.getId()); + + List historicPlanItemInstances = cmmnHistoryService.createHistoricPlanItemInstanceQuery() + .planItemInstanceCaseInstanceId(caseInstance.getId()).list(); + assertThat(historicPlanItemInstances).hasSize(6); + for (HistoricPlanItemInstance historicPlanItemInstance : historicPlanItemInstances) { + assertThat(historicPlanItemInstance.getCaseDefinitionId()).isEqualTo(destinationDefinition.getId()); + } + + List historicTasks = cmmnHistoryService.createHistoricTaskInstanceQuery().caseInstanceId(caseInstance.getId()).list(); + assertThat(historicTasks).hasSize(4); + for (HistoricTaskInstance historicTask : historicTasks) { + assertThat(historicTask.getScopeDefinitionId()).isEqualTo(destinationDefinition.getId()); + } + } + } + + @Test + void migrateCaseInstancesWithLocalVariablesForAvailableStage() { + // Arrange + deployCaseDefinition("test1", "org/flowable/cmmn/test/migration/task-local-variables.cmmn.xml"); + CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder().caseDefinitionKey("example-stage-case").start(); + CaseDefinition destinationDefinition = deployCaseDefinition("test1", "org/flowable/cmmn/test/migration/stage-local-variables.cmmn.xml"); + + assertThat(cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).count()).isEqualTo(1); + + Map localStageVarMap = new HashMap<>(); + localStageVarMap.put("stageNr", 1); + + // Act + cmmnMigrationService.createCaseInstanceMigrationBuilder() + .migrateToCaseDefinition(destinationDefinition.getId()) + .addMoveToAvailablePlanItemDefinitionMapping(PlanItemDefinitionMappingBuilder.createMoveToAvailablePlanItemDefinitionMappingFor("stage", localStageVarMap)) + .migrateCaseInstances(caseInstance.getCaseDefinitionId()); - // with sentries - // with stages - // with new expected case variables + // Assert + CaseInstance caseInstanceAfterMigration = cmmnRuntimeService.createCaseInstanceQuery() + .caseInstanceId(caseInstance.getId()) + .singleResult(); + assertThat(caseInstanceAfterMigration.getCaseDefinitionId()).isEqualTo(destinationDefinition.getId()); + List planItemInstances = cmmnRuntimeService.createPlanItemInstanceQuery() + .caseInstanceId(caseInstance.getId()) + .includeEnded() + .list(); + assertThat(planItemInstances).hasSize(2); + assertThat(planItemInstances) + .extracting(PlanItemInstance::getCaseDefinitionId) + .containsOnly(destinationDefinition.getId()); + assertThat(planItemInstances) + .extracting(PlanItemInstance::getName) + .containsExactlyInAnyOrder("Start Task", "Stage"); + assertThat(planItemInstances) + .filteredOn("name", "Start Task") + .extracting(PlanItemInstance::getState) + .containsOnly(PlanItemInstanceState.ACTIVE); + + assertThat(planItemInstances) + .filteredOn("name", "Stage") + .extracting(PlanItemInstance::getState) + .containsOnly(PlanItemInstanceState.AVAILABLE); + + assertThat(cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).count()).isEqualTo(1); + + Task task = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).taskDefinitionKey("task").singleResult(); + cmmnTaskService.complete(task.getId()); + + assertThat(cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).count()).isEqualTo(1); + + task = cmmnTaskService.createTaskQuery().caseInstanceId(caseInstance.getId()).taskDefinitionKey("stage-task").singleResult(); + cmmnTaskService.complete(task.getId()); + + assertThat(cmmnRuntimeService.createCaseInstanceQuery().caseInstanceId(caseInstance.getId()).count()).isZero(); + + if (CmmnHistoryTestHelper.isHistoryLevelAtLeast(HistoryLevel.ACTIVITY, cmmnEngineConfiguration)) { + assertThat(cmmnHistoryService.createHistoricCaseInstanceQuery().caseInstanceId(caseInstance.getId()).count()).isEqualTo(1); + assertThat(cmmnHistoryService.createHistoricCaseInstanceQuery().caseInstanceId(caseInstance.getId()).singleResult().getCaseDefinitionId()) + .isEqualTo(destinationDefinition.getId()); + + List historicPlanItemInstances = cmmnHistoryService.createHistoricPlanItemInstanceQuery() + .planItemInstanceCaseInstanceId(caseInstance.getId()).list(); + assertThat(historicPlanItemInstances).hasSize(3); + for (HistoricPlanItemInstance historicPlanItemInstance : historicPlanItemInstances) { + assertThat(historicPlanItemInstance.getCaseDefinitionId()).isEqualTo(destinationDefinition.getId()); + } + + List historicTasks = cmmnHistoryService.createHistoricTaskInstanceQuery().caseInstanceId(caseInstance.getId()).list(); + assertThat(historicTasks).hasSize(2); + for (HistoricTaskInstance historicTask : historicTasks) { + assertThat(historicTask.getScopeDefinitionId()).isEqualTo(destinationDefinition.getId()); + } + } + } protected class CustomTenantProvider implements DefaultTenantProvider { diff --git a/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/migration/stage-local-variables-extra-task.cmmn.xml b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/migration/stage-local-variables-extra-task.cmmn.xml new file mode 100644 index 00000000000..3394cee14ae --- /dev/null +++ b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/migration/stage-local-variables-extra-task.cmmn.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + complete + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/migration/stage-local-variables.cmmn.xml b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/migration/stage-local-variables.cmmn.xml new file mode 100644 index 00000000000..518ea058f8e --- /dev/null +++ b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/migration/stage-local-variables.cmmn.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + complete + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/migration/stage-repetition-local-variables-extra-task.cmmn.xml b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/migration/stage-repetition-local-variables-extra-task.cmmn.xml new file mode 100644 index 00000000000..f026e045bc3 --- /dev/null +++ b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/migration/stage-repetition-local-variables-extra-task.cmmn.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + complete + + + + + complete + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/migration/stage-repetition-local-variables.cmmn.xml b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/migration/stage-repetition-local-variables.cmmn.xml new file mode 100644 index 00000000000..03187f0f013 --- /dev/null +++ b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/migration/stage-repetition-local-variables.cmmn.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + complete + + + + + complete + + + + + + + + + + + + complete + + + + + + + + + \ No newline at end of file diff --git a/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/migration/task-local-variables.cmmn.xml b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/migration/task-local-variables.cmmn.xml new file mode 100644 index 00000000000..cb40e1d86f6 --- /dev/null +++ b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/migration/task-local-variables.cmmn.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file From f9e3e1e09ec0e1520de2a7e86fe2b7d71b696288 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 22 Feb 2024 08:14:56 +0100 Subject: [PATCH 14/15] Bump org.postgresql:postgresql from 42.5.4 to 42.7.2 (#3850) Bumps [org.postgresql:postgresql](https://github.com/pgjdbc/pgjdbc) from 42.5.4 to 42.7.2. - [Release notes](https://github.com/pgjdbc/pgjdbc/releases) - [Changelog](https://github.com/pgjdbc/pgjdbc/blob/master/CHANGELOG.md) - [Commits](https://github.com/pgjdbc/pgjdbc/commits) --- updated-dependencies: - dependency-name: org.postgresql:postgresql dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index dc69cd518ae..7b9aa064b33 100644 --- a/pom.xml +++ b/pom.xml @@ -389,7 +389,7 @@ org.postgresql postgresql - 42.5.4 + 42.7.2 xerces From dc2b970df94f52556f2f29972ee3819f360dd345 Mon Sep 17 00:00:00 2001 From: Joram Barrez Date: Mon, 4 Mar 2024 12:55:44 +0100 Subject: [PATCH 15/15] Fix bug in CmmnRuntimeService#getVariable when used in nested command context, where during looping a wrongly cached variable instance is returned. More specifically: the iteration order of the hashmap changes depending on the amount of variables stored during the same transaction. --- .../flowable-cmmn-engine-configurator/pom.xml | 5 + .../AbstractProcessEngineIntegrationTest.java | 16 +- .../org/flowable/cmmn/test/VariablesTest.java | 62 +++++ ...riableThroughCmmnRuntimeService.bpmn20.xml | 230 ++++++++++++++++++ ...vingVariableThroughCmmnRuntimeService.cmmn | 35 +++ .../cmmn/engine/impl/cmd/GetVariableCmd.java | 8 +- .../cmmn/test/runtime/VariablesTest.java | 52 ++++ ...GettingMultipleTimesInSameTransaction.cmmn | 43 ++++ .../persistence/cache/EntityCacheImpl.java | 13 +- .../test/api/variables/VariablesTest.java | 58 +++++ ...gMultipleTimesInSameTransaction.bpmn20.xml | 77 ++++++ 11 files changed, 594 insertions(+), 5 deletions(-) create mode 100644 modules/flowable-cmmn-engine-configurator/src/test/java/org/flowable/cmmn/test/VariablesTest.java create mode 100644 modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/VariablesTest.testSettingAndRemovingVariableThroughCmmnRuntimeService.bpmn20.xml create mode 100644 modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/VariablesTest.testSettingAndRemovingVariableThroughCmmnRuntimeService.cmmn create mode 100644 modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/runtime/VariablesTest.testSettingGettingMultipleTimesInSameTransaction.cmmn create mode 100644 modules/flowable-engine/src/test/resources/org/flowable/engine/test/api/variables/VariablesTest.testSettingGettingMultipleTimesInSameTransaction.bpmn20.xml diff --git a/modules/flowable-cmmn-engine-configurator/pom.xml b/modules/flowable-cmmn-engine-configurator/pom.xml index 3aee5bbd81b..cf8c0efca62 100644 --- a/modules/flowable-cmmn-engine-configurator/pom.xml +++ b/modules/flowable-cmmn-engine-configurator/pom.xml @@ -62,6 +62,11 @@ mockito-core test + + org.apache.groovy + groovy-jsr223 + test + net.javacrumbs.json-unit json-unit-assertj diff --git a/modules/flowable-cmmn-engine-configurator/src/test/java/org/flowable/cmmn/test/AbstractProcessEngineIntegrationTest.java b/modules/flowable-cmmn-engine-configurator/src/test/java/org/flowable/cmmn/test/AbstractProcessEngineIntegrationTest.java index ec4c05fe97e..a86a11567aa 100644 --- a/modules/flowable-cmmn-engine-configurator/src/test/java/org/flowable/cmmn/test/AbstractProcessEngineIntegrationTest.java +++ b/modules/flowable-cmmn-engine-configurator/src/test/java/org/flowable/cmmn/test/AbstractProcessEngineIntegrationTest.java @@ -15,7 +15,9 @@ import static org.assertj.core.api.Assertions.assertThat; import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import org.flowable.cmmn.api.CmmnHistoryService; @@ -52,6 +54,8 @@ public abstract class AbstractProcessEngineIntegrationTest { protected static CmmnEngineConfiguration cmmnEngineConfiguration; protected static ProcessEngine processEngine; + protected static Map beans = new HashMap<>(); + protected CmmnRepositoryService cmmnRepositoryService; protected CmmnRuntimeService cmmnRuntimeService; protected CmmnTaskService cmmnTaskService; @@ -69,7 +73,9 @@ public abstract class AbstractProcessEngineIntegrationTest { @BeforeClass public static void bootProcessEngine() { if (processEngine == null) { - processEngine = ProcessEngineConfiguration.createProcessEngineConfigurationFromResource("flowable.cfg.xml").buildProcessEngine(); + ProcessEngineConfiguration processEngineConfiguration = ProcessEngineConfiguration.createProcessEngineConfigurationFromResource("flowable.cfg.xml"); + processEngineConfiguration.setBeans(beans); + processEngine = processEngineConfiguration.buildProcessEngine(); cmmnEngineConfiguration = (CmmnEngineConfiguration) processEngine.getProcessEngineConfiguration() .getEngineConfigurations().get(EngineConfigurationConstants.KEY_CMMN_ENGINE_CONFIG); CmmnTestRunner.setCmmnEngineConfiguration(cmmnEngineConfiguration); @@ -91,6 +97,12 @@ public void setupServices() { this.processEngineHistoryService = processEngine.getHistoryService(); this.processEngineConfiguration = processEngine.getProcessEngineConfiguration(); this.processEngineDynamicBpmnService = processEngine.getDynamicBpmnService(); + + beans.put("cmmnRepositoryService", cmmnEngineConfiguration.getCmmnRepositoryService()); + beans.put("cmmnRuntimeService", cmmnEngineConfiguration.getCmmnRuntimeService()); + beans.put("cmmnTaskService", cmmnEngineConfiguration.getCmmnTaskService()); + beans.put("cmmnHistoryService", cmmnEngineConfiguration.getCmmnHistoryService()); + beans.put("cmmnManagementService", cmmnEngineConfiguration.getCmmnManagementService()); } @After @@ -102,6 +114,8 @@ public void cleanup() { for (CmmnDeployment deployment : cmmnRepositoryService.createDeploymentQuery().list()) { cmmnRepositoryService.deleteDeployment(deployment.getId(), true); } + + beans.clear(); } protected Date setCmmnClockFixedToCurrentTime() { diff --git a/modules/flowable-cmmn-engine-configurator/src/test/java/org/flowable/cmmn/test/VariablesTest.java b/modules/flowable-cmmn-engine-configurator/src/test/java/org/flowable/cmmn/test/VariablesTest.java new file mode 100644 index 00000000000..29885b477ae --- /dev/null +++ b/modules/flowable-cmmn-engine-configurator/src/test/java/org/flowable/cmmn/test/VariablesTest.java @@ -0,0 +1,62 @@ +/* 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 org.flowable.cmmn.test; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.List; + +import org.flowable.cmmn.api.runtime.CaseInstance; +import org.flowable.cmmn.engine.test.CmmnDeployment; +import org.flowable.cmmn.engine.test.impl.CmmnHistoryTestHelper; +import org.flowable.common.engine.impl.history.HistoryLevel; +import org.flowable.engine.test.Deployment; +import org.flowable.variable.api.history.HistoricVariableInstance; +import org.junit.Test; + +/** + * @author Joram Barrez + */ +public class VariablesTest extends AbstractProcessEngineIntegrationTest { + + @Test + @Deployment + @CmmnDeployment + public void testSettingAndRemovingVariableThroughCmmnRuntimeService() { + processEngineRepositoryService.createDeployment() + .addClasspathResource("org/flowable/cmmn/test/VariablesTest.testSettingAndRemovingVariableThroughCmmnRuntimeService.bpmn20.xml") + .deploy(); + + CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceBuilder() + .caseDefinitionKey("varSyncTestCase") + .variable("loopNum", 100) + .variable("round", 5) + .start(); + + // The case instance ending means all variable lookups succeeded + assertCaseInstanceEnded(caseInstance); + assertThat(cmmnRuntimeService.getVariables(caseInstance.getId())).isEmpty(); + + if (CmmnHistoryTestHelper.isHistoryLevelAtLeast(HistoryLevel.ACTIVITY, cmmnEngineConfiguration)) { + List historicVariables = cmmnHistoryService.createHistoricVariableInstanceQuery() + .caseInstanceId(caseInstance.getId()).list(); + + // The variables are recreated each loop with a non-null value + assertThat(historicVariables).hasSize(102); // 100 from the variables and 2 for round and loopNum + for (HistoricVariableInstance historicVariable : historicVariables) { + assertThat(historicVariable.getValue()).isNotNull(); + } + } + } + +} diff --git a/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/VariablesTest.testSettingAndRemovingVariableThroughCmmnRuntimeService.bpmn20.xml b/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/VariablesTest.testSettingAndRemovingVariableThroughCmmnRuntimeService.bpmn20.xml new file mode 100644 index 00000000000..78909e1ab08 --- /dev/null +++ b/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/VariablesTest.testSettingAndRemovingVariableThroughCmmnRuntimeService.bpmn20.xml @@ -0,0 +1,230 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0}]]> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/VariablesTest.testSettingAndRemovingVariableThroughCmmnRuntimeService.cmmn b/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/VariablesTest.testSettingAndRemovingVariableThroughCmmnRuntimeService.cmmn new file mode 100644 index 00000000000..c5abe5afb98 --- /dev/null +++ b/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/VariablesTest.testSettingAndRemovingVariableThroughCmmnRuntimeService.cmmn @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/GetVariableCmd.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/GetVariableCmd.java index 71e5cb47885..1457bd0e9f4 100644 --- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/GetVariableCmd.java +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/GetVariableCmd.java @@ -38,8 +38,12 @@ public Object execute(CommandContext commandContext) { if (caseInstanceId == null) { throw new FlowableIllegalArgumentException("caseInstanceId is null"); } - + CmmnEngineConfiguration cmmnEngineConfiguration = CommandContextUtil.getCmmnEngineConfiguration(commandContext); + + // In the BPMN engine, this is done by getting the variable on the execution. + // However, doing the same in CMMN will fetch the case instance and non-completed plan item instances in one query. + // Hence, why here a direct query is done here (which is cached). VariableInstanceEntity variableInstanceEntity = cmmnEngineConfiguration.getVariableServiceConfiguration().getVariableService() .createInternalVariableInstanceQuery() .scopeId(caseInstanceId) @@ -49,7 +53,7 @@ public Object execute(CommandContext commandContext) { .singleResult(); if (variableInstanceEntity != null) { return variableInstanceEntity.getValue(); - } + } return null; } diff --git a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/VariablesTest.java b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/VariablesTest.java index 84644bf60d2..84b690dc365 100644 --- a/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/VariablesTest.java +++ b/modules/flowable-cmmn-engine/src/test/java/org/flowable/cmmn/test/runtime/VariablesTest.java @@ -31,6 +31,7 @@ import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; +import org.flowable.cmmn.api.CmmnRuntimeService; import org.flowable.cmmn.api.delegate.DelegatePlanItemInstance; import org.flowable.cmmn.api.delegate.PlanItemJavaDelegate; import org.flowable.cmmn.api.history.HistoricMilestoneInstance; @@ -881,6 +882,22 @@ public void testUpdateMetaInfo() { } + @Test + @CmmnDeployment + public void testSettingGettingMultipleTimesInSameTransaction() { + TestSetGetVariablesDelegate.REMOVE_VARS_IN_LAST_ROUND = true; + CaseInstance caseInstance1 = cmmnRuntimeService.createCaseInstanceBuilder().caseDefinitionKey("testSettingGettingMultipleTimesInSameTransaction").start(); + assertThat(cmmnRuntimeService.getVariables(caseInstance1.getId())).isEmpty(); + + TestSetGetVariablesDelegate.REMOVE_VARS_IN_LAST_ROUND = false; + CaseInstance caseInstance2 = cmmnRuntimeService.createCaseInstanceBuilder().caseDefinitionKey("testSettingGettingMultipleTimesInSameTransaction").start(); + Map variables = cmmnRuntimeService.getVariables(caseInstance2.getId()); + assertThat(variables).hasSize(100); + for (String variableName : variables.keySet()) { + assertThat(variables.get(variableName)).isNotNull(); + } + } + protected void addVariableTypeIfNotExists(VariableType variableType) { // We can't remove the VariableType after every test since it would cause the test // to fail due to not being able to get the variable value during deleting @@ -1062,4 +1079,39 @@ public CustomTestVariable(String someValue, int someInt) { } } + public static class TestSetGetVariablesDelegate implements PlanItemJavaDelegate { + + public static boolean REMOVE_VARS_IN_LAST_ROUND = true; + + @Override + public void execute(DelegatePlanItemInstance planItemInstance) { + String caseInstanceId = planItemInstance.getCaseInstanceId(); + CmmnRuntimeService cmmnRuntimeService = CommandContextUtil.getCmmnRuntimeService(); + + int nrOfLoops = 100; + for (int nrOfRounds = 0; nrOfRounds < 4; nrOfRounds++) { + + // Set + for (int i = 0; i < nrOfLoops; i++) { + cmmnRuntimeService.setVariable(caseInstanceId, "test_" + i, i); + } + + // Get + for (int i = 0; i < nrOfLoops; i++) { + if (cmmnRuntimeService.getVariable(caseInstanceId, "test_" + i) == null) { + throw new RuntimeException("This exception shouldn't happen"); + } + } + + // Remove + if (REMOVE_VARS_IN_LAST_ROUND && nrOfRounds == 3) { + for (int i = 0; i < nrOfLoops; i++) { + cmmnRuntimeService.removeVariable(caseInstanceId, "test_" + i); + } + } + + } + } + } + } diff --git a/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/runtime/VariablesTest.testSettingGettingMultipleTimesInSameTransaction.cmmn b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/runtime/VariablesTest.testSettingGettingMultipleTimesInSameTransaction.cmmn new file mode 100644 index 00000000000..686dffd6cdb --- /dev/null +++ b/modules/flowable-cmmn-engine/src/test/resources/org/flowable/cmmn/test/runtime/VariablesTest.testSettingGettingMultipleTimesInSameTransaction.cmmn @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/persistence/cache/EntityCacheImpl.java b/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/persistence/cache/EntityCacheImpl.java index 2c67c960e39..c39f20af33b 100644 --- a/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/persistence/cache/EntityCacheImpl.java +++ b/modules/flowable-engine-common/src/main/java/org/flowable/common/engine/impl/persistence/cache/EntityCacheImpl.java @@ -98,9 +98,18 @@ public List findInCache(Class entityClass) { } if (classCache != null) { - List entities = new ArrayList<>(classCache.size()); + ArrayList entities = new ArrayList<>(classCache.size()); for (CachedEntity cachedObject : classCache.values()) { - entities.add((T) cachedObject.getEntity()); + + // Non-deleted entities go first in the returned list, + // while deleted ones go at the end. + // This way users of this method will first get the 'active' entities. + if (!cachedObject.getEntity().isDeleted()) { + entities.add(0, (T) cachedObject.getEntity()); + } else { + entities.add((T) cachedObject.getEntity()); + } + } return entities; } diff --git a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/variables/VariablesTest.java b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/variables/VariablesTest.java index 0149da7cb99..ac82123f767 100644 --- a/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/variables/VariablesTest.java +++ b/modules/flowable-engine/src/test/java/org/flowable/engine/test/api/variables/VariablesTest.java @@ -29,6 +29,7 @@ import org.assertj.core.groups.Tuple; import org.flowable.common.engine.impl.history.HistoryLevel; +import org.flowable.engine.RuntimeService; import org.flowable.engine.delegate.DelegateExecution; import org.flowable.engine.delegate.JavaDelegate; import org.flowable.engine.impl.test.HistoryTestHelper; @@ -733,6 +734,28 @@ public void testCreateAndUpdateWithValue() { } } + @Test + @Deployment + public void testSettingGettingMultipleTimesInSameTransaction() { + + TestSetGetVariablesDelegate.REMOVE_VARS_IN_LAST_ROUND = true; + + ProcessInstance processInstance1 = runtimeService.createProcessInstanceBuilder() + .processDefinitionKey("testSettingGettingMultipleTimesInSameTransaction") + .start(); + assertThat(runtimeService.getVariables(processInstance1.getId())).isEmpty(); + + TestSetGetVariablesDelegate.REMOVE_VARS_IN_LAST_ROUND = false; + ProcessInstance processInstance2 = runtimeService.createProcessInstanceBuilder() + .processDefinitionKey("testSettingGettingMultipleTimesInSameTransaction") + .start(); + Map variables = runtimeService.getVariables(processInstance2.getId()); + assertThat(variables).hasSize(100); + for (String variableName : variables.keySet()) { + assertThat(variables.get(variableName)).isNotNull(); + } + } + // Class to test variable serialization public static class TestSerializableVariable implements Serializable { @@ -906,4 +929,39 @@ public void execute(DelegateExecution execution) { } } + public static class TestSetGetVariablesDelegate implements JavaDelegate { + + public static boolean REMOVE_VARS_IN_LAST_ROUND = true; + + @Override + public void execute(DelegateExecution execution) { + String processInstanceId = execution.getProcessInstanceId(); + RuntimeService runtimeService = CommandContextUtil.getProcessEngineConfiguration().getRuntimeService(); + + int nrOfLoops = 100; + for (int nrOfRounds = 0; nrOfRounds < 4; nrOfRounds++) { + + // Set + for (int i = 0; i < nrOfLoops; i++) { + runtimeService.setVariable(processInstanceId, "test_" + i, i); + } + + // Get + for (int i = 0; i < nrOfLoops; i++) { + if (runtimeService.getVariable(processInstanceId, "test_" + i) == null) { + throw new RuntimeException("This exception shouldn't happen"); + } + } + + // Remove + if (REMOVE_VARS_IN_LAST_ROUND && nrOfRounds == 3) { + for (int i = 0; i < nrOfLoops; i++) { + runtimeService.removeVariable(processInstanceId, "test_" + i); + } + } + + } + } + } + } diff --git a/modules/flowable-engine/src/test/resources/org/flowable/engine/test/api/variables/VariablesTest.testSettingGettingMultipleTimesInSameTransaction.bpmn20.xml b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/api/variables/VariablesTest.testSettingGettingMultipleTimesInSameTransaction.bpmn20.xml new file mode 100644 index 00000000000..68cc58ed050 --- /dev/null +++ b/modules/flowable-engine/src/test/resources/org/flowable/engine/test/api/variables/VariablesTest.testSettingGettingMultipleTimesInSameTransaction.bpmn20.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file