PolarSPARC

Java Rules Engine - Drools :: Part 1


Bhaskar S 06/05/2021


Overview

Typical business applications comprise of two aspects - the core application logic and some business rules. The core logic does not change that often and remains static over time, while the business rules tend to change quite frequently over time. As an example, consider a retailer who offers promotions on different products based on different situations. The promotions will have to be coded as business rules as they will have to change often based on different situations. If the business rules are coded as Java class(es), then they will have to change every time the promotions change. This results in a tight coupling between the business rules and the core application logic. To accomodate for flexiblity of promotions, we need to externalize the business rules from the core application logic so they can be changed independent of the core application logic. This is where Rules Engine like Drools comes into play.

Setup

The setup will be on a Ubuntu 20.04 LTS based Linux desktop. Ensure at least Java 11 or above is installed and setup. Also, ensure Apache Maven is installed and setup.

We will use Drools with Spring Boot for all the demonstrations in this article.

To setup the root Java directory structure for the demonstrations in this article, execute the following commands:

$ cd $HOME

$ mkdir -p $HOME/java/Drools

$ cd $HOME/java/Drools


The following is the listing for the parent Maven project file pom.xml that will be located at $HOME/java/Drools:


pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.polarsparc</groupId>
    <artifactId>Drools</artifactId>
    <version>1.0</version>
    <packaging>pom</packaging>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.0</version>
    </parent>

    <properties>
        <java.version>11</java.version>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <drools.version>7.54.0.Final</drools.version>
        <slf4j.version>1.7.30</slf4j.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.kie</groupId>
            <artifactId>kie-api</artifactId>
            <version>${drools.version}</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-core</artifactId>
            <version>${drools.version}</version>
        </dependency>
        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-compiler</artifactId>
            <version>${drools.version}</version>
        </dependency>
        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-decisiontables</artifactId>
            <version>${drools.version}</version>
        </dependency>
        <dependency>
            <groupId>org.drools</groupId>
            <artifactId>drools-templates</artifactId>
            <version>${drools.version}</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>${slf4j.version}</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectors</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Drools Overview

The Drools rules engine uses a declarative style of programming. What this means is that one defines a set of business rules as Condition-Action pairs without defining the sequence of flow that must be followed. As the domain data flows through the rules engine, and if the Condition part matches, the Action part is executed. A subtle point here - the order of the rules execution is not based on the order of the rules defined in the rules engine. In other words, with the declarative approach, one does not specify the sequence of rules execution flow. It is determined by the conditions of the domain data flowing through the rules engine and triggers the execution of the actions.

The following are some of the terminology used to describe the Drools rules engine:

The following is the high-level architecture of Drools rules engine:

Drools Architecture
Drools Architecture

At a high level, when an application starts with Drools, the Rules are loaded into the Production Memory. As the application starts to add Facts into Drools, they get stored in the Working Memory and the Pattern Matcher identifies all the Rules from the Production Memory whose conditions match the various Facts in the Working Memory. The matched Rules are then activated in the Agenda for execution.

Drools Core Components

Every core component in Drools revolves around knowledge - hence we will observe the prefix KIE, which stands for Knowledge Is Everything in all the core components.

The following are some of the core components used in Drools rules engine:

The following is the pictorial representation of the Drools core components:

Drools Core Components
Drools Core Components

Hands-on with Drools

In the First application, we will have the application compute the cost of shipping based on the given weight.

First Application

To setup the Java directory structure for the First application, execute the following commands:

$ cd $HOME/java/Drools

$ mkdir -p $HOME/java/Drools/First

$ mkdir -p First/src/main/java First/src/main/resources First/target

$ mkdir -p First/src/main/resources/com/polarsparc/first

$ cd $HOME/java/Drools/First


The following is the listing for the Maven project file pom.xml that will be used:


pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>com.polarsparc</groupId>
        <artifactId>Drools</artifactId>
        <version>1.0</version>
    </parent>

    <artifactId>First</artifactId>
    <version>1.0</version>
    <name>First</name>
</project>

