Category Archives: how-to

How-To #5: Execute Health Insurance Demo

In this tutorial, we’ll see how to import a demo in your favourite IDE, launch it and see results of modifications applied on rules.

We’ll be using the health insurance demo, but the import steps are the same for the other ones.

First, download the archive JAR file from our web site:
 
 
 
Then, extract the files and import them in Eclipse, by opening the “Import” dialog. Select “Existing Maven Projects”, then click “Next”:
 
 
Select the directory where you extracted the sources, all the maven modules are detected and will be imported:
 
 
After what you will see this in the Eclipse Project Explorer:
 
 
 
Please import the licence file, for instance in the common main resources:
 
 
The IDE will download the dependencies and compile everything for you. Just be careful if you are behind a proxy, you should configure Maven to use it.
 
When all is compiled, then start the app. For instance, I will start the batch test Spring Boot app:
 
 
Then you can follow the startup in the console. You should get something like this:
 
 
This log shows that first run builds the ruleset. Next logs will show that it’s found in cache and not rebuilt.
 
Then, open the following url in a browser: http://locahost:8080
 
 
The current ruleset configuration for the batch is loaded. Modify the config like shown above.
Save the config using the button on the top and then look the console: the new configuration is directly taken into account.
 
 
The 2nd and 6th prices have changed accordingly to the new rules configuration.
 
 
Finally, if you have launched the “Batch Test” module, you can have a look at the MRules JMX Beans and the MRules monitoring extension results.
 
First, to see the MBeans, open Java Mission Control (jmc), select your local JVM and open the MBeans Browser.
You should find the “com.massa.mrules” domain, containing one MBean for the ruleset used in the demo module :
 
 
Then, you to the metrics scraping page : http://locahost:8080/actuator/prometheus which should outputs metrics in Open Metrics format, as follows, amongst other stats generated by Spring and Micrometer :
 
# HELP mrules_compilations_last_date RuleSet last compilation date
# TYPE mrules_compilations_last_date gauge
mrules_compilations_last_date{name="2019 Health Pricing",type="RuleSet",} 1.600812930481E12
# HELP mrules_optimizations_last_date RuleSet last optimization date
# TYPE mrules_optimizations_last_date gauge
mrules_optimizations_last_date{name="2019 Health Pricing",type="RuleSet",} 1.600812930505E12
# HELP mrules_compilations_failures RuleSet compilation failures
# TYPE mrules_compilations_failures gauge
mrules_compilations_failures{name="2019 Health Pricing",type="RuleSet",} 0.0
# HELP mrules_optimizations_last_time RuleSet last optimization time
# TYPE mrules_optimizations_last_time gauge
mrules_optimizations_last_time{name="2019 Health Pricing",type="RuleSet",} 2.40872E7
# HELP mrules_optimizations RuleSet optimization total
# TYPE mrules_optimizations gauge
mrules_optimizations{name="2019 Health Pricing",type="RuleSet",} 2.0
# HELP mrules_optimizations_time RuleSet optimization total time
# TYPE mrules_optimizations_time gauge
mrules_optimizations_time{name="2019 Health Pricing",type="RuleSet",} 6.5387726E7
# HELP mrules_optimizations_last_failure_date RuleSet last failed optimization date
# TYPE mrules_optimizations_last_failure_date gauge
mrules_optimizations_last_failure_date{name="2019 Health Pricing",type="RuleSet",} -1.0
# HELP mrules_compilations_time RuleSet compilation total time
# TYPE mrules_compilations_time gauge
mrules_compilations_time{name="2019 Health Pricing",type="RuleSet",} 5.8944502E7
# HELP mrules_executions_time RuleSet execution total time
# TYPE mrules_executions_time gauge
mrules_executions_time{name="2019 Health Pricing",type="RuleSet",} 1.75738057E8
# HELP mrules_optimizations_failures RuleSet optimization failures
# TYPE mrules_optimizations_failures gauge
mrules_optimizations_failures{name="2019 Health Pricing",type="RuleSet",} 0.0
# HELP mrules_compilations RuleSet compilation total
# TYPE mrules_compilations gauge
mrules_compilations{name="2019 Health Pricing",type="RuleSet",} 2.0
# HELP mrules_executions_failures RuleSet execution failures
# TYPE mrules_executions_failures gauge
mrules_executions_failures{name="2019 Health Pricing",type="RuleSet",} 147.0
# HELP mrules_executions_last_time RuleSet last execution time
# TYPE mrules_executions_last_time gauge
mrules_executions_last_time{name="2019 Health Pricing",type="RuleSet",} 215058.0
# HELP mrules_executions_last_date RuleSet last execution date
# TYPE mrules_executions_last_date gauge
mrules_executions_last_date{name="2019 Health Pricing",type="RuleSet",} 1.600813545031E12
# HELP mrules_compilations_last_time RuleSet last compilation time
# TYPE mrules_compilations_last_time gauge
mrules_compilations_last_time{name="2019 Health Pricing",type="RuleSet",} 1.3258079E7
# HELP mrules_executions RuleSet execution total
# TYPE mrules_executions gauge
mrules_executions{name="2019 Health Pricing",type="RuleSet",} 441.0
# HELP mrules_executions_last_failure_date RuleSet last failed execution date
# TYPE mrules_executions_last_failure_date gauge
mrules_executions_last_failure_date{name="2019 Health Pricing",type="RuleSet",} 1.60081354503E12
# HELP mrules_compilations_last_failure_date RuleSet last failed compilation date
# TYPE mrules_compilations_last_failure_date gauge
mrules_compilations_last_failure_date{name="2019 Health Pricing",type="RuleSet",} -1.0

