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(),