DmnDecisionTableResult and matching rule

I am missing a property to access to the matching rule or to know which matching rule is applied, after evaluating a DecisionTable. Why is not this information in the class DmnDecisionTableResult?

I read that I can add a listener like that:

dmnConfig.getCustomPostDecisionTableEvaluationListeners().add(myListener);

After processing this code line :
processEngine().getDecisionService().evaluateDecisionTableByKey(decisionDefinitionKey, variables);

I am waiting that my listener is fired, but it isn’t. Any ideas? I am testing my DecisionTable in a UnitTest.

Hi @MrPinedita,

it looks good so far. Please have a look at the User Guide which describes how to configure the DMN engine in combination with the process engine. Note that it is different to a standalone DMN engine.

If you still have his problem then please prove a full test case.

Best regards,
Philipp

I have tried with the example in the User Guida and I am getting an exception… Maybe can you understand why the listener is not running after evaluation the decission.

@Test
  @Deployment(resources = {"process.bpmn", "twitter_approval.dmn"})
  public void testProcessPublicWithDmnNotApproved_When_Content_Contains_HELLO() {
      Map<String, Object> variables = new HashMap<String, Object>();
      variables.put("content", "Content contains the word HELLO");
      variables.put("email", "xyz@xyz.com");
      
      
      ProcessEngineConfigurationImpl processEngineConfig = (ProcessEngineConfigurationImpl) processEngine().getProcessEngineConfiguration();
      //instantiate the listener
      DefaultDmnEngineConfiguration dmnEngineConfig = processEngineConfig.getDmnEngineConfiguration();
      
      DmnDecisionTableEvaluationListener myListener = new DmnTweetContentDecisionTableEvaluationListener();
      // notify after default listeners
      dmnEngineConfig.getCustomPostDecisionTableEvaluationListeners().add(myListener);
      
      //The function notify of DmnTweetContentDecisionTableEvaluationListener should be called after evaluating the DecisionTable. But nothing happens.
      DmnDecisionTableResult result = processEngine().getDecisionService().evaluateDecisionTableByKey("twitter_approval", variables);
      
      assertThat(result.getFirstResult()).containsEntry("approved",false);      
  }

Hi @MrPinedita,

you need to create and add the DMN engine configuration before you build the process engine. Please follow the example in the User Guide.

If this doesn’t help you then please provide a complete failing test case.

Best regards,
Philipp

Hi @Philipp_Ossler,

the problem is that the test class has a class field of type ProcessEngineRule. A ProcessEngineRule is instantiated and a process engine is build (the configuration of the process engine is read from the configuration file camunda.cfg.xml). The buildProcessEngine creates a h2 database. So when I am executing the test case, there is a process engine build and I am trying now to create another process engine with a DmnEngineConfiguration created by own. When I am calling the function buildProcessEngine() of my new ProcessEngineConfiguration I am getting the following exception.

I would like to keep the ProcessEngineRule class field for the other test cases. My code is the following:

/**
 * Test case starting an in-memory database-backed Process Engine.
 */
public class InMemoryH2Test {

  //MrPinedita: Here is build a process engine!
  @Rule
  public ProcessEngineRule rule = new ProcessEngineRule();

  private static final String PROCESS_DEFINITION_KEY = "my-first-process-app";
  
  static {
    LogFactory.useSlf4jLogging(); // MyBatis
  }

  @Before
  public void setup() {
    init(rule.getProcessEngine());
  }

  @Test
  @Deployment(resources = {"process.bpmn", "twitter_approval.dmn"})
  public void testProcessPublicWithDmnNotApproved_When_Content_Contains_Word_Hello() {
      Map<String, Object> variables = new HashMap<String, Object>();
      variables.put("content", "Content contains the word Hello");
      variables.put("email", "xyz@xyz.com");
      
      // create the process engine configuration
      ProcessEngineConfigurationImpl processEngineConfiguration = 
              (ProcessEngineConfigurationImpl) ProcessEngineConfigurationImpl.createStandaloneInMemProcessEngineConfiguration();
          
      // create the DMN engine configuration    
      DefaultDmnEngineConfiguration dmnEngineConfiguration = 
              (DefaultDmnEngineConfiguration) DmnEngineConfiguration.createDefaultDmnEngineConfiguration();

      DmnDecisionTableEvaluationListener myListener = new DmnTweetContentDecisionTableEvaluationListener();
      // notify after default listeners
      dmnEngineConfiguration.getCustomPostDecisionTableEvaluationListeners().add(myListener);
      // configure the DMN engine ...
      // e.g. set the default expression language for input expressions to `groovy`
      //dmnEngineConfiguration.setDefaultInputExpressionExpressionLanguage("groovy");
      
      // set the DMN engine configuration on the process engine configuration
      processEngineConfiguration.setDmnEngineConfiguration(dmnEngineConfiguration);

      //MrPinedita: Here I am getting the error because the Database H2 already exist and cannot create an existing table
      // build the process engine which includes the DMN engine
      processEngineConfiguration.buildProcessEngine();

      //The function notify of DmnTweetContentDecisionTableEvaluationListener should be called after evaluating the DecisionTable. But nothing happens.
      DmnDecisionTableResult result = processEngine().getDecisionService().evaluateDecisionTableByKey("twitter_approval", variables);
      assertThat(result.getFirstResult()).containsEntry("approved",false);      
  } 
  
