From 1d31bde76e8a7e9570f76cc80fa084381b439c60 Mon Sep 17 00:00:00 2001 From: Filip Hrisafov Date: Fri, 9 Oct 2026 20:28:47 +0200 Subject: [PATCH] Keep the parent deployment id on the cached process and case definitions The deployers copy the parent deployment id of the deployment onto the cached process and case definitions, so resolving the definition deployment id no longer needs to select the deployment. The start form, form definitions and decisions lookups of BPMN and CMMN (and the REST start form resources) now use the parent deployment id of the cached definitions instead of their own deployment lookups. The selects are verified with database operation count tests for a case with a case task, a case with a process task, a process with a case task, a process and a case with a decision task, and getting the decisions of a process and a case definition. --- .../test/VerifyDatabaseOperationsTest.java | 156 ++++++++++++ .../cmmn/test/caseTaskSameDeploymentTrue.cmmn | 16 ++ .../cmd/GetDecisionsForCaseDefinitionCmd.java | 16 +- ...etFormDefinitionsForCaseDefinitionCmd.java | 16 +- .../engine/impl/cmd/GetStartFormModelCmd.java | 5 +- .../engine/impl/deployer/CmmnDeployer.java | 2 + .../entity/CaseDefinitionEntity.java | 8 + .../entity/CaseDefinitionEntityImpl.java | 13 + .../impl/repository/CaseDefinitionUtil.java | 22 ++ .../repository/CaseDefinitionResource.java | 7 +- .../test/VerifyDatabaseOperationsTest.java | 236 ++++++++++++++++++ .../oneDecisionTaskCaseSameDeployment.cmmn | 38 +++ .../bpmn/deployer/BpmnDeploymentHelper.java | 4 +- .../GetDecisionsForProcessDefinitionCmd.java | 16 +- ...ormDefinitionsForProcessDefinitionCmd.java | 16 +- .../engine/impl/cmd/GetStartFormModelCmd.java | 5 +- .../entity/ProcessDefinitionEntity.java | 8 + .../entity/ProcessDefinitionEntityImpl.java | 13 + .../impl/util/ProcessDefinitionUtil.java | 18 ++ .../repository/ProcessDefinitionResource.java | 8 +- 20 files changed, 566 insertions(+), 57 deletions(-) create mode 100644 modules/flowable-cmmn-engine-configurator/src/test/java/org/flowable/cmmn/test/VerifyDatabaseOperationsTest.java create mode 100644 modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/caseTaskSameDeploymentTrue.cmmn create mode 100644 modules/flowable-dmn-engine-configurator/src/test/java/org/flowable/dmn/engine/test/VerifyDatabaseOperationsTest.java create mode 100644 modules/flowable-dmn-engine-configurator/src/test/resources/org/flowable/dmn/engine/test/deployment/oneDecisionTaskCaseSameDeployment.cmmn diff --git a/modules/flowable-cmmn-engine-configurator/src/test/java/org/flowable/cmmn/test/VerifyDatabaseOperationsTest.java b/modules/flowable-cmmn-engine-configurator/src/test/java/org/flowable/cmmn/test/VerifyDatabaseOperationsTest.java new file mode 100644 index 00000000000..61604962409 --- /dev/null +++ b/modules/flowable-cmmn-engine-configurator/src/test/java/org/flowable/cmmn/test/VerifyDatabaseOperationsTest.java @@ -0,0 +1,156 @@ +/* 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 static org.assertj.core.api.Assertions.entry; + +import java.util.Map; + +import org.flowable.common.engine.impl.cfg.CommandExecutorImpl; +import org.flowable.common.engine.impl.db.DbSqlSessionFactory; +import org.flowable.common.engine.impl.interceptor.CommandExecutor; +import org.flowable.common.engine.impl.interceptor.CommandInterceptor; +import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.flowable.engine.test.profiler.FlowableProfiler; +import org.flowable.engine.test.profiler.ProfilingDbSqlSessionFactory; +import org.flowable.engine.test.profiler.TotalExecutionTimeCommandInterceptor; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledIfSystemProperty; + +/** + * @author Filip Hrisafov + */ +@DisabledIfSystemProperty(named = "disableWhen", matches = "cockroachdb") +public class VerifyDatabaseOperationsTest extends AbstractProcessEngineIntegrationTest { + + protected DbSqlSessionFactory oldDbSqlSessionFactory; + + @BeforeEach + protected void setUp() { + // The process and the CMMN engine share the session factories + ProcessEngineConfigurationImpl processEngineConfigurationImpl = (ProcessEngineConfigurationImpl) processEngineConfiguration; + this.oldDbSqlSessionFactory = processEngineConfigurationImpl.getDbSqlSessionFactory(); + DbSqlSessionFactory newDbSqlSessionFactory = new ProfilingDbSqlSessionFactory(processEngineConfigurationImpl.isUsePrefixId()); + newDbSqlSessionFactory.setBulkInserteableEntityClasses(oldDbSqlSessionFactory.getBulkInserteableEntityClasses()); + newDbSqlSessionFactory.setInsertionOrder(oldDbSqlSessionFactory.getInsertionOrder()); + newDbSqlSessionFactory.setDeletionOrder(oldDbSqlSessionFactory.getDeletionOrder()); + newDbSqlSessionFactory.setDatabaseType(oldDbSqlSessionFactory.getDatabaseType()); + newDbSqlSessionFactory.setDatabaseTablePrefix(oldDbSqlSessionFactory.getDatabaseTablePrefix()); + newDbSqlSessionFactory.setTablePrefixIsSchema(oldDbSqlSessionFactory.isTablePrefixIsSchema()); + newDbSqlSessionFactory.setDatabaseCatalog(oldDbSqlSessionFactory.getDatabaseCatalog()); + newDbSqlSessionFactory.setDatabaseSchema(oldDbSqlSessionFactory.getDatabaseSchema()); + newDbSqlSessionFactory.setSqlSessionFactory(oldDbSqlSessionFactory.getSqlSessionFactory()); + newDbSqlSessionFactory.setDbHistoryUsed(oldDbSqlSessionFactory.isDbHistoryUsed()); + newDbSqlSessionFactory.setDatabaseSpecificStatements(oldDbSqlSessionFactory.getDatabaseSpecificStatements()); + newDbSqlSessionFactory.setLogicalNameToClassMapping(oldDbSqlSessionFactory.getLogicalNameToClassMapping()); + processEngineConfigurationImpl.addSessionFactory(newDbSqlSessionFactory); + } + + @AfterEach + protected void tearDown() { + ((ProcessEngineConfigurationImpl) processEngineConfiguration).addSessionFactory(oldDbSqlSessionFactory); + FlowableProfiler.getInstance().reset(); + } + + @Test + public void testCaseWithProcessTaskFromSameDeployment() { + processEngineRepositoryService.createDeployment() + .addClasspathResource("org/flowable/cmmn/test/processTaskSameDeploymentTrue.cmmn") + .addClasspathResource("org/flowable/cmmn/test/oneTaskProcess.bpmn20.xml") + .deploy(); + + Map selects = profileDatabaseSelects(cmmnEngineConfiguration.getCommandExecutor(), "org.flowable.cmmn.engine.impl.cmd.StartCaseInstanceCmd", + () -> cmmnRuntimeService.createCaseInstanceBuilder() + .caseDefinitionKey("oneProcessTaskCase") + .start()); + + // The cached case definition has the parent deployment id, so the CMMN deployment is not selected + assertThat(selects).containsOnly( + entry("selectLatestCaseDefinitionByKey", 1L), + entry("selectProcessDefinitionByParentDeploymentAndKey", 1L), + entry("selectProcessDefinitionInfoByProcessDefinitionId", 1L), + entry("selectPlanItemInstancesByCaseInstanceIdAndPlanItemId", 1L), + entry("selectEntityLinksByQuery", 2L) + ); + assertThat(processEngineRuntimeService.createProcessInstanceQuery().count()).isOne(); + } + + @Test + public void testCaseWithCaseTaskFromSameDeployment() { + cmmnRepositoryService.createDeployment() + .addClasspathResource("org/flowable/cmmn/test/caseTaskSameDeploymentTrue.cmmn") + .addClasspathResource("org/flowable/cmmn/test/oneHumanTaskCase.cmmn") + .deploy(); + + Map selects = profileDatabaseSelects(cmmnEngineConfiguration.getCommandExecutor(), "org.flowable.cmmn.engine.impl.cmd.StartCaseInstanceCmd", + () -> cmmnRuntimeService.createCaseInstanceBuilder() + .caseDefinitionKey("oneCaseTaskCase") + .start()); + + // The cached case definition has the parent deployment id, so the CMMN deployment is not selected + assertThat(selects).containsOnly( + entry("selectLatestCaseDefinitionByKey", 1L), + entry("selectCaseDefinitionByParentDeploymentAndKey", 1L), + entry("selectPlanItemInstancesByCaseInstanceIdAndPlanItemId", 2L), + entry("selectEntityLinksByQuery", 2L) + ); + assertThat(cmmnRuntimeService.createCaseInstanceQuery().caseDefinitionKey("oneHumanTaskCase").count()).isOne(); + } + + @Test + public void testProcessWithCaseTaskFromSameDeployment() { + processEngineRepositoryService.createDeployment() + .addClasspathResource("org/flowable/cmmn/test/caseTaskProcessWithSameDeployment.bpmn20.xml") + .addClasspathResource("org/flowable/cmmn/test/oneHumanTaskCase.cmmn") + .deploy(); + + Map selects = profileDatabaseSelects(processEngineConfiguration.getCommandExecutor(), "org.flowable.engine.impl.cmd.StartProcessInstanceCmd", + () -> processEngineRuntimeService.createProcessInstanceBuilder() + .processDefinitionKey("caseTask") + .start()); + + // The cached process definition has the parent deployment id, so the process deployment is not selected + assertThat(selects).containsOnly( + entry("selectLatestProcessDefinitionByKey", 1L), + entry("selectCaseDefinitionByParentDeploymentAndKey", 1L), + entry("selectProcessDefinitionInfoByProcessDefinitionId", 1L), + entry("selectPlanItemInstancesByCaseInstanceIdAndPlanItemId", 1L), + entry("selectEntityLinksByQuery", 2L) + ); + assertThat(cmmnRuntimeService.createCaseInstanceQuery().count()).isOne(); + } + + protected Map profileDatabaseSelects(CommandExecutor commandExecutor, String commandClass, Runnable runnable) { + // Only the outer command is profiled, the commands of the other engine reuse its command context + CommandExecutorImpl commandExecutorImpl = (CommandExecutorImpl) commandExecutor; + CommandInterceptor oldFirstCommandInterceptor = commandExecutorImpl.getFirst(); + TotalExecutionTimeCommandInterceptor timeCommandInterceptor = new TotalExecutionTimeCommandInterceptor(); + timeCommandInterceptor.setNext(oldFirstCommandInterceptor); + commandExecutorImpl.setFirst(timeCommandInterceptor); + + FlowableProfiler profiler = FlowableProfiler.getInstance(); + profiler.reset(); + profiler.startProfileSession("Profiling session"); + try { + runnable.run(); + } finally { + profiler.stopCurrentProfileSession(); + commandExecutorImpl.setFirst(oldFirstCommandInterceptor); + } + + return profiler.getProfileSessions().get(0).calculateSummaryStatistics().get(commandClass).getDbSelects(); + } +} diff --git a/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/caseTaskSameDeploymentTrue.cmmn b/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/caseTaskSameDeploymentTrue.cmmn new file mode 100644 index 00000000000..d38f4fa7a53 --- /dev/null +++ b/modules/flowable-cmmn-engine-configurator/src/test/resources/org/flowable/cmmn/test/caseTaskSameDeploymentTrue.cmmn @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/GetDecisionsForCaseDefinitionCmd.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/GetDecisionsForCaseDefinitionCmd.java index 920196d0091..93825f63f96 100644 --- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/GetDecisionsForCaseDefinitionCmd.java +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/GetDecisionsForCaseDefinitionCmd.java @@ -19,7 +19,6 @@ import java.util.Set; import org.flowable.cmmn.api.repository.CaseDefinition; -import org.flowable.cmmn.api.repository.CmmnDeployment; import org.flowable.cmmn.engine.impl.repository.CaseDefinitionUtil; import org.flowable.cmmn.engine.impl.util.CommandContextUtil; import org.flowable.cmmn.model.Case; @@ -96,16 +95,11 @@ protected List getDecisionsFromModel(Case caseModel, CaseDefinition protected void addDecisionToCollection(List decisions, String decisionKey, CaseDefinition caseDefinition) { DmnDecisionQuery definitionQuery = dmnRepositoryService.createDecisionQuery().decisionKey(decisionKey); - CmmnDeployment deployment = CommandContextUtil.getCmmnDeploymentEntityManager().findById(caseDefinition.getDeploymentId()); - if (deployment.getParentDeploymentId() != null) { - List dmnDeployments = dmnRepositoryService.createDeploymentQuery().parentDeploymentId(deployment.getParentDeploymentId()).list(); - - if (dmnDeployments != null && dmnDeployments.size() > 0) { - definitionQuery.deploymentId(dmnDeployments.get(0).getId()); - } else { - definitionQuery.latestVersion(); - } - + String parentDeploymentId = CaseDefinitionUtil.getDefinitionDeploymentId(caseDefinition, CommandContextUtil.getCmmnEngineConfiguration()); + List dmnDeployments = dmnRepositoryService.createDeploymentQuery().parentDeploymentId(parentDeploymentId).list(); + + if (!dmnDeployments.isEmpty()) { + definitionQuery.deploymentId(dmnDeployments.get(0).getId()); } else { definitionQuery.latestVersion(); } diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/GetFormDefinitionsForCaseDefinitionCmd.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/GetFormDefinitionsForCaseDefinitionCmd.java index 123314f92f1..241b895ee3b 100644 --- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/GetFormDefinitionsForCaseDefinitionCmd.java +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/GetFormDefinitionsForCaseDefinitionCmd.java @@ -20,7 +20,6 @@ import org.apache.commons.lang3.StringUtils; import org.flowable.cmmn.api.repository.CaseDefinition; -import org.flowable.cmmn.api.repository.CmmnDeployment; import org.flowable.cmmn.engine.impl.repository.CaseDefinitionUtil; import org.flowable.cmmn.engine.impl.util.CommandContextUtil; import org.flowable.cmmn.model.Case; @@ -94,16 +93,11 @@ protected List getFormDefinitionsFromModel(Case caseModel, CaseD protected void addFormDefinitionToCollection(List formDefinitions, String formKey, CaseDefinition caseDefinition) { FormDefinitionQuery formDefinitionQuery = formRepositoryService.createFormDefinitionQuery().formDefinitionKey(formKey); - CmmnDeployment deployment = CommandContextUtil.getCmmnDeploymentEntityManager().findById(caseDefinition.getDeploymentId()); - if (deployment.getParentDeploymentId() != null) { - List formDeployments = formRepositoryService.createDeploymentQuery().parentDeploymentId(deployment.getParentDeploymentId()).list(); - - if (formDeployments != null && formDeployments.size() > 0) { - formDefinitionQuery.deploymentId(formDeployments.get(0).getId()); - } else { - formDefinitionQuery.latestVersion(); - } - + String parentDeploymentId = CaseDefinitionUtil.getDefinitionDeploymentId(caseDefinition, CommandContextUtil.getCmmnEngineConfiguration()); + List formDeployments = formRepositoryService.createDeploymentQuery().parentDeploymentId(parentDeploymentId).list(); + + if (!formDeployments.isEmpty()) { + formDefinitionQuery.deploymentId(formDeployments.get(0).getId()); } else { formDefinitionQuery.latestVersion(); } diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/GetStartFormModelCmd.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/GetStartFormModelCmd.java index 3f537d6602f..26b72403f6b 100644 --- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/GetStartFormModelCmd.java +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/cmd/GetStartFormModelCmd.java @@ -16,7 +16,6 @@ import org.apache.commons.lang3.StringUtils; import org.flowable.cmmn.api.repository.CaseDefinition; -import org.flowable.cmmn.api.repository.CmmnDeployment; import org.flowable.cmmn.engine.CmmnEngineConfiguration; import org.flowable.cmmn.engine.impl.repository.CaseDefinitionUtil; import org.flowable.cmmn.engine.impl.util.CommandContextUtil; @@ -62,8 +61,8 @@ public FormInfo execute(CommandContext commandContext) { Stage planModel = caseModel.getPlanModel(); if (StringUtils.isNotEmpty(planModel.getFormKey())) { - CmmnDeployment deployment = CommandContextUtil.getCmmnDeploymentEntityManager(commandContext).findById(caseDefinition.getDeploymentId()); - formInfo = formService.getFormInstanceModelByKeyAndParentDeploymentIdAndScopeId(planModel.getFormKey(), deployment.getParentDeploymentId(), + String parentDeploymentId = CaseDefinitionUtil.getDefinitionDeploymentId(caseDefinition, cmmnEngineConfiguration); + formInfo = formService.getFormInstanceModelByKeyAndParentDeploymentIdAndScopeId(planModel.getFormKey(), parentDeploymentId, caseInstanceId, ScopeTypes.CMMN, null, caseDefinition.getTenantId(), cmmnEngineConfiguration.isFallbackToDefaultTenant()); } diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/deployer/CmmnDeployer.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/deployer/CmmnDeployer.java index d2ff183f859..3a3e1bca214 100644 --- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/deployer/CmmnDeployer.java +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/deployer/CmmnDeployer.java @@ -275,12 +275,14 @@ protected void verifyCaseDefinitionsDoNotShareKeys(Collection caseDefinitionEntities) { String tenantId = deployment.getTenantId(); String deploymentId = deployment.getId(); + String parentDeploymentId = ((CmmnDeploymentEntity) deployment).getParentDeploymentId(); for (CaseDefinitionEntity caseDefinitionEntity : caseDefinitionEntities) { if (tenantId != null) { caseDefinitionEntity.setTenantId(tenantId); } caseDefinitionEntity.setDeploymentId(deploymentId); + caseDefinitionEntity.setParentDeploymentId(parentDeploymentId); } } diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/persistence/entity/CaseDefinitionEntity.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/persistence/entity/CaseDefinitionEntity.java index 7c3d21b5f1b..c2c627e399d 100644 --- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/persistence/entity/CaseDefinitionEntity.java +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/persistence/entity/CaseDefinitionEntity.java @@ -43,5 +43,13 @@ public interface CaseDefinitionEntity extends Entity, CaseDefinition { void setTenantId(String tenantId); + /** + * The parent deployment id of the deployment of this case definition. + * This is not persisted, it is only set on the case definitions that are put in the case definition cache. + */ + String getParentDeploymentId(); + + void setParentDeploymentId(String parentDeploymentId); + List getIdentityLinks(); } diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/persistence/entity/CaseDefinitionEntityImpl.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/persistence/entity/CaseDefinitionEntityImpl.java index 9bd18c35b6d..d4e08893ae8 100644 --- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/persistence/entity/CaseDefinitionEntityImpl.java +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/persistence/entity/CaseDefinitionEntityImpl.java @@ -41,6 +41,9 @@ public class CaseDefinitionEntityImpl extends AbstractCmmnEngineEntity implement protected List definitionIdentityLinkEntities = new ArrayList<>(); protected String localizedName; protected String localizedDescription; + + // Not persisted, set by the deployer + protected String parentDeploymentId; @Override public Object getPersistentState() { @@ -198,4 +201,14 @@ public String getLocalizedDescription() { public void setLocalizedDescription(String localizedDescription) { this.localizedDescription = localizedDescription; } + + @Override + public String getParentDeploymentId() { + return parentDeploymentId; + } + + @Override + public void setParentDeploymentId(String parentDeploymentId) { + this.parentDeploymentId = parentDeploymentId; + } } diff --git a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/repository/CaseDefinitionUtil.java b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/repository/CaseDefinitionUtil.java index 0197279a685..310b6c7c09a 100644 --- a/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/repository/CaseDefinitionUtil.java +++ b/modules/flowable-cmmn-engine/src/main/java/org/flowable/cmmn/engine/impl/repository/CaseDefinitionUtil.java @@ -16,6 +16,7 @@ import org.flowable.cmmn.api.repository.CaseDefinition; import org.flowable.cmmn.engine.CmmnEngineConfiguration; import org.flowable.cmmn.engine.impl.deployer.CmmnDeploymentManager; +import org.flowable.cmmn.engine.impl.persistence.entity.CaseDefinitionEntity; import org.flowable.cmmn.engine.impl.persistence.entity.CmmnDeploymentEntity; import org.flowable.cmmn.engine.impl.persistence.entity.deploy.CaseDefinitionCacheEntry; import org.flowable.cmmn.engine.impl.util.CommandContextUtil; @@ -46,12 +47,33 @@ public static String getDefinitionDeploymentId(String caseDefinitionId, CmmnEngi public static String getDefinitionDeploymentId(CaseDefinition caseDefinition, CmmnEngineConfiguration cmmnEngineConfiguration) { CmmnDeploymentManager deploymentManager = cmmnEngineConfiguration.getDeploymentManager(); + String parentDeploymentId = getParentDeploymentId(caseDefinition); + if (parentDeploymentId == null) { + // A case definition loaded from the database does not have the parent deployment id, the cached one has it + CaseDefinitionCacheEntry cacheEntry = deploymentManager.getCaseDefinitionCache().get(caseDefinition.getId()); + if (cacheEntry != null) { + parentDeploymentId = getParentDeploymentId(cacheEntry.getCaseDefinition()); + } + } + + if (parentDeploymentId != null) { + return parentDeploymentId; + } + CmmnDeploymentEntity caseDeployment = deploymentManager.getDeploymentEntityManager().findById(caseDefinition.getDeploymentId()); if (StringUtils.isEmpty(caseDeployment.getParentDeploymentId())) { return caseDefinition.getDeploymentId(); } return caseDeployment.getParentDeploymentId(); } + + protected static String getParentDeploymentId(CaseDefinition caseDefinition) { + if (caseDefinition instanceof CaseDefinitionEntity caseDefinitionEntity + && StringUtils.isNotEmpty(caseDefinitionEntity.getParentDeploymentId())) { + return caseDefinitionEntity.getParentDeploymentId(); + } + return null; + } protected static CaseDefinition getCaseDefinition(String caseDefinitionId, CmmnDeploymentManager deploymentManager, CaseDefinitionCacheEntry cacheEntry) { if (cacheEntry != null) { diff --git a/modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/repository/CaseDefinitionResource.java b/modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/repository/CaseDefinitionResource.java index ecbe55d5338..e7494cdd48d 100644 --- a/modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/repository/CaseDefinitionResource.java +++ b/modules/flowable-cmmn-rest/src/main/java/org/flowable/cmmn/rest/service/api/repository/CaseDefinitionResource.java @@ -19,10 +19,10 @@ import org.flowable.cmmn.api.migration.CaseInstanceMigrationDocument; import org.flowable.cmmn.api.migration.HistoricCaseInstanceMigrationDocument; import org.flowable.cmmn.api.repository.CaseDefinition; -import org.flowable.cmmn.api.repository.CmmnDeployment; import org.flowable.cmmn.engine.CmmnEngineConfiguration; import org.flowable.cmmn.engine.impl.migration.CaseInstanceMigrationDocumentConverter; import org.flowable.cmmn.engine.impl.migration.HistoricCaseInstanceMigrationDocumentConverter; +import org.flowable.cmmn.engine.impl.persistence.entity.CaseDefinitionEntity; import org.flowable.cmmn.model.Case; import org.flowable.cmmn.model.CmmnModel; import org.flowable.cmmn.model.Stage; @@ -221,9 +221,10 @@ protected FormInfo getStartForm(FormRepositoryService formRepositoryService, Cas Stage stage = caze.getPlanModel(); if (StringUtils.isNotEmpty(stage.getFormKey())) { if (stage.isSameDeployment()) { - CmmnDeployment deployment = repositoryService.createDeploymentQuery().deploymentId(caseDefinition.getDeploymentId()).singleResult(); + // The case definition comes from the definition cache, which has the parent deployment id + String parentDeploymentId = caseDefinition instanceof CaseDefinitionEntity caseDefinitionEntity ? caseDefinitionEntity.getParentDeploymentId() : null; formInfo = formRepositoryService.getFormModelByKeyAndParentDeploymentId(stage.getFormKey(), - deployment.getParentDeploymentId(), caseDefinition.getTenantId(), cmmnEngineConfiguration.isFallbackToDefaultTenant()); + parentDeploymentId, caseDefinition.getTenantId(), cmmnEngineConfiguration.isFallbackToDefaultTenant()); } else { formInfo = formRepositoryService.getFormModelByKey(stage.getFormKey(), caseDefinition.getTenantId(), cmmnEngineConfiguration.isFallbackToDefaultTenant()); diff --git a/modules/flowable-dmn-engine-configurator/src/test/java/org/flowable/dmn/engine/test/VerifyDatabaseOperationsTest.java b/modules/flowable-dmn-engine-configurator/src/test/java/org/flowable/dmn/engine/test/VerifyDatabaseOperationsTest.java new file mode 100644 index 00000000000..c5494c012f2 --- /dev/null +++ b/modules/flowable-dmn-engine-configurator/src/test/java/org/flowable/dmn/engine/test/VerifyDatabaseOperationsTest.java @@ -0,0 +1,236 @@ +/* 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.dmn.engine.test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +import java.util.Map; + +import org.flowable.cmmn.api.CmmnRepositoryService; +import org.flowable.cmmn.api.CmmnRuntimeService; +import org.flowable.cmmn.api.repository.CaseDefinition; +import org.flowable.cmmn.api.repository.CmmnDeployment; +import org.flowable.cmmn.api.runtime.CaseInstance; +import org.flowable.cmmn.engine.CmmnEngineConfiguration; +import org.flowable.common.engine.impl.cfg.CommandExecutorImpl; +import org.flowable.common.engine.impl.db.DbSqlSessionFactory; +import org.flowable.common.engine.impl.interceptor.CommandExecutor; +import org.flowable.common.engine.impl.interceptor.CommandInterceptor; +import org.flowable.common.engine.impl.interceptor.EngineConfigurationConstants; +import org.flowable.dmn.api.DmnDecision; +import org.flowable.dmn.api.DmnDeployment; +import org.flowable.dmn.api.DmnRepositoryService; +import org.flowable.dmn.engine.DmnEngineConfiguration; +import org.flowable.engine.ProcessEngine; +import org.flowable.engine.HistoryService; +import org.flowable.engine.RepositoryService; +import org.flowable.engine.RuntimeService; +import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.flowable.engine.repository.Deployment; +import org.flowable.engine.repository.ProcessDefinition; +import org.flowable.engine.test.FlowableTest; +import org.flowable.engine.test.profiler.FlowableProfiler; +import org.flowable.engine.test.profiler.ProfilingDbSqlSessionFactory; +import org.flowable.engine.test.profiler.TotalExecutionTimeCommandInterceptor; +import org.flowable.variable.api.history.HistoricVariableInstance; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledIfSystemProperty; + +/** + * @author Filip Hrisafov + */ +@FlowableTest +@DisabledIfSystemProperty(named = "disableWhen", matches = "cockroachdb") +public class VerifyDatabaseOperationsTest { + + protected ProcessEngineConfigurationImpl processEngineConfiguration; + protected CmmnEngineConfiguration cmmnEngineConfiguration; + protected RepositoryService repositoryService; + protected RuntimeService runtimeService; + protected HistoryService historyService; + protected CmmnRepositoryService cmmnRepositoryService; + protected CmmnRuntimeService cmmnRuntimeService; + protected DmnRepositoryService dmnRepositoryService; + + protected DbSqlSessionFactory oldDbSqlSessionFactory; + + @BeforeEach + protected void setUp(ProcessEngine processEngine) { + this.processEngineConfiguration = (ProcessEngineConfigurationImpl) processEngine.getProcessEngineConfiguration(); + this.cmmnEngineConfiguration = (CmmnEngineConfiguration) processEngineConfiguration.getEngineConfigurations() + .get(EngineConfigurationConstants.KEY_CMMN_ENGINE_CONFIG); + DmnEngineConfiguration dmnEngineConfiguration = (DmnEngineConfiguration) processEngineConfiguration.getEngineConfigurations() + .get(EngineConfigurationConstants.KEY_DMN_ENGINE_CONFIG); + this.repositoryService = processEngine.getRepositoryService(); + this.runtimeService = processEngine.getRuntimeService(); + this.historyService = processEngine.getHistoryService(); + this.cmmnRepositoryService = cmmnEngineConfiguration.getCmmnRepositoryService(); + this.cmmnRuntimeService = cmmnEngineConfiguration.getCmmnRuntimeService(); + this.dmnRepositoryService = dmnEngineConfiguration.getDmnRepositoryService(); + + // The process, CMMN and DMN engine share the session factories + this.oldDbSqlSessionFactory = processEngineConfiguration.getDbSqlSessionFactory(); + DbSqlSessionFactory newDbSqlSessionFactory = new ProfilingDbSqlSessionFactory(processEngineConfiguration.isUsePrefixId()); + newDbSqlSessionFactory.setBulkInserteableEntityClasses(oldDbSqlSessionFactory.getBulkInserteableEntityClasses()); + newDbSqlSessionFactory.setInsertionOrder(oldDbSqlSessionFactory.getInsertionOrder()); + newDbSqlSessionFactory.setDeletionOrder(oldDbSqlSessionFactory.getDeletionOrder()); + newDbSqlSessionFactory.setDatabaseType(oldDbSqlSessionFactory.getDatabaseType()); + newDbSqlSessionFactory.setDatabaseTablePrefix(oldDbSqlSessionFactory.getDatabaseTablePrefix()); + newDbSqlSessionFactory.setTablePrefixIsSchema(oldDbSqlSessionFactory.isTablePrefixIsSchema()); + newDbSqlSessionFactory.setDatabaseCatalog(oldDbSqlSessionFactory.getDatabaseCatalog()); + newDbSqlSessionFactory.setDatabaseSchema(oldDbSqlSessionFactory.getDatabaseSchema()); + newDbSqlSessionFactory.setSqlSessionFactory(oldDbSqlSessionFactory.getSqlSessionFactory()); + newDbSqlSessionFactory.setDbHistoryUsed(oldDbSqlSessionFactory.isDbHistoryUsed()); + newDbSqlSessionFactory.setDatabaseSpecificStatements(oldDbSqlSessionFactory.getDatabaseSpecificStatements()); + newDbSqlSessionFactory.setLogicalNameToClassMapping(oldDbSqlSessionFactory.getLogicalNameToClassMapping()); + processEngineConfiguration.addSessionFactory(newDbSqlSessionFactory); + } + + @AfterEach + protected void tearDown() { + processEngineConfiguration.addSessionFactory(oldDbSqlSessionFactory); + FlowableProfiler.getInstance().reset(); + + for (Deployment deployment : repositoryService.createDeploymentQuery().list()) { + repositoryService.deleteDeployment(deployment.getId(), true); + } + for (CmmnDeployment deployment : cmmnRepositoryService.createDeploymentQuery().list()) { + cmmnRepositoryService.deleteDeployment(deployment.getId(), true); + } + for (DmnDeployment deployment : dmnRepositoryService.createDeploymentQuery().list()) { + dmnRepositoryService.deleteDeployment(deployment.getId()); + } + } + + @Test + public void testProcessWithDecisionTaskFromSameDeployment() { + repositoryService.createDeployment() + .addClasspathResource("org/flowable/dmn/engine/test/deployment/oneDecisionTaskProcessSameDeployment.bpmn20.xml") + .addClasspathResource("org/flowable/dmn/engine/test/deployment/simple.dmn") + .deploy(); + + Map selects = profileDatabaseSelects(processEngineConfiguration.getCommandExecutor(), + "org.flowable.engine.impl.cmd.StartProcessInstanceCmd", + () -> runtimeService.createProcessInstanceBuilder() + .processDefinitionKey("oneDecisionTaskProcess") + .variable("inputVariable1", 1) + .start()); + + // The cached process definition has the parent deployment id, so the process deployment is not selected + assertThat(selects).containsOnly( + entry("selectLatestProcessDefinitionByKey", 1L), + entry("selectProcessDefinitionInfoByProcessDefinitionId", 1L), + entry("selectDmnDeploymentsByQueryCriteria", 1L), + entry("selectDecisionByDeploymentAndKey", 1L), + entry("selectExecutionsByParentExecutionId", 2L), + entry("selectChildExecutionsByProcessInstanceId", 1L), + entry("selectVariablesByQuery", 1L), + entry("selectEntityLinksByQuery", 1L) + ); + assertThat(historyService.createHistoricVariableInstanceQuery().variableName("outputVariable1").singleResult()) + .extracting(HistoricVariableInstance::getValue) + .isEqualTo("result1"); + } + + @Test + public void testCaseWithDecisionTaskFromSameDeployment() { + repositoryService.createDeployment() + .addClasspathResource("org/flowable/dmn/engine/test/deployment/oneDecisionTaskCaseSameDeployment.cmmn") + .addClasspathResource("org/flowable/cmmn/test/runtime/DecisionTaskTest.testDecisionServiceTask.dmn") + .deploy(); + + Map selects = profileDatabaseSelects(cmmnEngineConfiguration.getCommandExecutor(), + "org.flowable.cmmn.engine.impl.cmd.StartCaseInstanceCmd", + () -> cmmnRuntimeService.createCaseInstanceBuilder() + .caseDefinitionKey("oneDecisionTaskCase") + .variable("testVar", "test2") + .start()); + + // The cached case definition has the parent deployment id, so the CMMN deployment is not selected + assertThat(selects).containsOnly( + entry("selectLatestCaseDefinitionByKey", 1L), + entry("selectDmnDeploymentsByQueryCriteria", 1L), + entry("selectDecisionByDeploymentAndKey", 1L), + entry("selectPlanItemInstancesByCaseInstanceIdAndPlanItemId", 1L), + entry("selectEventSubscriptionsByScopeIdAndType", 1L) + ); + CaseInstance caseInstance = cmmnRuntimeService.createCaseInstanceQuery().caseDefinitionKey("oneDecisionTaskCase").singleResult(); + assertThat(cmmnRuntimeService.getVariable(caseInstance.getId(), "resultVar")).isEqualTo("executed"); + } + + @Test + public void testGetDecisionsForProcessDefinition() { + repositoryService.createDeployment() + .addClasspathResource("org/flowable/dmn/engine/test/deployment/oneDecisionTaskProcessSameDeployment.bpmn20.xml") + .addClasspathResource("org/flowable/dmn/engine/test/deployment/simple.dmn") + .deploy(); + ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().processDefinitionKey("oneDecisionTaskProcess").singleResult(); + + Map selects = profileDatabaseSelects(processEngineConfiguration.getCommandExecutor(), + "org.flowable.engine.impl.cmd.GetDecisionsForProcessDefinitionCmd", + () -> assertThat(repositoryService.getDecisionsForProcessDefinition(processDefinition.getId())) + .extracting(DmnDecision::getKey) + .containsExactly("decision1")); + + // The cached process definition has the parent deployment id, so the process deployment is not selected + assertThat(selects).containsOnly( + entry("selectDmnDeploymentsByQueryCriteria", 1L), + entry("selectDecisionsByQueryCriteria", 1L) + ); + } + + @Test + public void testGetDecisionsForCaseDefinition() { + repositoryService.createDeployment() + .addClasspathResource("org/flowable/dmn/engine/test/deployment/oneDecisionTaskCaseSameDeployment.cmmn") + .addClasspathResource("org/flowable/cmmn/test/runtime/DecisionTaskTest.testDecisionServiceTask.dmn") + .deploy(); + CaseDefinition caseDefinition = cmmnRepositoryService.createCaseDefinitionQuery().caseDefinitionKey("oneDecisionTaskCase").singleResult(); + + Map selects = profileDatabaseSelects(cmmnEngineConfiguration.getCommandExecutor(), + "org.flowable.cmmn.engine.impl.cmd.GetDecisionsForCaseDefinitionCmd", + () -> assertThat(cmmnRepositoryService.getDecisionsForCaseDefinition(caseDefinition.getId())) + .extracting(DmnDecision::getKey) + .containsExactly("DecisionTable")); + + // The cached case definition has the parent deployment id, so the CMMN deployment is not selected + assertThat(selects).containsOnly( + entry("selectDmnDeploymentsByQueryCriteria", 1L), + entry("selectDecisionsByQueryCriteria", 1L) + ); + } + + protected Map profileDatabaseSelects(CommandExecutor commandExecutor, String commandClass, Runnable runnable) { + // Only the outer command is profiled, the commands of the other engines reuse its command context + CommandExecutorImpl commandExecutorImpl = (CommandExecutorImpl) commandExecutor; + CommandInterceptor oldFirstCommandInterceptor = commandExecutorImpl.getFirst(); + TotalExecutionTimeCommandInterceptor timeCommandInterceptor = new TotalExecutionTimeCommandInterceptor(); + timeCommandInterceptor.setNext(oldFirstCommandInterceptor); + commandExecutorImpl.setFirst(timeCommandInterceptor); + + FlowableProfiler profiler = FlowableProfiler.getInstance(); + profiler.reset(); + profiler.startProfileSession("Profiling session"); + try { + runnable.run(); + } finally { + profiler.stopCurrentProfileSession(); + commandExecutorImpl.setFirst(oldFirstCommandInterceptor); + } + + return profiler.getProfileSessions().get(0).calculateSummaryStatistics().get(commandClass).getDbSelects(); + } +} diff --git a/modules/flowable-dmn-engine-configurator/src/test/resources/org/flowable/dmn/engine/test/deployment/oneDecisionTaskCaseSameDeployment.cmmn b/modules/flowable-dmn-engine-configurator/src/test/resources/org/flowable/dmn/engine/test/deployment/oneDecisionTaskCaseSameDeployment.cmmn new file mode 100644 index 00000000000..1c06bb2508b --- /dev/null +++ b/modules/flowable-dmn-engine-configurator/src/test/resources/org/flowable/dmn/engine/test/deployment/oneDecisionTaskCaseSameDeployment.cmmn @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + complete + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/deployer/BpmnDeploymentHelper.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/deployer/BpmnDeploymentHelper.java index 473ba3bc0b9..92714e88b30 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/deployer/BpmnDeploymentHelper.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/bpmn/deployer/BpmnDeploymentHelper.java @@ -74,13 +74,14 @@ public void verifyProcessDefinitionsDoNotShareKeys(Collection processDefinitions) { String engineVersion = deployment.getEngineVersion(); String tenantId = deployment.getTenantId(); String deploymentId = deployment.getId(); + String parentDeploymentId = deployment.getParentDeploymentId(); for (ProcessDefinitionEntity processDefinition : processDefinitions) { @@ -95,6 +96,7 @@ public void copyDeploymentValuesToProcessDefinitions(DeploymentEntity deployment } processDefinition.setDeploymentId(deploymentId); + processDefinition.setParentDeploymentId(parentDeploymentId); } } diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/cmd/GetDecisionsForProcessDefinitionCmd.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/cmd/GetDecisionsForProcessDefinitionCmd.java index cd2fa3208f5..256e4f3ed0e 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/cmd/GetDecisionsForProcessDefinitionCmd.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/cmd/GetDecisionsForProcessDefinitionCmd.java @@ -31,7 +31,6 @@ import org.flowable.dmn.api.DmnRepositoryService; import org.flowable.engine.impl.util.CommandContextUtil; import org.flowable.engine.impl.util.ProcessDefinitionUtil; -import org.flowable.engine.repository.Deployment; import org.flowable.engine.repository.ProcessDefinition; /** @@ -98,16 +97,11 @@ protected List getDecisionsFromModel(BpmnModel bpmnModel, ProcessDe protected void addDecisionToCollection(List decisions, String decisionKey, ProcessDefinition processDefinition) { DmnDecisionQuery definitionQuery = dmnRepositoryService.createDecisionQuery().decisionKey(decisionKey); - Deployment deployment = CommandContextUtil.getDeploymentEntityManager().findById(processDefinition.getDeploymentId()); - if (deployment.getParentDeploymentId() != null) { - List dmnDeployments = dmnRepositoryService.createDeploymentQuery().parentDeploymentId(deployment.getParentDeploymentId()).list(); - - if (dmnDeployments != null && dmnDeployments.size() > 0) { - definitionQuery.deploymentId(dmnDeployments.get(0).getId()); - } else { - definitionQuery.latestVersion(); - } - + String parentDeploymentId = ProcessDefinitionUtil.getDefinitionDeploymentId(processDefinition, CommandContextUtil.getProcessEngineConfiguration()); + List dmnDeployments = dmnRepositoryService.createDeploymentQuery().parentDeploymentId(parentDeploymentId).list(); + + if (!dmnDeployments.isEmpty()) { + definitionQuery.deploymentId(dmnDeployments.get(0).getId()); } else { definitionQuery.latestVersion(); } diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/cmd/GetFormDefinitionsForProcessDefinitionCmd.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/cmd/GetFormDefinitionsForProcessDefinitionCmd.java index 09e1b5f2b54..b0b53e5d33b 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/cmd/GetFormDefinitionsForProcessDefinitionCmd.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/cmd/GetFormDefinitionsForProcessDefinitionCmd.java @@ -28,7 +28,6 @@ import org.flowable.common.engine.impl.interceptor.CommandContext; import org.flowable.engine.impl.util.CommandContextUtil; import org.flowable.engine.impl.util.ProcessDefinitionUtil; -import org.flowable.engine.repository.Deployment; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.form.api.FormDefinition; import org.flowable.form.api.FormDefinitionQuery; @@ -103,16 +102,11 @@ protected List getFormDefinitionsFromModel(BpmnModel bpmnModel, protected void addFormDefinitionToCollection(List formDefinitions, String formKey, ProcessDefinition processDefinition) { FormDefinitionQuery formDefinitionQuery = formRepositoryService.createFormDefinitionQuery().formDefinitionKey(formKey); - Deployment deployment = CommandContextUtil.getDeploymentEntityManager().findById(processDefinition.getDeploymentId()); - if (deployment.getParentDeploymentId() != null) { - List formDeployments = formRepositoryService.createDeploymentQuery().parentDeploymentId(deployment.getParentDeploymentId()).list(); - - if (formDeployments != null && formDeployments.size() > 0) { - formDefinitionQuery.deploymentId(formDeployments.get(0).getId()); - } else { - formDefinitionQuery.latestVersion(); - } - + String parentDeploymentId = ProcessDefinitionUtil.getDefinitionDeploymentId(processDefinition, CommandContextUtil.getProcessEngineConfiguration()); + List formDeployments = formRepositoryService.createDeploymentQuery().parentDeploymentId(parentDeploymentId).list(); + + if (!formDeployments.isEmpty()) { + formDefinitionQuery.deploymentId(formDeployments.get(0).getId()); } else { formDefinitionQuery.latestVersion(); } diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/cmd/GetStartFormModelCmd.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/cmd/GetStartFormModelCmd.java index b9310d41c71..6170c356c12 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/cmd/GetStartFormModelCmd.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/cmd/GetStartFormModelCmd.java @@ -26,7 +26,6 @@ import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; import org.flowable.engine.impl.util.CommandContextUtil; import org.flowable.engine.impl.util.ProcessDefinitionUtil; -import org.flowable.engine.repository.Deployment; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.form.api.FormFieldHandler; import org.flowable.form.api.FormInfo; @@ -62,8 +61,8 @@ public FormInfo execute(CommandContext commandContext) { FlowElement startElement = process.getInitialFlowElement(); if (startElement instanceof StartEvent startEvent) { if (StringUtils.isNotEmpty(startEvent.getFormKey())) { - Deployment deployment = CommandContextUtil.getDeploymentEntityManager(commandContext).findById(processDefinition.getDeploymentId()); - formInfo = formService.getFormInstanceModelByKeyAndParentDeploymentId(startEvent.getFormKey(), deployment.getParentDeploymentId(), + String parentDeploymentId = ProcessDefinitionUtil.getDefinitionDeploymentId(processDefinition, processEngineConfiguration); + formInfo = formService.getFormInstanceModelByKeyAndParentDeploymentId(startEvent.getFormKey(), parentDeploymentId, null, processInstanceId, null, processDefinition.getTenantId(), processEngineConfiguration.isFallbackToDefaultTenant()); } } diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/persistence/entity/ProcessDefinitionEntity.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/persistence/entity/ProcessDefinitionEntity.java index cf1b07f9e06..2a112f2f96c 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/persistence/entity/ProcessDefinitionEntity.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/persistence/entity/ProcessDefinitionEntity.java @@ -75,6 +75,14 @@ public interface ProcessDefinitionEntity extends ProcessDefinition, Entity, HasR String getEngineVersion(); void setEngineVersion(String engineVersion); + + /** + * The parent deployment id of the deployment of this process definition. + * This is not persisted, it is only set on the process definitions that are put in the process definition cache. + */ + String getParentDeploymentId(); + + void setParentDeploymentId(String parentDeploymentId); List getIdentityLinks(); diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/persistence/entity/ProcessDefinitionEntityImpl.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/persistence/entity/ProcessDefinitionEntityImpl.java index ef26cc64fc2..27c1c89b6d9 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/persistence/entity/ProcessDefinitionEntityImpl.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/persistence/entity/ProcessDefinitionEntityImpl.java @@ -59,6 +59,9 @@ public class ProcessDefinitionEntityImpl extends AbstractBpmnEngineEntity implem // Backwards compatibility protected String engineVersion; + // Not persisted, set by the deployer + protected String parentDeploymentId; + @Override public Object getPersistentState() { Map persistentState = new HashMap<>(); @@ -312,6 +315,16 @@ public void setEngineVersion(String engineVersion) { this.engineVersion = engineVersion; } + @Override + public String getParentDeploymentId() { + return parentDeploymentId; + } + + @Override + public void setParentDeploymentId(String parentDeploymentId) { + this.parentDeploymentId = parentDeploymentId; + } + public IOSpecification getIoSpecification() { return ioSpecification; } diff --git a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/util/ProcessDefinitionUtil.java b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/util/ProcessDefinitionUtil.java index 0fec6a48773..bc3c8956ebc 100644 --- a/modules/flowable-engine/src/main/java/org/flowable/engine/impl/util/ProcessDefinitionUtil.java +++ b/modules/flowable-engine/src/main/java/org/flowable/engine/impl/util/ProcessDefinitionUtil.java @@ -45,6 +45,16 @@ public static String getDefinitionDeploymentId(String processDefinitionId, Proce public static String getDefinitionDeploymentId(ProcessDefinition processDefinition, ProcessEngineConfigurationImpl processEngineConfiguration) { if (processDefinition != null) { + String parentDeploymentId = getParentDeploymentId(processDefinition); + if (parentDeploymentId == null) { + // A process definition loaded from the database does not have the parent deployment id, the cached one has it + parentDeploymentId = getParentDeploymentId(getProcessDefinition(processDefinition.getId(), true, processEngineConfiguration)); + } + + if (parentDeploymentId != null) { + return parentDeploymentId; + } + DeploymentManager deploymentManager = processEngineConfiguration.getDeploymentManager(); DeploymentEntity processDeployment = deploymentManager.getDeploymentEntityManager().findById(processDefinition.getDeploymentId()); if (StringUtils.isEmpty(processDeployment.getParentDeploymentId())) { @@ -56,6 +66,14 @@ public static String getDefinitionDeploymentId(ProcessDefinition processDefiniti return null; } + protected static String getParentDeploymentId(ProcessDefinition processDefinition) { + if (processDefinition instanceof ProcessDefinitionEntity processDefinitionEntity + && StringUtils.isNotEmpty(processDefinitionEntity.getParentDeploymentId())) { + return processDefinitionEntity.getParentDeploymentId(); + } + return null; + } + public static ProcessDefinition getProcessDefinition(String processDefinitionId) { return getProcessDefinition(processDefinitionId, false); } diff --git a/modules/flowable-rest/src/main/java/org/flowable/rest/service/api/repository/ProcessDefinitionResource.java b/modules/flowable-rest/src/main/java/org/flowable/rest/service/api/repository/ProcessDefinitionResource.java index e08fee3e615..d6d7ede9871 100644 --- a/modules/flowable-rest/src/main/java/org/flowable/rest/service/api/repository/ProcessDefinitionResource.java +++ b/modules/flowable-rest/src/main/java/org/flowable/rest/service/api/repository/ProcessDefinitionResource.java @@ -27,9 +27,9 @@ import org.flowable.common.rest.exception.FlowableConflictException; import org.flowable.engine.ProcessMigrationService; import org.flowable.engine.impl.cfg.ProcessEngineConfigurationImpl; +import org.flowable.engine.impl.persistence.entity.ProcessDefinitionEntity; import org.flowable.engine.migration.ProcessInstanceMigrationDocument; import org.flowable.engine.migration.ProcessInstanceMigrationDocumentConverter; -import org.flowable.engine.repository.Deployment; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.form.api.FormEngineConfigurationApi; import org.flowable.form.api.FormInfo; @@ -204,9 +204,11 @@ protected FormInfo getStartForm(FormRepositoryService formRepositoryService, Pro StartEvent startEvent = (StartEvent) startElement; if (StringUtils.isNotEmpty(startEvent.getFormKey())) { if (startEvent.isSameDeployment()) { - Deployment deployment = repositoryService.createDeploymentQuery().deploymentId(processDefinition.getDeploymentId()).singleResult(); + // The process definition comes from the definition cache, which has the parent deployment id + String parentDeploymentId = processDefinition instanceof ProcessDefinitionEntity processDefinitionEntity + ? processDefinitionEntity.getParentDeploymentId() : null; formInfo = formRepositoryService.getFormModelByKeyAndParentDeploymentId(startEvent.getFormKey(), - deployment.getParentDeploymentId(), processDefinition.getTenantId(), processEngineConfiguration.isFallbackToDefaultTenant()); + parentDeploymentId, processDefinition.getTenantId(), processEngineConfiguration.isFallbackToDefaultTenant()); } else { formInfo = formRepositoryService.getFormModelByKey(startEvent.getFormKey(), processDefinition.getTenantId(),