diff --git a/rule-engines-modules/pom.xml b/rule-engines-modules/pom.xml index dcd97c5f275a..bc163f8034a6 100644 --- a/rule-engines-modules/pom.xml +++ b/rule-engines-modules/pom.xml @@ -18,6 +18,7 @@ evrete openl-tablets rulebook + simple-rule-engine diff --git a/rule-engines-modules/simple-rule-engine/pom.xml b/rule-engines-modules/simple-rule-engine/pom.xml new file mode 100644 index 000000000000..4911464dfe7a --- /dev/null +++ b/rule-engines-modules/simple-rule-engine/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + com.baeldung.simpleruleengine + simple-rule-engine + 1.0 + simple-rule-engine + + + com.baeldung + rule-engines-modules + 1.0.0-SNAPSHOT + + + + + org.projectlombok + lombok + ${lombok.version} + provided + + + org.springframework + spring-expression + ${spring-expression.version} + + + + + 7.0.0-M7 + + + \ No newline at end of file diff --git a/rule-engines-modules/simple-rule-engine/src/main/java/com/baeldung/ruleengine/FirstOrderHighValueSpecialDiscountRule.java b/rule-engines-modules/simple-rule-engine/src/main/java/com/baeldung/ruleengine/FirstOrderHighValueSpecialDiscountRule.java new file mode 100644 index 000000000000..514cc74ad4cd --- /dev/null +++ b/rule-engines-modules/simple-rule-engine/src/main/java/com/baeldung/ruleengine/FirstOrderHighValueSpecialDiscountRule.java @@ -0,0 +1,17 @@ +package com.baeldung.ruleengine; + +import com.baeldung.ruleengine.model.Order; + +public class FirstOrderHighValueSpecialDiscountRule implements IRule { + + @Override + public boolean evaluate(Order order) { + return order.getCustomer() + .isFirstOrder() && order.getAmount() > 500; + } + + @Override + public String description() { + return "First Order Special Discount Rule: First Time customer with high value order"; + } +} diff --git a/rule-engines-modules/simple-rule-engine/src/main/java/com/baeldung/ruleengine/IRule.java b/rule-engines-modules/simple-rule-engine/src/main/java/com/baeldung/ruleengine/IRule.java new file mode 100644 index 000000000000..1f79f49fdd4b --- /dev/null +++ b/rule-engines-modules/simple-rule-engine/src/main/java/com/baeldung/ruleengine/IRule.java @@ -0,0 +1,8 @@ +package com.baeldung.ruleengine; + +import com.baeldung.ruleengine.model.Order; + +public interface IRule { + boolean evaluate(Order order); + String description(); +} diff --git a/rule-engines-modules/simple-rule-engine/src/main/java/com/baeldung/ruleengine/LoyaltyDiscountRule.java b/rule-engines-modules/simple-rule-engine/src/main/java/com/baeldung/ruleengine/LoyaltyDiscountRule.java new file mode 100644 index 000000000000..86bd39fab28a --- /dev/null +++ b/rule-engines-modules/simple-rule-engine/src/main/java/com/baeldung/ruleengine/LoyaltyDiscountRule.java @@ -0,0 +1,16 @@ +package com.baeldung.ruleengine; + +import com.baeldung.ruleengine.model.Order; + +public class LoyaltyDiscountRule implements IRule{ + + @Override + public boolean evaluate(Order order) { + return order.getCustomer().getLoyaltyPoints() > 500; + } + + @Override + public String description() { + return "Loyalty Discount Rule: Customer has more than 500 points"; + } +} diff --git a/rule-engines-modules/simple-rule-engine/src/main/java/com/baeldung/ruleengine/RuleEngine.java b/rule-engines-modules/simple-rule-engine/src/main/java/com/baeldung/ruleengine/RuleEngine.java new file mode 100644 index 000000000000..cc199cfa85da --- /dev/null +++ b/rule-engines-modules/simple-rule-engine/src/main/java/com/baeldung/ruleengine/RuleEngine.java @@ -0,0 +1,21 @@ +package com.baeldung.ruleengine; + +import java.util.List; +import java.util.stream.Collectors; + +import com.baeldung.ruleengine.model.Order; + +public class RuleEngine { + private final List rules; + + public RuleEngine(List rules) { + this.rules = rules; + } + + public List evaluate(Order order) { + return rules.stream() + .filter(rule -> rule.evaluate(order)) + .map(IRule::description) + .collect(Collectors.toList()); + } +} diff --git a/rule-engines-modules/simple-rule-engine/src/main/java/com/baeldung/ruleengine/SpelRule.java b/rule-engines-modules/simple-rule-engine/src/main/java/com/baeldung/ruleengine/SpelRule.java new file mode 100644 index 000000000000..9a386bf9f2f4 --- /dev/null +++ b/rule-engines-modules/simple-rule-engine/src/main/java/com/baeldung/ruleengine/SpelRule.java @@ -0,0 +1,31 @@ +package com.baeldung.ruleengine; + +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.standard.SpelExpressionParser; +import org.springframework.expression.spel.support.StandardEvaluationContext; + +import com.baeldung.ruleengine.model.Order; + + +public class SpelRule { + + private final String expression; + private final String description; + + public SpelRule(String expression, String description) { + this.expression = expression; + this.description = description; + } + + public boolean evaluate(Order order) { + ExpressionParser parser = new SpelExpressionParser(); + StandardEvaluationContext context = new StandardEvaluationContext(order); + context.setVariable("order", order); + return parser.parseExpression(expression) + .getValue(context, Boolean.class); + } + + public String getDescription() { + return description; + } +} diff --git a/rule-engines-modules/simple-rule-engine/src/main/java/com/baeldung/ruleengine/model/Customer.java b/rule-engines-modules/simple-rule-engine/src/main/java/com/baeldung/ruleengine/model/Customer.java new file mode 100644 index 000000000000..8264a6afaf4e --- /dev/null +++ b/rule-engines-modules/simple-rule-engine/src/main/java/com/baeldung/ruleengine/model/Customer.java @@ -0,0 +1,26 @@ +package com.baeldung.ruleengine.model; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.FieldDefaults; + +@Setter +@Getter +@FieldDefaults(level = AccessLevel.PRIVATE) +public class Customer { + + String name; + int loyaltyPoints; + boolean firstOrder; + + public Customer() { + } + + public Customer(String name, int loyaltyPoints, boolean firstOrder) { + this.name = name; + this.loyaltyPoints = loyaltyPoints; + this.firstOrder = firstOrder; + } + +} diff --git a/rule-engines-modules/simple-rule-engine/src/main/java/com/baeldung/ruleengine/model/Order.java b/rule-engines-modules/simple-rule-engine/src/main/java/com/baeldung/ruleengine/model/Order.java new file mode 100644 index 000000000000..4f9d0c7979f7 --- /dev/null +++ b/rule-engines-modules/simple-rule-engine/src/main/java/com/baeldung/ruleengine/model/Order.java @@ -0,0 +1,25 @@ +package com.baeldung.ruleengine.model; + +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.FieldDefaults; + +@Getter +@Setter +public class Order { + + private Double amount; + private Customer customer; + + public Order() { + } + + public Order(Double amount, Customer customer) { + this.amount = amount; + this.customer = customer; + } +} diff --git a/rule-engines-modules/simple-rule-engine/src/main/resources/logback.xml b/rule-engines-modules/simple-rule-engine/src/main/resources/logback.xml new file mode 100644 index 000000000000..7d900d8ea884 --- /dev/null +++ b/rule-engines-modules/simple-rule-engine/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-modules/simple-rule-engine/src/test/java/com/baeldung/ruleengine/RuleEngineUnitTest.java b/rule-engines-modules/simple-rule-engine/src/test/java/com/baeldung/ruleengine/RuleEngineUnitTest.java new file mode 100644 index 000000000000..c6f792e01776 --- /dev/null +++ b/rule-engines-modules/simple-rule-engine/src/test/java/com/baeldung/ruleengine/RuleEngineUnitTest.java @@ -0,0 +1,40 @@ +package com.baeldung.ruleengine; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.List; + +import com.baeldung.ruleengine.model.Customer; +import com.baeldung.ruleengine.model.Order; + +public class RuleEngineUnitTest { + + @Test + void whenTwoRulesTriggered_thenBothDescriptionsReturned() { + Customer customer = new Customer("Max", 550, true); + Order order = new Order(600.0, customer); + + RuleEngine engine = new RuleEngine(List.of(new LoyaltyDiscountRule(), new FirstOrderHighValueSpecialDiscountRule())); + + List results = engine.evaluate(order); + + assertEquals(2, results.size()); + assertTrue(results.contains("Loyalty Discount Rule: Customer has more than 500 points")); + assertTrue(results.contains("First Order Special Discount Rule: First Time customer with high value order")); + } + + @Test + void whenNoRulesTriggered_thenEmptyListReturned() { + Customer customer = new Customer("Max", 50, false); + Order order = new Order(200.0, customer); + + RuleEngine engine = new RuleEngine(List.of(new LoyaltyDiscountRule(), new FirstOrderHighValueSpecialDiscountRule())); + + List results = engine.evaluate(order); + + assertTrue(results.isEmpty()); + } +} diff --git a/rule-engines-modules/simple-rule-engine/src/test/java/com/baeldung/ruleengine/SpelRuleUnitTest.java b/rule-engines-modules/simple-rule-engine/src/test/java/com/baeldung/ruleengine/SpelRuleUnitTest.java new file mode 100644 index 000000000000..3d4a2c131027 --- /dev/null +++ b/rule-engines-modules/simple-rule-engine/src/test/java/com/baeldung/ruleengine/SpelRuleUnitTest.java @@ -0,0 +1,34 @@ +package com.baeldung.ruleengine; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.baeldung.ruleengine.model.Customer; +import com.baeldung.ruleengine.model.Order; + +public class SpelRuleUnitTest { + + @Test + void whenLoyalCustomer_thenEligibleForDiscount() { + Customer customer = new Customer("Bob", 730, false); + Order order = new Order(200.0, customer); + + SpelRule rule = new SpelRule( + "#order.customer.loyaltyPoints > 500", + "Loyalty discount rule" + ); + assertTrue(rule.evaluate(order)); + } + + @Test + void whenFirstOrderHighAmount_thenEligibleForSpecialDiscount() { + Customer customer = new Customer("Bob", 0, true); + Order order = new Order(800.0, customer); + + SpelRule approvalRule = new SpelRule( + "#order.customer.firstOrder and #order.amount > 500", + "First-time customer with high order gets special discount" + ); + assertTrue(approvalRule.evaluate(order)); + } +} diff --git a/rule-engines-modules/simple-rule-engine/src/test/resources/logback.xml b/rule-engines-modules/simple-rule-engine/src/test/resources/logback.xml new file mode 100644 index 000000000000..7d900d8ea884 --- /dev/null +++ b/rule-engines-modules/simple-rule-engine/src/test/resources/logback.xml @@ -0,0 +1,13 @@ + + + + + %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n + + + + + + + + \ No newline at end of file