Drools support in Spring Boot 2

Introduction

You will see an example on drools support in Spring Boot 2. You might have seen in past example on how to integrate drools in Spring application. I had used Spring Boot version 1.5.9 but here it will support Spring Boot 2.1.5 to 2.4.3 versions. I will not explain about drools in this post and if you want to know on drools then you can check my previous example how to integrate drools in Spring application.

Prerequisites

Java at least 1.8, Gradle 5.4.1 – 6.7.1, Drools 7.22.0 – 7.50.0

Project Setup

Create a gradle or maven based project in your favorite IDE or tool.

Update the content of the default generated build.gradle script to include the required dependencies.

buildscript {
	ext {
		springBootVersion = '2.1.5.RELEASE' to 2.4.3
	}
	
    repositories {
    	maven {
    		url 'https://plugins.gradle.org/m2/'
    	}
    }
    
    dependencies {
    	classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

plugins {
    id 'java-library'
    id 'org.springframework.boot' version "${springBootVersion}"
}

sourceCompatibility = 12
targetCompatibility = 12

repositories {
    mavenCentral()
    jcenter()
}

dependencies {
	implementation("org.springframework.boot:spring-boot-starter-web:${springBootVersion}")
	implementation("org.kie:kie-spring:7.22.0.Final") to 7.50.0.Final
	implementation("org.drools:drools-compiler:7.22.0.Final") to 7.51.0.t20210303
}

Drools Configuration

I want to use Java based configurations so create below java class to create all the required configurations. I will put all rule files under src/main/resources/rules directory.

I have defined few beans for our Spring and Drools integration.

I have created KieFileSystem bean that finds all the rule files put under the classpath file system. These rule files define the rules, which would be applied on the object fields for validation.

I have defined KieContainer bean which is used to create different components, such as, KieSession that is required to create before fire rules on the drools file.

package com.roytuts.spring.drools.config;
import java.io.IOException;
import org.kie.api.KieServices;
import org.kie.api.builder.KieBuilder;
import org.kie.api.builder.KieFileSystem;
import org.kie.api.builder.KieModule;
import org.kie.api.builder.KieRepository;
import org.kie.api.builder.ReleaseId;
import org.kie.api.runtime.KieContainer;
import org.kie.internal.io.ResourceFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
@Configuration
public class SpringDroolsConfig {
	private static final String RULES_PATH = "rules/";
	@Bean
	public KieFileSystem kieFileSystem() throws IOException {
		KieFileSystem kieFileSystem = getKieServices().newKieFileSystem();
		for (Resource file : getRuleFiles()) {
			kieFileSystem.write(ResourceFactory.newClassPathResource(RULES_PATH + file.getFilename(), "UTF-8"));
		}
		return kieFileSystem;
	}
	private Resource[] getRuleFiles() throws IOException {
		ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
		return resourcePatternResolver.getResources("classpath*:" + RULES_PATH + "**/*.*");
	}
	@Bean
	public KieContainer kieContainer() throws IOException {
		final KieRepository kieRepository = getKieServices().getRepository();
		kieRepository.addKieModule(new KieModule() {
			public ReleaseId getReleaseId() {
				return kieRepository.getDefaultReleaseId();
			}
		});
		KieBuilder kieBuilder = getKieServices().newKieBuilder(kieFileSystem());
		kieBuilder.buildAll();
		return getKieServices().newKieContainer(kieRepository.getDefaultReleaseId());
	}
	private KieServices getKieServices() {
		return KieServices.Factory.get();
	}
}

Custom Exception

I will create custom exception class to throw exception when validation fails. I will create NumberFormatException class to throw number format exception for non-integer value.

package com.roytuts.spring.drools.exception;
public class NumberFormatException extends RuntimeException {
	private static final long serialVersionUID = 1L;
	public NumberFormatException(String msg) {
		super(msg);
	}
}

VO Class

I will create VO (Value Object) class to use on REST controller.

I will apply validation on code using drools.

package com.roytuts.spring.drools.vo;
public class AreaPin {
	private int code;
	public AreaPin(int code) {
		this.code = code;
	}
	public int getCode() {
		return code;
	}
}

Rule Validation File

Create a file called PinCodeValidation.drl to perform validation on pin code. I will check whether the pin code is 6 digits integer value or not.

The rule validation file must be put into src/main/resources/rules folder as we mentioned earlier. You may put into other location and accordingly you have to change the configuration.

You find AreaPin in when clause is the object and field code is accessed directly by its property name in the AreaPin class.

package com.roytuts.spring.drools.rules
import com.roytuts.spring.drools.vo.AreaPin;
import com.roytuts.spring.drools.exception.NumberFormatException;
rule "PinCodeValidation"
	when
		AreaPin(code != 0 && code not matches "^[0-9]{6}$")
	then
		throw new NumberFormatException("Invalid Area Pin Code. Must be a valid 6 digits number.");
end

REST Controller

The Spring REST controller that handles end users requests/responses is given below.

I want to search the area name for a given pin code using REST service. So before I search into the Map whether the area name will be returned or not, I perform validation on pin code and rule is fired using drools.

Notice how I autowire KieContainer and create KieSession and fire the rule on AreaPin object.

If pin code validation fails then you will see exception in the console as well as on the web page or REST client.

If pin code validation passes then area name will be returned if found or Area not found message will be returned as a default value from the Map.

package com.roytuts.spring.drools.rest.controller;
import java.util.HashMap;
import java.util.Map;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import com.roytuts.spring.drools.vo.AreaPin;
@RestController
public class AreaRestController {
	@Autowired
	private KieContainer kieContainer;
	@GetMapping("/area/name/{pinCode}")
	public ResponseEntity<String> getAreaByPinCode(@PathVariable int pinCode) {
		KieSession kieSession = kieContainer.newKieSession();
		kieSession.insert(new AreaPin(pinCode)); // which object to validate
		kieSession.fireAllRules(); // fire all rules defined into drool file (drl)
		kieSession.dispose();
		return new ResponseEntity<String>(getAreaByCode(pinCode), HttpStatus.OK);
	}
	private String getAreaByCode(int pin) {
		final Map<Integer, String> areaMap = new HashMap<>();
		areaMap.put(700001, "BBD Bag");
		areaMap.put(700010, "Beliaghata");
		areaMap.put(700105, "Nabapally");
		areaMap.put(700098, "Sukanta Nagar");
		return areaMap.getOrDefault(pin, "Area not found");
	}
}

Creating Main Class

A class with main method and @SpringBootApplication is enough to start the Spring Boot application.

package com.roytuts.spring.drools;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication(scanBasePackages = "com.roytuts.spring.drools")
public class SpringBoot2Drools {
	public static void main(String[] args) {
		SpringApplication.run(SpringBoot2Drools.class, args);
	}
}

Testing the Application

Run the above main class to deploy into Tomcat server and start the application.

Once the application deployed, it starts on port number 8080 as it is a default port of Tomcat. You may also override this port number in application.properties or application.yml file using key server.port.

If you hit the URL http://localhost:8080/area/name/700098, you will get below output on the browser:

drools support in Spring Boot 2

If you hit below URL, you will see below exception on the page.

drools support in spring boot 2

Console error is given below:

[Request processing failed; nested exception is Exception executing consequence for rule "PinCodeValidation" in com.roytuts.spring.drools.rules: com.roytuts.spring.drools.exception.NumberFormatException: Invalid Area Pin Code. Must be a valid 6 digits number.]

That’s all about how to use drools in Spring framework.

Source Code

Download

Leave a Reply

Your email address will not be published. Required fields are marked *