The following is the listing for the slf4j-simple logger properties file simplelogger.properties located in the directory src/main/resources:


simplelogger.properties
#
### SLF4J Simple Logger properties
#

org.slf4j.simpleLogger.defaultLogLevel=info
org.slf4j.simpleLogger.showDateTime=true
org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss:SSS
org.slf4j.simpleLogger.showThreadName=true

The following is the listing for the Spring Boot application properties file application.properties located in the directory src/main/resources:


application.properties
#
### Spring Boot Application properties
#

spring.main.banner-mode=off

The following is the Drools rules set file (with file extension .drl) called src/main/resources/com/polarsparc/first/first.drl, that computes the shipping cost based on the given weight:


Listing.1
/*
 * Name:   first.drl
 * Author: Bhaskar S
 * Date:   06/05/2021
 * Blog:   https://www.polarsparc.com
 */

package com.polarsparc.first;

import com.polarsparc.first.model.First;
import org.slf4j.Logger;

global org.slf4j.Logger log;

rule "Rule 1"
    when
        $f: First(weight > 0 && weight <= 15)
    then
        log.info("Shipping Price I - the weight {} will be charged $ 12.00", $f.getWeight());
end

rule "Rule 2"
    when
        $f: First(weight > 15 && weight <= 25)
    then
        log.info("Shipping Price II - the weight {} will be charged $ 24.00", $f.getWeight());
end

rule "Rule 3"
    when
        $f: First(weight > 25)
    then
        log.info("Shipping Price III - the weight {} will be charged $ 48.00", $f.getWeight());
end

The general format of each Drools rule is as follows:


Drools Rule Format
package PACKAGE_NAME;

import IMPORT_STATEMENTS;

global G_TYPE G_NAME;

rule "RULE_NAME"
  when
    R_VARIABLE: R_CONDITION
  then
    ACTION_STATEMENTS;
end

The following are the explanations:

The following is the Java POJO that encapsulates the weight:


Listing.2
/*
 * Name:   First
 * Author: Bhaskar S
 * Date:   06/05/2021
 * Blog:   https://www.polarsparc.com
 */

package com.polarsparc.first.model;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.ToString;

@Getter
@AllArgsConstructor
@ToString
public class First {
    private final int weight;
}

The following is the Java Config that defines the desired Drools container bean:


Listing.3
/*
 * Name:   FirstDroolsConfig
 * Author: Bhaskar S
 * Date:   06/05/2021
 * Blog:   https://www.polarsparc.com
 */

package com.polarsparc.first.config;

import org.kie.api.KieServices;
import org.kie.api.builder.*;
import org.kie.api.io.KieResources;
import org.kie.api.runtime.KieContainer;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class FirstDroolsConfig {
    private final static String FIRST_DRL = "com/polarsparc/first/first.drl";

    @Bean
    public KieContainer firstKieContainer() {
        KieServices services = KieServices.Factory.get();
        KieResources resources = services.getResources();

        KieFileSystem fileSystem = services.newKieFileSystem();
        fileSystem.write(resources.newClassPathResource(FIRST_DRL));

        KieBuilder builder = services.newKieBuilder(fileSystem);
        Results results = builder.buildAll().getResults();
        if (results.hasMessages(Message.Level.ERROR)) {
            throw new BeanCreationException("Error building rules: " + results.getMessages());
        }

        KieModule module = builder.getKieModule();

        return services.newKieContainer(module.getReleaseId());
    }
}

The code from Listing.3 above needs some explanation:

@Configuration is a class level annotation and is used to tag a class as the source of bean definitions.

@Bean is a method level annotation to define a bean. By default, the method name is used as the name of the bean.

The interface org.kie.api.io.KieResources is a factory that provides the implementations of the various IO wrappers for the application resources such as the rules, etc. The method newClassPathResource() locates the specified rules file from the project resources directory at src/main/resources via the classpath.

The interface org.kie.api.builder.KieFileSystem is an in-memory file system that is used to programatically compose a KieModule, which under-the-hood encapsulates the core components KieBase and KieSession. The method write() is used to add the specified rules file as a resource.