We chose here to demonstrate how easy it is to expose metrics on how MRules behaves in your app and the export them to Prometheus. But lots of other collectors are also supported!

Don’t hesitate to change the metrics configuration in the code of the demo and to directly visualize the effects of the modifications.

Migrating MRules 1.9.x to 1.10.0

Globally, MRules upgrade will have no impact for existing rules. It will allow to have more features available.

However, if some of you rules perform average computations, minor adaptations might be necessary. Only calculations involving coefficients are impacted. These are minor adjustments which consist in:

  • Rename the “coefficients” property into “coefficientsSource”.
  • Rename the “coefficient” property into “coefficientsProperty”.

Changing these names allowed us to make them consistent with those of the properties of other mathematical operations, as well as to add new calculation possibilities.

How-To #4: Data access framework usage

Introduction

This article will provide you with details on how to use MRules built-in layer allowing reading and writing data from or to Java beans.

This layer can be used in a standalone way, it’s not coupled to the rule engine itself.

If you are familiar with anyone of the popular Java frameworks using reflection to access data layer, you won’t be surprised by the syntax, even if it’s slightly different sometimes because it offers more possibilities.

How it works

Concept

The property to access is represented by a String. For example:
myProperty.myNestedProperty“.

This String must be processed, meaning parsed and transformed to a specific Object. We call this step “compiling” the property. This is the role of the IPropertyCompiler utility.

When the String representation of the property is compiled, you obtain an instance of a class implementing the interface IProperty. This resulting instance is immutable and might be used several times, in a multi-threaded environment, to get or set the targeted property.

For example:

package com.massa.mrules.demos.site;

import org.junit.Assert;
import org.junit.Test;

import com.massa.util.ConfigDiscovery;
import com.massa.util.property.IProperty;
import com.massa.util.property.PropertySource;

/**
* Shows how to get and set a property from a Java Bean.
*/
public class PropertyExample {
    private String myProperty = "hello";
    @Test
    public void propertyExample() throws Exception {
        final IProperty property = ConfigDiscovery.getPropertyCompiler().compile("myProperty");
        Assert.assertEquals("hello", property.get(new PropertySource(this)));
        property.set(new PropertySource(this), "Hello World !");
        Assert.assertEquals("Hello World !", this.getMyProperty());
    }

    public String getMyProperty() {
        return this.myProperty;
    }
    public void setMyProperty(final String myProperty) {
        this.myProperty = myProperty;
    }
}

What is possible?

The property access framework shipped with MRules allows to perform all of the following operations:

Reading:

  • Read an unlimited number of nested properties in an Objects hierarchy.
  • Read Arrays elements, whatever is the number of dimensions of the array. The index of the element to read might be a constant or a value read from another property.
  • Lists are handled in the same way as Arrays.
  • Read Maps values, given the key. The key of the value to read might be a constant or a value read from another property.
  • Read instance fields without getter.
  • Invoke methods with or without arguments. The arguments might be constants or values read from another properties.
  • Read static fields.
  • Invoke static methods with or without arguments.
  • Combine all the above, whatever is the protection of the element to access (even if private).
  • Extra: Arrays length can be accessed with the “length” keyword used as if it were a direct field access.

