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=" " />
</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.