blob: d01cce5e276efe107e00c492204db048ba1a36b5 [file] [log] [blame]
[[_drools.releasenotesdrools.7.0.0]]
= Breaking changes in Drools 7.0 from 6.x
== Property reactivity enabled by default
Property reactivity has been introduced in Drools 5.4 but users had to explicitly enable it on a class by class basis through
the `@PropertyReactive` annotation or on the whole knowledge base using the `PropertySpecificOption.ALWAYS` builder option.
However, since using this feature is considered a good practice both under correctness and performance points of view, it has
been enabled by default in Drools 7.0. If required it is possible to disable property reactivity and reconfigure Drools 7.0
to work exactly as it did in version 6.x by adding the following configuration to the kmodule.xml file.
[source]
----
<configuration>
<property key="drools.propertySpecific" value="ALLOWED"/>
</configuration>
----
== Type preserving accumulate functions
In Drools 6 when using the ``sum`` function inside an accumulate pattern the result was always a Double regardless of the
field type on which the sum was performed. This caused the following 3 problems:
* Loss of precision: the sum of a long `1881617265586265321L` will incorrectly return `1.88161726558626534E18`.
The BigDecimal sum of `0.09` and `0.01` will also be incorrect.
* Loss of performance: summing with a Double total is significantly slower than summing with a Long or an Integer.
* Leaked complexity: it enforced the user to pattern matching on Double, or more generically (suggested choice) on Number,
while it may be expected that the result of summing on a field of type Integer would be an Integer as well.
Conversely Drools 7 preserves the type of the expression on which the sum is executed, so it will be possible to directly
match on that type as in:
[source]
----
Long(...) from accumulate(..., sum($p.getLongWeight()))
----
== Renaming TimedRuleExecutionOption
The KieSession option to control when timed rules have to be automatically executed has been renamed into `TimedRuleExecutionOption` fixing a typing mistake in its name which affected previous releases; the property has been aligned into `drools.timedRuleExecution`.
[cols="3", options="header"]
.Name changes
|===
|
|previous releases
|version `7.0.0.Final`
|KieSession option
|`TimedRuleExectionOption`
|`TimedRuleExecutionOption`
|property
|`drools.timedRuleExection`
|`drools.timedRuleExecution`
|===
= What is New and Noteworthy in Drools 7.0
== Multithreaded rule engine
[WARNING]
====
This feature is experimental
====
Historically Rete has always been a sequential algorithm and even if Phreak, its evolution introduced in Drools 6, allowed
some degree of parallelization, this possibility wasn't concretely leveraged yet. In Drools 7 is finally possible to make
the engine to evaluate more rules in parallel. This is achieved by partitioning the Rete/Phreak in independent partitions
and evaluating them in parallel.
This feature is off by default, but it's possible to create a parallel KieBase by enabling it via the ``KieBaseConfiguration`` as it follows:
[source]
----
KieServices ks = KieServices.Factory.get();
KieBaseConfiguration kieBaseConf = ks.newKieBaseConfiguration();
kieBaseConf.setOption(MultithreadEvaluationOption.YES);
KieBase kieBase = kieContainer.newKieBase(kieBaseConf);
----
or alternatively through the system property:
[source]
----
drools.multithreadEvaluation = true
----
[WARNING]
====
Rules using queries, salience or agenda-group are currently not supported by the parallel engine. In case they are present inside
the rule base the compiler emits a warning and automatically switches back using the single threaded implementation. There are
other cases where using the multithreaded rule engine is not correct and leads to wrong results even if the engine may be not
able to automatically detect them and then will not fallback to the single thread mode, e.g. rules rely on implicit salience
given by rule ordering inside the drl file. For this reason particular attention must be paid when enabling this option.
Finally at the moment session serialization and incremental compilation are not supported.
====
== OOPath improvements
[WARNING]
====
This feature is experimental
====
OOPath has been introduced with Drools 6.3.0.
In Drools 7.0.0 the support for standard Java Collection has been enhanced, with a dedicated implementation for `List` and `Set`,
as specialized `ReactiveList` and `ReactiveSet`; a `ReactiveCollection` is also available.
This also includes out of the box reactive support when performing mutable operations through their `Iterator` and `ListIterator`.
.Example:
[source,java]
----
public class School extends AbstractReactiveObject {
private String name;
private final List<Child> children = new ReactiveList<Child>(); // <1>
public void setName(String name) {
this.name = name;
notifyModification(); // <2>
}
public void addChild(Child child) {
children.add(child); // <3>
// no need to call notifyModification() here.
}
----
<1> Using specialized `ReactiveList` for reactive support over standard Java `List`.
<2> Usually `notifyModification()` is required to be called when a field is changed for reactive support
<3> but in the case of `ReactiveList` this is handled automatically, like every other mutating operations performed over the field `children`.
TIP: As a best-practice, it is recommended to declare reactive collection fields `final` as per the example shown.
=== OOPath Maven plug-in
The Kie Maven plug-in offers a new goal `injectreactive` to instrument bytecode and automatically inject reactivity support for standard cases.
The `injectreactive` goal is disabled by default, and can be enabled via Maven plug-in configuration `instrument-enabled` settings.
.Example:
[source,xml]
----
<groupId>org.kie</groupId>
<artifactId>kie-maven-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<instrument-enabled>true</instrument-enabled> <!--1-->
</configuration>
----
<1> Enable the `injectreactive` goal.
The `injectreactive` goal will instrument bytecode pertaining to the Maven project build's output directory `${project.build.outputDirectory}`.
It is possible to limit the scope of the goal to a specific package or hierarchy of packages via Maven plug-in
configuration `instrument-packages` settings list.
.Example:
[source,xml]
----
<groupId>org.kie</groupId>
<artifactId>kie-maven-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<instrument-enabled>true</instrument-enabled>
<instrument-packages>
<instrumentPackage>to.instrument</instrumentPackage> <!--1-->
<instrumentPackage>org.drools.compiler.xpath.tobeinstrumented.*</instrumentPackage> <!--2-->
</instrument-packages>
</configuration>
----
<1> Limit scope of instrumentation specifically to `to.instrument` package only.
<2> Limit scope of instrumentation to `org.drools.compiler.xpath.tobeinstrumented` and its children packages.
The plug-in will instrument bytecode for every field assignment under the following standard cases:
* a field assignment will also trigger `notifyModification()`
* wrap any field defined as List with a `ReactiveList`
* wrap any field defined as Set with a `ReactiveSet`
* wrap any field defined as Collection with a `ReactiveCollection`
NOTE: In order for a field of type List/Set to be wrapped correctly, the field member of the java class must be declared specifically using either
`java.util.Collection`, `java.util.List` or `java.util.Set`
(declaring for instance a field as `java.util.ArrayList` will not be instrumented with the specialized reactive collections).
WARNING: It is not recommended to mix manual support for reactivity (implemented manually) and the bytecode instrumentation Maven plug-in; it is better envisaged to keep the two scopes distinct, for instance by making use of the plug-in configuration to instrument only specific packages as documented above.
The following section present detailed examples of the plug-in instrumentation.
==== Instrumentation of field assignments
A field assignment like in the following example:
.Original:
[source,java]
----
public class Toy {
private String owner;
...
public void setOwner(String owner) {
this.owner = owner;
}
}
----
will be instrumented by intercepting the field assignment and triggering the `notifyModification()`:
.Result:
[source,java]
----
public class Toy implements ReactiveObject {
private String owner;
...
public void setOwner(final String owner) {
this.$$_drools_write_owner(owner);
}
public void $$_drools_write_owner(final String owner) {
this.owner = owner;
ReactiveObjectUtil.notifyModification((ReactiveObject) this);
}
}
----
Please notice this instrumentation applies only if the field is not a `Collection`.
In the case the field assignment is referring a `List` or a `Set`, the instrumentation will wrap the assignment with a `ReactiveList` or ``ReactiveSet` accordingly; for example:
.Original:
[source,java]
----
public class School {
private final String name;
private final List<Child> children = new ArrayList<Child>();
...
public School(String name) {
this.name = name;
}
public List<Child> getChildren() {
return children;
}
}
----
will be instrumented by intercepting and wrapping with `ReactiveList`:
.Result:
[source,java]
----
public class School implements ReactiveObject {
private final String name;
private final List<Child> children;
public School(final String name) {
this.$$_drools_write_children(new ArrayList());
this.name = name;
}
public List<Child> getChildren() {
return this.children;
}
public void $$_drools_write_children(final List list) {
this.children = (List<Child>) new ReactiveList(list);
}
----
== Soft expiration for events
When explicitly defining an event expiration in Drools 6.x, it is always considered an hard expiration, meaning that it always
takes precedence on any other expiration implicitly calculated on temporal windows and constraints where the event is involved.
Drools 7 also allows to specify a soft expiration for events that can be used if the inferred expiration offset is infinite.
In this way it is possible to have a guaranteed expiration that is either the inferred one or the specified one if the other
is missing. Moreover this implies that rule authors are not required to include a temporal constraint in all rules and then
event classes can be designed even if the rules are not yet known.
By default event expiration is considered to be hard, but it is possible to change the expiration policy and define a soft
expiration either annotating the event's class as it follows:
[source,java]
----
@Role(Role.Type.EVENT)
@Expires( value = "30s", policy = TIME_SOFT )
public class MyEvent { ... }
----
or using a type declaration:
[source]
----
declare MyEvent
@role( event )
@expires(value = 30s, policy = TIME_SOFT)
end
----
== Rule Units
[WARNING]
====
This feature is experimental
====
Rule units represent a purely declarative approach to partition a rules set into smaller units, binding different data sources
to those units and orchestrate the execution of the individual unit. A rule unit is an aggregate of data sources, global variables
and rules. It is possible to define a rule unit by just implementing the ``RuleUnit`` marker interface as in the following example:
.A RuleUnit class:
[source,java]
----
package org.mypackage.myunit;
public static class AdultUnit implements RuleUnit {
private int adultAge;
private DataSource<Person> persons;
public AdultUnit( ) { }
public AdultUnit( DataSource<Person> persons, int age ) {
this.persons = persons;
this.age = age;
}
// A DataSource of Persons for this RuleUnit
public DataSource<Person> getPersons() {
return persons;
}
// A global variable valid in this RuleUnit
public int getAdultAge() {
return adultAge;
}
// --- life cycle methods
@Override
public void onStart() {
System.out.println(getName() + " started.");
}
@Override
public void onEnd() {
System.out.println(getName() + " ended.");
}
}
----
Here ``persons`` is a source of facts of type Person representing the part of working memory related to that specific
entry-point used when the rule unit is evaluated, while ``adultAge`` is a global variable accessible from all the rules
belonging to this rule unit. The last 2 methods are part of the rule unit's life cycle and are invoked by the engine.
More in general the lifecycle of a RuleUnit can be monitored overriding the following methods:
[cols="2", options="header"]
.Rule Unit life cycle methods
|===
|Method
|Invoked when
|onStart()
|the rule engine starts evaluating the unit
|onEnd()
|the evaluation of this unit terminates
|onSuspend()
|the execution of unit is suspended (only for runUntilHalt)
|onResume()
|the execution of unit is resumed (only for runUntilHalt)
|onYield(RuleUnit other)
|the consequence of rule in a given rule unit triggers the execution of a different unit
|===
All these methods have an empty default implemention inside the ``RuleUnit`` interface, so their implementation is optional.
At this point it is possible to add one or more rules to this rule unit. By default all the rules in a drl file are automatically
associated to a rule unit following a naming convetion on the name of drl file itself: if the drl file is in the same package
and has the same name of a class implementing the ``RuleUnit`` interface all the rules in that drl file will implicitly belong
to that unit. So for example all the rules in the drl file named AdultUnit.drl in package org.mypackage.myunit will be automatically
part of the rule unit ``org.mypackage.myunit.AdultUnit``.
It is also allowed to avoid using this naming convention and explictly declare to which unit the rules in a drl file belongs
by using the new ``unit`` keyword. The unit declaration must always immediately follow the package declaration and contains
the name of the class (in that package) to which the rules of the drl file are part like in:
.A rule belonging to the rule unit
[source]
----
package org.mypackage.myunit
unit AdultUnit
rule Adult when
$p : Person(age >= adultAge) from persons
then
System.out.println($p.getName() + " is adult and greater than " + adultAge);
end
----
[WARNING]
====
It is not allowed to mix rules with and without a rule unit in the same KieBase.
====
It is also possible to rewrite the same pattern in a more convenient way using the oopath notation introduced in drools 6
as it follows:
.A rule belonging to a rule unit using the oopath notation
[source]
----
package org.mypackage.myunit
unit AdultUnit
rule Adult when
$p : /persons{age >= adultAge}
then
System.out.println($p.getName() + " is adult and greater than " + adultAge);
end
----
Here the persons matched by the left hand side of the rule comes from the ``DataSource`` contained in the RuleUnit class with
the same name, while the ``adultAge`` variable is used in both the left and right hand side in the same way as it was a global
defined at drl level. In other words the ``persons`` DataSource acts as a specific entry-point feeding the working memory.
The easiest way to create a ``DataSource`` is using a fixed set of data as in:
[source,java]
----
DataSource<Person> persons = DataSource.create( new Person( "Mario", 42 ),
new Person( "Marilena", 44 ),
new Person( "Sofia", 4 ) );
----
To execute one or more rule units defined in a given ``KieBase`` it is necessary to create a new ``RuleUnitExecutor`` and
bind it to the ``KieBase`` itself:
[source,java]
----
KieBase kbase = kieContainer.getKieBase();
RuleUnitExecutor executor = RuleUnitExecutor.create().bind( kbase );
----
At this point it is possible to create the ``AdultUnit`` by passing to it the persons ``DataSource`` and run it on the ``RuleUnitExecutor``
[source,java]
----
RuleUnit adultUnit = new AdultUnit(persons, 18);
executor.run( adultUnit );
----
that in this case will produce the following output:
[source]
----
org.mypackage.myunit.AdultUnit started.
Marilena is adult and greater than 18
Mario is adult and greater than 18
org.mypackage.myunit.AdultUnit ended.
----
Other than explicitly creating the rule unit instance it is often more convenient to just pass to the executor the class
of the rule unit that you want to run and let the executor to create an instance of it and to set on it the ``DataSource``s
and other variable before running it. In order to do so it is necessary to previously register those variables on the executor
itself, so that the following code will produce exactly the same result than the former one.
[source,java]
----
executor.bindVariable( "persons", persons );
.bindVariable( "adultAge", 18 );
executor.run( AdultUnit.class );
----
The name passed to the ``RuleUnitExecutor.bindVariable()`` method is used at runtime to bind said variable to the field of the
RuleUnit class having the same name. For instance in the former example the ``RuleUnitExecutor`` injects into the new rule unit
the data source formerly bound to the "persons" name and the value 18 bound to the String "adultAge" to the fields with the
corresponding names inside the ``AdultUnit`` class. It is possible to override this default and explictly define a logical
binding name for each field of the rule unit class using the ``@UnitVar`` annotation. For example, the field binding in the
following class can be redefined with alternative names:
[source,java]
----
package org.mypackage.myunit;
public static class AdultUnit implements RuleUnit {
@UnitVar("minAge")
private int adultAge = 18;
@UnitVar("data")
private DataSource<Person> persons;
}
----
and then binding the variables to the executor using those alternative names and run the unit
[source,java]
----
executor.bindVariable( "data", persons );
.bindVariable( "minAge", 18 );
executor.run( AdultUnit.class );
----
A rule unit can be executed in passive mode as shown above (corresponding to invoking ``fireAllRules`` on a whole session)
or in active mode using the ``runUntilHalt`` that is equivalent to the session's ``fireUntilHalt``. As for the fireUntilHalt,
the runUntilHalt is blocking and therefore has to be issued on a separated thread like in:
[source,java]
----
new Thread( () -> executor.runUntilHalt( adultUnit ) ).start();
----
=== Data sources
A ``DataSource`` is a source of the data processed by a given rule unit. A rule unit can have zero or more data sources and
to each DataSource declared inside a rule unit corresponds a different entry-point into the rule unit executor. A DataSource
can be shared by different units, but in this case there will be many different entry-points, one for each unit, through which
the same objects will be inserted.
In other terms the ``DataSource`` represents the entry-point of the rule unit, so it is possible to insert a new fact into it:
[source,java]
----
Person mario = new Person( "Mario", 42 );
FactHandle marioFh = persons.insert( mario );
----
modify that fact, optionally specifying the set of properties that have been modified in order to leverage property reactivity
[source,java]
----
mario.setAge( 43 );
persons.update( marioFh, mario, "age" );
----
or delete it
[source,java]
----
persons.delete( marioFh );
----
=== Imperatively running and declaratively guarding a RuleUnit
As anticipated, multiple rule units can be defined in the same knowledge base and these units can work in a coordinate way
by invoking or guarding the execution of each other. To demonstrate this let's suppose having the following 2 drl files
each of them containing a rule belonging to a distinct rule unit.
[source]
----
package org.mypackage.myunit
unit AdultUnit
rule Adult when
Person(age >= 18, $name : name) from persons
then
System.out.println($name + " is adult");
end
----
[source]
----
package org.mypackage.myunit
unit NotAdultUnit
rule NotAdult when
$p : Person(age < 18, $name : name) from persons
then
System.out.println($name + " is NOT adult");
modify($p) { setAge(18); }
drools.run( AdultUnit.class );
end
----
Also suppose to have a ``RuleUnitExecutor`` created from the ``KieBase`` built out of these rules and a ``DataSource`` of Persons
bound to it.
[source,java]
----
RuleUnitExecutor executor = RuleUnitExecutor.create().bind( kbase );
DataSource<Person> persons = executor.newDataSource( "persons",
new Person( "Mario", 42 ),
new Person( "Marilena", 44 ),
new Person( "Sofia", 4 ) );
----
Note that in this case we are creating the ``DataSource`` directly out of the ``RuleUnitExecutor`` and binding it to the
"persons" variable in a single statement.
At this point trying to execute the NotAdultUnit unit we obtain the following output:
[source]
----
Sofia is NOT adult
Mario is adult
Marilena is adult
Sofia is adult
----
In fact the NotAdult rule finds a match when evaluating the person "Sofia" who has an age lower than 18. Then it modifies
her age to 18 and with the statement ``drools.run( AdultUnit.class )`` triggers the execution of the other unit which has a
rule that now can fire for all the 3 persons in the ``DataSource``. This means that the ``drools.run()`` statement inside a
consequence is the way to imperatively interrupt the execution of a rule unit and cede the control to a different rule unit.
Conversely the ``drools.guard()`` statement allows to declaratively schedule the execution of another rule unit when the
condition in the LHS of the rule containing that statement is met. More precisely, using this mechanism a rule in a given
rule unit acts as a guard for a different unit. This means that, when the rule engine produces at least one match for the LHS
of the guarding rule, the guarded RuleUnit is considered active. Of course a RuleUnit can have more than one guarding rule.
Let's see how this works with another practical example. Suppose of having a simple ``BoxOffice`` class
[source,java]
----
public class BoxOffice {
private boolean open;
public BoxOffice( boolean open ) {
this.open = open;
}
public boolean isOpen() {
return open;
}
public void setOpen( boolean open ) {
this.open = open;
}
}
----
and a ``BoxOfficeUnit`` with a data source of box offices.
[source,java]
----
public class BoxOfficeUnit implements RuleUnit {
private DataSource<BoxOffice> boxOffices;
public DataSource<BoxOffice> getBoxOffices() {
return boxOffices;
}
}
----
We introduce now the requirement to keep selling tickets for the event as long as there is at least one opened box office.
To achieve this let's define a second unit with a ``DataSource`` of person and a second one of tickets.
[source,java]
----
public class TicketIssuerUnit implements RuleUnit {
private DataSource<Person> persons;
private DataSource<AdultTicket> tickets;
private List<String> results;
public TicketIssuerUnit() { }
public TicketIssuerUnit( DataSource<Person> persons, DataSource<AdultTicket> tickets ) {
this.persons = persons;
this.tickets = tickets;
}
public DataSource<Person> getPersons() {
return persons;
}
public DataSource<AdultTicket> getTickets() {
return tickets;
}
public List<String> getResults() {
return results;
}
}
----
Then we can define a first rule in the BoxOfficeUnit that guards for this second unit.
[source]
----
package org.mypackage.myunit;
unit BoxOfficeUnit;
rule BoxOfficeIsOpen when
$box: /boxOffices{ open }
then
drools.guard( TicketIssuerUnit.class );
end
----
In this way we achieved what we have anticipated: by running the BoxOfficeUnit at some point it will also evaluates the
rules in the TicketIssuerUnit defined as
[source]
----
package org.mypackage.myunit;
unit TicketIssuerUnit;
rule IssueAdultTicket when
$p: /persons{ age >= 18 }
then
tickets.insert(new AdultTicket($p));
end
rule RegisterAdultTicket when
$t: /tickets
then
results.add( $t.getPerson().getName() );
end
----
that is guarded by the BoxOfficeIsOpen rule, until there will exist at least a set of facts satisfying the LHS patterns
of that rule. In other terms the existence of at least one open box office will keep the guarding rule and in turn its
guarded unit active as it is evident in the following use case.
[source,java]
----
DataSource<Person> persons = executor.newDataSource( "persons" );
DataSource<BoxOffice> boxOffices = executor.newDataSource( "boxOffices" );
DataSource<AdultTicket> tickets = executor.newDataSource( "tickets" );
List<String> list = new ArrayList<>();
executor.bindVariable( "results", list );
// two open box offices
BoxOffice office1 = new BoxOffice(true);
FactHandle officeFH1 = boxOffices.insert( office1 );
BoxOffice office2 = new BoxOffice(true);
FactHandle officeFH2 = boxOffices.insert( office2 );
persons.insert(new Person("Mario", 40));
// fire BoxOfficeIsOpen -> run TicketIssuerUnit -> fire RegisterAdultTicket
executor.run(BoxOfficeUnit.class);
assertEquals( 1, list.size() );
assertEquals( "Mario", list.get(0) );
list.clear();
persons.insert(new Person("Matteo", 30));
executor.run(BoxOfficeUnit.class); // fire RegisterAdultTicket
assertEquals( 1, list.size() );
assertEquals( "Matteo", list.get(0) );
list.clear();
// close one box office, the other is still open
office1.setOpen(false);
boxOffices.update(officeFH1, office1);
persons.insert(new Person("Mark", 35));
executor.run(BoxOfficeUnit.class);
assertEquals( 1, list.size() );
assertEquals( "Mark", list.get(0) );
list.clear();
// all box offices, are now closed
office2.setOpen(false);
boxOffices.update(officeFH2, office2); // guarding rule no longer true
persons.insert(new Person("Edson", 35));
executor.run(BoxOfficeUnit.class); // no fire
assertEquals( 0, list.size() );
----
=== RuleUnit identity
Since a rule can guard multiple rule units and at the same time a unit can be guarded and then activated by multiple rules,
it is necessary to clearly define what is the identity of a given unit. By the default the identity of a unit is simply the
rule unit class. This is encoded in the ``getUnitIdentity()`` default method of the ``RuleUnit`` interface
[source,java]
----
default Identity getUnitIdentity() {
return new Identity( getClass() );
}
----
and implies that each unit is threated as a singleton by the ``RuleUnitExecutor``. To demonstrate this let's suppose of
having a simple ``RuleUnit`` class with only a ``DataSource`` accepting any kind of object
[source,java]
----
public class Unit0 implements RuleUnit {
private DataSource<Object> input;
public DataSource<Object> getInput() {
return input;
}
}
----
together with a rule belonging to this unit that guards another unit using 2 different conditions.
[source]
----
package org.mypackage.myunit
unit Unit0
rule GuardAgeCheck when
$i: /input{ #Integer }
$s: /input{ #String }
then
drools.guard( new AgeCheckUnit($i) );
drools.guard( new AgeCheckUnit($s.length()) );
end
----
This second ``RuleUnit`` is intended to check the age of a set of persons. Then it has a ``DataSource`` of the persons to check,
a minAge variable against which doing this check and a list were accumulating the results
[source,java]
----
public class AgeCheckUnit implements RuleUnit {
private final int minAge;
private DataSource<Person> persons;
private List<String> results;
public AgeCheckUnit( int minAge ) {
this.minAge = minAge;
}
public DataSource<Person> getPersons() {
return persons;
}
public int getMinAge() {
return minAge;
}
public List<String> getResults() {
return results;
}
}
----
while the corresponding rule actually performing the check of the persons in the ``DataSource`` is the following:
[source]
----
package org.mypackage.myunit
unit AgeCheckUnit
rule CheckAge when
$p : /persons{ age > minAge }
then
results.add($p.getName() + ">" + minAge);
end
----
At this point we can create a ``RuleUnitExecutor``, bind it to the knowledge base containing these 2 units and also create
the 2 ``DataSource``s to feed the same units.
[source,java]
----
RuleUnitExecutor executor = RuleUnitExecutor.create().bind( kbase );
DataSource<Object> input = executor.newDataSource( "input" );
DataSource<Person> persons = executor.newDataSource( "persons",
new Person( "Mario", 42 ),
new Person( "Sofia", 4 ) );
List<String> results = new ArrayList<>();
executor.bindVariable( "results", results );
----
We are now ready to insert some objects into the input data source and execute the Unit0.
[source,java]
----
ds.insert("test");
ds.insert(3);
ds.insert(4);
executor.run(Unit0.class);
----
As outcome of this execution the results list will contain the following:
[source,java]
----
[Sofia>3, Mario>3]
----
As anticipated the rule unit named AgeCheckUnit is seen as a singleton and then executed only once, this time with ``minAge``
equals to 3 (but this is not deterministic). Both the String "test" and the Integer 4 inserted into the input data source
could also trigger a second execution with ``minAge`` set to 4, but this is not happening because another unit with the same
identity has been already evaluated. To fix this problem it is enough to override the ``getUnitIdentity()`` method in the
``AgeCheckUnit`` class to also include the variable minAge in its identity.
[source,java]
----
public class AgeCheckUnit implements RuleUnit {
...
@Override
public Identity getUnitIdentity() {
return new Identity(getClass(), minAge);
}
}
----
Having done so, the units with minAge 3 and 4 are considered two different units and then both evaluated, so trying to rerun
the former example the result list will now contain
[source,java]
----
[Mario>4, Sofia>3, Mario>3]
----