Skip to content

Commit

Permalink
fix(engine): Pass-through historyTTL to DecisionDefinitionHandler
Browse files Browse the repository at this point in the history
- This commit ensures that if a Decision has no historyTimeToLive configured, the global historyTimeToLive will be used instead from ProcessEngineConfiguration.

Related to: camunda#2496
  • Loading branch information
psavidis authored Jun 13, 2023
1 parent 876ae80 commit ebe765c
Show file tree
Hide file tree
Showing 4 changed files with 230 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public class DmnEngineConfigurationBuilder {
protected DmnScriptEngineResolver scriptEngineResolver;
protected ElProvider elProvider;
protected List<FeelCustomFunctionProvider> feelCustomFunctionProviders;
protected String historyTimeToLive;

/**
* Creates a new builder to modify the given DMN engine configuration.
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -136,4 +137,8 @@ public DmnEngineConfigurationBuilder enableFeelLegacyBehavior(boolean dmnFeelEna
return this;
}

public DmnEngineConfigurationBuilder historyTimeToLive(String historyTimeToLive) {
this.historyTimeToLive = historyTimeToLive;
return this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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);
}

}
}
Original file line number Diff line number Diff line change
@@ -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<String, Object> 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<String, Object> 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;
}
}

0 comments on commit ebe765c

Please sign in to comment.