diff --git a/rule-engines/evrete/pom.xml b/rule-engines/evrete/pom.xml new file mode 100644 index 0000000000..819a912c43 --- /dev/null +++ b/rule-engines/evrete/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + com.baeldung.evrete + evrete + 1.0 + evrete + + + 2.1.04 + + + + com.baeldung + rule-engines + 1.0.0-SNAPSHOT + + + + + + org.evrete + evrete-core + ${evrete.version} + + + + org.evrete + evrete-dsl-java + ${evrete.version} + + + \ No newline at end of file diff --git a/rule-engines/evrete/src/main/java/com/baeldung/evrete/introduction/IntroductionAJR.java b/rule-engines/evrete/src/main/java/com/baeldung/evrete/introduction/IntroductionAJR.java new file mode 100644 index 0000000000..287d083311 --- /dev/null +++ b/rule-engines/evrete/src/main/java/com/baeldung/evrete/introduction/IntroductionAJR.java @@ -0,0 +1,47 @@ +package com.baeldung.evrete.introduction; + +import com.baeldung.evrete.introduction.model.Customer; +import com.baeldung.evrete.introduction.model.Invoice; +import org.evrete.KnowledgeService; +import org.evrete.api.Knowledge; + +import java.io.IOException; +import java.net.URL; +import java.util.*; + +public class IntroductionAJR { + public static void main(String[] args) throws IOException { + ClassLoader classLoader = IntroductionAJR.class.getClassLoader(); + KnowledgeService service = new KnowledgeService(); + URL rulesetUrl = classLoader.getResource("rules/SalesRuleset.java"); + Knowledge knowledge = service.newKnowledge( + "JAVA-SOURCE", + rulesetUrl + ); + + List customers = Arrays.asList( + new Customer("Customer A"), + new Customer("Customer B"), + new Customer("Customer C") + ); + + Random random = new Random(); + Collection sessionData = new LinkedList<>(customers); + for (int i = 0; i < 100_000; i++) { + Customer randomCustomer = customers.get(random.nextInt(customers.size())); + Invoice invoice = new Invoice(randomCustomer, 100 * random.nextDouble()); + sessionData.add(invoice); + } + + knowledge + .newStatelessSession() + .insert(sessionData) + .fire(); + + for (Customer c : customers) { + System.out.printf("%s:\t$%,.2f%n", c.getName(), c.getTotal()); + } + + service.shutdown(); + } +} diff --git a/rule-engines/evrete/src/main/java/com/baeldung/evrete/introduction/IntroductionInline.java b/rule-engines/evrete/src/main/java/com/baeldung/evrete/introduction/IntroductionInline.java new file mode 100644 index 0000000000..8867a72d23 --- /dev/null +++ b/rule-engines/evrete/src/main/java/com/baeldung/evrete/introduction/IntroductionInline.java @@ -0,0 +1,58 @@ +package com.baeldung.evrete.introduction; + +import org.evrete.KnowledgeService; +import org.evrete.api.Knowledge; +import com.baeldung.evrete.introduction.model.*; + +import java.util.*; + +public class IntroductionInline { + public static void main(String[] args) { + KnowledgeService service = new KnowledgeService(); + Knowledge knowledge = service + .newKnowledge() + .newRule("Clear total sales") + .forEach("$c", Customer.class) + .execute(ctx -> { + Customer c = ctx.get("$c"); + c.setTotal(0.0); + }) + .newRule("Compute totals") + .forEach( + "$c", Customer.class, + "$i", Invoice.class + ) + .where("$i.customer == $c") + .execute(ctx -> { + Customer c = ctx.get("$c"); + Invoice i = ctx.get("$i"); + c.addToTotal(i.getAmount()); + }); + + + List customers = Arrays.asList( + new Customer("Customer A"), + new Customer("Customer B"), + new Customer("Customer C") + ); + + Random random = new Random(); + Collection sessionData = new LinkedList<>(customers); + for (int i = 0; i < 100_000; i++) { + Customer randomCustomer = customers.get(random.nextInt(customers.size())); + Invoice invoice = new Invoice(randomCustomer, 100 * random.nextDouble()); + sessionData.add(invoice); + } + + knowledge + .newStatelessSession() + .insert(sessionData) + .fire(); + + for (Customer c : customers) { + System.out.printf("%s:\t$%,.2f%n", c.getName(), c.getTotal()); + } + + service.shutdown(); + } +} diff --git a/rule-engines/evrete/src/main/java/com/baeldung/evrete/introduction/model/Customer.java b/rule-engines/evrete/src/main/java/com/baeldung/evrete/introduction/model/Customer.java new file mode 100644 index 0000000000..9a60850d7c --- /dev/null +++ b/rule-engines/evrete/src/main/java/com/baeldung/evrete/introduction/model/Customer.java @@ -0,0 +1,26 @@ +package com.baeldung.evrete.introduction.model; + +public class Customer { + private double total = 0.0; + private final String name; + + public Customer(String name) { + this.name = name; + } + + public void addToTotal(double amount) { + this.total += amount; + } + + public String getName() { + return name; + } + + public double getTotal() { + return total; + } + + public void setTotal(double total) { + this.total = total; + } +} diff --git a/rule-engines/evrete/src/main/java/com/baeldung/evrete/introduction/model/Invoice.java b/rule-engines/evrete/src/main/java/com/baeldung/evrete/introduction/model/Invoice.java new file mode 100644 index 0000000000..3be4fd2908 --- /dev/null +++ b/rule-engines/evrete/src/main/java/com/baeldung/evrete/introduction/model/Invoice.java @@ -0,0 +1,19 @@ +package com.baeldung.evrete.introduction.model; + +public class Invoice { + private final Customer customer; + private final double amount; + + public Invoice(Customer customer, double amount) { + this.customer = customer; + this.amount = amount; + } + + public Customer getCustomer() { + return customer; + } + + public double getAmount() { + return amount; + } +} diff --git a/rule-engines/evrete/src/main/resources/logback.xml b/rule-engines/evrete/src/main/resources/logback.xml new file mode 100644 index 0000000000..7d900d8ea8 --- /dev/null +++ b/rule-engines/evrete/src/main/resources/logback.xml @@ -0,0 +1,13 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + \ No newline at end of file diff --git a/rule-engines/evrete/src/main/resources/rules/SalesRuleset.java b/rule-engines/evrete/src/main/resources/rules/SalesRuleset.java new file mode 100644 index 0000000000..d36aca9c4d --- /dev/null +++ b/rule-engines/evrete/src/main/resources/rules/SalesRuleset.java @@ -0,0 +1,20 @@ +package org.abc.author1; + +import com.baeldung.evrete.introduction.model.Customer; +import com.baeldung.evrete.introduction.model.Invoice; +import org.evrete.dsl.annotation.Rule; +import org.evrete.dsl.annotation.Where; + +public class SalesRuleset { + + @Rule + public void rule1(Customer $c) { + $c.setTotal(0.0); + } + + @Rule + @Where("$i.customer == $c") + public void rule2(Customer $c, Invoice $i) { + $c.addToTotal($i.getAmount()); + } +} \ No newline at end of file diff --git a/rule-engines/evrete/src/test/java/com/baeldung/evrete/introduction/IntroductionAJRUnitTest.java b/rule-engines/evrete/src/test/java/com/baeldung/evrete/introduction/IntroductionAJRUnitTest.java new file mode 100644 index 0000000000..955dbb2fe1 --- /dev/null +++ b/rule-engines/evrete/src/test/java/com/baeldung/evrete/introduction/IntroductionAJRUnitTest.java @@ -0,0 +1,80 @@ +package com.baeldung.evrete.introduction; + +import com.baeldung.evrete.introduction.model.Customer; +import com.baeldung.evrete.introduction.model.Invoice; +import org.evrete.KnowledgeService; +import org.evrete.api.Knowledge; +import org.evrete.api.RuleSession; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.io.IOException; +import java.util.*; + +class IntroductionAJRUnitTest { + private static KnowledgeService service; + + @BeforeAll + static void setUpClass() { + service = new KnowledgeService(); + } + + @AfterAll + static void shutDownClass() { + service.shutdown(); + } + + /** + * This test makes sure that each customer's actual total sales is equal to the amount + * computed by the rule engine + */ + @ParameterizedTest + @ValueSource(strings = {"true", "false"}) + void sessionTotalsTest(String type) throws IOException { + boolean stateful = Boolean.parseBoolean(type); + ClassLoader classLoader = IntroductionAJR.class.getClassLoader(); + KnowledgeService service = new KnowledgeService(); + Knowledge knowledge = service + .newKnowledge( + "JAVA-SOURCE", + classLoader.getResource("rules/SalesRuleset.java") + ); + + + List customers = Arrays.asList( + new Customer("Customer A"), + new Customer("Customer B"), + new Customer("Customer C") + ); + Collection sessionData = new LinkedList<>(customers); + + HashMap actualTotals = new HashMap<>(); + Random random = new Random(); + for (int i = 0; i < 1_000; i++) { + Customer randomCustomer = customers.get(random.nextInt(customers.size())); + Invoice invoice = new Invoice(randomCustomer, random.nextInt(100)); + sessionData.add(invoice); + + Double d = actualTotals.get(randomCustomer); + if(d == null) { + d = 0.0; + } + d = d + invoice.getAmount(); + actualTotals.put(randomCustomer, d); + } + + RuleSession session = stateful ? knowledge.newStatefulSession() : knowledge.newStatelessSession(); + session + .insert(sessionData) + .fire(); + + for(Customer c : customers) { + double d1 = c.getTotal(); + double d2 = actualTotals.get(c); + assert d1 == d2; + } + + } +} \ No newline at end of file diff --git a/rule-engines/evrete/src/test/java/com/baeldung/evrete/introduction/IntroductionInlineUnitTest.java b/rule-engines/evrete/src/test/java/com/baeldung/evrete/introduction/IntroductionInlineUnitTest.java new file mode 100644 index 0000000000..0a6aac83bd --- /dev/null +++ b/rule-engines/evrete/src/test/java/com/baeldung/evrete/introduction/IntroductionInlineUnitTest.java @@ -0,0 +1,90 @@ +package com.baeldung.evrete.introduction; + +import com.baeldung.evrete.introduction.model.Customer; +import com.baeldung.evrete.introduction.model.Invoice; +import org.evrete.KnowledgeService; +import org.evrete.api.Knowledge; +import org.evrete.api.RuleSession; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.*; + +class IntroductionInlineUnitTest { + private static KnowledgeService service; + + @BeforeAll + static void setUpClass() { + service = new KnowledgeService(); + } + + @AfterAll + static void shutDownClass() { + service.shutdown(); + } + + /** + * This test makes sure that each customer's actual total sales is equal to the amount + * computed by the rule engine + */ + @ParameterizedTest + @ValueSource(strings = {"true", "false"}) + void sessionTotalsTest(String type) { + boolean stateful = Boolean.parseBoolean(type); + Knowledge knowledge = service + .newKnowledge() + .newRule("Clear customer's total sales") + .forEach("$c", Customer.class) + .execute(ctx -> { + Customer c = ctx.get("$c"); + c.setTotal(0.0); + }) + .newRule("Compute totals") + .forEach( + "$c", Customer.class, + "$i", Invoice.class + ) + .where("$i.customer == $c") + .execute(ctx -> { + Customer c = ctx.get("$c"); + Invoice i = ctx.get("$i"); + c.addToTotal(i.getAmount()); + }); + + + List customers = Arrays.asList( + new Customer("Customer A"), + new Customer("Customer B"), + new Customer("Customer C") + ); + Collection sessionData = new LinkedList<>(customers); + + HashMap actualTotals = new HashMap<>(); + Random random = new Random(); + for (int i = 0; i < 1_000; i++) { + Customer randomCustomer = customers.get(random.nextInt(customers.size())); + Invoice invoice = new Invoice(randomCustomer, random.nextInt(100)); + sessionData.add(invoice); + + Double d = actualTotals.get(randomCustomer); + if(d == null) { + d = 0.0; + } + d = d + invoice.getAmount(); + actualTotals.put(randomCustomer, d); + } + + RuleSession session = stateful ? knowledge.newStatefulSession() : knowledge.newStatelessSession(); + session + .insert(sessionData) + .fire(); + + for(Customer c : customers) { + double d1 = c.getTotal(); + double d2 = actualTotals.get(c); + assert d1 == d2; + } + } +} \ No newline at end of file diff --git a/rule-engines/pom.xml b/rule-engines/pom.xml index db6b2e47ef..6d8a014128 100644 --- a/rule-engines/pom.xml +++ b/rule-engines/pom.xml @@ -15,6 +15,7 @@ easy-rules + evrete openl-tablets rulebook