JBoss.orgCommunity Documentation
Support has been added for the "timer" and "calendar" attributes.
Table 4.1. New attributes
Keyword | Initial | Value |
---|---|---|
TIMER | T | A timer definition. See "Timers and Calendars". |
CALENDARS | E | A calendars definition. See "Timers and Calendars". |
KnowledgeBuilder has a new batch mode, with a fluent interface, that allows to build multiple DRLs at once as in the following example:
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder(); kbuilder.batch() .add(ResourceFactory.newByteArrayResource(rules1.getBytes()), ResourceType.DRL) .add(ResourceFactory.newByteArrayResource(rules2.getBytes()), ResourceType.DRL) .add(ResourceFactory.newByteArrayResource(declarations.getBytes()), ResourceType.DRL) .build();
In this way it is no longer necessary to build the DRLs files in the right order (e.g. first the DRLs containing the type declarations and then the ones with the rules using them) and it will also be possible to have circular references among them.
Moreover the KnowledgeBuilder (regardless if you are using the batch mode or not) also allows to discard what has been added with the last DRL(s) building. This can be useful to recover from having added a wrong DRL to the KnowledgeBuilder as it follows:
kbuilder.add(ResourceFactory.newByteArrayResource(wrongDrl.getBytes()), ResourceType.DRL); if ( kbuilder.hasErrors() ) { kbuilder.undo(); }
In Drools when you invoke update() or modify() on a given object it will trigger a revaluation of all patterns of the matching object type in the knowledge base. As some have experienced, this can be a problem that often can lead to unwanted and useless evaluations and in the worst cases to infinite recursions. The only workaround to avoid it was to split up your objects into smaller ones having a 1 to 1 relationship with the original object.
This new feature allows the pattern matching to only react to modification of properties actually constrained or bound inside of a given pattern. That will help with performance and recursion and avoid artificial object splitting.
By default this feature is off in order to make the behavior of the rule engine backward compatible with the former releases. When you want to activate it on a specific bean you have to annotate it with @propertyReactive. This annotation works both on drl type declarations:
declare Person @propertyReactive firstName : String lastName : String end
and on Java classes:
@PropertyReactive public static class Person { private String firstName; private String lastName; }
In this way, for instance, if you have a rule like the following:
rule "Every person named Mario is a male" when $person : Person( firstName == "Mario" ) then modify ( $person ) { setMale( true ) } end
you won't have to add the no-loop attribute to it in order to avoid an infinite recursion because the engine recognizes that the pattern matching is done on the 'firstName' property while the RHS of the rule modifies the 'male' one. Note that this feature does not work for update(), and this is one of the reasons why we promote modify() since it encapsulates the field changes within the statement. Moreover, on Java classes, you can also annotate any method to say that its invocation actually modifies other properties. For instance in the former Person class you could have a method like:
@Modifies( { "firstName", "lastName" } ) public void setName(String name) { String[] names = name.split("\\s"); this.firstName = names[0]; this.lastName = names[1]; }
That means that if a rule has a RHS like the following:
modify($person) { setName("Mario Fusco") }
it will correctly recognize that the values of both properties 'firstName' and 'lastName' could have potentially been modified and act accordingly, not missing of reevaluating the patterns constrained on them. At the moment the usage of @Modifies is not allowed on fields but only on methods. This is coherent with the most common scenario where the @Modifies will be used for methods that are not related with a class field as in the Person.setName() in the former example. Also note that @Modifies is not transitive, meaning that if another method internally invokes the Person.setName() one it won't be enough to annotate it with @Modifies( { "name" } ), but it is necessary to use @Modifies( { "firstName", "lastName" } ) even on it. Very likely @Modifies transitivity will be implemented in the next release.
For what regards nested accessors, the engine will be notified only for top level fields. In other words a pattern matching like:
Person ( address.city.name == "London )
will be revaluated only for modification of the 'address' property of a Person object. In the same way the constraints analysis is currently strictly limited to what there is inside a pattern. Another example could help to clarify this. An LHS like the following:
$p : Person( ) Car( owner = $p.name )
will not listen on modifications of the person's name, while this one will do:
Person( $name : name ) Car( owner = $name )
To overcome this problem it is possible to annotate a pattern with @watch as it follows:
$p : Person( ) @watch ( name ) Car( owner = $p.name )
Indeed, annotating a pattern with @watch allows you to modify the inferred set of properties for which that pattern will react. Note that the properties named in the @watch annotation are actually added to the ones automatically inferred, but it is also possible to explicitly exclude one or more of them prepending their name with a ! and to make the pattern to listen for all or none of the properties of the type used in the pattern respectively with the wildcrds * and !*. So, for example, you can annotate a pattern in the LHS of a rule like:
// listens for changes on both firstName (inferred) and lastName Person( firstName == $expectedFirstName ) @watch( lastName ) // listens for all the properties of the Person bean Person( firstName == $expectedFirstName ) @watch( * ) // listens for changes on lastName and explicitly exclude firstName Person( firstName == $expectedFirstName ) @watch( lastName, !firstName ) // listens for changes on all the properties except the age one Person( firstName == $expectedFirstName ) @watch( *, !age )
Since doesn't make sense to use this annotation on a pattern using a type not annotated with @PropertyReactive the rule compiler will raise a compilation error if you try to do so. Also the duplicated usage of the same property in @watch (for example like in: @watch( firstName, ! firstName ) ) will end up in a compilation error. In a next release we will make the automatic detection of the properties to be listened smarter by doing analysis even outside of the pattern.
It also possible to enable this feature by default on all the types of your model or to completely disallow it by using on option of the KnowledgeBuilderConfiguration. In particular this new PropertySpecificOption can have one of the following 3 values:
- DISABLED => the feature is turned off and all the other related annotations are just ignored - ALLOWED => this is the default behavior: types are not property reactive unless they are not annotated with @PropertySpecific - ALWAYS => all types are property reactive by default
So, for example, to have a KnowledgeBuilder generating property reactive types by default you could do:
KnowledgeBuilderConfiguration config = KnowledgeBuilderFactory.newKnowledgeBuilderConfiguration(); config.setOption(PropertySpecificOption.ALWAYS); KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder(config);
In this last case it will be possible to disable the property reactivity feature on a specific type by annotating it with @ClassReactive.
Using the new fluent simulation testing, you can test your rules in unit tests more easily:
@Test
public void rejectMinors() {
SimulationFluent simulationFluent = new DefaultSimulationFluent();
Driver john = new Driver("John", "Smith", new LocalDate().minusYears(10));
Car mini = new Car("MINI-01", CarType.SMALL, false, new BigDecimal("10000.00"));
PolicyRequest johnMiniPolicyRequest = new PolicyRequest(john, mini);
johnMiniPolicyRequest.addCoverageRequest(new CoverageRequest(CoverageType.COLLISION));
johnMiniPolicyRequest.addCoverageRequest(new CoverageRequest(CoverageType.COMPREHENSIVE));
simulationFluent
.newKnowledgeBuilder()
.add(ResourceFactory.newClassPathResource("org/drools/examples/carinsurance/rule/policyRequestApprovalRules.drl"),
ResourceType.DRL)
.end()
.newKnowledgeBase()
.addKnowledgePackages()
.end()
.newStatefulKnowledgeSession()
.insert(john).set("john")
.insert(mini).set("mini")
.insert(johnMiniPolicyRequest).set("johnMiniPolicyRequest")
.fireAllRules()
.test("johnMiniPolicyRequest.automaticallyRejected == true")
.test("johnMiniPolicyRequest.rejectedMessageList.size() == 1")
.end()
.runSimulation();
}
You can even test your CEP rules in unit tests without suffering from slow tests:
@Test
public void lyingAboutAge() {
SimulationFluent simulationFluent = new DefaultSimulationFluent();
Driver realJohn = new Driver("John", "Smith", new LocalDate().minusYears(10));
Car realMini = new Car("MINI-01", CarType.SMALL, false, new BigDecimal("10000.00"));
PolicyRequest realJohnMiniPolicyRequest = new PolicyRequest(realJohn, realMini);
realJohnMiniPolicyRequest.addCoverageRequest(new CoverageRequest(CoverageType.COLLISION));
realJohnMiniPolicyRequest.addCoverageRequest(new CoverageRequest(CoverageType.COMPREHENSIVE));
realJohnMiniPolicyRequest.setAutomaticallyRejected(true);
realJohnMiniPolicyRequest.addRejectedMessage("Too young.");
Driver fakeJohn = new Driver("John", "Smith", new LocalDate().minusYears(30));
Car fakeMini = new Car("MINI-01", CarType.SMALL, false, new BigDecimal("10000.00"));
PolicyRequest fakeJohnMiniPolicyRequest = new PolicyRequest(fakeJohn, fakeMini);
fakeJohnMiniPolicyRequest.addCoverageRequest(new CoverageRequest(CoverageType.COLLISION));
fakeJohnMiniPolicyRequest.addCoverageRequest(new CoverageRequest(CoverageType.COMPREHENSIVE));
fakeJohnMiniPolicyRequest.setAutomaticallyRejected(false);
simulationFluent
.newStep(0)
.newKnowledgeBuilder()
.add(ResourceFactory.newClassPathResource("org/drools/examples/carinsurance/cep/policyRequestFraudDetectionRules.drl"),
ResourceType.DRL)
.end()
.newKnowledgeBase()
.addKnowledgePackages()
.end(World.ROOT, KnowledgeBase.class.getName())
.newStatefulKnowledgeSession()
.end()
.newStep(1000)
.getStatefulKnowledgeSession()
.insert(realJohn).set("realJohn")
.insert(realMini).set("realMini")
.insert(realJohnMiniPolicyRequest).set("realJohnMiniPolicyRequest")
.fireAllRules()
.test("realJohnMiniPolicyRequest.requiresManualApproval == false")
.end()
.newStep(5000)
.getStatefulKnowledgeSession()
.insert(fakeJohn).set("fakeJohn")
.insert(fakeMini).set("fakeMini")
.insert(fakeJohnMiniPolicyRequest).set("fakeJohnMiniPolicyRequest")
.fireAllRules()
.test("fakeJohnMiniPolicyRequest.requiresManualApproval == true")
.end()
.runSimulation();
}
Support has been added for the "timer" and "calendar" attributes.
Support has been added for the "timer" and "calendar" attributes.
Uploading a XLS decision table results in the creation of numerous new assets, including (obviously) web-guided Decision Tables, functions, declarative types and modifications to package globals and imports etc (Queries are not converted, although supported in the XLS form, as Guvnor doesn't support them yet).
This is the first stage of "round-tripping" decision tables. We still need to add the ability to export a guided decision table back to XLS, plus we'd like to add tighter integration of updated XLS assets to their original converted cousins - so if a new version of the XLS decision table is uploaded the related assets' versions are updated (rather than creating new) upon conversion.
This is a powerful enhancement and as such your feedback is critical to ensure we implement the feature as you'd like it to operate. Check it out, feedback your opinions and help guide the future work.
Numerical "value editors" (i.e. the text boxes for numerical values) in the BRL, Rule Template, Test Scenarios and Decision Table editors now support the types Byte, Short, Integer, Long, Double, Float, BigDecimal and BigInteger (and their primitive counterparts) correctly. The generated DRL is automatically appended with "B" or "I" type classifiers for BigDecimal and BigInteger values respectively, as provided for by Drools Expert. The Right-hand Side generates applicable DRL for BigDecimal and BigInteger values according to the rule's dialect.
Dependent enumerations can now be used in both the Web Guided Decision Table editor and the Rule Template Data grid. Furthermore improvements were made to the operation of dependent enumerations in the BRL Guided Rule editor for sub-fields and expressions.
The editor to define a default value has been greatly improved:-
A default value editor is correct for the data-type of the column's Fact\.
If a "Value List" is provided, the default value needs to be one of the values in the list.
If the column represents a field with an enumeration the default value must be one of the enumeration's members.
If the column uses an operator that does not need a value (e.g. "is null") a default value cannot be provided.
If the column field is a "dependent enumeration" the default value must be one of the permitted values based upon parent enumeration default values, if any.
Default values are not required for Limited Entry tables.
This is an implementation of the classic Traveling Salesman Problem: given a list of cities, find the shortest tour for a salesman that visits each city exactly once.
See this video.
This is an implementation of capacitated vehicle routing: Using a fleet of vehicles, transport items from the depot(s) to customers at different locations. Each vehicle can service multiple locations, but it has a limited capacity for items.
In the screenshot below, there are 6 vehicles (the lines in different colors) that drop off 541 items at 33 customer locations. Each vehicle can carry 100 items.
See this video.
The employee rostering example's GUI has been reworked to show shift assignment more clearly.
See this video.
Untill now, implementing TSP or Vehicle Routing like problems in Planner was hard. The new chaining support makes it easy.
You simply declare that a planning variable (previousAppearance
) of this
planning entity
(VrpCustomer
) is chained and therefor possibly referencing another planning
entity
(VrpCustomer
) itself, creating a chain with that entity.
public class VrpCustomer implements VrpAppearance {
...
@PlanningVariable(chained = true)
@ValueRanges({
@ValueRange(type = ValueRangeType.FROM_SOLUTION_PROPERTY, solutionProperty = "vehicleList"),
@ValueRange(type = ValueRangeType.FROM_SOLUTION_PROPERTY, solutionProperty = "customerList",
excludeUninitializedPlanningEntity = true)})
public VrpAppearance getPreviousAppearance() {
return previousAppearance;
}
...
}
This triggers automatic chain correction:
Without any extra boilerplate code, this is compatible with:
Every optimization algorithm: including construction heuristics (first fit, first fit decreasing, ...), local search (tabu search, simmulated annealing), ...
The generic build-in move factories. Note: currently there are chained alternatives for each move factory, but those will be unified with the originals soon.
Repeated planning, including real-time planning
For more information, read the Planner reference manual.
Property tabu has been renamed to planning entity tabu. Planning value tabu has been added. The generic moves support this out-of-the-box.
<acceptor>
<planningValueTabuSize>5</planningValueTabuSize>
</acceptor>
Planner can now alternatively, use a score calculation written in plain Java. Just implement this interface:
public interface SimpleScoreCalculator<Sol extends Solution> {
Score calculateScore(Sol solution);
}
See the CloudBalance example for an implementation.
In this way, Planner does not use Drools at all. This allows you to:
Use Planner, even if your company forbids any other language than Java (including DRL).
Hook Planner up to an existing score calculation system, which you don't want to migrate to DRL at this time.
Or just use Java if you prefer that over DRL.
Currently, there is only one Java way implemented: SimpleScoreCalculator
, which
is slow.
An IncrementalScoreCalculator
will be implemented soon.
The documentation now has a new chapter Quick start tutorial. It explains how to write the CloudBalance example from scratch. Read the Planner reference manual.