rename base package to org.jeasy.rules after moving to jeasy organisation

pull/81/head
Mahmoud Ben Hassine 8 years ago
parent 4a4a4469c5
commit c18f7eb1bb

@ -18,17 +18,17 @@ This is exactly what Easy Rules does, it provides the `Rule` abstraction to crea
##### First, define your rule..
```java
@Rule(name = "my awesome rule" )
public class MyRule {
@Rule(name = "weather rule", description = "if it rains then take an umbrella" )
public class WeatherRule {
@Condition
public boolean when() {
return true;
public boolean itRains(@Fact("rain") boolean rain) {
return rain;
}
@Action
public void then() {
System.out.println("Easy Rules rocks!");
public void takeAnUmbrella() {
System.out.println("It rains, take an umbrella!");
}
}
```
@ -38,33 +38,36 @@ public class MyRule {
```java
public class Test {
public static void main(String[] args) {
// create a rules engine
RulesEngine rulesEngine = aNewRulesEngine().build();
//register the rule
rulesEngine.registerRule(new MyRule());
//fire rules
rulesEngine.fireRules();
// define facts
Facts facts = new Facts();
facts.add("rain", true);
// define rules
Rules rules = new Rules(new WeatherRule());
// fire rules on known facts
RulesEngine rulesEngine = new DefaultRulesEngine();
rulesEngine.fire(rules, facts);
}
}
```
This is the hello world of Easy Rules. You can find other examples like the [FizzBuzz tutorial](http://www.easyrules.org/tutorials/fizzbuzz-tutorial.html) in the documentation.
This is the hello world of Easy Rules. You can find other examples like the [FizzBuzz tutorial](https://github.com/j-easy/easy-rules/wiki/fizz-buzz) in the wiki.
## Quick links
|Item |Link |
|:---------------------|:-------------------------------------------------------------------------------------|
|Project Home | [http://www.easyrules.org](http://www.easyrules.org) |
|Presentation | [https://speakerdeck.com/benas/easy-rules](https://speakerdeck.com/benas/easy-rules) |
|Continuous integration| [Build job @ Travis CI](https://travis-ci.org/EasyRules/easyrules) |
|Agile Board | [Backlog items @ waffle.io](https://waffle.io/EasyRules/easyrules) |
|Code coverage | [![Coverage](https://coveralls.io/repos/EasyRules/easyrules/badge.svg?style=flat&branch=master&service=github)](https://coveralls.io/github/EasyRules/easyrules?branch=master) |
|Item |Link |
|:---------------------|:--------------------------------------------------------------------------------------|
|Project Home | [https://github.com/j-easy/easy-rules/wiki](https://github.com/j-easy/easy-rules/wiki)|
|Presentation | [https://speakerdeck.com/benas/easy-rules](https://speakerdeck.com/benas/easy-rules) |
|Continuous integration| [Build job @ Travis CI](https://travis-ci.org/j-easy/easy-rules) |
|Code coverage | [![Coverage](https://coveralls.io/repos/j-easy/easy-rules/badge.svg?style=flat&branch=master&service=github)](https://coveralls.io/github/j-easy/easy-rules?branch=master) |
|Sonar analysis | [![Quality Gate](https://sonarqube.com/api/badges/gate?key=org.easyrules:easyrules)](https://sonarqube.com/overview?id=org.easyrules%3Aeasyrules) |
## Current version
* The current stable version is `2.4.0` : [![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.easyrules/easyrules-core/badge.svg?style=flat)](http://search.maven.org/#artifactdetails|org.easyrules|easyrules-core|2.4.0|)
* The current development version is `2.5.0-SNAPSHOT` : [![Build Status](https://travis-ci.org/EasyRules/easyrules.svg?branch=master)](https://travis-ci.org/EasyRules/easyrules)
* The current stable version is `2.5.0` : [![Maven Central](https://maven-badges.herokuapp.com/maven-central/org.easyrules/easyrules-core/badge.svg?style=flat)](http://search.maven.org/#artifactdetails|org.easyrules|easyrules-core|2.5.0|)
* The current development version is `3.0.0-SNAPSHOT` : [![Build Status](https://travis-ci.org/j-easy/easy-rules.svg?branch=master)](https://travis-ci.org/j-easy/easy-rules)
In order to use snapshot versions, you need to add the following maven repository in your `pom.xml`:
@ -79,9 +82,9 @@ In order to use snapshot versions, you need to add the following maven repositor
You are welcome to contribute to the project with pull requests on GitHub.
If you found a bug or want to request a feature, please use the [issue tracker](https://github.com/EasyRules/easyrules/issues).
If you found a bug or want to request a feature, please use the [issue tracker](https://github.com/j-easy/easy-rules/issues).
For any further question, you can use the [Gitter](https://gitter.im/EasyRules/easyrules) channel of the project.
For any further question, you can use the [Gitter](https://gitter.im/j-easy/easy-rules) channel of the project.
## Awesome contributors

@ -1,15 +1,15 @@
<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/maven-v4_0_0.xsd">
<parent>
<groupId>org.easyrules</groupId>
<artifactId>easyrules</artifactId>
<groupId>org.jeasy</groupId>
<artifactId>easy-rules</artifactId>
<version>3.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>easyrules-archetype</artifactId>
<artifactId>easy-rules-archetype</artifactId>
<name>Easy Rules Quick Start Archetype</name>
<description>Maven archetype to create a skeleton project</description>
<url>http://www.easyrules.org</url>
<url>http://www.github.com/j-easy/easy-rules</url>
<developers>
<developer>
@ -24,20 +24,20 @@
</developers>
<scm>
<url>git@github.com:EasyRules/easyrules.git</url>
<connection>scm:git:git@github.com:EasyRules/easyrules.git</connection>
<developerConnection>scm:git:git@github.com:EasyRules/easyrules.git</developerConnection>
<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/EasyRules/easyrules/issues</url>
<url>https://github.com/j-easy/easy-rules/issues</url>
</issueManagement>
<ciManagement>
<system>Travis CI</system>
<url>https://travis-ci.org/EasyRules/easyrules</url>
<url>https://travis-ci.org/j-easy/easy-rules</url>
</ciManagement>
<licenses>

@ -1,5 +1,5 @@
<archetype>
<id>easyrules-archetype</id>
<id>easy-rules-archetype</id>
<sources>
<source>src/main/java/HelloWorldRule.java</source>
<source>src/main/java/Launcher.java</source>

@ -7,9 +7,9 @@
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>org.easyrules</groupId>
<artifactId>easyrules-core</artifactId>
<version>2.5.0</version>
<groupId>org.jeasy</groupId>
<artifactId>easy-rules-core</artifactId>
<version>3.0.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>

@ -23,9 +23,9 @@
*/
package ${packageName};
import org.easyrules.annotation.Action;
import org.easyrules.annotation.Condition;
import org.easyrules.annotation.Rule;
import org.jeasy.rules.annotation.Action;
import org.jeasy.rules.annotation.Condition;
import org.jeasy.rules.annotation.Rule;
@Rule(name = "Hello World rule", description = "Say Hello to only duke's friends")
public class HelloWorldRule {

@ -23,11 +23,11 @@
*/
package ${packageName};
import org.easyrules.api.RulesEngine;
import org.jeasy.rules.api.RulesEngine;
import java.util.Scanner;
import static org.easyrules.core.RulesEngineBuilder.aNewRulesEngine;
import static org.jeasy.rules.core.RulesEngineBuilder.aNewRulesEngine;
/**
* Launcher class of the Hello World sample.

@ -2,32 +2,32 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.easyrules</groupId>
<artifactId>easyrules</artifactId>
<groupId>org.jeasy</groupId>
<artifactId>easy-rules</artifactId>
<version>3.0.0-SNAPSHOT</version>
</parent>
<artifactId>easyrules-core</artifactId>
<artifactId>easy-rules-core</artifactId>
<packaging>jar</packaging>
<name>Easy Rules core module</name>
<description>Public API and core implementation of Easy Rules</description>
<scm>
<url>git@github.com:EasyRules/easyrules.git</url>
<connection>scm:git:git@github.com:EasyRules/easyrules.git</connection>
<developerConnection>scm:git:git@github.com:EasyRules/easyrules.git</developerConnection>
<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/EasyRules/easyrules/issues</url>
<url>https://github.com/j-easy/easy-rules/issues</url>
</issueManagement>
<ciManagement>
<system>Travis CI</system>
<url>https://travis-ci.org/EasyRules/easyrules</url>
<url>https://travis-ci.org/j-easy/easy-rules</url>
</ciManagement>
<developers>

@ -0,0 +1,47 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.annotation;
import java.lang.annotation.*;
/**
* Annotation to mark a method as a rule action.
* Must annotate any public method with no arguments.
* The method return value will be ignored by the engine.
*
* @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
*/
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Action {
/**
* The order in which the action should be executed.
* @return the order in which the action should be executed
*/
int order() default 0;
}

@ -0,0 +1,40 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.annotation;
import java.lang.annotation.*;
/**
* Annotation to mark a method as a rule condition.
* Must annotate any public method with no arguments and that returns a boolean value.
*
* @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
*/
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Condition {
}

@ -0,0 +1,39 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.annotation;
import java.lang.annotation.*;
/**
* Annotation to mark a parameter as a fact.
*
* @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
*/
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER})
public @interface Fact {
String value();
}

@ -0,0 +1,40 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.annotation;
import java.lang.annotation.*;
/**
* Annotation to mark the method to execute to get rule priority.
* Must annotate any public method with no arguments and that returns an integer value.
*
* @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
*/
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Priority {
}

@ -0,0 +1,51 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.annotation;
import java.lang.annotation.*;
/**
* Annotation to mark a class as a rule.
*
* @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
*/
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Rule {
/**
* The rule name which must be unique within an rules registry.
* @return The rule name
*/
String name() default org.jeasy.rules.api.Rule.DEFAULT_NAME;
/**
* The rule description.
* @return The rule description
*/
String description() default org.jeasy.rules.api.Rule.DEFAULT_DESCRIPTION;
}

@ -0,0 +1,27 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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 Easy Rules annotations.
*/
package org.jeasy.rules.annotation;

@ -0,0 +1,61 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.api;
import java.util.*;
import static java.lang.String.format;
public class Facts implements Iterable<Map.Entry<String, Object>> {
private Map<String, Object> facts = new HashMap<>();
public void add(String name, Object fact) {
facts.put(name, fact);
}
public void remove(String name) {
facts.remove(name);
}
public Object get(String name) {
return facts.get(name);
}
@Override
public Iterator<Map.Entry<String, Object>> iterator() {
return facts.entrySet().iterator();
}
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder("Facts {").append("\n");
for (Map.Entry<String, Object> fact : facts.entrySet()) {
stringBuilder.append(format(" Fact { %s : %s }", fact.getKey(), fact.getValue().toString()));
stringBuilder.append("\n");
}
stringBuilder.append("}");
return stringBuilder.toString();
}
}

@ -0,0 +1,80 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.api;
/**
* Abstraction for a rule that can be fired by the rules engine.
*
* Rules are registered in the rules engine registry and must have a <strong>unique</strong> name.
*
* @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
*/
public interface Rule extends Comparable<Rule> {
/**
* Default rule name.
*/
String DEFAULT_NAME = "rule";
/**
* Default rule description.
*/
String DEFAULT_DESCRIPTION = "description";
/**
* Default rule priority.
*/
int DEFAULT_PRIORITY = Integer.MAX_VALUE - 1;
/**
* Getter for rule name.
* @return the rule name
*/
String getName();
/**
* Getter for rule description.
* @return rule description
*/
String getDescription();
/**
* Getter for rule priority.
* @return rule priority
*/
int getPriority();
/**
* Rule conditions abstraction : this method encapsulates the rule's conditions.
* @return true if the rule should be applied given the provided facts, false else
*/
boolean evaluate(Facts facts);
/**
* Rule actions abstraction : this method encapsulates the rule's actions.
* @throws Exception thrown if an exception occurs during actions performing
*/
void execute(Facts facts) throws Exception;
}

@ -0,0 +1,75 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.api;
/**
* A listener for rules execution events.
*
* @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
*/
public interface RuleListener {
/**
* Triggered before the evaluation of a rule.
*
* @param rule being evaluated
* @param facts known before evaluating the rule
* @return true if the rule should be evaluated, false otherwise
*/
boolean beforeEvaluate(Rule rule, Facts facts);
/**
* Triggered after the evaluation of a rule.
*
* @param rule that has been evaluated
* @param evaluationResult true if the rule evaluated to true, false otherwise
*/
void afterEvaluate(Rule rule, boolean evaluationResult);
/**
* Triggered before the execution of a rule.
*
* @param rule the current rule
* @param rule known before executing the rule
*/
void beforeExecute(Rule rule, Facts facts);
/**
* Triggered after a rule has been executed successfully.
*
* @param rule the current rule
* @param facts known after executing the rule
*/
void onSuccess(Rule rule, Facts facts);
/**
* Triggered after a rule has failed.
*
* @param rule the current rule
* @param exception the exception thrown when attempting to execute the rule
* @param facts known after executing the rule
*/
void onFailure(Rule rule, Exception exception, Facts facts);
}

@ -0,0 +1,75 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.api;
import org.jeasy.rules.core.RuleProxy;
import java.util.Collections;
import java.util.Iterator;
import java.util.Set;
import java.util.TreeSet;
public class Rules implements Iterable<Rule> {
private Set<Rule> rules = new TreeSet<>();
public Rules(Set<Rule> rules) {
this.rules = rules;
}
public Rules(Rule... rules ) {
Collections.addAll(this.rules, rules);
}
public Rules(Object... rules ) {
for (Object rule : rules) {
this.register(RuleProxy.asRule(rule));
}
}
public void register(Object rule) {
rules.add(RuleProxy.asRule(rule));
}
public void unregister(Object rule) {
rules.remove(RuleProxy.asRule(rule));
}
public boolean isEmpty() {
return rules.isEmpty();
}
public void clear() {
rules.clear();
}
@Override
public Iterator<Rule> iterator() {
return rules.iterator();
}
public void sort() {
rules = new TreeSet<>(rules);
}
}

@ -0,0 +1,72 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.api;
import java.util.List;
import java.util.Map;
import org.jeasy.rules.core.RulesEngineParameters;
/**
* Rules engine interface.
*
* @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
*/
public interface RulesEngine {
/**
* Default engine name.
*/
String DEFAULT_NAME = "engine";
/**
* Default rule priority threshold.
*/
int DEFAULT_RULE_PRIORITY_THRESHOLD = Integer.MAX_VALUE;
/**
* Return the rules engine parameters.
*
* @return The rules engine parameters
*/
RulesEngineParameters getParameters();
/**
* Return the list of registered rule listeners.
*
* @return the list of registered rule listeners
*/
List<RuleListener> getRuleListeners();
/**
* Fire all registered rules on given facts.
*/
void fire(Rules rules, Facts facts);
/**
* Check rules without firing them.
* @return a map with the result of evaluation of each rule
*/
Map<Rule, Boolean> check(Rules rules, Facts facts);
}

@ -0,0 +1,27 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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 Easy Rules public API.
*/
package org.jeasy.rules.api;

@ -0,0 +1,83 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.core;
import java.lang.reflect.Method;
/**
* Utility class that associates an action method and its execution order.
*
* @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
*/
class ActionMethodOrderBean implements Comparable<ActionMethodOrderBean> {
private Method method;
private int order;
ActionMethodOrderBean(final Method method, final int order) {
this.method = method;
this.order = order;
}
public int getOrder() {
return order;
}
public Method getMethod() {
return method;
}
@Override
public int compareTo(final ActionMethodOrderBean actionMethodOrderBean) {
if (order < actionMethodOrderBean.getOrder()) {
return -1;
} else if (order > actionMethodOrderBean.getOrder()) {
return 1;
} else {
return method.equals(actionMethodOrderBean.getMethod()) ? 0 : 1;
}
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (!(o instanceof ActionMethodOrderBean)) return false;
ActionMethodOrderBean that = (ActionMethodOrderBean) o;
if (order != that.order) return false;
if (!method.equals(that.method)) return false;
return true;
}
@Override
public int hashCode() {
int result = method.hashCode();
result = 31 * result + order;
return result;
}
}

@ -0,0 +1,172 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.core;
import org.jeasy.rules.api.Facts;
import org.jeasy.rules.api.Rule;
/**
* Basic rule implementation class that provides common methods.
*
* You can extend this class and override {@link BasicRule#evaluate(Facts)} and {@link BasicRule#execute(Facts)} to provide rule
* conditions and actions logic.
*
* @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
*/
public class BasicRule implements Rule, Comparable<Rule> {
/**
* Rule name.
*/
protected String name;
/**
* Rule description.
*/
protected String description;
/**
* Rule priority.
*/
protected int priority;
/**
* Create a new {@link BasicRule}.
*/
public BasicRule() {
this(Rule.DEFAULT_NAME, Rule.DEFAULT_DESCRIPTION, Rule.DEFAULT_PRIORITY);
}
/**
* Create a new {@link BasicRule}.
*
* @param name rule name
*/
public BasicRule(final String name) {
this(name, Rule.DEFAULT_DESCRIPTION, Rule.DEFAULT_PRIORITY);
}
/**
* Create a new {@link BasicRule}.
*
* @param name rule name
* @param description rule description
*/
public BasicRule(final String name, final String description) {
this(name, description, Rule.DEFAULT_PRIORITY);
}
/**
* Create a new {@link BasicRule}.
*
* @param name rule name
* @param description rule description
* @param priority rule priority
*/
public BasicRule(final String name, final String description, final int priority) {
this.name = name;
this.description = description;
this.priority = priority;
}
/**
* {@inheritDoc}
*/
public boolean evaluate(Facts facts) {
return false;
}
/**
* {@inheritDoc}
*/
public void execute(Facts facts) throws Exception {
// no op
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
public void setDescription(final String description) {
this.description = description;
}
public int getPriority() {
return priority;
}
public void setPriority(final int priority) {
this.priority = priority;
}
/*
* Rules are unique according to their names within a rules engine registry.
*/
@Override
public boolean equals(final Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
BasicRule basicRule = (BasicRule) o;
if (priority != basicRule.priority)
return false;
if (!name.equals(basicRule.name))
return false;
return !(description != null ? !description.equals(basicRule.description) : basicRule.description != null);
}
@Override
public int hashCode() {
int result = name.hashCode();
result = 31 * result + (description != null ? description.hashCode() : 0);
result = 31 * result + priority;
return result;
}
@Override
public String toString() {
return name;
}
@Override
public int compareTo(final Rule rule) {
if (getPriority() < rule.getPriority()) {
return -1;
} else if (getPriority() > rule.getPriority()) {
return 1;
} else {
return getName().compareTo(rule.getName());
}
}
}

@ -0,0 +1,141 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.core;
import org.jeasy.rules.api.Facts;
import org.jeasy.rules.api.Rule;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
/**
* Class representing a composite rule composed of a set of rules.
*
* A composite rule is triggered if <strong>ALL</strong> conditions of its composing rules are satisfied.
* When a composite rule is applied, actions of <strong>ALL</strong> composing rules are performed.
*
* @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
*/
public class CompositeRule extends BasicRule {
/**
* The set of composing rules.
*/
protected Set<Rule> rules;
protected Map<Object, Rule> proxyRules;
/**
* Create a new {@link CompositeRule}.
*/
public CompositeRule() {
this(Rule.DEFAULT_NAME, Rule.DEFAULT_DESCRIPTION, Rule.DEFAULT_PRIORITY);
}
/**
* Create a new {@link CompositeRule}.
*
* @param name rule name
*/
public CompositeRule(final String name) {
this(name, Rule.DEFAULT_DESCRIPTION, Rule.DEFAULT_PRIORITY);
}
/**
* Create a new {@link CompositeRule}.
*
* @param name rule name
* @param description rule description
*/
public CompositeRule(final String name, final String description) {
this(name, description, Rule.DEFAULT_PRIORITY);
}
/**
* Create a new {@link CompositeRule}.
*
* @param name rule name
* @param description rule description
* @param priority rule priority
*/
public CompositeRule(final String name, final String description, final int priority) {
super(name, description, priority);
rules = new TreeSet<>();
proxyRules = new HashMap<>();
}
/**
* A composite rule is triggered if <strong>ALL</strong> conditions of all composing rules are evaluated to true.
* @return true if <strong>ALL</strong> conditions of composing rules are evaluated to true
*/
@Override
public boolean evaluate(Facts facts) {
if (!rules.isEmpty()) {
for (Rule rule : rules) {
if (!rule.evaluate(facts)) {
return false;
}
}
return true;
}
return false;
}
/**
* When a composite rule is applied, <strong>ALL</strong> actions of composing rules are performed
* in their natural order.
*
* @throws Exception thrown if an exception occurs during actions performing
*/
@Override
public void execute(Facts facts) throws Exception {
for (Rule rule : rules) {
rule.execute(facts);
}
}
/**
* Add a rule to the composite rule.
* @param rule the rule to add
*/
public void addRule(final Object rule) {
Rule proxy = RuleProxy.asRule(rule);
rules.add(proxy);
proxyRules.put(rule, proxy);
}
/**
* Remove a rule from the composite rule.
* @param rule the rule to remove
*/
public void removeRule(final Object rule) {
Rule proxy = proxyRules.get(rule);
if (proxy != null) {
rules.remove(proxy);
}
}
}

@ -0,0 +1,220 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.core;
import org.jeasy.rules.api.*;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import static java.lang.String.format;
/**
* Default {@link RulesEngine} implementation.
*
* This implementation handles a set of rules with unique name.
*
* Rules are fired according to their natural order which is priority by default.
*
* @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
*/
public final class DefaultRulesEngine implements RulesEngine {
private static final Logger LOGGER = Logger.getLogger(RulesEngine.class.getName());
/**
* The engine parameters
*/
private RulesEngineParameters parameters;
/**
* The registered rule listeners.
*/
private List<RuleListener> ruleListeners;
public DefaultRulesEngine() {
this.parameters = new RulesEngineParameters();
this.ruleListeners = new ArrayList<>();
}
DefaultRulesEngine(final RulesEngineParameters parameters, final List<RuleListener> ruleListeners) {
this.parameters = parameters;
this.ruleListeners = ruleListeners;
if (parameters.isSilentMode()) {
Utils.muteLoggers();
}
}
@Override
public RulesEngineParameters getParameters() {
return parameters;
}
@Override
public List<RuleListener> getRuleListeners() {
return ruleListeners;
}
@Override
public void fire(Rules rules, Facts facts) {
if (rules.isEmpty()) {
LOGGER.warning("No rules registered! Nothing to apply");
return;
}
sort(rules);
logEngineParameters();
log(rules, facts);
apply(rules, facts);
}
@Override
public Map<Rule, Boolean> check(Rules rules, Facts facts) {
LOGGER.info("Checking rules");
sort(rules);
Map<Rule, Boolean> result = new HashMap<>();
for (Rule rule : rules) {
if (shouldBeEvaluated(rule, facts)) {
result.put(rule, rule.evaluate(facts));
}
}
return result;
}
private void sort(Rules rules) {
rules.sort();
}
private void apply(Rules rules, Facts facts) {
LOGGER.info("Rules evaluation started");
for (Rule rule : rules) {
final String name = rule.getName();
final int priority = rule.getPriority();
if (priority > parameters.getPriorityThreshold()) {
LOGGER.log(Level.INFO,
"Rule priority threshold ({0}) exceeded at rule ''{1}'' with priority={2}, next rules will be skipped",
new Object[]{parameters.getPriorityThreshold(), name, priority});
break;
}
if (!shouldBeEvaluated(rule, facts)) {
LOGGER.log(Level.INFO, "Rule ''{0}'' has been skipped before being evaluated", name);
continue;
}
if (rule.evaluate(facts)) {
LOGGER.log(Level.INFO, "Rule ''{0}'' triggered", name);
triggerListenersAfterEvaluate(rule, true);
try {
triggerListenersBeforeExecute(rule, facts);
rule.execute(facts);
LOGGER.log(Level.INFO, "Rule ''{0}'' performed successfully", name);
triggerListenersOnSuccess(rule, facts);
if (parameters.isSkipOnFirstAppliedRule()) {
LOGGER.info("Next rules will be skipped since parameter skipOnFirstAppliedRule is set");
break;
}
} catch (Exception exception) {
LOGGER.log(Level.SEVERE, String.format("Rule '%s' performed with error", name), exception);
triggerListenersOnFailure(rule, exception, facts);
if (parameters.isSkipOnFirstFailedRule()) {
LOGGER.info("Next rules will be skipped since parameter skipOnFirstFailedRule is set");
break;
}
}
} else {
LOGGER.log(Level.INFO, "Rule ''{0}'' has been evaluated to false, it has not been executed", name);
triggerListenersAfterEvaluate(rule, false);
if (parameters.isSkipOnFirstNonTriggeredRule()) {
LOGGER.info("Next rules will be skipped since parameter skipOnFirstNonTriggeredRule is set");
break;
}
}
}
}
private void triggerListenersOnFailure(final Rule rule, final Exception exception, Facts facts) {
for (RuleListener ruleListener : ruleListeners) {
ruleListener.onFailure(rule, exception, facts);
}
}
private void triggerListenersOnSuccess(final Rule rule, Facts facts) {
for (RuleListener ruleListener : ruleListeners) {
ruleListener.onSuccess(rule, facts);
}
}
private void triggerListenersBeforeExecute(final Rule rule, Facts facts) {
for (RuleListener ruleListener : ruleListeners) {
ruleListener.beforeExecute(rule, facts);
}
}
private boolean triggerListenersBeforeEvaluate(Rule rule, Facts facts) {
for (RuleListener ruleListener : ruleListeners) {
if (!ruleListener.beforeEvaluate(rule, facts)) {
return false;
}
}
return true;
}
private void triggerListenersAfterEvaluate(Rule rule, boolean evaluationResult) {
for (RuleListener ruleListener : ruleListeners) {
ruleListener.afterEvaluate(rule, evaluationResult);
}
}
private boolean shouldBeEvaluated(Rule rule, Facts facts) {
return triggerListenersBeforeEvaluate(rule, facts);
}
private void logEngineParameters() {
LOGGER.log(Level.INFO, "Engine name: {0}", parameters.getName());
LOGGER.log(Level.INFO, "Rule priority threshold: {0}", parameters.getPriorityThreshold());
LOGGER.log(Level.INFO, "Skip on first applied rule: {0}", parameters.isSkipOnFirstAppliedRule());
LOGGER.log(Level.INFO, "Skip on first non triggered rule: {0}", parameters.isSkipOnFirstNonTriggeredRule());
LOGGER.log(Level.INFO, "Skip on first failed rule: {0}", parameters.isSkipOnFirstFailedRule());
}
private void log(Rules rules, Facts facts) {
LOGGER.log(Level.INFO, "Registered rules:");
for (Rule rule : rules) {
LOGGER.log(Level.INFO, format("Rule { name = '%s', description = '%s', priority = '%s'}",
rule.getName(), rule.getDescription(), rule.getPriority()));
}
LOGGER.log(Level.INFO, "Known facts:");
for (Map.Entry<String, Object> fact : facts) {
LOGGER.log(Level.INFO, format("Fact { %s : %s }", fact.getKey(), fact.getValue().toString()));
}
}
}

@ -0,0 +1,161 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.core;
import org.jeasy.rules.annotation.*;
import org.jeasy.rules.api.Facts;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.List;
import static java.lang.String.format;
/**
* Validate that an annotated rule object is well defined.
*
* @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
*/
class RuleDefinitionValidator {
void validateRuleDefinition(final Object rule) {
checkRuleClass(rule);
checkConditionMethod(rule);
checkActionMethods(rule);
checkPriorityMethod(rule);
}
private void checkRuleClass(final Object rule) {
if (!isRuleClassWellDefined(rule)) {
throw new IllegalArgumentException(format("Rule '%s' is not annotated with '%s'", rule.getClass().getName(), Rule.class.getName()));
}
}
private void checkConditionMethod(final Object rule) {
List<Method> conditionMethods = getMethodsAnnotatedWith(Condition.class, rule);
if (conditionMethods.isEmpty()) {
throw new IllegalArgumentException(format("Rule '%s' must have a public method annotated with '%s'", rule.getClass().getName(), Condition.class.getName()));
}
if (conditionMethods.size() > 1) {
throw new IllegalArgumentException(format("Rule '%s' must have exactly one method annotated with '%s'", rule.getClass().getName(), Condition.class.getName()));
}
Method conditionMethod = conditionMethods.get(0);
if (!isConditionMethodWellDefined(conditionMethod)) {
throw new IllegalArgumentException(format("Condition method '%s' defined in rule '%s' must be public, may have parameters annotated with @Fact (and/or a parameter of type Facts) and return boolean type.", conditionMethod, rule.getClass().getName()));
}
}
private void checkActionMethods(final Object rule) {
List<Method> actionMethods = getMethodsAnnotatedWith(Action.class, rule);
if (actionMethods.isEmpty()) {
throw new IllegalArgumentException(format("Rule '%s' must have at least one public method annotated with '%s'", rule.getClass().getName(), Action.class.getName()));
}
for (Method actionMethod : actionMethods) {
if (!isActionMethodWellDefined(actionMethod)) {
throw new IllegalArgumentException(format("Action method '%s' defined in rule '%s' must be public and may have parameters annotated with @Fact (and/or a parameter of type Facts).", actionMethod, rule.getClass().getName()));
}
}
}
private void checkPriorityMethod(final Object rule) {
List<Method> priorityMethods = getMethodsAnnotatedWith(Priority.class, rule);
if (priorityMethods.isEmpty()) {
return;
}
if (priorityMethods.size() > 1) {
throw new IllegalArgumentException(format("Rule '%s' must have exactly one method annotated with '%s'", rule.getClass().getName(), Priority.class.getName()));
}
Method priorityMethod = priorityMethods.get(0);
if (!isPriorityMethodWellDefined(priorityMethod)) {
throw new IllegalArgumentException(format("Priority method '%s' defined in rule '%s' must be public, have no parameters and return integer type.", priorityMethod, rule.getClass().getName()));
}
}
private boolean isRuleClassWellDefined(final Object rule) {
return Utils.isAnnotationPresent(Rule.class, rule.getClass());
}
private boolean isConditionMethodWellDefined(final Method method) {
Parameter[] parameters = method.getParameters();
return Modifier.isPublic(method.getModifiers())
&& method.getReturnType().equals(Boolean.TYPE)
&& validParameters(parameters);
}
private boolean validParameters(Parameter[] parameters) {
List<Parameter> notAnnotatedParams = new ArrayList<>();
for (Parameter parameter : parameters) {
if (parameter.getAnnotation(Fact.class) == null) {
notAnnotatedParams.add(parameter);
}
}
if (notAnnotatedParams.size() > 1) {
return false;
} else if (notAnnotatedParams.size() == 1) {
Parameter parameter = notAnnotatedParams.get(0);
return parameter.getType().isAssignableFrom(Facts.class);
}
return true;
}
private boolean isActionMethodWellDefined(final Method method) {
Parameter[] parameters = method.getParameters();
return Modifier.isPublic(method.getModifiers())
&& validParameters(parameters);
}
private boolean isPriorityMethodWellDefined(final Method method) {
return Modifier.isPublic(method.getModifiers())
&& method.getReturnType().equals(Integer.TYPE)
&& method.getParameterTypes().length == 0;
}
private List<Method> getMethodsAnnotatedWith(final Class<? extends Annotation> annotation, final Object rule) {
Method[] methods = getMethods(rule);
List<Method> annotatedMethods = new ArrayList<>();
for (Method method : methods) {
if (method.isAnnotationPresent(annotation)) {
annotatedMethods.add(method);
}
}
return annotatedMethods;
}
private Method[] getMethods(final Object rule) {
return rule.getClass().getMethods();
}
}

@ -0,0 +1,245 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.core;
import org.jeasy.rules.annotation.Fact;
import org.jeasy.rules.api.Facts;
import org.jeasy.rules.annotation.Action;
import org.jeasy.rules.annotation.Condition;
import org.jeasy.rules.annotation.Priority;
import org.jeasy.rules.api.Rule;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Proxy;
import java.util.*;
import java.util.logging.Logger;
public class RuleProxy implements InvocationHandler {
private static final Logger LOGGER = Logger.getLogger(RuleProxy.class.getName());
private Object target;
private static RuleDefinitionValidator ruleDefinitionValidator = new RuleDefinitionValidator();
private RuleProxy(final Object target) {
this.target = target;
}
/**
* Makes the rule object implement the {@link Rule} interface.
*
* @param rule the annotated rule object.
* @return a proxy that implements the {@link Rule} interface.
*/
public static Rule asRule(final Object rule) {
Rule result;
if (Utils.getInterfaces(rule).contains(Rule.class)) {
result = (Rule) rule;
} else {
ruleDefinitionValidator.validateRuleDefinition(rule);
result = (Rule) Proxy.newProxyInstance(
Rule.class.getClassLoader(),
new Class[]{Rule.class, Comparable.class},
new RuleProxy(rule));
}
return result;
}
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
String methodName = method.getName();
if (methodName.equals("getName")) {
return getRuleName();
}
if (methodName.equals("getDescription")) {
return getRuleDescription();
}
if (methodName.equals("getPriority")) {
return getRulePriority();
}
if (methodName.equals("evaluate")) {
Facts facts = (Facts) args[0];
Method conditionMethod = getConditionMethod();
List<Object> actualParameters = getActualParameters(conditionMethod, facts);
return conditionMethod.invoke(target, actualParameters.toArray()); // validated upfront
}
if (methodName.equals("execute")) {
for (ActionMethodOrderBean actionMethodBean : getActionMethodBeans()) {
Facts facts = (Facts) args[0];
Method actiomMethod = actionMethodBean.getMethod();
List<Object> actualParameters = getActualParameters(actiomMethod, facts);
actiomMethod.invoke(target, actualParameters.toArray());
}
}
if (methodName.equals("equals")) {
return target.equals(args[0]);
}
if (methodName.equals("hashCode")) {
return target.hashCode();
}
if (methodName.equals("toString")) {
return target.toString();
}
if (methodName.equals("compareTo")) {
Method compareToMethod = getCompareToMethod();
if (compareToMethod != null) {
return compareToMethod.invoke(target, args);
} else {
Rule otherRule = (Rule) args[0];
return compareTo(otherRule);
}
}
return null;
}
private List<Object> getActualParameters(Method method, Facts facts) {
Parameter[] parameters = method.getParameters(); // validated upfront
List<Object> actualParameters = new ArrayList<>();
for (Parameter parameter : parameters) {
Fact annotation = parameter.getAnnotation(Fact.class); // validated upfront
if (annotation == null) { // validated upfront, there may be only one parameter not annotated and which is of type Facts.class
actualParameters.add(facts);
} else {
String factName = annotation.value();
Object fact = facts.get(factName);
if (fact == null) {
throw new RuntimeException(String.format("No fact named %s found in known facts", factName));
}
actualParameters.add(fact);
}
}
return actualParameters;
}
private int compareTo(final Rule otherRule) throws Exception {
String otherName = otherRule.getName();
int otherPriority = otherRule.getPriority();
String name = getRuleName();
int priority = getRulePriority();
if (priority < otherPriority) {
return -1;
} else if (priority > otherPriority) {
return 1;
} else {
return name.compareTo(otherName);
}
}
private int getRulePriority() throws Exception {
int priority = Rule.DEFAULT_PRIORITY;
Method[] methods = getMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(Priority.class)) {
priority = (Integer) method.invoke(target);
break;
}
}
return priority;
}
private Method getConditionMethod() {
Method[] methods = getMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(Condition.class)) {
return method;
}
}
return null;
}
private Set<ActionMethodOrderBean> getActionMethodBeans() {
Method[] methods = getMethods();
Set<ActionMethodOrderBean> actionMethodBeans = new TreeSet<>();
for (Method method : methods) {
if (method.isAnnotationPresent(Action.class)) {
Action actionAnnotation = method.getAnnotation(Action.class);
int order = actionAnnotation.order();
actionMethodBeans.add(new ActionMethodOrderBean(method, order));
}
}
return actionMethodBeans;
}
private Method getCompareToMethod() {
Method[] methods = getMethods();
for (Method method : methods) {
if (method.getName().equals("compareTo")) {
return method;
}
}
return null;
}
private Method[] getMethods() {
return getTargetClass().getMethods();
}
private org.jeasy.rules.annotation.Rule getRuleAnnotation() {
return Utils.findAnnotation(org.jeasy.rules.annotation.Rule.class, getTargetClass());
}
private String getRuleName() {
org.jeasy.rules.annotation.Rule rule = getRuleAnnotation();
return rule.name().equals(Rule.DEFAULT_NAME) ? getTargetClass().getSimpleName() : rule.name();
}
private String getRuleDescription() {
// Default description = "when " + conditionMethodName + " then " + comma separated actionMethodsNames
StringBuilder description = new StringBuilder();
appendConditionMethodName(description);
appendActionMethodsNames(description);
org.jeasy.rules.annotation.Rule rule = getRuleAnnotation();
return rule.description().equals(Rule.DEFAULT_DESCRIPTION) ? description.toString() : rule.description();
}
private void appendConditionMethodName(StringBuilder description) {
Method method = getConditionMethod();
if (method != null) {
description.append("when ");
description.append(method.getName());
description.append(" then ");
}
}
private void appendActionMethodsNames(StringBuilder description) {
Iterator<ActionMethodOrderBean> iterator = getActionMethodBeans().iterator();
while (iterator.hasNext()) {
description.append(iterator.next().getMethod().getName());
if (iterator.hasNext()) {
description.append(",");
}
}
}
private Class<?> getTargetClass() {
return target.getClass();
}
}

@ -0,0 +1,91 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.core;
import org.jeasy.rules.api.RuleListener;
import org.jeasy.rules.api.RulesEngine;
import java.util.ArrayList;
import java.util.List;
/**
* Builder for rules engine instances.
*
* @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
*/
public class RulesEngineBuilder {
private RulesEngineParameters parameters;
private List<RuleListener> ruleListeners;
public static RulesEngineBuilder aNewRulesEngine() {
return new RulesEngineBuilder();
}
private RulesEngineBuilder() {
parameters = new RulesEngineParameters(RulesEngine.DEFAULT_NAME, false, false, RulesEngine.DEFAULT_RULE_PRIORITY_THRESHOLD, false);
ruleListeners = new ArrayList<>();
}
public RulesEngineBuilder named(final String name) {
parameters.setName(name);
return this;
}
public RulesEngineBuilder withSkipOnFirstAppliedRule(final boolean skipOnFirstAppliedRule) {
parameters.setSkipOnFirstAppliedRule(skipOnFirstAppliedRule);
return this;
}
public RulesEngineBuilder withSkipOnFirstNonTriggeredRule(final boolean skipOnFirstNonTriggeredRule) {
parameters.setSkipOnFirstNonTriggeredRule(skipOnFirstNonTriggeredRule);
return this;
}
public RulesEngineBuilder withSkipOnFirstFailedRule(final boolean skipOnFirstFailedRule) {
parameters.setSkipOnFirstFailedRule(skipOnFirstFailedRule);
return this;
}
public RulesEngineBuilder withRulePriorityThreshold(final int priorityThreshold) {
parameters.setPriorityThreshold(priorityThreshold);
return this;
}
public RulesEngineBuilder withRuleListener(final RuleListener ruleListener) {
this.ruleListeners.add(ruleListener);
return this;
}
public RulesEngineBuilder withSilentMode(final boolean silentMode) {
parameters.setSilentMode(silentMode);
return this;
}
public RulesEngine build() {
return new DefaultRulesEngine(parameters, ruleListeners);
}
}

@ -0,0 +1,125 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.core;
import org.jeasy.rules.api.RulesEngine;
/**
* Parameters of the rules engine.
*
* @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
*/
public class RulesEngineParameters {
/**
* The engine name.
*/
protected String name;
/**
* Parameter to skip next applicable rules when a rule is applied.
*/
private boolean skipOnFirstAppliedRule;
/**
* Parameter to skip next applicable rules when a rule is non triggered
*/
private boolean skipOnFirstNonTriggeredRule;
/**
* Parameter to skip next applicable rules when a rule has failed.
*/
private boolean skipOnFirstFailedRule;
/**
* Parameter to skip next rules if priority exceeds a user defined threshold.
*/
private int priorityThreshold;
/**
* Parameter to mute loggers.
*/
private boolean silentMode;
public RulesEngineParameters() {
this.name = RulesEngine.DEFAULT_NAME;
this.priorityThreshold = RulesEngine.DEFAULT_RULE_PRIORITY_THRESHOLD;
}
public RulesEngineParameters(String name, boolean skipOnFirstAppliedRule, boolean skipOnFirstFailedRule, int priorityThreshold, boolean silentMode) {
this.name = name;
this.skipOnFirstAppliedRule = skipOnFirstAppliedRule;
this.skipOnFirstFailedRule = skipOnFirstFailedRule;
this.priorityThreshold = priorityThreshold;
this.silentMode = silentMode;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getPriorityThreshold() {
return priorityThreshold;
}
public void setPriorityThreshold(int priorityThreshold) {
this.priorityThreshold = priorityThreshold;
}
public boolean isSilentMode() {
return silentMode;
}
public void setSilentMode(boolean silentMode) {
this.silentMode = silentMode;
}
public boolean isSkipOnFirstAppliedRule() {
return skipOnFirstAppliedRule;
}
public void setSkipOnFirstAppliedRule(boolean skipOnFirstAppliedRule) {
this.skipOnFirstAppliedRule = skipOnFirstAppliedRule;
}
public boolean isSkipOnFirstNonTriggeredRule() {
return skipOnFirstNonTriggeredRule;
}
public void setSkipOnFirstNonTriggeredRule(boolean skipOnFirstNonTriggeredRule) {
this.skipOnFirstNonTriggeredRule = skipOnFirstNonTriggeredRule;
}
public boolean isSkipOnFirstFailedRule() {
return skipOnFirstFailedRule;
}
public void setSkipOnFirstFailedRule(boolean skipOnFirstFailedRule) {
this.skipOnFirstFailedRule = skipOnFirstFailedRule;
}
}

@ -0,0 +1,109 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.core;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;
import static java.util.Arrays.asList;
/**
* Utilities class.
*
* @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
*/
final class Utils {
private static final Logger LOGGER = Logger.getLogger(Utils.class.getName());
static {
try {
if (System.getProperty("java.util.logging.config.file") == null &&
System.getProperty("java.util.logging.config.class") == null) {
LogManager.getLogManager().readConfiguration(Utils.class.getResourceAsStream("/logging.properties"));
}
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Unable to load logging configuration file", e);
}
}
private Utils() {
}
static void muteLoggers() {
Enumeration<String> loggerNames = LogManager.getLogManager().getLoggerNames();
while (loggerNames.hasMoreElements()) {
String loggerName = loggerNames.nextElement();
if (loggerName.startsWith("org.easyrules")) {
muteLogger(loggerName);
}
}
}
private static void muteLogger(final String logger) {
Logger.getLogger(logger).setUseParentHandlers(false);
Handler[] handlers = Logger.getLogger(logger).getHandlers();
for (Handler handler : handlers) {
Logger.getLogger(logger).removeHandler(handler);
}
}
static List<Class<?>> getInterfaces(final Object rule) {
List<Class<?>> interfaces = new ArrayList<>();
Class<?> clazz = rule.getClass();
while (clazz.getSuperclass() != null) {
interfaces.addAll(asList(clazz.getInterfaces()));
clazz = clazz.getSuperclass();
}
return interfaces;
}
static <A extends Annotation> A findAnnotation(final Class<A> targetAnnotation, final Class<?> annotatedType) {
A foundAnnotation = annotatedType.getAnnotation(targetAnnotation);
if (foundAnnotation == null) {
for (Annotation annotation : annotatedType.getAnnotations()) {
Class<? extends Annotation> annotationType = annotation.annotationType();
if (annotationType.isAnnotationPresent(targetAnnotation)) {
foundAnnotation = annotationType.getAnnotation(targetAnnotation);
break;
}
}
}
return foundAnnotation;
}
static boolean isAnnotationPresent(final Class<? extends Annotation> targetAnnotation, final Class<?> annotatedType) {
return findAnnotation(targetAnnotation, annotatedType) != null;
}
}

@ -0,0 +1,27 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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 Easy Rules core implementation.
*/
package org.jeasy.rules.core;

@ -0,0 +1,54 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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;
import junit.framework.TestSuite;
import org.jeasy.rules.core.*;
import org.junit.runner.RunWith;
import org.junit.runners.Suite;
/**
* Test suite for Easy Rules core module.
*
* @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
*/
@RunWith(Suite.class)
@Suite.SuiteClasses({
AnnotationInheritanceTest.class,
BasicRuleTest.class,
RulePriorityThresholdTest.class,
SkipOnFirstAppliedRuleTest.class,
SkipOnFirstFailedRuleTest.class,
SkipOnFirstNonTriggeredRuleTest.class,
RuleListenerTest.class,
CustomRuleOrderingTest.class,
RuleProxyTest.class,
RulesEngineBuilderTest.class,
CompositeRuleTest.class,
RuleDefinitionValidatorTest.class,
DefaultRulesEngineTest.class,
UtilsTest.class})
public class EasyRulesTestSuite extends TestSuite {
}

@ -0,0 +1,47 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.annotation;
@Rule
public class AnnotatedRuleWithActionMethodHavingOneArgumentNotOfTypeFacts {
private boolean executed;
@Condition
public boolean when() {
return true;
}
@Action
public void then(int i) throws Exception {
if (i == 1) {
executed = true;
}
}
public boolean isExecuted() {
return executed;
}
}

@ -0,0 +1,45 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.annotation;
@Rule
public class AnnotatedRuleWithConditionMethodHavingNonBooleanReturnType {
private boolean executed;
@Condition
public int when() {
return 0;
}
@Action
public void then() throws Exception {
executed = true;
}
public boolean isExecuted() {
return executed;
}
}

@ -0,0 +1,45 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.annotation;
@Rule
public class AnnotatedRuleWithConditionMethodHavingOneArgumentNotOfTypeFacts {
private boolean executed;
@Condition
public boolean when(int i) {
return i == 0;
}
@Action
public void then() throws Exception {
executed = true;
}
public boolean isExecuted() {
return executed;
}
}

@ -0,0 +1,37 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.annotation;
@MetaRule
public class AnnotatedRuleWithMetaRuleAnnotation {
@Condition
public boolean when() {
return true;
}
@Action
public void then() throws Exception {
}
}

@ -0,0 +1,55 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.annotation;
@Rule
public class AnnotatedRuleWithMoreThanOnePriorityMethod {
private boolean executed;
@Condition
public boolean when() {
return true;
}
@Action
public void then() throws Exception {
executed = true;
}
@Priority
public int getPriority() {
return 0;
}
@Priority
public int getRulePriority() {
return 1;
}
public boolean isExecuted() {
return executed;
}
}

@ -0,0 +1,45 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.annotation;
@Rule
public class AnnotatedRuleWithNotPublicActionMethod {
private boolean executed;
@Condition
private boolean when() {
return true;
}
@Action
private void then() throws Exception {
executed = true;
}
public boolean isExecuted() {
return executed;
}
}

@ -0,0 +1,45 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.annotation;
@Rule
public class AnnotatedRuleWithNotPublicConditionMethod {
private boolean executed;
@Condition
private boolean when() {
return true;
}
@Action
public void then() throws Exception {
executed = true;
}
public boolean isExecuted() {
return executed;
}
}

@ -0,0 +1,50 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.annotation;
@Rule
public class AnnotatedRuleWithNotPublicPriorityMethod {
private boolean executed;
@Condition
private boolean when() {
return true;
}
@Action
private void then() throws Exception {
executed = true;
}
public boolean isExecuted() {
return executed;
}
@Priority
private int getPriority() {
return 1;
}
}

@ -0,0 +1,50 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.annotation;
@Rule
public class AnnotatedRuleWithPriorityMethodHavingArguments {
private boolean executed;
@Condition
private boolean when() {
return true;
}
@Action
private void then() throws Exception {
executed = true;
}
public boolean isExecuted() {
return executed;
}
@Priority
public int getPriority(int i) {
return i;
}
}

@ -0,0 +1,50 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.annotation;
@Rule
public class AnnotatedRuleWithPriorityMethodHavingNonIntegerReturnType {
private boolean executed;
@Condition
private boolean when() {
return true;
}
@Action
private void then() throws Exception {
executed = true;
}
public boolean isExecuted() {
return executed;
}
@Priority
public String getPriority() {
return "1";
}
}

@ -0,0 +1,44 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.annotation;
@Rule
public class AnnotatedRuleWithoutActionMethod {
private boolean executed;
@Condition
public boolean when() {
return true;
}
public void then() throws Exception {
executed = true;
}
public boolean isExecuted() {
return executed;
}
}

@ -0,0 +1,44 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.annotation;
@Rule
public class AnnotatedRuleWithoutCondition {
private boolean executed;
public boolean when() {
return true;
}
@Action
public void then() throws Exception {
executed = true;
}
public boolean isExecuted() {
return executed;
}
}

@ -0,0 +1,35 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Rule
public @interface MetaRule {
}

@ -0,0 +1,58 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.core;
import org.jeasy.rules.api.Facts;
import org.jeasy.rules.api.Rule;
import org.jeasy.rules.api.Rules;
import org.jeasy.rules.api.RulesEngine;
import org.junit.Before;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public abstract class AbstractTest {
@Mock
protected Rule rule1, rule2;
@Mock
protected Object fact1, fact2;
protected Facts facts;
protected Rules rules;
protected RulesEngine rulesEngine;
@Before
public void setup() throws Exception {
facts = new Facts();
facts.add("fact1", fact1);
facts.add("fact2", fact2);
rules = new Rules(rule1, rule2);
rulesEngine = RulesEngineBuilder.aNewRulesEngine().build();
}
}

@ -0,0 +1,83 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.core;
import org.jeasy.rules.annotation.Action;
import org.jeasy.rules.annotation.Condition;
import org.jeasy.rules.annotation.Fact;
import org.jeasy.rules.annotation.Rule;
import org.jeasy.rules.api.Facts;
import org.jeasy.rules.api.Rules;
import org.jeasy.rules.api.RulesEngine;
import org.junit.Test;
public class AnnotatedRulesTest {
@Test
public void test() throws Exception {
Facts facts = new Facts();
facts.add("rain", true);
facts.add("age", 18);
Rules rules = new Rules(
new WeatherRule(),
new AgeRule()
);
RulesEngine rulesEngine = new DefaultRulesEngine();
rulesEngine.fire(rules, facts);
}
@Rule
class AgeRule {
@Condition
public boolean isAdult(@Fact("age") int age) {
return age >= 18;
}
@Action
public void then() {
System.out.println("You are an adult");
}
}
@Rule
class WeatherRule {
@Condition
public boolean itRains(@Fact("rain") boolean rain) {
return rain;
}
@Action
public void takeAnUmbrella(Facts facts) {
System.out.println("It rains, take an umbrella!");
}
}
}

@ -0,0 +1,68 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.core;
import org.jeasy.rules.annotation.Action;
import org.jeasy.rules.annotation.Condition;
import org.jeasy.rules.api.Facts;
import org.jeasy.rules.api.Rules;
import org.jeasy.rules.api.RulesEngine;
import org.jeasy.rules.annotation.Rule;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class AnnotationInheritanceTest {
@Test
public void annotationsShouldBeInherited() throws Exception {
MyChildRule myChildRule = new MyChildRule();
RulesEngine rulesEngine = RulesEngineBuilder.aNewRulesEngine().build();
Rules rules = new Rules();
rules.register(myChildRule);
rulesEngine.fire(rules, new Facts());
assertThat(myChildRule.isExecuted()).isTrue();
}
@Rule
class MyBaseRule {
protected boolean executed;
@Condition
public boolean when() {
return true;
}
@Action
public void then() {
executed = true;
}
public boolean isExecuted() {
return executed;
}
}
class MyChildRule extends MyBaseRule {
}
}

@ -0,0 +1,112 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.core;
import org.jeasy.rules.api.Facts;
import org.jeasy.rules.api.Rules;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
public class BasicRuleTest extends AbstractTest {
@Test
public void basicRuleEvaluateShouldReturnFalse() throws Exception {
BasicRule basicRule = new BasicRule();
assertThat(basicRule.evaluate(facts)).isFalse();
}
@Test
public void testCompareTo() {
FirstRule rule1 = new FirstRule();
FirstRule rule2 = new FirstRule();
assertThat(rule1.compareTo(rule2)).isEqualTo(0);
assertThat(rule2.compareTo(rule1)).isEqualTo(0);
}
@Test
public void testSortSequence() {
FirstRule rule1 = new FirstRule();
SecondRule rule2 = new SecondRule();
ThirdRule rule3 = new ThirdRule();
rules = new Rules(rule1, rule2, rule3);
rulesEngine.check(rules, facts);
assertThat(rules).containsSequence(rule1, rule3, rule2);
}
class FirstRule extends BasicRule {
@Override
public int getPriority() {
return 1;
}
@Override
public boolean evaluate(Facts facts) {
return true;
}
@Override
public String getName() {
return "rule1";
}
}
class SecondRule extends BasicRule {
@Override
public int getPriority() {
return 3;
}
@Override
public boolean evaluate(Facts facts) {
return true;
}
@Override
public String getName() {
return "rule2";
}
}
class ThirdRule extends BasicRule {
@Override
public int getPriority() {
return 2;
}
@Override
public boolean evaluate(Facts facts) {
return true;
}
@Override
public String getName() {
return "rule3";
}
}
}

@ -0,0 +1,171 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.core;
import org.jeasy.rules.annotation.Action;
import org.jeasy.rules.annotation.Condition;
import org.jeasy.rules.annotation.Rule;
import org.junit.Before;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.*;
/**
* Test class for composite rule execution.
*
* @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
*/
public class CompositeRuleTest extends AbstractTest {
private CompositeRule compositeRule;
@Before
public void setup() throws Exception {
super.setup();
when(rule1.evaluate(facts)).thenReturn(true);
when(rule2.evaluate(facts)).thenReturn(true);
when(rule2.compareTo(rule1)).thenReturn(1);
compositeRule = new CompositeRule();
}
@Test
public void compositeRuleAndComposingRulesMustBeExecuted() throws Exception {
compositeRule.addRule(rule1);
compositeRule.addRule(rule2);
rules.clear(); // FIXME
rules.register(compositeRule);
rulesEngine.fire(rules, facts);
verify(rule1).execute(facts);
verify(rule2).execute(facts);
}
@Test
public void compositeRuleMustNotBeExecutedIfAComposingRuleEvaluatesToFalse() throws Exception {
when(rule2.evaluate(facts)).thenReturn(false);
compositeRule.addRule(rule1);
compositeRule.addRule(rule2);
rules.clear(); // FIXME
rules.register(compositeRule);
rulesEngine.fire(rules, facts);
/*
* The composing rules should not be executed
* since not all rules conditions evaluate to TRUE
*/
//Rule 1 should not be executed
verify(rule1, never()).execute(facts);
//Rule 2 should not be executed
verify(rule2, never()).execute(facts);
}
@Test
public void whenARuleIsRemoved_thenItShouldNotBeEvaluated() throws Exception {
compositeRule.addRule(rule1);
compositeRule.addRule(rule2);
compositeRule.removeRule(rule2);
rules.clear(); // FIXME
rules.register(compositeRule);
rulesEngine.fire(rules, facts);
//Rule 1 should be executed
verify(rule1).execute(facts);
//Rule 2 should not be evaluated nor executed
verify(rule2, never()).evaluate(facts);
verify(rule2, never()).execute(facts);
}
@Test
public void whenNoComposingRulesAreRegistered_thenCompositeRuleShouldEvaluateToFalse() {
assertThat(compositeRule.evaluate(facts)).isFalse();
}
@Test
public void testCompositeRuleWithAnnotatedComposingRules() throws Exception {
MyRule rule = new MyRule();
compositeRule = new CompositeRule("myCompositeRule");
compositeRule.addRule(rule);
rules.register(compositeRule);;
rulesEngine.fire(rules, facts);
assertThat(rule.isExecuted()).isTrue();
}
@Test
public void whenAnnotatedRuleIsRemoved_thenItsProxyShouldBeRetrieved() throws Exception {
MyRule rule = new MyRule();
MyAnnotatedRule annotatedRule = new MyAnnotatedRule();
compositeRule = new CompositeRule("myCompositeRule", "composite rule with mixed types of rules");
compositeRule.addRule(rule);
compositeRule.addRule(annotatedRule);
compositeRule.removeRule(annotatedRule);
rules.register(compositeRule);
rulesEngine.fire(rules, facts);
assertThat(rule.isExecuted()).isTrue();
assertThat(annotatedRule.isExecuted()).isFalse();
}
@Rule
class MyRule {
boolean executed;
@Condition
public boolean when() {
return true;
}
@Action
public void then() {
executed = true;
}
public boolean isExecuted() {
return executed;
}
}
@Rule
static class MyAnnotatedRule {
private boolean executed;
@Condition
public boolean evaluate() {
return true;
}
@Action
public void execute() {
executed = true;
}
public boolean isExecuted() {
return executed;
}
}
}

@ -0,0 +1,84 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.core;
import org.jeasy.rules.api.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.when;
/**
* Test class for custom rule ordering.
*
* @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
*/
@RunWith(MockitoJUnitRunner.class)
public class CustomRuleOrderingTest extends AbstractTest {
@Mock
private MyRule rule1, rule2;
@Test
public void whenCompareToIsOverridden_thenShouldExecuteRulesInTheCustomOrder() throws Exception {
when(rule1.getName()).thenReturn("a");
when(rule1.getPriority()).thenReturn(1);
when(rule1.evaluate(facts)).thenReturn(true);
when(rule2.getName()).thenReturn("b");
when(rule2.getPriority()).thenReturn(0);
when(rule2.evaluate(facts)).thenReturn(true);
when(rule2.compareTo(rule1)).thenCallRealMethod();
rules.clear();
rules.register(rule1);
rules.register(rule2);
rulesEngine.fire(rules, facts);
/*
* By default, if compareTo is not overridden, then rule2 should be executed first (priority 0 < 1).
* But in this case, the compareTo method order rules by their name, so rule1 should be executed first ("a" < "b")
*/
InOrder inOrder = inOrder(rule1, rule2);
inOrder.verify(rule1).execute(facts);
inOrder.verify(rule2).execute(facts);
}
class MyRule extends BasicRule {
@Override
public int compareTo(Rule rule) {
return getName().compareTo(rule.getName());
}
}
}

@ -0,0 +1,266 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.core;
import org.jeasy.rules.annotation.Action;
import org.jeasy.rules.annotation.Condition;
import org.jeasy.rules.annotation.Priority;
import org.jeasy.rules.api.RuleListener;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.*;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.*;
/**
* Test class for {@link DefaultRulesEngine}.
*
* @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
*/
public class DefaultRulesEngineTest extends AbstractTest {
@Mock
private BasicRule rule, anotherRule;
@Mock
private RuleListener ruleListener;
private AnnotatedRule annotatedRule;
@Before
public void setup() throws Exception {
super.setup();
when(rule.getName()).thenReturn("r");
when(rule.getDescription()).thenReturn("d");
when(rule.getPriority()).thenReturn(1);
annotatedRule = new AnnotatedRule();
}
@Test
public void whenConditionIsTrue_thenActionShouldBeExecuted() throws Exception {
when(rule.evaluate(facts)).thenReturn(true);
rules.clear();// FIXME
rules.register(rule);
rulesEngine.fire(rules, facts);
verify(rule).execute(facts);
}
@Test
public void whenConditionIsFalse_thenActionShouldNotBeExecuted() throws Exception {
when(rule.evaluate(facts)).thenReturn(false);
rules.clear();// FIXME
rules.register(rule);
rulesEngine.fire(rules, facts);
verify(rule, never()).execute(facts);
}
@Test
public void rulesMustBeTriggeredInTheirNaturalOrder() throws Exception {
when(rule.evaluate(facts)).thenReturn(true);
when(anotherRule.evaluate(facts)).thenReturn(true);
when(anotherRule.compareTo(rule)).thenReturn(1);
rules.clear();// FIXME
rules.register(rule);
rules.register(anotherRule);
rulesEngine.fire(rules, facts);
InOrder inOrder = inOrder(rule, anotherRule);
inOrder.verify(rule).execute(facts);
inOrder.verify(anotherRule).execute(facts);
}
@Test
public void rulesMustBeCheckedInTheirNaturalOrder() throws Exception {
when(rule.evaluate(facts)).thenReturn(true);
when(anotherRule.evaluate(facts)).thenReturn(true);
when(anotherRule.compareTo(rule)).thenReturn(1);
rules.clear();// FIXME
rules.register(rule);
rules.register(anotherRule);
rulesEngine.check(rules, facts);
InOrder inOrder = inOrder(rule, anotherRule);
inOrder.verify(rule).evaluate(facts);
inOrder.verify(anotherRule).evaluate(facts);
}
@Test
public void actionsMustBeExecutedInTheDefinedOrder() {
rules.clear(); // FIXME
rules.register(annotatedRule);
rulesEngine.fire(rules, facts);
assertEquals("012", annotatedRule.getActionSequence());
}
@Test
public void annotatedRulesAndNonAnnotatedRulesShouldBeUsableTogether() throws Exception {
when(rule.evaluate(facts)).thenReturn(true);
rules.clear(); // FIXME
rules.register(rule);
rules.register(annotatedRule);
rulesEngine.fire(rules, facts);
verify(rule).execute(facts);
assertThat(annotatedRule.isExecuted()).isTrue();
}
@Test
public void whenRuleNameIsNotSpecified_thenItShouldBeEqualToClassNameByDefault() throws Exception {
org.jeasy.rules.api.Rule rule = RuleProxy.asRule(new DummyRule());
assertThat(rule.getName()).isEqualTo("DummyRule");
}
@Test
public void whenRuleDescriptionIsNotSpecified_thenItShouldBeEqualToConditionNameFollowedByActionsNames() throws Exception {
org.jeasy.rules.api.Rule rule = RuleProxy.asRule(new DummyRule());
assertThat(rule.getDescription()).isEqualTo("when condition then action1,action2");
}
@Test
public void testCheckRules() throws Exception {
// Given
when(rule.evaluate(facts)).thenReturn(true);
rules.clear(); // FIXME
rules.register(rule);
rules.register(annotatedRule);
// When
Map<org.jeasy.rules.api.Rule, Boolean> result = rulesEngine.check(rules, facts);
// Then
assertThat(result).hasSize(2);
for (org.jeasy.rules.api.Rule r : rules) {
assertThat(result.get(r)).isTrue();
}
}
@Test
public void listenerShouldBeInvokedBeforeCheckingRules() throws Exception {
// Given
when(rule.evaluate(facts)).thenReturn(true);
when(ruleListener.beforeEvaluate(rule, facts)).thenReturn(true);
rulesEngine = RulesEngineBuilder.aNewRulesEngine()
.withRuleListener(ruleListener)
.build();
rules.clear(); // FIXME
rules.register(rule);
// When
rulesEngine.check(rules, facts);
// Then
verify(ruleListener).beforeEvaluate(rule, facts);
}
@Test
public void testGetRuleListeners() throws Exception {
rulesEngine = RulesEngineBuilder.aNewRulesEngine()
.withRuleListener(ruleListener)
.build();
assertThat(rulesEngine.getRuleListeners())
.containsExactly(ruleListener);
}
@After
public void clearRules() {
rules.clear();
}
@org.jeasy.rules.annotation.Rule(name = "myRule", description = "my rule description")
public class AnnotatedRule {
private boolean executed;
private String actionSequence = "";
@Condition
public boolean when() {
return true;
}
@Action
public void then0() throws Exception {
actionSequence += "0";
}
@Action(order = 1)
public void then1() throws Exception {
actionSequence += "1";
}
@Action(order = 2)
public void then2() throws Exception {
actionSequence += "2";
executed = true;
}
@Priority
public int getPriority() {
return 0;
}
public boolean isExecuted() {
return executed;
}
public String getActionSequence() {
return actionSequence;
}
}
@org.jeasy.rules.annotation.Rule
public class DummyRule {
@Condition
public boolean condition() {
return true;
}
@Action(order = 1)
public void action1() throws Exception {
// no op
}
@Action(order = 2)
public void action2() throws Exception {
// no op
}
}
}

@ -0,0 +1,117 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.core;
import org.jeasy.rules.annotation.*;
import org.junit.Before;
import org.junit.Test;
public class RuleDefinitionValidatorTest {
private RuleDefinitionValidator ruleDefinitionValidator;
@Before
public void setup(){
ruleDefinitionValidator = new RuleDefinitionValidator();
}
/*
* Rule annotation test
*/
@Test(expected = IllegalArgumentException.class)
public void notAnnotatedRuleMustNotBeAccepted() {
ruleDefinitionValidator.validateRuleDefinition(new Object());
}
@Test
public void withCustomAnnotationThatIsItselfAnnotatedWithTheRuleAnnotation() throws Throwable {
ruleDefinitionValidator.validateRuleDefinition(new AnnotatedRuleWithMetaRuleAnnotation());
}
/*
* Conditions methods tests
*/
@Test(expected = IllegalArgumentException.class)
public void conditionMethodMustBeDefined() {
ruleDefinitionValidator.validateRuleDefinition(new AnnotatedRuleWithoutCondition());
}
@Test(expected = IllegalArgumentException.class)
public void conditionMethodMustBePublic() {
ruleDefinitionValidator.validateRuleDefinition(new AnnotatedRuleWithNotPublicConditionMethod());
}
@Test(expected = IllegalArgumentException.class)
public void whenConditionMethodHasOneNonAnnotatedParameter_thenThisParameterMustBeOfTypeFacts() {
ruleDefinitionValidator.validateRuleDefinition(new AnnotatedRuleWithConditionMethodHavingOneArgumentNotOfTypeFacts());
}
@Test(expected = IllegalArgumentException.class)
public void conditionMethodMustReturnBooleanType() {
ruleDefinitionValidator.validateRuleDefinition(new AnnotatedRuleWithConditionMethodHavingNonBooleanReturnType());
}
/*
* Action method tests
*/
@Test(expected = IllegalArgumentException.class)
public void actionMethodMustBeDefined() {
ruleDefinitionValidator.validateRuleDefinition(new AnnotatedRuleWithoutActionMethod());
}
@Test(expected = IllegalArgumentException.class)
public void actionMethodMustBePublic() {
ruleDefinitionValidator.validateRuleDefinition(new AnnotatedRuleWithNotPublicActionMethod());
}
@Test(expected = IllegalArgumentException.class)
public void actionMethodMustHaveNoArguments() {
ruleDefinitionValidator.validateRuleDefinition(new AnnotatedRuleWithActionMethodHavingOneArgumentNotOfTypeFacts());
}
/*
* Priority method tests
*/
@Test(expected = IllegalArgumentException.class)
public void priorityMethodMustBeUnique() {
ruleDefinitionValidator.validateRuleDefinition(new AnnotatedRuleWithMoreThanOnePriorityMethod());
}
@Test(expected = IllegalArgumentException.class)
public void priorityMethodMustBePublic() {
ruleDefinitionValidator.validateRuleDefinition(new AnnotatedRuleWithNotPublicPriorityMethod());
}
@Test(expected = IllegalArgumentException.class)
public void priorityMethodMustHaveNoArguments() {
ruleDefinitionValidator.validateRuleDefinition(new AnnotatedRuleWithPriorityMethodHavingArguments());
}
@Test(expected = IllegalArgumentException.class)
public void priorityMethodReturnTypeMustBeInteger() {
ruleDefinitionValidator.validateRuleDefinition(new AnnotatedRuleWithPriorityMethodHavingNonIntegerReturnType());
}
}

@ -0,0 +1,158 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.core;
import org.jeasy.rules.api.RuleListener;
import org.junit.Before;
import org.junit.Test;
import org.mockito.InOrder;
import org.mockito.Mock;
import static org.mockito.Mockito.*;
/**
* Test class of the execution of rule listeners.
*
* @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
*/
public class RuleListenerTest extends AbstractTest {
@Mock
private RuleListener ruleListener1, ruleListener2;
@Before
public void setup() throws Exception {
super.setup();
when(ruleListener1.beforeEvaluate(rule1, facts)).thenReturn(true);
when(ruleListener2.beforeEvaluate(rule1, facts)).thenReturn(true);
rulesEngine = RulesEngineBuilder.aNewRulesEngine()
.withRuleListener(ruleListener1)
.withRuleListener(ruleListener2)
.build();
}
@Test
public void whenTheRuleExecutesSuccessfully_thenOnSuccessShouldBeExecuted() throws Exception {
when(rule1.evaluate(facts)).thenReturn(true);
rules.clear(); // FIXME
rules.register(rule1);
rulesEngine.fire(rules, facts);
InOrder inOrder = inOrder(rule1, fact1, fact2, ruleListener1, ruleListener2);
inOrder.verify(ruleListener1).beforeExecute(rule1, facts);
inOrder.verify(ruleListener2).beforeExecute(rule1, facts);
inOrder.verify(ruleListener1).onSuccess(rule1, facts);
inOrder.verify(ruleListener2).onSuccess(rule1, facts);
}
@Test
public void whenTheRuleFails_thenOnFailureShouldBeExecuted() throws Exception {
final Exception exception = new Exception("fatal error!");
doThrow(exception).when(rule1).execute(facts);
when(rule1.evaluate(facts)).thenReturn(true);
rules.clear(); // FIXME
rules.register(rule1);
rulesEngine.fire(rules, facts);
InOrder inOrder = inOrder(rule1, fact1, fact2, ruleListener1, ruleListener2);
inOrder.verify(ruleListener1).beforeExecute(rule1, facts);
inOrder.verify(ruleListener2).beforeExecute(rule1, facts);
inOrder.verify(ruleListener1).onFailure(rule1, exception, facts);
inOrder.verify(ruleListener2).onFailure(rule1, exception, facts);
}
@Test
public void whenListenerReturnsFalse_thenTheRuleShouldBeSkippedBeforeBeingEvaluated() throws Exception {
// Given
when(ruleListener1.beforeEvaluate(rule1, facts)).thenReturn(false);
rulesEngine = RulesEngineBuilder.aNewRulesEngine()
.withRuleListener(ruleListener1)
.build();
// When
rules.register(rule1);
rulesEngine.fire(rules, facts);
// Then
verify(rule1, never()).evaluate(facts);
}
@Test
public void whenListenerReturnsTrue_thenTheRuleShouldBeEvaluated() throws Exception {
// Given
when(ruleListener1.beforeEvaluate(rule1, facts)).thenReturn(true);
rulesEngine = RulesEngineBuilder.aNewRulesEngine()
.withRuleListener(ruleListener1)
.build();
// When
rules.register(rule1);
rulesEngine.fire(rules, facts);
// Then
verify(rule1).evaluate(facts);
}
@Test
public void whenTheRuleEvaluatesToTrue_thenTheListenerShouldBeInvoked() throws Exception {
// Given
when(rule1.evaluate(facts)).thenReturn(true);
rulesEngine = RulesEngineBuilder.aNewRulesEngine()
.withRuleListener(ruleListener1)
.build();
// When
rules.clear();
rules.register(rule1);
rulesEngine.fire(rules, facts);
// Then
verify(ruleListener1).afterEvaluate(rule1, true);
}
@Test
public void whenTheRuleEvaluatesToFalse_thenTheListenerShouldBeInvoked() throws Exception {
// Given
when(rule1.evaluate(facts)).thenReturn(false);
rulesEngine = RulesEngineBuilder.aNewRulesEngine()
.withRuleListener(ruleListener1)
.build();
// When
rules.clear();
rules.register(rule1);
rulesEngine.fire(rules, facts);
// Then
verify(ruleListener1).afterEvaluate(rule1, false);
}
}

@ -0,0 +1,68 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.core;
import org.junit.Before;
import org.junit.Test;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/**
* Test class of rules priority comparison.
*
* @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
*/
public class RulePriorityThresholdTest extends AbstractTest {
@Before
public void setup() throws Exception {
super.setup();
when(rule1.getPriority()).thenReturn(1);
when(rule1.evaluate(facts)).thenReturn(true);
rulesEngine = RulesEngineBuilder.aNewRulesEngine()
.withRulePriorityThreshold(1)
.build();
}
@Test
public void rulesThatExceedPriorityThresholdMustNotBeExecuted() throws Exception {
rules.register(rule1);
rules.register(rule2);
rulesEngine.fire(rules, facts);
//Rule 1 should be executed
verify(rule1).execute(facts);
//Rule 2 should be skipped since its priority (2) exceeds priority threshold (1)
verify(rule2, never()).execute(facts);
}
}

@ -0,0 +1,43 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.core;
import org.jeasy.rules.annotation.AnnotatedRuleWithMetaRuleAnnotation;
import org.jeasy.rules.api.Rule;
import org.junit.Test;
import static org.junit.Assert.assertNotNull;
public class RuleProxyTest {
@Test
public void proxyingHappensEvenWhenRuleIsAnnotatedWithMetaRuleAnnotation() {
AnnotatedRuleWithMetaRuleAnnotation rule1 = new AnnotatedRuleWithMetaRuleAnnotation();
Rule rule = RuleProxy.asRule(rule1);
assertNotNull(rule.getDescription());
assertNotNull(rule.getName());
}
}

@ -0,0 +1,81 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.core;
import org.jeasy.rules.api.RuleListener;
import org.jeasy.rules.api.RulesEngine;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import static org.assertj.core.api.Assertions.assertThat;
@RunWith(MockitoJUnitRunner.class)
public class RulesEngineBuilderTest {
@Mock
private RuleListener ruleListener;
@Test
public void testCreationWithDefaultParameters() {
RulesEngine rulesEngine = RulesEngineBuilder.aNewRulesEngine().build();
assertThat(rulesEngine).isNotNull();
RulesEngineParameters parameters = rulesEngine.getParameters();
assertThat(parameters.getName()).isEqualTo(RulesEngine.DEFAULT_NAME);
assertThat(parameters.getPriorityThreshold()).isEqualTo(RulesEngine.DEFAULT_RULE_PRIORITY_THRESHOLD);
assertThat(parameters.isSkipOnFirstAppliedRule()).isFalse();
assertThat(parameters.isSkipOnFirstFailedRule()).isFalse();
assertThat(parameters.isSkipOnFirstNonTriggeredRule()).isFalse();
}
@Test
public void testCreationWithCustomParameters() {
String name = "myRulesEngine";
int expectedThreshold = 10;
RulesEngine rulesEngine = RulesEngineBuilder.aNewRulesEngine()
.named(name)
.withRuleListener(ruleListener)
.withRulePriorityThreshold(expectedThreshold)
.withSilentMode(true)
.withSkipOnFirstNonTriggeredRule(true)
.withSkipOnFirstAppliedRule(true)
.withSkipOnFirstFailedRule(true)
.build();
assertThat(rulesEngine).isNotNull();
RulesEngineParameters parameters = rulesEngine.getParameters();
assertThat(parameters.getName()).isEqualTo(name);
assertThat(parameters.getPriorityThreshold()).isEqualTo(expectedThreshold);
assertThat(parameters.isSilentMode()).isTrue();
assertThat(parameters.isSkipOnFirstAppliedRule()).isTrue();
assertThat(parameters.isSkipOnFirstFailedRule()).isTrue();
assertThat(parameters.isSkipOnFirstNonTriggeredRule()).isTrue();
}
}

@ -0,0 +1,80 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.core;
import org.junit.Before;
import org.junit.Test;
import static org.jeasy.rules.core.RulesEngineBuilder.aNewRulesEngine;
import static org.mockito.Mockito.*;
/**
* Test class of "skip on first applied rule" parameter of Easy Rules default engine.
*
* @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
*/
public class SkipOnFirstAppliedRuleTest extends AbstractTest {
@Before
public void setup() throws Exception {
super.setup();
setUpRule1();
rulesEngine = aNewRulesEngine()
.withSkipOnFirstAppliedRule(true)
.build();
}
@Test
public void testSkipOnFirstAppliedRule() throws Exception {
rulesEngine.fire(rules, facts);
//Rule 1 should be executed
verify(rule1).execute(facts);
//Rule 2 should be skipped since Rule 1 has been executed
verify(rule2, never()).execute(facts);
}
@Test
public void testSkipOnFirstAppliedRuleWithException() throws Exception {
rulesEngine.fire(rules, facts);
//If an exception occurs when executing Rule 0, Rule 1 should still be applied
verify(rule1).execute(facts);
//Rule 2 should be skipped since Rule 1 has been executed
verify(rule2, never()).execute(facts);
}
private void setUpRule1() throws Exception {
when(rule1.evaluate(facts)).thenReturn(true);
final Exception exception = new Exception("fatal error!");
doThrow(exception).when(rule1).execute(facts);
}
}

@ -0,0 +1,68 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.core;
import org.junit.Before;
import org.junit.Test;
import static org.mockito.Mockito.*;
/**
* Test class of "skip on first failed rule" parameter of Easy Rules default engine.
*
* @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
*/
public class SkipOnFirstFailedRuleTest extends AbstractTest {
@Before
public void setup() throws Exception {
super.setup();
setUpRule1();
rulesEngine = RulesEngineBuilder.aNewRulesEngine()
.withSkipOnFirstFailedRule(true)
.build();
}
@Test
public void testSkipOnFirstFailedRule() throws Exception {
rulesEngine.fire(rules, facts);
//Rule 1 should be executed
verify(rule1).execute(facts);
//Rule 2 should be skipped since Rule 1 has failed
verify(rule2, never()).execute(facts);
}
private void setUpRule1() throws Exception {
when(rule1.getName()).thenReturn("r1");
when(rule1.getPriority()).thenReturn(1);
when(rule1.evaluate(facts)).thenReturn(true);
final Exception exception = new Exception("fatal error!");
doThrow(exception).when(rule1).execute(facts);
}
}

@ -0,0 +1,66 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.core;
import org.junit.Before;
import org.junit.Test;
import static org.mockito.Mockito.*;
/**
* Test class of "skip on first non triggered rule" parameter of Easy Rules default engine.
*
* @author Krzysztof Kozlowski (krzysztof.kozlowski@coderion.pl)
*/
public class SkipOnFirstNonTriggeredRuleTest extends AbstractTest {
@Before
public void setup() throws Exception {
super.setup();
setUpRule1();
rulesEngine = RulesEngineBuilder.aNewRulesEngine()
.withSkipOnFirstNonTriggeredRule(true)
.build();
}
@Test
public void testSkipOnFirstNonTriggeredRule() throws Exception {
rulesEngine.fire(rules, facts);
//Rule1 is non triggered
verify(rule1, never()).execute(facts);
//Rule 2 should be skipped since Rule 1 has not been executed
verify(rule2, never()).execute(facts);
}
private void setUpRule1() throws Exception {
when(rule1.getName()).thenReturn("r1");
when(rule1.getPriority()).thenReturn(1);
when(rule1.evaluate(facts)).thenReturn(false);
}
}

@ -0,0 +1,95 @@
/**
* The MIT License
*
* Copyright (c) 2017, 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.core;
import org.junit.Test;
import java.lang.annotation.*;
import static org.junit.Assert.*;
public class UtilsTest {
@Test
public void findAnnotationWithClassWhereAnnotationIsPresent() {
Annotation foo = Utils.findAnnotation(Foo.class, AnnotationIsPresent.class);
assertCorrectAnnotationIsFound(Foo.class, foo);
}
@Test
public void findAnnotationWithClassWhereAnnotationIsPresentViaMetaAnnotation() {
Annotation foo = Utils.findAnnotation(Foo.class, AnnotationIsPresentViaMetaAnnotation.class);
assertCorrectAnnotationIsFound(Foo.class, foo);
}
@Test
public void findAnnotationWithClassWhereAnnotationIsNotPresent() {
Annotation foo = Utils.findAnnotation(Foo.class, Object.class);
assertNull(foo);
}
@Test
public void isAnnotationPresentWithClassWhereAnnotationIsPresent() {
assertTrue(Utils.isAnnotationPresent(Foo.class, AnnotationIsPresent.class));
}
@Test
public void isAnnotationPresentWithClassWhereAnnotationIsPresentViaMetaAnnotation() {
assertTrue(Utils.isAnnotationPresent(Foo.class, AnnotationIsPresentViaMetaAnnotation.class));
}
@Test
public void isAnnotationPresentWithClassWhereAnnotationIsNotPresent() {
assertFalse(Utils.isAnnotationPresent(Foo.class, Object.class));
}
private static void assertCorrectAnnotationIsFound(
Class<?> expectedAnnotationType, Annotation actualAnnotation) {
assertNotNull(actualAnnotation);
assertEquals(expectedAnnotationType, actualAnnotation.annotationType());
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
private @interface Foo {
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Foo
private @interface MetaFoo {
}
@Foo
private static final class AnnotationIsPresent {
}
@MetaFoo
private static final class AnnotationIsPresentViaMetaAnnotation {
}
}

@ -1,84 +1,84 @@
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

@ -4,7 +4,7 @@ import org.easyrules.samples.fire.rules.*
import org.easyrules.samples.fire.beans.*
import static org.easyrules.core.RulesEngineBuilder.aNewRulesEngine as EasyRules
import static RulesEngineBuilder.aNewRulesEngine as EasyRules
class Launcher {

@ -1,9 +1,9 @@
package org.easyrules.samples.fire.rules
import org.easyrules.annotation.Action
import org.easyrules.annotation.Condition
import org.easyrules.annotation.Rule
import org.easyrules.annotation.Priority
import Action
import Condition
import Rule
import Priority
@Rule(description='All the fires are out, cancel the alarm')
class CancelAlarmRule {

@ -1,9 +1,9 @@
package org.easyrules.samples.fire.rules
import org.easyrules.annotation.Action
import org.easyrules.annotation.Condition
import org.easyrules.annotation.Rule
import org.easyrules.annotation.Priority
import Action
import Condition
import Rule
import Priority
@Rule(description='No alarm, nothing to see here; This need to be last rule considered')
class EverythingOKRule {

@ -2,10 +2,10 @@ package org.easyrules.samples.fire.rules
import org.easyrules.samples.fire.beans.Alarm
import org.easyrules.annotation.Action
import org.easyrules.annotation.Condition
import org.easyrules.annotation.Rule
import org.easyrules.annotation.Priority
import Action
import Condition
import Rule
import Priority
@Rule(description='A fire will raise an alarm')
class RaiseAlarmRule {

@ -1,9 +1,9 @@
package org.easyrules.samples.fire.rules
import org.easyrules.annotation.Action
import org.easyrules.annotation.Condition
import org.easyrules.annotation.Rule
import org.easyrules.annotation.Priority
import Action
import Condition
import Rule
import Priority
@Rule(description='The alarm is detected at the fire station')
class ThereIsAnAlarmRule {

@ -1,9 +1,9 @@
package org.easyrules.samples.fire.rules
import org.easyrules.annotation.Action
import org.easyrules.annotation.Condition
import org.easyrules.annotation.Rule
import org.easyrules.annotation.Priority
import Action
import Condition
import Rule
import Priority
@Rule(description='The fires are out, turn off all of the sprinklers')
class TurnSprinklerOffRule {

@ -1,9 +1,9 @@
package org.easyrules.samples.fire.rules
import org.easyrules.annotation.Action
import org.easyrules.annotation.Condition
import org.easyrules.annotation.Rule
import org.easyrules.annotation.Priority
import Action
import Condition
import Rule
import Priority
@Rule(description='A fire has been detected in a room, turn on the sprinkler in the room; Highest priority rule')
class TurnSprinklerOnRule {

@ -1,9 +1,9 @@
package org.easyrules.samples.fizzbuzz
import org.easyrules.annotation.Action
import org.easyrules.annotation.Condition
import org.easyrules.annotation.Priority
import org.easyrules.annotation.Rule
import Action
import Condition
import Priority
import Rule
@Rule
class BuzzRule {

@ -1,6 +1,6 @@
package org.easyrules.samples.fizzbuzz
import org.easyrules.core.CompositeRule
import CompositeRule
class FizzBuzzRule extends CompositeRule {

@ -1,6 +1,6 @@
package org.easyrules.samples.fizzbuzz
import org.easyrules.core.RulesEngineBuilder
import RulesEngineBuilder
class FizzBuzzWithEasyRules {

@ -1,9 +1,9 @@
package org.easyrules.samples.fizzbuzz
import org.easyrules.annotation.Action
import org.easyrules.annotation.Condition
import org.easyrules.annotation.Priority
import org.easyrules.annotation.Rule
import Action
import Condition
import Priority
import Rule
@Rule
class FizzRule {

@ -1,9 +1,9 @@
package org.easyrules.samples.fizzbuzz
import org.easyrules.annotation.Action
import org.easyrules.annotation.Condition
import org.easyrules.annotation.Priority
import org.easyrules.annotation.Rule
import Action
import Condition
import Priority
import Rule
@Rule
class NonFizzBuzzRule {

@ -1,8 +1,8 @@
package org.easyrules.samples.helloworld
import org.easyrules.annotation.Action
import org.easyrules.annotation.Condition
import org.easyrules.annotation.Rule
import Action
import Condition
import Rule
@Rule(name = "Hello World rule", description = "Say Hello to duke's friends only")
class HelloWorldRule {

@ -1,6 +1,6 @@
package org.easyrules.samples.helloworld
import static org.easyrules.core.RulesEngineBuilder.aNewRulesEngine
import static RulesEngineBuilder.aNewRulesEngine
import java.util.Scanner
class Launcher {

@ -1,6 +1,6 @@
package org.easyrules.samples.scheduling
import org.easyrules.core.RulesEngineBuilder
import RulesEngineBuilder
import org.easyrules.quartz.RulesEngineScheduler
import java.util.Date

@ -1,8 +1,8 @@
package org.easyrules.samples.scheduling
import org.easyrules.annotation.Action
import org.easyrules.annotation.Condition
import org.easyrules.annotation.Rule
import Action
import Condition
import Rule
import java.util.Date

@ -1,6 +1,6 @@
package org.easyrules.samples.shop
import org.easyrules.core.BasicRule
import BasicRule
public class AgeRule extends BasicRule {

@ -1,8 +1,8 @@
package org.easyrules.samples.shop
import static org.easyrules.core.RulesEngineBuilder.aNewRulesEngine
import static RulesEngineBuilder.aNewRulesEngine
import org.easyrules.api.RulesEngine
import RulesEngine
class Launcher {

@ -1,8 +1,8 @@
package org.easyrules.samples.shop
import org.easyrules.annotation.Action
import org.easyrules.annotation.Condition
import org.easyrules.core.BasicRule
import Action
import Condition
import BasicRule
class OkToSellRule extends BasicRule {

@ -1,8 +1,8 @@
package org.easyrules.samples.shop
import org.easyrules.annotation.Action
import org.easyrules.annotation.Condition
import org.easyrules.core.BasicRule
import Action
import Condition
import BasicRule
class UnderAgeRule extends BasicRule {

@ -1,6 +1,6 @@
package org.easyrules.samples.simple
import static org.easyrules.core.RulesEngineBuilder.aNewRulesEngine
import static RulesEngineBuilder.aNewRulesEngine
class Launcher {

@ -1,9 +1,9 @@
package org.easyrules.samples.simple
import org.easyrules.annotation.Action
import org.easyrules.annotation.Condition
import org.easyrules.annotation.Priority
import org.easyrules.annotation.Rule
import Action
import Condition
import Priority
import Rule
@Rule
public class SimpleRule {

@ -1,8 +1,8 @@
package org.easyrules.samples.spring
import org.easyrules.annotation.Action
import org.easyrules.annotation.Condition
import org.easyrules.annotation.Rule
import Action
import Condition
import Rule
@Rule(name = "dummy rule")
class DummyRule {

@ -1,6 +1,6 @@
package org.easyrules.samples.spring
import org.easyrules.api.RulesEngine
import RulesEngine
import org.springframework.context.support.ClassPathXmlApplicationContext
class Launcher {

@ -8,20 +8,20 @@
<version>7</version>
</parent>
<groupId>org.easyrules</groupId>
<artifactId>easyrules</artifactId>
<groupId>org.jeasy</groupId>
<artifactId>easy-rules</artifactId>
<version>3.0.0-SNAPSHOT</version>
<modules>
<module>easyrules-archetype</module>
<module>easyrules-core</module>
<module>easy-rules-archetype</module>
<module>easy-rules-core</module>
</modules>
<packaging>pom</packaging>
<name>Easy Rules</name>
<description>Easy Rules is a simple, stupid Java rules engine</description>
<url>http://www.easyrules.org</url>
<description>Easy Rules is a simple, stupid rules engine for Java</description>
<url>http://www.github.com/j-easy/easy-rules</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@ -39,20 +39,20 @@
</properties>
<scm>
<url>git@github.com:EasyRules/easyrules.git</url>
<connection>scm:git:git@github.com:EasyRules/easyrules.git</connection>
<developerConnection>scm:git:git@github.com:EasyRules/easyrules.git</developerConnection>
<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/EasyRules/easyrules/issues</url>
<url>https://github.com/j-easy/easy-rules/issues</url>
</issueManagement>
<ciManagement>
<system>Travis CI</system>
<url>https://travis-ci.org/EasyRules/easyrules</url>
<url>https://travis-ci.org/j-easy/easy-rules</url>
</ciManagement>
<developers>

Loading…
Cancel
Save