From ebe765c65441fda8d0cf1b4e149ab3aed1a44e43 Mon Sep 17 00:00:00 2001 From: psavidis <69160690+psavidis@users.noreply.github.com> Date: Tue, 13 Jun 2023 10:00:46 +0300 Subject: [PATCH] fix(engine): Pass-through historyTTL to DecisionDefinitionHandler - This commit ensures that if a Decision has no historyTimeToLive configured, the global historyTimeToLive will be used instead from ProcessEngineConfiguration. Related to: #2496 --- .../cfg/ProcessEngineConfigurationImpl.java | 3 +- .../DmnEngineConfigurationBuilder.java | 7 +- .../DecisionDefinitionHandler.java | 23 +- .../deployment/DecisionDefinitionTest.java | 202 ++++++++++++++++++ 4 files changed, 230 insertions(+), 5 deletions(-) create mode 100644 engine/src/test/java/org/camunda/bpm/engine/test/dmn/deployment/DecisionDefinitionTest.java diff --git a/engine/src/main/java/org/camunda/bpm/engine/impl/cfg/ProcessEngineConfigurationImpl.java b/engine/src/main/java/org/camunda/bpm/engine/impl/cfg/ProcessEngineConfigurationImpl.java index f44da8d5219..c950a1919f9 100644 --- a/engine/src/main/java/org/camunda/bpm/engine/impl/cfg/ProcessEngineConfigurationImpl.java +++ b/engine/src/main/java/org/camunda/bpm/engine/impl/cfg/ProcessEngineConfigurationImpl.java @@ -2641,7 +2641,8 @@ protected void initDmnEngine() { .dmnHistoryEventProducer(dmnHistoryEventProducer) .scriptEngineResolver(scriptingEngines) .feelCustomFunctionProviders(dmnFeelCustomFunctionProviders) - .enableFeelLegacyBehavior(dmnFeelEnableLegacyBehavior); + .enableFeelLegacyBehavior(dmnFeelEnableLegacyBehavior) + .historyTimeToLive(historyTimeToLive); if (dmnElProvider != null) { dmnEngineConfigurationBuilder.elProvider(dmnElProvider); diff --git a/engine/src/main/java/org/camunda/bpm/engine/impl/dmn/configuration/DmnEngineConfigurationBuilder.java b/engine/src/main/java/org/camunda/bpm/engine/impl/dmn/configuration/DmnEngineConfigurationBuilder.java index 5b038b6d891..de88ca328f2 100644 --- a/engine/src/main/java/org/camunda/bpm/engine/impl/dmn/configuration/DmnEngineConfigurationBuilder.java +++ b/engine/src/main/java/org/camunda/bpm/engine/impl/dmn/configuration/DmnEngineConfigurationBuilder.java @@ -49,6 +49,7 @@ public class DmnEngineConfigurationBuilder { protected DmnScriptEngineResolver scriptEngineResolver; protected ElProvider elProvider; protected List feelCustomFunctionProviders; + protected String historyTimeToLive; /** * Creates a new builder to modify the given DMN engine configuration. @@ -94,7 +95,7 @@ public DefaultDmnEngineConfiguration build() { // override the decision table handler DmnTransformer dmnTransformer = dmnEngineConfiguration.getTransformer(); dmnTransformer.getElementTransformHandlerRegistry().addHandler(Definitions.class, new DecisionRequirementsDefinitionTransformHandler()); - dmnTransformer.getElementTransformHandlerRegistry().addHandler(Decision.class, new DecisionDefinitionHandler()); + dmnTransformer.getElementTransformHandlerRegistry().addHandler(Decision.class, new DecisionDefinitionHandler(historyTimeToLive)); // do not override the script engine resolver if set if (dmnEngineConfiguration.getScriptEngineResolver() == null) { @@ -136,4 +137,8 @@ public DmnEngineConfigurationBuilder enableFeelLegacyBehavior(boolean dmnFeelEna return this; } + public DmnEngineConfigurationBuilder historyTimeToLive(String historyTimeToLive) { + this.historyTimeToLive = historyTimeToLive; + return this; + } } diff --git a/engine/src/main/java/org/camunda/bpm/engine/impl/dmn/transformer/DecisionDefinitionHandler.java b/engine/src/main/java/org/camunda/bpm/engine/impl/dmn/transformer/DecisionDefinitionHandler.java index b12c87e9c67..3edf2f513d8 100644 --- a/engine/src/main/java/org/camunda/bpm/engine/impl/dmn/transformer/DecisionDefinitionHandler.java +++ b/engine/src/main/java/org/camunda/bpm/engine/impl/dmn/transformer/DecisionDefinitionHandler.java @@ -25,6 +25,12 @@ public class DecisionDefinitionHandler extends DmnDecisionTransformHandler { + protected final Integer configuredHistoryTTL; + + public DecisionDefinitionHandler(String configuredHistoryTTL) { + this.configuredHistoryTTL = ParseUtil.parseHistoryTimeToLive(configuredHistoryTTL); + } + @Override protected DmnDecisionImpl createDmnElement() { return new DecisionDefinitionEntity(); @@ -33,13 +39,24 @@ protected DmnDecisionImpl createDmnElement() { @Override protected DmnDecisionImpl createFromDecision(DmnElementTransformContext context, Decision decision) { DecisionDefinitionEntity decisionDefinition = (DecisionDefinitionEntity) super.createFromDecision(context, decision); - String category = context.getModelInstance().getDefinitions().getNamespace(); + decisionDefinition.setCategory(category); - decisionDefinition.setHistoryTimeToLive(ParseUtil.parseHistoryTimeToLive(decision.getCamundaHistoryTimeToLiveString())); decisionDefinition.setVersionTag(decision.getVersionTag()); + setHistoryTTL(decision, decisionDefinition); + return decisionDefinition; } -} + private void setHistoryTTL(Decision decision, DecisionDefinitionEntity decisionDefinition) { + Integer localHistoryTimeToLive = ParseUtil.parseHistoryTimeToLive(decision.getCamundaHistoryTimeToLiveString()); + + if (localHistoryTimeToLive != null) { + decisionDefinition.setHistoryTimeToLive(localHistoryTimeToLive); + } else { + decisionDefinition.setHistoryTimeToLive(configuredHistoryTTL); + } + + } +} \ No newline at end of file diff --git a/engine/src/test/java/org/camunda/bpm/engine/test/dmn/deployment/DecisionDefinitionTest.java b/engine/src/test/java/org/camunda/bpm/engine/test/dmn/deployment/DecisionDefinitionTest.java new file mode 100644 index 00000000000..b505395f8af --- /dev/null +++ b/engine/src/test/java/org/camunda/bpm/engine/test/dmn/deployment/DecisionDefinitionTest.java @@ -0,0 +1,202 @@ +/* + * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH + * under one or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information regarding copyright + * ownership. Camunda licenses this file to you under the Apache License, + * Version 2.0; 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.camunda.bpm.engine.test.dmn.deployment; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.temporal.ChronoUnit; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import org.camunda.bpm.engine.DecisionService; +import org.camunda.bpm.engine.HistoryService; +import org.camunda.bpm.engine.RepositoryService; +import org.camunda.bpm.engine.history.HistoricDecisionInstance; +import org.camunda.bpm.engine.impl.util.ClockUtil; +import org.camunda.bpm.engine.repository.DeploymentBuilder; +import org.camunda.bpm.engine.repository.DeploymentWithDefinitions; +import org.camunda.bpm.engine.test.util.ProcessEngineBootstrapRule; +import org.camunda.bpm.engine.test.util.ProcessEngineTestRule; +import org.camunda.bpm.engine.test.util.ProvidedProcessEngineRule; +import org.camunda.bpm.model.dmn.Dmn; +import org.camunda.bpm.model.dmn.DmnModelInstance; +import org.camunda.bpm.model.dmn.HitPolicy; +import org.camunda.bpm.model.dmn.impl.DmnModelConstants; +import org.camunda.bpm.model.dmn.instance.Decision; +import org.camunda.bpm.model.dmn.instance.DecisionTable; +import org.camunda.bpm.model.dmn.instance.Definitions; +import org.camunda.bpm.model.dmn.instance.Input; +import org.camunda.bpm.model.dmn.instance.InputExpression; +import org.camunda.bpm.model.dmn.instance.Output; +import org.camunda.bpm.model.dmn.instance.Text; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.RuleChain; + +public class DecisionDefinitionTest { + + protected ProcessEngineBootstrapRule bootstrapRule = new ProcessEngineBootstrapRule(configuration -> { + configuration.setHistoryTimeToLive("P30D"); + }); + + protected ProvidedProcessEngineRule engineRule = new ProvidedProcessEngineRule(bootstrapRule); + + protected ProcessEngineTestRule testRule = new ProcessEngineTestRule(engineRule); + + @Rule + public RuleChain ruleChain = RuleChain.outerRule(bootstrapRule).around(engineRule).around(testRule); + + protected RepositoryService repositoryService; + protected DecisionService decisionService; + protected HistoryService historyService; + + @Before + public void init() { + this.repositoryService = engineRule.getRepositoryService(); + this.decisionService = engineRule.getDecisionService(); + this.historyService = engineRule.getHistoryService(); + } + + @Test + public void shouldUseHistoryTTLOnDecisionDefinitions() { + // given + DmnModelInstance model = createDmnModelInstance(null); + + DeploymentBuilder builder = repositoryService.createDeployment().addModelInstance("foo.dmn", model); + + // when + DeploymentWithDefinitions deployment = testRule.deploy(builder); + + // then + assertThat(deployment.getDeployedDecisionDefinitions().size()).isEqualTo(1); + assertThat(deployment.getDeployedDecisionDefinitions().get(0).getHistoryTimeToLive()).isEqualTo(30); + } + + @Test + public void shouldNotOverrideWithGlobalConfigOnDecisionHistoryTTLPresence() { + // given + DmnModelInstance model = createDmnModelInstance("P10D"); + + DeploymentBuilder builder = repositoryService.createDeployment().addModelInstance("foo.dmn", model); + + // when + DeploymentWithDefinitions deployment = testRule.deploy(builder); + + // then + assertThat(deployment.getDeployedDecisionDefinitions().size()).isEqualTo(1); + assertThat(deployment.getDeployedDecisionDefinitions().get(0).getHistoryTimeToLive()).isEqualTo(10); + } + + @Test + public void shouldApplyHistoryTTLOnRemovalTimeOfDecisionInstanceLocal() { + // given + DmnModelInstance model = createDmnModelInstance("P10D"); + DeploymentBuilder builder = repositoryService.createDeployment().addModelInstance("foo.dmn", model); + + testRule.deploy(builder); + + Map variables = new HashMap<>(); + variables.put("input", "single entry"); + + // when + decisionService.evaluateDecisionByKey("Decision-1") + .variables(variables) + .evaluate(); + + HistoricDecisionInstance result = historyService.createHistoricDecisionInstanceQuery().singleResult(); + + // then + Date expectedRemovalDate = Date.from(ClockUtil.now() + .toInstant() + .plus(10, ChronoUnit.DAYS) + ); + + assertThat(result.getRemovalTime()).isInSameDayAs(expectedRemovalDate); + } + + @Test + public void shouldApplyHistoryTTLOnRemovalTimeOfDecisionInstanceGlobal() { + // given + DmnModelInstance model = createDmnModelInstance(null); + DeploymentBuilder builder = repositoryService.createDeployment().addModelInstance("foo.dmn", model); + + testRule.deploy(builder); + + Map variables = new HashMap<>(); + variables.put("input", "single entry"); + + // when + decisionService.evaluateDecisionByKey("Decision-1") + .variables(variables) + .evaluate(); + + HistoricDecisionInstance result = historyService.createHistoricDecisionInstanceQuery().singleResult(); + + // then + Date expectedRemovalDate = Date.from(ClockUtil.now() + .toInstant() + .plus(30, ChronoUnit.DAYS) + ); + + assertThat(result.getRemovalTime()).isInSameDayAs(expectedRemovalDate); + } + + protected DmnModelInstance createDmnModelInstance(String historyTTL) { + DmnModelInstance modelInstance = Dmn.createEmptyModel(); + Definitions definitions = modelInstance.newInstance(Definitions.class); + definitions.setId(DmnModelConstants.DMN_ELEMENT_DEFINITIONS); + definitions.setName(DmnModelConstants.DMN_ELEMENT_DEFINITIONS); + definitions.setNamespace(DmnModelConstants.CAMUNDA_NS); + modelInstance.setDefinitions(definitions); + + Decision decision = modelInstance.newInstance(Decision.class); + decision.setId("Decision-1"); + decision.setName("foo"); + + decision.setCamundaHistoryTimeToLiveString(historyTTL); + + modelInstance.getDefinitions().addChildElement(decision); + + DecisionTable decisionTable = modelInstance.newInstance(DecisionTable.class); + decisionTable.setId(DmnModelConstants.DMN_ELEMENT_DECISION_TABLE); + decisionTable.setHitPolicy(HitPolicy.FIRST); + decision.addChildElement(decisionTable); + + Input input = modelInstance.newInstance(Input.class); + input.setId("Input-1"); + input.setLabel("Input"); + decisionTable.addChildElement(input); + + InputExpression inputExpression = modelInstance.newInstance(InputExpression.class); + inputExpression.setId("InputExpression-1"); + Text inputExpressionText = modelInstance.newInstance(Text.class); + inputExpressionText.setTextContent("input"); + inputExpression.setText(inputExpressionText); + inputExpression.setTypeRef("string"); + input.setInputExpression(inputExpression); + + Output output = modelInstance.newInstance(Output.class); + output.setName("output"); + output.setLabel("Output"); + output.setTypeRef("string"); + decisionTable.addChildElement(output); + + return modelInstance; + } +}