The interface org.kie.api.builder.KieBuilder is a builder for the resources (such as rules) contained in a KieModule. The method buildAll() builds a default KieBase with the added rules file. The method getResults() returns the results from the build processs.

The following is the main Spring Boot application to test the Drools rules engine:


Listing.4
/*
 * Name:   FirstApplication
 * Author: Bhaskar S
 * Date:   06/05/2021
 * Blog:   https://www.polarsparc.com
 */

package com.polarsparc.first;

import com.polarsparc.first.model.First;
import lombok.extern.slf4j.Slf4j;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@Slf4j
public class FirstApplication implements ApplicationRunner {
    private KieContainer container;

    @Autowired
    public void setKieContainer(KieContainer container) {
        this.container = container;
    }

    public static void main(String[] args) {
        SpringApplication.run(FirstApplication.class, args);
    }

    @Override
    public void run(ApplicationArguments args) {
        log.info("ReleaseId: {}", container.getReleaseId());

        // Test 1
        KieSession ks1 = container.newKieSession();
        ks1.setGlobal("log", log);
        First f1 = new First(12);
        ks1.insert(f1);
        ks1.fireAllRules();
        ks1.dispose();
        log.info("[1] First: f1 = {}", f1);

        // Test 2
        KieSession ks2 = container.newKieSession();
        ks2.setGlobal("log", log);
        First f2 = new First(23);
        ks2.insert(f2);
        ks2.fireAllRules();
        ks2.dispose();
        log.info("[2] First: f2 = {}", f2);

        // Test 3
        KieSession ks3 = container.newKieSession();
        ks3.setGlobal("log", log);
        First f3 = new First(36);
        ks3.insert(f3);
        ks3.fireAllRules();
        ks3.dispose();
        log.info("[3] First: f3 = {}", f3);

        log.info("Done !!!");
    }
}

The code from Listing.4 above needs some explanation:

@SpringBootApplication is a class level annotation that triggers automatic scan of all the beans and auto configurations them.

@Slf4j is a class level annotation that causes Lombok to generate an instance of org.slf4j.Logger as a variable log.

The interface org.springframework.boot.ApplicationRunner indicates that a bean should run as a standalone Java application.

The method setGlobal() on a KieSession allows one to pass an external object to the rules engine. In this example, we are passing a reference to the Logger.

The method insert() allows one to add a fact to the rules engine. In other words, it allows one to add an application object to the working memory of the rules engine. This in-turn triggers the evaluation of the rules and all the matches rules are activated in the agenda of the rules engine.

The method fireAllRules() triggers the execution of all the activated rules in the agenda of the rules engine.

The method dispose() is *VERY* important and needs to be called in the end. It allows one to free-up all the KieSession resources and allows for it to be garbage collected.

To execute the code from Listing.4, open a terminal window and run the following commands:

$ cd $HOME/java/Drools/First

$ mvn spring-boot:run

The following would be the typical output:

Output.1

