PolarSPARC |
Java Rules Engine - Drools :: Part 3
Bhaskar S | 06/12/2021 |
Overview
In Part 1, we provided an overview of Drools and its core components.
In Part 2, we demonstrated two examples - one to isolate rules in different KieBases and the other to showcase a pseudo real-world scenario.
In this part, we will demonstrate two more examples (that would be relevant in a typical enterprise environment) - one to showcase the situation of hierarchical decisions (try option 1 and if it fails, try option 2, and so on) and the other to execute rules in parallel and prove that each KieSession is isolated from the other (in other words use different working memory).
Hands-on with Drools
In the Fifth application, we demonstrate the following business logic - for a given product, we determine the primary supplier. The supplier provides a quote for the product and if the cost is within the 5% tolerance (either up or down), we accept the quote and the processing is done. In case, the tolerance is breached, a secondary supplier is identified and we go through the same process. In this case, if the tolerance is breached, we display an exception message and the processing is done.
Fifth Application
To setup the Java directory structure for the Fifth application, execute the following commands:
$ cd $HOME/java/Drools
$ mkdir -p $HOME/java/Drools/Fifth
$ mkdir -p Fifth/src/main/java Fifth/src/main/resources Fifth/target
$ mkdir -p Fifth/src/main/resources/com/polarsparc/fifth
$ cd $HOME/java/Drools/Fifth
The following is the listing for the Maven project file pom.xml that will be used:
<?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>Fifth</artifactId> <version>1.0</version> <name>Fifth</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 in Part 1 and hence we will not show them here again.
The following is the Drools rules set file called src/main/resources/com/polarsparc/fifth/fifth.drl that handles our business logic:
/* * Name: fifth.drl * Author: Bhaskar S * Date: 06/12/2021 * Blog: https://www.polarsparc.com */ package com.polarsparc.fifth; import com.polarsparc.fifth.model.*; import com.polarsparc.fifth.util.QuoteHelper; import org.slf4j.Logger; import java.util.Collection; import java.util.Comparator import com.polarsparc.fifth.util.QuoteHelper; global org.slf4j.Logger log; rule "Find_Preferred_Supplier" when Product($name: name) then log.info("{}: Product {} first preference Supplier S1", drools.getRule().getName(), $name); insert(new Supplier(SupplierType.PRIMARY, "S1", $name)); end rule "Get_Primary_Supplier_Quote" when Product($name: name) $sup: Supplier(type == SupplierType.PRIMARY, product == $name) $inv: Inventory(product == $name) then Quote quote = QuoteHelper.generateQuote($sup.getName(), $name, $inv.getPrice()); log.info("{}: Quote {} from Supplier {} for Product {}", drools.getRule().getName(), String.format("%.2f", quote.getPrice()), $sup.getName(), $name); insert(quote); end rule "Verify_Primary_Quote" when Product($prod: name) $sup: Supplier(type == SupplierType.PRIMARY, $supp: name, product == $prod) Inventory(product == $prod, $iprc: price) $quo: Quote(supplier == $supp, product == $prod, Math.abs((price - $iprc)/$iprc) > 0.05, $qprc: price) then log.info("{}: Given {}, Quote {} from Supplier {} for Product {} REJECTED !!!", drools.getRule().getName(), $iprc, String.format("%.2f", $qprc), $supp, $prod); delete($sup); delete($quo); insert(new Supplier(SupplierType.SECONDARY, "S2", $prod)); end rule "Accept_Primary_Quote" when $prd: Product($prod: name) $sup: Supplier(type == SupplierType.PRIMARY, $supp: name, product == $prod) $inv: Inventory(product == $prod, $iprc: price) $quo: Quote(supplier == $supp, product == $prod, Math.abs((price - $iprc)/$iprc) <= 0.05, $qprc: price) then log.info("{}: Given {}, Quote {} from Primary Supplier {} for Product {} ACCEPTED !!!", drools.getRule().getName(), $iprc, String.format("%.2f", $qprc), $supp, $prod); insert(new Order($supp, $prod, $qprc)); delete($prd); delete($sup); delete($inv); delete($quo); end rule "Get_Secondary_Supplier_Quote" when Product($name: name) $sup: Supplier(type == SupplierType.SECONDARY, product == $name) $inv: Inventory(product == $name) then Quote quote = QuoteHelper.generateQuote($sup.getName(), $name, $inv.getPrice()); log.info("{}: Quote {} from Secondary Supplier {} for Product {}", drools.getRule().getName(), String.format("%.2f", quote.getPrice()), $sup.getName(), $name); insert(quote); end rule "Verify_Secondary_Quote" when $prd: Product($prod: name) $sup: Supplier(type == SupplierType.SECONDARY, $supp: name, product == $prod) $inv: Inventory(product == $prod, $iprc: price) $quo: Quote(supplier == $supp, product == $prod, Math.abs((price - $iprc)/$iprc) > 0.05, $qprc: price) then log.info("{}: Given {}, Quote {} from Secondary Supplier {} for Product {} REJECTED !!!", drools.getRule().getName(), $iprc, String.format("%.2f", $qprc), $supp, $prod); delete($prd); delete($sup); delete($inv); delete($quo); log.info("{}: Product exception processing !!!", drools.getRule().getName()); end rule "Accept_Secondary_Quote" when $prd: Product($prod: name) $sup: Supplier(type == SupplierType.SECONDARY, $supp: name, product == $prod) $inv: Inventory(product == $prod, $iprc: price) $quo: Quote(supplier == $supp, product == $prod, Math.abs((price - $iprc)/$iprc) <= 0.05, $qprc: price) then log.info("{}: Given {}, Quote {} from Secondary Supplier {} for Product {} ACCEPTED !!!", drools.getRule().getName(), $iprc, String.format("%.2f", $qprc), $supp, $prod); insert(new Order($supp, $prod, $qprc)); delete($prd); delete($sup); delete($inv); delete($quo); end rule "Display_Supplier_Quote" when $ord: Order() then log.info("{}: CONFIRMED - Order Product {} from Supplier {} for Price {}", drools.getRule().getName(), $ord.getProduct(), $ord.getSupplier(), String.format("%.2f", $ord.getPrice())); end
Watch out for the if-then-else logic in the then part; it is an indication that the rule needs to be REFACTORED !!!
The following is the Java POJO that encapsulates the product details:
/* * Name: Product * Author: Bhaskar S * Date: 06/12/2021 * Blog: https://www.polarsparc.com */ package com.polarsparc.fifth.model; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.ToString; @AllArgsConstructor @Getter @ToString public class Product { private final String name; }
The following is the Java enum POJO that encapsulates the supplier type:
/* * Name: SupplierType * Author: Bhaskar S * Date: 06/12/2021 * Blog: https://www.polarsparc.com */ package com.polarsparc.fifth.model; public enum SupplierType { PRIMARY, SECONDARY }
The following is the Java POJO that encapsulates the supplier details:
/* * Name: Supplier * Author: Bhaskar S * Date: 06/12/2021 * Blog: https://www.polarsparc.com */ package com.polarsparc.fifth.model; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.ToString; @AllArgsConstructor @Getter @ToString public class Supplier { private final SupplierType type; private final String name; private final String product; }
The following is the Java POJO that encapsulates the inventory details:
/* * Name: Inventory * Author: Bhaskar S * Date: 06/12/2021 * Blog: https://www.polarsparc.com */ package com.polarsparc.fifth.model; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.Setter; import lombok.ToString; @AllArgsConstructor @Getter @Setter @ToString public class Inventory { private String product; private double price; }
The following is the Java POJO that encapsulates the quote details:
/* * Name: Quote * Author: Bhaskar S * Date: 06/12/2021 * Blog: https://www.polarsparc.com */ package com.polarsparc.fifth.model; import lombok.*; @AllArgsConstructor @Getter @ToString public class Quote { private final String supplier; private final String product; private final double price; }
The following is the Java POJO that encapsulates the order details:
/* * Name: Order * Author: Bhaskar S * Date: 06/12/2021 * Blog: https://www.polarsparc.com */ package com.polarsparc.fifth.model; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.ToString; @AllArgsConstructor @Getter @ToString public class Order { private final String supplier; private final String product; private final double price; }
The following is the Java Config that defines the desired Drools container bean:
/* * Name: FifthDroolsConfig * Author: Bhaskar S * Date: 06/12/2021 * Blog: https://www.polarsparc.com */ package com.polarsparc.fifth.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 FifthDroolsConfig { private final static String FIFTH_DRL = "com/polarsparc/fifth/fifth.drl"; @Bean public KieContainer fifthKieContainer() { KieServices services = KieServices.Factory.get(); KieResources resources = services.getResources(); KieFileSystem fileSystem = services.newKieFileSystem(); fileSystem.write(resources.newClassPathResource(FIFTH_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 following is the main Spring Boot application to test the Drools rules engine:
/* * Name: FifthApplication * Author: Bhaskar S * Date: 06/12/2021 * Blog: https://www.polarsparc.com */ package com.polarsparc.fifth; import com.polarsparc.fifth.model.Inventory; import com.polarsparc.fifth.model.Order; import com.polarsparc.fifth.model.Product; import lombok.extern.slf4j.Slf4j; import org.kie.api.KieBase; 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; import java.util.Optional; @SpringBootApplication @Slf4j public class FifthApplication implements ApplicationRunner { private KieContainer container; @Autowired public void setKieContainer(KieContainer container) { this.container = container; } public static void main(String[] args) { SpringApplication.run(FifthApplication.class, args); } @Override public void run(ApplicationArguments args) { KieBase kb = container.getKieBase(); // Test Case 1 { // Product P1 Product p1 = new Product("P1"); // Current inventory of P1 Inventory in = new Inventory("P1", 18.99); KieSession ks = kb.newKieSession(); ks.setGlobal("log", log); ks.insert(p1); ks.insert(in); ks.fireAllRules(); Optional<?> op = ks.getObjects().stream().filter(obj -> obj instanceof Order).findFirst(); if (op.isPresent()) { Order order = (Order) op.get(); log.info("{}: [1] Supplier: {}, Product: {}, Discount: {}", FifthApplication.class.getName(), order.getSupplier(), order.getProduct(), String.format("%.2f", order.getPrice())); } ks.dispose(); } // Test Case 2 { // Product P2 Product p2 = new Product("P2"); // Current inventory of P1 Inventory in = new Inventory("P2", 21.99); KieSession ks = kb.newKieSession(); ks.setGlobal("log", log); ks.insert(p2); ks.insert(in); ks.fireAllRules(); Optional<?> op = ks.getObjects().stream().filter(obj -> obj instanceof Order).findFirst(); if (op.isPresent()) { Order order = (Order) op.get(); log.info("{}: [2] Supplier: {}, Product: {}, Discount: {}", FifthApplication.class.getName(), order.getSupplier(), order.getProduct(), String.format("%.2f", order.getPrice())); } ks.dispose(); } } }
To execute the code from Listing.29, open a terminal window and run the following commands:
$ cd $HOME/java/Drools/Fifth
$ mvn spring-boot:run
The following could be the typical output:
2021-06-12 15:26:57:233 [main] INFO com.polarsparc.fifth.FifthApplication - Starting FifthApplication using Java 15.0.2 on polarsparc with PID 17988 (/home/polarsparc/java/Drools/Fifth/target/classes started by polarsparc in /home/polarsparc/java/Drools/Fifth) 2021-06-12 15:26:57:233 [main] INFO com.polarsparc.fifth.FifthApplication - No active profile set, falling back to default profiles: default 2021-06-12 15:26:58:472 [main] INFO com.polarsparc.fifth.FifthApplication - Started FifthApplication in 1.514 seconds (JVM running for 1.763) 2021-06-12 15:26:58:474 [main] INFO org.springframework.boot.availability.ApplicationAvailabilityBean - Application availability state LivenessState changed to CORRECT 2021-06-12 15:26:58:474 [main] INFO org.drools.compiler.kie.builder.impl.KieContainerImpl - Start creation of KieBase: defaultKieBase 2021-06-12 15:26:58:522 [main] INFO org.drools.compiler.kie.builder.impl.KieContainerImpl - End creation of KieBase: defaultKieBase 2021-06-12 15:26:58:567 [main] INFO com.polarsparc.fifth.FifthApplication - Find_Preferred_Supplier: Product P1 first preference Supplier S1 2021-06-12 15:26:58:572 [main] INFO com.polarsparc.fifth.FifthApplication - Get_Primary_Supplier_Quote: Quote 17.28 from Supplier S1 for Product P1 2021-06-12 15:26:58:577 [main] INFO com.polarsparc.fifth.FifthApplication - Verify_Primary_Quote: Given 18.99, Quote 17.28 from Supplier S1 for Product P1 REJECTED !!! 2021-06-12 15:26:58:580 [main] INFO com.polarsparc.fifth.FifthApplication - Get_Secondary_Supplier_Quote: Quote 17.66 from Secondary Supplier S2 for Product P1 2021-06-12 15:26:58:582 [main] INFO com.polarsparc.fifth.FifthApplication - Verify_Secondary_Quote: Given 18.99, Quote 17.66 from Secondary Supplier S2 for Product P1 REJECTED !!! 2021-06-12 15:26:58:582 [main] INFO com.polarsparc.fifth.FifthApplication - Verify_Secondary_Quote: Product exception processing !!! 2021-06-12 15:26:58:584 [main] INFO com.polarsparc.fifth.FifthApplication - Find_Preferred_Supplier: Product P2 first preference Supplier S1 2021-06-12 15:26:58:585 [main] INFO com.polarsparc.fifth.FifthApplication - Get_Primary_Supplier_Quote: Quote 21.33 from Supplier S1 for Product P2 2021-06-12 15:26:58:587 [main] INFO com.polarsparc.fifth.FifthApplication - Accept_Primary_Quote: Given 21.99, Quote 21.33 from Primary Supplier S1 for Product P2 ACCEPTED !!! 2021-06-12 15:26:58:588 [main] INFO com.polarsparc.fifth.FifthApplication - Display_Supplier_Quote: CONFIRMED - Order Product P2 from Supplier S1 for Price 21.33 2021-06-12 15:26:58:589 [main] INFO com.polarsparc.fifth.FifthApplication - com.polarsparc.fifth.FifthApplication: [2] Supplier: S1, Product: P2, Discount: 21.33 2021-06-12 15:26:58:590 [main] INFO org.springframework.boot.availability.ApplicationAvailabilityBean - Application availability state ReadinessState changed to ACCEPTING_TRAFFIC [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 4.719 s [INFO] Finished at: 2021-06-12T15:26:58-04:00 [INFO] ------------------------------------------------------------------------
As can be observed from the Output.5 above, both the primary and secondary supplier quotes were rejected for product P1 and resulted in an exception process. For product P2, the quote from the primary supplier was accepted.
Re-running the command once more, the following could be the typical output:
2021-06-12 15:29:12:762 [main] INFO com.polarsparc.fifth.FifthApplication - Starting FifthApplication using Java 15.0.2 on polarsparc with PID 18271 (/home/polarsparc/java/Drools/Fifth/target/classes started by polarsparc in /home/polarsparc/java/Drools/Fifth) 2021-06-12 15:29:12:763 [main] INFO com.polarsparc.fifth.FifthApplication - No active profile set, falling back to default profiles: default 2021-06-12 15:29:14:000 [main] INFO com.polarsparc.fifth.FifthApplication - Started FifthApplication in 1.529 seconds (JVM running for 1.78) 2021-06-12 15:29:14:002 [main] INFO org.springframework.boot.availability.ApplicationAvailabilityBean - Application availability state LivenessState changed to CORRECT 2021-06-12 15:29:14:002 [main] INFO org.drools.compiler.kie.builder.impl.KieContainerImpl - Start creation of KieBase: defaultKieBase 2021-06-12 15:29:14:050 [main] INFO org.drools.compiler.kie.builder.impl.KieContainerImpl - End creation of KieBase: defaultKieBase 2021-06-12 15:29:14:094 [main] INFO com.polarsparc.fifth.FifthApplication - Find_Preferred_Supplier: Product P1 first preference Supplier S1 2021-06-12 15:29:14:099 [main] INFO com.polarsparc.fifth.FifthApplication - Get_Primary_Supplier_Quote: Quote 17.28 from Supplier S1 for Product P1 2021-06-12 15:29:14:107 [main] INFO com.polarsparc.fifth.FifthApplication - Verify_Primary_Quote: Given 18.99, Quote 17.28 from Supplier S1 for Product P1 REJECTED !!! 2021-06-12 15:29:14:113 [main] INFO com.polarsparc.fifth.FifthApplication - Get_Secondary_Supplier_Quote: Quote 18.80 from Secondary Supplier S2 for Product P1 2021-06-12 15:29:14:115 [main] INFO com.polarsparc.fifth.FifthApplication - Accept_Secondary_Quote: Given 18.99, Quote 18.80 from Secondary Supplier S2 for Product P1 ACCEPTED !!! 2021-06-12 15:29:14:117 [main] INFO com.polarsparc.fifth.FifthApplication - Display_Supplier_Quote: CONFIRMED - Order Product P1 from Supplier S2 for Price 18.80 2021-06-12 15:29:14:118 [main] INFO com.polarsparc.fifth.FifthApplication - com.polarsparc.fifth.FifthApplication: [1] Supplier: S2, Product: P1, Discount: 18.80 2021-06-12 15:29:14:119 [main] INFO com.polarsparc.fifth.FifthApplication - Find_Preferred_Supplier: Product P2 first preference Supplier S1 2021-06-12 15:29:14:120 [main] INFO com.polarsparc.fifth.FifthApplication - Get_Primary_Supplier_Quote: Quote 23.31 from Supplier S1 for Product P2 2021-06-12 15:29:14:120 [main] INFO com.polarsparc.fifth.FifthApplication - Verify_Primary_Quote: Given 21.99, Quote 23.31 from Supplier S1 for Product P2 REJECTED !!! 2021-06-12 15:29:14:121 [main] INFO com.polarsparc.fifth.FifthApplication - Get_Secondary_Supplier_Quote: Quote 21.33 from Secondary Supplier S2 for Product P2 2021-06-12 15:29:14:122 [main] INFO com.polarsparc.fifth.FifthApplication - Accept_Secondary_Quote: Given 21.99, Quote 21.33 from Secondary Supplier S2 for Product P2 ACCEPTED !!! 2021-06-12 15:29:14:122 [main] INFO com.polarsparc.fifth.FifthApplication - Display_Supplier_Quote: CONFIRMED - Order Product P2 from Supplier S2 for Price 21.33 2021-06-12 15:29:14:123 [main] INFO com.polarsparc.fifth.FifthApplication - com.polarsparc.fifth.FifthApplication: [2] Supplier: S2, Product: P2, Discount: 21.33 2021-06-12 15:29:14:124 [main] INFO org.springframework.boot.availability.ApplicationAvailabilityBean - Application availability state ReadinessState changed to ACCEPTING_TRAFFIC [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 3.123 s [INFO] Finished at: 2021-06-12T15:29:14-04:00 [INFO] ------------------------------------------------------------------------
As can be observed from the Output.6 above, for both the products P1 and P2, the primary supplier quotes were rejected and the quote from the secondary supplier were accepted.
In the Sixth application, we will demonstrate how to execute a rule set in multiple threads (in parallel) and also prove that each KieSession uses its own working memory.
Sixth Application
To setup the Java directory structure for the Sixth application, execute the following commands:
$ cd $HOME/java/Drools
$ mkdir -p $HOME/java/Drools/Sixth
$ mkdir -p Sixth/src/main/java Sixth/src/main/resources Sixth/target
$ mkdir -p Sixth/src/main/resources/com/polarsparc/sixth
$ cd $HOME/java/Drools/Sixth
The following is the listing for the Maven project file pom.xml that will be used:
<?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>Sixth</artifactId> <version>1.0</version> <name>Sixth</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 in Part 1 and hence we will not show them here again.
The following is the Drools rules set file called src/main/resources/com/polarsparc/sixth/sixth.drl that finds the primary supplier for a given product:
/* * Name: sixth.drl * Author: Bhaskar S * Date: 06/12/2021 * Blog: https://www.polarsparc.com */ package com.polarsparc.sixth; import com.polarsparc.sixth.model.*; global org.slf4j.Logger log; rule "Find_Preferred_Supplier_For_P1" no-loop true when $prd: Product(name == "P1") then log.info("{}: Product P1 {} first preference Supplier S1", drools.getRule().getName(), $prd.getId()); modify($prd){incCounter()} insert(new Supplier(SupplierType.PRIMARY, "S1", "P1")); end rule "Find_Preferred_Supplier_For_P2" salience 5 lock-on-active true when $prd: Product(name == "P2") then log.info("{}: Product P2 {} first preference Supplier S2", drools.getRule().getName(), $prd.getId()); modify($prd){incCounter()} insert(new Supplier(SupplierType.PRIMARY, "S2", "P2")); end rule "Display_Primary_Supplier" when $prd: Product($id: id, $name: name) $sup: Supplier(type == SupplierType.PRIMARY, product == $name) then log.info("{}: Primary Supplier {} for Product {} {}", drools.getRule().getName(), $sup.getName(), $name, $id); delete($sup); delete($prd); end
The rules from Listing.30 above needs some explanation:
The keyword no-loop true prevents self-looping. When a fact (domain data object) is updated via modify(), it triggers the pattern matcher to evaluate the rules from the production memory which causes the rule that triggered the change to be activated again for execution. This causes the infinite looping. The use of no-loop true prevents this self-looping.
The following pictorially depicts the self-looping in Drools rules engine:
How do we prevent looping in a situation where rule A triggers another rule B, which in turn triggers rule A. In those cases, no-loop true will NOT work. In those complex looping situations, the keyword lock-on-active true must be used to prevent looping.
The keyword salience N (where N is a number) allows one to control the priority of a rule. The higher the number, the higher the priority. In this example, we will have the rule for product P2 take a higher priority than that for product P1.
The following is the Java POJO that encapsulates the product details:
/* * Name: Product * Author: Bhaskar S * Date: 06/12/2021 * Blog: https://www.polarsparc.com */ package com.polarsparc.sixth.model; import lombok.*; @RequiredArgsConstructor @Getter @ToString public class Product { private final String id; private final String name; private long counter = 0; public void incCounter() { counter++; } }
The following is the Java enum POJO that encapsulates the supplier type:
/* * Name: SupplierType * Author: Bhaskar S * Date: 06/12/2021 * Blog: https://www.polarsparc.com */ package com.polarsparc.sixth.model; public enum SupplierType { PRIMARY, SECONDARY }
The following is the Java POJO that encapsulates the supplier details:
/* * Name: Supplier * Author: Bhaskar S * Date: 06/12/2021 * Blog: https://www.polarsparc.com */ package com.polarsparc.sixth.model; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.ToString; @AllArgsConstructor @Getter @ToString public class Supplier { private final SupplierType type; private final String name; private final String product; }
The following is the Java Config that defines the desired Drools container bean:
/* * Name: SixthDroolsConfig * Author: Bhaskar S * Date: 06/12/2021 * Blog: https://www.polarsparc.com */ package com.polarsparc.sixth.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; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; @Configuration public class SixthDroolsConfig { private final static String SIXTH_DRL = "com/polarsparc/sixth/sixth.drl"; @Bean public ThreadPoolTaskExecutor sixthThreadPoolTaskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(3); executor.setMaxPoolSize(3); executor.setThreadNamePrefix("SixthThreadPoolTask-"); executor.setWaitForTasksToCompleteOnShutdown(true); executor.initialize(); return executor; } @Bean public KieContainer sixthKieContainer() { KieServices services = KieServices.Factory.get(); KieResources resources = services.getResources(); KieFileSystem fileSystem = services.newKieFileSystem(); fileSystem.write(resources.newClassPathResource(SIXTH_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()); } }
Notice the definition of the org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor bean, which will be used to start multiple threads to execute the Drools rules in parallel.
The following is the main Spring Boot application to test the Drools rules engine:
/* * Name: SixthApplication * Author: Bhaskar S * Date: 06/12/2021 * Blog: https://www.polarsparc.com */ package com.polarsparc.sixth; import com.polarsparc.sixth.model.Product; import lombok.extern.slf4j.Slf4j; import org.kie.api.KieBase; import org.kie.api.runtime.KieContainer; import org.kie.api.runtime.KieSession; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationRunner; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import java.util.concurrent.CountDownLatch; @SpringBootApplication @Slf4j public class SixthApplication implements ApplicationRunner { private ThreadPoolTaskExecutor executor; private KieContainer container; @Autowired public void setThreadPoolTaskExecutor(@Qualifier("sixthThreadPoolTaskExecutor") ThreadPoolTaskExecutor executor) { this.executor = executor; } @Autowired public void setKieContainer(KieContainer container) { this.container = container; } public static void main(String[] args) { SpringApplication.run(SixthApplication.class, args); } @Override public void run(ApplicationArguments args) throws InterruptedException { final int COUNT = 3; KieBase kb = container.getKieBase(); Product gp1 = new Product("GP1", "P1"); Product gp2 = new Product("GP2", "P2"); CountDownLatch latch = new CountDownLatch(COUNT); for (int i = 1; i <= COUNT; i++) { int finalI = i; executor.execute(() -> { Product lp1 = new Product("LP1-" + finalI, "P1"); Product lp2 = new Product("LP2-" + finalI, "P2"); KieSession ks = kb.newKieSession(); ks.setGlobal("log", log); ks.insert(gp1); ks.insert(gp2); ks.insert(lp1); ks.insert(lp2); ks.fireAllRules(); ks.dispose(); log.info("{}: Product (lp1): {}", SixthApplication.class.getName(), lp1); log.info("{}: Product (lp2): {}", SixthApplication.class.getName(), lp2); latch.countDown(); }); } latch.await(); log.info("{}: Product (gp1): {}", SixthApplication.class.getName(), gp1); log.info("{}: Product (gp2): {}", SixthApplication.class.getName(), gp2); executor.shutdown(); } }
To execute the code from Listing.35, open a terminal window and run the following commands:
$ cd $HOME/java/Drools/Sixth
$ mvn spring-boot:run
The following would be the typical output:
2021-06-12 16:42:21:103 [main] INFO com.polarsparc.sixth.SixthApplication - Starting SixthApplication using Java 15.0.2 on polarsparc with PID 20558 (/home/polarsparc/java/Drools/Sixth/target/classes started by polarsparc in /home/polarsparc/java/Drools/Sixth) 2021-06-12 16:42:21:103 [main] INFO com.polarsparc.sixth.SixthApplication - No active profile set, falling back to default profiles: default 2021-06-12 16:42:22:189 [main] INFO com.polarsparc.sixth.SixthApplication - Started SixthApplication in 1.368 seconds (JVM running for 1.627) 2021-06-12 16:42:22:190 [main] INFO org.springframework.boot.availability.ApplicationAvailabilityBean - Application availability state LivenessState changed to CORRECT 2021-06-12 16:42:22:191 [main] INFO org.drools.compiler.kie.builder.impl.KieContainerImpl - Start creation of KieBase: defaultKieBase 2021-06-12 16:42:22:233 [main] INFO org.drools.compiler.kie.builder.impl.KieContainerImpl - End creation of KieBase: defaultKieBase 2021-06-12 16:42:22:295 [SixthThreadPoolTask-3] INFO com.polarsparc.sixth.SixthApplication - Find_Preferred_Supplier_For_P2: Product P2 LP2-3 first preference Supplier S2 2021-06-12 16:42:22:295 [SixthThreadPoolTask-2] INFO com.polarsparc.sixth.SixthApplication - Find_Preferred_Supplier_For_P2: Product P2 LP2-2 first preference Supplier S2 2021-06-12 16:42:22:295 [SixthThreadPoolTask-1] INFO com.polarsparc.sixth.SixthApplication - Find_Preferred_Supplier_For_P2: Product P2 LP2-1 first preference Supplier S2 2021-06-12 16:42:22:297 [SixthThreadPoolTask-3] INFO com.polarsparc.sixth.SixthApplication - Find_Preferred_Supplier_For_P2: Product P2 GP2 first preference Supplier S2 2021-06-12 16:42:22:298 [SixthThreadPoolTask-1] INFO com.polarsparc.sixth.SixthApplication - Find_Preferred_Supplier_For_P2: Product P2 GP2 first preference Supplier S2 2021-06-12 16:42:22:298 [SixthThreadPoolTask-2] INFO com.polarsparc.sixth.SixthApplication - Find_Preferred_Supplier_For_P2: Product P2 GP2 first preference Supplier S2 2021-06-12 16:42:22:299 [SixthThreadPoolTask-3] INFO com.polarsparc.sixth.SixthApplication - Find_Preferred_Supplier_For_P1: Product P1 GP1 first preference Supplier S1 2021-06-12 16:42:22:299 [SixthThreadPoolTask-2] INFO com.polarsparc.sixth.SixthApplication - Find_Preferred_Supplier_For_P1: Product P1 GP1 first preference Supplier S1 2021-06-12 16:42:22:299 [SixthThreadPoolTask-1] INFO com.polarsparc.sixth.SixthApplication - Find_Preferred_Supplier_For_P1: Product P1 GP1 first preference Supplier S1 2021-06-12 16:42:22:299 [SixthThreadPoolTask-3] INFO com.polarsparc.sixth.SixthApplication - Find_Preferred_Supplier_For_P1: Product P1 LP1-3 first preference Supplier S1 2021-06-12 16:42:22:299 [SixthThreadPoolTask-1] INFO com.polarsparc.sixth.SixthApplication - Find_Preferred_Supplier_For_P1: Product P1 LP1-1 first preference Supplier S1 2021-06-12 16:42:22:304 [SixthThreadPoolTask-1] INFO com.polarsparc.sixth.SixthApplication - Display_Primary_Supplier: Primary Supplier S1 for Product P1 GP1 2021-06-12 16:42:22:306 [SixthThreadPoolTask-1] INFO com.polarsparc.sixth.SixthApplication - Display_Primary_Supplier: Primary Supplier S2 for Product P2 GP2 2021-06-12 16:42:22:306 [SixthThreadPoolTask-1] INFO com.polarsparc.sixth.SixthApplication - Display_Primary_Supplier: Primary Supplier S1 for Product P1 LP1-1 2021-06-12 16:42:22:306 [SixthThreadPoolTask-1] INFO com.polarsparc.sixth.SixthApplication - Display_Primary_Supplier: Primary Supplier S2 for Product P2 LP2-1 2021-06-12 16:42:22:311 [SixthThreadPoolTask-1] INFO com.polarsparc.sixth.SixthApplication - com.polarsparc.sixth.SixthApplication: Product (lp1): Product(id=LP1-1, name=P1, counter=1) 2021-06-12 16:42:22:311 [SixthThreadPoolTask-1] INFO com.polarsparc.sixth.SixthApplication - com.polarsparc.sixth.SixthApplication: Product (lp2): Product(id=LP2-1, name=P2, counter=1) 2021-06-12 16:42:22:312 [SixthThreadPoolTask-2] INFO com.polarsparc.sixth.SixthApplication - Find_Preferred_Supplier_For_P1: Product P1 LP1-2 first preference Supplier S1 2021-06-12 16:42:22:312 [SixthThreadPoolTask-2] INFO com.polarsparc.sixth.SixthApplication - Display_Primary_Supplier: Primary Supplier S1 for Product P1 GP1 2021-06-12 16:42:22:313 [SixthThreadPoolTask-3] INFO com.polarsparc.sixth.SixthApplication - Display_Primary_Supplier: Primary Supplier S1 for Product P1 GP1 2021-06-12 16:42:22:313 [SixthThreadPoolTask-2] INFO com.polarsparc.sixth.SixthApplication - Display_Primary_Supplier: Primary Supplier S2 for Product P2 GP2 2021-06-12 16:42:22:313 [SixthThreadPoolTask-2] INFO com.polarsparc.sixth.SixthApplication - Display_Primary_Supplier: Primary Supplier S1 for Product P1 LP1-2 2021-06-12 16:42:22:313 [SixthThreadPoolTask-3] INFO com.polarsparc.sixth.SixthApplication - Display_Primary_Supplier: Primary Supplier S2 for Product P2 GP2 2021-06-12 16:42:22:313 [SixthThreadPoolTask-2] INFO com.polarsparc.sixth.SixthApplication - Display_Primary_Supplier: Primary Supplier S2 for Product P2 LP2-2 2021-06-12 16:42:22:313 [SixthThreadPoolTask-2] INFO com.polarsparc.sixth.SixthApplication - com.polarsparc.sixth.SixthApplication: Product (lp1): Product(id=LP1-2, name=P1, counter=1) 2021-06-12 16:42:22:314 [SixthThreadPoolTask-2] INFO com.polarsparc.sixth.SixthApplication - com.polarsparc.sixth.SixthApplication: Product (lp2): Product(id=LP2-2, name=P2, counter=1) 2021-06-12 16:42:22:314 [SixthThreadPoolTask-3] INFO com.polarsparc.sixth.SixthApplication - Display_Primary_Supplier: Primary Supplier S1 for Product P1 LP1-3 2021-06-12 16:42:22:314 [SixthThreadPoolTask-3] INFO com.polarsparc.sixth.SixthApplication - Display_Primary_Supplier: Primary Supplier S2 for Product P2 LP2-3 2021-06-12 16:42:22:315 [SixthThreadPoolTask-3] INFO com.polarsparc.sixth.SixthApplication - com.polarsparc.sixth.SixthApplication: Product (lp1): Product(id=LP1-3, name=P1, counter=1) 2021-06-12 16:42:22:315 [SixthThreadPoolTask-3] INFO com.polarsparc.sixth.SixthApplication - com.polarsparc.sixth.SixthApplication: Product (lp2): Product(id=LP2-3, name=P2, counter=1) 2021-06-12 16:42:22:315 [main] INFO com.polarsparc.sixth.SixthApplication - com.polarsparc.sixth.SixthApplication: Product (gp1): Product(id=GP1, name=P1, counter=3) 2021-06-12 16:42:22:315 [main] INFO com.polarsparc.sixth.SixthApplication - com.polarsparc.sixth.SixthApplication: Product (gp2): Product(id=GP2, name=P2, counter=3) 2021-06-12 16:42:22:316 [main] INFO org.springframework.boot.availability.ApplicationAvailabilityBean - Application availability state ReadinessState changed to ACCEPTING_TRAFFIC [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 4.527 s [INFO] Finished at: 2021-06-12T16:42:22-04:00 [INFO] ------------------------------------------------------------------------
References