JBoss.orgCommunity Documentation
A Kie Project has the structure of a normal maven project with the only peculiarity
of including a kmodule.xml file defining in a declaratively way the KieBase
s
and KieSession
s that can be created from it. This file has to be placed in the
resources/META-INF folder of the maven project while all the other Kie artifacts, such as
DRL or a Excel files, must be stored in the resource folder or in any other subfolder under it.
Since meaningful defaults have been provided for all configuration aspects, the simplest kmodule.xml file can contain just an empty kmodule tag like the following:
Example 3.1. An empty kmodule.xml file
<?xml version="1.0" encoding="UTF-8"?>
<kmodule xmlns="http://jboss.org/kie/6.0.0/kmodule"/>
In this way the kmodule will contain one single KieBase
and all the Kie artifacts
stored undered the resources folder, or any of its subfolder, will be compiled and added to it.
To trigger the building of these artifacts it is enough to create a KieContainer
for them.
For this simple case it is enough to create a KieContainer
that reads the files
to be build from the classpath as it follows:
Example 3.2. Creating a KieContainer from the classpath
KieServices kieServices = KieServices.Factory.get();
KieContainer kContainer = kieServices.getKieClasspathContainer();
where KieServices
is the interface from where it possible to access all the Kie
building and runtime facilities:
In this way all the java sources and the Kie resources are compiled and deployed into the KieContainer which makes its contents available for use at runtime.
As anticipated in the former section the kmodule.xml file is the place where it is possible
to declaratively configure the KieBase
(s) and KieSession
(s) that can be
created from a KIE project.
In particular a KieBase
is a repository of all the application's knowledge definitions.
It will contain rules, processes, functions, and type models. The KieBase
itself does not contain
data; instead, sessions are created from the KieBase
into which data can be inserted and from
which process instances may be started. Creating the KieBase
can be heavy, whereas session
creation is very light, so it is recommended that KieBase
be cached where possible to allow
for repeated session creation. However end-users usually shouldn't worry about it, because this caching
mechanism is already automatically provided by the KieContainer
.
Conversely the KieSession
stores and executes on the runtime data.
It is created from the KieBase
or more easily can be created directly from the
KieContainer
if it has been defined in the kmodule.xml file
The kmodule.xml allows to define and configure one or more KieBase
s and for each
KieBase
all the different KieSession
s that can be created from it,
as showed by the follwing example:
Example 3.3. A sample kmodule.xml file
<kmodule xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://jboss.org/kie/6.0.0/kmodule">
<kbase name="KBase1" default="true" eventProcessingMode="cloud" equalsBehavior="equality" declarativeAgenda="enabled" packages="org.domain.pkg1">
<ksession name="KSession2_1" type="stateful" default="true/">
<ksession name="KSession2_1" type="stateless" default="false/">
</kbase>
<kbase name="KBase2" default="false" eventProcessingMode="stream" equalsBehavior="equality" declarativeAgenda="enabled" packages="org.domain.pkg2, org.domain.pkg3" includes="KBase1">
<ksession name="KSession2_1" type="stateful" default="false" clockType="realtime">
<fileLogger file="drools.log" threaded="true" interval="10"/>
<workItemHandlers>
<workItemHandler name="name" type="org.domain.WorkItemHandler"/>
</workItemHandlers>
<listeners>
<workingMemoryEventListener type="org.domain.WMEListener"/>
<agendaEventListener type="org.domain.FirstAgendaListener"/>
<agendaEventListener type="org.domain.SecondAgendaListener"/>
<processEventListener type="org.domain.ProcessListener"/>
</listeners>
</ksession>
</kbase>
</kmodule>
Here 2 KieBase
s have been defined and it is possible to instance 2 different types of
KieSession
s from the first one, while only one from the second. A list of the attributes that
can be defined on the kbase tag, together with their meaning and default values follows:
Table 3.1. kbase Attributes
Attribute name | Default value | Admitted values | Meaning |
---|---|---|---|
name | none | any | The name with which retrieve this KieBase from the KieContainer. This is the only mandatory attribute. |
includes | none | any comma separated list | A comma separated list of other KieBases contained in this kmodule. The artifacts of all these KieBases will be also included in this one. |
packages | all | any comma separated list | By default all the Drools artifacts under the resources folder, at any level, are included into the KieBase. This attribute allows to limit the artifacts that will be compiled in this KieBase to only the ones belonging to the list of packages. |
default | false | true, false | Defines if this KieBase is the default one for this module, so it can be created from the KieContainer without passing any name to it. There can be at most one default KieBase in each module. |
equalsBehavior | identity | identity, equality | Defines the behavior of Drools when a new fact is inserted into the Working Memory. With identity it always create a new FactHandle unless the same object isn't already present in the Working Memory, while with equality only if the newly inserted object is not equal (according to its equal method) to an already existing fact. |
eventProcessingMode | cloud | cloud, stream | When compiled in cloud mode the KieBase treats events as normal facts, while in stream mode allow temporal reasoning on them. |
declarativeAgenda | disabled | disabled, enabled | Defines if the Declarative Agenda is enabled or not. |
In the same way also all attributes of the ksession tag (except of course the name) have meaningful default. They are listed and described in the following table:
Table 3.2. ksession Attributes
Attribute name | Default value | Admitted values | Meaning |
---|---|---|---|
name | none | any | The name with which retrieve this KieSession from the KieContainer. This is the only mandatory attribute. |
type | stateful | stateful, stateless | A stateful session allows to iteratively work with the Working Memory, while a stateless one is a one-off execution of a Working Memory with a provided data set. |
default | false | true, false | Defines if this KieSession is the default one for this module, so it can be created from the KieContainer without passing any name to it. In each module there can be at most one default KieSession for each type. |
clockType | realtime | realtime, pseudo | Defines if events timestamps are determined by the system clock or by a psuedo clock controlled by the application. This clock is specially useful for unit testing temporal rules. |
As outlined in the former kmodule.xml sample, it is also possible to declaratively
create on each KieSession
a file (or a console) logger, one or more
WorkItemHandler
s and some listeners that can be of 3 different types:
workingMemoryEventListener, agendaEventListener and processEventListener
Having defined a kmodule.xml like the one in the former sample, it is now possible to simply retrieve the KieBases and KieSessions from the KieContainer using their names.
Example 3.4. Retriving KieBases and KieSessions from the KieContainer
KieServices kieServices = KieServices.Factory.get();
KieContainer kContainer = kieServices.getKieClasspathContainer();
KieBase kBase1 = kContainer.getKieBase("KBase1");
KieSession kieSession1 = kContainer.newKieSession("KSession2_1");
StatelessKieSession kieSession2 = kContainer.newStatelessKieSession("KSession2_2");
It has to be noted that since KSession2_1 and KSession2_2 are of 2 different types
(the first is stateful, while the second is stateless) it is necessary to invoke 2 different methods on the
KieContainer
according to their declared type. If the type of the KieSession
requested to the KieContainer
doesn't correspond with the one declared in the
kmodule.xml file the KieContainer
will throw a RuntimeException
.
Also since a KieBase
and a KieSession
have been flagged as default
is it possible to get them from the KieContainer
without passing any name.
Example 3.5. Retriving default KieBases and KieSessions from the KieContainer
KieContainer kContainer = ...
KieBase kBase1 = kContainer.getKieBase(); // returns KBase1
KieSession kieSession1 = kContainer.newKieSession(); // returns KSession2_1
Since a Kie project is also a maven project the groupId, artifactId and version declared
in the pom.xml file are also used to generate a ReleaseId
that uniquely identify
this project inside your application. This also allows to create a new KieContainer from that
project by simply passing its ReleaseId
to the KieServices
.
Example 3.6. Creating a KieContainer of an existing project retriving it by ReleaseId
KieServices kieServices = KieServices.Factory.get();
ReleaseId releaseId = kieServices.newReleaseId( "org.acme", "myartifact", "1.0" );
KieContainer kieContainer = kieServices.newKieContainer( releaseId );
It is also possible to define the KieBase
s and KieSession
s
belonging to a KieModule programatically instead of declaratively define them in the
kmodule.xml file. The same programatic API also allows to explicitly add the file containing the
Kie artifacts instead of automatically read them from the resources folder of your project.
To do that it is necessary to create a KieFileSystem
, a sort of virtual file
system, and add all the resources contained in your project to it.
Like all other Kie core component you can obtain an instance of the KieFileSystem
from
the KieServices
. One of the thing that for sure it will be necessary to add to this file
system is the kmodule.xml configuration file. As anticipated above Kie also provides a convenient fluent
API, implemented by the KieModuleModel
, to programatically create this file.
To do this in practice it is necessary to create a KieModuleModel
from the
KieServices
, configure it with the desired KieBase
s and
KieSession
s, convert it in xml and add the xml to the KieFileSystem
.
This process is shown by the following example:
Example 3.7. Creating a kmodule.xml programmatically and adding it to a KieFileSystem
KieServices kieServices = KieServices.Factory.get();
KieModuleModel kieModuleModel = kieServices.newKieModuleModel();
KieBaseModel kieBaseModel1 = kieModuleModel.newKieBaseModel( "KBase1 ")
.setDefault( true )
.setEqualsBehavior( EqualityBehaviorOption.EQUALITY )
.setEventProcessingMode( EventProcessingOption.STREAM );
KieSessionModel ksessionModel1 = kieBaseModel1.newKieSessionModel( "KSession1" )
.setDefault( true )
.setType( KieSessionModel.KieSessionType.STATEFUL )
.setClockType( ClockTypeOption.get("realtime") );
KieFileSystem kfs = kieServices.newKieFileSystem();
At this point it is also necessary to add to the KieFileSystem
, through its
fluent API, all others Kie artifacts composing your project. These artifacts have to be added
in the same position of a corresponding usual maven project.
Example 3.8. Adding Kie artifacts to a KieFileSystem
KieFileSystem kfs = ...
kfs.write( "src/main/resources/KBase1/ruleSet1.drl", stringContainingAValidDRL )
.write( "src/main/resources/dtable.xls",
kieServices.getResources().newInputStreamResource( dtableFileStream ) );
This example shows that it is possible to add the Kie artifacts both as plain Strings and
as Resource
s. In this second case the Resource
s can be created by the
KieResources
factory, also provided by the KieServices
. The
KieResources
provides many convenient factory methods to convert an InputStream
,
a URL
, a File
, or a String
representing a path of your file
system to a Resource
that can be managed by the KieFileSystem
.
Normally the type of a Resource
can be inferred from the extension of the name
used to add it to the KieFileSystem
. However it also possible to not follow the Kie
conventions about file extension and then explicitly assign a specific ResourceType
to aResource
as in the following example
Example 3.9. Creating and adding a Resource with an explicit type
KieFileSystem kfs = ...
kfs.write( "src/main/resources/myDrl.txt",
kieServices.getResources().newInputStreamResource( drlStream )
.setResourceType(ResourceType.DRL) );
After having added to the KieFileSystem
all the resources that has to be
included into the project, it is possible to build it by passing the KieFileSystem
to a KieBuilder
When a the contents of a KieFileSystem
is successfully built, the KieModule
resulting from this compilation is automatically added to the KieRepository
.
The KieRepository
is a singleton acting as a repository for all the available KieModule
s.
After this it is possible to create through the KieServices
a new
KieContainer
for that KieModule
using its ReleaseId
. However,
since in this case the KieFileSystem
don't contain any pom.xml file (it is possible to
add one using the KieFileSystem.writePomXML
method), Kie cannot determine the
ReleaseId
of the KieModule
and assign to it a default one. This
default ReleaseId
can be obtained from the KieRepository
and used
to identify the KieModule
inside the KieRepository
itself.
The following example shows this whole process.
Example 3.10. Building the contents of a KieFileSystem and creating a KieContainer
KieServices kieServices = KieServices.Factory.get();
KieFileSystem kfs = ...
kieServices.newKieBuilder( kfs ).buildAll();
KieContainer kieContainer = kieServices.newKieContainer(kieServices.getRepository().getDefaultReleaseId());
At this point it is possible to get KieBase
s and create new KieSession
s
from this KieContainer
exactly in the same way as in the case of a KieContainer
created directly from the classpath.
It is a best practice to check the compilation results. The KieBuilder
can report compilation results of 3 different severities: ERROR, WARNING and INFO. An ERROR
indicates that the compilation of the project failed and in the case no KieModule
is
produced and then nothing is added to the KieRepository
. WARNING and INFO results
can be ignored, but are available for inspection nonetheless.
Example 3.11. Checking that a compilation didn't produce any error
KieBuilder kieBuilder = kieServices.newKieBuilder( kfs ).buildAll();
assertEquals( 0, kieBuilder.getResults().getMessages( Message.Level.ERROR ).size() );
In some cases, it is possible to change the default severity of a type of build result. For instance, when a new rule with the same name of an existing rule is added to a package, the default behavior is to replace the old rule by the new rule and report it as an INFO. This is probably ideal for most use cases, but in some deployments the user might want to prevent the rule update and report it as an error.
Changing the default severity for a result type is configured like any other option in Drools and can be done by API calls, system properties or configuration files. As of this version, Drools supports configurable result severity for rule updates and function updates. To configure it using system properties or configuration files, the user has to use the following properties:
Example 3.12. Setting the severity using properties
// sets the severity of rule updates drools.kbuilder.severity.duplicateRule = <INFO|WARNING|ERROR> // sets the severity of function updates drools.kbuilder.severity.duplicateFunction = <INFO|WARNING|ERROR>
The KieBase
is a repository of all the
application's knowledge definitions. It will contain rules, processes,
functions, and type models. The KieBase
itself does not contain
data; instead, sessions are created from the KieBase
into which data can be inserted and from which process instances may be
started. The KieBase
can be obtained from the KieContainer
containing the KieModule
where the KieBase
has been defined.
Sometimes, for instance in a OSGi environment, the KieBase
needs to resolve
types that are not in the default class loader. In this case it will be necessary to create a
KieBaseConfiguration
with an additional class loader and pass it to KieContainer
when creating a new KieBase
from it.
Example 3.13. Creating a new KieBase with a custom ClassLoader
KieServices kieServices = KieServices.Factory.get();
KieBaseConfiguration kbaseConf = kieServices.newKieBaseConfiguration( null, MyType.class.getClassLoader() );
KieBase kbase = kieContainer.newKieBase( kbaseConf );
KieSessions will be discussed in more detail in
section "Running". The KieBase
creates and returns
KieSession
objects, and it may optionally keep
references to those. When KieBase
modifications occur
those modifications are applied against the data in the sessions. This
reference is a weak reference and it is also optional, which is controlled
by a boolean flag.
The KieScanner
allows to continueing monitoring your maven repository
to check if a new release of a Kie project has been installed and if so deploying it in
the KieContainer
wrapping that project. The use of the requires KieScanner
kie-ci.jar to be on the classpath.
In more detail a KieScanner
can be registered on a KieContainer
as in the following example.
Example 3.14. Registering and starting a KieScanner on a KieContainer
KieServices kieServices = KieServices.Factory.get();
ReleaseId releaseId = kieServices.newReleaseId( "org.acme", "myartifact", "1.0-SNAPSHOT" );
KieContainer kContainer = kieServices.newKieContainer( releaseId );
KieScanner kScanner = kieServices.newKieScanner( kContainer );
// Start the KieScanner polling the maven repository every 10 seconds
kScanner.start( 10000L );
In this example the KieScanner
is configured to run with a fixed
time interval, but it is also possible to run it on demand by invoking the
scanNow()
method on it. If the KieScanner
finds in the
maven repository an updated version of the Kie project used by that KieContainer
it automatically downloads the new version and triggers an incremental build of the new
project. From this moment all the new KieBase
s and KieSession
s
created from that KieContainer
will use the new project version.
The KieBase
is a repository of all the
application's knowledge definitions. It will contain rules, processes,
functions, and type models. The KieBase
itself does not contain
data; instead, sessions are created from the KieBase
into which data can be inserted and from which process instances may be
started. The KieBase
can be obtained from the KieContainer
containing the KieModule
where the KieBase
has been defined.
The KieSession
stores and executes on the
runtime data. It is created from the KieBase
.
The EntryPoint
provides the methods
around inserting, updating and retrieving facts. The term "entry point"
is related to the fact that we have multiple partitions in a Working
Memory and you can choose which one you are inserting into, although
this use case is aimed at event processing and covered in more detail in
the Fusion manual. Most rule based applications will work with the
default entry point alone.
The KieRuntime
interface provides the main
interaction with the engine. It is available in rule consequences and
process actions. In this manual the focus is on the methods and
interfaces related to rules, and the methods pertaining to processes
will be ignored for now. But you'll notice that the
KieRuntime
inherits methods from both the
WorkingMemory
and the ProcessRuntime
, thereby
providing a unified API to work with processes and rules. When working
with rules, three interfaces form the KieRuntime
:
EntryPoint
, WorkingMemory
and the
KieRuntime
itself.
Insertion is the act of telling the WorkingMemory
about a fact, which you do by ksession.insert(yourObject)
,
for example. When a fact is inserted it is not immediately examined for
matches against the rules. This means that all of
the work for deciding about firing or not firing a rule is done lazily
when fireAllRules()
is invoked. Expert systems typically use the term
assert or assertion to refer
to facts made available to the system. However, due to "assert" being
a keyword in most languages, we have decided to use the
insert
keyword; however, expect to hear the two terms
used interchangeably.
When an Object is inserted it returns a FactHandle
.
This FactHandle
is the token used to represent your
inserted object within the WorkingMemory
. It is also used
for interactions with the WorkingMemory
when you wish to
retract or modify an object.
Cheese stilton = new Cheese("stilton");
FactHandle stiltonHandle = ksession.insert( stilton );
As mentioned in the KieBase section, a Working Memory may operate in two assertion modes, i.e., equality or identity, with identity being the default.
Identity means that the Working Memory uses
an IdentityHashMap
to store all asserted objects. New
instance assertions always result in the return of new
FactHandle
, but if an instance is asserted again then it
returns the original fact handle, i.e., it ignores repeated insertions
for the same object.
Equality means that the Working Memory uses
a HashMap
to store all asserted objects. An object instance
assertion will only return a new FactHandle
if the inserted
object is not equal (according to its equal
method) to an
already existing fact.
Retraction is the removal of a fact from Working Memory, which
means that it will no longer track and match that fact, and any rules
that are activated and dependent on that fact will be cancelled. Note
that it is possible to have rules that depend on the nonexistence of a
fact, in which case retracting a fact may cause a rule to activate.
(See the not
and exists
keywords.) Retraction
may be done using the FactHandle
that was returned by the
insert call. On the right hand side of a rule the
retract
statement is used, which works with a simple
object reference.
Cheese stilton = new Cheese("stilton");
FactHandle stiltonHandle = ksession.insert( stilton );
....
ksession.retract( stiltonHandle );
The Rule Engine must be notified of modified facts, so that they
can be reprocessed. You must use the update()
method to
notify the WorkingMemory
of changed objects for those
objects that are not able to notify the WorkingMemory
themselves. Notice that update()
always takes the
modified object as a second parameter, which allows you to specify new
instances for immutable objects. On the right hand side of a rule the
modify
statement is recommended, as it makes the
changes and notifies the engine in a single statement. Alternatively,
after changing a fact object's field values through calls of setter
methods you must invoke update
immediately, event
before changing another fact, or you will cause problems with the
indexing within the rule engine. The modify statement avoids this
problem.
Cheese stilton = new Cheese("stilton");
FactHandle stiltonHandle = workingMemory.insert( stilton );
...
stilton.setPrice( 100 );
workingMemory.update( stiltonHandle, stilton );
The RuleRuntime provides access to the Agenda, permits query executions, and lets you access named Entry Points.
Queries are used to retrieve fact sets based on patterns, as
they are used in rules. Patterns may make use of optional parameters.
Queries can be defined in the Knowledge Base, from where they are
called up to return the matching results. While iterating over the
result collection, any identifier bound in the query can be used to
access the corresponding fact or fact field by calling the
get
method with the binding variable's name as its
argument. If the binding refers to a fact object, its FactHandle can
be retrieved by calling getFactHandle
, again with the
variable's name as the parameter.
Example 3.17. Simple Query Example
QueryResults results =
ksession.getQueryResults( "my query", new Object[] { "string" } );
for ( QueryResultsRow row : results ) {
System.out.println( row.get( "varName" ) );
}
Invoking queries and processing the results by iterating over the returned set is not a good way to monitor changes over time.
To alleviate this, Drools provides Live Queries, which have a
listener attached instead of returning an iterable result set. These
live queries stay open by creating a view and publishing change events for
the contents of this view. To activate, you start your query with
parameters and listen to changes in the resulting view. The
dispose
method terminates the query and discontinues this
reactive scenario.
Example 3.18. Implementing ViewChangedEventListener
final List updated = new ArrayList();
final List removed = new ArrayList();
final List added = new ArrayList();
ViewChangedEventListener listener = new ViewChangedEventListener() {
public void rowUpdated(Row row) {
updated.add( row.get( "$price" ) );
}
public void rowRemoved(Row row) {
removed.add( row.get( "$price" ) );
}
public void rowAdded(Row row) {
added.add( row.get( "$price" ) );
}
};
// Open the LiveQuery
LiveQuery query = ksession.openLiveQuery( "cheeses",
new Object[] { "cheddar", "stilton" },
listener );
...
...
query.dispose() // calling dispose to terminate the live query
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
The KieRuntime
provides further methods that
are applicable to both rules and processes, such as setting globals and
registering channels. ("Exit point" is an obsolete synonym for "channel".)
Globals are named objects that are made visible to the rule engine, but in a way that is fundamentally different from the one for facts: changes in the object backing a global do not trigger reevaluation of rules. Still, globals are useful for providing static information, as an object offering services that are used in the RHS of a rule, or as a means to return objects from the rule engine. When you use a global on the LHS of a rule, make sure it is immutable, or, at least, don't expect changes to have any effect on the behavior of your rules.
A global must be declared in a rules file, and then it needs to be backed up with a Java object.
global java.util.List list
With the Knowledge Base now aware of the global identifier and
its type, it is now possible to call ksession.setGlobal()
with the global's name and an object, for any session, to associate
the object with the global. Failure to declare the global type and
identifier in DRL code will result in an exception being thrown from
this call.
List list = new ArrayList();
ksession.setGlobal("list", list);
Make sure to set any global before it is used in the evaluation
of a rule. Failure to do so results in a NullPointerException
.
The StatefulRuleSession
is inherited by the
KieSession
and provides the rule related
methods that are relevant from outside of the engine.
AgendaFilter
objects are optional implementations
of the filter interface which are used to allow or deny the firing of
a match. What you filter on is entirely up to the
implementation. Drools 4.0 used to supply some out of the box filters,
which have not be exposed in drools 5.0 knowledge-api, but they are
simple to implement and the Drools 4.0 code base can be referred
to.
To use a filter specify it while calling
fireAllRules()
. The following example permits only rules
ending in the string "Test"
. All others will be filtered
out.
ksession.fireAllRules( new RuleNameEndsWithAgendaFilter( "Test" ) );
The Agenda is a Rete feature. During actions on
the WorkingMemory
, rules may become fully matched and
eligible for execution; a single Working Memory Action can result in
multiple eligible rules. When a rule is fully matched a Match is
created, referencing the rule and the matched facts, and placed onto the
Agenda. The Agenda controls the execution order of these Matches using
a Conflict Resolution strategy.
The engine cycles repeatedly through two phases:
Working Memory Actions. This is where most of the work takes
place, either in the Consequence (the RHS itself) or the main Java
application process. Once the Consequence has finished or the main
Java application process calls fireAllRules()
the engine
switches to the Agenda Evaluation phase.
Agenda Evaluation. This attempts to select a rule to fire. If no rule is found it exits, otherwise it fires the found rule, switching the phase back to Working Memory Actions.
The process repeats until the agenda is clear, in which case control returns to the calling application. When Working Memory Actions are taking place, no rules are being fired.
Conflict resolution is required when there are multiple rules on the agenda. (The basics to this are covered in chapter "Quick Start".) As firing a rule may have side effects on the working memory, the rule engine needs to know in what order the rules should fire (for instance, firing ruleA may cause ruleB to be removed from the agenda).
The default conflict resolution strategies employed by Drools are: Salience and LIFO (last in, first out).
The most visible one is salience (or priority), in which case a user can specify that a certain rule has a higher priority (by giving it a higher number) than other rules. In that case, the rule with higher salience will be preferred. LIFO priorities are based on the assigned Working Memory Action counter value, with all rules created during the same action receiving the same value. The execution order of a set of firings with the same priority value is arbitrary.
As a general rule, it is a good idea not to count on rules firing in any particular order, and to author the rules without worrying about a "flow". However when a flow is needed a number of possibilities exist, including but not limited to: agenda groups, rule flow groups, activation groups, control/semaphore facts. These are discussed in later sections.
Drools 4.0 supported custom conflict resolution strategies; while this capability still exists in Drools it has not yet been exposed to the end user via knowledge-api in Drools 5.0.
Agenda groups are a way to partition rules (matches, actually) on the agenda. At any one time, only one group has "focus" which means that matches for rules in that group only will take effect. You can also have rules with "auto focus" which means that the focus is taken for its agenda group when that rule's conditions are true.
Agenda groups are known as "modules" in CLIPS terminology. While it best to design rules that do not need control flow, this is not always possible. Agenda groups provide a handy way to create a "flow" between grouped rules. You can switch the group which has focus either from within the rule engine, or via the API. If your rules have a clear need for multiple "phases" or "sequences" of processing, consider using agenda-groups for this purpose.
Each time setFocus()
is called it pushes that Agenda
Group onto a stack. When the focus group is empty it is popped from the
stack and the focus group that is now on top evaluates. An Agenda Group
can appear in multiple locations on the stack. The default Agenda Group
is "MAIN", with all rules which do not specify an Agenda Group being in
this group. It is also always the first group on the stack, given focus
initially, by default.
ksession.getAgenda().getAgendaGroup( "Group A" ).setFocus();
An activation group is a set of rules bound together by the same
"activation-group" rule attribute. In this group only one rule can fire,
and after that rule has fired all the other rules are cancelled from the
agenda. The clear()
method can be called at any time, which
cancels all of the activations before one has had a chance to
fire.
ksession.getAgenda().getActivationGroup( "Group B" ).clear();
A rule flow group is a group of rules associated by the
"ruleflow-group" rule attribute. These rules can only fire when the
group is activate. The group itself can only become active when the
elaboration of the ruleflow diagram reaches the node representing the
group. Here too, the clear()
method can be called at any
time to cancels all matches still remaining on the Agenda.
ksession.getAgenda().getRuleFlowGroup( "Group C" ).clear();
The event package provides means to be notified of rule engine events, including rules firing, objects being asserted, etc. This allows you, for instance, to separate logging and auditing activities from the main part of your application (and the rules).
The KieRuntimeEventManager
interface is
implemented by the KieRuntime
which provides two
interfaces, WorkingMemoryEventManager
and
ProcessEventManager
. We will only cover the
WorkingMemoryEventManager
here.
The WorkingMemoryEventManager
allows for listeners to
be added and removed, so that events for the working memory and the agenda
can be listened to.
The following code snippet shows how a simple agenda listener is declared and attached to a session. It will print matches after they have fired.
Example 3.19. Adding an AgendaEventListener
ksession.addEventListener( new DefaultAgendaEventListener() {
public void afterMatchFired(AfterMatchFiredEvent event) {
super.afterMatchFired( event );
System.out.println( event );
}
});
Drools also provides DebugWorkingMemoryEventListener
and DebugAgendaEventListener
which implement each method with
a debug print statement. To print all Working Memory events, you add a
listener like this:
Example 3.20. Adding a DebugWorkingMemoryEventListener
ksession.addEventListener( new DebugWorkingMemoryEventListener() );
All emitted events implement the KieRuntimeEvent
interface which can be used to retrieve the actual
KnowlegeRuntime
the event originated from.
The events currently supported are:
MatchCreatedEvent
MatchCancelledEvent
BeforeMatchFiredEvent
AfterMatchFiredEvent
AgendaGroupPushedEvent
AgendaGroupPoppedEvent
ObjectInsertEvent
ObjectDeletedEvent
ObjectUpdatedEvent
ProcessCompletedEvent
ProcessNodeLeftEvent
ProcessNodeTriggeredEvent
ProcessStartEvent
The KieRuntimeLogger uses the comprehensive event system in Drools to create an audit log that can be used to log the execution of an application for later inspection, using tools such as the Eclipse audit viewer.
Example 3.21. FileLogger
KieRuntimeLogger logger =
KieServices.Factory.get().newFileLogger(ksession, "logdir/mylogfile");
...
logger.close();
The StatelessKieSession
wraps the
KieSession
, instead of extending it. Its main
focus is on decision service type scenarios. It avoids the need to call
dispose()
. Stateless sessions do not support iterative
insertions and the method call fireAllRules()
from Java code;
the act of calling execute()
is a single-shot method that
will internally instantiate a KieSession
, add
all the user data and execute user commands, call
fireAllRules()
, and then call dispose()
. While
the main way to work with this class is via the
BatchExecution
(a subinterface of Command
) as
supported by the CommandExecutor
interface, two convenience
methods are provided for when simple object insertion is all that's
required. The CommandExecutor
and BatchExecution
are talked about in detail in their own section.
Our simple example shows a stateless session executing a given collection of Java objects using the convenience API. It will iterate the collection, inserting each element in turn.
Example 3.22. Simple StatelessKieSession execution with a Collection
StatelessKieSession ksession = kbase.newStatelessKieSession();
ksession.execute( collection );
If this was done as a single Command it would be as follows:
Example 3.23. Simple StatelessKieSession execution with InsertElements Command
ksession.execute( CommandFactory.newInsertElements( collection ) );
If you wanted to insert the collection itself, and the collection's
individual elements, then
CommandFactory.newInsert(collection)
would do the job.
Methods of the CommandFactory
create the supported
commands, all of which can be marshalled using XStream and the
BatchExecutionHelper
. BatchExecutionHelper
provides details on the XML format as well as how to use Drools Pipeline
to automate the marshalling of BatchExecution
and
ExecutionResults
.
StatelessKieSession
supports globals, scoped in a
number of ways. I'll cover the non-command way first, as commands are
scoped to a specific execution call. Globals can be resolved in three
ways.
The StatelessKieSession method getGlobals()
returns a Globals instance which provides access to the session's
globals. These are shared for all execution
calls. Exercise caution regarding mutable globals because execution
calls can be executing simultaneously in different threads.
Example 3.24. Session scoped global
StatelessKieSession ksession = kbase.newStatelessKieSession();
// Set a global hbnSession, that can be used for DB interactions in the rules.
ksession.setGlobal( "hbnSession", hibernateSession );
// Execute while being able to resolve the "hbnSession" identifier.
ksession.execute( collection );
Using a delegate is another way of global resolution. Assigning
a value to a global (with setGlobal(String, Object)
)
results in the value being stored in an internal collection mapping
identifiers to values. Identifiers in this internal collection will
have priority over any supplied delegate. Only if an identifier cannot
be found in this internal collection, the delegate global (if any)
will be used.
The third way of resolving globals is to have execution scoped
globals. Here, a Command
to set a global is passed to the
CommandExecutor
.
The CommandExecutor
interface also offers the ability
to export data via "out" parameters. Inserted facts, globals and query
results can all be returned.
Example 3.25. Out identifiers
// Set up a list of commands
List cmds = new ArrayList();
cmds.add( CommandFactory.newSetGlobal( "list1", new ArrayList(), true ) );
cmds.add( CommandFactory.newInsert( new Person( "jon", 102 ), "person" ) );
cmds.add( CommandFactory.newQuery( "Get People" "getPeople" );
// Execute the list
ExecutionResults results =
ksession.execute( CommandFactory.newBatchExecution( cmds ) );
// Retrieve the ArrayList
results.getValue( "list1" );
// Retrieve the inserted Person fact
results.getValue( "person" );
// Retrieve the query as a QueryResults instance.
results.getValue( "Get People" );
With Rete you have a stateful session where objects can be asserted and modified over time, and where rules can also be added and removed. Now what happens if we assume a stateless session, where after the initial data set no more data can be asserted or modified and rules cannot be added or removed? Certainly it won't be necessary to re-evaluate rules, and the engine will be able to operate in a simplified way.
Order the Rules by salience and position in the ruleset (by setting a sequence attribute on the rule terminal node).
Create an elements, one element for each possible rule match; element position indicates firing order.
Turn off all node memories, except the right-input Object memory.
Disconnect the Left Input Adapter Node propagation, and let the Object plus the Node be referenced in a Command object, which is added to a list on the Working Memory for later execution.
Assert all objects, and, when all assertions are finished and thus right-input node memories are populated, check the Command list and execute each in turn.
All resulting Matches should be placed in the elements, based upon the determined sequence number of the Rule. Record the first and last populated elements, to reduce the iteration range.
Iterate the elements of Matches, executing populated element in turn.
If we have a maximum number of allowed rule executions, we can exit our network evaluations early to fire all the rules in the elements.
The LeftInputAdapterNode
no longer creates a Tuple,
adding the Object, and then propagate the Tuple – instead a Command
object is created and added to a list in the Working Memory. This
Command object holds a reference to the
LeftInputAdapterNode
and the propagated object. This stops
any left-input propagations at insertion time, so that we know that a
right-input propagation will never need to attempt a join with the
left-inputs (removing the need for left-input memory). All nodes have
their memory turned off, including the left-input Tuple memory but
excluding the right-input object memory, which means that the only node
remembering an insertion propagation is the right-input object memory.
Once all the assertions are finished and all right-input memories
populated, we can then iterate the list of
LeftInputAdatperNode
Command objects calling each in turn.
They will propagate down the network attempting to join with the
right-input objects, but they won't be remembered in the left input as
we know there will be no further object assertions and thus propagations
into the right-input memory.
There is no longer an Agenda, with a priority queue to schedule
the Tuples; instead, there is simply an elements for the number of rules.
The sequence number of the RuleTerminalNode
indicates the
element within the elements where to place the Match. Once all Command
objects have finished we can iterate our elements, checking each element in
turn, and firing the Matches if they exist. To improve performance,
we remember the first and the last populated cell in the elements. The
network is constructed, with each RuleTerminalNode
being
given a sequence number based on a salience number and its order of
being added to the network.
Typically the right-input node memories are Hash Maps, for fast object deletion; here, as we know there will be no object deletions, we can use a list when the values of the object are not indexed. For larger numbers of objects indexed Hash Maps provide a performance increase; if we know an object type has only a few instances, indexing is probably not advantageous, and a list can be used.
Sequential mode can only be used with a Stateless Session and is
off by default. To turn it on, either call
RuleBaseConfiguration.setSequential(true)
, or set the
rulebase configuration property drools.sequential
to true.
Sequential mode can fall back to a dynamic agenda by calling
setSequentialAgenda
with
SequentialAgenda.DYNAMIC
. You may also set the
"drools.sequential.agenda" property to "sequential" or "dynamic".
Drools has the concept of stateful or stateless sessions. We've already covered stateful sessions, which use the standard working memory that can be worked with iteratively over time. Stateless is a one-off execution of a working memory with a provided data set. It may return some results, with the session being disposed at the end, prohibiting further iterative interactions. You can think of stateless as treating a rule engine like a function call with optional return results.
In Drools 4 we supported these two paradigms but the way the user
interacted with them was different. StatelessSession used an execute(...)
method which would insert a collection of objects as facts.
StatefulSession didn't have this method, and insert used the more
traditional insert(...)
method. The other issue was that the
StatelessSession did not return any results, so that users themselves had
to map globals to get results, and it wasn't possible to do anything
besides inserting objects; users could not start processes or execute
queries.
Drools 5.0 addresses all of these issues and more. The foundation
for this is the CommandExecutor
interface, which both the
stateful and stateless interfaces extend, creating consistency and
ExecutionResults
:
The CommandFactory
allows for commands to be executed
on those sessions, the only difference being that the Stateless Knowledge
Session executes fireAllRules()
at the end before disposing
the session. The currently supported commands are:
FireAllRules
GetGlobal
SetGlobal
InsertObject
InsertElements
Query
StartProcess
BatchExecution
InsertObject
will insert a single object, with an
optional "out" identifier. InsertElements
will iterate an
Iterable, inserting each of the elements. What this means is that a
Stateless Knowledge Session is no longer limited to just inserting
objects, it can now start processes or execute queries, and do this in any
order.
Example 3.26. Insert Command
StatelessKieSession ksession = kbase.newStatelessKieSession();
ExecutionResults bresults =
ksession.execute( CommandFactory.newInsert( new Cheese( "stilton" ), "stilton_id" ) );
Stilton stilton = bresults.getValue( "stilton_id" );
The execute method always returns an ExecutionResults
instance, which allows access to any command results if they specify an
out identifier such as the "stilton_id" above.
Example 3.27. InsertElements Command
StatelessKieSession ksession = kbase.newStatelessKieSession();
Command cmd = CommandFactory.newInsertElements( Arrays.asList( Object[] {
new Cheese( "stilton" ),
new Cheese( "brie" ),
new Cheese( "cheddar" ),
});
ExecutionResults bresults = ksession.execute( cmd );
The execute method only allows for a single command. That's where
BatchExecution
comes in, which represents a composite
command, created from a list of commands. Now, execute will iterate over
the list and execute each command in turn. This means you can insert some
objects, start a process, call fireAllRules and execute a query, all in a
single execute(...)
call, which is quite powerful.
As mentioned previosly, the StatelessKieSession will execute
fireAllRules()
automatically at the end. However the
keen-eyed reader probably has already noticed the
FireAllRules
command and wondered how that works with a
StatelessKieSession. The FireAllRules
command is
allowed, and using it will disable the automatic execution at the end;
think of using it as a sort of manual override function.
Commands support out identifiers. Any command that has an out identifier set on it will add its results to the returned ExecutionResults instance. Let's look at a simple example to see how this works.
Example 3.28. BatchExecution Command
StatelessKieSession ksession = kbase.newStatelessKieSession();
List cmds = new ArrayList();
cmds.add( CommandFactory.newInsertObject( new Cheese( "stilton", 1), "stilton") );
cmds.add( CommandFactory.newStartProcess( "process cheeses" ) );
cmds.add( CommandFactory.newQuery( "cheeses" ) );
ExecutionResults bresults = ksession.execute( CommandFactory.newBatchExecution( cmds ) );
Cheese stilton = ( Cheese ) bresults.getValue( "stilton" );
QueryResults qresults = ( QueryResults ) bresults.getValue( "cheeses" );
In the above example multiple commands are executed, two of which
populate the ExecutionResults
. The query command defaults to
use the same identifier as the query name, but it can also be mapped to a
different identifier.
A custom XStream marshaller can be used with the Drools Pipeline to
achieve XML scripting, which is perfect for services. Here are two simple
XML samples, one for the BatchExecution and one for the
ExecutionResults
.
Example 3.29. Simple BatchExecution XML
<batch-execution>
<insert out-identifier='outStilton'>
<org.drools.compiler.Cheese>
<type>stilton</type>
<price>25</price>
<oldPrice>0</oldPrice>
</org.drools.compiler.Cheese>
</insert>
</batch-execution>
Example 3.30. Simple ExecutionResults XML
<execution-results>
<result identifier='outStilton'>
<org.drools.compiler.Cheese>
<type>stilton</type>
<oldPrice>25</oldPrice>
<price>30</price>
</org.drools.compiler.Cheese>
</result>
</execution-results>
Spring and Camel, covered in the integrations book, facilitate declarative services.
Example 3.31. BatchExecution Marshalled to XML
<batch-execution>
<insert out-identifier="stilton">
<org.drools.compiler.Cheese>
<type>stilton</type>
<price>1</price>
<oldPrice>0</oldPrice>
</org.drools.compiler.Cheese>
</insert>
<query out-identifier='cheeses2' name='cheesesWithParams'>
<string>stilton</string>
<string>cheddar</string>
</query>
</batch-execution>
The CommandExecutor
returns an
ExecutionResults
, and this is handled by the pipeline code
snippet as well. A similar output for the <batch-execution> XML
sample above would be:
Example 3.32. ExecutionResults Marshalled to XML
<execution-results>
<result identifier="stilton">
<org.drools.compiler.Cheese>
<type>stilton</type>
<price>2</price>
</org.drools.compiler.Cheese>
</result>
<result identifier='cheeses2'>
<query-results>
<identifiers>
<identifier>cheese</identifier>
</identifiers>
<row>
<org.drools.compiler.Cheese>
<type>cheddar</type>
<price>2</price>
<oldPrice>0</oldPrice>
</org.drools.compiler.Cheese>
</row>
<row>
<org.drools.compiler.Cheese>
<type>cheddar</type>
<price>1</price>
<oldPrice>0</oldPrice>
</org.drools.compiler.Cheese>
</row>
</query-results>
</result>
</execution-results>
The BatchExecutionHelper
provides a configured XStream
instance to support the marshalling of Batch Executions, where the
resulting XML can be used as a message format, as shown above. Configured
converters only exist for the commands supported via the Command Factory.
The user may add other converters for their user objects. This is very
useful for scripting stateless or stateful knowledge sessions, especially
when services are involved.
There is currently no XML schema to support schema validation. The
basic format is outlined here, and the drools-pipeline module has an
illustrative unit test in the XStreamBatchExecutionTest
unit
test. The root element is <batch-execution> and it can contain zero
or more commands elements.
This contains a list of elements that represent commands, the supported commands is limited to those Commands provided by the Command Factory. The most basic of these is the <insert> element, which inserts objects. The contents of the insert element is the user object, as dictated by XStream.
Example 3.34. Insert
<batch-execution>
<insert>
...<!-- any user object -->
</insert>
</batch-execution>
The insert element features an "out-identifier" attribute, demanding that the inserted object will also be returned as part of the result payload.
Example 3.35. Insert with Out Identifier Command
<batch-execution>
<insert out-identifier='userVar'>
...
</insert>
</batch-execution>
It's also possible to insert a collection of objects using the
<insert-elements> element. This command does not support an
out-identifier. The org.domain.UserClass
is just an
illustrative user object that XStream would serialize.
Example 3.36. Insert Elements command
<batch-execution>
<insert-elements>
<org.domain.UserClass>
...
</org.domain.UserClass>
<org.domain.UserClass>
...
</org.domain.UserClass>
<org.domain.UserClass>
...
</org.domain.UserClass>
</insert-elements>
</batch-execution>
Next, there is the <set-global>
element, which
sets a global for the session.
Example 3.37. Insert Elements command
<batch-execution>
<set-global identifier='userVar'>
<org.domain.UserClass>
...
</org.domain.UserClass>
</set-global>
</batch-execution>
<set-global>
also supports two other optional
attributes, out
and out-identifier
.
A true value for the boolean out
will add the global to
the <batch-execution-results>
payload, using the name
from the identifier
attribute.
out-identifier
works like out
but
additionally allows you to override the identifier used in the
<batch-execution-results>
payload.
Example 3.38. Set Global Command
<batch-execution>
<set-global identifier='userVar1' out='true'>
<org.domain.UserClass>
...
</org.domain.UserClass>
</set-global>
<set-global identifier='userVar2' out-identifier='alternativeUserVar2'>
<org.domain.UserClass>
...
</org.domain.UserClass>
</set-global>
</batch-execution>
There is also a <get-global>
element, without
contents, with just an out-identifier
attribute. (There
is no need for an out
attribute because retrieving the
value is the sole purpose of a <get-global>
element.
Example 3.39. Get Global Command
<batch-execution>
<get-global identifier='userVar1' />
<get-global identifier='userVar2' out-identifier='alternativeUserVar2'/>
</batch-execution>
While the out
attribute is useful in returning
specific instances as a result payload, we often wish to run actual
queries. Both parameter and parameterless queries are supported. The
name
attribute is the name of the query to be called,
and the out-identifier
is the identifier to be used for
the query results in the <execution-results>
payload.
Example 3.40. Query Command
<batch-execution>
<query out-identifier='cheeses' name='cheeses'/>
<query out-identifier='cheeses2' name='cheesesWithParams'>
<string>stilton</string>
<string>cheddar</string>
</query>
</batch-execution>
The <start-process>
command accepts optional
parameters. Other process related methods will be added later, like
interacting with work items.
Example 3.41. Start Process Command
<batch-execution>
<startProcess processId='org.drools.actions'>
<parameter identifier='person'>
<org.drools.TestVariable>
<name>John Doe</name>
</org.drools.TestVariable>
</parameter>
</startProcess>
</batch-execution
Example 3.42. Signal Event Command
<signal-event process-instance-id='1' event-type='MyEvent'>
<string>MyValue</string>
</signal-event>
Example 3.43. Complete Work Item Command
<complete-work-item id='" + workItem.getId() + "' >
<result identifier='Result'>
<string>SomeOtherString</string>
</result>
</complete-work-item>
Support for more commands will be added over time.
The KieMarshallers
is used to marshal and unmarshal
KieSessions.
An instance of the KieMarshallers
can be retrieved from the KieServices
and at the simplest the it can be used as follows:
Example 3.45. Simple Marshaller Example
// ksession is the KieSession
// kbase is the KieBase
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Marshaller marshaller = KieServices.Factory.get().getMarshallers().newMarshaller( kbase );
marshaller.marshall( baos, ksession );
baos.close();
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 users can implement their own. The two supplied strategies
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 integer id for
each user object and stores them in a Map, while the id is written to the
stream. When unmarshalling it accesses the
IdentityMarshallingStrategy
map to retrieve the instance.
This means that if you use the IdentityMarshallingStrategy
,
it is stateful for the life of the Marshaller instance and will create ids
and keep references to all objects that it attempts to marshal. Below is
he code to use an Identity Marshalling Strategy.
Example 3.46. IdentityMarshallingStrategy
ByteArrayOutputStream baos = new ByteArrayOutputStream();
KieMarshallers kMarshallers = KieServices.Factory.get().getMarshallers()
ObjectMarshallingStrategy oms = kMarshallers.newIdentityMarshallingStrategy()
Marshaller marshaller =
kMarshallers.newMarshaller( kbase, new ObjectMarshallingStrategy[]{ oms } );
marshaller.marshall( baos, ksession );
baos.close();
For added flexability we can't assume that a single strategy is
suitable. Therefore we have added the
ObjectMarshallingStrategyAcceptor
interface that each Object
Marshalling Strategy contains. 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 of the provided implementations is
ClassFilterAcceptor
. This allows strings and wild cards to be
used to match class names. The default is "*.*", so in the above example
the Identity Marshalling Strategy is used which has a default "*.*"
acceptor.
Assuming that we want to serialize all classes except for one given package, where we will use identity lookup, we could do the following:
Example 3.47. IdentityMarshallingStrategy with Acceptor
ByteArrayOutputStream baos = new ByteArrayOutputStream();
KieMarshallers kMarshallers = KieServices.Factory.get().getMarshallers()
ObjectMarshallingStrategyAcceptor identityAcceptor =
kMarshallers.newClassFilterAcceptor( new String[] { "org.domain.pkg1.*" } );
ObjectMarshallingStrategy identityStrategy =
kMarshallers.newIdentityMarshallingStrategy( identityAcceptor );
ObjectMarshallingStrategy sms = kMarshallers.newSerializeMarshallingStrategy();
Marshaller marshaller =
kMarshallers.newMarshaller( kbase,
new ObjectMarshallingStrategy[]{ identityStrategy, sms } );
marshaller.marshall( baos, ksession );
baos.close();
Note that the acceptance checking order is in the natural order of the supplied elements.
Also note that if you are using scheduled matches (i.e. some of your rules use timers or calendars) they are marshallable only if, before you use it, you configure your KieSession to use a trackable timer job factory manager as it follows:
Example 3.48. Configuring a trackable timer job factory manager
KieSessionConfiguration ksconf = KieServices.Factory.get().newKieSessionConfiguration();
ksconf.setOption(TimerJobFactoryOption.get("trackable"));
KSession ksession = kbase.newKieSession(ksconf, null);
Longterm out of the box persistence with Java Persistence API (JPA) is possible with Drools. You will need to have some implementation of the Java Transaction API (JTA) installed. For development purposes we recommend the Bitronix Transaction Manager, as it's simple to set up and works embedded, but for production use JBoss Transactions is recommended.
Example 3.49. Simple example using transactions
Environment env = KieServices.Factory.get().newEnvironment();
env.set( EnvironmentName.ENTITY_MANAGER_FACTORY,
Persistence.createEntityManagerFactory( "emf-name" ) );
env.set( EnvironmentName.TRANSACTION_MANAGER,
TransactionManagerServices.getTransactionManager() );
// KnowledgeSessionConfiguration may be null, and a default will be used
KieSession ksession =
JPAKnowledgeService.newStatefulKnowledgeSession( kbase, null, env );
int sessionId = ksession.getId();
UserTransaction ut =
(UserTransaction) new InitialContext().lookup( "java:comp/UserTransaction" );
ut.begin();
ksession.insert( data1 );
ksession.insert( data2 );
ksession.startProcess( "process1" );
ut.commit();
To use a JPA, the Environment must be set with both the
EntityManagerFactory
and the TransactionManager
.
If rollback occurs the ksession state is also rolled back, so you can
continue to use it after a rollback. To load a previously persisted
Stateful Knowledge Session you'll need the id, as shown below:
Example 3.50. Loading a KieSession
StatefulKnowledgeSession ksession =
JPAKnowledgeService.loadStatefulKnowledgeSession( sessionId, kbase, null, env );
To enable persistence several classes must be added to your persistence.xml, as in the example below:
Example 3.51. Configuring JPA
<persistence-unit name="org.drools.persistence.jpa" transaction-type="JTA">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<jta-data-source>jdbc/BitronixJTADataSource</jta-data-source>
<class>org.drools.persistence.info.SessionInfo</class>
<class>org.drools.persistence.info.WorkItemInfo</class>
<properties>
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
<property name="hibernate.max_fetch_depth" value="3"/>
<property name="hibernate.hbm2ddl.auto" value="update" />
<property name="hibernate.show_sql" value="true" />
<property name="hibernate.transaction.manager_lookup_class"
value="org.hibernate.transaction.BTMTransactionManagerLookup" />
</properties>
</persistence-unit>
The jdbc JTA data source would have to be configured first. Bitronix provides a number of ways of doing this, and its documentation should be contsulted for details. For a quick start, here is the programmatic approach:
Example 3.52. Configuring JTA DataSource
PoolingDataSource ds = new PoolingDataSource();
ds.setUniqueName( "jdbc/BitronixJTADataSource" );
ds.setClassName( "org.h2.jdbcx.JdbcDataSource" );
ds.setMaxPoolSize( 3 );
ds.setAllowLocalTransactions( true );
ds.getDriverProperties().put( "user", "sa" );
ds.getDriverProperties().put( "password", "sasa" );
ds.getDriverProperties().put( "URL", "jdbc:h2:mem:mydb" );
ds.init();
Bitronix also provides a simple embedded JNDI service, ideal for testing. To use it add a jndi.properties file to your META-INF and add the following line to it:
Example 3.53. JNDI properties
java.naming.factory.initial=bitronix.tm.jndi.BitronixInitialContextFactory