2021-06-05 13:31:10:339 [main] INFO com.polarsparc.first.FirstApplication - Starting FirstApplication using Java 15.0.2 on polarsparc with PID 16908 (/home/polarsparc/java/Drools/First/target/classes started by polarsparc in /home/polarsparc/java/Drools/First)
2021-06-05 13:31:10:340 [main] INFO com.polarsparc.first.FirstApplication - No active profile set, falling back to default profiles: default
2021-06-05 13:31:11:422 [main] INFO com.polarsparc.first.FirstApplication - Started FirstApplication in 1.358 seconds (JVM running for 1.611)
2021-06-05 13:31:11:423 [main] INFO org.springframework.boot.availability.ApplicationAvailabilityBean - Application availability state LivenessState changed to CORRECT
2021-06-05 13:31:11:424 [main] INFO com.polarsparc.first.FirstApplication - ReleaseId: org.default:artifact:1.0.0
2021-06-05 13:31:11:424 [main] INFO com.polarsparc.first.FirstApplication - KieBases: [defaultKieBase]
2021-06-05 13:31:11:424 [main] INFO com.polarsparc.first.FirstApplication - KieBase: defaultKieBase, KieSessions: [defaultStatelessKieSession, defaultKieSession]
2021-06-05 13:31:11:424 [main] INFO org.drools.compiler.kie.builder.impl.KieContainerImpl - Start creation of KieBase: defaultKieBase
2021-06-05 13:31:11:466 [main] INFO org.drools.compiler.kie.builder.impl.KieContainerImpl - End creation of KieBase: defaultKieBase
2021-06-05 13:31:11:511 [main] INFO com.polarsparc.first.FirstApplication - Shipping Price I - the weight 12 will be charged $ 12.00
2021-06-05 13:31:11:515 [main] INFO com.polarsparc.first.FirstApplication - [1] First: f1 = First(weight=12)
2021-06-05 13:31:11:518 [main] INFO com.polarsparc.first.FirstApplication - Shipping Price II - the weight 23 will be charged $ 24.00
2021-06-05 13:31:11:518 [main] INFO com.polarsparc.first.FirstApplication - [2] First: f2 = First(weight=23)
2021-06-05 13:31:11:521 [main] INFO com.polarsparc.first.FirstApplication - Shipping Price III - the weight 36 will be charged $ 48.00
2021-06-05 13:31:11:521 [main] INFO com.polarsparc.first.FirstApplication - [3] First: f3 = First(weight=36)
2021-06-05 13:31:11:522 [main] INFO com.polarsparc.first.FirstApplication - Done !!!
2021-06-05 13:31:11:523 [main] INFO org.springframework.boot.availability.ApplicationAvailabilityBean - Application availability state ReadinessState changed to ACCEPTING_TRAFFIC
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  2.971 s
[INFO] Finished at: 2021-06-05T13:31:11-04:00
[INFO] ------------------------------------------------------------------------

In the Second application, we will have the application display the supplier name, the product, and the product cost (per the supplier).

Second Application

To setup the Java directory structure for the Second application, execute the following commands:

$ cd $HOME/java/Drools

$ mkdir -p $HOME/java/Drools/Second

$ mkdir -p Second/src/main/java Second/src/main/resources Second/target

$ mkdir -p Second/src/main/resources/com/polarsparc/second

$ cd $HOME/java/Drools/Second


The following is the listing for the Maven project file pom.xml that will be used:


pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>com.polarsparc</groupId>
        <artifactId>Drools</artifactId>
        <version>1.0</version>
    </parent>

    <artifactId>Second</artifactId>
    <version>1.0</version>
    <name>Second</name>
</project>

The contents of the simplelogger.properties and application.properties located in the directory src/main/resources will be identical to the one from the First application listed above and hence we will not show them here again.

The following is the Drools rules set file called src/main/resources/com/polarsparc/second/second.drl, that display the supplier name, the product, and the product cost (per the supplier):


Listing.5
/*
 * Name:   second.drl
 * Author: Bhaskar S
 * Date:   06/05/2021
 * Blog:   https://www.polarsparc.com
 */

package com.polarsparc.second;

import com.polarsparc.second.model.Second;
import org.slf4j.Logger;

global org.slf4j.Logger log;

rule "Rule One"
    when
        $s: Second()
    then
        log.info("Second - supplier: {}, product: {}, price: {}", $s.getSupplier(), $s.getProduct(), $s.getPrice());
end

The following is the Java POJO that encapsulates the supplier-product details:


Listing.6
/*
 * Name:   Second
 * Author: Bhaskar S
 * Date:   06/05/2021
 * Blog:   https://www.polarsparc.com
 */

package com.polarsparc.second.model;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@AllArgsConstructor
@ToString
public class Second {
    private String supplier;
    private String product;
    private double price;
}

The following is the Java Config that defines the desired Drools container bean:


Listing.7
/*
 * Name:   SecondDroolsConfig
 * Author: Bhaskar S
 * Date:   06/05/2021
 * Blog:   https://www.polarsparc.com
 */

package com.polarsparc.second.config;

