| [[_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] |
| ---- |