Adding easy-rules-jexl to support Java Expression Language (JEXL)

pull/324/head
Lauri Kimmel 5 years ago committed by Mahmoud Ben Hassine
parent 0072222390
commit eab2e3b367
No known key found for this signature in database
GPG Key ID: 2B4156D07E8A1737

@ -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!\");"

@ -19,6 +19,7 @@
<module>easy-rules-mvel</module>
<module>easy-rules-support</module>
<module>easy-rules-spel</module>
<module>easy-rules-jexl</module>
</modules>
<packaging>pom</packaging>

Loading…
Cancel
Save