import org.kie.api.KieServices;
import org.kie.api.builder.*;
import org.kie.api.builder.model.KieBaseModel;
import org.kie.api.builder.model.KieModuleModel;
import org.kie.api.builder.model.KieSessionModel;
import org.kie.api.conf.EqualityBehaviorOption;
import org.kie.api.conf.EventProcessingOption;
import org.kie.api.io.KieResources;
import org.kie.api.io.Resource;
import org.kie.api.io.ResourceType;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.conf.ClockTypeOption;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class SecondDroolsConfig {
    private final static String SECOND_DRL = "src/main/resources/com/polarsparc/second/second.drl";

    @Bean
    public KieContainer secondKieContainer() {
        KieServices services = KieServices.Factory.get();

        ReleaseId releaseId = services.newReleaseId("com.polarsparc.second", "second", "1.0");

        KieFileSystem fileSystem = services.newKieFileSystem();

        KieResources resources = services.getResources();
        Resource drlResource = resources.newFileSystemResource(SECOND_DRL)
                .setResourceType(ResourceType.DRL);

        KieModuleModel model = services.newKieModuleModel();
        KieBaseModel base = model.newKieBaseModel("second-base")
                .setDefault(true)
                .addPackage("com.polarsparc.second")
                .setEqualsBehavior(EqualityBehaviorOption.EQUALITY)
                .setEventProcessingMode(EventProcessingOption.CLOUD);
        base.newKieSessionModel("second-session")
                .setDefault(true)
                .setType(KieSessionModel.KieSessionType.STATEFUL)
                .setClockType(ClockTypeOption.REALTIME);

        fileSystem.generateAndWritePomXML(releaseId)
                .write(drlResource)
                .writeKModuleXML(model.toXML());

        KieBuilder builder = services.newKieBuilder(fileSystem);
        Results results = builder.buildAll().getResults();
        if (results.hasMessages(Message.Level.ERROR)) {
            throw new BeanCreationException("Error building rules: " + results.getMessages());
        }

        KieModule module = builder.getKieModule();

        return services.newKieContainer(module.getReleaseId());
    }
}

The code from Listing.7 above needs some explanation:

The interface org.kie.api.builder.ReleaseId is the Maven like 3-pair tuple resource identifier. As indicated earlier, the KieModule has a Maven like structure. This implies it has an associated 3-pair tuple - the groupID, the artifactId, and a version.

The interface org.kie.api.io.Resource is a resource wrapper for the application resources such as the rules, etc.

The interface org.kie.api.builder.model.KieModuleModel is a factory for programatically creating a KieModule.

The interface org.kie.api.builder.model.KieBaseModel is a factory for programatically creating a KieBase. The method setDefault() allows one to set the created KieBase as the default instance. The method addPackage() isolates the created KieBase to the specified rules package (which is com.polarsparc.second for this example).

The enum EqualityBehaviorOption has two values - EQUALITY and IDENTITY. It defines how Drools will behave when a new fact (domain object) is inserted into the working memory. With EQUALITY, when a fact object is inserted, a new reference to the fact object is created and added to the working memory only if the fact object is not equal to a similar fact object. On the other hand, with IDENTITY, a new reference to the fact object is created and added to the working memory only if the same fact object is not already present.

The enum EventProcessingOption has two values - CLOUD and STREAM. In the CLOUD mode, every fact object inserted into the working memory is treated without any regard for time (as is individually). On the other hand, in the STREAM mode, Drools will try to correlate the fact objects inserted into the working memory. In other words, this mode is suitable for complex events processing.

The method newKieSessionModel() returns an object that is a factory for programatically creating a KieSession.

The enum KieSessionModel.KieSessionType has two values - STATEFUL and STATELESS. In the STATEFUL mode, the KieSession is long-lived and allows for iterative changes to the state of the fact objects. In other words, the rules engine continuously matches and processes rules as the state of the fact objects change. On the other hand, the STATELESS mode, is for simple one-off execution of the rules and any state changes to the fact objects does not trigger additional rules processing.

The option ClockTypeOption has two values - PSEUDO and REALTIME. In the PSEUDO mode, the clock is controlled by the application, while in the REALTIME mode, the clock is determined by the system clock.

