Adding easy-rules-jexl to support Java Expression Language (JEXL)
parent
0072222390
commit
eab2e3b367
@ -0,0 +1,102 @@
|
|||||||
|
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>org.jeasy</groupId>
|
||||||
|
<artifactId>easy-rules</artifactId>
|
||||||
|
<version>4.0.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>easy-rules-jexl</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<name>Easy Rules JEXL module</name>
|
||||||
|
<description>JEXL integration module</description>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<jexl.version>3.1</jexl.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<scm>
|
||||||
|
<url>git@github.com:j-easy/easy-rules.git</url>
|
||||||
|
<connection>scm:git:git@github.com:j-easy/easy-rules.git</connection>
|
||||||
|
<developerConnection>scm:git:git@github.com:j-easy/easy-rules.git</developerConnection>
|
||||||
|
<tag>HEAD</tag>
|
||||||
|
</scm>
|
||||||
|
|
||||||
|
<issueManagement>
|
||||||
|
<system>GitHub</system>
|
||||||
|
<url>https://github.com/j-easy/easy-rules/issues</url>
|
||||||
|
</issueManagement>
|
||||||
|
|
||||||
|
<ciManagement>
|
||||||
|
<system>Travis CI</system>
|
||||||
|
<url>https://travis-ci.org/j-easy/easy-rules</url>
|
||||||
|
</ciManagement>
|
||||||
|
|
||||||
|
<developers>
|
||||||
|
<developer>
|
||||||
|
<id>benas</id>
|
||||||
|
<name>Mahmoud Ben Hassine</name>
|
||||||
|
<url>http://benas.github.io</url>
|
||||||
|
<email>mahmoud.benhassine@icloud.com</email>
|
||||||
|
<roles>
|
||||||
|
<role>Lead developer</role>
|
||||||
|
</roles>
|
||||||
|
</developer>
|
||||||
|
</developers>
|
||||||
|
|
||||||
|
<licenses>
|
||||||
|
<license>
|
||||||
|
<name>MIT License</name>
|
||||||
|
<url>http://opensource.org/licenses/mit-license.php</url>
|
||||||
|
</license>
|
||||||
|
</licenses>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jeasy</groupId>
|
||||||
|
<artifactId>easy-rules-support</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-simple</artifactId>
|
||||||
|
<version>${slf4j-api.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-jexl3</artifactId>
|
||||||
|
<version>${jexl.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.assertj</groupId>
|
||||||
|
<artifactId>assertj-core</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.github.stefanbirkner</groupId>
|
||||||
|
<artifactId>system-rules</artifactId>
|
||||||
|
<version>${system-rules.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>com.mycila</groupId>
|
||||||
|
<artifactId>license-maven-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<header>${project.parent.basedir}/licence-header-template.txt</header>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</project>
|
||||||
|
|
@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* The MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020, Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package org.jeasy.rules.jexl;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.apache.commons.jexl3.JexlEngine;
|
||||||
|
import org.apache.commons.jexl3.JexlException;
|
||||||
|
import org.apache.commons.jexl3.JexlScript;
|
||||||
|
import org.apache.commons.jexl3.MapContext;
|
||||||
|
import org.jeasy.rules.api.Action;
|
||||||
|
import org.jeasy.rules.api.Facts;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class JexlAction implements Action {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(JexlAction.class);
|
||||||
|
|
||||||
|
private final JexlScript compiledScript;
|
||||||
|
private final String expression;
|
||||||
|
|
||||||
|
public JexlAction(String expression) {
|
||||||
|
this.expression = Objects.requireNonNull(expression, "expression cannot be null");
|
||||||
|
this.compiledScript = JexlRule.DEFAULT_JEXL.createScript(expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JexlAction(String expression, JexlEngine jexl) {
|
||||||
|
this.expression = Objects.requireNonNull(expression, "expression cannot be null");
|
||||||
|
Objects.requireNonNull(jexl, "jexl cannot be null");
|
||||||
|
this.compiledScript = jexl.createScript(expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Facts facts) throws Exception {
|
||||||
|
Objects.requireNonNull(facts, "facts cannot be null");
|
||||||
|
MapContext ctx = new MapContext(facts.asMap());
|
||||||
|
try {
|
||||||
|
compiledScript.execute(ctx);
|
||||||
|
} catch (JexlException e) {
|
||||||
|
LOGGER.error("Unable to evaluate expression: '" + expression + "' on facts: " + facts, e);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* The MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020, Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package org.jeasy.rules.jexl;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.apache.commons.jexl3.JexlEngine;
|
||||||
|
import org.apache.commons.jexl3.JexlScript;
|
||||||
|
import org.apache.commons.jexl3.MapContext;
|
||||||
|
import org.jeasy.rules.api.Condition;
|
||||||
|
import org.jeasy.rules.api.Facts;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
public class JexlCondition implements Condition {
|
||||||
|
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger(JexlCondition.class);
|
||||||
|
|
||||||
|
private final JexlScript compiledScript;
|
||||||
|
private final String expression;
|
||||||
|
|
||||||
|
public JexlCondition(String expression) {
|
||||||
|
this.expression = Objects.requireNonNull(expression, "expression cannot be null");
|
||||||
|
this.compiledScript = JexlRule.DEFAULT_JEXL.createScript(expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JexlCondition(String expression, JexlEngine jexl) {
|
||||||
|
this.expression = Objects.requireNonNull(expression, "expression cannot be null");
|
||||||
|
Objects.requireNonNull(jexl, "jexl cannot be null");
|
||||||
|
this.compiledScript = jexl.createScript(expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean evaluate(Facts facts) {
|
||||||
|
Objects.requireNonNull(facts, "facts cannot be null");
|
||||||
|
MapContext ctx = new MapContext(facts.asMap());
|
||||||
|
try {
|
||||||
|
return (Boolean) compiledScript.execute(ctx);
|
||||||
|
} catch (Exception e) {
|
||||||
|
LOGGER.error("Unable to evaluate expression: '" + expression + "' on facts: " + facts, e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* The MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020, Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package org.jeasy.rules.jexl;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.apache.commons.jexl3.JexlBuilder;
|
||||||
|
import org.apache.commons.jexl3.JexlEngine;
|
||||||
|
import org.jeasy.rules.api.Action;
|
||||||
|
import org.jeasy.rules.api.Condition;
|
||||||
|
import org.jeasy.rules.api.Facts;
|
||||||
|
import org.jeasy.rules.api.Rule;
|
||||||
|
import org.jeasy.rules.core.BasicRule;
|
||||||
|
|
||||||
|
public class JexlRule extends BasicRule {
|
||||||
|
|
||||||
|
static final JexlEngine DEFAULT_JEXL = new JexlBuilder().create();
|
||||||
|
|
||||||
|
private Condition condition = Condition.FALSE;
|
||||||
|
private final List<Action> actions = new ArrayList<>();
|
||||||
|
private final JexlEngine jexl;
|
||||||
|
|
||||||
|
public JexlRule() {
|
||||||
|
this(DEFAULT_JEXL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JexlRule(JexlEngine jexl) {
|
||||||
|
super(Rule.DEFAULT_NAME, Rule.DEFAULT_DESCRIPTION, Rule.DEFAULT_PRIORITY);
|
||||||
|
this.jexl = Objects.requireNonNull(jexl, "jexl cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
public JexlRule name(String name) {
|
||||||
|
this.name = Objects.requireNonNull(name, "name cannot be null");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JexlRule description(String description) {
|
||||||
|
this.description = Objects.requireNonNull(description, "description cannot be null");
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JexlRule priority(int priority) {
|
||||||
|
this.priority = priority;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JexlRule when(String condition) {
|
||||||
|
Objects.requireNonNull(condition, "condition cannot be null");
|
||||||
|
return when(condition, jexl);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JexlRule when(String condition, JexlEngine jexl) {
|
||||||
|
Objects.requireNonNull(condition, "condition cannot be null");
|
||||||
|
Objects.requireNonNull(jexl, "jexl cannot be null");
|
||||||
|
this.condition = new JexlCondition(condition, jexl);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JexlRule then(String action) {
|
||||||
|
Objects.requireNonNull(action, "action cannot be null");
|
||||||
|
return then(action, jexl);
|
||||||
|
}
|
||||||
|
|
||||||
|
public JexlRule then(String action, JexlEngine jexl) {
|
||||||
|
Objects.requireNonNull(action, "action cannot be null");
|
||||||
|
Objects.requireNonNull(jexl, "jexl cannot be null");
|
||||||
|
this.actions.add(new JexlAction(action, jexl));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean evaluate(Facts facts) {
|
||||||
|
Objects.requireNonNull(facts, "facts cannot be null");
|
||||||
|
return condition.evaluate(facts);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Facts facts) throws Exception {
|
||||||
|
Objects.requireNonNull(facts, "facts cannot be null");
|
||||||
|
for (Action action : actions) {
|
||||||
|
action.execute(facts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
* The MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020, Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package org.jeasy.rules.jexl;
|
||||||
|
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import org.apache.commons.jexl3.JexlEngine;
|
||||||
|
import org.jeasy.rules.api.Rule;
|
||||||
|
import org.jeasy.rules.api.Rules;
|
||||||
|
import org.jeasy.rules.support.AbstractRuleFactory;
|
||||||
|
import org.jeasy.rules.support.RuleDefinition;
|
||||||
|
import org.jeasy.rules.support.RuleDefinitionReader;
|
||||||
|
|
||||||
|
public class JexlRuleFactory extends AbstractRuleFactory<JexlEngine> {
|
||||||
|
|
||||||
|
private RuleDefinitionReader reader;
|
||||||
|
|
||||||
|
public JexlRuleFactory(RuleDefinitionReader reader) {
|
||||||
|
this.reader = Objects.requireNonNull(reader, "reader cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Rule createRule(Reader ruleDescriptor) throws Exception {
|
||||||
|
Objects.requireNonNull(ruleDescriptor, "ruleDescriptor cannot be null");
|
||||||
|
return createRule(ruleDescriptor, JexlRule.DEFAULT_JEXL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Rule createRule(Reader ruleDescriptor, JexlEngine jexl) throws Exception {
|
||||||
|
Objects.requireNonNull(ruleDescriptor, "ruleDescriptor cannot be null");
|
||||||
|
Objects.requireNonNull(jexl, "jexl cannot be null");
|
||||||
|
List<RuleDefinition> ruleDefinitions = reader.read(ruleDescriptor);
|
||||||
|
if (ruleDefinitions.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("rule descriptor is empty");
|
||||||
|
}
|
||||||
|
return createRule(ruleDefinitions.get(0), jexl);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Rules createRules(Reader rulesDescriptor) throws Exception {
|
||||||
|
Objects.requireNonNull(rulesDescriptor, "rulesDescriptor cannot be null");
|
||||||
|
return createRules(rulesDescriptor, JexlRule.DEFAULT_JEXL);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Rules createRules(Reader rulesDescriptor, JexlEngine jexl) throws Exception {
|
||||||
|
Objects.requireNonNull(rulesDescriptor, "rulesDescriptor cannot be null");
|
||||||
|
Objects.requireNonNull(jexl, "jexl cannot be null");
|
||||||
|
Rules rules = new Rules();
|
||||||
|
List<RuleDefinition> ruleDefinitions = reader.read(rulesDescriptor);
|
||||||
|
for (RuleDefinition ruleDefinition : ruleDefinitions) {
|
||||||
|
rules.register(createRule(ruleDefinition, jexl));
|
||||||
|
}
|
||||||
|
return rules;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Rule createSimpleRule(RuleDefinition ruleDefinition, JexlEngine jexl) {
|
||||||
|
Objects.requireNonNull(ruleDefinition, "ruleDefinition cannot be null");
|
||||||
|
Objects.requireNonNull(jexl, "jexl cannot be null");
|
||||||
|
JexlRule rule = new JexlRule()
|
||||||
|
.name(ruleDefinition.getName())
|
||||||
|
.description(ruleDefinition.getDescription())
|
||||||
|
.priority(ruleDefinition.getPriority())
|
||||||
|
.when(ruleDefinition.getCondition(), jexl);
|
||||||
|
for (String action : ruleDefinition.getActions()) {
|
||||||
|
rule.then(action, jexl);
|
||||||
|
}
|
||||||
|
return rule;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* The MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020, Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* This package contains classes to support <a href="http://commons.apache.org/proper/commons-jexl/">JEXL</a>.
|
||||||
|
*/
|
||||||
|
package org.jeasy.rules.jexl;
|
@ -0,0 +1,219 @@
|
|||||||
|
/*
|
||||||
|
* The MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020, Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package org.jeasy.rules.jexl;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.apache.commons.jexl3.JexlBuilder;
|
||||||
|
import org.apache.commons.jexl3.JexlEngine;
|
||||||
|
import org.apache.commons.jexl3.JexlException;
|
||||||
|
import org.apache.commons.jexl3.introspection.JexlSandbox;
|
||||||
|
import org.jeasy.rules.api.Action;
|
||||||
|
import org.jeasy.rules.api.Facts;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.contrib.java.lang.system.SystemOutRule;
|
||||||
|
import org.junit.rules.ExpectedException;
|
||||||
|
|
||||||
|
public class JexlActionTest {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public final SystemOutRule systemOutRule = new SystemOutRule().enableLog();
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
@Rule
|
||||||
|
public final ExpectedException expectedException = ExpectedException.none();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMVELActionExecution() throws Exception {
|
||||||
|
// given
|
||||||
|
Action markAsAdult = new JexlAction("person.setAdult(true);");
|
||||||
|
Facts facts = new Facts();
|
||||||
|
Person foo = new Person("foo", 20);
|
||||||
|
facts.put("person", foo);
|
||||||
|
|
||||||
|
// when
|
||||||
|
markAsAdult.execute(facts);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertThat(foo.isAdult()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testJexlFunctionExecution() throws Exception {
|
||||||
|
// given
|
||||||
|
Action printAction = new JexlAction("var hello = function() { System.out.println(\"Hello from JEXL!\"); }; hello();");
|
||||||
|
Facts facts = new Facts();
|
||||||
|
facts.put("System", System.class);
|
||||||
|
|
||||||
|
// when
|
||||||
|
printAction.execute(facts);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertThat(systemOutRule.getLog()).contains("Hello from JEXL!");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMVELActionExecutionWithFailure() throws Exception {
|
||||||
|
// given
|
||||||
|
expectedException.expect(JexlException.Method.class);
|
||||||
|
expectedException.expectMessage("org.jeasy.rules.jexl.JexlAction.<init>@1:7 unsolvable function/method 'setBlah'");
|
||||||
|
Action action = new JexlAction("person.setBlah(true);");
|
||||||
|
Facts facts = new Facts();
|
||||||
|
Person foo = new Person("foo", 20);
|
||||||
|
facts.put("person", foo);
|
||||||
|
|
||||||
|
// when
|
||||||
|
action.execute(facts);
|
||||||
|
|
||||||
|
// then
|
||||||
|
// excepted exception
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testJexlActionWithExpressionAndFacts() throws Exception {
|
||||||
|
// given
|
||||||
|
JexlEngine jexl = new JexlBuilder()
|
||||||
|
.create();
|
||||||
|
Action printAction = new JexlAction(
|
||||||
|
"var random = function() { System.out.println(\"Random from JEXL = \" + new('java.util.Random', 123).nextInt(10)); }; random();",
|
||||||
|
jexl);
|
||||||
|
Facts facts = new Facts();
|
||||||
|
facts.put("System", System.class);
|
||||||
|
|
||||||
|
// when
|
||||||
|
printAction.execute(facts);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertThat(systemOutRule.getLog()).contains("Random from JEXL = 2");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWithBlackSanbox() throws Exception {
|
||||||
|
JexlSandbox sandbox = new JexlSandbox(false);
|
||||||
|
sandbox.white(System.class.getName()).execute("currentTimeMillis");
|
||||||
|
JexlEngine jexl = new JexlBuilder()
|
||||||
|
.sandbox(sandbox)
|
||||||
|
.create();
|
||||||
|
|
||||||
|
Facts facts = new Facts();
|
||||||
|
facts.put("result", null);
|
||||||
|
facts.put("System", System.class);
|
||||||
|
facts.put("UUID", UUID.class);
|
||||||
|
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
new JexlAction("result = System.currentTimeMillis()", jexl).execute(facts);
|
||||||
|
Object result = facts.get("result");
|
||||||
|
assertThat(result).isInstanceOf(Long.class);
|
||||||
|
assertThat((long) result).isGreaterThanOrEqualTo(now);
|
||||||
|
|
||||||
|
try {
|
||||||
|
new JexlAction("result = System.nanoTime()", jexl).execute(facts);
|
||||||
|
fail("Exception expected");
|
||||||
|
} catch (Exception e) {
|
||||||
|
assertThat(e).isInstanceOf(JexlException.Method.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
new JexlAction("result = new('java.lang.Double', 10)", jexl).execute(facts);
|
||||||
|
fail("Exception expected");
|
||||||
|
} catch (Exception e) {
|
||||||
|
assertThat(e).isInstanceOf(JexlException.Method.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
new JexlAction("result = new('java.io.File', '/tmp/jexl-sandbox-test')", jexl).execute(facts);
|
||||||
|
fail("Exception expected");
|
||||||
|
} catch (Exception e) {
|
||||||
|
assertThat(e).isInstanceOf(JexlException.Method.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
new JexlAction("result = UUID.randomUUID()", jexl).execute(facts);
|
||||||
|
fail("Exception expected");
|
||||||
|
} catch (Exception e) {
|
||||||
|
assertThat(e).isInstanceOf(JexlException.Method.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
new JexlAction("result = Math.PI", jexl).execute(facts);
|
||||||
|
fail("Exception expected");
|
||||||
|
} catch (Exception e) {
|
||||||
|
assertThat(e).isInstanceOf(JexlException.Variable.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testWithWhiteSanbox() throws Exception {
|
||||||
|
JexlSandbox sandbox = new JexlSandbox(true);
|
||||||
|
sandbox.black(System.class.getName()).execute("nanoTime");
|
||||||
|
JexlEngine jexl = new JexlBuilder()
|
||||||
|
.sandbox(sandbox)
|
||||||
|
.create();
|
||||||
|
|
||||||
|
Facts facts = new Facts();
|
||||||
|
facts.put("result", null);
|
||||||
|
facts.put("System", System.class);
|
||||||
|
facts.put("UUID", UUID.class);
|
||||||
|
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
new JexlAction("result = System.currentTimeMillis()", jexl).execute(facts);
|
||||||
|
Object result = facts.get("result");
|
||||||
|
assertThat(result).isInstanceOf(Long.class);
|
||||||
|
assertThat((long) result).isGreaterThanOrEqualTo(now);
|
||||||
|
|
||||||
|
try {
|
||||||
|
new JexlAction("result = System.nanoTime()", jexl).execute(facts);
|
||||||
|
fail("Exception expected");
|
||||||
|
} catch (Exception e) {
|
||||||
|
assertThat(e).isInstanceOf(JexlException.Method.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
new JexlAction("result = new('java.lang.Double', 10)", jexl).execute(facts);
|
||||||
|
result = facts.get("result");
|
||||||
|
assertThat(result).isInstanceOf(Double.class);
|
||||||
|
assertThat((double) result).isEqualTo(10.0);
|
||||||
|
|
||||||
|
new JexlAction("result = new('java.io.File', '/tmp/jexl-sandbox-test')", jexl).execute(facts);
|
||||||
|
result = facts.get("result");
|
||||||
|
assertThat(result).isInstanceOf(File.class);
|
||||||
|
assertThat(((File) result).exists()).isFalse();
|
||||||
|
assertThat(((File) result).getName()).isEqualTo("jexl-sandbox-test");
|
||||||
|
|
||||||
|
new JexlAction("result = UUID.randomUUID()", jexl).execute(facts);
|
||||||
|
result = facts.get("result");
|
||||||
|
assertThat(result).isInstanceOf(UUID.class);
|
||||||
|
|
||||||
|
try {
|
||||||
|
new JexlAction("result = Math.PI", jexl).execute(facts);
|
||||||
|
fail("Exception expected");
|
||||||
|
} catch (Exception e) {
|
||||||
|
assertThat(e).isInstanceOf(JexlException.Variable.class);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* The MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020, Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package org.jeasy.rules.jexl;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
import org.jeasy.rules.api.Condition;
|
||||||
|
import org.jeasy.rules.api.Facts;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class JexlConditionTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testJexlExpressionEvaluation() {
|
||||||
|
// given
|
||||||
|
Condition isAdult = new JexlCondition("person.age > 18");
|
||||||
|
Facts facts = new Facts();
|
||||||
|
facts.put("person", new Person("foo", 20));
|
||||||
|
|
||||||
|
// when
|
||||||
|
boolean evaluationResult = isAdult.evaluate(facts);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertThat(evaluationResult).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenDeclaredFactIsNotPresent_thenShouldReturnFalse() {
|
||||||
|
// given
|
||||||
|
Condition isHot = new JexlCondition("temperature > 30");
|
||||||
|
Facts facts = new Facts();
|
||||||
|
|
||||||
|
// when
|
||||||
|
boolean evaluationResult = isHot.evaluate(facts);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertThat(evaluationResult).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testJexlConditionWithFacts() {
|
||||||
|
// given
|
||||||
|
Condition condition = new JexlCondition("return rnd.nextBoolean();");
|
||||||
|
Facts facts = new Facts();
|
||||||
|
facts.put("rnd", new Random(123));
|
||||||
|
|
||||||
|
// when
|
||||||
|
boolean evaluationResult = condition.evaluate(facts);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertThat(evaluationResult).isTrue();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,191 @@
|
|||||||
|
/*
|
||||||
|
* The MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020, Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package org.jeasy.rules.jexl;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileReader;
|
||||||
|
import java.io.Reader;
|
||||||
|
import java.io.StringReader;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Iterator;
|
||||||
|
|
||||||
|
import org.jeasy.rules.api.Rule;
|
||||||
|
import org.jeasy.rules.api.Rules;
|
||||||
|
import org.jeasy.rules.support.JsonRuleDefinitionReader;
|
||||||
|
import org.jeasy.rules.support.UnitRuleGroup;
|
||||||
|
import org.jeasy.rules.support.YamlRuleDefinitionReader;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.ExpectedException;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.Parameterized;
|
||||||
|
import org.junit.runners.Parameterized.Parameter;
|
||||||
|
import org.junit.runners.Parameterized.Parameters;
|
||||||
|
|
||||||
|
@RunWith(Parameterized.class)
|
||||||
|
public class JexlRuleFactoryTest {
|
||||||
|
|
||||||
|
@Parameters
|
||||||
|
public static Collection<Object[]> params() {
|
||||||
|
return Arrays.asList(new Object[][] {
|
||||||
|
{ new JexlRuleFactory(new YamlRuleDefinitionReader()), "yml" },
|
||||||
|
{ new JexlRuleFactory(new JsonRuleDefinitionReader()), "json" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
@org.junit.Rule
|
||||||
|
public ExpectedException expectedException = ExpectedException.none();
|
||||||
|
|
||||||
|
@Parameter(0)
|
||||||
|
public JexlRuleFactory factory;
|
||||||
|
|
||||||
|
@Parameter(1)
|
||||||
|
public String ext;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRulesCreation() throws Exception {
|
||||||
|
// given
|
||||||
|
File rulesDescriptor = new File("src/test/resources/rules." + ext);
|
||||||
|
|
||||||
|
// when
|
||||||
|
Rules rules = factory.createRules(new FileReader(rulesDescriptor));
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertThat(rules).hasSize(2);
|
||||||
|
Iterator<Rule> iterator = rules.iterator();
|
||||||
|
|
||||||
|
Rule rule = iterator.next();
|
||||||
|
assertThat(rule).isNotNull();
|
||||||
|
assertThat(rule.getName()).isEqualTo("adult rule");
|
||||||
|
assertThat(rule.getDescription()).isEqualTo("when age is greater then 18, then mark as adult");
|
||||||
|
assertThat(rule.getPriority()).isEqualTo(1);
|
||||||
|
|
||||||
|
rule = iterator.next();
|
||||||
|
assertThat(rule).isNotNull();
|
||||||
|
assertThat(rule.getName()).isEqualTo("weather rule");
|
||||||
|
assertThat(rule.getDescription()).isEqualTo("when it rains, then take an umbrella");
|
||||||
|
assertThat(rule.getPriority()).isEqualTo(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRuleCreationFromFileReader() throws Exception {
|
||||||
|
// given
|
||||||
|
Reader adultRuleDescriptorAsReader = new FileReader("src/test/resources/adult-rule." + ext);
|
||||||
|
|
||||||
|
// when
|
||||||
|
Rule adultRule = factory.createRule(adultRuleDescriptorAsReader);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertThat(adultRule.getName()).isEqualTo("adult rule");
|
||||||
|
assertThat(adultRule.getDescription()).isEqualTo("when age is greater then 18, then mark as adult");
|
||||||
|
assertThat(adultRule.getPriority()).isEqualTo(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRuleCreationFromStringReader() throws Exception {
|
||||||
|
// given
|
||||||
|
Reader adultRuleDescriptorAsReader = new StringReader(new String(Files.readAllBytes(Paths.get("src/test/resources/adult-rule." + ext))));
|
||||||
|
|
||||||
|
// when
|
||||||
|
Rule adultRule = factory.createRule(adultRuleDescriptorAsReader);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertThat(adultRule.getName()).isEqualTo("adult rule");
|
||||||
|
assertThat(adultRule.getDescription()).isEqualTo("when age is greater then 18, then mark as adult");
|
||||||
|
assertThat(adultRule.getPriority()).isEqualTo(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRuleCreationFromFileReader_withCompositeRules() throws Exception {
|
||||||
|
// given
|
||||||
|
File rulesDescriptor = new File("src/test/resources/composite-rules." + ext);
|
||||||
|
|
||||||
|
// when
|
||||||
|
Rules rules = factory.createRules(new FileReader(rulesDescriptor));
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertThat(rules).hasSize(2);
|
||||||
|
Iterator<Rule> iterator = rules.iterator();
|
||||||
|
|
||||||
|
Rule rule = iterator.next();
|
||||||
|
assertThat(rule).isNotNull();
|
||||||
|
assertThat(rule.getName()).isEqualTo("Movie id rule");
|
||||||
|
assertThat(rule.getDescription()).isEqualTo("description");
|
||||||
|
assertThat(rule.getPriority()).isEqualTo(1);
|
||||||
|
assertThat(rule).isInstanceOf(UnitRuleGroup.class);
|
||||||
|
|
||||||
|
rule = iterator.next();
|
||||||
|
assertThat(rule).isNotNull();
|
||||||
|
assertThat(rule.getName()).isEqualTo("weather rule");
|
||||||
|
assertThat(rule.getDescription()).isEqualTo("when it rains, then take an umbrella");
|
||||||
|
assertThat(rule.getPriority()).isEqualTo(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRuleCreationFromFileReader_withInvalidCompositeRuleType() throws Exception {
|
||||||
|
// given
|
||||||
|
expectedException.expect(IllegalArgumentException.class);
|
||||||
|
expectedException.expectMessage("Invalid composite rule type, must be one of [UnitRuleGroup, ConditionalRuleGroup, ActivationRuleGroup]");
|
||||||
|
File rulesDescriptor = new File("src/test/resources/composite-rule-invalid-composite-rule-type." + ext);
|
||||||
|
|
||||||
|
// when
|
||||||
|
factory.createRule(new FileReader(rulesDescriptor));
|
||||||
|
|
||||||
|
// then
|
||||||
|
// expected exception
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRuleCreationFromFileReader_withEmptyComposingRules() throws Exception {
|
||||||
|
// given
|
||||||
|
expectedException.expect(IllegalArgumentException.class);
|
||||||
|
expectedException.expectMessage("Composite rules must have composing rules specified");
|
||||||
|
File rulesDescriptor = new File("src/test/resources/composite-rule-invalid-empty-composing-rules." + ext);
|
||||||
|
|
||||||
|
// when
|
||||||
|
factory.createRule(new FileReader(rulesDescriptor));
|
||||||
|
|
||||||
|
// then
|
||||||
|
// expected exception
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testRuleCreationFromFileReader_withNonCompositeRuleDeclaresComposingRules() throws Exception {
|
||||||
|
// given
|
||||||
|
expectedException.expect(IllegalArgumentException.class);
|
||||||
|
expectedException.expectMessage("Non-composite rules cannot have composing rules");
|
||||||
|
File rulesDescriptor = new File("src/test/resources/non-composite-rule-with-composing-rules." + ext);
|
||||||
|
|
||||||
|
// when
|
||||||
|
factory.createRule(new FileReader(rulesDescriptor));
|
||||||
|
|
||||||
|
// then
|
||||||
|
// expected exception
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* The MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020, Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package org.jeasy.rules.jexl;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
import org.jeasy.rules.api.Facts;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class JexlRuleTest {
|
||||||
|
|
||||||
|
private Facts facts = new Facts();
|
||||||
|
private JexlRule jexlRule = new JexlRule().name("name").description("description").priority(1);
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
jexlRule.when("person.age > 18");
|
||||||
|
jexlRule.then("person.setAdult(true);");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenTheRuleIsTriggered_thenConditionShouldBeEvaluated() {
|
||||||
|
// given
|
||||||
|
facts.put("person", new Person("foo", 20));
|
||||||
|
|
||||||
|
// when
|
||||||
|
boolean evaluationResult = jexlRule.evaluate(facts);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertThat(evaluationResult).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void whenTheConditionIsTrue_thenActionsShouldBeExecuted() throws Exception {
|
||||||
|
// given
|
||||||
|
Person foo = new Person("foo", 20);
|
||||||
|
facts.put("person", foo);
|
||||||
|
|
||||||
|
// when
|
||||||
|
jexlRule.execute(facts);
|
||||||
|
|
||||||
|
// then
|
||||||
|
assertThat(foo.isAdult()).isTrue();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
* The MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020, Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package org.jeasy.rules.jexl;
|
||||||
|
|
||||||
|
public class Person {
|
||||||
|
|
||||||
|
private String name;
|
||||||
|
private int age;
|
||||||
|
private boolean isAdult;
|
||||||
|
|
||||||
|
public Person(String name, int age) {
|
||||||
|
this.name = name;
|
||||||
|
this.age = age;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setName(String name) {
|
||||||
|
this.name = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getAge() {
|
||||||
|
return age;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAge(int age) {
|
||||||
|
this.age = age;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAdult() {
|
||||||
|
return isAdult;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAdult(boolean adult) {
|
||||||
|
isAdult = adult;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "adult rule",
|
||||||
|
"description": "when age is greater then 18, then mark as adult",
|
||||||
|
"priority": 1,
|
||||||
|
"condition": "person.age > 18",
|
||||||
|
"actions": [
|
||||||
|
"person.setAdult(true);"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
@ -0,0 +1,6 @@
|
|||||||
|
name: adult rule
|
||||||
|
description: when age is greater then 18, then mark as adult
|
||||||
|
priority: 1
|
||||||
|
condition: "person.age > 18"
|
||||||
|
actions:
|
||||||
|
- "person.setAdult(true);"
|
@ -0,0 +1,23 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "invalid rule",
|
||||||
|
"compositeRuleType": "foo",
|
||||||
|
"priority": 1,
|
||||||
|
"composingRules": [
|
||||||
|
{
|
||||||
|
"name": "rule1",
|
||||||
|
"condition": "true",
|
||||||
|
"actions": [
|
||||||
|
"System.out.println();"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "rule2",
|
||||||
|
"condition": "false",
|
||||||
|
"actions": [
|
||||||
|
"System.out.println();"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
@ -0,0 +1,12 @@
|
|||||||
|
name: invalid rule
|
||||||
|
compositeRuleType: foo
|
||||||
|
priority: 1
|
||||||
|
composingRules:
|
||||||
|
- name: rule1
|
||||||
|
condition: "true"
|
||||||
|
actions:
|
||||||
|
- "System.out.println();"
|
||||||
|
- name: rule2
|
||||||
|
condition: "false"
|
||||||
|
actions:
|
||||||
|
- "System.out.println();"
|
@ -0,0 +1,8 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "invalid rule",
|
||||||
|
"compositeRuleType": "UnitRuleGroup",
|
||||||
|
"priority": 1,
|
||||||
|
"composingRules": []
|
||||||
|
}
|
||||||
|
]
|
@ -0,0 +1,4 @@
|
|||||||
|
name: invalid rule
|
||||||
|
compositeRuleType: UnitRuleGroup
|
||||||
|
priority: 1
|
||||||
|
composingRules:
|
@ -0,0 +1,36 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Movie id rule",
|
||||||
|
"compositeRuleType": "UnitRuleGroup",
|
||||||
|
"priority": 1,
|
||||||
|
"composingRules": [
|
||||||
|
{
|
||||||
|
"name": "Time is evening",
|
||||||
|
"description": "If it's later than 7pm",
|
||||||
|
"priority": 1,
|
||||||
|
"condition": "day.hour > 19",
|
||||||
|
"actions": [
|
||||||
|
"person.shouldProvideId(true);"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Movie is rated R",
|
||||||
|
"description": "If the movie is rated R",
|
||||||
|
"priority": 1,
|
||||||
|
"condition": "movie.rating == R",
|
||||||
|
"actions": [
|
||||||
|
"person.shouldProvideId(true);"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "weather rule",
|
||||||
|
"description": "when it rains, then take an umbrella",
|
||||||
|
"priority": 1,
|
||||||
|
"condition": "rain == True",
|
||||||
|
"actions": [
|
||||||
|
"System.out.println(\"It rains, take an umbrella!\");"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
@ -0,0 +1,23 @@
|
|||||||
|
name: Movie id rule
|
||||||
|
compositeRuleType: UnitRuleGroup
|
||||||
|
priority: 1
|
||||||
|
composingRules:
|
||||||
|
- name: Time is evening
|
||||||
|
description: If it's later than 7pm
|
||||||
|
priority: 1
|
||||||
|
condition: "day.hour > 19"
|
||||||
|
actions:
|
||||||
|
- "person.shouldProvideId(true);"
|
||||||
|
- name: Movie is rated R
|
||||||
|
description: If the movie is rated R
|
||||||
|
priority: 1
|
||||||
|
condition: "movie.rating == R"
|
||||||
|
actions:
|
||||||
|
- "person.shouldProvideId(true);"
|
||||||
|
---
|
||||||
|
name: weather rule
|
||||||
|
description: when it rains, then take an umbrella
|
||||||
|
priority: 1
|
||||||
|
condition: "rain == True"
|
||||||
|
actions:
|
||||||
|
- "System.out.println(\"It rains, take an umbrella!\");"
|
@ -0,0 +1,30 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "Movie id rule",
|
||||||
|
"priority": 1,
|
||||||
|
"condition": "true",
|
||||||
|
"actions": [
|
||||||
|
"System.out.println();"
|
||||||
|
],
|
||||||
|
"composingRules": [
|
||||||
|
{
|
||||||
|
"name": "Time is evening",
|
||||||
|
"description": "If it's later than 7pm",
|
||||||
|
"priority": 1,
|
||||||
|
"condition": "day.hour > 19",
|
||||||
|
"actions": [
|
||||||
|
"person.shouldProvideId(true);"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Movie is rated R",
|
||||||
|
"description": "If the movie is rated R",
|
||||||
|
"priority": 1,
|
||||||
|
"condition": "movie.rating == R",
|
||||||
|
"actions": [
|
||||||
|
"person.shouldProvideId(true);"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
@ -0,0 +1,18 @@
|
|||||||
|
name: Movie id rule
|
||||||
|
priority: 1
|
||||||
|
condition: "true"
|
||||||
|
actions:
|
||||||
|
- "System.out.println();"
|
||||||
|
composingRules:
|
||||||
|
- name: Time is evening
|
||||||
|
description: If it's later than 7pm
|
||||||
|
priority: 1
|
||||||
|
condition: "day.hour > 19"
|
||||||
|
actions:
|
||||||
|
- "person.shouldProvideId(true);"
|
||||||
|
- name: Movie is rated R
|
||||||
|
description: If the movie is rated R
|
||||||
|
priority: 1
|
||||||
|
condition: "movie.rating == R"
|
||||||
|
actions:
|
||||||
|
- "person.shouldProvideId(true);"
|
@ -0,0 +1,20 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"name": "adult rule",
|
||||||
|
"description": "when age is greater then 18, then mark as adult",
|
||||||
|
"priority": 1,
|
||||||
|
"condition": "person.age > 18",
|
||||||
|
"actions": [
|
||||||
|
"person.setAdult(true);"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "weather rule",
|
||||||
|
"description": "when it rains, then take an umbrella",
|
||||||
|
"priority": 2,
|
||||||
|
"condition": "rain == true",
|
||||||
|
"actions": [
|
||||||
|
"System.out.println(\"It rains, take an umbrella!\");"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
@ -0,0 +1,14 @@
|
|||||||
|
---
|
||||||
|
name: adult rule
|
||||||
|
description: when age is greater then 18, then mark as adult
|
||||||
|
priority: 1
|
||||||
|
condition: "person.age > 18"
|
||||||
|
actions:
|
||||||
|
- "person.setAdult(true);"
|
||||||
|
---
|
||||||
|
name: weather rule
|
||||||
|
description: when it rains, then take an umbrella
|
||||||
|
priority: 2
|
||||||
|
condition: "rain == true"
|
||||||
|
actions:
|
||||||
|
- "System.out.println(\"It rains, take an umbrella!\");"
|
Loading…
Reference in New Issue