Writing:

  • Write any property of a bean in an Objects hierarchy, with no limitation of level.
  • Write an element of an Array. The Array will be automatically adjusted if too small.
  • Lists are handled in the same way as Arrays.
  • Set Maps values, given the key.
  • Write an instance field, even if private and / or final.
  • Write a static field, even if private. Final static fields can not be written.

Indexed and mapped getters / setters:

Reading or Writing Arrays and Lists might be achieved in two ways:

  • A getter / setter which returns / sets directly the Array or List.
  • An indexed getter / setter, with an integer argument, returning or writing the corresponding element.

The same feature is available for Maps access, with an extra argument corresponding to the key.

This feature allows complying with Apache Common Beanutils possibilities, but we advise to limit its usage for when it’s absolutely necessary and to prefer the standard getters / setters.

Performances

Thanks to the two-phase life cycle of the IProperty beans and to a very optimized code, our framework offers impressive performances for data access: up to 40% faster than the Apache common bean utils.

Moreover, an extension allowing generating bytecode at runtime avoid the usage of reflection for public member accesses, and further improves the execution speed.

Limitations

Current
  • Indexed and mapped getters / setters: only one dimension or level.
Solved
  • Static fields could not be directly accessed until version 2.2.0.
  • Static methods could not be directly accessed until version 2.2.0.
  • Methods with more than one argument could not be directly accessed until version 1.10.0.

Usage

Basics

Reading an object hierarchy is as simple as separating all properties names to traverse with a dot.

For example: “myProperty.myNestedProperty.deeperProperty.alwaysDeeper.leafValue

Mapped properties

Accessing Maps values id done by using parenthesis. For example: “myMap('My Key')” or “myMap("My Key")“.

If the key value comes from another sub-property, just write “myMap(myBean.myKey)“.

Another way to access Mapped properties is to use the same syntax as basic access. For example : “myMap.myKey” will work fine. If the “myMap” property value is not an extension of Map with a “getMyKey()” method, then the framework will intent to use the string “myKey” as the key.

This second way has some disadvantages, so we advise to use the parenthesis syntax :

  • It’s clearer
  • Keys are not limited to Strings
  • Key values can be read from other properties.

Indexed properties

For Lists and Arrays, the syntax is the same. It’s also the same if the Java Bean model used indexes getters / setters or not.

Accessing an Array or List element is achieved by : “myArray['2']” or “myArray["2"]“.

Note: The syntax “myArray[2]” is accepted here, as an integer cannot be interpreted as a sub-property.

If the index value comes from another sub-property, just write “myArray[myBean.myIntegerProperty]“.

Direct access

Instance fields and methods

MRules allows directly accessing fields or invoking methods. The syntax to achieve this operation uses the exclamation mark as a delimiter.

The field name or the method invocation instruction (name + parenthesis + argument) start and end with an exclamation mark.

For example:

  • Field: “!myField!“.
  • Method: “!myMethod()!
    or “!myMethod('My Argument')!
    or “!myMethod(myBean.myArgument)!“.

The last exclamation mark might be omitted when it’s obvious (the dot implies a closing exclamation mark), but take care that it does not change the meaning of the property !

For example:

  • Field: “!myField” is OK.
  • Method: “!myMethod()” is OK.
  • This one cannot be omitted : “!myMapField!("My Key")“.

Extra : the length of an array is accessed using the “!length!” field access: “myArray.!length“.

Static fields and methods

The syntax is very similar to the one used to access instance fields or methods. The access is also preceeded by an exclamation mark. But the whole static access is surrounded by parentheses.

For example:

  • Field: “!(MyClassName.myStaticField)“.
  • Method: “!(MyClassName.myStaticMethod())
    or “!(MyClassName.myStaticMethod('My Argument'))
    or “!(MyClassName.myStaticMethod(myBean.myArgument))
    or even “!(MyClassName.myStaticMethod(!(MyClassName.myStaticField)))“.

Mixing everything!

All possibilities above can be mixed, and allow to adapt to most of the Java Beans models.

