JBoss.orgCommunity Documentation
I've been working in the rules field now for around 7 years and I finally feel like I'm getting to grips with things and ideas are starting to gel and the real innovation is starting to happen. To me It feels like we actually know what we are doing now, compared to the past where there was a lot of wild guessing and exploration. I've been working hard on the next generation Drools Expert design document with Edson Tirelli and Davide Sottara. I invite you to read the document and get involved, http://community.jboss.org/wiki/DroolsLanguageEnhancements. The document takes things to the next level pushing Drools forward as a hybrid engine, not just a capable production rule system, but also melding in logic programming (prolog) with functional programming and description logic along with a host of other ideas for a more expressive and modern feeling language.
I hope you can feel the passion that my team and I have while working on Drools, and that some of it rubs off on you during your adventures.
The following is a description of the important libraries that make up JBoss Drools
The jars are also available in the central maven repository (and also in the JBoss maven repository).
If you use Maven, add KIE and Drools dependencies in your project's pom.xml
like
this:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-bom</artifactId>
<type>pom</type>
<version>...</version>
<scope>import</scope>
</dependency>
...
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-api</artifactId>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-compiler</artifactId>
<scope>runtime</scope>
</dependency>
...
<dependencies>
This is similar for Gradle, Ivy and Buildr. To identify the latest version, check the maven repository.
If you're still using ANT (without Ivy), copy all the jars from the download zip's
binaries
directory and manually verify that your classpath doesn't contain duplicate
jars.
Open the Help->Software updates...->Available Software->Add Site... from the help menu. Location is:
http://download.eclipse.org/tools/gef/updates/releases/
http://www.jboss.org/drools/downloads.html
To check that the installation was successful, try opening the Drools perspective: Click the 'Open Perspective' button in the top right corner of your Eclipse window, select 'Other...' and pick the Drools perspective. If you cannot find the Drools perspective as one of the possible perspectives, the installation probably was unsuccessful. Check whether you executed each of the required steps correctly: Do you have the right version of Eclipse (3.4.x)? Do you have Eclipse GEF installed (check whether the org.eclipse.gef_3.4.*.jar exists in the plugins directory in your eclipse root folder)? Did you extract the Drools Eclipse plugin correctly (check whether the org.drools.eclipse_*.jar exists in the plugins directory in your eclipse root folder)? If you cannot find the problem, try contacting us (e.g. on irc or on the user mailing list), more info can be found no our homepage here:
Drools and jBPM use Git for source control. The blessed git repositories are hosted on Github:
Git allows you to fork our code, independently make personal changes on it, yet still merge in our latest changes regularly and optionally share your changes with us. To learn more about git, read the free book Git Pro.
In essense, building from source is very easy, for example if you want to build the guvnor project:
$ git clone git@github.com:droolsjbpm/guvnor.git ... $ cd guvnor $ mvn clean install -DskipTests -Dfull ...
However, there are a lot potential pitfalls, so if you're serious about building from source and possibly contributing to the project, follow the instructions in the README file in droolsjbpm-build-bootstrap.
We are often asked "How do I get involved". Luckily the answer is simple, just write some code and submit it :) There are no hoops you have to jump through or secret handshakes. We have a very minimal "overhead" that we do request to allow for scalable project development. Below we provide a general overview of the tools and "workflow" we request, along with some general advice.
If you contribute some good work, don't forget to blog about it :)
Signing to jboss.org will give you access to the JBoss wiki, forums and Jira. Go to http://www.jboss.org/ and click "Register".
Many things are changing for Drools 6.0.
Along with the functional and feature changes we have restructured the Guvnor github repository to better reflect our new architecture. Guvnor has historically been the web application for Drools. It was a composition of editors specific to Drools, a back-end repository and a simplistic asset management system.
Things are now different.
For Drools 6.0 the web application has been extensively re-written to use UberFire that provides a generic Workbench environment, a Metadata Engine, Security Framework, a VFS API and clustering support.
Guvnor has become a generic asset management framework providing common services for generic projects and their dependencies. Drools use of both UberFire and Guvnor has born the Drools Workbench.
A picture always helps:
UberFire is the foundation of all components for both Drools and jBPM. Every editor and service leverages UberFire. Components can be mixed and matched into either a full featured application of used in isolation.
Guvnor adds project services and dependency management to the mix.
At present Guvnor consists of few parts; being principally a port of common project services that existed in the old Guvnor. As things settle down and the module matures pluggable workflow will be supported allowing for sensitive operations to be controlled by jBPM processes and rules. Work is already underway to include this for 6.0.
Both Drools and jBPM editors and services share the need for a common set of re-usable screens, services and widgets.
Rather than pollute Guvnor with screens and services needed only by Drools and jBPM this module contains such common dependencies.
It is possible to just re-use the UberFire and Guvnor stack to create your own project-based workbench type application and take advantage of the underlying services.
Drools Workbench is the end product for people looking for a web application that is composed of all Drools related editors, screens and services. It is equivalent to the old Guvnor.
Looking for the web application to accompany Drools Expert and Drools Fusion; an environment to author, test and deploy rules. This is what you're looking for.
KIE Drools Workbench (for want of a better name - it's amazing how difficult names can be) is an extension of Drools Workbench including jBPM Designer to support Rule Flow.
jBPM Designer, now being an UberFire compatible component, does not need to be deployed as a separate web application. We bundle it here, along with Drools as a convenience for people looking to author Rule Flows along side their rules.
This is the daddy of them all.
KIE Workbench is the composition of everything known to man; from both the Drools and jBPM worlds. It provides for authoring of projects, data models, guided rules, decision tables etc, test services, process authoring, a process run-time execution environment and human task interaction.
KIE Workbench is the old Guvnor, jBPM Designer and jBPM Console applications combined. On steroids.
UberFire VFS is a NIO.2 based API that provides a unified interface to access different file systems - by default it uses Git as its primary backend implematation. Resources (ie. files and directories) are represented, as proposed by NIO.2, as URIs. Here are some examples:
file:///path/to/some/file.txt
git://repository-name/path/to/file.txt
git://repository-name/path/to/dir/
git://my_branch@repository-name/path/to/file.txt
By using Git as its default implementation, UberFire VFS provides out-of-box a versioned system, which means that every information stored/deleted has its history preserved.
Your system doesn't need to have GIT installed, UberFire uses JGIT internally, a 100% Java based Git implementation.
For those curious to know what is under the hood, Metadata engine uses Apache Lucene 4 as its default backend, but was designed to be able to have different and eventually mixed/composed backends.
Drools Workbench is the corner stone of the new function and features for 6.0.
Many of the new concepts and functions are described in more detail in the following sections.
Drools Workbench starts with a Home Page that will provides short-cut navigation to common tasks.
The appearance and content of the Home is likely to change as we approach a final release.
The Project Explorer is where most users will find themselves when authoring projects.
The Project Explorer will support both a Business and Technical views. The Technical view will probably not make it into 6.0.0.Beta4.
The basic aspect of the Data Modeller is shown in the following screenshot:
The Data Modeller screen is divided into the following sections:
Model browser: the leftmost section, which allows creation of new data objects and where the existing ones are listed.
Object browser: the middle section, which displays a table with the fields of the data object that has been selected in the Model browser. This section also enables creating new attributes for the currently selected object.
Object / Attribute editor: the rightmost section. This is a tabbed editor where either the currently selected object's properties (as currently shown in the screenshot), or a previously selected object attribute's properties, can be modified.
Whenever a data model screen is activated, an additional entry will appear in the top menu, allowing creation of new data objects, as well as saving the model's modifications. Saving the model will also generate all its assets (pojo's), which will then become available to the rest of the tools.
Upload, download and manage KJars with Guvnor M2 repository.
Upload KJars to Guvnor M2 repository:
A KJar has a pom.xml (and pom.properties) that define the GAV and POJO model dependencies. If the pom.properties is missing the user is prompted for the GAV and a pom.properties file is appended to the JAR before being uploaded to Guvnor M2 repo.
The Guvnor M2 REPO is exposed via REST with URL pattern http://{ServerName}/{httpPort}/{droolsWBWarFilename}/rest
jcr2vfs is a cli tool that migrates legancys Guvnor repository to 6.0.
usage: runMigration [options...]
-f,--forceOverwriteOutputVfsRepository Force overwriting the Guvnor 6 VFS repository
-h,--help help for the command.
OptaPlanner has a new website (http://www.optaplanner.org), a new groupId/artifactId and its own IRC channel. It's a rename, not a fork. It's still the same license (ASL), same team, ...
For more information, see the full announcement.
The new ConstraintMatch system is:
rule "conflictingLecturesSameCourseInSamePeriod"
when
...
then
insertLogical(new IntConstraintOccurrence("conflictingLecturesSameCourseInSamePeriod", ConstraintType.HARD,
-1,
$leftLecture, $rightLecture));
end
rule "conflictingLecturesSameCourseInSamePeriod"
when
...
then
scoreHolder.addHardConstraintMatch(kcontext, -1);
end
For more information, see the blog post and the manual.
For more information, see the blog post and the manual.
VRP subchain selection improved if maximumSubChainSize
is set.
The Benchmarker now highlights infeasible solutions with an orange exclamation mark.
The Benchmarker now shows standard deviation per solver configuration (thanks to Miroslav Svitok).
BendableScore
is now compatible with XStream: use
XStreamBendableScoreConverter.
Drools properties can now be optionally specified in the solver configuration XML.
We already allow nested accessors to be used as follows, where address is the nested object:
Person( name == "mark", address.city == "london", address.country == "uk" )
Person( name== "mark", address.( city == "london", country == "uk") )
Person( name=="mark", address#LongAddress.country == "uk" )
Person( name=="mark", address#org.domain.LongAddress.country == "uk" )
It is possible to use multiple inline casts in the same expression:
Person( name == "mark", address#LongAddress.country#DetailedCountry.population > 10000000 )
Person( name=="mark", address instanceof LongAddress, address.country == "uk" )
rule "Give 10% discount to customers older than 60" when $customer : Customer( age > 60 ) then modify($customer) { setDiscount( 0.1 ) }; end rule "Give free parking to customers older than 60" when $customer : Customer( age > 60 ) $car : Car ( owner == $customer ) then modify($car) { setFreeParking( true ) }; end
rule "Give 10% discount to customers older than 60" when $customer : Customer( age > 60 ) then modify($customer) { setDiscount( 0.1 ) }; end rule "Give free parking to customers older than 60" extends "Give 10% discount to customers older than 60" when $car : Car ( owner == $customer ) then modify($car) { setFreeParking( true ) }; end
rule "Give 10% discount and free parking to customers older than 60" when $customer : Customer( age > 60 ) do[giveDiscount] $car : Car ( owner == $customer ) then modify($car) { setFreeParking( true ) }; then[giveDiscount] modify($customer) { setDiscount( 0.1 ) }; end
rule "Give free parking to customers older than 60 and 10% discount to golden ones among them" when $customer : Customer( age > 60 ) if ( type == "Golden" ) do[giveDiscount] $car : Car ( owner == $customer ) then modify($car) { setFreeParking( true ) }; then[giveDiscount] modify($customer) { setDiscount( 0.1 ) }; end
rule "Give free parking and 10% discount to over 60 Golden customer and 5% to Silver ones" when $customer : Customer( age > 60 ) if ( type == "Golden" ) do[giveDiscount10] else if ( type == "Silver" ) break[giveDiscount5] $car : Car ( owner == $customer ) then modify($car) { setFreeParking( true ) }; then[giveDiscount10] modify($customer) { setDiscount( 0.1 ) }; then[giveDiscount5] modify($customer) { setDiscount( 0.05 ) }; end
The audit log is persisted whenever the asset is checked in.
Once the capture of events has been enabled all subsequent operations are recorded. Users are able to perform the following:
Record an explanatory note beside each event.
Delete an event from the log. Event details remain in the underlying repository.
The simulation project that was first started in 2009, http://blog.athico.com/2009/07/drools-simulation-and-test-framework.html,
has undergone an over haul and is now in a usable state. We have not yet promoted this to
knowledge-api
, so it's considered unstable and will change during the beta process. For now
though, the adventurous people can take a look at the unit tests and start playing.
The Simulator runs the Simulation. The Simulation is your scenario definition. The Simulation consists of 1
to n Paths, you can think of a Path as a sort of Thread. The Path is a chronological line on which Steps are
specified at given temporal distances from the start. You don't specify a time unit for the Step, say 12:00am,
instead it is always a relative time distance from the start of the Simulation (note: in Beta2 this will be
relative time distance from the last step in the same path). Each Step contains one or more Commands, i.e. create
a StatefulKnowledgeSession
or insert an object or start a process. These are the very same
commands that you would use to script a knowledge session using the batch execution, so it's re-using existing
concepts.
1.1 Simulation
1..n Paths
1..n Steps
1..n Commands
All the steps, from all paths, are added to a priority queue which is ordered by the temporal distance, and allows us to incrementally execute the engine using a time slicing approach. The simulator pops of the steps from the queue in turn. For each Step it increments the engine clock and then executes all the Step's Commands.
Here is an example Command (notice it uses the same Commands as used by the CommandExecutor):
new InsertObjectCommand( new Person( "darth", 97 ) )
Commands can be grouped together, especially Assertion commands, via test groups. The test groups are mapped to JUnit "test methods", so as they pass or fail using a specialised JUnit Runner the Eclipse GUI is updated - as illustrated in the above image, showing two passed test groups named "test1" and "test2".
Using the JUnit integration is trivial. Just annotate the class with @RunWith(JUnitSimulationRunner.class). Then any method that is annotated with @Test and returns a Simulation instance will be invoked executing the returned Simulation instance in the Simulator. As test groups are executed the JUnit GUI is updated.
When executing any commands on a KnowledgeBuilder, KnowledgeBase or StatefulKnowledgeSession the system assumes a "register" approach. To get a feel for this look at the org.drools.simulation.impl.SimulationTest at github (path may change over time).
cmds.add( new NewKnowledgeBuilderCommand( null ) ); cmds.add( new SetVariableCommandFromLastReturn( "path1", KnowledgeBuilder.class.getName() ) ); cmds.add( new KnowledgeBuilderAddCommand( ResourceFactory.newByteArrayResource( str.getBytes() ), ResourceType.DRL, null ) );
Notice the set command. "path1" is the context, each path has it's own variable context. All paths inherit from a "root" context. "KnowledgeBuilder.class.getName() " is the name that we are setting the return value of the last command. As mentioned before we consider the class names of those classes as registers, any further commands that attempt to operate on a knowledge builder will use what ever is assigned to that, as in the case of KnowledgeBuilderAddCommand. This allows multiple kbuilders, kbases and ksessions to exist in one context under different variable names, but only the one assigned to the register name is the one that is currently executed on.
The code below show the rough outline used in SimulationTest:
Simulation simulation = new SimulationImpl(); PathImpl path = new PathImpl( simulation, "path1" ); simulation.getPaths().put( "path1", path ); List<Step> steps = new ArrayList<Step>(); path.setSteps( steps ); List<Command> cmds = new ArrayList<Command>(); .... add commands to step here .... // create a step at temporal distance of 2000ms from start steps.add( new StepImpl( path, cmds, 2000 ) );
We know the above looks quite verbose. SimulationTest just shows our low level canonical model, the idea is that high level representations are built ontop of this. As this is a builder API we are currently focusing on two sets of fluents, compact and standard. We will also work on a spreadsheet UI for building these, and eventually a dedicated textual dsl.
The compact fluent is designed to provide the absolute minimum necessary to run against a single ksession. A good place to start is org.drools.simulation.impl.CompactFluentTest, a snippet of which is shown below. Notice we set "yoda" to "y" and can then assert on that. Currently inside of the test string it executes using mvel. The eventual goal is to build out a set of hamcrest matchers that will allow assertions against the state of the engine, such as what rules have fired and optionally with with data.
FluentCompactSimulation f = new FluentCompactSimulationImpl(); f.newStatefulKnowledgeSession() .getKnowledgeBase() .addKnowledgePackages( ResourceFactory.newByteArrayResource( str.getBytes() ), ResourceType.DRL ) .end() .newStep( 100 ) // increases the time 100ms .insert( new Person( "yoda", 150 ) ).set( "y" ) .fireAllRules() // show testing inside of ksession execution .test( "y.name == 'yoda'" ) .test( "y.age == 160" );
Note that the test is not executing at build time, it's building a script to be executed later. The script underneath matches what you saw in SimulationTest. Currently the way to run a simulation manually is shown below. Although you already saw in SimulationTest that JUnit will execute these automatically. We'll improve this over time.
SimulationImpl sim = (SimulationImpl) ((FluentCompactSimulationImpl) f).getSimulation(); Simulator simulator = new Simulator( sim, new Date().getTime() ); simulator.run();
The standard fluent is almost a 1 to 1 mapping to the canonical path, step and command structure in SimulationTest- just more compact. Start by looking in org.drools.simulation.impl.StandardFluentTest. This fluent allows you to run any number of paths and steps, along with a lot more control over multiple kbuilders, kbases and ksessions.
FluentStandardSimulation f = new FluentStandardSimulationImpl(); f.newPath("init") .newStep( 0 ) // set to ROOT, as I want paths to share this .newKnowledgeBuilder() .add( ResourceFactory.newByteArrayResource( str.getBytes() ), ResourceType.DRL ) .end(ContextManager.ROOT, KnowledgeBuilder.class.getName() ) .newKnowledgeBase() .addKnowledgePackages() .end(ContextManager.ROOT, KnowledgeBase.class.getName() ) .end() .newPath( "path1" ) .newStep( 1000 ) .newStatefulKnowledgeSession() .insert( new Person( "yoda", 150 ) ).set( "y" ) .fireAllRules() .test( "y.name == 'yoda'" ) .test( "y.age == 160" ) .end() .end() .newPath( "path2" ) .newStep( 800 ) .newStatefulKnowledgeSession() .insert( new Person( "darth", 70 ) ).set( "d" ) .fireAllRules() .test( "d.name == 'darth'" ) .test( "d.age == 80" ) .end() .end() .end
There is still an awful lot to do, this is designed to eventually provide a unified simulation and testing environment for rules, workflow and event processing over time, and eventually also over distributed architectures.
Flesh out the api to support more commands, and also to encompass jBPM commands
Improve out of the box usability, including moving interfaces to knowledge-api and hiding "new" constructors with factory methods
Commands are already marshallable to json and xml. They should be updated to allow full round tripping from java api commands and json/xml documents.
Develop hamcrest matchers for testing state
What rule(s) fired, including optionally what data was used with the executing rule (Drools)
What rules are active for a given fact
What rules activated and de-activated for a given fact change
Process variable state (jBPM)
Wait node states (jBPM)
Design and build tabular authoring tools via spreadsheet, targeting the web with round tripping to excel.
Design and develop textual DSL for authoring - maybe part of DRL (long term task).
Multi-function accumulate now supports inline constraints. The simplified EBNF is:
lhsAccumulate := ACCUMULATE LEFT_PAREN lhsAnd (COMMA|SEMICOLON) accumulateFunctionBinding (COMMA accumulateFunctionBinding)* (SEMICOLON constraints)? RIGHT_PAREN SEMICOLON?
rule "Accumulate example" when accumulate( Cheese( $price : price ); $a1 : average( $price ), $m1 : min( $price ), $M1 : max( $price ); // a semicolon, followed by inline constraints $a1 > 10 && $M1 <= 100, // inline constraint $m1 == 5 // inline constraint ) then // do something end
A new RuleSet property has been added called "Declare".
This provides a slot in the RuleSet definition to define declared types.
A working version of Wumpus World, an AI example covered in in the book "Artificial Intelligence : A Modern Approach", is now available among the other examples. A more detailed overview of Wumpus World can be found here
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();
kbuilder.add(ResourceFactory.newByteArrayResource(wrongDrl.getBytes()), ResourceType.DRL); if ( kbuilder.hasErrors() ) { kbuilder.undo(); }
declare Person @propertyReactive firstName : String lastName : String end
@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
@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") }
Person ( address.city.name == "London )
$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 )
// 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 )
- 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
KnowledgeBuilderConfiguration config = KnowledgeBuilderFactory.newKnowledgeBuilderConfiguration(); config.setOption(PropertySpecificOption.ALWAYS); KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder(config);
This API is experimental: future backwards incompatible changes are possible.
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();
}
The simplified EBNF to declare an entry-point is:
entryPointDeclaration := DECLARE ENTRY-POINT epName annotation* END epName := stringId
declare entry-point STStream @doc("A stream of StockTicks") end
The simplified EBNF to declare a window is:
windowDeclaration := DECLARE WINDOW ID annotation* lhsPatternBind END
declare window Ticks @doc("last 10 stock ticks") StockTick( source == "NYSE" ) over window:length( 10 ) from entry-point STStream end
Rules can then use the declared window by referencing using a FROM CE. Example:
rule "RHT ticks in the window" when accumulate( StockTick( symbol == "RHT" ) from window Ticks, $cnt : count(1) ) then // there has been $cnt RHT ticks over the last 10 ticks end
This release brings the ability to define Action columns to retract Facts.
If however you are authoring a Limited Entry decision table the Fact being retracted is defined in the column definition.
BRL fragments can now be used for Condition and/or Action columns.
A BRL fragment is a section of a rule created using Guvnor's (BRL) Guided Rule Editor: Condition columns permit the definition of "WHEN" sections and Action columns the definition of "THEN" sections. Fields defined therein as "Template Keys" become columns in the decision table.
Consequently any rule that could be defined with the (BRL) Guided Rule Editor can now be defined with a decision table; including free-format DRL and DSL Sentences.
BRL fragments are fully integrated with other columns in the decision table, so that a Pattern or field defined in a regular column can be referenced in the BRL fragments and vice-versa.
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.
The editor to define a default value has been greatly improved:-
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:
The basic idea is:
All matched rule's Activations are inserted into WorkingMemory as facts. So you can now match against an Activation. The rule's metadata and declarations are available as fields on the Activation object.
You can use the kcontext.blockActivation( Activation match ) for the current rule to block the selected activation. Only when that rule becomes false will the activation be eligible for firing. If it is already eligible for firing and is later blocked, it will be removed from the agenda until it is unblocked.
An activation may have multiple blockers and a count is kept. All blockers must became false for the counter to reach zero to enable the Activation to be eligible for firing.
kcontext.unblockAllActivations( $a ) is an over-ride rule that will remove all blockers regardless
An activation may also be cancelled, so it never fires with cancelActivation
An unblocked Activation is added to the Agenda and obeys normal salience, agenda groups, ruleflow groups etc.
@activationListener('direct') allows a rule to fire as soon as it's matched, this is to be used for rules that block/unblock activations, it is not desirable for these rules to have side effects that impact else where. The name may change later, this is actually part of the pluggable terminal node handlers I made, which is an "internal" feature for the moment.
Example 4.2. New RuleContext methods
void blockActivation(Activation match); void unblockAllActivations(Activation match); void cancelActivation(Activation match);
Here is a basic example that will block all activations from rules that have metadata @department('sales'). They will stay blocked until the blockerAllSalesRules rule becomes false, i.e. "go2" is retracted.
Example 4.3. Block rules based on rule metadata
rule rule1 @department('sales') when $s : String( this == 'go1' ) then list.add( kcontext.rule.name + ':' + $s ); end rule rule2 @department('sales') when $s : String( this == 'go1' ) then list.add( kcontext.rule.name + ':' + $s ); end rule blockerAllSalesRules @activationListener('direct') when $s : String( this == 'go2' ) $i : Activation( department == 'sales' ) then list.add( $i.rule.name + ':' + $s ); kcontext.blockActivation( $i ); end
This example shows how you can use active property to count the number of active or inactive (already fired) activations.
Example 4.4. Count the number of active/inactive Activations
rule rule1 @department('sales') when $s : String( this == 'go1' ) then list.add( kcontext.rule.name + ':' + $s ); end rule rule2 @department('sales') when $s : String( this == 'go1' ) then list.add( kcontext.rule.name + ':' + $s ); end rule rule3 @department('sales') when $s : String( this == 'go1' ) then list.add( kcontext.rule.name + ':' + $s ); end rule countActivateInActive @activationListener('direct') when $s : String( this == 'go2' ) $active : Number( this == 1 ) from accumulate( $a : Activation( department == 'sales', active == true ), count( $a ) ) $inActive : Number( this == 2 ) from accumulate( $a : Activation( department == 'sales', active == false ), count( $a ) ) then kcontext.halt( ); end
Entry points can now be explicitly declared. Syntax is:
entryPointDeclaration := DECLARE ENTRY-POINT stringId annotation* END
We also took the opportunity to make a few improvements of our own.
<asseteditor> <class>org.drools.guvnor.client.modeldriven.ui.RuleModeller</class> <format>brl</format> <icon>images.ruleAsset()</icon> <title>constants.BusinessRuleAssets()</title> </asseteditor>
The format of the new screen is being tried for 5.3.0.Beta1. There has been some discussion whether a single table containing all assets would be better - with collapsible rows to group different types of asset. The immediate problem with this approach is however that finding different asset types on a "paged table" becomes more cumbersome for the user; as they'd have to sort by type and page through.
We therefore ask for community feedback.
A couple of minor enhancements have been made to Guvnor's guided decision table editor:-
Generate constructors with parameters for declared types.
Example: for a declared type like the following:
declare Person firstName : String @key lastName : String @key age : int end
Person() // parameterless constructor Person( String firstName, String lastName ) Person( String firstName, String lastName, int age )
Type declarations now support 'extends' keyword for inheritance
import org.people.Person declare Person end declare Student extends Person school : String end declare LongTermStudent extends Student years : int course : String end
Person( age * 2 > $anotherPersonsAge + 2 ) // mathematical expressions Person( addresses["home"].streetName.startsWith( "High Park" ) ) // method calls and collections simplified syntax Person( isAdult() ) // boolean expression without relational operator
Cheese( ) from [ $stilton, $brie, $provolone ] // inline list creation and iteration
Patterns now support positional arguments on type declarations.
declare Cheese name : String shop : String price : int end
The default order is the declared order, but this can be overiden using @Position
declare Cheese name : String @position(1) shop : String @position(2) price : int @position(0) end
Cheese( "stilton", "Cheese Shop", p; ) Cheese( "stilton", "Cheese Shop"; p : price ) Cheese( "stilton"; shop == "Cheese Shop", p : price ) Cheese( name == "stilton"; shop == "Cheese Shop", p : price )
declare Location thing : String location : String end query isContainedIn( String x, String y ) Location( x := thing, y := location) or ( Location(z := thing, y := location) and ?isContainedIn( x := x, z := y ) ) end
Positional and mixed positional/named are supported.
declare Location thing : String location : String end query isContainedIn( String x, String y ) Location(x, y;) or ( Location(z, y;) and ?isContainedIn(x, z;) ) end
Here is an example of query element inside of a rule using mixed positional/named arguments.
package org.drools.test import java.util.List import java.util.ArrayList dialect "mvel" declare Here place : String end declare Door fromLocation : String toLocation : String end declare Location thing : String location : String end declare Edible thing : String end query connect( String x, String y ) Door(x, y;) or Door(y, x;) end query whereFood( String x, String y ) ( Location(x, y;) and Edible(x;) ) or ( Location(z, y;) and whereFood(x, z;) ) end query look(String place, List things, List food, List exits) Here(place;) things := List() from accumulate( Location(thing, place;), collectList( thing ) ) food := List() from accumulate( ?whereFood(thing, place;), collectList( thing ) ) exits := List() from accumulate( ?connect(place, exit;), collectList( exit ) ) end rule reactiveLook when Here( $place : place) ?look($place, $things; $food := food, $exits := exits) then System.out.println( \"You are in the \" + $place); System.out.println( \" You can see \" + $things ); System.out.println( \" You can eat \" + $food ); System.out.println( \" You can go to \" + $exits ); end
query isContainedIn( String x, String y ) Location(x, y;) or ( Location(z, y;) and isContainedIn(x, z;) ) end rule look when Person( $l : likes ) isContainedIn( $l, 'office'; ) then insertLogical( $l + 'is in the office' ); end
results = ksession.getQueryResults( "isContainedIn", new Object[] { Variable.v, "office" } ); l = new ArrayList<List<String>>(); for ( QueryResultsRow r : results ) { l.add( Arrays.asList( new String[] { (String) r.get( "x" ), (String) r.get( "y" ) } ) ); }
The algorithm uses stacks to handle recursion, so the method stack will not blow up.
Entire rules can be negated, giving rise to DRL fragements such as:-
rule "no cheddar liked by Fred" when not ( $c : Cheese( name == "Cheddar" ) Person( name == "Fred", favouriteCheese == $c ) ) then .. end
// from row number: 1 rule "Row 1 dtable" salience 1 dialect "mvel" when $p : Person( name == "Bill" , age != "30" ) then $p.setAge( 12345 ); end // from row number: 2 rule "Row 2 dtable" salience 2 dialect "mvel" when $p : Person( name == "Ben" , age in ( "30", "40", "50" ) ) then $p.setAge( 12345 ); end // from row number: 3 rule "Row 3 dtable" salience 3 dialect "mvel" when $p : Person( name == "Weed" , age != "40" ) then $p.setAge( 12345 ); end // from row number: 4 rule "Row 4 dtable" salience 4 dialect "mvel" when $p : Person( name not in ( "Bill", "Ben", "Weed" ) , age != "50" ) then $p.setAge( 12345 ); end
KnowledgeBase takes the following configurations: "advanced-process-rule-integration, multithread, mbeans, event-processing-mode, accumulate-functions, evaluators and assert-behavior".
From the the kbase reference ksessions can be created
Example 4.8. Knowlege Sessions
<drools:ksession id="ksession1" type="stateless" name="stateless1" kbase="kbase1" />
<drools:ksession id="ksession2" type="stateful" kbase="kbase1" />
Like KnowledgeBases Knowlege sessions can take a number of configurations, including "work-item-handlers, "keep-references", "clock-type", "jpa-persistence".
Example 4.9. Knowlege Sessions Configurations
<drools:ksession id="ksession1" type="stateful" kbase="kbase1" >
<drools:configuration>
<drools:work-item-handlers>
<drools:work-item-handler name="handlername" ref="handlerid" />
</drools:work-item-handlers>
<drools:keep-reference enabled="true" />
<drools:clock-type type="REALTIME" />
</drools:configuration>
</drools:ksession>
StatefulKnowledgeSessions can be configured for JPA persistence
Example 4.10. JPA configuration for StatefulKnowledgeSessions
<bean id="ds" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="org.h2.Driver" />
<property name="url" value="jdbc:h2:tcp://localhost/DroolsFlow" />
<property name="username" value="sa" />
<property name="password" value="" />
</bean>
<bean id="myEmf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="ds" />
<property name="persistenceUnitName" value="org.drools.persistence.jpa.local" />
</bean>
<bean id="txManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="myEmf" />
</bean>
<drools:ksession id="jpaSingleSessionCommandService" type="stateful" kbase="kbase1">
<drools:configuration>
<drools:jpa-persistence>
<drools:transaction-manager ref="txManager" />
<drools:entity-manager-factory ref="myEmf" />
<drools:variable-persisters>
<drools:persister for-class="javax.persistence.Entity" implementation="org.drools.persistence.processinstance.persisters.JPAVariablePersister"/>
<drools:persister for-class="java.lang.String" implementation="org.drools.container.spring.beans.persistence.StringVariablePersister"/>
<drools:persister for-class="java.io.Serializable" implementation="org.drools.persistence.processinstance.persisters.SerializableVariablePersister"/>
</drools:variable-persisters>
</drools:jpa-persistence>
</drools:configuration>
</drools:ksession>
Knowledge Sessions can support startup batch scripts, previous versions used the "script" element name, this will be updated to "batch". The following commands are supported: "insert-object", "set-global", "fire-all-rules", "fire-until-halt", "start-process", "signal-event". Anonymous beans or named "ref" attributes may be used.
Example 4.11. Startup Batch Commands
<drools:ksession id="jpaSingleSessionCommandService" type="stateful" kbase="kbase1">
<drools:script>
<drools:insert-object ref="person1" />
<drools:start-process process-id="proc name">
<drools:parameter identifier="varName" ref="varRef" />
</drools:start-process>
<drools:fire-all-rules />
</drools:script>
</drools:ksession>
ExecutionNodes are supported in Spring , these provide a Context of registered ksessions; this can be used with Camel to provide ksession routing.
Example 4.12. Execution Nodes
<execution-node id="node1" />
<drools:ksession id="ksession1" type="stateless" name="stateless1" kbase="kbase1" node="node1"/>
<drools:ksession id="ksession2" type="stateful" kbase="kbase1" node="node1"/>
Camel routes can then be attached to CXF endpoints, allowing you control over the payload for things like data formatting and executing against Drools ksessions. The DroolsPolicy adds some smarts to the route. If JAXB or XStream are used, it would inject custom paths and converters, it can also set the classloader too on the server side, based on the target ksession. On the client side it automatically unwrapes the Response object.
This example unmarshalls the payload using an augmented XStream DataFormat and executes it against the ksession1 instance. The "node" there refers to the ExecutionContext, which is a context of registered ksessions.
Example 4.14. Camel Route
<bean id="droolsPolicy" class="org.drools.camel.component.DroolsPolicy" />
<camelContext id="camel" xmlns="http://camel.apache.org/schema/spring">
<route>
<from uri="cxfrs://bean://rsServer"/>
<policy ref="droolsPolicy">
<unmarshal ref="xstream" />
<to uri="drools://node/ksession1" />
<marshal ref="xstream" />
</policy>
</route>
</camelContext>
The Drools endpoint "drools:node/ksession1" consists of the execution node name followed by a separator and optional knowledge session name. If the knowledge session is not specified the route will look at the "lookup" attribute on the incoming payload instace or in the head attribute "DroolsLookup" to find it.
To inspect a session, one can use the following API calls:
The StatefulKnowledgeSessionInfo instance will contain a lot of relevant data gathered during the analysis of the session. A simple example report template is provided and can be generated with the following API call:
Example 4.16. Generating a Report
String report = SessionReporter.generateReport( "simple", info, null );
A Drools blog article contains an example of Glazed Lists integration for live queries,
http://blog.athico.com/2010/07/glazed-lists-examples-for-drools-live.html
Interval "int:" timers follow the JDK semantics for initial delay optionally followed by a repeat interval. Cron "cron:" timers follow standard cron expressions:
Example 4.19. A Cron Example
rule "Send SMS every 15 minutes"
timer (cron:* 0/15 * * * ?)
when
$a : Alarm( on == true )
then
channels[ "sms" ].insert( new Sms( $a.mobileNumber, "The alarm is still on" );
end
Calendars can now controll when rules can fire. The Calendar api is modelled on Quartz http://www.quartz-scheduler.org/ :
Calendars are registered with the StatefulKnowledgeSession:
They can be used in conjunction with normal rules and rules including timers. The rule calendar attribute can have one or more comma calendar names.
Example 4.22. Using Calendars and Timers together
rule "weekdays are high priority"
calendars "weekday"
timer (int:0 1h)
when
Alarm()
then
send( "priority high - we have an alarm );
end
rule "weekend are low priority"
calendars "weekend"
timer (int:0 4h)
when
Alarm()
then
send( "priority low - we have an alarm );
end
The inbox feature provides the ability to track what you have opened, or edited - this shows up under an "Inbox" item in the main navigator. http://blog.athico.com/2009/09/inbox-feature-to-track-recent-changes.html
Recently Opened
Clicking on the recently opened item will open a listing of all items you have "recently" opened (it tracks a few hundred items that you were last to look at).
Recently Edited
Any items that you save changes to, or comment on will show up here, once again.
Incoming changes
This tracks changes made by *other people* to items that are in *your* "Recently Edited" list. When you open these items they then are removed from this list (but remain in your Recently Edited list).
When using 'from collect' In the left pattern you can choose from “java.util.Collection”, “java.util.List” or “java.util.Set” Fact Types. This Fact Types will be automatically included in the package’s Fact Types list.
The right pattern of the collect conditional element could be one of this patterns:
Fact Type Pattern
Free Form Expression
From Pattern
From Collect Pattern
From Accumulate Pattern
When using 'from accumulate' The left pattern could be any Fact Type Pattern. The right section of this conditional element is splited in two:
The left pattern could be any Fact Type Pattern. The right section of this conditional element is splited in two:
Source Pattern: (Bed $n, in the screenshot) could be any Fact Type, From, Collect or Accumulate pattern.
Accumulate function: Here you will find a tabbed panel where you can enter an accumulate function (sum() in the screenshot) or you can create an online custom function using the “Custom Code” tab.
A Typical example to load a process resource. Notice the
ResourceType
is changed, in accordance with the
Resource
type:
Example 4.26. A Typical example to load a process resource. Notice the
ResourceType
is changed, in accordance with the
Resource
type
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
kbuilder.add( ResourceFactory.newUrlResource( url ),
ResourceType.DRF );
if ( kbuilder.hasErrors() ) {
System.err.println( builder.getErrors().toString() );
}
KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();
kbase.addKnowledgePackages( builder.getKnowledgePackages() );
StatefulKnowledgeSession ksession = knowledgeBase.newStatefulKnowledgeSession();
ksession.startProcess( "Buy Order Process" );
ksession.dispose();
'kbuilder', 'kbase', 'ksession' are the variable identifiers often used, the k prefix is for 'knowledge'.
Example 4.27. We have uniformed how decision trees are loaded, and they are now consistent with no need to pre generate the DRL with the spreadsheet compiler
DecisionTableConfiguration dtconf = KnowledgeBuilderFactory.newDecisionTableConfiguration();
dtconf.setInputType( DecisionTableInputType.XLS );
dtconf.setWorksheetName( "Tables_2" );
kbuilder.add( ResourceFactory.newUrlResource( "file://IntegrationExampleTest.xls" ),
ResourceType.DTABLE,
dtconf );
It is also possible to configure a KnowledgeBase
using configuration, via a xml change set, instead of programmatically.
Example 4.28. Here is a simple change set
<change-set xmlns='http://drools.org/drools-5.0/change-set'
xmlns:xs='http://www.w3.org/2001/XMLSchema-instance'
xs:schemaLocation='http://drools.org/drools-5.0/change-set change-set-5.0.xsd' >
<add>
<resource source='classpath:org/domain/someRules.drl' type='DRL' />
<resource source='classpath:org/domain/aFlow.drf' type='DRF' />
</add>
</change-set>
Example 4.29. And it is added just like any other ResourceType
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
kbuilder.add( ResourceFactory.newUrlResource( url ),
ResourceType.ChangeSet );
The other big change for the KnowledgeAgent
,
compared to the RuleAgent
, is that polling scanner is now a
service. further to this there is an abstraction between the agent
notification and the resource monitoring, to allow other mechanisms to be
used other than polling.
Example 4.30. These services currently are not started by default, to start them do the following
ResourceFactory.getResourceChangeNotifierService().start();
ResourceFactory.getResourceChangeScannerService().start();
There are two new interfaces added,
ResourceChangeNotifier
and
ResourceChangeMonitor
. KnowlegeAgents
subscribe
for resource change notifications using the
ResourceChangeNotifier
implementation. The
ResourceChangeNotifier
is informed of resource changes by the
added ResourceChangeMonitors
. We currently only provide one
out of the box monitor, ResourceChangeScannerService
, which
polls resources for changes. However the api is there for users to add
their own monitors, and thus use a push based monitor such as JMS.
ResourceFactory.getResourceChangeNotifierService().addResourceChangeMonitor(
myJmsMonitor);
For options that don't have a predefined constant or can assume multiple values, a factory method is provided. For instance, to configure the alpha threshold to 5, just use the "get" factory method:
As you can see, the same setOption()
method is
used for the different possible configurations, but they are still type
safe.
Typically though you will want to execute a batch of
commands, this can be achieved via the composite Command
BatchExecution
. BatchExecutionResults
is now
used to handle the results, some commands can specify "out" identifiers
which it used to add the result to the
BatchExecutionResult
. Handily querries can now be executed
and results added to the BatchExecutionResult
. Further to
this results are scoped to this execute call and return via the
BatchExecutionResults
:
Example 4.41. Using BatchExecutionResult
List<Command> cmds = new ArrayList<Command>();
cmds.add( CommandFactory.newSetGlobal( "list1", new ArrayList(), true ) );
cmds.add( CommandFactory.newInsert( new Person( "jon", 102 ), "person" ) );
cmds.add( CommandFactory.newQuery( "Get People" "getPeople" );
BatchExecutionResults results = ksession.execute( CommandFactory.newBatchExecution( cmds ) );
results.getValue( "list1" ); // returns the ArrayList
results.getValue( "person" ); // returns the inserted fact Person
results.getValue( "Get People" );// returns the query as a QueryResults instance.
end
The CommandFactory
details the supported
commands, all of which can marshalled using XStream and the
BatchExecutionHelper
. This can be combined with the
pipeline to automate the scripting of a session.
Example 4.42. Using PipelineFactory
Action executeResultHandler = PipelineFactory.newExecuteResultHandler();
Action assignResult = PipelineFactory.newAssignObjectAsResult();
assignResult.setReceiver( executeResultHandler );
Transformer outTransformer = PipelineFactory.newXStreamToXmlTransformer( BatchExecutionHelper.newXStreamMarshaller() );
outTransformer.setReceiver( assignResult );
KnowledgeRuntimeCommand batchExecution = PipelineFactory.newBatchExecutor();
batchExecution.setReceiver( outTransformer );
Transformer inTransformer = PipelineFactory.newXStreamFromXmlTransformer( BatchExecutionHelper.newXStreamMarshaller() );
inTransformer.setReceiver( batchExecution );
Pipeline pipeline = PipelineFactory.newStatelessKnowledgeSessionPipeline( ksession );
pipeline.setReceiver( inTransformer );
Using the above for a rulset that updates the price of a Cheese fact, given the following xml to insert a Cheese instance using an out-identifier:
Example 4.43. Updating Cheese fact
<batch-execution>
<insert out-identifier='outStilton'>
<org.drools.Cheese>
<type>stilton</type>
<price>25</price>
<oldPrice>0</oldPrice>
</org.drools.Cheese>
</insert>
</batch-execution>
We then get the following
BatchExecutionResults
:
Example 4.44. Updating Cheese fact
<batch-execution-results>
<result identifier='outStilton'>
<org.drools.Cheese>
<type>stilton</type>
<oldPrice>0</oldPrice>
<price>30</price>
</org.drools.Cheese>
</result>
</batch-execution-results>
However with marshalling you need more flexibility when
dealing with referenced user data. To achieve this we have the
ObjectMarshallingStrategy
interface. Two implementations
are provided, but the user can implement their own. The two supplied are
IdentityMarshallingStrategy
and
SerializeMarshallingStrategy
.
SerializeMarshallingStrategy
is the default, as used in the
example above and it just calls the Serializable
or
Externalizable
methods on a user instance.
IdentityMarshallingStrategy
instead creates an int id for
each user object and stores them in a Map
the id is written
to the stream. When unmarshalling it simply looks to the
IdentityMarshallingStrategy
map to retrieve the instance.
This means that if you use the IdentityMarshallingStrategy
it's stateful for the life of the Marshaller instance and will create
ids and keep references to all objects that it attempts to marshal.
Example 4.46. Code to use a
IdentityMarshallingStrategy
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Marshaller marshaller = MarshallerFactory.newMarshaller( kbase, new ObjectMarshallingStrategy[] { MarshallerFactory.newIdentityMarshallingStrategy() } );
marshaller.marshall( baos, ksession );
baos.close();
For added flexability we can't assume that a single
strategy is suitable for this we have added the
ObjectMarshallingStrategyAcceptor
interface that each
ObjectMarshallingStrategy
has. The Marshaller has a chain
of strategies and when it attempts to read or write a user object it
iterates the strategies asking if they accept responsability for
marshalling the user object. One one implementation is provided the
ClassFilterAcceptor
. This allows strings and wild cards to
be used to match class names. The default is "*.*", so in the above the
IdentityMarshallingStrategy
is used which has a default
"*.*" acceptor. But lets say we want to serialise all classes except for
one given package, where we will use identity lookup, we could do the
following:
Example 4.47. Using identity lookup
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectMarshallingStrategyAcceptor identityAceceptor = MarshallerFactory.newClassFilterAcceptor( new String[] { "org.domain.pkg1.*" } );
ObjectMarshallingStrategy identityStratetgy = MarshallerFactory.newIdentityMarshallingStrategy( identityAceceptor );
Marshaller marshaller = MarshallerFactory.newMarshaller( kbase, new ObjectMarshallingStrategy[] { identityStratetgy, MarshallerFactory.newSerializeMarshallingStrategy() } );
marshaller.marshall( baos, ksession );
baos.close();
$st : StockTick( company == "ACME", price > 10 ) from
entry-point "stock stream"
StreamTest shows a unit for this.
Drools 5.0 adds support for reasoning over sliding windows of events. For instance:
New Conditional Elements: from, collect, accumulate and forall
New Field Constraint operators: not matches, not contains, in, not in, memberOf, not memberOf
Full support for Conditional Elements nesting, for First Order Logic completeness.
Support for multi-restrictions and constraint connectives && and ||
Support for pluggable dialects and full support for MVEL scripting language
Fact attributes auto-vivification for return value restrictions and inline-eval constraints
Support for nested accessors, property navigation and simplified collection, arrays and maps syntax
There are a few API changes that are visible to regular users and need to be fixed.
In Drools 4.0.x it must be changed to:
Example 4.58. Drools 4.0.x: Stateful Rule Session Creation
StatefulSession wm = rulebase.newStatefulSession();
The StatefulSession object has the same behavior as the Drools 3.0.x WorkingMemory (it even extends the WorkingMemory interface), so there should be no other problems with this fix.
The DRL Rule Language also has some backward incompatible changes as detailed below.
java -cp $CLASSPATH org.drools.tools.update.UpdateTool -f <filemask> [-d <basedir>] [-s <sufix>]
The program parameters are very easy to understand as following.