The following is the main Spring Boot application to test the Drools rules engine:


Listing.8
/*
 * Name:   SecondApplication
 * Author: Bhaskar S
 * Date:   06/05/2021
 * Blog:   https://www.polarsparc.com
 */

package com.polarsparc.second;

import com.polarsparc.second.model.Second;
import lombok.extern.slf4j.Slf4j;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@Slf4j
public class SecondApplication implements ApplicationRunner {
    private KieContainer container;

    @Autowired
    public void setKieContainer(KieContainer container) {
        this.container = container;
    }

    public static void main(String[] args) {
        SpringApplication.run(SecondApplication.class, args);
    }

    @Override
    public void run(ApplicationArguments args) {
        log.info("ReleaseId: {}", container.getReleaseId());
        log.info("KieBases: {}", container.getKieBaseNames());
        container.getKieBaseNames().forEach(name ->
                log.info("KieBase: {}, KieSessions: {}", name, container.getKieSessionNamesInKieBase(name)));

        KieSession ks = container.newKieSession();
        ks.setGlobal("log", log);
        Second s1 = new Second("S1", "P1", 25.49);
        Second s2 = new Second("S2", "P1", 24.99);
        Second s3 = new Second("S3", "P2", 18.78);
        ks.insert(s1);
        ks.insert(s2);
        ks.insert(s3);
        ks.fireAllRules();
        ks.dispose();

        log.info("Done !!!");
    }
}

To execute the code from Listing.8, open a terminal window and run the following commands:

$ cd $HOME/java/Drools/Second

$ mvn spring-boot:run

The following would be the typical output:

Output.2

2021-06-05 14:54:34:303 [main] INFO com.polarsparc.second.SecondApplication - Starting SecondApplication using Java 15.0.2 on polarsparc with PID 21471 (/home/polarsparc/java/Drools/Second/target/classes started by polarsparc in /home/polarsparc/java/Drools/Second)
2021-06-05 14:54:34:304 [main] INFO com.polarsparc.second.SecondApplication - No active profile set, falling back to default profiles: default
2021-06-05 14:54:35:402 [main] INFO com.polarsparc.second.SecondApplication - Started SecondApplication in 1.392 seconds (JVM running for 1.636)
2021-06-05 14:54:35:403 [main] INFO org.springframework.boot.availability.ApplicationAvailabilityBean - Application availability state LivenessState changed to CORRECT
2021-06-05 14:54:35:404 [main] INFO com.polarsparc.second.SecondApplication - ReleaseId: com.polarsparc.second:second:1.0
2021-06-05 14:54:35:405 [main] INFO com.polarsparc.second.SecondApplication - KieBases: [second-base]
2021-06-05 14:54:35:405 [main] INFO com.polarsparc.second.SecondApplication - KieBase: second-base, KieSessions: [second-session]
2021-06-05 14:54:35:405 [main] INFO org.drools.compiler.kie.builder.impl.KieContainerImpl - Start creation of KieBase: second-base
2021-06-05 14:54:35:442 [main] INFO org.drools.compiler.kie.builder.impl.KieContainerImpl - End creation of KieBase: second-base
2021-06-05 14:54:35:495 [main] INFO com.polarsparc.second.SecondApplication - Second - supplier: S1, product: P1, price: 25.49
2021-06-05 14:54:35:495 [main] INFO com.polarsparc.second.SecondApplication - Second - supplier: S2, product: P1, price: 24.99
2021-06-05 14:54:35:495 [main] INFO com.polarsparc.second.SecondApplication - Second - supplier: S3, product: P2, price: 18.78
2021-06-05 14:54:35:496 [main] INFO com.polarsparc.second.SecondApplication - Done !!!
2021-06-05 14:54:35:497 [main] INFO org.springframework.boot.availability.ApplicationAvailabilityBean - Application availability state ReadinessState changed to ACCEPTING_TRAFFIC
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  4.478 s
[INFO] Finished at: 2021-06-05T14:54:35-04:00
[INFO] ------------------------------------------------------------------------

References

Drools Documentation



© PolarSPARC