  @After
  public void calculateCoverageForAllTests() throws Exception {
    ProcessTestCoverage.calculate(rule.getProcessEngine());
  }  

}

I would like to keep the class field ProcessEngineRule rule and only in the test case to (re)build my process engine with my DefaultDmnEngineConfiguration with the attached listener.

Hi @MrPinedita,

I see two ways to solve your issue:

Otherwise, you have to build the process engine by your own and can’t use the ProcessEngineRule.

Best regards,
Philipp

Hi @Philipp_Ossler,

I have got it with the declaration of my listener in my XML configuration.I am going to try the other strategies. Thanks!
Best regards,
Sig

In a Spring Boot app, if we have these dependencies in pom.xml - camunda-bpm-spring-boot-starter, camunda-bpm-spring-boot-starter-webapp, camunda-bpm-spring-boot-starter-rest, a default Process Engine will be auto-configured during the startup of the Spring Boot app. But this Process Engine does not by default enables the Rule details. So we configure a custom Process Engine plugin which will be applied on top of the default Process Engine in order to get the matching rule details every time DMN is hit.

import org.camunda.bpm.engine.impl.cfg.ProcessEnginePlugin;
import org.camunda.bpm.spring.boot.starter.configuration.Ordering;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;

@Configuration
public class CamundaConfig {
	
	@Bean
	@Order(Ordering.DEFAULT_ORDER + 1)
	public static ProcessEnginePlugin customProcessEnginePluginConfig() {
		return new CustomProcessEnginePlugin();
	}
	
}

Our CustomProcessEnginePlugin will extend AbstractCamundaConfiguration and override postInit() method. This postInit() method adds a DmnDecisionTableEvaluationListener. This listner triggers an event everytime DMN is hit with matching rule and has notify() method with event details which contains all the DMN details like dmnID and matching rules detail.

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.camunda.bpm.dmn.engine.delegate.DmnDecisionTableEvaluationEvent;
import org.camunda.bpm.dmn.engine.delegate.DmnDecisionTableEvaluationListener;
import org.camunda.bpm.dmn.engine.delegate.DmnEvaluatedDecisionRule;
import org.camunda.bpm.dmn.engine.delegate.DmnEvaluatedInput;
import org.camunda.bpm.dmn.engine.impl.DefaultDmnEngineConfiguration;
import org.camunda.bpm.engine.impl.cfg.ProcessEngineConfigurationImpl;
import org.camunda.bpm.engine.variable.value.TypedValue;
import org.camunda.bpm.spring.boot.starter.configuration.Ordering;
import org.camunda.bpm.spring.boot.starter.configuration.impl.AbstractCamundaConfiguration;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import lombok.extern.slf4j.Slf4j;

@Slf4j
@Component
@Order(Ordering.DEFAULT_ORDER + 1)
public class CustomProcessEnginePlugin extends AbstractCamundaConfiguration {
	@Override
	public void postInit(ProcessEngineConfigurationImpl processEngineConfig) {
		DefaultDmnEngineConfiguration dmnEngineConfig = processEngineConfig.getDmnEngineConfiguration();
		dmnEngineConfig.customPostDecisionTableEvaluationListeners(Collections.singletonList(new DmnDecisionTableEvaluationListener(){

			@Override
			public void notify(DmnDecisionTableEvaluationEvent event) {
				String dmnID = event.getDecisionTable().getKey();
				Map<String, TypedValue> dmnInput = event.getInputs().stream().collect(Collectors.toMap(DmnEvaluatedInput::getName, DmnEvaluatedInput::getValue));
				List<DmnEvaluatedDecisionRule> matchingRuleList = new ArrayList<>();
				matchingRuleList = event.getMatchingRules();
				log.info("DMN ID = {}", dmnID);
				log.info("DMN Input = {}", dmnInput);

				if(null != matchingRuleList) {
					log.info("DMN Matched Rules = {}", matchingRuleList.size());
					if(!matchingRuleList.isEmpty()) {
						for(DmnEvaluatedDecisionRule rule : matchingRuleList) {
							log.info("DMN Matching Rule ID = {}", rule.getId());
							log.info("DMN Output = {}", rule.getOutputEntries());
						}
					}
					else {
						log.info("DMN Output = No matching rule found");
					}
				}
				log.info("************************************************************************************************");
			}
		}));
		dmnEngineConfig.buildEngine();
		processEngineConfig.setDmnEngineConfiguration(dmnEngineConfig);
	}
}

There is another way to get the matching rule details - by implementing ProcessEnginePlugin and overriding postProcessEngineBuild() and preInit() methods instead of extending AbstractCamundaConfiguration and overriding postInit() method as above.