Some detailed examples:

  • Accessing a bean, then Map, for which the key comes from the second element of an List: “myBean.myMap(myList[2])“.
  • Accessing a multi-dimensionnal array: “myArray[2][myBean.myIntegerProperty][!myMethod()]“.
  • Retrieving the size of a List: “myBean.myList.!size()“.
  • And even using it to access the element of another list: “myBean.myMainList[myBean.myList.!size()].myListElementProperty“.
  • Using a static access as argument: “myBean.myMainList[!(MyClass.MY_STATIC_INT)].myListElementProperty“.

Almost everything is possible, you are only limited by the complexity of your data model!

Apache Commons connector

You might work with an existing application, making intensive use of Java common bean utils. And you might want to improve speed and user experience.

We developed a connector just for you, allowing to replace part of the common bean utils code with ours. This allows making the legacy code based on bean utils using MRules data access framework, without refactoring.

Just add MRules framework in your classpath, and execute on unique instruction at startup:

org.apache.commons.beanutils.BeanUtilsBean.setInstance(new com.massa.util.commons.MBeanUtilsBean(true);

The boolean “true” indicates that the Apache bean utils syntax, which is slightly different of MRules one, should be used, still to avoid refactoring and unexpected results.

We are working on this connector, to replace more features of the legacy library. But we are progressing carefully, in order to not being introducing unexpected behaviors.

MRules specific syntax

MRules intensively uses the data access framework, as all inputs / outputs are based on Java Beans properties.

To handle features specific to the rule engine, an extended property compiler is used internally and enriches the syntax with the following:

Local variables

They are prefixed with the “$” character and can be read and written.

For example:

  • Accessing a variable: “$myVariable“.
  • Accessing a map with the key as a variable: “myMap($myVariable)".
  • Accessing an array with the index as a variable: “myArray[$myVariable]".
  • Accessing nested properties of a variable: “$myVariable.myNestedProperty“.

The “$” character must be the first of the property (or sub-property). The following is invalid: “myBean.$myVariable“.

Global variables

They are prefixed with the “#” character and are read only. By convention, global variables are upper case. The list of predefined global variables can be found here.

For example:

  • Accessing a variable: “#NOW”.
  • Accessing an map with the index as a variable: “myMap(#RULESETNAME)".
  • Accessing properties of a variable: “#RULESETNAME.!length()“.

The “#” character must be the first of the property  (or sub-property). The following is invalid: “myBean.#READBASE“.

How-To #3: Spring Integration

Introduction

This article will show you how to integrate MRules in a project based on Spring. More precisely, we will see here how to use dependency injection with MRules and Spring.

Dependency injection

Dependency injection is a mechanism allowing to implement inversion of control. Programs using dependency injection are loosely coupled, as dependencies between software components are resolved dynamically at runtime instead of being hard coded and static.

MRules supports native CDI specified by Java EE 6 since version 1.3.0. But the Spring implementation is more powerful and can be used with JDK 1.5.

So the version 1.9.0 of MRules is shipped with a brand new extension, providing support for Spring dependency injection. See below how to use it.

Add extension to you project

The extension is shipped as a Maven dependency. A transitive dependency allows to retrieve the library core. Only one dependency declaration is necessary.

POM example for Maven :

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <dependencies>
        <!-- Adding dependency -->
        <dependency>
            <groupId>com.massa.mrules.extensions</groupId>
            <artifactId>spring</artifactId>
            <version>1.9.0</version>
        </dependency>
    </dependencies>
</project>

Annotation Configuration

If you are using on your project the new annotation-oriented way to configure Spring (which is in my opinion far more elegant than the old fashioned XML files) then you will have two solutions available to integrate MRules :

With the factory

In the Spring configuration class, you will have to declare a Bean returning an instance of the MRules factory. You can extend the factory class in order to read configuration from external configuration file, database, or any other way. Then Spring will automatically use the factory instance to instanciate the rule engine.
Example :

package com.massa.mrules.demos.spring;

public class MRulesSpringConfig {
    /**
    * Creates a rule engine, managed by Spring via the MRules Factory Bean.
    */
    @Bean
    public MRulesFactoryBean getRuleEngine() throws MConfigurationException {
        final MRulesFactoryBean fact = new MRulesFactoryBean();
        fact.setUri("TEST_SPRING");
        fact.setFactoryImpl(RichXMLFactory.class.getName());
        fact.setConfigHolderImpl(XmlRuleEngineConfigHolder.class.getName());
        fact.setXmlFile("rules.xml");
        return fact;
    }
}

Then, just use your rule engine in your module !

package com.massa.mrules.demos.spring;
public class MRulesSpring {
/**
* Spring will inject your rule engine instance in this field at runtime.
*/
@Autowired
protected IMruleExecutionSet ruleEngine;

// Use the rule engine ...
}

Without the factory

It’s almost the same as the factory way, but you instanciate your rule engine directly in the Spring config class instead of delegating this operation to the factory. And you use you rule engine instance the same way.

It’s a little less flexible, but may be enough depending on the context.

package com.massa.mrules.demos.spring;

public class MRulesSpringConfig {
    /**
    * Creates a rule engine, managed by Spring via the MRules Factory Bean.
    */
    @Bean
    public IMruleExecutionSet getRuleEngine() throws MConfigurationException {
        final InputStream xml = ResourceLoader.getResourceAsStream("rules.xml");
        final RichXMLFactory factory = new RichXMLFactory();
        return factory.read(xml);
    }
}

XML Configuration

If your Spring project is configured using XML, you will have no difficulties to integrate MRules. Just declare a bean in your Spring context configuration, as follow :

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="http://www.springframework.org/schema/beans
   http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- Configures rule engine, for Spring XML Demo -->
    <bean id="ruleEngine" class="com.massa.mrules.extensions.spring.MRulesFactoryBean">
        <property name="uri" value="spring test" />
        <property name="configHolderImpl" value="com.massa.mrules.builder.XmlRuleEngineConfigHolder" />
           <property name="factoryImpl" value="com.massa.mrules.factory.xml.RichXMLFactory" />
        <property name="xmlFile" value="rules.xml" />
        <property name="compilationLevel" value="LIGHT" />
    </bean>
</beans>

Then, just use your rule engine in your module, with the @Autowired annotation, or with the XML IOC configuration.

Demo

A demo, which can be found in the download page, illustrates all the examples above.

How-To #2 : Performances tips

Introduction

This article will provide some tips on how to optimize performances when using MRules. Depending on the version, some of them become unuseful, as the internal optimizer becomes more and more powerful. This will be precised each time.

Compilation mode

Compilation (coupled with optimization) is the first step of the life cycle of a rule set. MRules provides two modes for this : the STANDARD mode and the LIGHT mode.

In a general way, the STANDARD mode should be used everytime it’s possible, meaning when you know the structure of the input and output Java beans.

The LIGHT mode allows greater flexibility, but some optimizations can’t be performed if using it. For example, in STANDARD mode, constant values are cast only once at compilation type, in order to correspond to target type. In LIGHT mode, they are cast at runtime, at each execution.

Choose the right rule implementation

To perform actions based on the value of input data, the most common way is to perform an evaluation. For example:

IF (input.value == "A") THEN ACTION "A"
IF (input.value == "B") THEN ACTION "B"
IF (input.value == "C") THEN ACTION "C"
IF (input.value == "D") THEN ACTION "D"

This can lead to complex  rules and to more computations than necessary.

To avoid this, the INDEX rule is very adapted to this type of cases:

INDEX(input.value)
WHEN "A" THEN ACTION "A"

  WHEN "B" THEN ACTION "B"
  WHEN "C" THEN ACTION "C"
  WHEN "D" THEN ACTION "D"

The “Transportation fees computation” demo shows a use case written using the two techniques.

Condition writing

The EVAL condition (used to perform evaluation, for example “value == otherValue”) is generally written using three inputs:

  • source: the first value to be compared
  • operator: the evaluation to perform (equality, greater than, lesser than, …)
  • reference: the second value to be compared

The name of the input values is choosen accordingly to how they are treated: if types are different, the reference is cast to correspond to the source type, so that the evaluation can be made.

So, if one of the values is a constant, it should be the “reference”, so that it will be cast at compilation time. If not, the cast can not be made and the variable value will be cast at each execution to correspond to the constant type.

For example:

IF ("125" IN input.collectionOfInteger) THEN ACTION "A"

In this case, all integer values in the collection will be cast to Strings, and then the engine will check if the String “125” is present in the temporary collection of Strings.

If written:

IF (input.collectionOfInteger CONTAINS "125") THEN ACTION "A"

Then the String “125” will be cast as Integer at compilation time and no unuseful operation performed at execution.

Starting from version 1.8.0, the optimizer detects this situation and automatically corrects it.

Use variables when necessary

If some complex operation are performed several times, it might be useful to store the result in a variable to avoid double (or more) computations.

For example:

IF (input.value1 IN SPLIT("A;B;C;D") AND input.value2 == "A") THEN ACTION "A"
IF (input.value1 IN SPLIT("A;B;C;D") AND input.value2 == "B") THEN ACTION "B"

In this case, the SPLIT might be performed twice. Better write:

$tempArray = SPLIT("A;B;C;D")
IF (input.value1 IN $tempArray AND input.value2 == "A") THEN ACTION "A
IF (input.value1 IN $tempArray AND input.value2 == "B") THEN ACTION "B"

Note:

  • Versions anterior to 1.7.0 will try to re-order conditions to evaluate first the simplest ones
  • Version 1.7.0 (and more) will transform the result of the SPLIT operation into a constant value in the previous case (as the splitted value is a constant).
  • Version 1.8.0 will be able to cache the result of the SPLIt operation (if not performed on a constant value) and reuse it.

Performance extension

MRules is based on Java reflection API to access input and output data. This API becomes faster and faster at each version of the JRE, but it stays slower than the compiled code.

If you are running  MRules using a JRE 7 or greater, the extension allowing to generate bytecode when possible instead of using reflexion might be a good choice.

For public fields and methods of the input / output beans, no reflexion will be used. Global performances at execution will be greatly improved (up to 60 % faster). The generated bytecode is cached and generated only once for a given method, even if the rule engine is recompiled. It is also shared between different rule engines if the Java beans are the same.

Upgrade

New versions provide more and more features and optimizations, allowing rules writers to write the rules in a more intuitive way and to not being aware of all subtleties of performance improvements.

The version 1.8.0 is a major version concerning performances, at all levels. The cache usage has been improved and is more accurate. The internal optimizer is more powerful. Some parts of the library code have been reworked to be always faster.

So don’t hesitate and always upgrade to the latest version !

How-To #1: Working with Collections

This article is the first of a new series, which aims to provide tips and tricks on avanced MRules features. We hope that you will find it useful and would be pleased to receive comments on what you did or did not like.

So, this first How To tutorial is about how MRules deals with Collections. As MRules is designed to be used by non-technical people, the way to design rules must be as natural, simple and concise as possible.

So, support of Collections is native and lots of tools are included to handle them:

  • You can count the number of entries in a list, eventually including only some of them in the counting depending on conditions.
  • You can sum the elements, compute averages, etc …
  • You can iterate on the elements.
  • and more…

Excel users will find out that the concept of the “SUM.IF” formula is present, and extended to other operations.
To illustrate, we’ll use a very simple example. The case consists in performing operations on a company employees. The input data is very simple:

  • A List of Employees
  • Each Employee is:
    • A name
    • A birth date
    • A gender
    • A daily Cost

With MRules, we will compute:

  • The number of “young” employees (who are less than 30 years old)
  • The total daily cost of the employees
  • The percentage of women in the company
  • A flag checking that all employees are paid at least 600
  • A list of all employees names

The Java data model is the following:

public class Company {
    //INPUT
    private List<Employee> employees;

    //OUTPUT
    private double womenPercentage;
    private double totalDailyCost;
    private int numberOfYoungEmployees;
    private boolean allPaidAtLeast600;
    private List<String> names;

   // Getter / Setter
}
public class Employee {
    private String name;
    private Date birthDate;
    private String gender;
    private double dailyCost;
    public Employee(final String name, final Date birthDate, final String gender, final double dailyCost) {
    super();
    this.name = name;
    this.birthDate = birthDate;
    this.gender = gender;
    this.dailyCost = dailyCost;
    }
    // Getter / Setter
}

And this JUnit test set will be used to see the result of our rules:

final Company c = new Company();
c.setEmployees(new ArrayList<HowToWorkWithCollections.Employee>());
final Calendar young = Calendar.getInstance();
young.add(Calendar.YEAR, -29);
final Calendar old = Calendar.getInstance();
old.add(Calendar.YEAR, -31);

int i = 0;
c.getEmployees().add(new Employee("Name " + ++i, young.getTime(), "F", 500));
c.getEmployees().add(new Employee("Name " + ++i, young.getTime(), "F", 600));
c.getEmployees().add(new Employee("Name " + ++i, old.getTime(), "M", 700));
c.getEmployees().add(new Employee("Name " + ++i, old.getTime(), "F", 800));
c.getEmployees().add(new Employee("Name " + ++i, young.getTime(), "F", 500));
c.getEmployees().add(new Employee("Name " + ++i, old.getTime(), "M", 500));
c.getEmployees().add(new Employee("Name " + ++i, young.getTime(), "F", 600));
c.getEmployees().add(new Employee("Name " + ++i, young.getTime(), "F", 700));
c.getEmployees().add(new Employee("Name " + ++i, old.getTime(), "M", 700));
c.getEmployees().add(new Employee("Name " + ++i, young.getTime(), "F", 600));

final ArrayList<String> names = new ArrayList<String>();
for (i = 1 ; i <= c.getEmployees().size() ; i++) {
    names.add("Name " + i);
}

final IMruleExecutionSet set = this.getEngine();

final MExecutionContext<Company> exec = new MExecutionContext<Company>(set, c);
exec.execute();

Assert.assertEquals(6, c.getNumberOfYoungEmployees());
Assert.assertEquals(6200, c.getTotalDailyCost(), 0.0001);
Assert.assertEquals(70, c.getWomenPercentage(), 0.0001);
Assert.assertEquals(names, c.getNames());

Counting the number of “young” employees

The first rule to write is an action, which consists in setting a value into an output field.

So we’ll use the SET action, with the “numberOfYoungEmployees” output property as a target. The value to set, the source, is not directly accessible, we have to use the COUNT accessor to compute it. The count is applied on the “employee” input property as the target. But not all employees should be taken into account: so we have to add a condition. This condition will evaluate if age of the persons is less than 30 years. It has three parameters:

  • The source: the employee’s age, which must be computed from the birth date.
  • The operator: LT for Less Than.
  • The reference: simply the value”30″.

Now, computing the age is simple. Date operations are included in MRules and will be the objective of the next How-To. Here, we have to compute the difference beween Now and the birth date in years. So the DATEDIFF accessor will be used, with the following parameters:

  • date1: the global variable #NOW
  • date2: the property “birthDate”
  • field: “Y” (for Year)

The final rule is as follow:

<executable implem="SET" target=":numberOfYoungEmployees">
    <source implem="COUNT" source=":employees">
        <condition implem="EVAL" operator="LT" reference="30">
            <source implem="DATEDIFF" date1="#NOW" date2=":birthDate" field="Y" />
        </condition>
    </source>
</executable>

Computing the total daily cost

The second rule is quite simple compared to the first one. It also consists in setting a value into an output field, so the SET action with the “totalDailyCost” output property as a target will be used.

The source is a sum of the employees’ daily costs, so the SUM accessor will be used, with the “employee” input property as the source parameter, and “dailyCost” property as the property parameter. This second parameters indicates that the daily cost must be read from the iterated element and that this value must be summed.

The final rule is as follow:

<executable implem="SET" target=":totalDailyCost">
    <source implem="SUM" source=":employees" property=":dailyCost" />
</executable>

Computing the percentage of women

This rule is a little more complicated. The SET action is still used, to put the value in the “womenPercentage” output property.

The steps are :

  • Counting the number of employees, with a condition on the “gender” property which has to be “F”. This sub-rule is built on the same model as the first one.
  • Then we need the number of employees, with no condition. Here, the COUNT accessor with no condition would do the job, but it will be more efficient to use the SIZE accessor.
  • The value of the first step is divided by the value of the second one.
  • And then we multiply all this by 100 to abtain a percentage.

The final rule could be as follow:

<executable implem="SET" target=":womenPercentage">
    <source implem="MULTIPLY">
        <value implem="DIVIDE" decimals="2">
            <value implem="COUNT" source=":employees">
                <condition implem="EVAL" source=":gender" operator="EQ" reference="F" />
            </value>
            <value implem="SIZE" source=":employees" />
        </value>
        <value implem="VALUE" value="100" />
    </source>
</executable>

But, multplying by 100 is equivalent to dividing by 0.01. So this rule can be simplified by simply adding a divisor :

<executable implem="SET" target=":womenPercentage">
    <source implem="DIVIDE" decimals="2">
        <value implem="COUNT" source=":employees">
            <condition implem="EVAL" source=":gender" operator="EQ" reference="F" />
        </value>
        <value implem="SIZE" source=":employees" />
        <value implem="VALUE" value="0.01" />
    </source>
</executable>

Checking that all employees are paid at least 600

The objective of this rule is to show two advanced feature:

  • One offered by the PROPERTY Accessor (allowing to read data from input). It accepts a “source” parameter allowing to read the property from outside the default read base of the context.
  • The other one offered by the Evaluation Condition. The “iterationOperator” parameter allows to perform several times the condition all all elements of an iterable and to combine the result with a logical operator (AND, OR, …).

So, by combining these two features, this rule is written in a very simple way :

<executable implem="SET" target=":allPaidAtLeast600">
    <source implem="EVAL" iterationOperator="AND" operator="GTE" reference="600.0">
        <source implem="PROPERTY" property="dailyCost" source="ITERATE::employees" />
    </source>
</executable>

Creating a list of all employees names

For this last rule, the Employees list should be iterated and their names added ond by one to the target “names” output property.

The FOREACH action will be used, combined with the ADD action. A local variable is used to store the current employee.

The final rule is as follow:

<executable implem="FOREACH" var="employee" source=":employees">
    <executable implem="ADD" source="$employee.name" target=":names" />
</executable>

***Edit:
Since MRules version 1.7.0, the previous rule can be writen in a more efficient way as follow:

<executable implem="ADDALL" target=":names" >
    <source implem="PROPERTY" source="ITERATE::employees" property="name"/>
</executable>

The PROPERTY accessor is told to iterate on all employees and to retrieve “name” property of all iterated objects. The ADDALL action will then iterate on all names and add them to the list. Bby using the ADDALL function, the rule will be more efficient, because less cache access will be performed.

Final configuration

Combining all previously written rules, here is the final rule set configuration:

<?xml version="1.0" encoding="UTF-8"?>

<rules implem="RULEEXECUTIONSET" name="HowToWorkWithCollections">
    <!-- Counting the number of "young" employees, i.e. who are less than 30 years old. -->
    <executable implem="SET" target=":numberOfYoungEmployees">
        <source implem="COUNT" source=":employees">
            <condition implem="EVAL" operator="LT" reference="30">
                <source implem="DATEDIFF" date1="#NOW" date2=":birthDate" field="Y" />
            </condition>
        </source>
    </executable>

    <!-- Sum of the daily cost of each employee. -->
    <executable implem="SET" target=":totalDailyCost">
        <source implem="SUM" source=":employees" property=":dailyCost" />
    </executable>

    <!--
     Computing the percentage of women:
     - counting the number of employees whose gender is "F"
     - Dividing by the number of employees
     - Multiplying by 100 
    -->
    <executable implem="SET" target=":womenPercentage">
        <source implem="DIVIDE" decimals="2">
            <value implem="COUNT" source=":employees">
                <condition implem="EVAL" source=":gender" operator="EQ" reference="F" />
            </value>
            <value implem="SIZE" source=":employees" />
            <value implem="VALUE" value="0.01" />
        </source>
    </executable>

    <!-- Creating a list of all employees names, by iterating on all employees and adding the employee name in the target list. -->
    <executable implem="FOREACH" var="employee" source=":employees">
        <executable implem="ADD" source="$employee.name" target=":names" />
    </executable>

    <!-- Checking that all employees are paid at least 600. -->
    <executable implem="SET" target=":allPaidAtLeast600">
        <source implem="EVAL" iterationOperator="AND" operator="GTE" reference="600.0">
            <source implem="PROPERTY" property="dailyCost" source="ITERATE::employees" />
        </source>
    </executable>
</rules>

To go further

Using the tools seen in this tutorial, it’s no easy to aggregate Collection values. We can imagine to:

  • Check if the mean women salary is greater or lesser than the mean men salary.
  • Compute the mean salary of young women.
  • Count old men with a salary less than 600.
  • etc.. Everything is possible.

Also note that you can dynamically create collections to iterate on, using the SPLIT accessor. For example, the following rule will perform five iterations, with the values “10” to “50”, and print them:

<executable implem="FOREACH" var="myVariable" source="SPLIT:10;20;30;40;50">
    <executable implem="PRINT" source="$myVariable" />
    <executable implem="PRINT" source="&#10;" />
</executable>

The ITERATE accessor also provides the same behaviour for different use cases.

All examples in this tutorial are realized using MRules 1.6.1. Previous versions may not include all features detailled here, so some examples could fail with  them.