Welcome
1. Introduction
1.1. Introduction
KIE (Knowledge Is Everything) is an umbrella project introduced to bring our related technologies together under one roof. It also acts as the core shared between our projects.
KIE contains the following different but related projects offering a complete portfolio of solutions for business automation and management:
-
Drools is a business-rule management system with a forward-chaining and backward-chaining inference-based rules engine, allowing fast and reliable evaluation of business rules and complex event processing. A rules engine is also a fundamental building block to create an expert system which, in artificial intelligence, is a computer system that emulates the decision-making ability of a human expert.
-
jBPM is a flexible Business Process Management suite allowing you to model your business goals by describing the steps that need to be executed to achieve those goals.
-
OptaPlanner is a constraint solver that optimizes use cases such as employee rostering, vehicle routing, task assignment and cloud optimization.
-
Business Central is a full featured web application for the visual composition of custom business rules and processes.
-
UberFire is a web-based workbench framework inspired by Eclipse Rich Client Platform.
The 7.x series will follow a more agile approach with more regular and iterative releases. We plan to do some bigger changes than normal for a series of minor releases, and users need to be aware those are coming before adopting.
-
UI sections and links will become object oriented, rather than task oriented. https://en.wikipedia.org/wiki/Object-oriented_user_interface
-
Authoring/Library will become project oriented, rather than repository oriented. You’ll create, browse and open projects rather than repositories. The repository concept will be pushed lower, for instance it’ll be created automatically when you create the project.
-
The old form modeller will be removed and only the new one made available. Although old forms will continue to render.
-
The new designer will continue to mature with more nodes and improved UXD. Eventually it’ll become the default editor, but we will not remove the old one until there is feature parity in BPMN2 support.
-
Continued UXD improvements in lots of places.
-
We will introduce the AppFormer project, this will be a re-org and consolidation of existing projects and result in some artifact renames. UberFire will become AppFormer-Core, forms, data modeller and dashbuilder will come under AppFormer. Dashbuilder will most likely be called Appformer-Insight.
The 8.x series will come towards the end of this year. We have ongoing parallel work to introduce concepts of workspaces with improved git support, that will have a built in workflow for forking and pull requests. This will be combined with horizontal scaling and improved high availability. These changes are important for usability and cloud scalability, but too much of a change for a minor release, hence the bump to 8.x
1.2. Getting Involved
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 :)
1.2.1. Sign up to jboss.org
Signing to jboss.org will give you access to the JBoss wiki, forums and JIRA. Go to https://www.jboss.org/ and click "Register".
1.2.2. Sign the Contributor Agreement
The only form you need to sign is the contributor agreement, which is fully automated via the web. As the image below says "This establishes the terms and conditions for your contributions and ensures that source code can be licensed appropriately"
1.2.3. Submitting issues via JIRA
To be able to interact with the core development team you will need to use JIRA, the issue tracker. This ensures that all requests are logged and allocated to a release schedule and all discussions captured in one place. Bug reports, bug fixes, feature requests and feature submissions should all go here. General questions should be undertaken at the mailing lists.
Minor code submissions, like format or documentation fixes do not need an associated JIRA issue created.
1.2.4. Fork GitHub
With the contributor agreement signed and your requests submitted to JIRA you should now be ready to code :) Create a GitHub account and fork any of the Drools, jBPM or Guvnor repositories. The fork will create a copy in your own GitHub space which you can work on at your own pace. If you make a mistake, don’t worry blow it away and fork again. Note each GitHub repository provides you the clone (checkout) URL, GitHub will provide you URLs specific to your fork.
1.2.5. Writing Tests
When writing tests, try and keep them minimal and self contained. We prefer to keep the DRL fragments within the test, as it makes for quicker reviewing. If there are a large number of rules then using a String is not practical so then by all means place them in separate DRL files instead to be loaded from the classpath. If your tests need to use a model, please try to use those that already exist for other unit tests; such as Person, Cheese or Order. If no classes exist that have the fields you need, try and update fields of existing classes before adding a new class.
There are a vast number of tests to look over to get an idea, MiscTest is a good place to start.
1.2.6. Commit with Correct Conventions
When you commit, make sure you use the correct conventions. The commit must start with the JIRA issue id, such as DROOLS-1946. This ensures the commits are cross referenced via JIRA, so we can see all commits for a given issue in the same place. After the id the title of the issue should come next. Then use a newline, indented with a dash, to provide additional information related to this commit. Use an additional new line and dash for each separate point you wish to make. You may add additional JIRA cross references to the same commit, if it’s appropriate. In general try to avoid combining unrelated issues in the same commit.
Don’t forget to rebase your local fork from the original master and then push your commits back to your fork.
1.2.7. Submit Pull Requests
With your code rebased from original master and pushed to your personal GitHub area, you can now submit your work as a pull request. If you look at the top of the page in GitHub for your work area there will be a "Pull Request" button. Selecting this will then provide a gui to automate the submission of your pull request.
The pull request then goes into a queue for everyone to see and comment on. Below you can see a typical pull request. The pull requests allow for discussions and it shows all associated commits and the diffs for each commit. The discussions typically involve code reviews which provide helpful suggestions for improvements, and allows for us to leave inline comments on specific parts of the code. Don’t be disheartened if we don’t merge straight away, it can often take several revisions before we accept a pull request. Luckily GitHub makes it very trivial to go back to your code, do some more commits and then update your pull request to your latest and greatest.
It can take time for us to get round to responding to pull requests, so please be patient. Submitted tests that come with a fix will generally be applied quite quickly, where as just tests will often way until we get time to also submit that with a fix. Don’t forget to rebase and resubmit your request from time to time, otherwise over time it will have merge conflicts and core developers will general ignore those.
1.3. Installation and Setup (Core and IDE)
1.3.1. Installing and using
Drools provides an Eclipse-based IDE (which is optional), but at its core only Java 1.5 (Java SE) is required.
A simple way to get started is to download and install the Eclipse plug-in - this will also require the Eclipse GEF framework to be installed (see below, if you don’t have it installed already). This will provide you with all the dependencies you need to get going: you can simply create a new rule project and everything will be done for you. Refer to the chapter on Business Central and IDE for detailed instructions on this. Installing the Eclipse plug-in is generally as simple as unzipping a file into your Eclipse plug-in directory.
Use of the Eclipse plug-in is not required. Rule files are just textual input (or spreadsheets as the case may be) and the IDE (also known as Business Central) is just a convenience. People have integrated the Drools engine in many ways, there is no "one size fits all".
Alternatively, you can download the binary distribution, and include the relevant JARs in your projects classpath.
1.3.1.1. Dependencies and JARs
Drools is broken down into a few modules, some are required during rule development/compiling, and some are required at runtime. In many cases, people will simply want to include all the dependencies at runtime, and this is fine. It allows you to have the most flexibility. However, some may prefer to have their "runtime" stripped down to the bare minimum, as they will be deploying rules in binary form - this is also possible. The core Drools engine can be quite compact, and only requires a few 100 kilobytes across 3 JAR files.
The following is a description of the important libraries that make up JBoss Drools
-
knowledge-api.jar - this provides the interfaces and factories. It also helps clearly show what is intended as a user API and what is just an engine API.
-
knowledge-internal-api.jar - this provides internal interfaces and factories.
-
drools-core.jar - this is the core Drools engine, runtime component. Contains both the RETE engine and the LEAPS engine. This is the only runtime dependency if you are pre-compiling rules (and deploying via Package or RuleBase objects).
-
drools-compiler.jar - this contains the compiler/builder components to take rule source, and build executable rule bases. This is often a runtime dependency of your application, but it need not be if you are pre-compiling your rules. This depends on drools-core.
-
drools-jsr94.jar - this is the JSR-94 compliant implementation, this is essentially a layer over the drools-compiler component. Note that due to the nature of the JSR-94 specification, not all features are easily exposed via this interface. In some cases, it will be easier to go direct to the Drools API, but in some environments the JSR-94 is mandated.
-
drools-decisiontables.jar - this is the decision tables 'compiler' component, which uses the drools-compiler component. This supports both excel and CSV input formats.
There are quite a few other dependencies which the above components require, most of which are for the drools-compiler, drools-jsr94 or drools-decisiontables module. Some key ones to note are "POI" which provides the spreadsheet parsing ability, and "antlr" which provides the parsing for the rule language itself.
if you are using Drools in J2EE or servlet containers and you come across classpath issues with "JDT", then you can switch to the janino compiler. Set the system property "drools.compiler": For example: -Ddrools.compiler=JANINO. |
For up to date info on dependencies in a release, consult the released POMs, which can be found on the Maven repository.
1.3.1.2. Use with Maven, Gradle, Ivy, Buildr or Ant
The JARs are also available in the central Maven repository (and also in https://repository.jboss.org/nexus/index.html#nexus-search;gavorg.drools~[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.
1.3.1.3. Runtime
The "runtime" requirements mentioned here are if you are deploying rules as their binary form (either as KnowledgePackage objects, or KnowledgeBase objects etc). This is an optional feature that allows you to keep your runtime very light. You may use drools-compiler to produce rule packages "out of process", and then deploy them to a runtime system. This runtime system only requires drools-core.jar and knowledge-api for execution. This is an optional deployment pattern, and many people do not need to "trim" their application this much, but it is an ideal option for certain environments.
1.3.1.4. Installing IDE (Rule Workbench)
The rule workbench (for Eclipse) requires that you have Eclipse 3.4 or greater, as well as Eclipse GEF 3.4 or greater. You can install it either by downloading the plug-in or using the update site.
Another option is to use the JBoss IDE, which comes with all the plug-in requirements pre packaged, as well as a choice of other tools separate to rules. You can choose just to install rules from the "bundle" that JBoss IDE ships with.
Installing GEF (a required dependency)
GEF is the Eclipse Graphical Editing Framework, which is used for graph viewing components in the plug-in.
If you don’t have GEF installed, you can install it using the built in update mechanism (or downloading GEF from the Eclipse.org website not recommended). JBoss IDE has GEF already, as do many other "distributions" of Eclipse, so this step may be redundant for some people.
Open the Help→Software updates…→Available Software→Add Site… from the help menu. Location is:
http://download.eclipse.org/tools/gef/updates/releases/
Next you choose the GEF plug-in:
Press next, and agree to install the plug-in (an Eclipse restart may be required). Once this is completed, then you can continue on installing the rules plug-in.
Installing GEF from zip file
To install from the zip file, download and unzip the file. Inside the zip you will see a plug-in directory, and the plug-in JAR itself. You place the plug-in JAR into your Eclipse applications plug-in directory, and restart Eclipse.
Installing Drools plug-in from zip file
Download the Drools Eclipse IDE plugin from the link below. Unzip the downloaded file in your main eclipse folder (do not just copy the file there, extract it so that the feature and plugin JARs end up in the features and plugin directory of eclipse) and (re)start Eclipse.
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 Runtimes
A Drools runtime is a collection of JARs on your file system that represent one specific release of the Drools project JARs. To create a runtime, you must point the IDE to the release of your choice. If you want to create a new runtime based on the latest Drools project JARs included in the plugin itself, you can also easily do that. You are required to specify a default Drools runtime for your Eclipse workspace, but each individual project can override the default and select the appropriate runtime for that project specifically.
You are required to define one or more Drools runtimes using the Eclipse preferences view. To open up your preferences, in the menu Window select the Preferences menu item. A new preferences dialog should show all your preferences. On the left side of this dialog, under the Drools category, select "Installed Drools runtimes". The panel on the right should then show the currently defined Drools runtimes. If you have not yet defined any runtimes, it should like something like the figure below.
To define a new Drools runtime, click the add button. A dialog as shown below should pop up, requiring the name for your runtime and the location on your file system where it can be found.
In general, you have two options:
-
If you simply want to use the default JARs as included in the Drools Eclipse plugin, you can create a new Drools runtime automatically by clicking the "Create a new Drools 5 runtime …" button. A file browser will show up, asking you to select the folder on your file system where you want this runtime to be created. The plugin will then automatically copy all required dependencies to the specified folder. After selecting this folder, the dialog should look like the figure shown below.
-
If you want to use one specific release of the Drools project, you should create a folder on your file system that contains all the necessary Drools libraries and dependencies. Instead of creating a new Drools runtime as explained above, give your runtime a name and select the location of this folder containing all the required JARs.
After clicking the OK button, the runtime should show up in your table of installed Drools runtimes, as shown below. Click checkbox in front of the newly created runtime to make it the default Drools runtime. The default Drools runtime will be used as the runtime of all your Drools project that have not selected a project-specific runtime.
You can add as many Drools runtimes as you need. For example, the screenshot below shows a configuration where three runtimes have been defined: a Drools 4.0.7 runtime, a Drools 5.0.0 runtime and a Drools 5.0.0.SNAPSHOT runtime. The Drools 5.0.0 runtime is selected as the default one.
Note that you will need to restart Eclipse if you changed the default runtime and you want to make sure that all the projects that are using the default runtime update their classpath accordingly.
Whenever you create a Drools project (using the New Drools Project wizard or by converting an existing Java project to a Drools project using the "Convert to Drools Project" action that is shown when you are in the Drools perspective and you right-click an existing Java project), the plugin will automatically add all the required JARs to the classpath of your project.
When creating a new Drools project, the plugin will automatically use the default Drools runtime for that project, unless you specify a project-specific one. You can do this in the final step of the New Drools Project wizard, as shown below, by deselecting the "Use default Drools runtime" checkbox and selecting the appropriate runtime in the drop-down box. If you click the "Configure workspace settings …" link, the workspace preferences showing the currently installed Drools runtimes will be opened, so you can add new runtimes there.
You can change the runtime of a Drools project at any time by opening the project properties (right-click the project and select Properties) and selecting the Drools category, as shown below. Check the "Enable project specific settings" checkbox and select the appropriate runtime from the drop-down box. If you click the "Configure workspace settings …" link, the workspace preferences showing the currently installed Drools runtimes will be opened, so you can add new runtimes there. If you deselect the "Enable project specific settings" checkbox, it will use the default runtime as defined in your global preferences.
1.3.2. Building from source
1.3.2.1. Getting the sources
The source code of each Maven artifact is available in the JBoss Maven repository as a source JAR. The same source JARs are also included in the download zips. However, if you want to build from source, it’s highly recommended to get our sources from our source control.
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.
1.3.2.2. Building the sources
In essence, building from source is very easy, for example if you want to build the guvnor project:
$ git clone git@github.com:kiegroup/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.
1.3.3. Eclipse
1.3.3.1. Importing Eclipse Projects
With the Eclipse project files generated they can now be imported into Eclipse. When starting Eclipse open the workspace in the root of your subversion checkout.
When calling mvn install
all the project dependencies were downloaded and added to the local Maven repository.
Eclipse cannot find those dependencies unless you tell it where that repository is.
To do this setup an M2_REPO
classpath variable.
KIE
KIE is the shared core for Drools and jBPM. It provides a unified methodology and programming model for building, deploying and utilizing resources.
2. KIE
2.1. Overview
2.1.1. Anatomy of Projects
The process of researching an integration knowledge solution for Drools and jBPM has simply used the "kiegroup" group name. This name permeates GitHub accounts and Maven POMs. As scopes broadened and new projects were spun KIE, an acronym for Knowledge Is Everything, was chosen as the new group name. The KIE name is also used for the shared aspects of the system; such as the unified build, deploy and utilization.
KIE currently consists of the following subprojects:
OptaPlanner, a local search and optimization tool, has been spun off from Drools Planner and is now a top level project with Drools and jBPM. This was a natural evolution as Optaplanner, while having strong Drools integration, has long been independent of Drools.
From the Polymita acquisition, along with other things, comes the powerful Dashboard Builder which provides powerful reporting capabilities. Dashboard Builder is currently a temporary name and after the 6.0 release a new name will be chosen. Dashboard Builder is completely independent of Drools and jBPM and will be used by many projects at JBoss, and hopefully outside of JBoss :)
UberFire is the new base Business Central project, spun off from the ground up rewrite. UberFire provides Eclipse-like workbench capabilities, with panels and pages from plugins. The project is independent of Drools and jBPM and anyone can use it as a basis of building flexible and powerful workbenches like Business Central. UberFire will be used for console and workbench development throughout JBoss.
It was determined that the Guvnor brand leaked too much from its intended role; such as the authoring metaphors, like Decision Tables, being considered Guvnor components instead of Drools components. This wasn’t helped by the monolithic projects structure used in 5.x for Guvnor. In 6.0 Guvnor’s focus has been narrowed to encapsulate the set of UberFire plugins that provide the basis for building a web based IDE. Such as Maven integration for building and deploying, management of Maven repositories and activity notifications via inboxes. Drools and jBPM build Business Central distributions using Uberfire as the base and including a set of plugins, such as Guvnor, along with their own plugins for things like decision tables, guided editors, BPMN2 designer, human tasks. Business Central is called business-central.
KIE-WB is the uber workbench that combined all the Guvnor, Drools and jBPM plugins. The jBPM-WB is ghosted out, as it doesn’t actually exist, being made redundant by KIE-WB.
2.1.2. Lifecycles
The different aspects, or life cycles, of working with KIE system, whether it’s Drools or jBPM, can typically be broken down into the following:
-
Author
-
Authoring of knowledge using a UI metaphor, such as: DRL, BPMN2, decision table, class models.
-
-
Build
-
Builds the authored knowledge into deployable units.
-
For KIE this unit is a JAR.
-
-
Test
-
Test KIE knowledge before it’s deployed to the application.
-
-
Deploy
-
Deploys the unit to a location where applications may utilize (consume) them.
-
KIE uses Maven style repository.
-
-
Utilize
-
The loading of a JAR to provide a KIE session (KieSession), for which the application can interact with.
-
KIE exposes the JAR at runtime via a KIE container (KieContainer).
-
KieSessions, for the runtime’s to interact with, are created from the KieContainer.
-
-
Run
-
System interaction with the KieSession, via API.
-
-
Work
-
User interaction with the KieSession, via command line or UI.
-
-
Manage
-
Manage any KieSession or KieContainer.
-
2.1.3. Installation environment options for Drools
With Drools, you can set up a development environment to develop business applications, a runtime environment to run those applications to support decisions, or both.
-
Development environment: Typically consists of one Business Central installation and at least one KIE Server installation. You can use Business Central to design decisions and other artifacts, and you can use KIE Server to execute and test the artifacts that you created.
-
Runtime environment: Consists of one or more KIE Server instances with or without Business Central. Business Central has an embedded Drools controller. If you install Business Central, use the Menu → Deploy → Execution servers page to create and maintain containers. If you want to automate KIE Server management without Business Central, you can use the headless Drools controller.
You can also cluster both development and runtime environments. A clustered development or runtime environment consists of a unified group or "cluster" of two or more servers. The primary benefit of clustering Drools development environments is high availability and enhanced collaboration, while the primary benefit of clustering Drools runtime environments is high availability and load balancing. High availability decreases the chance of a loss of data when a single server fails. When a server fails, another server fills the gap by providing a copy of the data that was on the failed server. When the failed server comes online again, it resumes its place in the cluster. Load balancing shares the computing load across the nodes of the cluster to improve the overall performance.
Clustering of the runtime environment is currently supported on Red Hat JBoss EAP 7.2 only. Clustering of Business Central is currently a Technology Preview feature that is not yet intended for production use. |
2.1.4. Decision-authoring assets in Drools
Drools supports several assets that you can use to define business decisions for your decision service. Each decision-authoring asset has different advantages, and you might prefer to use one or a combination of multiple assets depending on your goals and needs.
The following table highlights the main decision-authoring assets supported in Drools projects to help you decide or confirm the best method for defining decisions in your decision service.
Asset | Highlights | Authoring tools | Documentation |
---|---|---|---|
Decision Model and Notation (DMN) models |
|
Business Central or other DMN-compliant editor |
|
Guided decision tables |
|
Business Central |
|
Spreadsheet decision tables |
|
Spreadsheet editor |
|
Guided rules |
|
Business Central |
|
Guided rule templates |
|
Business Central |
|
DRL rules |
|
Business Central or integrated development environment (IDE) |
|
Predictive Model Markup Language (PMML) models |
|
PMML or XML editor |
2.1.5. Project storage and build options with Drools
As you develop a Drools project, you need to be able to track the versions of your project with a version-controlled repository, manage your project assets in a stable environment, and build your project for testing and deployment. You can use Business Central for all of these tasks, or use a combination of Business Central and external tools and repositories. Drools supports Git repositories for project version control, Apache Maven for project management, and a variety of Maven-based, Java-based, or custom-tool-based build options.
The following options are the main methods for Drools project versioning, storage, and building:
Versioning option | Description | Documentation |
---|---|---|
Business Central Git VFS |
Business Central contains a built-in Git Virtual File System (VFS) that stores all processes, rules, and other artifacts that you create in the authoring environment. Git is a distributed version control system that implements revisions as commit objects. When you commit your changes into a repository, a new commit object in the Git repository is created. When you create a project in Business Central, the project is added to the Git repository connected to Business Central. |
NA |
External Git repository |
If you have Drools projects in Git repositories outside of Business Central, you can import them into Drools spaces and use Git hooks to synchronize the internal and external Git repositories. |
NA |
Management option | Description | Documentation | ||
---|---|---|---|---|
Business Central Maven repository |
Business Central contains a built-in Maven repository that organizes and builds project assets that you create in the authoring environment. Maven is a distributed build-automation tool that uses repositories to store Java libraries, plug-ins, and other build artifacts. When building projects and archetypes, Maven dynamically retrieves Java libraries and Maven plug-ins from local or remote repositories to promote shared dependencies across projects.
|
|||
External Maven repository |
If you have Drools projects in an external Maven repository, such as Nexus or Artifactory, you can create a |
Build option | Description | Documentation |
---|---|---|
Business Central (KJAR) |
Business Central builds Drools projects stored in either the built-in Maven repository or a configured external Maven repository. Projects in Business Central are packaged automatically as knowledge JAR (KJAR) files with all components needed for deployment when you build the projects. |
|
Standalone Maven project (KJAR) |
If you have a standalone Drools Maven project outside of Business Central, you can edit the project |
|
Embedded Java application (KJAR) |
If you have an embedded Java application from which you want to build your Drools project, you can use a |
|
CI/CD tool (KJAR) |
If you use a tool for continuous integration and continuous delivery (CI/CD), you can configure the tool set to integrate with your Drools Git repositories to build a specified project. Ensure that your projects are packaged and built as KJAR files to ensure optimal deployment. |
NA |
2.1.6. Project deployment options with Drools
After you develop, test, and build your Drools project, you can deploy the project to begin using the business assets you have created. You can deploy a Drools project to a configured KIE Server, to an embedded Java application, or into a Red Hat OpenShift Container Platform environment for an enhanced containerized implementation.
The following options are the main methods for Drools project deployment:
Deployment option | Description | Documentation |
---|---|---|
Deployment to KIE Server |
KIE Server is the server provided with Drools that runs the decision services, process applications, and other deployable assets from a packaged and deployed Drools project (KJAR file). These services are consumed at run time through an instantiated KIE container, or deployment unit. You can deploy and maintain deployment units in KIE Server using Business Central or using a headless Drools controller with its associated REST API (considered a managed KIE Server instance). You can also deploy and maintain deployment units using the KIE Server REST API or Java client API from a standalone Maven project, an embedded Java application, or other custom environment (considered an unmanaged KIE Server instance). |
|
Deployment to an embedded Java application |
If you want to deploy Drools projects to your own Java virtual machine (JVM) environment, microservice, or application server, you can bundle the application resources in the project WAR files to create a deployment unit similar to a KIE container. You can also use the core KIE APIs (not KIE Server APIs) to configure a KIE scanner to periodically update KIE containers. |
2.1.7. Asset execution options with Drools
After you build and deploy your Drools project to KIE Server or other environment, you can execute the deployed assets for testing or for runtime consumption. You can also execute assets locally in addition to or instead of executing them after deployment.
The following options are the main methods for Drools asset execution:
Execution option | Description | Documentation |
---|---|---|
Execution in KIE Server |
If you deployed Drools project assets to KIE Server, you can use the KIE Server REST API or Java client API to execute and interact with the deployed assets. You can also use Business Central or the headless Drools controller outside of Business Central to manage the configurations and KIE containers in the KIE Server instances associated with your deployed assets. |
|
Execution in an embedded Java application |
If you deployed Drools project assets in your own Java virtual machine (JVM) environment, microservice, or application server, you can use custom APIs or application interactions with core KIE APIs (not KIE Server APIs) to execute assets in the embedded engine. |
|
Execution in a local environment for extended testing |
As part of your development cycle, you can execute assets locally to ensure that the assets you have created in Drools function as intended. You can use local execution in addition to or instead of executing assets after deployment. |
Smart Router (KIE Server router)
Depending on your deployment and execution environment, you can use a Smart Router to aggregate multiple independent KIE Server instances as though they are a single server. Smart Router is a single endpoint that can receive calls from client applications to any of your services and route each call automatically to the KIE Server that runs the service. For more information about Smart Router, see KIE Server router. |
2.1.8. Example decision management architectures with Drools
The following scenarios illustrate common variations of Drools installation, asset authoring, project storage, project deployment, and asset execution in a decision management architecture. Each section summarizes the methods and tools used and the advantages for the given architecture. The examples are basic and are only a few of the many combinations you might consider, depending on your specific goals and needs with Drools.
- Drools on Wildfly with Business Central and KIE Server
-
-
Installation environment: Drools on Wildfly
-
Project storage and build environment: External Git repository for project versioning synchronized with the Business Central Git repository using Git hooks, and external Maven repository for project management and building configured with KIE Server
-
Asset-authoring tool: Business Central
-
Main asset types: Decision Model and Notation (DMN) models for decisions
-
Project deployment and execution environment: KIE Server
-
Scenario advantages:
-
Stable implementation of Drools in an on-premise development environment
-
Access to the repositories, assets, asset designers, and project build options in Business Central
-
Standardized asset-authoring approach using DMN for optimal integration and stability
-
Access to KIE Server functionality and KIE APIs for asset deployment and execution
-
Figure 2. Drools on Wildfly with Business Central and KIE Server -
- Drools on Wildfly with an IDE and KIE Server
-
-
Installation environment: Drools on Wildfly
-
Project storage and build environment: External Git repository for project versioning (not synchronized with Business Central) and external Maven repository for project management and building configured with KIE Server
-
Asset-authoring tools: Integrated development environment (IDE), such as Eclipse, and a spreadsheet editor or a Decision Model and Notation (DMN) modeling tool for other decision formats
-
Main asset types: Drools Rule Language (DRL) rules, spreadsheet decision tables, and Decision Model and Notation (DMN) models for decisions
-
Project deployment and execution environment: KIE Server
-
Scenario advantages:
-
Flexible implementation of Drools in an on-premise development environment
-
Ability to define business assets using an external IDE and other asset-authoring tools of your choice
-
Access to KIE Server functionality and KIE APIs for asset deployment and execution
-
Figure 3. Drools on Wildfly with an IDE and KIE Server -
- Drools with an IDE and an embedded Java application
-
-
Installation environment: Drools libraries embedded within a custom application
-
Project storage and build environment: External Git repository for project versioning (not synchronized with Business Central) and external Maven repository for project management and building configured with your embedded Java application (not configured with KIE Server)
-
Asset-authoring tools: Integrated development environment (IDE), such as Eclipse, and a spreadsheet editor or a Decision Model and Notation (DMN) modeling tool for other decision formats
-
Main asset types: Drools Rule Language (DRL) rules, spreadsheet decision tables, and Decision Model and Notation (DMN) models for decisions
-
Project deployment and execution environment: Embedded Java application, such as in a Java virtual machine (JVM) environment, microservice, or custom application server
-
Scenario advantages:
-
Custom implementation of Drools in an on-premise development environment with an embedded Java application
-
Ability to define business assets using an external IDE and other asset-authoring tools of your choice
-
Use of custom APIs to interact with core KIE APIs (not KIE Server APIs) and to execute assets in the embedded engine
-
Figure 4. Drools with an IDE and an embedded Java application -
2.2. Build, Deploy, Utilize and Run
2.2.1. Introduction
6.0 introduces a new configuration and convention approach to building KIE bases, instead of using the programmatic builder approach in 5.x. The builder is still available to fall back on, as it’s used for the tooling integration.
Building now uses Maven, and aligns with Maven practices. A KIE project or module is simply a Maven Java project or module; with an additional metadata file META-INF/kmodule.xml. The kmodule.xml file is the descriptor that selects resources to KIE bases and configures those KIE bases and sessions. There is also alternative XML support via Spring and OSGi BluePrints.
While standard Maven can build and package KIE resources, it will not provide validation at build time. There is a Maven plugin which is recommended to use to get build time validation. The plugin also generates many classes, making the runtime loading faster too.
The example project layout and Maven POM descriptor is illustrated in the screenshot
KIE uses defaults to minimise the amount of configuration. With an empty kmodule.xml being the simplest configuration. There must always be a kmodule.xml file, even if empty, as it’s used for discovery of the JAR and its contents.
Maven can either 'mvn install' to deploy a KieModule to the local machine, where all other applications on the local machine use it. Or it can 'mvn deploy' to push the KieModule to a remote Maven repository. Building the Application will pull in the KieModule and populate the local Maven repository in the process.
JARs can be deployed in one of two ways. Either added to the classpath, like any other JAR in a Maven dependency listing, or they can be dynamically loaded at runtime. KIE will scan the classpath to find all the JARs with a kmodule.xml in it. Each found JAR is represented by the KieModule interface. The terms classpath KieModule and dynamic KieModule are used to refer to the two loading approaches. While dynamic modules support side by side versioning, classpath modules do not. Further once a module is on the classpath, no other version may be loaded dynamically.
Detailed references for the API are included in the next sections, the impatient can jump straight to the examples section, which is fairly self-explanatory on the different use cases.
2.2.2. Building
2.2.2.1. Creating and building a Kie Project
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 Excel files, must be stored in the resources 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:
<?xml version="1.0" encoding="UTF-8"?>
<kmodule xmlns="http://www.drools.org/xsd/kmodule"/>
In this way the kmodule will contain one single default KieBase
.
All Kie assets stored under the resources folder, or any of its subfolders, 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 built from the classpath:
KieServices kieServices = KieServices.Factory.get();
KieContainer kContainer = kieServices.getKieClasspathContainer();
` 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.
2.2.2.2. The kmodule.xml file
As explained 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:
<kmodule xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.drools.org/xsd/kmodule">
<configuration>
<property key="drools.evaluator.supersetOf" value="org.mycompany.SupersetOfEvaluatorDefinition"/>
</configuration>
<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_2" type="stateless" default="false" beliefSystem="jtms"/>
</kbase>
<kbase name="KBase2" default="false" eventProcessingMode="stream" equalsBehavior="equality" declarativeAgenda="enabled" packages="org.domain.pkg2, org.domain.pkg3" includes="KBase1">
<ksession name="KSession3_1" type="stateful" default="false" clockType="realtime">
<fileLogger file="drools.log" threaded="true" interval="10"/>
<workItemHandlers>
<workItemHandler name="name" type="org.domain.WorkItemHandler"/>
</workItemHandlers>
<calendars>
<calendar name="monday" type="org.domain.Monday"/>
</calendars>
<listeners>
<ruleRuntimeEventListener type="org.domain.RuleRuntimeListener"/>
<agendaEventListener type="org.domain.FirstAgendaListener"/>
<agendaEventListener type="org.domain.SecondAgendaListener"/>
<processEventListener type="org.domain.ProcessListener"/>
</listeners>
</ksession>
</kbase>
</kmodule>
Here the
tag contains a list of key-value pairs that are the optional properties used to configure the KieBase
s building process.
For instance this sample kmodule.xml file defines an additional custom operator named supersetOf
and implemented by the org.mycompany.SupersetOfEvaluatorDefinition
class.
After this 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:
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. |
Similarly all attributes of the ksession tag (except of course the name) have meaningful default. They are listed and described in the following table:
Attribute name | Default value | Admitted values | Meaning |
---|---|---|---|
name |
none |
any |
Unique name of this KieSession. Used to fetch the 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 pseudo clock controlled by the application. This clock is especially useful for unit testing temporal rules. |
beliefSystem |
simple |
simple, jtms, defeasible |
Defines the type of belief system used by the KieSession. |
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 Calendar
s plus some listeners that can be of 3 different types: ruleRuntimeEventListener, 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.
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.
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 used to generate a ReleaseId
that uniquely identifies this project inside your application.
This allows creation of a new KieContainer from the project by simply passing its ReleaseId
to the KieServices
.
KieServices kieServices = KieServices.Factory.get();
ReleaseId releaseId = kieServices.newReleaseId( "org.acme", "myartifact", "1.0" );
KieContainer kieContainer = kieServices.newKieContainer( releaseId );
2.2.2.3. Building with Maven
The KIE plugin for Maven ensures that artifact resources are validated and pre-compiled, it is recommended that this is used at all times.
To use the plugin simply add it to the build section of the Maven pom.xml and activate it by using packaging kjar
.
<packaging>kjar</packaging>
...
<build>
<plugins>
<plugin>
<groupId>org.kie</groupId>
<artifactId>kie-maven-plugin</artifactId>
<version>7.35.0.Final</version>
<extensions>true</extensions>
</plugin>
</plugins>
</build>
The plugin comes with support for all the Drools/jBPM knowledge resources.
However, in case you are using specific KIE annotations in your Java classes, like for example @kie.api.Position
, you will need to add compile time dependency on kie-api
into your project.
We recommend to use the provided scope for all the additional KIE dependencies.
That way the kjar stays as lightweight as possible, and not dependant on any particular KIE version.
Building a KIE module without the Maven plugin will copy all the resources, as is, into the resulting JAR. When that JAR is loaded by the runtime, it will attempt to build all the resources then. If there are compilation issues it will return a null KieContainer. It also pushes the compilation overhead to the runtime. In general this is not recommended, and the Maven plugin should always be used.
2.2.2.4. Defining a KieModule programmatically
It is also possible to define the KieBase
s and KieSession
s belonging to a KieModule programmatically instead of the declarative definition in the kmodule.xml file.
The same programmatic API also allows in explicitly adding 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 components you can obtain an instance of the KieFileSystem
from the KieServices
.
The kmodule.xml configuration file must be added to the filesystem.
This is a mandatory step.
Kie also provides a convenient fluent API, implemented by the KieModuleModel
, to programmatically 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:
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();
kfs.writeKModuleXML(kieModuleModel.toXML());
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.
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 the latter 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 extensions and explicitly assign a specific ResourceType
to a Resource
as shown below:
KieFileSystem kfs = ...
kfs.write( "src/main/resources/myDrl.txt",
kieServices.getResources().newInputStreamResource( drlStream )
.setResourceType(ResourceType.DRL) );
Add all the resources to the KieFileSystem
and build it by passing the KieFileSystem
to a KieBuilder
When the contents of a KieFileSystem
are successfully built, the resulting KieModule
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
doesn’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.
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
reports 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 nothing is added to the KieRepository
.
WARNING and INFO results can be ignored, but are available for inspection.
KieBuilder kieBuilder = kieServices.newKieBuilder( kfs ).buildAll();
assertEquals( 0, kieBuilder.getResults().getMessages( Message.Level.ERROR ).size() );
2.2.2.5. Changing the Default Build Result Severity
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, configured like any other option in Drools, 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:
// 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>
2.2.2.6. Building and running Drools in a fat jar
Many modules of Drools (e.g. drools-core, drools-compiler) have a file named kie.conf
containing the names of the classes implementing the services
provided by the corresponding module. When running Drools in a fat JAR, for example created by the Maven Shade Plugin, those various kie.conf
files
need to be merged, otherwise , the fat JAR will contain only 1 kie.conf from a single dependency, resulting into errors. You can merge resources in
the Maven Shade Plugin using transformers, like this:
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/kie.conf</resource>
</transformer>
For instance this is required when running Drools in a Vert.x application. In this case the Maven Shade Plugin can be configured as it follows:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Main-Class>io.vertx.core.Launcher</Main-Class>
<Main-Verticle>${main.verticle}</Main-Verticle>
</manifestEntries>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/services/io.vertx.core.spi.VerticleFactory</resource>
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
<resource>META-INF/kie.conf</resource>
</transformer>
</transformers>
<artifactSet>
</artifactSet>
<outputFile>${project.build.directory}/${project.artifactId}-${project.version}-fat.jar</outputFile>
</configuration>
</execution>
</executions>
</plugin>
2.2.3. Deploying
2.2.3.1. KieBase
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.
KieServices kieServices = KieServices.Factory.get();
KieBaseConfiguration kbaseConf = kieServices.newKieBaseConfiguration( null, MyType.class.getClassLoader() );
KieBase kbase = kieContainer.newKieBase( kbaseConf );
2.2.3.2. KieSessions and KieBase Modifications
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.
2.2.3.3. KieScanner
The KieScanner
allows continuous monitoring of your Maven repository to check whether a new release of a Kie project has been installed.
A new release is deployed in the KieContainer
wrapping that project.
The use of the KieScanner
requires kie-ci.jar to be on the classpath.
A KieScanner
can be registered on a KieContainer
as in the following example.
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.
At this point, existing KieBase
s and KieSession
s under the control of KieContainer
will get automatically upgraded with it - specifically, those KieBase
s obtained with getKieBase()
along with their related KieSession
s, and any KieSession
obtained directly with KieContainer.newKieSession()
thus referencing the default KieBase
.
Additionally, from this moment on, all the new KieBase
s and KieSession
s created from that KieContainer
will use the new project version.
Please notice however any existing KieBase
which was obtained via newKieBase()
before the KieScanner upgrade, and any of its related KieSession
s, will not get automatically upgraded; this is because KieBase
s obtained via newKieBase()
are not under the direct control of the KieContainer
.
The KieScanner
will only pickup changes to deployed jars if it is using a SNAPSHOT, version range, the LATEST, or the RELEASE setting.
Fixed versions will not automatically update at runtime.
In case you don’t want to install a maven repository, it is also possible to have a KieScanner
that works
by simply fetching update from a folder of a plain file system. You can create such a KieScanner
as simply as
KieServices kieServices = KieServices.Factory.get();
KieScanner kScanner = kieServices.newKieScanner( kContainer, "/myrepo/kjars" );
where "/myrepo/kjars" will be the folder where the KieScanner
will look for kjar updates. The jar files placed in this folder
have to follow the maven convention and then have to be a name in the form {artifactId}-{versionId}.jar
2.2.3.4. Maven Versions and Dependencies
Maven supports a number of mechanisms to manage versioning and dependencies within applications. Modules can be published with specific version numbers, or they can use the SNAPSHOT suffix. Dependencies can specify version ranges to consume, or take advantage of SNAPSHOT mechanism.
StackOverflow provides a very good description for this, which is reproduced below.
Since Maven 3.x metaversions RELEASE and LATEST are no longer supported for the sake of reproducible builds. |
See the POM Syntax section of the Maven book for more details.
Here’s an example illustrating the various options. In the Maven repository, com.foo:my-foo has the following metadata:
<metadata>
<groupId>com.foo</groupId>
<artifactId>my-foo</artifactId>
<version>2.0.0</version>
<versioning>
<release>1.1.1</release>
<versions>
<version>1.0</version>
<version>1.0.1</version>
<version>1.1</version>
<version>1.1.1</version>
<version>2.0.0</version>
</versions>
<lastUpdated>20090722140000</lastUpdated>
</versioning>
</metadata>
If a dependency on that artifact is required, you have the following options (other version ranges can be specified of course, just showing the relevant ones here): Declare an exact version (will always resolve to 1.0.1):
<version>[1.0.1]</version>
Declare an explicit version (will always resolve to 1.0.1 unless a collision occurs, when Maven will select a matching version):
<version>1.0.1</version>
Declare a version range for all 1.x (will currently resolve to 1.1.1):
<version>[1.0.0,2.0.0)</version>
Declare an open-ended version range (will resolve to 2.0.0):
<version>[1.0.0,)</version>
Declare the version as LATEST (will resolve to 2.0.0):
<version>LATEST</version>
Declare the version as RELEASE (will resolve to 1.1.1):
<version>RELEASE</version>
Note that by default your own deployments will update the "latest" entry in the Maven metadata, but to update the "release" entry, you need to activate the "release-profile" from the Maven super POM. You can do this with either "-Prelease-profile" or "-DperformRelease=true"
2.2.3.5. Settings.xml and Remote Repository Setup
The maven settings.xml is used to configure Maven execution. Detailed instructions can be found at the Maven website:
The settings.xml file can be located in 3 locations, the actual settings used is a merge of those 3 locations.
-
The Maven install:
$M2_HOME/conf/settings.xml
-
A user’s install:
${user.home}/.m2/settings.xml
-
Folder location specified by the system property
kie.maven.settings.custom
The settings.xml is used to specify the location of remote repositories. It is important that you activate the profile that specifies the remote repository, typically this can be done using "activeByDefault":
<profiles>
<profile>
<id>profile-1</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
...
</profile>
</profiles>
Maven provides detailed documentation on using multiple remote repositories:
2.2.4. Running
2.2.4.1. KieBase
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.
KieBase kBase = kContainer.getKieBase();
2.2.4.2. KieSession
The KieSession
stores and executes on the runtime data.
It is created from the KieBase
.
KieSession ksession = kbase.newKieSession();
2.2.4.3. KieRuntime
KieRuntime
The KieRuntime
provides 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 Drools 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 Drools 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 KIE 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<String> 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
.
2.2.4.4. Event Model
The event package provides means to be notified of Drools engine events, including rules firing, objects being asserted, etc. This allows separation of 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, RuleRuntimeEventManager
and ProcessEventManager
.
We will only cover the RuleRuntimeEventManager
here.
The RuleRuntimeEventManager
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.
ksession.addEventListener( new DefaultAgendaEventListener() {
public void afterMatchFired(AfterMatchFiredEvent event) {
super.afterMatchFired( event );
System.out.println( event );
}
});
Drools also provides DebugRuleRuntimeEventListener
and DebugAgendaEventListener
which implement each method with a debug print statement.
To print all Working Memory events, you add a listener like this:
ksession.addEventListener( new DebugRuleRuntimeEventListener() );
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
2.2.4.5. KieRuntimeLogger
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.
KieRuntimeLogger logger =
KieServices.Factory.get().getLoggers().newFileLogger(ksession, "logdir/mylogfile");
...
logger.close();
2.2.4.6. Commands and the CommandExecutor
KIE has the concept of stateful or stateless sessions. Stateful sessions have already been covered, which use the standard KieRuntime, and can be worked with iteratively over time. Stateless is a one-off execution of a KieRuntime 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 an engine like a function call with optional return results.
The foundation for this is the CommandExecutor
interface, which both the stateful and stateless interfaces extend.
This returns an ExecutionResults
:
The CommandExecutor
allows for commands to be executed on those sessions, the only difference being that the StatelessKieSession executes fireAllRules()
at the end before disposing the session.
The commands can be created using the CommandExecutor
.The Javadocs provide the full list of the allowed commands using the CommandExecutor
.
setGlobal and getGlobal are two commands relevant to both Drools and jBPM.
Set Global calls setGlobal underneath.
The optional boolean indicates whether the command should return the global’s value as part of the ExecutionResults
.
If true it uses the same name as the global name.
A String can be used instead of the boolean, if an alternative name is desired.
StatelessKieSession ksession = kbase.newStatelessKieSession();
ExecutionResults bresults =
ksession.execute( CommandFactory.newSetGlobal( "stilton", new Cheese( "stilton" ), true);
Cheese stilton = bresults.getValue( "stilton" );
Allows an existing global to be returned. The second optional String argument allows for an alternative return name.
StatelessKieSession ksession = kbase.newStatelessKieSession();
ExecutionResults bresults =
ksession.execute( CommandFactory.getGlobal( "stilton" );
Cheese stilton = bresults.getValue( "stilton" );
All the above examples execute single commands.
The BatchExecution
represents a composite command, created from a list of commands.
It 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.
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.
Any command, in the batch, that has an out identifier set will add its results to the returned ExecutionResults
instance.
Let’s look at a simple example to see how this works.
The example presented includes command from the Drools and jBPM, for the sake of illustration.
They are covered in more detail in the Drool and jBPM specific sections.
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.
All commands support XML and JSON marshalling using XStream, as well as JAXB marshalling. This is covered in Drools commands.
2.2.4.7. StatelessKieSession
The StatelessKieSession
wraps the KieSession
, instead of extending it.
Its main focus is on the 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.
StatelessKieSession ksession = kbase.newStatelessKieSession();
ksession.execute( collection );
If this was done as a single Command it would be as follows:
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.
We 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 used for all execution calls. Exercise caution regarding mutable globals because execution calls can be executing simultaneously in different threads.Example 26. Session scoped globalStatelessKieSession 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 theCommandExecutor
.
The CommandExecutor
interface also offers the ability to export data via "out" parameters.
Inserted facts, globals and query results can all be returned.
// 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" );
2.2.4.8. Marshalling
The KieMarshallers
are used to marshal and unmarshal KieSessions.
An instance of the KieMarshallers
can be retrieved from the KieServices
.
A simple example is shown below:
// 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 will need more flexibility when dealing with referenced user data.
To achieve this use 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 shown in the example above, and it just calls the Serializable
or Externalizable
methods on a user instance. IdentityMarshallingStrategy
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 the code to use an Identity Marshalling Strategy.
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();
In most cases, a single strategy is insufficient.
For added flexibility, the ObjectMarshallingStrategyAcceptor
interface can be used.
This Marshaller has a chain of strategies, and while reading or writing a user object it iterates the strategies asking if they accept responsibility 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:
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.
2.2.4.9. Persistence and Transactions
Longterm out of the box persistence with Java Persistence API (JPA) is possible with Drools. It is necessary to have some implementation of the Java Transaction API (JTA) installed. For development purposes the Bitronix Transaction Manager is suggested, as it’s simple to set up and works embedded, but for production use JBoss Transactions is recommended.
KieServices kieServices = KieServices.Factory.get();
Environment env = kieServices.newEnvironment();
env.set( EnvironmentName.ENTITY_MANAGER_FACTORY,
Persistence.createEntityManagerFactory( "emf-name" ) );
env.set( EnvironmentName.TRANSACTION_MANAGER,
TransactionManagerServices.getTransactionManager() );
// KieSessionConfiguration may be null, and a default will be used
KieSession ksession =
kieServices.getStoreServices().newKieSession( 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, hence it is possible to continue to use it after a rollback.
To load a previously persisted KieSession you’ll need the id, as shown below:
KieSession ksession =
kieServices.getStoreServices().loadKieSession( sessionId, kbase, null, env );
To enable persistence several classes must be added to your persistence.xml, as in the example below:
<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 consulted for details. For a quick start, here is the programmatic approach:
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 folder and add the following line to it:
java.naming.factory.initial=bitronix.tm.jndi.BitronixInitialContextFactory
2.2.5. Installation and Deployment Cheat Sheets
2.2.6. Build, Deploy and Utilize Examples
The best way to learn the new build system is by example. The source project "drools-examples-api" contains a number of examples, and can be found at GitHub:
Each example is described below, the order starts with the simplest (most of the options are defaulted) and working its way up to more complex use cases.
The Deploy use cases shown below all involve mvn install
.
Remote deployment of JARs in Maven is well covered in Maven literature.
Utilize refers to the initial act of loading the resources and providing access to the KIE runtimes.
Whereas Run refers to the act of interacting with those runtimes.
2.2.6.1. Default KieSession
-
Project: default-kesession.
-
Summary: Empty kmodule.xml KieModule on the classpath that includes all resources in a single default KieBase. The example shows the retrieval of the default KieSession from the classpath.
An empty kmodule.xml will produce a single KieBase that includes all files found under resources path, be it DRL, BPMN2, XLS etc. That single KieBase is the default and also includes a single default KieSession. Default means they can be created without knowing their names.
<kmodule xmlns="http://www.drools.org/xsd/kmodule"> </kmodule>
mvn install
ks.getKieClasspathContainer() returns the KieContainer that contains the KieBases deployed onto the environment classpath. kContainer.newKieSession() creates the default KieSession. Notice that you no longer need to look up the KieBase, in order to create the KieSession. The KieSession knows which KieBase it’s associated with, and use that, which in this case is the default KieBase.
KieServices ks = KieServices.Factory.get();
KieContainer kContainer = ks.getKieClasspathContainer();
KieSession kSession = kContainer.newKieSession();
kSession.setGlobal("out", out);
kSession.insert(new Message("Dave", "Hello, HAL. Do you read me, HAL?"));
kSession.fireAllRules();
2.2.6.2. Named KieSession
-
Project: named-kiesession.
-
Summary: kmodule.xml that has one named KieBase and one named KieSession. The examples shows the retrieval of the named KieSession from the classpath.
kmodule.xml will produce a single named KieBase, 'kbase1' that includes all files found under resources path, be it DRL, BPMN2, XLS etc. KieSession 'ksession1' is associated with that KieBase and can be created by name.
<kmodule xmlns="http://www.drools.org/xsd/kmodule">
<kbase name="kbase1">
<ksession name="ksession1"/>
</kbase>
</kmodule>
mvn install
ks.getKieClasspathContainer() returns the KieContainer that contains the KieBases deployed onto the environment classpath. This time the KieSession uses the name 'ksession1'. You do not need to lookup the KieBase first, as it knows which KieBase 'ksession1' is assocaited with.
KieServices ks = KieServices.Factory.get();
KieContainer kContainer = ks.getKieClasspathContainer();
KieSession kSession = kContainer.newKieSession("ksession1");
kSession.setGlobal("out", out);
kSession.insert(new Message("Dave", "Hello, HAL. Do you read me, HAL?"));
kSession.fireAllRules();
2.2.6.3. KieBase Inheritance
-
Project: kiebase-inclusion.
-
Summary: 'kmodule.xml' demonstrates that one KieBase can include the resources from another KieBase, from another KieModule. In this case it inherits the named KieBase from the 'name-kiesession' example. The included KieBase can be from the current KieModule or any other KieModule that is in the pom.xml dependency list.
kmodule.xml will produce a single named KieBase, 'kbase2' that includes all files found under resources path, be it DRL, BPMN2, XLS etc. Further it will include all the resources found from the KieBase 'kbase1', due to the use of the 'includes' attribute. KieSession 'ksession2' is associated with that KieBase and can be created by name.
<kbase name="kbase2" includes="kbase1">
<ksession name="ksession2"/>
</kbase>
This example requires that the previous example, 'named-kiesession', is built and installed to the local Maven repository first. Once installed it can be included as a dependency, using the standard Maven <dependencies> element.
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.drools</groupId>
<artifactId>drools-examples-api</artifactId>
<version>6.0.0/version>
</parent>
<artifactId>kiebase-inclusion</artifactId>
<name>Drools API examples - KieBase Inclusion</name>
<dependencies>
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-compiler</artifactId>
</dependency>
<dependency>
<groupId>org.drools</groupId>
<artifactId>named-kiesession</artifactId>
<version>6.0.0</version>
</dependency>
</dependencies>
</project>
Once 'named-kiesession' is built and installed this example can be built and installed as normal. Again the act of installing, will force the unit tests to run, demonstrating the use case.
mvn install
ks.getKieClasspathContainer() returns the KieContainer that contains the KieBases deployed onto the environment classpath. This time the KieSession uses the name 'ksession2'. You do not need to lookup the KieBase first, as it knows which KieBase 'ksession1' is assocaited with. Notice two rules fire this time, showing that KieBase 'kbase2' has included the resources from the dependency KieBase 'kbase1'.
KieServices ks = KieServices.Factory.get();
KieContainer kContainer = ks.getKieClasspathContainer();
KieSession kSession = kContainer.newKieSession("ksession2");
kSession.setGlobal("out", out);
kSession.insert(new Message("Dave", "Hello, HAL. Do you read me, HAL?"));
kSession.fireAllRules();
kSession.insert(new Message("Dave", "Open the pod bay doors, HAL."));
kSession.fireAllRules();
2.2.6.4. Multiple KieBases
-
Project: 'multiple-kbases.
-
Summary: Demonstrates that the 'kmodule.xml' can contain any number of KieBase or KieSession declarations. Introduces the 'packages' attribute to select the folders for the resources to be included in the KieBase.
kmodule.xml produces 6 different named KieBases. 'kbase1' includes all resources from the KieModule. The other KieBases include resources from other selected folders, via the 'packages' attribute. Note the use of wildcard '*', to select this package and all packages below it.
<kmodule xmlns="http://www.drools.org/xsd/kmodule">
<kbase name="kbase1">
<ksession name="ksession1"/>
</kbase>
<kbase name="kbase2" packages="org.some.pkg">
<ksession name="ksession2"/>
</kbase>
<kbase name="kbase3" includes="kbase2" packages="org.some.pkg2">
<ksession name="ksession3"/>
</kbase>
<kbase name="kbase4" packages="org.some.pkg, org.other.pkg">
<ksession name="ksession4"/>
</kbase>
<kbase name="kbase5" packages="org.*">
<ksession name="ksession5"/>
</kbase>
<kbase name="kbase6" packages="org.some.*">
<ksession name="ksession6"/>
</kbase>
</kmodule>
mvn install
Only part of the example is included below, as there is a test method per KieSession, but each one is a repetition of the other, with different list expectations.
@Test
public void testSimpleKieBase() {
List<Integer> list = useKieSession("ksession1");
// no packages imported means import everything
assertEquals(4, list.size());
assertTrue( list.containsAll( asList(0, 1, 2, 3) ) );
}
//.. other tests for ksession2 to ksession6 here
private List<Integer> useKieSession(String name) {
KieServices ks = KieServices.Factory.get();
KieContainer kContainer = ks.getKieClasspathContainer();
KieSession kSession = kContainer.newKieSession(name);
List<Integer> list = new ArrayList<Integer>();
kSession.setGlobal("list", list);
kSession.insert(1);
kSession.fireAllRules();
return list;
}
2.2.6.5. KieContainer from KieRepository
-
Project: kcontainer-from-repository
-
Summary: The project does not contain a kmodule.xml, nor does the pom.xml have any dependencies for other KieModules. Instead the Java code demonstrates the loading of a dynamic KieModule from a Maven repository.
The pom.xml must include kie-ci as a dependency, to ensure Maven is available at runtime. As this uses Maven under the hood you can also use the standard Maven settings.xml file.
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.drools</groupId>
<artifactId>drools-examples-api</artifactId>
<version>6.0.0</version>
</parent>
<artifactId>kiecontainer-from-kierepo</artifactId>
<name>Drools API examples - KieContainer from KieRepo</name>
<dependencies>
<dependency>
<groupId>org.kie</groupId>
<artifactId>kie-ci</artifactId>
</dependency>
</dependencies>
</project>
mvn install
In the previous examples the classpath KieContainer used. This example creates a dynamic KieContainer as specified by the ReleaseId. The ReleaseId uses Maven conventions for group id, artifact id and version. It also obeys LATEST and SNAPSHOT for versions.
KieServices ks = KieServices.Factory.get();
// Install example1 in the local Maven repo before to do this
KieContainer kContainer = ks.newKieContainer(ks.newReleaseId("org.drools", "named-kiesession", "6.0.0-SNAPSHOT"));
KieSession kSession = kContainer.newKieSession("ksession1");
kSession.setGlobal("out", out);
Object msg1 = createMessage(kContainer, "Dave", "Hello, HAL. Do you read me, HAL?");
kSession.insert(msg1);
kSession.fireAllRules();
2.2.6.6. Default KieSession from File
-
Project: default-kiesession-from-file
-
Summary: Dynamic KieModules can also be loaded from any Resource location. The loaded KieModule provides default KieBase and KieSession definitions.
No kmodue.xml file exists. The project 'default-kiesession' must be built first, so that the resulting JAR, in the target folder, can be referenced as a File.
mvn install
Any KieModule can be loaded from a Resource location and added to the KieRepository. Once deployed in the KieRepository it can be resolved via its ReleaseId. Note neither Maven or kie-ci are needed here. It will not set up a transitive dependency parent classloader.
KieServices ks = KieServices.Factory.get();
KieRepository kr = ks.getRepository();
KieModule kModule = kr.addKieModule(ks.getResources().newFileSystemResource(getFile("default-kiesession")));
KieContainer kContainer = ks.newKieContainer(kModule.getReleaseId());
KieSession kSession = kContainer.newKieSession();
kSession.setGlobal("out", out);
Object msg1 = createMessage(kContainer, "Dave", "Hello, HAL. Do you read me, HAL?");
kSession.insert(msg1);
kSession.fireAllRules();
2.2.6.7. Named KieSession from File
-
Project: named-kiesession-from-file
-
Summary: Dynamic KieModules can also be loaded from any Resource location. The loaded KieModule provides named KieBase and KieSession definitions.
No kmodue.xml file exists. The project 'named-kiesession' must be built first, so that the resulting JAR, in the target folder, can be referenced as a File.
mvn install
Any KieModule can be loaded from a Resource location and added to the KieRepository. Once in the KieRepository it can be resolved via its ReleaseId. Note neither Maven or kie-ci are needed here. It will not setup a transitive dependency parent classloader.
KieServices ks = KieServices.Factory.get();
KieRepository kr = ks.getRepository();
KieModule kModule = kr.addKieModule(ks.getResources().newFileSystemResource(getFile("named-kiesession")));
KieContainer kContainer = ks.newKieContainer(kModule.getReleaseId());
KieSession kSession = kContainer.newKieSession("ksession1");
kSession.setGlobal("out", out);
Object msg1 = createMessage(kContainer, "Dave", "Hello, HAL. Do you read me, HAL?");
kSession.insert(msg1);
kSession.fireAllRules();
2.2.6.8. KieModule with Dependent KieModule
-
Project: kie-module-form-multiple-files
-
Summary: Programmatically provide the list of dependant KieModules, without using Maven to resolve anything.
No kmodue.xml file exists. The projects 'named-kiesession' and 'kiebase-include' must be built first, so that the resulting JARs, in the target folders, can be referenced as Files.
mvn install
Creates two resources. One is for the main KieModule 'exRes1' the other is for the dependency 'exRes2'. Even though kie-ci is not present and thus Maven is not available to resolve the dependencies, this shows how you can manually specify the dependent KieModules, for the vararg.
KieServices ks = KieServices.Factory.get();
KieRepository kr = ks.getRepository();
Resource ex1Res = ks.getResources().newFileSystemResource(getFile("kiebase-inclusion"));
Resource ex2Res = ks.getResources().newFileSystemResource(getFile("named-kiesession"));
KieModule kModule = kr.addKieModule(ex1Res, ex2Res);
KieContainer kContainer = ks.newKieContainer(kModule.getReleaseId());
KieSession kSession = kContainer.newKieSession("ksession2");
kSession.setGlobal("out", out);
Object msg1 = createMessage(kContainer, "Dave", "Hello, HAL. Do you read me, HAL?");
kSession.insert(msg1);
kSession.fireAllRules();
Object msg2 = createMessage(kContainer, "Dave", "Open the pod bay doors, HAL.");
kSession.insert(msg2);
kSession.fireAllRules();
2.2.6.9. Programmatically build a Simple KieModule with Defaults
-
Project: kiemoduelmodel-example
-
Summary: Programmatically build a KieModule from just a single file. The POM and models are all defaulted. This is the quickest out of the box approach, but should not be added to a Maven repository.
mvn install
This programmatically builds a KieModule. It populates the model that represents the ReleaseId and kmodule.xml, and it adds the relevant resources. A pom.xml is generated from the ReleaseId.
KieServices ks = KieServices.Factory.get();
KieRepository kr = ks.getRepository();
KieFileSystem kfs = ks.newKieFileSystem();
kfs.write("src/main/resources/org/kie/example5/HAL5.drl", getRule());
KieBuilder kb = ks.newKieBuilder(kfs);
kb.buildAll(); // kieModule is automatically deployed to KieRepository if successfully built.
if (kb.getResults().hasMessages(Level.ERROR)) {
throw new RuntimeException("Build Errors:\n" + kb.getResults().toString());
}
KieContainer kContainer = ks.newKieContainer(kr.getDefaultReleaseId());
KieSession kSession = kContainer.newKieSession();
kSession.setGlobal("out", out);
kSession.insert(new Message("Dave", "Hello, HAL. Do you read me, HAL?"));
kSession.fireAllRules();
2.2.6.10. Programmatically build a KieModule using Meta Models
-
Project: kiemoduelmodel-example
-
Summary: Programmatically build a KieModule, by creating its kmodule.xml meta model resources.
mvn install
This programmatically builds a KieModule. It populates the model that represents the ReleaseId and kmodule.xml, as well as add the relevant resources. A pom.xml is generated from the ReleaseId.
KieServices ks = KieServices.Factory.get();
KieFileSystem kfs = ks.newKieFileSystem();
Resource ex1Res = ks.getResources().newFileSystemResource(getFile("named-kiesession"));
Resource ex2Res = ks.getResources().newFileSystemResource(getFile("kiebase-inclusion"));
ReleaseId rid = ks.newReleaseId("org.drools", "kiemodulemodel-example", "6.0.0-SNAPSHOT");
kfs.generateAndWritePomXML(rid);
KieModuleModel kModuleModel = ks.newKieModuleModel();
kModuleModel.newKieBaseModel("kiemodulemodel")
.addInclude("kiebase1")
.addInclude("kiebase2")
.newKieSessionModel("ksession6");
kfs.writeKModuleXML(kModuleModel.toXML());
kfs.write("src/main/resources/kiemodulemodel/HAL6.drl", getRule());
KieBuilder kb = ks.newKieBuilder(kfs);
kb.setDependencies(ex1Res, ex2Res);
kb.buildAll(); // kieModule is automatically deployed to KieRepository if successfully built.
if (kb.getResults().hasMessages(Level.ERROR)) {
throw new RuntimeException("Build Errors:\n" + kb.getResults().toString());
}
KieContainer kContainer = ks.newKieContainer(rid);
KieSession kSession = kContainer.newKieSession("ksession6");
kSession.setGlobal("out", out);
Object msg1 = createMessage(kContainer, "Dave", "Hello, HAL. Do you read me, HAL?");
kSession.insert(msg1);
kSession.fireAllRules();
Object msg2 = createMessage(kContainer, "Dave", "Open the pod bay doors, HAL.");
kSession.insert(msg2);
kSession.fireAllRules();
Object msg3 = createMessage(kContainer, "Dave", "What's the problem?");
kSession.insert(msg3);
kSession.fireAllRules();
2.2.7. Executable rule models
Rule assets in Drools are built from executable rule models by default with the standard kie-maven-plugin
plugin. Executable rule models are embedded models that provide a Java-based representation of a rule set for execution at build time. The executable model is a more efficient alternative to the standard asset packaging in previous versions of Drools and enables KIE containers and KIE bases to be created more quickly, especially when you have large lists of DRL (Drools Rule Language) files and other Drools assets.
If you do not use the kie-maven-plugin
plugin or if the required drools-model-compiler
dependency is missing from your project, then rule assets are built without executable models.
Executable rule models provide the following specific advantages for your projects:
-
Compile time: Traditionally, a packaged Drools project (KJAR) contains a list of DRL files and other Drools artifacts that define the rule base together with some pre-generated classes implementing the constraints and the consequences. Those DRL files must be parsed and compiled when the KJAR is downloaded from the Maven repository and installed in a KIE container. This process can be slow, especially for large rule sets. With an executable model, you can package within the project KJAR the Java classes that implement the executable model of the project rule base and re-create the KIE container and its KIE bases out of it in a much faster way. In Maven projects, you use the
kie-maven-plugin
plugin to automatically generate the executable model sources from the DRL files during the compilation process. -
Run time: In an executable model, all constraints are defined as Java lambda expressions. The same lambda expressions are also used for constraints evaluation, so you no longer need to use
mvel
expressions for interpreted evaluation nor the just-in-time (JIT) process to transform themvel
-based constraints into bytecode. This creates a quicker and more efficient run time. -
Development time: An executable model enables you to develop and experiment with new features of the Drools engine without needing to encode elements directly in the DRL format or modify the DRL parser to support them.
For query definitions in executable rule models, you can use up to 10 arguments only. For variables within rule consequences in executable rule models, you can use up to 24 bound variables only (including the built-in
|
2.2.7.1. Modifying or disabling executable rule models in a Drools project
Rule assets in Drools are built from executable rule models by default with the standard kie-maven-plugin
plugin. The executable model is a more efficient alternative to the standard asset packaging in previous versions of Drools. However, if needed, you can modify or disable executable rule models to build a Drools project as a DRL-based KJAR instead of the default model-based KJAR.
Build your Drools project in the usual way, but provide an alternate build option, depending on the type of project:
-
For a Maven project, navigate to your Maven project directory in a command terminal and run the following command:
mvn clean install -DgenerateModel=<VALUE>
Replace
<VALUE>
with one of three values:-
YES_WITHDRL
: (Default) Generates the executable model corresponding to the DRL files in the original project and also adds the DRL files to the generated KJAR for documentation purposes (the KIE base is built from the executable model regardless). -
YES
: Generates the executable model corresponding to the DRL files in the original project and excludes the DRL files from the generated KJAR. -
NO
: Does not generate the executable model.
Example build command to disable the default executable model behavior:
mvn clean install -DgenerateModel=NO
-
-
For a Java application configured programmatically, the executable model is disabled by default. Add rule assets to the KIE virtual file system
KieFileSystem
and useKieBuilder
with one of the followingbuildAll()
methods:-
buildAll()
(Default) orbuildAll(DrlProject.class)
: Does not generate the executable model. -
buildAll(ExecutableModelProject.class)
: Generates the executable model corresponding to the DRL files in the original project.
Example code to enable executable model behavior:
import org.kie.api.KieServices; import org.kie.api.builder.KieFileSystem; import org.kie.api.builder.KieBuilder; KieServices ks = KieServices.Factory.get(); KieFileSystem kfs = ks.newKieFileSystem() kfs.write("src/main/resources/KBase1/ruleSet1.drl", stringContainingAValidDRL) .write("src/main/resources/dtable.xls", kieServices.getResources().newInputStreamResource(dtableFileStream)); KieBuilder kieBuilder = ks.newKieBuilder( kfs ); // Enable executable model kieBuilder.buildAll(ExecutableModelProject.class) assertEquals(0, kieBuilder.getResults().getMessages(Message.Level.ERROR).size());
-
2.2.7.2. Enabling executable rule models when upgrading to Drools 7.33
Beginning in Drools 7.33, rule assets are built from executable rule models by default with the standard kie-maven-plugin
plugin. The executable model is a more efficient alternative to the standard asset packaging in previous versions of Drools.
When you install Drools 7.33, this default executable model behavior is configured for all new projects that you create going forward. However, if you are upgrading to Drools 7.33 from a previous version of the product and you have not already enabled executable rule models, you must add the required dependency to your existing Drools projects so that your rule assets are built from executable models in Drools 7.33. If you do not use the kie-maven-plugin
plugin or if the required drools-model-compiler
dependency is missing from your project, then rule assets are built without executable models.
For more information about executable rule models, see Executable rule models.
In the pom.xml
file of your Maven project or on the relevant class path of your Java project, add the following dependency to enable rule assets to be built from the default executable model:
<dependency>
<groupId>org.drools</groupId>
<artifactId>drools-model-compiler</artifactId>
<version>${drools.version}</version>
</dependency>
This dependency compiles the executable model into Drools internal data structures so that it can be executed by the Drools engine.
The <version>
is the Maven artifact version for Drools currently used in your project (for example, 7.33.0.Final-redhat-00002).
2.2.8. Using a KIE scanner to monitor and update KIE containers
The KIE scanner in Drools monitors your Maven repository for new SNAPSHOT
versions of your Drools project and then deploys the latest version of the project to a specified KIE container. You can use a KIE scanner in a development environment to maintain your Drools project deployments more efficiently as new versions become available.
For production environments, do not use a KIE scanner with |
-
The
kie-ci.jar
file is available on the class path of your Drools project.
-
In the relevant
.java
class in your project, register and start the KIE scanner as shown in the following example code:Registering and starting a KIE scanner for a KIE containerimport org.kie.api.KieServices; import org.kie.api.builder.ReleaseId; import org.kie.api.runtime.KieContainer; import org.kie.api.builder.KieScanner; ... KieServices kieServices = KieServices.Factory.get(); ReleaseId releaseId = kieServices .newReleaseId("com.sample", "my-app", "1.0-SNAPSHOT"); KieContainer kContainer = kieServices.newKieContainer(releaseId); KieScanner kScanner = kieServices.newKieScanner(kContainer); // Start KIE scanner for polling the Maven repository every 10 seconds (10000 ms) kScanner.start(10000L);
In this example, the KIE scanner is configured to run with a fixed time interval. The minimum KIE scanner polling interval is 1 millisecond (ms) and the maximum polling interval is the maximum value of the data type
long
. A polling interval of 0 or less results in ajava.lang.IllegalArgumentException: pollingInterval must be positive
error. You can also configure the KIE scanner to run on demand by invoking thescanNow()
method.The project group ID, artifact ID, and version (GAV) settings in the example are defined as
com.sample:my-app:1.0-SNAPSHOT
. The project version must contain the-SNAPSHOT
suffix to enable the KIE scanner to retrieve the latest build of the specified artifact version. If you change the snapshot project version number, such as increasing to1.0.1-SNAPSHOT
, then you must also update the version in the GAV definition in your KIE scanner configuration. The KIE scanner does not retrieve updates for projects with static versions, such ascom.sample:my-app:1.0
. -
In the
settings.xml
file of your Maven repository, set theupdatePolicy
configuration toalways
to enable the KIE scanner to function properly:<profile> <id>guvnor-m2-repo</id> <repositories> <repository> <id>guvnor-m2-repo</id> <name>BA Repository</name> <url>http://localhost:8080/business-central/maven2/</url> <layout>default</layout> <releases> <enabled>true</enabled> <updatePolicy>always</updatePolicy> </releases> <snapshots> <enabled>true</enabled> <updatePolicy>always</updatePolicy> </snapshots> </repository> </repositories> </profile>
After the KIE scanner starts polling, if the KIE scanner detects an updated version of the
SNAPSHOT
project in the specified KIE container, the KIE scanner automatically downloads the new project version and triggers an incremental build of the new project. From that moment, all of the newKieBase
andKieSession
objects that were created from the KIE container use the new project version.For information about starting or stopping a KIE scanner using KIE Server APIs, see KIE Server and KIE container commands in Drools.
2.3. Security
2.3.1. Security Manager
The KIE engine is a platform for the modelling and execution of business behavior, using a multitude of declarative abstractions and metaphors, like rules, processes, decision tables and etc.
Many times, the authoring of these metaphors is done by third party groups, be it a different group inside the same company, a group from a partner company, or even anonymous third parties on the internet.
Rules and Processes are designed to execute arbitrary code in order to do their job, but in such cases it might be necessary to constrain what they can do.
For instance, it is unlikely a rule should be allowed to create a classloader (what could open the system to an attack) and certainly it should not be allowed to make a call to System.exit()
.
The Java Platform provides a very comprehensive and well defined security framework that allows users to define policies for what a system can do. The KIE platform leverages that framework and allow application developers to define a specific policy to be applied to any execution of user provided code, be it in rules, processes, work item handlers and etc.
2.3.1.1. How to define a KIE Policy
Rules and processes can run with very restrict permissions, but the engine itself needs to perform many complex operations in order to work. Examples are: it needs to create classloaders, read system properties, access the file system, etc.
Once a security manager is installed, though, it will apply restrictions to all the code executing in the JVM according to the defined policy. For that reason, KIE allows the user to define two different policy files: one for the engine itself and one for the assets deployed into and executed by the engine.
One easy way to setup the environment is to give the engine itself a very permissive policy, while providing a constrained policy for rules and processes.
Policy files follow the standard policy file syntax as described in the Java documentation. For more details, see:
A permissive policy file for the engine can look like the following:
grant {
permission java.security.AllPermission;
}
An example security policy for rules could be:
grant {
permission java.util.PropertyPermission "*", "read";
permission java.lang.RuntimePermission "accessDeclaredMembers";
}
Please note that depending on what the rules and processes are supposed to do, many more permissions might need to be granted, like accessing files in the filesystem, databases, etc.
In order to use these policy files, all that is necessary is to execute the application with these files as parameters to the JVM. Three parameters are required:
Parameter | Meaning |
---|---|
-Djava.security.manager |
Enables the security manager |
-Djava.security.policy=<jvm_policy_file> |
Defines the global policy file to be applied to the whole application, including the engine |
-Dkie.security.policy=<kie_policy_file> |
Defines the policy file to be applied to rules and processes |
For instance:
java -Djava.security.manager -Djava.security.policy=global.policy -Dkie.security.policy=rules.policy
foo.bar.MyApp
When executing the engine inside a container, use your container’s documentation to find out how to configure the Security Manager and how to define the global security policy.
Define the kie security policy as described above and set the |
Please note that unless a Security Manager is configured, the |
A Security Manager has a high performance impact in the JVM. Applications with strict performance requirements are strongly discouraged of using a Security Manager. An alternative is the use of other security procedures like the auditing of rules/processes before testing and deployment to prevent malicious code from being deployed to the environment. |
Drools Run time and Language
Drools is a powerful hybrid reasoning system that intelligently and efficiently processes rule data.
3. Drools engine
The Drools engine is the rules engine in Drools. The Drools engine stores, processes, and evaluates data to execute the business rules or decision models that you define. The basic function of the Drools engine is to match incoming data, or facts, to the conditions of rules and determine whether and how to execute the rules.
The Drools engine operates using the following basic components:
-
Rules: Business rules or DMN decisions that you define. All rules must contain at a minimum the conditions that trigger the rule and the actions that the rule dictates.
-
Facts: Data that enters or changes in the Drools engine that the Drools engine matches to rule conditions to execute applicable rules.
-
Production memory: Location where rules are stored in the Drools engine.
-
Working memory: Location where facts are stored in the Drools engine.
-
Agenda: Location where activated rules are registered and sorted (if applicable) in preparation for execution.
When a business user or an automated system adds or updates rule-related information in Drools, that information is inserted into the working memory of the Drools engine in the form of one or more facts. The Drools engine matches those facts to the conditions of the rules that are stored in the production memory to determine eligible rule executions. (This process of matching facts to rules is often referred to as pattern matching.) When rule conditions are met, the Drools engine activates and registers rules in the agenda, where the Drools engine then sorts prioritized or conflicting rules in preparation for execution.
The following diagram illustrates these basic components of the Drools engine:
These core concepts can help you to better understand other more advanced components, processes, and sub-processes of the Drools engine, and as a result, to design more effective business assets in Drools.
3.1. KIE sessions
In Drools, a KIE session stores and executes runtime data. The KIE session is created from a KIE base or directly from a KIE container if you have defined the KIE session in the KIE module descriptor file (kmodule.xml
) for your project.
kmodule.xml
file<kmodule>
...
<kbase>
...
<ksession name="KSession2_1" type="stateless" default="true" clockType="realtime">
...
</kbase>
...
</kmodule>
A KIE base is a repository that you define in the KIE module descriptor file (kmodule.xml
) for your project and contains all
in Drools, but does not contain any runtime data.
kmodule.xml
file<kmodule>
...
<kbase name="KBase2" default="false" eventProcessingMode="stream" equalsBehavior="equality" declarativeAgenda="enabled" packages="org.domain.pkg2, org.domain.pkg3" includes="KBase1">
...
</kbase>
...
</kmodule>
A KIE session can be stateless or stateful. In a stateless KIE session, data from a previous invocation of the KIE session (the previous session state) is discarded between session invocations. In a stateful KIE session, that data is retained. The type of KIE session you use depends on your project requirements and how you want data from different asset invocations to be persisted.
3.1.1. Stateless KIE sessions
A stateless KIE session is a session that does not use inference to make iterative changes to facts over time. In a stateless KIE session, data from a previous invocation of the KIE session (the previous session state) is discarded between session invocations, whereas in a stateful KIE session, that data is retained. A stateless KIE session behaves similarly to a function in that the results that it produces are determined by the contents of the KIE base and by the data that is passed into the KIE session for execution at a specific point in time. The KIE session has no memory of any data that was passed into the KIE session previously.
Stateless KIE sessions are commonly used for the following use cases:
-
Validation, such as validating that a person is eligible for a mortgage
-
Calculation, such as computing a mortgage premium
-
Routing and filtering, such as sorting incoming emails into folders or sending incoming emails to a destination
For example, consider the following driver’s license data model and sample DRL rule:
public class Applicant {
private String name;
private int age;
private boolean valid;
// Getter and setter methods
}
package com.company.license
rule "Is of valid age"
when
$a : Applicant(age < 18)
then
$a.setValid(false);
end
The Is of valid age
rule disqualifies any applicant younger than 18 years old. When the Applicant
object is inserted into the Drools engine, the Drools engine evaluates the constraints for each rule and searches for a match. The "objectType"
constraint is always implied, after which any number of explicit field constraints are evaluated. The variable $a
is a binding variable that references the matched object in the rule consequence.
The dollar sign ( |
In this example, the sample rule and all other files in the ~/resources
folder of the Drools project are built with the following code:
KieServices kieServices = KieServices.Factory.get();
KieContainer kContainer = kieServices.getKieClasspathContainer();
This code compiles all the rule files found on the class path and adds the result of this compilation, a KieModule
object, in the KieContainer
.
Finally, the StatelessKieSession
object is instantiated from the KieContainer
and is executed against specified data:
StatelessKieSession kSession = kContainer.newStatelessKieSession();
Applicant applicant = new Applicant("Mr John Smith", 16);
assertTrue(applicant.isValid());
ksession.execute(applicant);
assertFalse(applicant.isValid());
In a stateless KIE session configuration, the execute()
call acts as a combination method that instantiates the KieSession
object, adds all the user data and executes user commands, calls fireAllRules()
, and then calls dispose()
. Therefore, with a stateless KIE session, you do not need to call fireAllRules()
or call dispose()
after session invocation as you do with a stateful KIE session.
In this case, the specified applicant is under the age of 18, so the application is declined.
For a more complex use case, see the following example. This example uses a stateless KIE session and executes rules against an iterable list of objects, such as a collection.
public class Applicant {
private String name;
private int age;
// Getter and setter methods
}
public class Application {
private Date dateApplied;
private boolean valid;
// Getter and setter methods
}
package com.company.license
rule "Is of valid age"
when
Applicant(age < 18)
$a : Application()
then
$a.setValid(false);
end
rule "Application was made this year"
when
$a : Application(dateApplied > "01-jan-2009")
then
$a.setValid(false);
end
StatelessKieSession ksession = kbase.newStatelessKnowledgeSession();
Applicant applicant = new Applicant("Mr John Smith", 16);
Application application = new Application();
assertTrue(application.isValid());
ksession.execute(Arrays.asList(new Object[] { application, applicant })); (1)
assertFalse(application.isValid());
ksession.execute
(CommandFactory.newInsertIterable(new Object[] { application, applicant })); (2)
List<Command> cmds = new ArrayList<Command>(); (3)
cmds.add(CommandFactory.newInsert(new Person("Mr John Smith"), "mrSmith"));
cmds.add(CommandFactory.newInsert(new Person("Mr John Doe"), "mrDoe"));
BatchExecutionResults results = ksession.execute(CommandFactory.newBatchExecution(cmds));
assertEquals(new Person("Mr John Smith"), results.getValue("mrSmith"));
1 | Method for executing rules against an iterable collection of objects produced by the Arrays.asList() method. Every collection element is inserted before any matched rules are executed. The execute(Object object) and execute(Iterable objects) methods are wrappers around the execute(Command command) method that comes from the BatchExecutor interface. |
2 | Execution of the iterable collection of objects using the CommandFactory interface. |
3 | BatchExecutor and CommandFactory configurations for working with many different commands or result output identifiers. The CommandFactory interface supports other commands that you can use in the BatchExecutor , such as StartProcess , Query , and SetGlobal . |
3.1.1.1. Global variables in stateless KIE sessions
The StatelessKieSession
object supports global variables (globals) that you can configure to be resolved as session-scoped globals, delegate globals, or execution-scoped globals.
-
Session-scoped globals: For session-scoped globals, you can use the method
getGlobals()
to return aGlobals
instance that provides access to the KIE session globals. These globals are used for all execution calls. Use caution with mutable globals because execution calls can be executing simultaneously in different threads.Session-scoped globalimport org.kie.api.runtime.StatelessKieSession; StatelessKieSession ksession = kbase.newStatelessKieSession(); // Set a global `myGlobal` that can be used in the rules. ksession.setGlobal("myGlobal", "I am a global"); // Execute while resolving the `myGlobal` identifier. ksession.execute(collection);
-
Delegate globals: For delegate globals, you can assign a value to a global (with
setGlobal(String, Object)
) so that the value is stored in an internal collection that maps identifiers to values. Identifiers in this internal collection have priority over any supplied delegate. If an identifier cannot be found in this internal collection, the delegate global (if any) is used. -
Execution-scoped globals: For execution-scoped globals, you can use the
Command
object to set a global that is passed to theCommandExecutor
interface for execution-specific global resolution.
The CommandExecutor
interface also enables you to export data using out identifiers for globals, inserted facts, and query results:
import org.kie.api.runtime.ExecutionResults;
// 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");
3.1.2. Stateful KIE sessions
A stateful KIE session is a session that uses inference to make iterative changes to facts over time. In a stateful KIE session, data from a previous invocation of the KIE session (the previous session state) is retained between session invocations, whereas in a stateless KIE session, that data is discarded.
Ensure that you call the dispose() method after running a stateful KIE session so that no memory leaks occur between session invocations.
|
Stateful KIE sessions are commonly used for the following use cases:
-
Monitoring, such as monitoring a stock market and automating the buying process
-
Diagnostics, such as running fault-finding processes or medical diagnostic processes
-
Logistics, such as parcel tracking and delivery provisioning
-
Ensuring compliance, such as verifying the legality of market trades
For example, consider the following fire alarm data model and sample DRL rules:
public class Room {
private String name;
// Getter and setter methods
}
public class Sprinkler {
private Room room;
private boolean on;
// Getter and setter methods
}
public class Fire {
private Room room;
// Getter and setter methods
}
public class Alarm { }
rule "When there is a fire turn on the sprinkler"
when
Fire($room : room)
$sprinkler : Sprinkler(room == $room, on == false)
then
modify($sprinkler) { setOn(true) };
System.out.println("Turn on the sprinkler for room "+$room.getName());
end
rule "Raise the alarm when we have one or more fires"
when
exists Fire()
then
insert( new Alarm() );
System.out.println( "Raise the alarm" );
end
rule "Cancel the alarm when all the fires have gone"
when
not Fire()
$alarm : Alarm()
then
delete( $alarm );
System.out.println( "Cancel the alarm" );
end
rule "Status output when things are ok"
when
not Alarm()
not Sprinkler( on == true )
then
System.out.println( "Everything is ok" );
end
For the When there is a fire turn on the sprinkler
rule, when a fire occurs, the instances of the Fire
class are created for that room and inserted into the KIE session. The rule adds a constraint for the specific room
matched in the Fire
instance so that only the sprinkler for that room is checked. When this rule is executed, the sprinkler activates. The other sample rules determine when the alarm is activated or deactivated accordingly.
Whereas a stateless KIE session relies on standard Java syntax to modify a field, a stateful KIE session relies on the modify
statement in rules to notify the Drools engine of changes. The Drools engine then reasons over the changes and assesses impact on subsequent rule executions. This process is part of the Drools engine ability to use inference and truth maintenance and is essential in stateful KIE sessions.
In this example, the sample rules and all other files in the ~/resources
folder of the Drools project are built with the following code:
KieServices kieServices = KieServices.Factory.get();
KieContainer kContainer = kieServices.getKieClasspathContainer();
This code compiles all the rule files found on the class path and adds the result of this compilation, a KieModule
object, in the KieContainer
.
Finally, the KieSession
object is instantiated from the KieContainer
and is executed against specified data:
KieSession ksession = kContainer.newKieSession();
String[] names = new String[]{"kitchen", "bedroom", "office", "livingroom"};
Map<String,Room> name2room = new HashMap<String,Room>();
for( String name: names ){
Room room = new Room( name );
name2room.put( name, room );
ksession.insert( room );
Sprinkler sprinkler = new Sprinkler( room );
ksession.insert( sprinkler );
}
ksession.fireAllRules();
> Everything is ok
With the data added, the Drools engine completes all pattern matching but no rules have been executed, so the configured verification message appears. As new data triggers rule conditions, the Drools engine executes rules to activate the alarm and later to cancel the alarm that has been activated:
Fire kitchenFire = new Fire( name2room.get( "kitchen" ) );
Fire officeFire = new Fire( name2room.get( "office" ) );
FactHandle kitchenFireHandle = ksession.insert( kitchenFire );
FactHandle officeFireHandle = ksession.insert( officeFire );
ksession.fireAllRules();
> Raise the alarm
> Turn on the sprinkler for room kitchen
> Turn on the sprinkler for room office
ksession.delete( kitchenFireHandle );
ksession.delete( officeFireHandle );
ksession.fireAllRules();
> Cancel the alarm
> Turn off the sprinkler for room office
> Turn off the sprinkler for room kitchen
> Everything is ok
In this case, a reference is kept for the returned FactHandle
object. A fact handle is an internal engine reference to the inserted instance and enables instances to be retracted or modified later.
As this example illustrates, the data and results from previous stateful KIE sessions (the activated alarm) affect the invocation of subsequent sessions (alarm cancellation).
3.1.3. KIE session pools
In use cases with large amounts of KIE runtime data and high system activity, KIE sessions might be created and disposed very frequently. A high turnover of KIE sessions is not always time consuming, but when the turnover is repeated millions of times, the process can become a bottleneck and require substantial clean-up effort.
For these high-volume cases, you can use KIE session pools instead of many individual KIE sessions. To use a KIE session pool, you obtain a KIE session pool from a KIE container, define the initial number of KIE sessions in the pool, and create the KIE sessions from that pool as usual:
// Obtain a KIE session pool from the KIE container
KieContainerSessionsPool pool = kContainer.newKieSessionsPool(10);
// Create KIE sessions from the KIE session pool
KieSession kSession = pool.newKieSession();
In this example, the KIE session pool starts with 10 KIE sessions in it, but you can specify the number of KIE sessions that you need. This integer value is the number of KIE sessions that are only initially created in the pool. If required by the running application, the number of KIE sessions in the pool can dynamically grow beyond that value.
After you define a KIE session pool, the next time you use the KIE session as usual and call dispose()
on it, the KIE session is reset and pushed back into the pool instead of being destroyed.
KIE session pools typically apply to stateful KIE sessions, but KIE session pools can also affect stateless KIE sessions that you reuse with multiple execute()
calls. When you create a stateless KIE session directly from a KIE container, the KIE session continues to internally create a new KIE session for each execute()
invocation. Conversely, when you create a stateless KIE session from a KIE session pool, the KIE session internally uses only the specific KIE sessions provided by the pool.
When you finish using a KIE session pool, you can call the shutdown()
method on it to avoid memory leaks. Alternatively, you can call dispose()
on the KIE container to shut down all the pools created from the KIE container.
3.2. Inference and truth maintenance in the Drools engine
The basic function of the Drools engine is to match data to business rules and determine whether and how to execute rules. To ensure that relevant data is applied to the appropriate rules, the Drools engine makes inferences based on existing knowledge and performs the actions based on the inferred information.
For example, the following DRL rule determines the age requirements for adults, such as in a bus pass policy:
rule "Infer Adult"
when
$p : Person(age >= 18)
then
insert(new IsAdult($p))
end
Based on this rule, the Drools engine infers whether a person is an adult or a child and performs the specified action (the then
consequence). Every person who is 18 years old or older has an instance of IsAdult
inserted for them in the working memory. This inferred relation of age and bus pass can then be invoked in any rule, such as in the following rule segment:
$p : Person()
IsAdult(person == $p)
In many cases, new data in a rule system is the result of other rule executions, and this new data can affect the execution of other rules. If the Drools engine asserts data as a result of executing a rule, the Drools engine uses truth maintenance to justify the assertion and enforce truthfulness when applying inferred information to other rules. Truth maintenance also helps to identify inconsistencies and to handle contradictions. For example, if two rules are executed and result in a contradictory action, the Drools engine chooses the action based on assumptions from previously calculated conclusions.
The Drools engine inserts facts using either stated or logical insertions:
-
Stated insertions: Defined with
insert()
. After stated insertions, facts are generally retracted explicitly. (The term insertion, when used generically, refers to stated insertion.) -
Logical insertions: Defined with
insertLogical()
. After logical insertions, the facts that were inserted are automatically retracted when the conditions in the rules that inserted the facts are no longer true. The facts are retracted when no condition supports the logical insertion. A fact that is logically inserted is considered to be justified by the Drools engine.
For example, the following sample DRL rules use stated fact insertion to determine the age requirements for issuing a child bus pass or an adult bus pass:
rule "Issue Child Bus Pass"
when
$p : Person(age < 18)
then
insert(new ChildBusPass($p));
end
rule "Issue Adult Bus Pass"
when
$p : Person(age >= 18)
then
insert(new AdultBusPass($p));
end
These rules are not easily maintained in the Drools engine as bus riders increase in age and move from child to adult bus pass. As an alternative, these rules can be separated into rules for bus rider age and rules for bus pass type using logical fact insertion. The logical insertion of the fact makes the fact dependent on the truth of the when
clause.
The following DRL rules use logical insertion to determine the age requirements for children and adults:
rule "Infer Child"
when
$p : Person(age < 18)
then
insertLogical(new IsChild($p))
end
rule "Infer Adult"
when
$p : Person(age >= 18)
then
insertLogical(new IsAdult($p))
end
For logical insertions, your fact objects must override the equals and hashCode methods from the java.lang.Object object according to the Java standard. Two objects are equal if their equals methods return true for each other and if their hashCode methods return the same values. For more information, see the Java API documentation for your Java version.
|
When the condition in the rule is false, the fact is automatically retracted. This behavior is helpful in this example because the two rules are mutually exclusive. In this example, if the person is younger than 18 years old, the rule logically inserts an IsChild
fact. After the person is 18 years old or older, the IsChild
fact is automatically retracted and the IsAdult
fact is inserted.
The following DRL rules then determine whether to issue a child bus pass or an adult bus pass and logically insert the ChildBusPass
and AdultBusPass
facts. This rule configuration is possible because the truth maintenance system in the Drools engine supports chaining of logical insertions for a cascading set of retracts.
rule "Issue Child Bus Pass"
when
$p : Person()
IsChild(person == $p)
then
insertLogical(new ChildBusPass($p));
end
rule "Issue Adult Bus Pass"
when
$p : Person()
IsAdult(person =$p)
then
insertLogical(new AdultBusPass($p));
end
When a person turns 18 years old, the IsChild
fact and the person’s ChildBusPass
fact is retracted. To these set of conditions, you can relate another rule that states that a person must return the child pass after turning 18 years old. When the Drools engine automatically retracts the ChildBusPass
object, the following rule is executed to send a request to the person:
rule "Return ChildBusPass Request"
when
$p : Person()
not(ChildBusPass(person == $p))
then
requestChildBusPass($p);
end
The following flowcharts illustrate the life cycle of stated and logical insertions:
When the Drools engine logically inserts an object during a rule execution, the Drools engine justifies the object by executing the rule. For each logical insertion, only one equal object can exist, and each subsequent equal logical insertion increases the justification counter for that logical insertion. A justification is removed when the conditions of the rule become untrue. When no more justifications exist, the logical object is automatically retracted.
3.2.1. Government ID example
So now we know what inference is, and have a basic example, how does this facilitate good rule design and maintenance?
Consider a government ID department that is responsible for issuing ID cards when children become adults. They might have a decision table that includes logic like this, which says when an adult living in London is 18 or over, issue the card:
RuleTable ID Card |
|||
CONDITION |
CONDITION |
ACTION |
|
p : Person |
|||
location |
age >= $1 |
issueIdCard($1) |
|
Select Person |
Select Adults |
Issue ID Card |
|
Issue ID Card to Adults |
London |
18 |
p |
However the ID department does not set the policy on who an adult is. That’s done at a central government level. If the central government were to change that age to 21, this would initiate a change management process. Someone would have to liaise with the ID department and make sure their systems are updated, in time for the law going live.
This change management process and communication between departments is not ideal for an agile environment, and change becomes costly and error prone. Also the card department is managing more information than it needs to be aware of with its "monolithic" approach to rules management which is "leaking" information better placed elsewhere. By this I mean that it doesn’t care what explicit "age >= 18" information determines whether someone is an adult, only that they are an adult.
In contrast to this, let’s pursue an approach where we split (de-couple) the authoring responsibilities, so that both the central government and the ID department maintain their own rules.
It’s the central government’s job to determine who is an adult. If they change the law they just update their central repository with the new rules, which others use:
RuleTable Age Policy |
||
CONDITION |
ACTION |
|
p : Person |
||
age >= $1 |
insert($1) |
|
Adult Age Policy |
Add Adult Relation |
|
Infer Adult |
18 |
new IsAdult( p ) |
The IsAdult fact, as discussed previously, is inferred from the policy rules. It encapsulates the seemingly arbitrary piece of logic "age >= 18" and provides semantic abstractions for its meaning. Now if anyone uses the above rules, they no longer need to be aware of explicit information that determines whether someone is an adult or not. They can just use the inferred fact:
RuleTable ID Card |
|||
CONDITION |
CONDITION |
ACTION |
|
p : Person |
isAdult |
||
location |
person == $1 |
issueIdCard($1) |
|
Select Person |
Select Adults |
Issue ID Card |
|
Issue ID Card to Adults |
London |
p |
p |
While the example is very minimal and trivial it illustrates some important points. We started with a monolithic and leaky approach to our knowledge engineering. We created a single decision table that had all possible information in it and that leaks information from central government that the ID department did not care about and did not want to manage.
We first de-coupled the knowledge process so each department was responsible for only what it needed to know. We then encapsulated this leaky knowledge using an inferred fact IsAdult. The use of the term IsAdult also gave a semantic abstraction to the previously arbitrary logic "age >= 18".
So a general rule of thumb when doing your knowledge engineering is:
-
Bad
-
Monolithic
-
Leaky
-
-
Good
-
De-couple knowledge responsibilities
-
Encapsulate knowledge
-
Provide semantic abstractions for those encapsulations
-
3.2.2. Fact equality modes in the Drools engine
The Drools engine supports the following fact equality modes that determine how the Drools engine stores and compares inserted facts:
-
identity
: (Default) The Drools engine uses anIdentityHashMap
to store all inserted facts. For every new fact insertion, the Drools engine returns a newFactHandle
object. If a fact is inserted again, the Drools engine returns the originalFactHandle
object, ignoring repeated insertions for the same fact. In this mode, two facts are the same for the Drools engine only if they are the very same object with the same identity. -
equality
: The Drools engine uses aHashMap
to store all inserted facts. The Drools engine returns a newFactHandle
object only if the inserted fact is not equal to an existing fact, according to theequals()
method of the inserted fact. In this mode, two facts are the same for the Drools engine if they are composed the same way, regardless of identity. Use this mode when you want objects to be assessed based on feature equality instead of explicit identity.
As an illustration of fact equality modes, consider the following example facts:
Person p1 = new Person("John", 45);
Person p2 = new Person("John", 45);
In identity
mode, facts p1
and p2
are different instances of a Person
class and are treated as separate objects because they have separate identities. In equality
mode, facts p1
and p2
are treated as the same object because they are composed the same way. This difference in behavior affects how you can interact with fact handles.
For example, assume that you insert facts p1
and p2
into the Drools engine and later you want to retrieve the fact handle for p1
. In identity
mode, you must specify p1
to return the fact handle for that exact object, whereas in equality
mode, you can specify p1
, p2
, or new Person("John", 45)
to return the fact handle.
identity
modeksession.insert(p1);
ksession.getFactHandle(p1);
equality
modeksession.insert(p1);
ksession.getFactHandle(p1);
// Alternate option:
ksession.getFactHandle(new Person("John", 45));
To set the fact equality mode, use one of the following options:
-
Set the system property
drools.equalityBehavior
toidentity
(default) orequality
. -
Set the equality mode while creating the KIE base programmatically:
KieServices ks = KieServices.get(); KieBaseConfiguration kieBaseConf = ks.newKieBaseConfiguration(); kieBaseConf.setOption(EqualityBehaviorOption.EQUALITY); KieBase kieBase = kieContainer.newKieBase(kieBaseConf);
-
Set the equality mode in the KIE module descriptor file (
kmodule.xml
) for a specific Drools project:<kmodule> ... <kbase name="KBase2" default="false" equalsBehavior="equality" packages="org.domain.pkg2, org.domain.pkg3" includes="KBase1"> ... </kbase> ... </kmodule>
3.3. Execution control in the Drools engine
When new rule data enters the working memory of the Drools engine, rules may become fully matched and eligible for execution. A single working memory action can result in multiple eligible rule executions. When a rule is fully matched, the Drools engine creates an activation instance, referencing the rule and the matched facts, and adds the activation onto the Drools engine agenda. The agenda controls the execution order of these rule activations using a conflict resolution strategy.
After the first call of fireAllRules()
in the Java application, the Drools engine cycles repeatedly through two phases:
-
Agenda evaluation. In this phase, the Drools engine selects all rules that can be executed. If no executable rules exist, the execution cycle ends. If an executable rule is found, the Drools engine registers the activation in the agenda and then moves on to the working memory actions phase to perform rule consequence actions.
-
Working memory actions. In this phase, the Drools engine performs the rule consequence actions (the
then
portion of each rule) for all activated rules previously registered in the agenda. After all the consequence actions are complete or the main Java application process callsfireAllRules()
again, the Drools engine returns to the agenda evaluation phase to reassess rules.
When multiple rules exist on the agenda, the execution of one rule may cause another rule to be removed from the agenda. To avoid this, you can define how and when rules are executed in the Drools engine. Some common methods for defining rule execution order are by using rule salience, agenda groups, activation groups, or rule units for DRL rule sets.
3.3.1. Salience for rules
Each rule has an integer salience
attribute that determines the order of execution. Rules with a higher salience value are given higher priority when ordered in the activation queue. The default salience value for rules is zero, but the salience can be negative or positive.
For example, the following sample DRL rules are listed in the Drools engine stack in the order shown:
rule "RuleA"
salience 95
when
$fact : MyFact( field1 == true )
then
System.out.println("Rule2 : " + $fact);
update($fact);
end
rule "RuleB"
salience 100
when
$fact : MyFact( field1 == false )
then
System.out.println("Rule1 : " + $fact);
$fact.setField1(true);
update($fact);
end
The RuleB
rule is listed second, but it has a higher salience value than the RuleA
rule and is therefore executed first.
3.3.2. Agenda groups for rules
An agenda group is a set of rules bound together by the same agenda-group
rule attribute. Agenda groups partition rules on the Drools engine agenda. At any one time, only one group has a focus that gives that group of rules priority for execution before rules in other agenda groups. You determine the focus with a setFocus()
call for the agenda group. You can also define rules with an auto-focus
attribute so that the next time the rule is activated, the focus is automatically given to the entire agenda group to which the rule is assigned.
Each time the setFocus()
call is made in a Java application, the Drools engine adds the specified agenda group to the top of the rule stack. The default agenda group "MAIN"
contains all rules that do not belong to a specified agenda group and is executed first in the stack unless another group has the focus.
For example, the following sample DRL rules belong to specified agenda groups and are listed in the Drools engine stack in the order shown:
rule "Increase balance for credits"
agenda-group "calculation"
when
ap : AccountPeriod()
acc : Account( $accountNo : accountNo )
CashFlow( type == CREDIT,
accountNo == $accountNo,
date >= ap.start && <= ap.end,
$amount : amount )
then
acc.balance += $amount;
end
rule "Print balance for AccountPeriod"
agenda-group "report"
when
ap : AccountPeriod()
acc : Account()
then
System.out.println( acc.accountNo +
" : " + acc.balance );
end
For this example, the rules in the "report"
agenda group must always be executed first and the rules in the "calculation"
agenda group must always be executed second. Any remaining rules in other agenda groups can then be executed. Therefore, the "report"
and "calculation"
groups must receive the focus to be executed in that order, before other rules can be executed:
Agenda agenda = ksession.getAgenda();
agenda.getAgendaGroup( "report" ).setFocus();
agenda.getAgendaGroup( "calculation" ).setFocus();
ksession.fireAllRules();
You can also use the clear()
method to cancel all the activations generated by the rules belonging to a given agenda group before each has had a chance to be executed:
ksession.getAgenda().getAgendaGroup( "Group A" ).clear();
3.3.3. Activation groups for rules
An activation group is a set of rules bound together by the same activation-group
rule attribute. In this group, only one rule can be executed. After conditions are met for a rule in that group to be executed, all other pending rule executions from that activation group are removed from the agenda.
For example, the following sample DRL rules belong to the specified activation group and are listed in the Drools engine stack in the order shown:
rule "Print balance for AccountPeriod1"
activation-group "report"
when
ap : AccountPeriod1()
acc : Account()
then
System.out.println( acc.accountNo +
" : " + acc.balance );
end
rule "Print balance for AccountPeriod2"
activation-group "report"
when
ap : AccountPeriod2()
acc : Account()
then
System.out.println( acc.accountNo +
" : " + acc.balance );
end
For this example, if the first rule in the "report"
activation group is executed, the second rule in the group and all other executable rules on the agenda are removed from the agenda.
3.3.4. Rule execution modes and thread safety in the Drools engine
The Drools engine supports the following rule execution modes that determine how and when the Drools engine executes rules:
-
Passive mode: (Default) The Drools engine evaluates rules when a user or an application explicitly calls
fireAllRules()
. Passive mode in the Drools engine is best for applications that require direct control over rule evaluation and execution, or for complex event processing (CEP) applications that use the pseudo clock implementation in the Drools engine.Example CEP application code with the Drools engine in passive modeKieSessionConfiguration config = KieServices.Factory.get().newKieSessionConfiguration(); config.setOption( ClockTypeOption.get("pseudo") ); KieSession session = kbase.newKieSession( conf, null ); SessionPseudoClock clock = session.getSessionClock(); session.insert( tick1 ); session.fireAllRules(); clock.advanceTime(1, TimeUnit.SECONDS); session.insert( tick2 ); session.fireAllRules(); clock.advanceTime(1, TimeUnit.SECONDS); session.insert( tick3 ); session.fireAllRules(); session.dispose();
-
Active mode: If a user or application calls
fireUntilHalt()
, the Drools engine starts in active mode and evaluates rules continually until the user or application explicitly callshalt()
. Active mode in the Drools engine is best for applications that delegate control of rule evaluation and execution to the Drools engine, or for complex event processing (CEP) applications that use the real-time clock implementation in the Drools engine. Active mode is also optimal for CEP applications that use active queries.Example CEP application code with the Drools engine in active modeKieSessionConfiguration config = KieServices.Factory.get().newKieSessionConfiguration(); config.setOption( ClockTypeOption.get("realtime") ); KieSession session = kbase.newKieSession( conf, null ); new Thread( new Runnable() { @Override public void run() { session.fireUntilHalt(); } } ).start(); session.insert( tick1 ); ... Thread.sleep( 1000L ); ... session.insert( tick2 ); ... Thread.sleep( 1000L ); ... session.insert( tick3 ); session.halt(); session.dispose();
This example calls
fireUntilHalt()
from a dedicated execution thread to prevent the current thread from being blocked indefinitely while the Drools engine continues evaluating rules. The dedicated thread also enables you to callhalt()
at a later stage in the application code.
Although you should avoid using both fireAllRules()
and fireUntilHalt()
calls, especially from different threads, the Drools engine can handle such situations safely using thread-safety logic and an internal state machine. If a fireAllRules()
call is in progress and you call fireUntilHalt()
, the Drools engine continues to run in passive mode until the fireAllRules()
operation is complete and then starts in active mode in response to the fireUntilHalt()
call. However, if the Drools engine is running in active mode following a fireUntilHalt()
call and you call fireAllRules()
, the fireAllRules()
call is ignored and the Drools engine continues to run in active mode until you call halt()
.
For more details about thread-safety and the internal state machine, see Improved multi-threading behaviour.
For added thread safety in active mode, the Drools engine supports a submit()
method that you can use to group and perform operations on a KIE session in a thread-safe, atomic action:
submit()
method to perform atomic operations in active modeKieSession session = ...;
new Thread( new Runnable() {
@Override
public void run() {
session.fireUntilHalt();
}
} ).start();
final FactHandle fh = session.insert( fact_a );
... Thread.sleep( 1000L ); ...
session.submit( new KieSession.AtomicAction() {
@Override
public void execute( KieSession kieSession ) {
fact_a.setField("value");
kieSession.update( fh, fact_a );
kieSession.insert( fact_1 );
kieSession.insert( fact_2 );
kieSession.insert( fact_3 );
}
} );
... Thread.sleep( 1000L ); ...
session.insert( fact_z );
session.halt();
session.dispose();
Thread safety and atomic operations are also helpful from a client-side perspective. For example, you might need to insert more than one fact at a given time, but require the Drools engine to consider the insertions as an atomic operation and to wait until all the insertions are complete before evaluating the rules again.
3.3.5. Fact propagation modes in the Drools engine
The Drools engine supports the following fact propagation modes that determine how the Drools engine progresses inserted facts through the engine network in preparation for rule execution:
-
Lazy: (Default) Facts are propagated in batch collections at rule execution, not in real time as the facts are individually inserted by a user or application. As a result, the order in which the facts are ultimately propagated through the Drools engine may be different from the order in which the facts were individually inserted.
-
Immediate: Facts are propagated immediately in the order that they are inserted by a user or application.
-
Eager: Facts are propagated lazily (in batch collections), but before rule execution. The Drools engine uses this propagation behavior for rules that have the
no-loop
orlock-on-active
attribute.
By default, the Phreak rule algorithm in the Drools engine uses lazy fact propagation for improved rule evaluation overall. However, in few cases, this lazy propagation behavior can alter the expected result of certain rule executions that may require immediate or eager propagation.
For example, the following rule uses a specified query with a ?
prefix to invoke the query in pull-only or passive fashion:
query Q (Integer i)
String( this == i.toString() )
end
rule "Rule"
when
$i : Integer()
?Q( $i; )
then
System.out.println( $i );
end
For this example, the rule should be executed only when a String
that satisfies the query is inserted before the Integer
, such as in the following example commands:
KieSession ksession = ...
ksession.insert("1");
ksession.insert(1);
ksession.fireAllRules();
However, due to the default lazy propagation behavior in Phreak, the Drools engine does not detect the insertion sequence of the two facts in this case, so this rule is executed regardless of String
and Integer
insertion order. For this example, immediate propagation is required for the expected rule evaluation.
To alter the Drools engine propagation mode to achieve the expected rule evaluation in this case, you can add the @Propagation(<type>)
tag to your rule and set <type>
to LAZY
, IMMEDIATE
, or EAGER
.
In the same example rule, the immediate propagation annotation enables the rule to be evaluated only when a String
that satisfies the query is inserted before the Integer
, as expected:
query Q (Integer i)
String( this == i.toString() )
end
rule "Rule" @Propagation(IMMEDIATE)
when
$i : Integer()
?Q( $i; )
then
System.out.println( $i );
end
3.3.6. Agenda evaluation filters
The Drools engine supports an AgendaFilter
object in the filter interface that you can use to allow or deny the evaluation of specified rules during agenda evaluation. You can specify an agenda filter as part of a fireAllRules()
call.
The following example code permits only rules ending with the string "Test"
to be evaluated and executed. All other rules are filtered out of the Drools engine agenda.
ksession.fireAllRules( new RuleNameEndsWithAgendaFilter( "Test" ) );
3.3.7. Rule units in DRL rule sets
Rule units are groups of data sources, global variables, and DRL rules that function together for a specific purpose. You can use rule units to partition a rule set into smaller units, bind different data sources to those units, and then execute the individual unit. Rule units are an enhanced alternative to rule-grouping DRL attributes such as rule agenda groups or activation groups for execution control.
Rule units are helpful when you want to coordinate rule execution so that the complete execution of one rule unit triggers the start of another rule unit and so on. For example, assume that you have a set of rules for data enrichment, another set of rules that processes that data, and another set of rules that extract the output from the processed data. If you add these rule sets into three distinct rule units, you can coordinate those rule units so that complete execution of the first unit triggers the start of the second unit and the complete execution of the second unit triggers the start of third unit.
To define a rule unit, implement the RuleUnit
interface as shown in the following example:
package org.mypackage.myunit;
public static class AdultUnit implements RuleUnit {
private int adultAge;
private DataSource<Person> persons;
public AdultUnit( ) { }
public AdultUnit( DataSource<Person> persons, int age ) {
this.persons = persons;
this.age = age;
}
// A data source of `Persons` in this rule unit:
public DataSource<Person> getPersons() {
return persons;
}
// A global variable in this rule unit:
public int getAdultAge() {
return adultAge;
}
// Life-cycle methods:
@Override
public void onStart() {
System.out.println("AdultUnit started.");
}
@Override
public void onEnd() {
System.out.println("AdultUnit ended.");
}
}
In this example, persons
is a source of facts of type Person
. A rule unit data source is a source of the data processed by a given rule unit and represents the entry point that the Drools engine uses to evaluate the rule unit. The adultAge
global variable is accessible from all the rules belonging to this rule unit. The last two methods are part of the rule unit life cycle and are invoked by the Drools engine.
The Drools engine supports the following optional life-cycle methods for rule units:
Method | Invoked when |
---|---|
|
Rule unit execution starts |
|
Rule unit execution ends |
|
Rule unit execution is suspended (used only with |
|
Rule unit execution is resumed (used only with |
|
The consequence of a rule in the rule unit triggers the execution of a different rule unit |
You can add one or more rules to a rule unit. By default, all the rules in a DRL file are automatically associated with a rule unit that follows the naming convention of the DRL file name. If the DRL file is in the same package and has the same name as a class that implements the RuleUnit
interface, then all of the rules in that DRL file implicitly belong to that rule unit. For example, all the rules in the AdultUnit.drl
file in the org.mypackage.myunit
package are automatically part of the rule unit org.mypackage.myunit.AdultUnit
.
To override this naming convention and explicitly declare the rule unit that the rules in a DRL file belong to, use the unit
keyword in the DRL file. The unit
declaration must immediately follow the package declaration and contain the name of the class in that package that the rules in the DRL file are part of.
package org.mypackage.myunit
unit AdultUnit
rule Adult
when
$p : Person(age >= adultAge) from persons
then
System.out.println($p.getName() + " is adult and greater than " + adultAge);
end
Do not mix rules with and without a rule unit in the same KIE base. Mixing two rule paradigms in a KIE base results in a compilation error. |
You can also rewrite the same pattern in a more convenient way using OOPath notation, as shown in the following example:
package org.mypackage.myunit
unit AdultUnit
rule Adult
when
$p : /persons[age >= adultAge]
then
System.out.println($p.getName() + " is adult and greater than " + adultAge);
end
OOPath is an object-oriented syntax extension of XPath that is designed for browsing graphs of objects in DRL rule condition constraints. OOPath uses the compact notation from XPath for navigating through related elements while handling collections and filtering constraints, and is specifically useful for graphs of objects. |
In this example, any matching facts in the rule conditions are retrieved from the persons
data source defined in the DataSource
definition in the rule unit class. The rule condition and action use the adultAge
variable in the same way that a global variable is defined at the DRL file level.
To execute one or more rule units defined in a KIE base, create a new RuleUnitExecutor
class bound to the KIE base, create the rule unit from the relevant data source, and run the rule unit executer:
// Create a `RuleUnitExecutor` class and bind it to the KIE base:
KieBase kbase = kieContainer.getKieBase();
RuleUnitExecutor executor = RuleUnitExecutor.create().bind( kbase );
// Create the `AdultUnit` rule unit using the `persons` data source and run the executor:
RuleUnit adultUnit = new AdultUnit(persons, 18);
executor.run( adultUnit );
Rules are executed by the RuleUnitExecutor
class. The RuleUnitExecutor
class creates KIE sessions and adds the required DataSource
objects to those sessions, and then executes the rules based on the RuleUnit
that is passed as a parameter to the run()
method.
The example execution code produces the following output when the relevant Person
facts are inserted in the persons
data source:
org.mypackage.myunit.AdultUnit started.
Jane is adult and greater than 18
John is adult and greater than 18
org.mypackage.myunit.AdultUnit ended.
Instead of explicitly creating the rule unit instance, you can register the rule unit variables in the executor and pass to the executor the rule unit class that you want to run, and then the executor creates an instance of the rule unit. You can then set the DataSource
definition and other variables as needed before running the rule unit.
executor.bindVariable( "persons", persons );
.bindVariable( "adultAge", 18 );
executor.run( AdultUnit.class );
The name that you pass to the RuleUnitExecutor.bindVariable()
method is used at run time to bind the variable to the field of the rule unit class with the same name. In the previous example, the RuleUnitExecutor
inserts into the new rule unit the data source bound to the "persons"
name and inserts the value 18
bound to the String "adultAge"
into the fields with the corresponding names inside the AdultUnit
class.
To override this default variable-binding behavior, use the @UnitVar
annotation to explicitly define a logical binding name for each field of the rule unit class. For example, the field bindings in the following class are redefined with alternative names:
@UnitVar
package org.mypackage.myunit;
public static class AdultUnit implements RuleUnit {
@UnitVar("minAge")
private int adultAge = 18;
@UnitVar("data")
private DataSource<Person> persons;
}
You can then bind the variables to the executor using those alternative names and run the rule unit:
executor.bindVariable( "data", persons );
.bindVariable( "minAge", 18 );
executor.run( AdultUnit.class );
You can execute a rule unit in passive mode by using the run()
method (equivalent to invoking fireAllRules()
on a KIE session)
or in active mode using the runUntilHalt()
method (equivalent to invoking fireUntilHalt()
on a KIE session). By default, the Drools engine runs in passive mode and evaluates rule units only when a user or an application explicitly calls run()
(or fireAllRules()
for standard rules). If a user or application calls runUntilHalt()
for rule units (or fireUntilHalt()
for standard rules), the Drools engine starts in active mode and evaluates rule units continually until the user or application explicitly calls halt()
.
If you use the runUntilHalt()
method, invoke the method on a separate execution thread to avoid blocking the main thread:
runUntilHalt()
on a separate threadnew Thread( () -> executor.runUntilHalt( adultUnit ) ).start();
3.3.7.1. Data sources for rule units
A rule unit data source is a source of the data processed by a given rule unit and represents the entry point that the Drools engine uses to evaluate the rule unit. A rule unit can have zero or more data sources and each DataSource
definition declared inside a rule unit can correspond to a different entry point into the rule unit executor. Multiple rule units can share a single data source, but each rule unit must use different entry points through which the same objects are inserted.
You can create a DataSource
definition with a fixed set of data in a rule unit class, as shown in the following example:
DataSource<Person> persons = DataSource.create( new Person( "John", 42 ),
new Person( "Jane", 44 ),
new Person( "Sally", 4 ) );
Because a data source represents the entry point of the rule unit, you can insert, update, or delete facts in a rule unit:
// Insert a fact:
Person john = new Person( "John", 42 );
FactHandle johnFh = persons.insert( john );
// Modify the fact and optionally specify modified properties (for property reactivity):
john.setAge( 43 );
persons.update( johnFh, john, "age" );
// Delete the fact:
persons.delete( johnFh );
3.3.7.2. Rule unit execution control
Rule units are helpful when you want to coordinate rule execution so that the execution of one rule unit triggers the start of another rule unit and so on.
To facilitate rule unit execution control, the Drools engine supports the following rule unit methods that you can use in DRL rule actions to coordinate the execution of rule units:
-
drools.run()
: Triggers the execution of a specified rule unit class. This method imperatively interrupts the execution of the rule unit and activates the other specified rule unit. -
drools.guard()
: Prevents (guards) a specified rule unit class from being executed until the associated rule condition is met. This method declaratively schedules the execution of the other specified rule unit. When the Drools engine produces at least one match for the condition in the guarding rule, the guarded rule unit is considered active. A rule unit can contain multiple guarding rules.
As an example of the drools.run()
method, consider the following DRL rules that each belong to a specified rule unit. The NotAdult
rule uses the drools.run( AdultUnit.class )
method to trigger the execution of the AdultUnit
rule unit:
drools.run()
package org.mypackage.myunit
unit AdultUnit
rule Adult
when
Person(age >= 18, $name : name) from persons
then
System.out.println($name + " is adult");
end
package org.mypackage.myunit
unit NotAdultUnit
rule NotAdult
when
$p : Person(age < 18, $name : name) from persons
then
System.out.println($name + " is NOT adult");
modify($p) { setAge(18); }
drools.run( AdultUnit.class );
end
The example also uses a RuleUnitExecutor
class created from the KIE base that was built from these rules and a DataSource
definition of persons
bound to it:
RuleUnitExecutor executor = RuleUnitExecutor.create().bind( kbase );
DataSource<Person> persons = executor.newDataSource( "persons",
new Person( "John", 42 ),
new Person( "Jane", 44 ),
new Person( "Sally", 4 ) );
In this case, the example creates the DataSource
definition directly from the RuleUnitExecutor
class and binds it to the "persons"
variable in a single statement.
The example execution code produces the following output when the relevant Person
facts are inserted in the persons
data source:
Sally is NOT adult
John is adult
Jane is adult
Sally is adult
The NotAdult
rule detects a match when evaluating the person "Sally"
, who is under 18 years old. The rule then modifies
her age to 18
and uses the drools.run( AdultUnit.class )
method to trigger the execution of the AdultUnit
rule unit. The AdultUnit
rule unit contains a rule that can now be executed for all of the 3 persons
in the DataSource
definition.
As an example of the drools.guard()
method, consider the following BoxOffice
class and BoxOfficeUnit
rule unit class:
BoxOffice
classpublic class BoxOffice {
private boolean open;
public BoxOffice( boolean open ) {
this.open = open;
}
public boolean isOpen() {
return open;
}
public void setOpen( boolean open ) {
this.open = open;
}
}
BoxOfficeUnit
rule unit classpublic class BoxOfficeUnit implements RuleUnit {
private DataSource<BoxOffice> boxOffices;
public DataSource<BoxOffice> getBoxOffices() {
return boxOffices;
}
}
The example also uses the following TicketIssuerUnit
rule unit class to keep selling box office tickets for the event as long as at least one box office is open. This rule unit uses DataSource
definitions of persons
and tickets
:
TicketIssuerUnit
rule unit classpublic class TicketIssuerUnit implements RuleUnit {
private DataSource<Person> persons;
private DataSource<AdultTicket> tickets;
private List<String> results;
public TicketIssuerUnit() { }
public TicketIssuerUnit( DataSource<Person> persons, DataSource<AdultTicket> tickets ) {
this.persons = persons;
this.tickets = tickets;
}
public DataSource<Person> getPersons() {
return persons;
}
public DataSource<AdultTicket> getTickets() {
return tickets;
}
public List<String> getResults() {
return results;
}
}
The BoxOfficeUnit
rule unit contains a BoxOfficeIsOpen
DRL rule that uses the drools.guard( TicketIssuerUnit.class )
method to guard the execution of the TicketIssuerUnit
rule unit that distributes the event tickets, as shown in the following DRL rule examples:
drools.guard()
package org.mypackage.myunit;
unit TicketIssuerUnit;
rule IssueAdultTicket when
$p: /persons[ age >= 18 ]
then
tickets.insert(new AdultTicket($p));
end
rule RegisterAdultTicket when
$t: /tickets
then
results.add( $t.getPerson().getName() );
end
package org.mypackage.myunit;
unit BoxOfficeUnit;
rule BoxOfficeIsOpen
when
$box: /boxOffices[ open ]
then
drools.guard( TicketIssuerUnit.class );
end
In this example, so long as at least one box office is open
, the guarded TicketIssuerUnit
rule unit is active and distributes event tickets. When no more box offices are in open
state, the guarded TicketIssuerUnit
rule unit is prevented from being executed.
The following example class illustrates a more complete box office scenario:
DataSource<Person> persons = executor.newDataSource( "persons" );
DataSource<BoxOffice> boxOffices = executor.newDataSource( "boxOffices" );
DataSource<AdultTicket> tickets = executor.newDataSource( "tickets" );
List<String> list = new ArrayList<>();
executor.bindVariable( "results", list );
// Two box offices are open:
BoxOffice office1 = new BoxOffice(true);
FactHandle officeFH1 = boxOffices.insert( office1 );
BoxOffice office2 = new BoxOffice(true);
FactHandle officeFH2 = boxOffices.insert( office2 );
persons.insert(new Person("John", 40));
// Execute `BoxOfficeIsOpen` rule, run `TicketIssuerUnit` rule unit, and execute `RegisterAdultTicket` rule:
executor.run(BoxOfficeUnit.class);
assertEquals( 1, list.size() );
assertEquals( "John", list.get(0) );
list.clear();
persons.insert(new Person("Matteo", 30));
// Execute `RegisterAdultTicket` rule:
executor.run(BoxOfficeUnit.class);
assertEquals( 1, list.size() );
assertEquals( "Matteo", list.get(0) );
list.clear();
// One box office is closed, the other is open:
office1.setOpen(false);
boxOffices.update(officeFH1, office1);
persons.insert(new Person("Mark", 35));
executor.run(BoxOfficeUnit.class);
assertEquals( 1, list.size() );
assertEquals( "Mark", list.get(0) );
list.clear();
// All box offices are closed:
office2.setOpen(false);
boxOffices.update(officeFH2, office2); // Guarding rule is no longer true.
persons.insert(new Person("Edson", 35));
executor.run(BoxOfficeUnit.class); // No execution
assertEquals( 0, list.size() );
3.3.7.3. Rule unit identity conflicts
In rule unit execution scenarios with guarded rule units, a rule can guard multiple rule units and at the same time a rule unit can be guarded and then activated by multiple rules. For these two-way guarding scenarios, rule units must have a clearly defined identity to avoid identity conflicts.
By default, the identity of a rule unit is the rule unit class name and is treated as a singleton class by the RuleUnitExecutor
. This identification behavior is encoded in the getUnitIdentity()
default method of the RuleUnit
interface:
RuleUnit
interfacedefault Identity getUnitIdentity() {
return new Identity( getClass() );
}
In some cases, you may need to override this default identification behavior to avoid conflicting identities between rule units.
For example, the following RuleUnit
class contains a DataSource
definition that accepts any kind of object:
Unit0
rule unit classpublic class Unit0 implements RuleUnit {
private DataSource<Object> input;
public DataSource<Object> getInput() {
return input;
}
}
This rule unit contains the following DRL rule that guards another rule unit based on two conditions (in OOPath notation):
GuardAgeCheck
DRL rule in the rule unitpackage org.mypackage.myunit
unit Unit0
rule GuardAgeCheck
when
$i: /input#Integer
$s: /input#String
then
drools.guard( new AgeCheckUnit($i) );
drools.guard( new AgeCheckUnit($s.length()) );
end
The guarded AgeCheckUnit
rule unit verifies the age of a set of persons
. The AgeCheckUnit
contains a DataSource
definition of the persons
to check, a minAge
variable that it verifies against, and a List
for gathering the results:
AgeCheckUnit
rule unitpublic class AgeCheckUnit implements RuleUnit {
private final int minAge;
private DataSource<Person> persons;
private List<String> results;
public AgeCheckUnit( int minAge ) {
this.minAge = minAge;
}
public DataSource<Person> getPersons() {
return persons;
}
public int getMinAge() {
return minAge;
}
public List<String> getResults() {
return results;
}
}
The AgeCheckUnit
rule unit contains the following DRL rule that performs the verification of the persons
in the data source:
CheckAge
DRL rule in the rule unitpackage org.mypackage.myunit
unit AgeCheckUnit
rule CheckAge
when
$p : /persons{ age > minAge }
then
results.add($p.getName() + ">" + minAge);
end
This example creates a RuleUnitExecutor
class, binds the class to the KIE base that contains these two rule units, and creates
the two DataSource
definitions for the same rule units:
RuleUnitExecutor executor = RuleUnitExecutor.create().bind( kbase );
DataSource<Object> input = executor.newDataSource( "input" );
DataSource<Person> persons = executor.newDataSource( "persons",
new Person( "John", 42 ),
new Person( "Sally", 4 ) );
List<String> results = new ArrayList<>();
executor.bindVariable( "results", results );
You can now insert some objects into the input data source and execute the Unit0
rule unit:
ds.insert("test");
ds.insert(3);
ds.insert(4);
executor.run(Unit0.class);
[Sally>3, John>3]
In this example, the rule unit named AgeCheckUnit
is considered a singleton class and then executed only once, with the minAge
variable set to 3
. Both the String "test"
and the Integer 4
inserted into the input data source can also trigger a second execution with the minAge
variable set to 4
. However, the second execution does not occur because another rule unit with the same identity has already been evaluated.
To resolve this rule unit identity conflict, override the getUnitIdentity()
method in the AgeCheckUnit
class to include also the minAge
variable in the rule unit identity:
AgeCheckUnit
rule unit to override the getUnitIdentity()
methodpublic class AgeCheckUnit implements RuleUnit {
...
@Override
public Identity getUnitIdentity() {
return new Identity(getClass(), minAge);
}
}
With this override in place, the previous example rule unit execution produces the following output:
[John>4, Sally>3, John>3]
The rule units with minAge
set to 3
and 4
are now considered two different rule units and both are executed.
3.4. Phreak rule algorithm in the Drools engine
The Drools engine in Drools uses the Phreak algorithm for rule evaluation. Phreak evolved from the Rete algorithm, including the enhanced Rete algorithm ReteOO that was introduced in previous versions of Drools for object-oriented systems. Overall, Phreak is more scalable than Rete and ReteOO, and is faster in large systems.
While Rete is considered eager (immediate rule evaluation) and data oriented, Phreak is considered lazy (delayed rule evaluation) and goal oriented. The Rete algorithm performs many actions during the insert, update, and delete actions in order to find partial matches for all rules. This eagerness of the Rete algorithm during rule matching requires a lot of time before eventually executing rules, especially in large systems. With Phreak, this partial matching of rules is delayed deliberately to handle large amounts of data more efficiently.
The Phreak algorithm adds the following set of enhancements to previous Rete algorithms:
-
Three layers of contextual memory: Node, segment, and rule memory types
-
Rule-based, segment-based, and node-based linking
-
Lazy (delayed) rule evaluation
-
Stack-based evaluations with pause and resume
-
Isolated rule evaluation
-
Set-oriented propagations
3.4.1. Rule evaluation in Phreak
When the Drools engine starts, all rules are considered to be unlinked from pattern-matching data that can trigger the rules. At this stage, the Phreak algorithm in the Drools engine does not evaluate the rules. The insert
, update
, and delete
actions are queued, and Phreak uses a heuristic, based on the rule most likely to result in execution, to calculate and select the next rule for evaluation. When all the required input values are populated for a rule, the rule is considered to be linked to the relevant pattern-matching data. Phreak then creates a goal that represents this rule and places the goal into a priority queue that is ordered by rule salience. Only the rule for which the goal was created is evaluated, and other potential rule evaluations are delayed. While individual rules are evaluated, node sharing is still achieved through the process of segmentation.
Unlike the tuple-oriented Rete, the Phreak propagation is collection oriented. For the rule that is being evaluated, the Drools engine accesses the first node and processes all queued insert, update, and delete actions. The results are added to a set, and the set is propagated to the child node. In the child node, all queued insert, update, and delete actions are processed, adding the results to the same set. The set is then propagated to the next child node and the same process repeats until it reaches the terminal node. This cycle creates a batch process effect that can provide performance advantages for certain rule constructs.
The linking and unlinking of rules happens through a layered bit-mask system, based on network segmentation. When the rule network is built, segments are created for rule network nodes that are shared by the same set of rules. A rule is composed of a path of segments. In case a rule does not share any node with any other rule, it becomes a single segment.
A bit-mask offset is assigned to each node in the segment. Another bit mask is assigned to each segment in the path of the rule according to these requirements:
-
If at least one input for a node exists, the node bit is set to the
on
state. -
If each node in a segment has the bit set to the
on
state, the segment bit is also set to theon
state. -
If any node bit is set to the
off
state, the segment is also set to theoff
state. -
If each segment in the path of the rule is set to the
on
state, the rule is considered linked, and a goal is created to schedule the rule for evaluation.
The same bit-mask technique is used to track modified nodes, segments, and rules. This tracking ability enables an already linked rule to be unscheduled from evaluation if it has been modified since the evaluation goal for it was created. As a result, no rules can ever evaluate partial matches.
This process of rule evaluation is possible in Phreak because, as opposed to a single unit of memory in Rete, Phreak has three layers of contextual memory with node, segment, and rule memory types. This layering enables much more contextual understanding during the evaluation of a rule.
The following examples illustrate how rules are organized and evaluated in this three-layered memory system in Phreak.
Example 1: A single rule (R1) with three patterns: A, B and C. The rule forms a single segment, with bits 1, 2, and 4 for the nodes. The single segment has a bit offset of 1.
Example 2: Rule R2 is added and shares pattern A.
Pattern A is placed in its own segment, resulting in two segments for each rule. Those two segments form a path for their respective rules. The first segment is shared by both paths. When pattern A is linked, the segment becomes linked. The segment then iterates over each path that the segment is shared by, setting the bit 1 to on
. If patterns B and C are later turned on, the second segment for path R1 is linked, and this causes bit 2 to be turned on for R1. With bit 1 and bit 2 turned on for R1, the rule is now linked and a goal is created to schedule the rule for later evaluation and execution.
When a rule is evaluated, the segments enable the results of the matching to be shared. Each segment has a staging memory to queue all inserts, updates, and deletes for that segment. When R1 is evaluated, the rule processes pattern A, and this results in a set of tuples. The algorithm detects a segmentation split, creates peered tuples for each insert, update, and delete in the set, and adds them to the R2 staging memory. Those tuples are then merged with any existing staged tuples and are executed when R2 is eventually evaluated.
Example 3: Rules R3 and R4 are added and share patterns A and B.
Rules R3 and R4 have three segments and R1 has two segments. Patterns A and B are shared by R1, R3, and R4, while pattern D is shared by R3 and R4.
Example 4: A single rule (R1) with a subnetwork and no pattern sharing.
Subnetworks are formed when a Not
, Exists
, or Accumulate
node contains more than one element. In this example, the element B not( C )
forms the subnetwork. The element not( C )
is a single element that does not require a subnetwork and is therefore merged inside of the Not
node. The subnetwork uses a dedicated segment. Rule R1 still has a path of two segments and the subnetwork forms another inner path. When the subnetwork is linked, it is also linked in the outer segment.
Example 5: Rule R1 with a subnetwork that is shared by rule R2.
The subnetwork nodes in a rule can be shared by another rule that does not have a subnetwork. This sharing causes the subnetwork segment to be split into two segments.
Constrained Not
nodes and Accumulate
nodes can never unlink a segment, and are always considered to have their bits turned on.
The Phreak evaluation algorithm is stack based instead of method-recursion based. Rule evaluation can be paused and resumed at any time when a StackEntry
is used to represent the node currently being evaluated.
When a rule evaluation reaches a subnetwork, a StackEntry
object is created for the outer path segment and the subnetwork segment. The subnetwork segment is evaluated first, and when the set reaches the end of the subnetwork path, the segment is merged into a staging list for the outer node that the segment feeds into. The previous StackEntry
object is then resumed and can now process the results of the subnetwork. This process has the added benefit, especially for Accumulate
nodes, that all work is completed in a batch, before propagating to the child node.
The same stack system is used for efficient backward chaining. When a rule evaluation reaches a query node, the evaluation is paused and the query is added to the stack. The query is then evaluated to produce a result set, which is saved in a memory location for the resumed StackEntry
object to pick up and propagate to the child node. If the query itself called other queries, the process repeats, while the current query is paused and a new evaluation is set up for the current query node.
3.4.1.1. Rule evaluation with forward and backward chaining
The Drools engine in Drools is a hybrid reasoning system that uses both forward chaining and backward chaining to evaluate rules. A forward-chaining rule system is a data-driven system that starts with a fact in the working memory of the Drools engine and reacts to changes to that fact. When objects are inserted into working memory, any rule conditions that become true as a result of the change are scheduled for execution by the agenda.
In contrast, a backward-chaining rule system is a goal-driven system that starts with a conclusion that the Drools engine attempts to satisfy, often using recursion. If the system cannot reach the conclusion or goal, it searches for subgoals, which are conclusions that complete part of the current goal. The system continues this process until either the initial conclusion is satisfied or all subgoals are satisfied.
The following diagram illustrates how the Drools engine evaluates rules using forward chaining overall with a backward-chaining segment in the logic flow:
3.4.2. Rule base configuration
Drools contains a RuleBaseConfiguration.java
object that you can use to configure exception handler settings, multithreaded execution, and sequential mode in the Drools engine.
For the rule base configuration options, see the Drools RuleBaseConfiguration.java page in GitHub.
The following rule base configuration options are available for the Drools engine:
- drools.consequenceExceptionHandler
-
When configured, this system property defines the class that manages the exceptions thrown by rule consequences. You can use this property to specify a custom exception handler for rule evaluation in the Drools engine.
Default value:
org.drools.core.runtime.rule.impl.DefaultConsequenceExceptionHandler
You can specify the custom exception handler using one of the following options:
-
Specify the exception handler in a system property:
drools.consequenceExceptionHandler=org.drools.core.runtime.rule.impl.MyCustomConsequenceExceptionHandler
-
Specify the exception handler while creating the KIE base programmatically:
KieServices ks = KieServices.Factory.get(); KieBaseConfiguration kieBaseConf = ks.newKieBaseConfiguration(); kieBaseConf.setOption(ConsequenceExceptionHandlerOption.get(MyCustomConsequenceExceptionHandler.class)); KieBase kieBase = kieContainer.newKieBase(kieBaseConf);
-
- drools.multithreadEvaluation
-
When enabled, this system property enables the Drools engine to evaluate rules in parallel by dividing the Phreak rule network into independent partitions. You can use this property to increase the speed of rule evaluation for specific rule bases.
Default value:
false
You can enable multithreaded evaluation using one of the following options:
-
Enable the multithreaded evaluation system property:
drools.multithreadEvaluation=true
-
Enable multithreaded evaluation while creating the KIE base programmatically:
KieServices ks = KieServices.Factory.get(); KieBaseConfiguration kieBaseConf = ks.newKieBaseConfiguration(); kieBaseConf.setOption(MultithreadEvaluationOption.YES); KieBase kieBase = kieContainer.newKieBase(kieBaseConf);
Rules that use queries, salience, or agenda groups are currently not supported by the parallel Drools engine. If these rule elements are present in the KIE base, the compiler emits a warning and automatically switches back to single-threaded evaluation. However, in some cases, the Drools engine might not detect the unsupported rule elements and rules might be evaluated incorrectly. For example, the Drools engine might not detect when rules rely on implicit salience given by rule ordering inside the DRL file, resulting in incorrect evaluation due to the unsupported salience attribute.
-
- drools.sequential
-
When enabled, this system property enables sequential mode in the Drools engine. In sequential mode, the Drools engine evaluates rules one time in the order that they are listed in the Drools engine agenda without regard to changes in the working memory. This means that the Drools engine ignores any
insert
,modify
, orupdate
statements in rules and executes rules in a single sequence. As a result, rule execution may be faster in sequential mode, but important updates may not be applied to your rules. You can use this property if you use stateless KIE sessions and you do not want the execution of rules to influence subsequent rules in the agenda. Sequential mode applies to stateless KIE sessions only.Default value:
false
You can enable sequential mode using one of the following options:
-
Enable the sequential mode system property:
drools.sequential=true
-
Enable sequential mode while creating the KIE base programmatically:
KieServices ks = KieServices.Factory.get(); KieBaseConfiguration kieBaseConf = ks.newKieBaseConfiguration(); kieBaseConf.setOption(SequentialOption.YES); KieBase kieBase = kieContainer.newKieBase(kieBaseConf);
-
Enable sequential mode in the KIE module descriptor file (
kmodule.xml
) for a specific Drools project:<kmodule> ... <kbase name="KBase2" default="false" sequential="true" packages="org.domain.pkg2, org.domain.pkg3" includes="KBase1"> ... </kbase> ... </kmodule>
-
3.4.3. Sequential mode in Phreak
Sequential mode is an advanced rule base configuration in the Drools engine, supported by Phreak, that enables the Drools engine to evaluate rules one time in the order that they are listed in the Drools engine agenda without regard to changes in the working memory. In sequential mode, the Drools engine ignores any insert
, modify
, or update
statements in rules and executes rules in a single sequence. As a result, rule execution may be faster in sequential mode, but important updates may not be applied to your rules.
Sequential mode applies to only stateless KIE sessions because stateful KIE sessions inherently use data from previously invoked KIE sessions. If you use a stateless KIE session and you want the execution of rules to influence subsequent rules in the agenda, then do not enable sequential mode. Sequential mode is disabled by default in the Drools engine.
To enable sequential mode, use one of the following options:
-
Set the system property
drools.sequential
totrue
. -
Enable sequential mode while creating the KIE base programmatically:
KieServices ks = KieServices.Factory.get(); KieBaseConfiguration kieBaseConf = ks.newKieBaseConfiguration(); kieBaseConf.setOption(SequentialOption.YES); KieBase kieBase = kieContainer.newKieBase(kieBaseConf);
-
Enable sequential mode in the KIE module descriptor file (
kmodule.xml
) for a specific Drools project:<kmodule> ... <kbase name="KBase2" default="false" sequential="true" packages="org.domain.pkg2, org.domain.pkg3" includes="KBase1"> ... </kbase> ... </kmodule>
To configure sequential mode to use a dynamic agenda, use one of the following options:
-
Set the system property
drools.sequential.agenda
todynamic
. -
Set the sequential agenda option while creating the KIE base programmatically:
KieServices ks = KieServices.Factory.get(); KieBaseConfiguration kieBaseConf = ks.newKieBaseConfiguration(); kieBaseConf.setOption(SequentialAgendaOption.DYNAMIC); KieBase kieBase = kieContainer.newKieBase(kieBaseConf);
When you enable sequential mode, the Drools engine evaluates rules in the following way:
-
Rules are ordered by salience and position in the rule set.
-
An element for each possible rule match is created. The element position indicates the execution order.
-
Node memory is disabled, with the exception of the right-input object memory.
-
The left-input adapter node propagation is disconnected and the object with the node is referenced in a
Command
object. TheCommand
object is added to a list in the working memory for later execution. -
All objects are asserted, and then the list of
Command
objects is checked and executed. -
All matches that result from executing the list are added to elements based on the sequence number of the rule.
-
The elements that contain matches are executed in a sequence. If you set a maximum number of rule executions, the Drools engine activates no more than that number of rules in the agenda for execution.
In sequential mode, the LeftInputAdapterNode
node creates a Command
object and adds it to a list in the working memory of the Drools engine. This Command
object contains references to the LeftInputAdapterNode
node and the propagated object. These references stop any left-input propagations at insertion time so that the right-input propagation never needs to attempt to join the left inputs. The references also avoid the need for the left-input memory.
All nodes have their memory turned off, including the left-input tuple memory, but excluding the right-input object memory. After all the assertions are finished and the right-input memory of all the objects is populated, the Drools engine iterates over the list of LeftInputAdatperNode
Command
objects. The objects propagate down the network, attempting to join the right-input objects, but they are not retained in the left input.
The agenda with a priority queue to schedule the tuples is replaced by an element for each rule. The sequence number of the RuleTerminalNode
node indicates the element where to place the match. After all Command
objects have finished, the elements are checked and existing matches are executed. To improve performance, the first and the last populated cell in the elements are retained.
When the network is constructed, each RuleTerminalNode
node receives a sequence number based on its salience number and the order in which it was added to the network.
The right-input node memories are typically hash maps for fast object deletion. Because object deletions are not supported, Phreak uses an object list when the values of the object are not indexed. For a large number of objects, indexed hash maps provide a performance increase. If an object has only a few instances, Phreak uses an object list instead of an index.
3.5. Complex event processing (CEP)
In Drools, an event is a record of a significant change of state in the application domain at a point in time. Depending on how the domain is modeled, the change of state may be represented by a single event, multiple atomic events, or hierarchies of correlated events. From a complex event processing (CEP) perspective, an event is a type of fact or object that occurs at a specific point in time, and a business rule is a definition of how to react to the data from that fact or object. For example, in a stock broker application, a change in security prices, a change in ownership from seller to buyer, or a change in an account holder’s balance are all considered to be events because a change has occurred in the state of the application domain at a given time.
The Drools engine in Drools uses complex event processing (CEP) to detect and process multiple events within a collection of events, to uncover relationships that exist between events, and to infer new data from the events and their relationships.
CEP use cases share several requirements and goals with business rule use cases.
From a business perspective, business rule definitions are often defined based on the occurrence of scenarios triggered by events. In the following examples, events form the basis of business rules:
-
In an algorithmic trading application, a rule performs an action if the security price increases by X percent above the day opening price. The price increases are denoted by events on a stock trading application.
-
In a monitoring application, a rule performs an action if the temperature in the server room increases X degrees in Y minutes. The sensor readings are denoted by events.
From a technical perspective, business rule evaluation and CEP have the following key similarities:
-
Both business rule evaluation and CEP require seamless integration with the enterprise infrastructure and applications. This is particularly important with life-cycle management, auditing, and security.
-
Both business rule evaluation and CEP have functional requirements such as pattern matching, and non-functional requirements such as response time limits and query-rule explanations.
CEP scenarios have the following key characteristics:
-
Scenarios usually process large numbers of events, but only a small percentage of the events are relevant.
-
Events are usually immutable and represent a record of change in state.
-
Rules and queries run against events and must react to detected event patterns.
-
Related events usually have a strong temporal relationship.
-
Individual events are not prioritized. The CEP system prioritizes patterns of related events and the relationships between them.
-
Events usually need to be composed and aggregated.
Given these common CEP scenario characteristics, the CEP system in Drools supports the following features and functions to optimize event processing:
-
Event processing with proper semantics
-
Event detection, correlation, aggregation, and composition
-
Event stream processing
-
Temporal constraints to model the temporal relationships between events
-
Sliding windows of significant events
-
Session-scoped unified clock
-
Required volumes of events for CEP use cases
-
Reactive rules
-
Adapters for event input into the Drools engine (pipeline)
3.5.1. Events in complex event processing
In Drools, an event is a record of a significant change of state in the application domain at a point in time. Depending on how the domain is modeled, the change of state may be represented by a single event, multiple atomic events, or hierarchies of correlated events. From a complex event processing (CEP) perspective, an event is a type of fact or object that occurs at a specific point in time, and a business rule is a definition of how to react to the data from that fact or object. For example, in a stock broker application, a change in security prices, a change in ownership from seller to buyer, or a change in an account holder’s balance are all considered to be events because a change has occurred in the state of the application domain at a given time.
Events have the following key characteristics:
-
Are immutable: An event is a record of change that has occurred at some time in the past and cannot be changed.
The Drools engine does not enforce immutability on the Java objects that represent events. This behavior makes event data enrichment possible. Your application should be able to populate unpopulated event attributes, and these attributes are used by the Drools engine to enrich the event with inferred data. However, you should not change event attributes that have already been populated.
-
Have strong temporal constraints: Rules involving events usually require the correlation of multiple events that occur at different points in time relative to each other.
-
Have managed life cycles: Because events are immutable and have temporal constraints, they are usually only relevant for a specified period of time. This means that the Drools engine can automatically manage the life cycle of events.
-
Can use sliding windows: You can define sliding windows of time or length with events. A sliding time window is a specified period of time during which events can be processed. A sliding length window is a specified number of events that can be processed.
3.5.2. Declaring facts as events
You can declare facts as events in your Java class or DRL rule file so that the Drools engine handles the facts as events during complex event processing. You can declare the facts as interval-based events or point-in-time events. Interval-based events have a duration time and persist in the working memory of the Drools engine until their duration time has lapsed. Point-in-time events have no duration and are essentially interval-based events with a duration of zero.
For the relevant fact type in your Java class or DRL rule file, enter the @role( event )
metadata tag and parameter. The @role
metadata tag accepts the following two values:
-
fact
: (Default) Declares the type as a regular fact -
event
: Declares the type as an event
For example, the following snippet declares that the StockPoint
fact type in a stock broker application must be handled as an event:
import some.package.StockPoint
declare StockPoint
@role( event )
end
If StockPoint
is a fact type declared in the DRL rule file instead of in a pre-existing class, you can declare the event in-line in your application code:
declare StockPoint
@role( event )
datetime : java.util.Date
symbol : String
price : double
end
3.5.3. Metadata tags for events
The Drools engine uses the following metadata tags for events that are inserted into the working memory of the Drools engine. You can change the default metadata tag values in your Java class or DRL rule file as needed.
The examples in this section that refer to the VoiceCall fact class in an example Telecom domain model
|
- @role
-
This tag determines whether a given fact type is handled as a regular fact or an event in the Drools engine during complex event processing.
Default parameter:
fact
Supported parameters:
fact
,event
@role( fact | event )
Example: Declare VoiceCall as event typedeclare VoiceCall @role( event ) end
- @timestamp
-
This tag is automatically assigned to every event in the Drools engine. By default, the time is provided by the session clock and assigned to the event when it is inserted into the working memory of the Drools engine. You can specify a custom time stamp attribute instead of the default time stamp added by the session clock.
Default parameter: The time added by the Drools engine session clock
Supported parameters: Session clock time or custom time stamp attribute
@timestamp( <attributeName> )
Example: Declare VoiceCall timestamp attributedeclare VoiceCall @role( event ) @timestamp( callDateTime ) end
- @duration
-
This tag determines the duration time for events in the Drools engine. Events can be interval-based events or point-in-time events. Interval-based events have a duration time and persist in the working memory of the Drools engine until their duration time has lapsed. Point-in-time events have no duration and are essentially interval-based events with a duration of zero. By default, every event in the Drools engine has a duration of zero. You can specify a custom duration attribute instead of the default.
Default parameter: Null (zero)
Supported parameters: Custom duration attribute
@duration( <attributeName> )
Example: Declare VoiceCall duration attributedeclare VoiceCall @role( event ) @timestamp( callDateTime ) @duration( callDuration ) end
- @expires
-
This tag determines the time duration before an event expires in the working memory of the Drools engine. By default, an event expires when the event can no longer match and activate any of the current rules. You can define an amount of time after which an event should expire. This tag definition also overrides the implicit expiration offset calculated from temporal constraints and sliding windows in the KIE base. This tag is available only when the Drools engine is running in stream mode.
Default parameter: Null (event expires after event can no longer match and activate rules)
Supported parameters: Custom
timeOffset
attribute in the format[#d][#h][#m][#s][[ms]]
@expires( <timeOffset> )
Example: Declare expiration offset for VoiceCall eventsdeclare VoiceCall @role( event ) @timestamp( callDateTime ) @duration( callDuration ) @expires( 1h35m ) end
3.5.4. Event processing modes in the Drools engine
The Drools engine runs in either cloud mode or stream mode. In cloud mode, the Drools engine processes facts as facts with no temporal constraints, independent of time, and in no particular order. In stream mode, the Drools engine processes facts as events with strong temporal constraints, in real time or near real time. Stream mode uses synchronization to make event processing possible in Drools.
- Cloud mode
-
Cloud mode is the default operating mode of the Drools engine. In cloud mode, the Drools engine treats events as an unordered cloud. Events still have time stamps, but the Drools engine running in cloud mode cannot draw relevance from the time stamp because cloud mode ignores the present time. This mode uses the rule constraints to find the matching tuples to activate and execute rules.
Cloud mode does not impose any kind of additional requirements on facts. However, because the Drools engine in this mode has no concept of time, it cannot use temporal features such as sliding windows or automatic life-cycle management. In cloud mode, events must be explicitly retracted when they are no longer needed.
The following requirements are not imposed in cloud mode:
-
No clock synchronization because the Drools engine has no notion of time
-
No ordering of events because the Drools engine processes events as an unordered cloud, against which the Drools engine match rules
You can specify cloud mode either by setting the system property in the relevant configuration files or by using the Java client API:
Set cloud mode using system propertydrools.eventProcessingMode=cloud
Set cloud mode using Java client APIimport org.kie.api.conf.EventProcessingOption; import org.kie.api.KieBaseConfiguration; import org.kie.api.KieServices.Factory; KieBaseConfiguration config = KieServices.Factory.get().newKieBaseConfiguration(); config.setOption(EventProcessingOption.CLOUD);
You can also specify cloud mode using the
eventProcessingMode="<mode>"
KIE base attribute in the KIE module descriptor file (kmodule.xml
) for a specific Drools project:Set cloud mode using projectkmodule.xml
file<kmodule> ... <kbase name="KBase2" default="false" eventProcessingMode="cloud" packages="org.domain.pkg2, org.domain.pkg3" includes="KBase1"> ... </kbase> ... </kmodule>
-
- Stream mode
-
Stream mode enables the Drools engine to process events chronologically and in real time as they are inserted into the Drools engine. In stream mode, the Drools engine synchronizes streams of events (so that events in different streams can be processed in chronological order), implements sliding windows of time or length, and enables automatic life-cycle management.
The following requirements apply to stream mode:
-
Events in each stream must be ordered chronologically.
-
A session clock must be present to synchronize event streams.
Your application does not need to enforce ordering events between streams, but using event streams that have not been synchronized may cause unexpected results. You can specify stream mode either by setting the system property in the relevant configuration files or by using the Java client API:
Set stream mode using system propertydrools.eventProcessingMode=stream
Set stream mode using Java client APIimport org.kie.api.conf.EventProcessingOption; import org.kie.api.KieBaseConfiguration; import org.kie.api.KieServices.Factory; KieBaseConfiguration config = KieServices.Factory.get().newKieBaseConfiguration(); config.setOption(EventProcessingOption.STREAM);
You can also specify stream mode using the
eventProcessingMode="<mode>"
KIE base attribute in the KIE module descriptor file (kmodule.xml
) for a specific Drools project:Set stream mode using projectkmodule.xml
file<kmodule> ... <kbase name="KBase2" default="false" eventProcessingMode="stream" packages="org.domain.pkg2, org.domain.pkg3" includes="KBase1"> ... </kbase> ... </kmodule>
-
3.5.4.1. Negative patterns in Drools engine stream mode
A negative pattern is a pattern for conditions that are not met. For example, the following DRL rule activates a fire alarm if a fire is detected and the sprinkler is not activated:
rule "Sound the alarm"
when
$f : FireDetected()
not(SprinklerActivated())
then
// Sound the alarm.
end
In cloud mode, the Drools engine assumes all facts (regular facts and events) are known in advance and evaluates negative patterns immediately. In stream mode, the Drools engine can support temporal constraints on facts to wait for a set time before activating a rule.
The same example rule in stream mode activates the fire alarm as usual, but applies a 10-second delay.
rule "Sound the alarm"
when
$f : FireDetected()
not(SprinklerActivated(this after[0s,10s] $f))
then
// Sound the alarm.
end
The following modified fire alarm rule expects one Heartbeat
event to occur every 10 seconds. If the expected event does not occur, the rule is executed. This rule uses the same type of object in both the first pattern and in the negative pattern. The negative pattern has the temporal constraint to wait 0 to 10 seconds before executing and excludes the Heartbeat
event bound to $h
so that the rule can be executed. The bound event $h
must be explicitly excluded in order for the rule to be executed because the temporal constraint [0s, …]
does not inherently exclude that event from being matched again.
rule "Sound the alarm"
when
$h: Heartbeat() from entry-point "MonitoringStream"
not(Heartbeat(this != $h, this after[0s,10s] $h) from entry-point "MonitoringStream")
then
// Sound the alarm.
end
3.5.5. Property-change settings and listeners for fact types
By default, the Drools engine does not re-evaluate all fact patterns for fact types each time a rule is triggered, but instead reacts only to modified properties that are constrained or bound inside a given pattern. For example, if a rule calls modify()
as part of the rule actions but the action does not generate new data in the KIE base, the Drools engine does not automatically re-evaluate all fact patterns because no data was modified. This property reactivity behavior prevents unwanted recursions in the KIE base and results in more efficient rule evaluation. This behavior also means that you do not always need to use the no-loop
rule attribute to avoid infinite recursion.
You can modify or disable this property reactivity behavior with the following KnowledgeBuilderConfiguration
options, and then use a property-change setting in your Java class or DRL files to fine-tune property reactivity as needed:
-
ALWAYS
: (Default) All types are property reactive, but you can disable property reactivity for a specific type by using the@classReactive
property-change setting. -
ALLOWED
: No types are property reactive, but you can enable property reactivity for a specific type by using the@propertyReactive
property-change setting. -
DISABLED
: No types are property reactive. All property-change listeners are ignored.
KnowledgeBuilderConfiguration config = KnowledgeBuilderFactory.newKnowledgeBuilderConfiguration();
config.setOption(PropertySpecificOption.ALLOWED);
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder(config);
Alternatively, you can update the drools.propertySpecific
system property in the standalone.xml
file of your Drools distribution:
<system-properties>
...
<property name="drools.propertySpecific" value="ALLOWED"/>
...
</system-properties>
The Drools engine supports the following property-change settings and listeners for fact classes or declared DRL fact types:
- @classReactive
-
If property reactivity is set to
ALWAYS
in the Drools engine (all types are property reactive), this tag disables the default property reactivity behavior for a specific Java class or a declared DRL fact type. You can use this tag if you want the Drools engine to re-evaluate all fact patterns for the specified fact type each time the rule is triggered, instead of reacting only to modified properties that are constrained or bound inside a given pattern.Example: Disable default property reactivity in a DRL type declarationdeclare Person @classReactive firstName : String lastName : String end
Example: Disable default property reactivity in a Java class@classReactive public static class Person { private String firstName; private String lastName; }
- @propertyReactive
-
If property reactivity is set to
ALLOWED
in the Drools engine (no types are property reactive unless specified), this tag enables property reactivity for a specific Java class or a declared DRL fact type. You can use this tag if you want the Drools engine to react only to modified properties that are constrained or bound inside a given pattern for the specified fact type, instead of re-evaluating all fact patterns for the fact each time the rule is triggered.Example: Enable property reactivity in a DRL type declaration (when reactivity is disabled globally)declare Person @propertyReactive firstName : String lastName : String end
Example: Enable property reactivity in a Java class (when reactivity is disabled globally)@propertyReactive public static class Person { private String firstName; private String lastName; }
- @watch
-
This tag enables property reactivity for additional properties that you specify in-line in fact patterns in DRL rules. This tag is supported only if property reactivity is set to
ALWAYS
in the Drools engine, or if property reactivity is set toALLOWED
and the relevant fact type uses the@propertyReactive
tag. You can use this tag in DRL rules to add or exclude specific properties in fact property reactivity logic.Default parameter: None
Supported parameters: Property name,
*
(all),!
(not),!*
(no properties)<factPattern> @watch ( <property> )
Example: Enable or disable property reactivity in fact patterns// Listens for changes in both `firstName` (inferred) and `lastName`: Person(firstName == $expectedFirstName) @watch( lastName ) // Listens for changes in all properties of the `Person` fact: Person(firstName == $expectedFirstName) @watch( * ) // Listens for changes in `lastName` and explicitly excludes changes in `firstName`: Person(firstName == $expectedFirstName) @watch( lastName, !firstName ) // Listens for changes in all properties of the `Person` fact except `age`: Person(firstName == $expectedFirstName) @watch( *, !age ) // Excludes changes in all properties of the `Person` fact (equivalent to using `@classReactivity` tag): Person(firstName == $expectedFirstName) @watch( !* )
The Drools engine generates a compilation error if you use the
@watch
tag for properties in a fact type that uses the@classReactive
tag (disables property reactivity) or when property reactivity is set toALLOWED
in the Drools engine and the relevant fact type does not use the@propertyReactive
tag. Compilation errors also arise if you duplicate properties in listener annotations, such as@watch( firstName, ! firstName )
. - @propertyChangeSupport
-
For facts that implement support for property changes as defined in the JavaBeans Specification, this tag enables the Drools engine to monitor changes in the fact properties.
Example: Declare property change support in JavaBeans objectdeclare Person @propertyChangeSupport end
3.5.6. Temporal operators for events
In stream mode, the Drools engine supports the following temporal operators for events that are inserted into the working memory of the Drools engine. You can use these operators to define the temporal reasoning behavior of the events that you declare in your Java class or DRL rule file. Temporal operators are not supported when the Drools engine is running in cloud mode.
-
after
-
before
-
coincides
-
during
-
includes
-
finishes
-
finished by
-
meets
-
met by
-
overlaps
-
overlapped by
-
starts
-
started by
- after
-
This operator specifies if the current event occurs after the correlated event. This operator can also define an amount of time after which the current event can follow the correlated event, or a delimiting time range during which the current event can follow the correlated event.
For example, the following pattern matches if
$eventA
starts between 3 minutes and 30 seconds and 4 minutes after$eventB
finishes. If$eventA
starts earlier than 3 minutes and 30 seconds after$eventB
finishes, or later than 4 minutes after$eventB
finishes, then the pattern is not matched.$eventA : EventA(this after[3m30s, 4m] $eventB)
You can also express this operator in the following way:
3m30s <= $eventA.startTimestamp - $eventB.endTimeStamp <= 4m
The
after
operator supports up to two parameter values:-
If two values are defined, the interval starts on the first value (3 minutes and 30 seconds in the example) and ends on the second value (4 minutes in the example).
-
If only one value is defined, the interval starts on the provided value and runs indefinitely with no end time.
-
If no value is defined, the interval starts at 1 millisecond and runs indefinitely with no end time.
The
after
operator also supports negative time ranges:$eventA : EventA(this after[-3m30s, -2m] $eventB)
If the first value is greater than the second value, the Drools engine automatically reverses them. For example, the following two patterns are interpreted by the Drools engine in the same way:
$eventA : EventA(this after[-3m30s, -2m] $eventB) $eventA : EventA(this after[-2m, -3m30s] $eventB)
-
- before
-
This operator specifies if the current event occurs before the correlated event. This operator can also define an amount of time before which the current event can precede the correlated event, or a delimiting time range during which the current event can precede the correlated event.
For example, the following pattern matches if
$eventA
finishes between 3 minutes and 30 seconds and 4 minutes before$eventB
starts. If$eventA
finishes earlier than 3 minutes and 30 seconds before$eventB
starts, or later than 4 minutes before$eventB
starts, then the pattern is not matched.$eventA : EventA(this before[3m30s, 4m] $eventB)
You can also express this operator in the following way:
3m30s <= $eventB.startTimestamp - $eventA.endTimeStamp <= 4m
The
before
operator supports up to two parameter values:-
If two values are defined, the interval starts on the first value (3 minutes and 30 seconds in the example) and ends on the second value (4 minutes in the example).
-
If only one value is defined, the interval starts on the provided value and runs indefinitely with no end time.
-
If no value is defined, the interval starts at 1 millisecond and runs indefinitely with no end time.
The
before
operator also supports negative time ranges:$eventA : EventA(this before[-3m30s, -2m] $eventB)
If the first value is greater than the second value, the Drools engine automatically reverses them. For example, the following two patterns are interpreted by the Drools engine in the same way:
$eventA : EventA(this before[-3m30s, -2m] $eventB) $eventA : EventA(this before[-2m, -3m30s] $eventB)
-
- coincides
-
This operator specifies if the two events occur at the same time, with the same start and end times.
For example, the following pattern matches if both the start and end time stamps of
$eventA
and$eventB
are identical:$eventA : EventA(this coincides $eventB)
The
coincides
operator supports up to two parameter values for the distance between the event start and end times, if they are not identical:-
If only one parameter is given, the parameter is used to set the threshold for both the start and end times of both events.
-
If two parameters are given, the first is used as a threshold for the start time and the second is used as a threshold for the end time.
The following pattern uses start and end time thresholds:
$eventA : EventA(this coincides[15s, 10s] $eventB)
The pattern matches if the following conditions are met:
abs($eventA.startTimestamp - $eventB.startTimestamp) <= 15s && abs($eventA.endTimestamp - $eventB.endTimestamp) <= 10s
The Drools engine does not support negative intervals for the coincides
operator. If you use negative intervals, the Drools engine generates an error. -
- during
-
This operator specifies if the current event occurs within the time frame of when the correlated event starts and ends. The current event must start after the correlated event starts and must end before the correlated event ends. (With the
coincides
operator, the start and end times are the same or nearly the same.)For example, the following pattern matches if
$eventA
starts after$eventB
starts and ends before$eventB
ends:$eventA : EventA(this during $eventB)
You can also express this operator in the following way:
$eventB.startTimestamp < $eventA.startTimestamp <= $eventA.endTimestamp < $eventB.endTimestamp
The
during
operator supports one, two, or four optional parameters:-
If one value is defined, this value is the maximum distance between the start times of the two events and the maximum distance between the end times of the two events.
-
If two values are defined, these values are a threshold between which the current event start time and end time must occur in relation to the correlated event start and end times.
For example, if the values are
5s
and10s
, the current event must start between 5 and 10 seconds after the correlated event starts and must end between 5 and 10 seconds before the correlated event ends. -
If four values are defined, the first and second values are the minimum and maximum distances between the start times of the events, and the third and fourth values are the minimum and maximum distances between the end times of the two events.
-
- includes
-
This operator specifies if the correlated event occurs within the time frame of when the current event occurs. The correlated event must start after the current event starts and must end before the current event ends. (The behavior of this operator is the reverse of the
during
operator behavior.)For example, the following pattern matches if
$eventB
starts after$eventA
starts and ends before$eventA
ends:$eventA : EventA(this includes $eventB)
You can also express this operator in the following way:
$eventA.startTimestamp < $eventB.startTimestamp <= $eventB.endTimestamp < $eventA.endTimestamp
The
includes
operator supports one, two, or four optional parameters:-
If one value is defined, this value is the maximum distance between the start times of the two events and the maximum distance between the end times of the two events.
-
If two values are defined, these values are a threshold between which the correlated event start time and end time must occur in relation to the current event start and end times.
For example, if the values are
5s
and10s
, the correlated event must start between 5 and 10 seconds after the current event starts and must end between 5 and 10 seconds before the current event ends. -
If four values are defined, the first and second values are the minimum and maximum distances between the start times of the events, and the third and fourth values are the minimum and maximum distances between the end times of the two events.
-
- finishes
-
This operator specifies if the current event starts after the correlated event but both events end at the same time.
For example, the following pattern matches if
$eventA
starts after$eventB
starts and ends at the same time when$eventB
ends:$eventA : EventA(this finishes $eventB)
You can also express this operator in the following way:
$eventB.startTimestamp < $eventA.startTimestamp && $eventA.endTimestamp == $eventB.endTimestamp
The
finishes
operator supports one optional parameter that sets the maximum time allowed between the end times of the two events:$eventA : EventA(this finishes[5s] $eventB)
This pattern matches if these conditions are met:
$eventB.startTimestamp < $eventA.startTimestamp && abs($eventA.endTimestamp - $eventB.endTimestamp) <= 5s
The Drools engine does not support negative intervals for the finishes
operator. If you use negative intervals, the Drools engine generates an error. - finished by
-
This operator specifies if the correlated event starts after the current event but both events end at the same time. (The behavior of this operator is the reverse of the
finishes
operator behavior.)For example, the following pattern matches if
$eventB
starts after$eventA
starts and ends at the same time when$eventA
ends:$eventA : EventA(this finishedby $eventB)
You can also express this operator in the following way:
$eventA.startTimestamp < $eventB.startTimestamp && $eventA.endTimestamp == $eventB.endTimestamp
The
finished by
operator supports one optional parameter that sets the maximum time allowed between the end times of the two events:$eventA : EventA(this finishedby[5s] $eventB)
This pattern matches if these conditions are met:
$eventA.startTimestamp < $eventB.startTimestamp && abs($eventA.endTimestamp - $eventB.endTimestamp) <= 5s
The Drools engine does not support negative intervals for the finished by
operator. If you use negative intervals, the Drools engine generates an error. - meets
-
This operator specifies if the current event ends at the same time when the correlated event starts.
For example, the following pattern matches if
$eventA
ends at the same time when$eventB
starts:$eventA : EventA(this meets $eventB)
You can also express this operator in the following way:
abs($eventB.startTimestamp - $eventA.endTimestamp) == 0
The
meets
operator supports one optional parameter that sets the maximum time allowed between the end time of the current event and the start time of the correlated event:$eventA : EventA(this meets[5s] $eventB)
This pattern matches if these conditions are met:
abs($eventB.startTimestamp - $eventA.endTimestamp) <= 5s
The Drools engine does not support negative intervals for the meets
operator. If you use negative intervals, the Drools engine generates an error. - met by
-
This operator specifies if the correlated event ends at the same time when the current event starts. (The behavior of this operator is the reverse of the
meets
operator behavior.)For example, the following pattern matches if
$eventB
ends at the same time when$eventA
starts:$eventA : EventA(this metby $eventB)
You can also express this operator in the following way:
abs($eventA.startTimestamp - $eventB.endTimestamp) == 0
The
met by
operator supports one optional parameter that sets the maximum distance between the end time of the correlated event and the start time of the current event:$eventA : EventA(this metby[5s] $eventB)
This pattern matches if these conditions are met:
abs($eventA.startTimestamp - $eventB.endTimestamp) <= 5s
The Drools engine does not support negative intervals for the met by
operator. If you use negative intervals, the Drools engine generates an error. - overlaps
-
This operator specifies if the current event starts before the correlated event starts and it ends during the time frame that the correlated event occurs. The current event must end between the start and end times of the correlated event.
For example, the following pattern matches if
$eventA
starts before$eventB
starts and then ends while$eventB
occurs, before$eventB
ends:$eventA : EventA(this overlaps $eventB)
The
overlaps
operator supports up to two parameters:-
If one parameter is defined, the value is the maximum distance between the start time of the correlated event and the end time of the current event.
-
If two parameters are defined, the values are the minimum distance (first value) and the maximum distance (second value) between the start time of the correlated event and the end time of the current event.
-
- overlapped by
-
This operator specifies if the correlated event starts before the current event starts and it ends during the time frame that the current event occurs. The correlated event must end between the start and end times of the current event. (The behavior of this operator is the reverse of the
overlaps
operator behavior.)For example, the following pattern matches if
$eventB
starts before$eventA
starts and then ends while$eventA
occurs, before$eventA
ends:$eventA : EventA(this overlappedby $eventB)
The
overlapped by
operator supports up to two parameters:-
If one parameter is defined, the value is the maximum distance between the start time of the current event and the end time of the correlated event.
-
If two parameters are defined, the values are the minimum distance (first value) and the maximum distance (second value) between the start time of the current event and the end time of the correlated event.
-
- starts
-
This operator specifies if the two events start at the same time but the current event ends before the correlated event ends.
For example, the following pattern matches if
$eventA
and$eventB
start at the same time, and$eventA
ends before$eventB
ends:$eventA : EventA(this starts $eventB)
You can also express this operator in the following way:
$eventA.startTimestamp == $eventB.startTimestamp && $eventA.endTimestamp < $eventB.endTimestamp
The
starts
operator supports one optional parameter that sets the maximum distance between the start times of the two events:$eventA : EventA(this starts[5s] $eventB)
This pattern matches if these conditions are met:
abs($eventA.startTimestamp - $eventB.startTimestamp) <= 5s && $eventA.endTimestamp < $eventB.endTimestamp
The Drools engine does not support negative intervals for the starts
operator. If you use negative intervals, the Drools engine generates an error. - started by
-
This operator specifies if the two events start at the same time but the correlated event ends before the current event ends. (The behavior of this operator is the reverse of the
starts
operator behavior.)For example, the following pattern matches if
$eventA
and$eventB
start at the same time, and$eventB
ends before$eventA
ends:$eventA : EventA(this startedby $eventB)
You can also express this operator in the following way:
$eventA.startTimestamp == $eventB.startTimestamp && $eventA.endTimestamp > $eventB.endTimestamp
The
started by
operator supports one optional parameter that sets the maximum distance between the start times of the two events:$eventA : EventA( this starts[5s] $eventB)
This pattern matches if these conditions are met:
abs( $eventA.startTimestamp - $eventB.startTimestamp ) <= 5s && $eventA.endTimestamp > $eventB.endTimestamp
The Drools engine does not support negative intervals for the started by
operator. If you use negative intervals, the Drools engine generates an error.
3.5.7. Session clock implementations in the Drools engine
During complex event processing, events in the Drools engine may have temporal constraints and therefore require a session clock that provides the current time. For example, if a rule needs to determine the average price of a given stock over the last 60 minutes, the Drools engine must be able to compare the stock price event time stamp with the current time in the session clock.
The Drools engine supports a real-time clock and a pseudo clock. You can use one or both clock types depending on the scenario:
-
Rules testing: Testing requires a controlled environment, and when the tests include rules with temporal constraints, you must be able to control the input rules and facts and the flow of time.
-
Regular execution: The Drools engine reacts to events in real time and therefore requires a real-time clock.
-
Special environments: Specific environments may have specific time control requirements. For example, clustered environments may require clock synchronization or Java Enterprise Edition (JEE) environments may require a clock provided by the application server.
-
Rules replay or simulation: In order to replay or simulate scenarios, the application must be able to control the flow of time.
Consider your environment requirements as you decide whether to use a real-time clock or pseudo clock in the Drools engine.
- Real-time clock
-
The real-time clock is the default clock implementation in the Drools engine and uses the system clock to determine the current time for time stamps. To configure the Drools engine to use the real-time clock, set the KIE session configuration parameter to
realtime
:Configure real-time clock in KIE sessionimport org.kie.api.KieServices.Factory; import org.kie.api.runtime.conf.ClockTypeOption; import org.kie.api.runtime.KieSessionConfiguration; KieSessionConfiguration config = KieServices.Factory.get().newKieSessionConfiguration(); config.setOption(ClockTypeOption.get("realtime"));
- Pseudo clock
-
The pseudo clock implementation in the Drools engine is helpful for testing temporal rules and it can be controlled by the application. To configure the Drools engine to use the pseudo clock, set the KIE session configuration parameter to
pseudo
:Configure pseudo clock in KIE sessionimport org.kie.api.runtime.conf.ClockTypeOption; import org.kie.api.runtime.KieSessionConfiguration; import org.kie.api.KieServices.Factory; KieSessionConfiguration config = KieServices.Factory.get().newKieSessionConfiguration(); config.setOption(ClockTypeOption.get("pseudo"));
You can also use additional configurations and fact handlers to control the pseudo clock:
Control pseudo clock behavior in KIE sessionimport java.util.concurrent.TimeUnit; import org.kie.api.runtime.KieSessionConfiguration; import org.kie.api.KieServices.Factory; import org.kie.api.runtime.KieSession; import org.drools.core.time.SessionPseudoClock; import org.kie.api.runtime.rule.FactHandle; import org.kie.api.runtime.conf.ClockTypeOption; KieSessionConfiguration conf = KieServices.Factory.get().newKieSessionConfiguration(); conf.setOption( ClockTypeOption.get("pseudo")); KieSession session = kbase.newKieSession(conf, null); SessionPseudoClock clock = session.getSessionClock(); // While inserting facts, advance the clock as necessary. FactHandle handle1 = session.insert(tick1); clock.advanceTime(10, TimeUnit.SECONDS); FactHandle handle2 = session.insert(tick2); clock.advanceTime(30, TimeUnit.SECONDS); FactHandle handle3 = session.insert(tick3);
3.5.8. Event streams and entry points
The Drools engine can process high volumes of events in the form of event streams. In DRL rule declarations, a stream is also known as an entry point. When you declare an entry point in a DRL rule or Java application, the Drools engine, at compile time, identifies and creates the proper internal structures to use data from only that entry point to evaluate that rule.
Facts from one entry point, or stream, can join facts from any other entry point in addition to facts already in the working memory of the Drools engine. Facts always remain associated with the entry point through which they entered the Drools engine. Facts of the same type can enter the Drools engine through several entry points, but facts that enter the Drools engine through entry point A can never match a pattern from entry point B.
Event streams have the following characteristics:
-
Events in the stream are ordered by time stamp. The time stamps may have different semantics for different streams, but they are always ordered internally.
-
Event streams usually have a high volume of events.
-
Atomic events in streams are usually not useful individually, only collectively in a stream.
-
Event streams can be homogeneous and contain a single type of event, or heterogeneous and contain events of different types.
3.5.8.1. Declaring entry points for rule data
You can declare an entry point (event stream) for events so that the Drools engine uses data from only that entry point to evaluate the rules. You can declare an entry point either implicitly by referencing it in DRL rules or explicitly in your Java application.
Use one of the following methods to declare the entry point:
-
In the DRL rule file, specify
from entry-point "<name>"
for the inserted fact:Authorize withdrawal rule with "ATM Stream" entry pointrule "Authorize withdrawal" when WithdrawRequest($ai : accountId, $am : amount) from entry-point "ATM Stream" CheckingAccount(accountId == $ai, balance > $am) then // Authorize withdrawal. end
Apply fee rule with "Branch Stream" entry pointrule "Apply fee on withdraws on branches" when WithdrawRequest($ai : accountId, processed == true) from entry-point "Branch Stream" CheckingAccount(accountId == $ai) then // Apply a $2 fee on the account. end
Both example DRL rules from a banking application insert the event
WithdrawalRequest
with the factCheckingAccount
, but from different entry points. At run time, the Drools engine evaluates theAuthorize withdrawal
rule using data from only the"ATM Stream"
entry point, and evaluates theApply fee
rule using data from only the"Branch Stream"
entry point. Any events inserted into the"ATM Stream"
can never match patterns for the"Apply fee"
rule, and any events inserted into the"Branch Stream"
can never match patterns for the"Authorize withdrawal rule"
. -
In the Java application code, use the
getEntryPoint()
method to specify and obtain anEntryPoint
object and insert facts into that entry point accordingly:Java application code with EntryPoint object and inserted factsimport org.kie.api.runtime.KieSession; import org.kie.api.runtime.rule.EntryPoint; // Create your KIE base and KIE session as usual. KieSession session = ... // Create a reference to the entry point. EntryPoint atmStream = session.getEntryPoint("ATM Stream"); // Start inserting your facts into the entry point. atmStream.insert(aWithdrawRequest);
Any DRL rules that specify
from entry-point "ATM Stream"
are then evaluated based on the data in this entry point only.
3.5.9. Sliding windows of time or length
In stream mode, the Drools engine can process events from a specified sliding window of time or length. A sliding time window is a specified period of time during which events can be processed. A sliding length window is a specified number of events that can be processed. When you declare a sliding window in a DRL rule or Java application, the Drools engine, at compile time, identifies and creates the proper internal structures to use data from only that sliding window to evaluate that rule.
For example, the following DRL rule snippets instruct the Drools engine to process only the stock points from the last 2 minutes (sliding time window) or to process only the last 10 stock points (sliding length window):
StockPoint() over window:time(2m)
StockPoint() over window:length(10)
3.5.9.1. Declaring sliding windows for rule data
You can declare a sliding window of time (flow of time) or length (number of occurrences) for events so that the Drools engine uses data from only that window to evaluate the rules.
In the DRL rule file, specify over window:<time_or_length>(<value>)
for the inserted fact.
For example, the following two DRL rules activate a fire alarm based on an average temperature. However, the first rule uses a sliding time window to calculate the average over the last 10 minutes while the second rule uses a sliding length window to calculate the average over the last one hundred temperature readings.
rule "Sound the alarm if temperature rises above threshold"
when
TemperatureThreshold($max : max)
Number(doubleValue > $max) from accumulate(
SensorReading($temp : temperature) over window:time(10m),
average($temp))
then
// Sound the alarm.
end
rule "Sound the alarm if temperature rises above threshold"
when
TemperatureThreshold($max : max)
Number(doubleValue > $max) from accumulate(
SensorReading($temp : temperature) over window:length(100),
average($temp))
then
// Sound the alarm.
end
The Drools engine discards any SensorReading
events that are more than 10 minutes old or that are not part of the last one hundred readings, and continues recalculating the average as the minutes or readings "slide" forward in real time.
The Drools engine does not automatically remove outdated events from the KIE session because other rules without sliding window declarations might depend on those events. The Drools engine stores events in the KIE session until the events expire either by explicit rule declarations or by implicit reasoning within the Drools engine based on inferred data in the KIE base.
3.5.10. Memory management for events
In stream mode, the Drools engine uses automatic memory management to maintain events that are stored in KIE sessions. The Drools engine can retract from a KIE session any events that no longer match any rule due to their temporal constraints and release any resources held by the retracted events.
The Drools engine uses either explicit or inferred expiration to retract outdated events:
-
Explicit expiration: The Drools engine removes events that are explicitly set to expire in rules that declare the
@expires
tag:DRL rule snippet with explicit expirationdeclare StockPoint @expires( 30m ) end
This example rule sets any
StockPoint
events to expire after 30 minutes and to be removed from the KIE session if no other rules use the events. -
Inferred expiration: The Drools engine can calculate the expiration offset for a given event implicitly by analyzing the temporal constraints in the rules:
DRL rule with temporal constraintsrule "Correlate orders" when $bo : BuyOrder($id : id) $ae : AckOrder(id == $id, this after[0,10s] $bo) then // Perform an action. end
For this example rule, the Drools engine automatically calculates that whenever a
BuyOrder
event occurs, the Drools engine needs to store the event for up to 10 seconds and wait for the matchingAckOrder
event. After 10 seconds, the Drools engine infers the expiration and removes the event from the KIE session. AnAckOrder
event can only match an existingBuyOrder
event, so the Drools engine infers the expiration if no match occurs and removes the event immediately.The Drools engine analyzes the entire KIE base to find the offset for every event type and to ensure that no other rules use the events that are pending removal. Whenever an implicit expiration clashes with an explicit expiration value, the Drools engine uses the greater time frame of the two to store the event longer.
3.6. Drools engine queries and live queries
You can use queries with the Drools engine to retrieve fact sets based on fact patterns as they are used in rules. The patterns might also use optional parameters.
To use queries with the Drools engine, you add the query definitions in DRL files and then obtain the matching results in your application code. While a query iterates over a result collection, you can use any identifier that is bound to the query to access the corresponding fact or fact field by calling the get()
method with the binding variable name as the argument. If the binding refers to a fact object, you can retrieve the fact handle by calling getFactHandle()
with the variable name as the parameter.
query "people under the age of 21"
$person : Person( age < 21 )
end
QueryResults results = ksession.getQueryResults( "people under the age of 21" );
System.out.println( "we have " + results.size() + " people under the age of 21" );
System.out.println( "These people are under the age of 21:" );
for ( QueryResultsRow row : results ) {
Person person = ( Person ) row.get( "person" );
System.out.println( person.getName() + "\n" );
}
Invoking queries and processing the results by iterating over the returned set can be difficult when you are monitoring changes over time. To alleviate this difficulty with ongoing queries, Drools provides live queries, which use an attached listener for change events instead of returning an iterable result set. Live queries remain open by creating a view and publishing change events for the contents of this view.
To activate a live query, start your query with parameters and monitor changes in the resulting view. You can use the dispose()
method to terminate the query and discontinue this reactive scenario.
query colors(String $color1, String $color2)
TShirt(mainColor = $color1, secondColor = $color2, $price: manufactureCost)
end
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 live query:
LiveQuery query = ksession.openLiveQuery( "colors",
new Object[] { "red", "blue" },
listener );
...
...
// Terminate the live query:
query.dispose()
For more live query examples, see Glazed Lists examples for Drools Live Queries.
3.7. Drools engine event listeners and debug logging
In Drools, you can add or remove listeners for Drools engine events, such as fact insertions and rule executions. With Drools engine event listeners, you can be notified of Drools engine activity and separate your logging and auditing work from the core of your application.
The Drools engine supports the following default event listeners for the agenda and working memory:
-
AgendaEventListener
-
WorkingMemoryEventListener
For each event listener, the Drools engine also supports the following specific events that you can specify to be monitored:
-
MatchCreatedEvent
-
MatchCancelledEvent
-
BeforeMatchFiredEvent
-
AfterMatchFiredEvent
-
AgendaGroupPushedEvent
-
AgendaGroupPoppedEvent
-
ObjectInsertEvent
-
ObjectDeletedEvent
-
ObjectUpdatedEvent
-
ProcessCompletedEvent
-
ProcessNodeLeftEvent
-
ProcessNodeTriggeredEvent
-
ProcessStartEvent
For example, the following code uses a DefaultAgendaEventListener
listener attached to a KIE session and specifies the AfterMatchFiredEvent
event to be monitored. The code prints pattern matches after the rules are executed (fired):
AfterMatchFiredEvent
events in the agendaksession.addEventListener( new DefaultAgendaEventListener() {
public void afterMatchFired(AfterMatchFiredEvent event) {
super.afterMatchFired( event );
System.out.println( event );
}
});
The Drools engine also supports the following agenda and working memory event listeners for debug logging:
-
DebugAgendaEventListener
-
DebugRuleRuntimeEventListener
These event listeners implement the same supported event-listener methods and include a debug print statement by default. You can add a specific supported event to be monitored and documented, or monitor all agenda or working memory activity.
For example, the following code uses the DebugRuleRuntimeEventListener
event listener to monitor and print all working memory events:
ksession.addEventListener( new DebugRuleRuntimeEventListener() );
3.7.1. Configuring a logging utility in the Drools engine
The Drools engine uses the Java logging API SLF4J for system logging. You can use one of the following logging utilities with the Drools engine to investigate Drools engine activity, such as for troubleshooting or data gathering:
-
Logback
-
Apache Commons Logging
-
Apache Log4j
-
java.util.logging
package
For the logging utility that you want to use, add the relevant dependency to your Maven project or save the relevant XML configuration file in the org.drools
package of your Drools distribution:
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>${logback.version}</version>
</dependency>
<configuration>
<logger name="org.drools" level="debug"/>
...
<configuration>
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<category name="org.drools">
<priority value="debug" />
</category>
...
</log4j:configuration>
If you are developing for an ultra light environment, use the slf4j-nop or slf4j-simple logger.
|
3.8. Performance tuning considerations with the Drools engine
The following key concepts or suggested practices can help you optimize Drools engine performance. These concepts are summarized in this section as a convenience and are explained in more detail in the cross-referenced documentation, where applicable. This section will expand or change as needed with new releases of Drools.
- Use sequential mode for stateless KIE sessions that do not require important Drools engine updates
-
Sequential mode is an advanced rule base configuration in the Drools engine that enables the Drools engine to evaluate rules one time in the order that they are listed in the Drools engine agenda without regard to changes in the working memory. As a result, rule execution may be faster in sequential mode, but important updates may not be applied to your rules. Sequential mode applies to stateless KIE sessions only.
To enable sequential mode, set the system property
drools.sequential
totrue
.For more information about sequential mode or other options for enabling it, see Sequential mode in Phreak.
- Use simple operations with event listeners
-
Limit the number of event listeners and the type of operations they perform. Use event listeners for simple operations, such as debug logging and setting properties. Complicated operations, such as network calls, in listeners can impede rule execution. After you finish working with a KIE session, remove the attached event listeners so that the session can be cleaned, as shown in the following example:
Example event listener removed after useListener listener = ...; StatelessKnowledgeSession ksession = createSession(); try { ksession.insert(fact); ksession.fireAllRules(); ... } finally { if (session != null) { ksession.detachListener(listener); ksession.dispose(); } }
For information about built-in event listeners and debug logging in the Drools engine, see Drools engine event listeners and debug logging.
4. Rule Language Reference
4.1. DRL (Drools Rule Language) rules
DRL (Drools Rule Language) rules are business rules that you define directly in .drl
text files. These DRL files are the source in which all other rule assets in Business Central are ultimately rendered. You can create and manage DRL files within the Business Central interface, or create them externally as part of a Maven or Java project using Red Hat CodeReady Studio or another integrated development environment (IDE). A DRL file can contain one or more rules that define at a minimum the rule conditions (when
) and actions (then
). The DRL designer in Business Central provides syntax highlighting for Java, DRL, and XML.
DRL files consist of the following components:
package
import
function // Optional
query // Optional
declare // Optional
global // Optional
rule "rule name"
// Attributes
when
// Conditions
then
// Actions
end
rule "rule2 name"
...
The following example DRL rule determines the age limit in a loan application decision service:
rule "Underage"
salience 15
agenda-group "applicationGroup"
when
$application : LoanApplication()
Applicant( age < 21 )
then
$application.setApproved( false );
$application.setExplanation( "Underage" );
end
A DRL file can contain single or multiple rules, queries, and functions, and can define resource declarations such as imports, globals, and attributes that are assigned and used by your rules and queries. The DRL package must be listed at the top of a DRL file and the rules are typically listed last. All other DRL components can follow any order.
Each rule must have a unique name within the rule package. If you use the same rule name more than once in any DRL file in the package, the rules fail to compile. Always enclose rule names with double quotation marks (rule "rule name"
) to prevent possible compilation errors, especially if you use spaces in rule names.
All data objects related to a DRL rule must be in the same project package as the DRL file in Business Central. Assets in the same package are imported by default. Existing assets in other packages can be imported with the DRL rule.
4.1.1. Packages in DRL
A package is a folder of related assets in Drools, such as data objects, DRL files, decision tables, and other asset types. A package also serves as a unique namespace for each group of rules. A single rule base can contain multiple packages. You typically store all the rules for a package in the same file as the package declaration so that the package is self-contained. However, you can import objects from other packages that you want to use in the rules.
The following example is a package name and namespace for a DRL file in a mortgage application decision service:
package org.mortgages;
The following railroad diagram shows all the components that may make up a package:
Note that a package must have a namespace and be declared using standard Java conventions for package names; i.e., no spaces, unlike rule names which allow spaces.
In terms of the order of elements, they can appear in any order in the rule file, with the exception of the package
statement, which must be at the top of the file.
In all cases, the semicolons are optional.
Notice that any rule attribute (as described the section Rule Attributes) may also be written at package level, superseding the attribute’s default value. The modified default may still be replaced by an attribute setting within a rule.
4.1.2. Import statements in DRL
Similar to import statements in Java, imports in DRL files identify the fully qualified paths and type names for any objects that you want to use in the rules. You specify the package and data object in the format packageName.objectName
, with multiple imports on separate lines. The Drools engine automatically imports classes from the Java package with the same name as the DRL package and from the package java.lang
.
The following example is an import statement for a loan application object in a mortgage application decision service:
import org.mortgages.LoanApplication;
4.1.3. Functions in DRL
Functions in DRL files put semantic code in your rule source file instead of in Java classes. Functions are especially useful if an action (then
) part of a rule is used repeatedly and only the parameters differ for each rule. Above the rules in the DRL file, you can declare the function or import a static method from a helper class as a function, and then use the function by name in an action (then
) part of the rule.
The following examples illustrate a function that is either declared or imported in a DRL file:
function String hello(String applicantName) {
return "Hello " + applicantName + "!";
}
rule "Using a function"
when
// Empty
then
System.out.println( hello( "James" ) );
end
import function my.package.applicant.hello;
rule "Using a function"
when
// Empty
then
System.out.println( hello( "James" ) );
end
4.1.4. Queries in DRL
Queries in DRL files search the working memory of the Drools engine for facts related to the rules in the DRL file. You add the query definitions in DRL files and then obtain the matching results in your application code. Queries search for a set of defined conditions and do not require when
or then
specifications. Query names are global to the KIE base and therefore must be unique among all other rule queries in the project. To return the results of a query, you construct a QueryResults
definition using ksession.getQueryResults("name")
, where "name"
is the query name. This returns a list of query results, which enable you to retrieve the objects that matched the query. You define the query and query results parameters above the rules in the DRL file.
The following example is a query definition in a DRL file for underage applicants in a mortgage application decision service, with the accompanying application code:
query "people under the age of 21"
$person : Person( age < 21 )
end
QueryResults results = ksession.getQueryResults( "people under the age of 21" );
System.out.println( "we have " + results.size() + " people under the age of 21" );
You can also iterate over the returned QueryResults
using a standard for
loop. Each element is a QueryResultsRow
that you can use to access each of the columns in the tuple.
QueryResults results = ksession.getQueryResults( "people under the age of 21" );
System.out.println( "we have " + results.size() + " people under the age of 21" );
System.out.println( "These people are under the age of 21:" );
for ( QueryResultsRow row : results ) {
Person person = ( Person ) row.get( "person" );
System.out.println( person.getName() + "\n" );
}
Support for positional syntax has been added for more compact code. By default the declared type order in the type declaration matches the argument position. But it possible to override these using the @position annotation. This allows patterns to be used with positional arguments, instead of the more verbose named arguments.
declare Cheese
name : String @position(1)
shop : String @position(2)
price : int @position(0)
end
The @Position annotation, in the org.drools.definition.type package, can be used to annotate original pojos on the classpath.
Currently only fields on classes can be annotated.
Inheritance of classes is supported, but not interfaces or methods.
The isContainedIn query below demonstrates the use of positional arguments in a pattern; Location(x, y;)
instead of Location( thing == x, location == y).
Queries can now call other queries, this combined with optional query arguments provides derivation query style backward chaining. Positional and named syntax is supported for arguments. It is also possible to mix both positional and named, but positional must come first, separated by a semi colon. Literal expressions can be passed as query arguments, but at this stage you cannot mix expressions with variables. Here is an example of a query that calls another query. Note that 'z' here will always be an 'out' variable. The '?' symbol means the query is pull only, once the results are returned you will not receive further results as the underlying data changes.
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
As previously mentioned you can use live "open" queries to reactively receive changes over time from the query results, as the underlying data it queries against changes. Notice the "look" rule calls the query without using '?'.
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
Drools supports unification for derivation queries, in short this means that arguments are optional. It is possible to call queries from Java leaving arguments unspecified using the static field org.drools.core.runtime.rule.Variable.v - note you must use 'v' and not an alternative instance of Variable. These are referred to as 'out' arguments. Note that the query itself does not declare at compile time whether an argument is in or an out, this can be defined purely at runtime on each use. The following example will return all objects contained in the office.
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.
It is also possible to use as input argument for a query both the field of a fact as in:
query contains(String $s, String $c)
$s := String( this.contains( $c ) )
end
rule PersonNamesWithA when
$p : Person()
contains( $p.name, "a"; )
then
end
and more in general any kind of valid expression like in:
query checkLength(String $s, int $l)
$s := String( length == $l )
end
rule CheckPersonNameLength when
$i : Integer()
$p : Person()
checkLength( $p.name, 1 + $i + $p.age; )
then
end
The following is not yet supported:
-
List and Map unification
-
Expression unification - pred( X, X + 1, X * Y / 7 )
4.1.5. Type declarations and metadata in DRL
Declarations in DRL files define new fact types or metadata for fact types to be used by rules in the DRL file:
-
New fact types: The default fact type in the
java.lang
package of Drools isObject
, but you can declare other types in DRL files as needed. Declaring fact types in DRL files enables you to define a new fact model directly in the Drools engine, without creating models in a lower-level language like Java. You can also declare a new type when a domain model is already built and you want to complement this model with additional entities that are used mainly during the reasoning process. -
Metadata for fact types: You can associate metadata in the format
@key(value)
with new or existing facts. Metadata can be any kind of data that is not represented by the fact attributes and is consistent among all instances of that fact type. The metadata can be queried at run time by the Drools engine and used in the reasoning process.
4.1.5.1. Type declarations without metadata in DRL
A declaration of a new fact does not require any metadata, but must include a list of attributes or fields. If a type declaration does not include identifying attributes, the Drools engine searches for an existing fact class in the classpath and raises an error if the class is missing.
The following example is a declaration of a new fact type Person
with no metadata in a DRL file:
declare Person
name : String
dateOfBirth : java.util.Date
address : Address
end
rule "Using a declared type"
when
$p : Person( name == "James" )
then // Insert Mark, who is a customer of James.
Person mark = new Person();
mark.setName( "Mark" );
insert( mark );
end
In this example, the new fact type Person
has the three attributes name
, dateOfBirth
, and address
. Each attribute has a type that can be any valid Java type, including another class that you create or a fact type that you previously declared. The dateOfBirth
attribute has the type java.util.Date
, from the Java API, and the address
attribute has the previously defined fact type Address
.
To avoid writing the fully qualified name of a class every time you declare it, you can define the full class name as part of the import
clause:
import java.util.Date
declare Person
name : String
dateOfBirth : Date
address : Address
end
When you declare a new fact type, the Drools engine generates at compile time a Java class representing the fact type. The generated Java class is a one-to-one JavaBeans mapping of the type definition.
For example, the following Java class is generated from the example Person
type declaration:
public class Person implements Serializable {
private String name;
private java.util.Date dateOfBirth;
private Address address;
// Empty constructor
public Person() {...}
// Constructor with all fields
public Person( String name, Date dateOfBirth, Address address ) {...}
// If keys are defined, constructor with keys
public Person( ...keys... ) {...}
// Getters and setters
// `equals` and `hashCode`
// `toString`
}
You can then use the generated class in your rules like any other fact, as illustrated in the previous rule example with the Person
type declaration:
rule "Using a declared type"
when
$p : Person( name == "James" )
then // Insert Mark, who is a customer of James.
Person mark = new Person();
mark.setName( "Mark" );
insert( mark );
end
4.1.5.2. Enumerative type declarations in DRL
DRL supports the declaration of enumerative types in the format declare enum <factType>
, followed by a comma-separated list of values ending with a semicolon. You can then use the enumerative list in the rules in the DRL file.
For example, the following enumerative type declaration defines days of the week for an employee scheduling rule:
declare enum DaysOfWeek
SUN("Sunday"),MON("Monday"),TUE("Tuesday"),WED("Wednesday"),THU("Thursday"),FRI("Friday"),SAT("Saturday");
fullName : String
end
rule "Using a declared Enum"
when
$emp : Employee( dayOff == DaysOfWeek.MONDAY )
then
...
end
4.1.5.3. Extended type declarations in DRL
DRL supports type declaration inheritance in the format declare <factType1> extends <factType2>
. To extend a type declared in Java by a subtype declared in DRL, you repeat the parent type in a declaration statement without any fields.
For example, the following type declarations extend a Student
type from a top-level Person
type, and a LongTermStudent
type from the Student
subtype:
import org.people.Person
declare Person end
declare Student extends Person
school : String
end
declare LongTermStudent extends Student
years : int
course : String
end
4.1.5.4. Type declarations with metadata in DRL
You can associate metadata in the format @key(value)
(the value is optional) with fact types or fact attributes. Metadata can be any kind of data that is not represented by the fact attributes and is consistent among all instances of that fact type. The metadata can be queried at run time by the Drools engine and used in the reasoning process. Any metadata that you declare before the attributes of a fact type are assigned to the fact type, while metadata that you declare after an attribute are assigned to that particular attribute.
In the following example, the two metadata attributes @author
and @dateOfCreation
are declared for the Person
fact type, and the two metadata items @key
and @maxLength
are declared for the name
attribute. The @key
metadata attribute has no required value, so the parentheses and the value are omitted.
import java.util.Date
declare Person
@author( Bob )
@dateOfCreation( 01-Feb-2009 )
name : String @key @maxLength( 30 )
dateOfBirth : Date
address : Address
end
For declarations of metadata attributes for existing types, you can identify the fully qualified class name as part of the import
clause for all declarations or as part of the individual declare
clause:
import org.drools.examples.Person
declare Person
@author( Bob )
@dateOfCreation( 01-Feb-2009 )
end
declare org.drools.examples.Person
@author( Bob )
@dateOfCreation( 01-Feb-2009 )
end
4.1.5.5. Metadata tags for fact type and attribute declarations in DRL
Although you can define custom metadata attributes in DRL declarations, the Drools engine also supports the following predefined metadata tags for declarations of fact types or fact type attributes.
The examples in this section that refer to the VoiceCall fact class in an example Telecom domain model
|
- @role
-
This tag determines whether a given fact type is handled as a regular fact or an event in the Drools engine during complex event processing.
Default parameter:
fact
Supported parameters:
fact
,event
@role( fact | event )
Example: Declare VoiceCall as event typedeclare VoiceCall @role( event ) end
- @timestamp
-
This tag is automatically assigned to every event in the Drools engine. By default, the time is provided by the session clock and assigned to the event when it is inserted into the working memory of the Drools engine. You can specify a custom time stamp attribute instead of the default time stamp added by the session clock.
Default parameter: The time added by the Drools engine session clock
Supported parameters: Session clock time or custom time stamp attribute
@timestamp( <attributeName> )
Example: Declare VoiceCall timestamp attributedeclare VoiceCall @role( event ) @timestamp( callDateTime ) end
- @duration
-
This tag determines the duration time for events in the Drools engine. Events can be interval-based events or point-in-time events. Interval-based events have a duration time and persist in the working memory of the Drools engine until their duration time has lapsed. Point-in-time events have no duration and are essentially interval-based events with a duration of zero. By default, every event in the Drools engine has a duration of zero. You can specify a custom duration attribute instead of the default.
Default parameter: Null (zero)
Supported parameters: Custom duration attribute
@duration( <attributeName> )
Example: Declare VoiceCall duration attributedeclare VoiceCall @role( event ) @timestamp( callDateTime ) @duration( callDuration ) end
- @expires
-
This tag determines the time duration before an event expires in the working memory of the Drools engine. By default, an event expires when the event can no longer match and activate any of the current rules. You can define an amount of time after which an event should expire. This tag definition also overrides the implicit expiration offset calculated from temporal constraints and sliding windows in the KIE base. This tag is available only when the Drools engine is running in stream mode.
Default parameter: Null (event expires after event can no longer match and activate rules)
Supported parameters: Custom
timeOffset
attribute in the format[#d][#h][#m][#s][[ms]]
@expires( <timeOffset> )
Example: Declare expiration offset for VoiceCall eventsdeclare VoiceCall @role( event ) @timestamp( callDateTime ) @duration( callDuration ) @expires( 1h35m ) end
- @typesafe
-
This tab determines whether a given fact type is compiled with or without type safety. By default, all type declarations are compiled with type safety enabled. You can override this behavior to type-unsafe evaluation, where all constraints are generated as MVEL constraints and executed dynamically. This is useful when dealing with collections that do not have any generics or mixed type collections.
Default parameter:
true
Supported parameters:
true
,false
@typesafe( <boolean> )
Example: Declare VoiceCall for type-unsafe evaluationdeclare VoiceCall @role( fact ) @typesafe( false ) end
- @serialVersionUID
-
This tag defines an identifying
serialVersionUID
value for a serializable class in a fact declaration. If a serializable class does not explicitly declare aserialVersionUID
, the serialization run time calculates a defaultserialVersionUID
value for that class based on various aspects of the class, as described in the Java Object Serialization Specification. However, for optimal deserialization results and for greater compatibility with serialized KIE sessions, set theserialVersionUID
as needed in the relevant class or in your DRL declarations.Default parameter: Null
Supported parameters: Custom
serialVersionUID
integer@serialVersionUID( <integer> )
Example: Declare serialVersionUID for a VoiceCall classdeclare VoiceCall @serialVersionUID( 42 ) end
- @key
-
This tag enables a fact type attribute to be used as a key identifier for the fact type. The generated class can then implement the
equals()
andhashCode()
methods to determine if two instances of the type are equal to each other. The Drools engine can also generate a constructor using all the key attributes as parameters.Default parameter: None
Supported parameters: None
<attributeDefinition> @key
Example: Declare Person type attributes as keysdeclare Person firstName : String @key lastName : String @key age : int end
For this example, the Drools engine checks the
firstName
andlastName
attributes to determine if two instances ofPerson
are equal to each other, but it does not check theage
attribute. The Drools engine also implicitly generates three constructors: one without parameters, one with the@key
fields, and one with all fields:Example constructors from the key declarationsPerson() // Empty constructor Person( String firstName, String lastName ) Person( String firstName, String lastName, int age )
You can then create instances of the type based on the key constructors, as shown in the following example:
Example instance using the key constructorPerson person = new Person( "John", "Doe" );
- @position
-
This tag determines the position of a declared fact type attribute or field in a positional argument, overriding the default declared order of attributes. You can use this tag to modify positional constraints in patterns while maintaining a consistent format in your type declarations and positional arguments. You can use this tag only for fields in classes on the classpath. If some fields in a single class use this tag and some do not, the attributes without this tag are positioned last, in the declared order. Inheritance of classes is supported, but not interfaces of methods.
Default parameter: None
Supported parameters: Any integer
<attributeDefinition> @position ( <integer> )
Example: Declare a fact type and override declared orderdeclare Person firstName : String @position( 1 ) lastName : String @position( 0 ) age : int @position( 2 ) occupation: String end
In this example, the attributes are prioritized in positional arguments in the following order:
-
lastName
-
firstName
-
age
-
occupation
In positional arguments, you do not need to specify the field name because the position maps to a known named field. For example, the argument
Person( lastName == "Doe" )
is the same asPerson( "Doe"; )
, where thelastName
field has the highest position annotation in the DRL declaration. The semicolon;
indicates that everything before it is a positional argument. You can mix positional and named arguments on a pattern by using the semicolon to separate them. Any variables in a positional argument that have not yet been bound are bound to the field that maps to that position.The following example patterns illustrate different ways of constructing positional and named arguments. The patterns have two constraints and a binding, and the semicolon differentiates the positional section from the named argument section. Variables and literals and expressions using only literals are supported in positional arguments, but not variables alone.
Example patterns with positional and named argumentsPerson( "Doe", "John", $a; ) Person( "Doe", "John"; $a : age ) Person( "Doe"; firstName == "John", $a : age ) Person( lastName == "Doe"; firstName == "John", $a : age )
Positional arguments can be classified as input arguments or output arguments. Input arguments contain a previously declared binding and constrain against that binding using unification. Output arguments generate the declaration and bind it to the field represented by the positional argument when the binding does not yet exist.
In extended type declarations, use caution when defining
@position
annotations because the attribute positions are inherited in subtypes. This inheritance can result in a mixed attribute order that can be confusing in some cases. Two fields can have the same@position
value and consecutive values do not need to be declared. If a position is repeated, the conflict is solved using inheritance, where position values in the parent type have precedence, and then using the declaration order from the first to last declaration.For example, the following extended type declarations result in mixed positional priorities:
Example extended fact type with mixed position annotationsdeclare Person firstName : String @position( 1 ) lastName : String @position( 0 ) age : int @position( 2 ) occupation: String end declare Student extends Person degree : String @position( 1 ) school : String @position( 0 ) graduationDate : Date end
In this example, the attributes are prioritized in positional arguments in the following order:
-
lastName
(position 0 in the parent type) -
school
(position 0 in the subtype) -
firstName
(position 1 in the parent type) -
degree
(position 1 in the subtype) -
age
(position 2 in the parent type) -
occupation
(first field with no position annotation) -
graduationDate
(second field with no position annotation)
-
4.1.5.6. Property-change settings and listeners for fact types
By default, the Drools engine does not re-evaluate all fact patterns for fact types each time a rule is triggered, but instead reacts only to modified properties that are constrained or bound inside a given pattern. For example, if a rule calls modify()
as part of the rule actions but the action does not generate new data in the KIE base, the Drools engine does not automatically re-evaluate all fact patterns because no data was modified. This property reactivity behavior prevents unwanted recursions in the KIE base and results in more efficient rule evaluation. This behavior also means that you do not always need to use the no-loop
rule attribute to avoid infinite recursion.
You can modify or disable this property reactivity behavior with the following KnowledgeBuilderConfiguration
options, and then use a property-change setting in your Java class or DRL files to fine-tune property reactivity as needed:
-
ALWAYS
: (Default) All types are property reactive, but you can disable property reactivity for a specific type by using the@classReactive
property-change setting. -
ALLOWED
: No types are property reactive, but you can enable property reactivity for a specific type by using the@propertyReactive
property-change setting. -
DISABLED
: No types are property reactive. All property-change listeners are ignored.
KnowledgeBuilderConfiguration config = KnowledgeBuilderFactory.newKnowledgeBuilderConfiguration();
config.setOption(PropertySpecificOption.ALLOWED);
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder(config);
Alternatively, you can update the drools.propertySpecific
system property in the standalone.xml
file of your Drools distribution:
<system-properties>
...
<property name="drools.propertySpecific" value="ALLOWED"/>
...
</system-properties>
The Drools engine supports the following property-change settings and listeners for fact classes or declared DRL fact types:
- @classReactive
-
If property reactivity is set to
ALWAYS
in the Drools engine (all types are property reactive), this tag disables the default property reactivity behavior for a specific Java class or a declared DRL fact type. You can use this tag if you want the Drools engine to re-evaluate all fact patterns for the specified fact type each time the rule is triggered, instead of reacting only to modified properties that are constrained or bound inside a given pattern.Example: Disable default property reactivity in a DRL type declarationdeclare Person @classReactive firstName : String lastName : String end
Example: Disable default property reactivity in a Java class@classReactive public static class Person { private String firstName; private String lastName; }
- @propertyReactive
-
If property reactivity is set to
ALLOWED
in the Drools engine (no types are property reactive unless specified), this tag enables property reactivity for a specific Java class or a declared DRL fact type. You can use this tag if you want the Drools engine to react only to modified properties that are constrained or bound inside a given pattern for the specified fact type, instead of re-evaluating all fact patterns for the fact each time the rule is triggered.Example: Enable property reactivity in a DRL type declaration (when reactivity is disabled globally)declare Person @propertyReactive firstName : String lastName : String end
Example: Enable property reactivity in a Java class (when reactivity is disabled globally)@propertyReactive public static class Person { private String firstName; private String lastName; }
- @watch
-
This tag enables property reactivity for additional properties that you specify in-line in fact patterns in DRL rules. This tag is supported only if property reactivity is set to
ALWAYS
in the Drools engine, or if property reactivity is set toALLOWED
and the relevant fact type uses the@propertyReactive
tag. You can use this tag in DRL rules to add or exclude specific properties in fact property reactivity logic.Default parameter: None
Supported parameters: Property name,
*
(all),!
(not),!*
(no properties)<factPattern> @watch ( <property> )
Example: Enable or disable property reactivity in fact patterns// Listens for changes in both `firstName` (inferred) and `lastName`: Person(firstName == $expectedFirstName) @watch( lastName ) // Listens for changes in all properties of the `Person` fact: Person(firstName == $expectedFirstName) @watch( * ) // Listens for changes in `lastName` and explicitly excludes changes in `firstName`: Person(firstName == $expectedFirstName) @watch( lastName, !firstName ) // Listens for changes in all properties of the `Person` fact except `age`: Person(firstName == $expectedFirstName) @watch( *, !age ) // Excludes changes in all properties of the `Person` fact (equivalent to using `@classReactivity` tag): Person(firstName == $expectedFirstName) @watch( !* )
The Drools engine generates a compilation error if you use the
@watch
tag for properties in a fact type that uses the@classReactive
tag (disables property reactivity) or when property reactivity is set toALLOWED
in the Drools engine and the relevant fact type does not use the@propertyReactive
tag. Compilation errors also arise if you duplicate properties in listener annotations, such as@watch( firstName, ! firstName )
. - @propertyChangeSupport
-
For facts that implement support for property changes as defined in the JavaBeans Specification, this tag enables the Drools engine to monitor changes in the fact properties.
Example: Declare property change support in JavaBeans objectdeclare Person @propertyChangeSupport end
4.1.5.7. Access to DRL declared types in application code
Declared types in DRL are typically used within the DRL files while Java models are typically used when the model is shared between rules and applications. Because declared types are generated at KIE base compile time, an application cannot access them until application run time. In some cases, an application needs to access and handle facts directly from the declared types, especially when the application wraps the Drools engine and provides higher-level, domain-specific user interfaces for rules management.
To handle declared types directly from the application code, you can use the org.drools.definition.type.FactType
API in Drools. Through this API, you can instantiate, read, and write fields in the declared fact types.
The following example code modifies a Person
fact type directly from an application:
import java.util.Date;
import org.kie.api.definition.type.FactType;
import org.kie.api.KieBase;
import org.kie.api.runtime.KieSession;
...
// Get a reference to a KIE base with the declared type:
KieBase kbase = ...
// Get the declared fact type:
FactType personType = kbase.getFactType("org.drools.examples", "Person");
// Create instances:
Object bob = personType.newInstance();
// Set attribute values:
personType.set(bob, "name", "Bob" );
personType.set(bob, "dateOfBirth", new Date());
personType.set(bob, "address", new Address("King's Road","London","404"));
// Insert the fact into a KIE session:
KieSession ksession = ...
ksession.insert(bob);
ksession.fireAllRules();
// Read attributes:
String name = (String) personType.get(bob, "name");
Date date = (Date) personType.get(bob, "dateOfBirth");
The API also includes other helpful methods, such as setting all the attributes at once, reading values from a Map
collection, or reading all attributes at once into a Map
collection.
Although the API behavior is similar to Java reflection, the API does not use reflection and relies on more performant accessors that are implemented with generated bytecode.
4.1.6. Global variables in DRL
Global variables in DRL files typically provide data or services for the rules, such as application services used in rule consequences, and return data from rules, such as logs or values added in rule consequences. You set the global value in the working memory of the Drools engine through a KIE session configuration or REST operation, declare the global variable above the rules in the DRL file, and then use it in an action (then
) part of the rule. For multiple global variables, use separate lines in the DRL file.
The following example illustrates a global variable list configuration for the Drools engine and the corresponding global variable definition in the DRL file:
List<String> list = new ArrayList<>();
KieSession kieSession = kiebase.newKieSession();
kieSession.setGlobal( "myGlobalList", list );
global java.util.List myGlobalList;
rule "Using a global"
when
// Empty
then
myGlobalList.add( "My global list" );
end
Do not use global variables to establish conditions in rules unless a global variable has a constant immutable value. Global variables are not inserted into the working memory of the Drools engine, so the Drools engine cannot track value changes of variables. Do not use global variables to share data between rules. Rules always reason and react to the working memory state, so if you want to pass data from rule to rule, assert the data as facts into the working memory of the Drools engine. |
A use case for a global variable might be an instance of an email service. In your integration code that is calling the Drools engine, you obtain your emailService
object and then set it in the working memory of the Drools engine. In the DRL file, you declare that you have a global of type emailService
and give it the name "email"
, and then in your rule consequences, you can use actions such as email.sendSMS(number, message)
.
If you declare global variables with the same identifier in multiple packages, then you must set all the packages with the same type so that they all reference the same global value.
4.1.7. Rule attributes in DRL
Rule attributes are additional specifications that you can add to business rules to modify rule behavior. In DRL files, you typically define rule attributes above the rule conditions and actions, with multiple attributes on separate lines, in the following format:
rule "rule_name"
// Attribute
// Attribute
when
// Conditions
then
// Actions
end
The following table lists the names and supported values of the attributes that you can assign to rules:
Attribute | Value |
---|---|
|
An integer defining the priority of the rule. Rules with a higher salience value are given higher priority when ordered in the activation queue. Example: |
|
A Boolean value. When the option is selected, the rule is enabled. When the option is not selected, the rule is disabled. Example: |
|
A string containing a date and time definition. The rule can be activated only if the current date and time is after a Example: |
|
A string containing a date and time definition. The rule cannot be activated if the current date and time is after the Example: |
|
A Boolean value. When the option is selected, the rule cannot be reactivated (looped) if a consequence of the rule re-triggers a previously met condition. When the condition is not selected, the rule can be looped in these circumstances. Example: |
|
A string identifying an agenda group to which you want to assign the rule. Agenda groups allow you to partition the agenda to provide more execution control over groups of rules. Only rules in an agenda group that has acquired a focus are able to be activated. Example: |
|
A string identifying an activation (or XOR) group to which you want to assign the rule. In activation groups, only one rule can be activated. The first rule to fire will cancel all pending activations of all rules in the activation group. Example: |
|
A long integer value defining the duration of time in milliseconds after which the rule can be activated, if the rule conditions are still met. Example: |
|
A string identifying either Example: |
|
A Quartz calendar definition for scheduling the rule. Example: |
|
A Boolean value, applicable only to rules within agenda groups. When the option is selected, the next time the rule is activated, a focus is automatically given to the agenda group to which the rule is assigned. Example: |
|
A Boolean value, applicable only to rules within rule flow groups or agenda groups. When the option is selected, the next time the ruleflow group for the rule becomes active or the agenda group for the rule receives a focus, the rule cannot be activated again until the ruleflow group is no longer active or the agenda group loses the focus. This is a stronger version of the Example: |
|
A string identifying a rule flow group. In rule flow groups, rules can fire only when the group is activated by the associated rule flow. Example: |
|
A string identifying either Example: |
4.1.7.1. Timer and calendar rule attributes in DRL
Timers and calendars are DRL rule attributes that enable you to apply scheduling and timing constraints to your DRL rules. These attributes require additional configurations depending on the use case.
The timer
attribute in DRL rules is a string identifying either int
(interval) or cron
timer definitions for scheduling a rule and supports the following formats:
timer ( int: <initial delay> <repeat interval> )
timer ( cron: <cron expression> )
// Run after a 30-second delay
timer ( int: 30s )
// Run every 5 minutes after a 30-second delay each time
timer ( int: 30s 5m )
// Run every 15 minutes
timer ( cron:* 0/15 * * * ? )
Interval timers follow the semantics of java.util.Timer
objects, with an initial delay and an optional repeat interval. Cron timers follow standard Unix cron expressions.
The following example DRL rule uses a cron timer to send an SMS text message every 15 minutes:
rule "Send SMS message 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
Generally, a rule that is controlled by a timer becomes active when the rule is triggered and the rule consequence is executed repeatedly, according to the timer settings. The execution stops when the rule condition no longer matches incoming facts. However, the way the Drools engine handles rules with timers depends on whether the Drools engine is in active mode or in passive mode.
By default, the Drools engine runs in passive mode and evaluates rules, according to the defined timer settings, when a user or an application explicitly calls fireAllRules()
. Conversely, if a user or application calls fireUntilHalt()
, the Drools engine starts in active mode and evaluates rules continually until the user or application explicitly calls halt()
.
When the Drools engine is in active mode, rule consequences are executed even after control returns from a call to fireUntilHalt()
and the Drools engine remains reactive to any changes made to the working memory. For example, removing a fact that was involved in triggering the timer rule execution causes the repeated execution to terminate, and inserting a fact so that some rule matches causes that rule to be executed. However, the Drools engine is not continually active, but is active only after a rule is executed. Therefore, the Drools engine does not react to asynchronous fact insertions until the next execution of a timer-controlled rule. Disposing a KIE session terminates all timer activity.
When the Drools engine is in passive mode, rule consequences of timed rules are evaluated only when fireAllRules()
is invoked again. However, you can change the default timer-execution behavior in passive mode by configuring the KIE session with a TimedRuleExecutionOption
option, as shown in the following example:
KieSessionConfiguration ksconf = KieServices.Factory.get().newKieSessionConfiguration();
ksconf.setOption( TimedRuleExecutionOption.YES );
KSession ksession = kbase.newKieSession(ksconf, null);
You can additionally set a FILTERED
specification on the TimedRuleExecutionOption
option that enables you to define a
callback to filter those rules, as shown in the following example:
KieSessionConfiguration ksconf = KieServices.Factory.get().newKieSessionConfiguration();
conf.setOption( new TimedRuleExecutionOption.FILTERED(new TimedRuleExecutionFilter() {
public boolean accept(Rule[] rules) {
return rules[0].getName().equals("MyRule");
}
}) );
For interval timers, you can also use an expression timer with expr
instead of int
to define both the delay and interval as an expression instead of a fixed value.
The following example DRL file declares a fact type with a delay and period that are then used in the subsequent rule with an expression timer:
declare Bean
delay : String = "30s"
period : long = 60000
end
rule "Expression timer"
timer ( expr: $d, $p )
when
Bean( $d : delay, $p : period )
then
// Actions
end
The expressions, such as $d
and $p
in this example, can use any variable defined in the pattern-matching part of the rule. The variable can be any String
value that can be parsed into a time duration or any numeric value that is internally converted in a long
value for a duration in milliseconds.
Both interval and expression timers can use the following optional parameters:
-
start
andend
: ADate
or aString
representing aDate
or along
value. The value can also be aNumber
that is transformed into a JavaDate
in the formatnew Date( ((Number) n).longValue() )
. -
repeat-limit
: An integer that defines the maximum number of repetitions allowed by the timer. If both theend
and therepeat-limit
parameters are set, the timer stops when the first of the two is reached.
start
, end
, and repeat-limit
parameterstimer (int: 30s 1h; start=3-JAN-2020, end=4-JAN-2020, repeat-limit=50)
In this example, the rule is scheduled for every hour, after a delay of 30 seconds each hour, beginning on 3 January 2020 and ending either on 4 January 2020 or when the cycle repeats 50 times.
If the system is paused (for example, the session is serialized and then later deserialized), the rule is scheduled only one time to recover from missing activations regardless of how many activations were missed during the pause, and then the rule is subsequently scheduled again to continue in sync with the timer setting.
The calendar
attribute in DRL rules is a Quartz calendar definition for scheduling a rule and supports the following format:
calendars "<definition or registered name>"
// Exclude non-business hours
calendars "* * 0-7,18-23 ? * *"
// Weekdays only, as registered in the KIE session
calendars "weekday"
You can adapt a Quartz calendar based on the Quartz calendar API and then register the calendar in the KIE session, as shown in the following example:
Calendar weekDayCal = QuartzHelper.quartzCalendarAdapter(org.quartz.Calendar quartzCal)
ksession.getCalendars().set( "weekday", weekDayCal );
You can use calendars with standard rules and with rules that use timers. The calendar attribute can contain one or more comma-separated calendar names written as String
literals.
The following example rules use both calendars and timers to schedule the rules:
rule "Weekdays are high priority"
calendars "weekday"
timer ( int:0 1h )
when
Alarm()
then
send( "priority high - we have an alarm" );
end
rule "Weekends are low priority"
calendars "weekend"
timer ( int:0 4h )
when
Alarm()
then
send( "priority low - we have an alarm" );
end
4.1.8. Rule conditions in DRL (WHEN)
The when
part of a DRL rule (also known as the Left Hand Side (LHS) of the rule) contains the conditions that must be met to execute an action. Conditions consist of a series of stated patterns and constraints, with optional bindings and supported rule condition elements (keywords), based on the available data objects in the package. For example, if a bank requires loan applicants to have over 21 years of age, then the when
condition of an "Underage"
rule would be Applicant( age < 21 )
.
DRL uses when instead of if because if is typically part of a procedural execution flow during which a condition is checked at a specific point in time. In contrast, when indicates that the condition evaluation is not limited to a specific evaluation sequence or point in time, but instead occurs continually at any time. Whenever the condition is met, the actions are executed.
|
If the when
section is empty, then the conditions are considered to be true and the actions in the then
section are executed the first time a fireAllRules()
call is made in the Drools engine. This is useful if you want to use rules to set up the Drools engine state.
The following example rule uses empty conditions to insert a fact every time the rule is executed:
rule "Always insert applicant"
when
// Empty
then // Actions to be executed once
insert( new Applicant() );
end
// The rule is internally rewritten in the following way:
rule "Always insert applicant"
when
eval( true )
then
insert( new Applicant() );
end
If rule conditions use multiple patterns with no defined keyword conjunctions (such as and
, or
, or not
), the default conjunction is and
:
rule "Underage"
when
application : LoanApplication()
Applicant( age < 21 )
then
// Actions
end
// The rule is internally rewritten in the following way:
rule "Underage"
when
application : LoanApplication()
and Applicant( age < 21 )
then
// Actions
end
4.1.8.1. Patterns and constraints
A pattern in a DRL rule condition is the segment to be matched by the Drools engine. A pattern can potentially match each fact that is inserted into the working memory of the Drools engine. A pattern can also contain constraints to further define the facts to be matched.
The railroad diagram below shows the syntax for this:
In the simplest form, with no constraints, a pattern matches a fact of the given type. In the following example, the type is Person
, so the pattern will match against all Person
objects in the working memory of the Drools engine:
Person()
The type does not need to be the actual class of some fact object. Patterns can refer to superclasses or even interfaces, potentially matching facts from many different classes. For example, the following pattern matches all objects in the working memory of the Drools engine:
Object() // Matches all objects in the working memory
The parentheses of a pattern enclose the constraints, such as the following constraint on the person’s age:
Person( age == 50 )
A constraint is an expression that returns true
or false
. Pattern constraints in DRL are essentially Java expressions with some enhancements, such as property access, and some differences, such as equals()
and !equals()
semantics for ==
and !=
(instead of the usual same
and not same
semantics).
Any JavaBeans property can be accessed directly from pattern constraints. A bean property is exposed internally using a standard JavaBeans getter that takes no arguments and returns something. For example, the age
property is written as age
in DRL instead of the getter getAge()
:
Person( age == 50 )
// This is the same as the following getter format:
Person( getAge() == 50 )
Drools uses the standard JDK Introspector
class to achieve this mapping, so it follows the standard JavaBeans specification. For optimal Drools engine performance, use the property access format, such as age
, instead of using getters explicitly, such as getAge()
.
Do not use property accessors to change the state of the object in a way that might affect the rules because the Drools engine caches the results of the match between invocations for higher efficiency. For example, do not use property accessors in the following ways:
Instead of following the second example, insert a fact that wraps the current date in the working memory and update that fact between |
However, if the getter of a property cannot be found, the compiler uses the property name as a fallback method name, without arguments:
Person( age == 50 )
// If `Person.getAge()` does not exist, the compiler uses the following syntax:
Person( age() == 50 )
You can also nest access properties in patterns, as shown in the following example. Nested properties are indexed by the Drools engine.
Person( address.houseNumber == 50 )
// This is the same as the following format:
Person( getAddress().getHouseNumber() == 50 )
In stateful KIE sessions, use nested accessors carefully because the working memory of the Drools engine is not aware of any of the nested values and does not detect when they change. Either consider the nested values immutable while any of their parent references are inserted into the working memory, or, if you want to modify a nested value, mark all of the outer facts as updated. In the previous example, when the houseNumber property changes, any Person with that Address must be marked as updated.
|
You can use any Java expression that returns a boolean
value as a constraint inside the parentheses of a pattern. Java expressions can be mixed with other expression enhancements, such as property access:
Person( age == 50 )
You can change the evaluation priority by using parentheses, as in any logical or mathematical expression:
Person( age > 100 && ( age % 10 == 0 ) )
You can also reuse Java methods in constraints, as shown in the following example:
Person( Math.round( weight / ( height * height ) ) < 25.0 )
Do not use constraints to change the state of the object in a way that might affect the rules because the Drools engine caches the results of the match between invocations for higher efficiency. Any method that is executed on a fact in the rule conditions must be a read-only method. Also, the state of a fact should not change between rule invocations unless those facts are marked as updated in the working memory on every change. For example, do not use a pattern constraint in the following ways:
|
Standard Java operator precedence applies to constraint operators in DRL, and DRL operators follow standard Java semantics except for the ==
and !=
operators.
The ==
operator uses null-safe equals()
semantics instead of the usual same
semantics. For example, the pattern Person( firstName == "John" )
is similar to java.util.Objects.equals(person.getFirstName(), "John")
, and because "John"
is not null, the pattern is also similar to "John".equals(person.getFirstName())
.
The !=
operator uses null-safe !equals()
semantics instead of the usual not same
semantics. For example, the pattern Person( firstName != "John" )
is similar to !java.util.Objects.equals(person.getFirstName(), "John")
.
If the field and the value of a constraint are of different types, the Drools engine uses type coercion to resolve the conflict and reduce compilation errors. For instance, if "ten"
is provided as a string in a numeric evaluator, a compilation error occurs, whereas "10"
is coerced to a numeric 10. In coercion, the field type always takes precedence over the value type:
Person( age == "10" ) // "10" is coerced to 10
For groups of constraints, you can use a delimiting comma ,
to use implicit and
connective semantics:
// Person is at least 50 years old and weighs at least 80 kilograms:
Person( age > 50, weight > 80 )
// Person is at least 50 years old, weighs at least 80 kilograms, and is taller than 2 meters:
Person( age > 50, weight > 80, height > 2 )
Although the && and , operators have the same semantics, they are resolved with different priorities. The && operator precedes the || operator, and both the && and || operators together precede the , operator. Use the comma operator at the top-level constraint for optimal Drools engine performance and human readability.
|
You cannot embed a comma operator in a composite constraint expression, such as in parentheses:
// Do not use the following format:
Person( ( age > 50, weight > 80 ) || height > 2 )
// Use the following format instead:
Person( ( age > 50 && weight > 80 ) || height > 2 )
4.1.8.2. Bound variables in patterns and constraints
You can bind variables to patterns and constraints to refer to matched objects in other portions of a rule. Bound variables can help you define rules more efficiently or more consistently with how you annotate facts in your data model. To differentiate more easily between variables and fields in a rule, use the standard format $variable
for variables, especially in complex rules. This convention is helpful but not required in DRL.
For example, the following DRL rule uses the variable $p
for a pattern with the Person
fact:
rule "simple rule"
when
$p : Person()
then
System.out.println( "Person " + $p );
end
Similarly, you can also bind variables to properties in pattern constraints, as shown in the following example:
// Two persons of the same age:
Person( $firstAge : age ) // Binding
Person( age == $firstAge ) // Constraint expression
Ensure that you separate constraint bindings and constraint expressions for clearer and more efficient rule definitions. Although mixed bindings and expressions are supported, they can complicate patterns and affect evaluation efficiency.
|
The Drools engine does not support bindings to the same declaration, but does support unification of arguments across several properties. While positional arguments are always processed with unification, the unification symbol :=
exists for named arguments.
The following example patterns unify the age
property across two Person
facts:
Person( $age := age )
Person( $age := age )
Unification declares a binding for the first occurrence and constrains to the same value of the bound field for sequence occurrences.
4.1.8.3. Nested constraints and inline casts
In some cases, you might need to access multiple properties of a nested object, as shown in the following example:
Person( name == "mark", address.city == "london", address.country == "uk" )
You can group these property accessors to nested objects with the syntax .( <constraints> )
for more readable rules, as shown in the following example:
Person( name == "mark", address.( city == "london", country == "uk") )
The period prefix . differentiates the nested object constraints from a method call.
|
When you work with nested objects in patterns, you can use the syntax <type>#<subtype>
to cast to a subtype and make the getters from the parent type available to the subtype. You can use either the object name or fully qualified class name, and you can cast to one or multiple subtypes, as shown in the following examples:
// Inline casting with subtype name:
Person( name == "mark", address#LongAddress.country == "uk" )
// Inline casting with fully qualified class name:
Person( name == "mark", address#org.domain.LongAddress.country == "uk" )
// Multiple inline casts:
Person( name == "mark", address#LongAddress.country#DetailedCountry.population > 10000000 )
These example patterns cast Address
to LongAddress
, and additionally to DetailedCountry
in the last example, making the parent getters available to the subtypes in each case.
You can use the instanceof
operator to infer the results of the specified type in subsequent uses of that field with the pattern, as shown in the following example:
Person( name == "mark", address instanceof LongAddress, address.country == "uk" )
If an inline cast is not possible (for example, if instanceof
returns false
), the evaluation is considered false
.
4.1.8.4. Date literal in constraints
By default, the Drools engine supports the date format dd-mmm-yyyy
. You can customize the date format, including a time format mask if needed, by providing an alternative format mask with the system property drools.dateformat="dd-mmm-yyyy hh:mm"
. You can also customize the date format by changing the language locale with the drools.defaultlanguage
and drools.defaultcountry
system properties (for example, the locale of Thailand is set as drools.defaultlanguage=th
and drools.defaultcountry=TH
).
Person( bornBefore < "27-Oct-2009" )
4.1.8.5. Auto-boxing and primitive types
Drools attempts to preserve numbers in their primitive or object wrapper form, so a variable bound to an int primitive when used in a code block or expression will no longer need manual unboxing; unlike early Drools versions where all primitives were autoboxed, requiring manual unboxing. A variable bound to an object wrapper will remain as an object; the existing JDK 1.5 and JDK 5 rules to handle auto-boxing and unboxing apply in this case. When evaluating field constraints, the system attempts to coerce one of the values into a comparable format; so a primitive is comparable to an object wrapper.
4.1.8.6. Supported operators in DRL pattern constraints
DRL supports standard Java semantics for operators in pattern constraints, with some exceptions and with some additional operators that are unique in DRL. The following list summarizes the operators that are handled differently in DRL constraints than in standard Java semantics or that are unique in DRL constraints.
.()
,#
-
Use the
.()
operator to group property accessors to nested objects, and use the#
operator to cast to a subtype in nested objects. Casting to a subtype makes the getters from the parent type available to the subtype. You can use either the object name or fully qualified class name, and you can cast to one or multiple subtypes.Example patterns with nested objects// Ungrouped property accessors: Person( name == "mark", address.city == "london", address.country == "uk" ) // Grouped property accessors: Person( name == "mark", address.( city == "london", country == "uk") )
The period prefix .
differentiates the nested object constraints from a method call.Example patterns with inline casting to a subtype// Inline casting with subtype name: Person( name == "mark", address#LongAddress.country == "uk" ) // Inline casting with fully qualified class name: Person( name == "mark", address#org.domain.LongAddress.country == "uk" ) // Multiple inline casts: Person( name == "mark", address#LongAddress.country#DetailedCountry.population > 10000000 )
!.
-
Use this operator to dereference a property in a null-safe way. The value to the left of the
!.
operator must be not null (interpreted as!= null
) in order to give a positive result for pattern matching.Example constraint with null-safe dereferencingPerson( $streetName : address!.street ) // This is internally rewritten in the following way: Person( address != null, $streetName : address.street )
[]
-
Use this operator to access a
List
value by index or aMap
value by key.Example constraints withList
andMap
access// The following format is the same as `childList(0).getAge() == 18`: Person(childList[0].age == 18) // The following format is the same as `credentialMap.get("jdoe").isValid()`: Person(credentialMap["jdoe"].valid)
<
,<=
,>
,>=
-
Use these operators on properties with natural ordering. For example, for
Date
fields, the<
operator means before, and forString
fields, the operator means alphabetically before. These properties apply only to comparable properties.Example constraints withbefore
operatorPerson( birthDate < $otherBirthDate ) Person( firstName < $otherFirstName )
==
,!=
-
Use these operators as
equals()
and!equals()
methods in constraints, instead of the usualsame
andnot same
semantics.Example constraint with null-safe equalityPerson( firstName == "John" ) // This is similar to the following formats: java.util.Objects.equals(person.getFirstName(), "John") "John".equals(person.getFirstName())
Example constraint with null-safe not equalityPerson( firstName != "John" ) // This is similar to the following format: !java.util.Objects.equals(person.getFirstName(), "John")
&&
,||
-
Use these operators to create an abbreviated combined relation condition that adds more than one restriction on a field. You can group constraints with parentheses
()
to create a recursive syntax pattern.Example constraints with abbreviated combined relation// Simple abbreviated combined relation condition using a single `&&`: Person(age > 30 && < 40) // Complex abbreviated combined relation using groupings: Person(age ((> 30 && < 40) || (> 20 && < 25))) // Mixing abbreviated combined relation with constraint connectives: Person(age > 30 && < 40 || location == "london")
Figure 57. Abbreviated combined relation conditionFigure 58. Abbreviated combined relation condition withparentheses matches
,not matches
-
Use these operators to indicate that a field matches or does not match a specified Java regular expression. Typically, the regular expression is a
String
literal, but variables that resolve to a valid regular expression are also supported. These operators apply only toString
properties. If you usematches
against anull
value, the resulting evaluation is alwaysfalse
. If you usenot matches
against anull
value, the resulting evaluation is alwaystrue
. As in Java, regular expressions that you write asString
literals must use a double backslash\\
to escape.Example constraint to match or not match a regular expressionPerson( country matches "(USA)?\\S*UK" ) Person( country not matches "(USA)?\\S*UK" )
contains
,not contains
-
Use these operators to verify whether a field that is an
Array
or aCollection
contains or does not contain a specified value. These operators apply toArray
orCollection
properties, but you can also use these operators in place ofString.contains()
and!String.contains()
constraints checks.Example constraints withcontains
andnot contains
for a Collection// Collection with a specified field: FamilyTree( countries contains "UK" ) FamilyTree( countries not contains "UK" ) // Collection with a variable: FamilyTree( countries contains $var ) FamilyTree( countries not contains $var )
Example constraints withcontains
andnot contains
for a String literal// Sting literal with a specified field: Person( fullName contains "Jr" ) Person( fullName not contains "Jr" ) // String literal with a variable: Person( fullName contains $var ) Person( fullName not contains $var )
For backward compatibility, the excludes
operator is a supported synonym fornot contains
. memberOf
,not memberOf
-
Use these operators to verify whether a field is a member of or is not a member of an
Array
or aCollection
that is defined as a variable. TheArray
orCollection
must be a variable.Example constraints withmemberOf
andnot memberOf
with a CollectionFamilyTree( person memberOf $europeanDescendants ) FamilyTree( person not memberOf $europeanDescendants )
soundslike
-
Use this operator to verify whether a word has almost the same sound, using English pronunciation, as the given value (similar to the
matches
operator). This operator uses the Soundex algorithm.Example constraint withsoundslike
// Match firstName "Jon" or "John": Person( firstName soundslike "John" )
str
-
Use this operator to verify whether a field that is a
String
starts with or ends with a specified value. You can also use this operator to verify the length of theString
.Example constraints withstr
// Verify what the String starts with: Message( routingValue str[startsWith] "R1" ) // Verify what the String ends with: Message( routingValue str[endsWith] "R2" ) // Verify the length of the String: Message( routingValue str[length] 17 )
in
,notin
-
Use these operators to specify more than one possible value to match in a constraint (compound value restriction). This functionality of compound value restriction is supported only in the
in
andnot in
operators. The second operand of these operators must be a comma-separated list of values enclosed in parentheses. You can provide values as variables, literals, return values, or qualified identifiers. These operators are internally rewritten as a list of multiple restrictions using the operators==
or!=
.Figure 59. compoundValueRestrictionExample constraints within
andnotin
Person( $color : favoriteColor ) Color( type in ( "red", "blue", $color ) ) Person( $color : favoriteColor ) Color( type notin ( "red", "blue", $color ) )
4.1.8.7. Operator precedence in DRL pattern constraints
DRL supports standard Java operator precedence for applicable constraint operators, with some exceptions and with some additional operators that are unique in DRL. The following table lists DRL operator precedence where applicable, from highest to lowest precedence:
Operator type | Operators | Notes |
---|---|---|
Nested or null-safe property access |
|
Not standard Java semantics |
|
|
Not standard Java semantics |
Constraint binding |
|
Not standard Java semantics |
Multiplicative |
|
|
Additive |
|
|
Shift |
|
|
Relational |
|
|
Equality |
|
Uses |
Non-short-circuiting |
|
|
Non-short-circuiting exclusive |
|
|
Non-short-circuiting inclusive |
|
|
Logical |
|
|
Logical |
|
|
Ternary |
|
|
Comma-separated |
|
Not standard Java semantics |
4.1.8.8. Supported rule condition elements in DRL (keywords)
DRL supports the following rule condition elements (keywords) that you can use with the patterns that you define in DRL rule conditions:
and
-
Use this to group conditional components into a logical conjunction. Infix and prefix
and
are supported. You can group patterns explicitly with parentheses()
. By default, all listed patterns are combined withand
when no conjunction is specified.Figure 60. infixAndFigure 61. prefixAndExample patterns withand
//Infix `and`: Color( colorType : type ) and Person( favoriteColor == colorType ) //Infix `and` with grouping: (Color( colorType : type ) and (Person( favoriteColor == colorType ) or Person( favoriteColor == colorType )) // Prefix `and`: (and Color( colorType : type ) Person( favoriteColor == colorType )) // Default implicit `and`: Color( colorType : type ) Person( favoriteColor == colorType )
Do not use a leading declaration binding with the
and
keyword (as you can withor
, for example). A declaration can only reference a single fact at a time, and if you use a declaration binding withand
, then whenand
is satisfied, it matches both facts and results in an error.Example misuse ofand
// Causes compile error: $person : (Person( name == "Romeo" ) and Person( name == "Juliet"))
or
-
Use this to group conditional components into a logical disjunction. Infix and prefix
or
are supported. You can group patterns explicitly with parentheses()
. You can also use pattern binding withor
, but each pattern must be bound separately.Figure 62. infixOrFigure 63. prefixOrExample patterns withor
//Infix `or`: Color( colorType : type ) or Person( favoriteColor == colorType ) //Infix `or` with grouping: (Color( colorType : type ) or (Person( favoriteColor == colorType ) and Person( favoriteColor == colorType )) // Prefix `or`: (or Color( colorType : type ) Person( favoriteColor == colorType ))
Example patterns withor
and pattern bindingpensioner : (Person( sex == "f", age > 60 ) or Person( sex == "m", age > 65 )) (or pensioner : Person( sex == "f", age > 60 ) pensioner : Person( sex == "m", age > 65 ))
The behavior of the
or
condition element is different from the connective||
operator for constraints and restrictions in field constraints. The Drools engine does not directly interpret theor
element but uses logical transformations to rewrite a rule withor
as a number of sub-rules. This process ultimately results in a rule that has a singleor
as the root node and one sub-rule for each of its condition elements. Each sub-rule is activated and executed like any normal rule, with no special behavior or interaction between the sub-rules.Therefore, consider the
or
condition element a shortcut for generating two or more similar rules that, in turn, can create multiple activations when two or more terms of the disjunction are true. exists
-
Use this to specify facts and constraints that must exist. This option is triggered on only the first match, not subsequent matches. If you use this element with multiple patterns, enclose the patterns with parentheses
()
.Figure 64. ExistsExample patterns withexists
exists Person( firstName == "John") exists (Person( firstName == "John", age == 42 )) exists (Person( firstName == "John" ) and Person( lastName == "Doe" ))
not
-
Use this to specify facts and constraints that must not exist. If you use this element with multiple patterns, enclose the patterns with parentheses
()
.Figure 65. NotExample patterns withnot
not Person( firstName == "John") not (Person( firstName == "John", age == 42 )) not (Person( firstName == "John" ) and Person( lastName == "Doe" ))
forall
-
Use this to verify whether all facts that match the first pattern match all the remaining patterns. When a
forall
construct is satisfied, the rule evaluates totrue
. This element is a scope delimiter, so it can use any previously bound variable, but no variable bound inside of it is available for use outside of it.Figure 66. ForallExample rule withforall
rule "All full-time employees have red ID badges" when forall( $emp : Employee( type == "fulltime" ) Employee( this == $emp, badgeColor = "red" ) ) then // True, all full-time employees have red ID badges. end
In this example, the rule selects all
Employee
objects whose type is"fulltime"
. For each fact that matches this pattern, the rule evaluates the patterns that follow (badge color) and if they match, the rule evaluates totrue
.To state that all facts of a given type in the working memory of the Drools engine must match a set of constraints, you can use
forall
with a single pattern for simplicity.Example rule withforall
and a single patternrule "All full-time employees have red ID badges" when forall( Employee( badgeColor = "red" ) ) then // True, all full-time employees have red ID badges. end
You can use
forall
constructs with multiple patterns or nest them with other condition elements, such as inside anot
element construct.Example rule withforall
and multiple patternsrule "All employees have health and dental care programs" when forall( $emp : Employee() HealthCare( employee == $emp ) DentalCare( employee == $emp ) ) then // True, all employees have health and dental care. end
Example rule withforall
andnot
rule "Not all employees have health and dental care" when not ( forall( $emp : Employee() HealthCare( employee == $emp ) DentalCare( employee == $emp ) ) ) then // True, not all employees have health and dental care. end
The format forall( p1 p2 p3 …)
is equivalent tonot( p1 and not( and p2 p3 … ) )
. from
-
Use this to specify a data source for a pattern. This enables the Drools engine to reason over data that is not in the working memory. The data source can be a sub-field on a bound variable or the result of a method call. The expression used to define the object source is any expression that follows regular MVEL syntax. Therefore, the
from
element enables you to easily use object property navigation, execute method calls, and access maps and collection elements.Figure 67. fromExample rule withfrom
and pattern bindingrule "Validate zipcode" when Person( $personAddress : address ) Address( zipcode == "23920W" ) from $personAddress then // Zip code is okay. end
Example rule withfrom
and a graph notationrule "Validate zipcode" when $p : Person() $a : Address( zipcode == "23920W" ) from $p.address then // Zip code is okay. end
Example rule withfrom
to iterate over all objectsrule "Apply 10% discount to all items over US$ 100 in an order" when $order : Order() $item : OrderItem( value > 100 ) from $order.items then // Apply discount to `$item`. end
For large collections of objects, instead of adding an object with a large graph that the Drools engine must iterate over frequently, add the collection directly to the KIE session and then join the collection in the condition, as shown in the following example:
when $order : Order() OrderItem( value > 100, order == $order )
Example rule withfrom
andlock-on-active
rule attributerule "Assign people in North Carolina (NC) to sales region 1" ruleflow-group "test" lock-on-active true when $p : Person() $a : Address( state == "NC" ) from $p.address then modify ($p) {} // Assign the person to sales region 1. end rule "Apply a discount to people in the city of Raleigh" ruleflow-group "test" lock-on-active true when $p : Person() $a : Address( city == "Raleigh" ) from $p.address then modify ($p) {} // Apply discount to the person. end
Using
from
withlock-on-active
rule attribute can result in rules not being executed. You can address this issue in one of the following ways:-
Avoid using the
from
element when you can insert all facts into the working memory of the Drools engine or use nested object references in your constraint expressions. -
Place the variable used in the
modify()
block as the last sentence in your rule condition. -
Avoid using the
lock-on-active
rule attribute when you can explicitly manage how rules within the same ruleflow group place activations on one another.
The pattern that contains a
from
clause cannot be followed by another pattern starting with a parenthesis. The reason for this restriction is that the DRL parser reads thefrom
expression as"from $l (String() or Number())"
and it cannot differentiate this expression from a function call. The simplest workaround to this is to wrap thefrom
clause in parentheses, as shown in the following example:Example rules withfrom
used incorrectly and correctly// Do not use `from` in this way: rule R when $l : List() String() from $l (String() or Number()) then // Actions end // Use `from` in this way instead: rule R when $l : List() (String() from $l) (String() or Number()) then // Actions end
-
entry-point
-
Use this to define an entry point, or event stream, corresponding to a data source for the pattern. This element is typically used with the
from
condition element. You can declare an entry point for events so that the Drools engine uses data from only that entry point to evaluate the rules. You can declare an entry point either implicitly by referencing it in DRL rules or explicitly in your Java application.Example rule withfrom entry-point
rule "Authorize withdrawal" when WithdrawRequest( $ai : accountId, $am : amount ) from entry-point "ATM Stream" CheckingAccount( accountId == $ai, balance > $am ) then // Authorize withdrawal. end
Example Java application code with EntryPoint object and inserted factsimport org.kie.api.runtime.KieSession; import org.kie.api.runtime.rule.EntryPoint; // Create your KIE base and KIE session as usual: KieSession session = ... // Create a reference to the entry point: EntryPoint atmStream = session.getEntryPoint("ATM Stream"); // Start inserting your facts into the entry point: atmStream.insert(aWithdrawRequest);
collect
-
Use this to define a collection of objects that the rule can use as part of the condition. The rule obtains the collection either from a specified source or from the working memory of the Drools engine. The result pattern of the
collect
element can be any concrete class that implements thejava.util.Collection
interface and provides a default no-arg public constructor. You can use Java collections likeList
,LinkedList
, andHashSet
, or your own class. If variables are bound before thecollect
element in a condition, you can use the variables to constrain both your source and result patterns. However, any binding made inside thecollect
element is not available for use outside of it.Figure 68. CollectExample rule withcollect
import java.util.List rule "Raise priority when system has more than three pending alarms" when $system : System() $alarms : List( size >= 3 ) from collect( Alarm( system == $system, status == 'pending' ) ) then // Raise priority because `$system` has three or more `$alarms` pending. end
In this example, the rule assesses all pending alarms in the working memory of the Drools engine for each given system and groups them in a
List
. If three or more alarms are found for a given system, the rule is executed.You can also use the
collect
element with nestedfrom
elements, as shown in the following example:Example rule withcollect
and nestedfrom
import java.util.LinkedList; rule "Send a message to all parents" when $town : Town( name == 'Paris' ) $mothers : LinkedList() from collect( Person( children > 0 ) from $town.getPeople() ) then // Send a message to all parents. end
accumulate
-
Use this to iterate over a collection of objects, execute custom actions for each of the elements, and return one or more result objects (if the constraints evaluate to
true
). This element is a more flexible and powerful form of thecollect
condition element. You can use predefined functions in youraccumulate
conditions or implement custom functions as needed. You can also use the abbreviationacc
foraccumulate
in rule conditions.Use the following format to define
accumulate
conditions in rules:Preferred format foraccumulate
accumulate( <source pattern>; <functions> [;<constraints>] )
Figure 69. AccumulateAlthough the Drools engine supports alternate formats for the accumulate
element for backward compatibility, this format is preferred for optimal performance in rules and applications.The Drools engine supports the following predefined
accumulate
functions. These functions accept any expression as input.-
average
-
min
-
max
-
count
-
sum
-
collectList
-
collectSet
In the following example rule,
min
,max
, andaverage
areaccumulate
functions that calculate the minimum, maximum, and average temperature values over all the readings for each sensor:Example rule withaccumulate
to calculate temperature valuesrule "Raise alarm" when $s : Sensor() accumulate( Reading( sensor == $s, $temp : temperature ); $min : min( $temp ), $max : max( $temp ), $avg : average( $temp ); $min < 20, $avg > 70 ) then // Raise the alarm. end
The following example rule uses the
average
function withaccumulate
to calculate the average profit for all items in an order:Example rule withaccumulate
to calculate average profitrule "Average profit" when $order : Order() accumulate( OrderItem( order == $order, $cost : cost, $price : price ); $avgProfit : average( 1 - $cost / $price ) ) then // Average profit for `$order` is `$avgProfit`. end
To use custom, domain-specific functions in
accumulate
conditions, create a Java class that implements theorg.kie.api.runtime.rule.AccumulateFunction
interface. For example, the following Java class defines a custom implementation of anAverageData
function:Example Java class with custom implementation ofaverage
function// An implementation of an accumulator capable of calculating average values public class AverageAccumulateFunction implements org.kie.api.runtime.rule.AccumulateFunction<AverageAccumulateFunction.AverageData> { public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { } public void writeExternal(ObjectOutput out) throws IOException { } public static class AverageData implements Externalizable { public int count = 0; public double total = 0; public AverageData() {} public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { count = in.readInt(); total = in.readDouble(); } public void writeExternal(ObjectOutput out) throws IOException { out.writeInt(count); out.writeDouble(total); } } /* (non-Javadoc) * @see org.kie.api.runtime.rule.AccumulateFunction#createContext() */ public AverageData createContext() { return new AverageData(); } /* (non-Javadoc) * @see org.kie.api.runtime.rule.AccumulateFunction#init(java.io.Serializable) */ public void init(AverageData context) { context.count = 0; context.total = 0; } /* (non-Javadoc) * @see org.kie.api.runtime.rule.AccumulateFunction#accumulate(java.io.Serializable, java.lang.Object) */ public void accumulate(AverageData context, Object value) { context.count++; context.total += ((Number) value).doubleValue(); } /* (non-Javadoc) * @see org.kie.api.runtime.rule.AccumulateFunction#reverse(java.io.Serializable, java.lang.Object) */ public void reverse(AverageData context, Object value) { context.count--; context.total -= ((Number) value).doubleValue(); } /* (non-Javadoc) * @see org.kie.api.runtime.rule.AccumulateFunction#getResult(java.io.Serializable) */ public Object getResult(AverageData context) { return new Double( context.count == 0 ? 0 : context.total / context.count ); } /* (non-Javadoc) * @see org.kie.api.runtime.rule.AccumulateFunction#supportsReverse() */ public boolean supportsReverse() { return true; } /* (non-Javadoc) * @see org.kie.api.runtime.rule.AccumulateFunction#getResultType() */ public Class< ? > getResultType() { return Number.class; } }
To use the custom function in a DRL rule, import the function using the
import accumulate
statement:Format to import a custom functionimport accumulate <class_name> <function_name>
Example rule with the importedaverage
functionimport accumulate AverageAccumulateFunction.AverageData average rule "Average profit" when $order : Order() accumulate( OrderItem( order == $order, $cost : cost, $price : price ); $avgProfit : average( 1 - $cost / $price ) ) then // Average profit for `$order` is `$avgProfit`. end
For backward compatibility, the Drools engine also supports the configuration of
accumulate
functions through configuration files and system properties, but this is a deprecated method. To configure theaverage
function from the previous example using the configuration file or system property, set a property as shown in the following example:drools.accumulate.function.average = AverageAccumulateFunction.AverageData
Note that
drools.accumulate.function
is a required prefix,average
is how the function is used in the DRL files, andAverageAccumulateFunction.AverageData
is the fully qualified name of the class that implements the function behavior. -
accumulate
alternate syntax for a single function with return type-
The accumulate syntax evolved over time with the goal of becoming more compact and expressive. Nevertheless, Drools still supports previous syntaxes for backward compatibility purposes.
In case the rule is using a single accumulate function on a given accumulate, the author may add a pattern for the result object and use the "from" keyword to link it to the accumulate result.
Example: a rule to apply a 10% discount on orders over $100 could be written in the following way:
rule "Apply 10% discount to orders over US$ 100,00" when $order : Order() $total : Number( doubleValue > 100 ) from accumulate( OrderItem( order == $order, $value : value ), sum( $value ) ) then // apply discount to $order end
In the above example, the accumulate element is using only one function (sum), and so, the rules author opted to explicitly write a pattern for the result type of the accumulate function (Number) and write the constraints inside it. There are no problems in using this syntax over the compact syntax presented before, except that is is a bit more verbose. Also note that it is not allowed to use both the return type and the functions binding in the same accumulate statement.
Compile-time checks are performed in order to ensure the pattern used with the "
from
" keyword is assignable from the result of the accumulate function used.With this syntax, the "
from
" binds to the single result returned by the accumulate function, and it does not iterate.In the above example, "
$total
" is bound to the result returned by the accumulate sum() function.As another example however, if the result of the accumulate function is a collection, "
from
" still binds to the single result and it does not iterate:rule "Person names" when $x : Object() from accumulate(MyPerson( $val : name ); collectList( $val ) ) then // $x is a List end
The bound "
$x : Object()
" is the List itself, returned by the collectList accumulate function used.This is an important distinction to highlight, as the "
from
" keyword can also be used separately of accumulate, to iterate over the elements of a collection:rule "Iterate the numbers" when $xs : List() $x : Integer() from $xs then // $x matches and binds to each Integer in the collection end
While this syntax is still supported for backward compatibility purposes, for this and other reasons we encourage rule authors to make use instead of the preferred
accumulate
syntax (described previously), to avoid any potential pitfalls. accumulate
with inline custom code-
Another possible syntax for the
accumulate
is to define inline custom code, instead of using accumulate functions.The use of accumulate with inline custom code is not a good practice for several reasons, including difficulties on maintaining and testing rules that use them, as well as the inability of reusing that code. Implementing your own accumulate functions is very simple and straightforward, they are easy to unit test and to use. This form of accumulate is supported for backward compatibility only.
Only limited support for inline accumulate is provided while using the executable model. For example, you cannot use an external binding in the code while using the MVEL dialect:
rule R dialect "mvel" when String( $l : length ) $sum : Integer() from accumulate ( Person( age > 18, $age : age ), init( int sum = 0 * $l; ), action( sum += $age; ), reverse( sum -= $age; ), result( sum ) )
The general syntax of the
accumulate
CE with inline custom code is:<result pattern> from accumulate( <source pattern>, init( <init code> ), action( <action code> ), reverse( <reverse code> ), result( <result expression> ) )
The meaning of each of the elements is the following:
-
<source pattern>: the source pattern is a regular pattern that the Drools engine will try to match against each of the source objects.
-
<init code>: this is a semantic block of code in the selected dialect that will be executed once for each tuple, before iterating over the source objects.
-
<action code>: this is a semantic block of code in the selected dialect that will be executed for each of the source objects.
-
<reverse code>: this is an optional semantic block of code in the selected dialect that if present will be executed for each source object that no longer matches the source pattern. The objective of this code block is to undo any calculation done in the <action code> block, so that the Drools engine can do decremental calculation when a source object is modified or deleted, hugely improving performance of these operations.
-
<result expression>: this is a semantic expression in the selected dialect that is executed after all source objects are iterated.
-
<result pattern>: this is a regular pattern that the Drools engine tries to match against the object returned from the <result expression>. If it matches, the
accumulate
conditional element evaluates to true and the Drools engine proceeds with the evaluation of the next CE in the rule. If it does not matches, theaccumulate
CE evaluates to false and the Drools engine stops evaluating CEs for that rule.
It is easier to understand if we look at an example:
rule "Apply 10% discount to orders over US$ 100,00" when $order : Order() $total : Number( doubleValue > 100 ) from accumulate( OrderItem( order == $order, $value : value ), init( double total = 0; ), action( total += $value; ), reverse( total -= $value; ), result( total ) ) then // apply discount to $order end
In the above example, for each
Order
in the Working Memory, the Drools engine will execute the init code initializing the total variable to zero. Then it will iterate over allOrderItem
objects for that order, executing the action for each one (in the example, it will sum the value of all items into the total variable). After iterating over allOrderItem
objects, it will return the value corresponding to the result expression (in the above example, the value of variabletotal
). Finally, the Drools engine will try to match the result with theNumber
pattern, and if the double value is greater than 100, the rule will fire.The example used Java as the semantic dialect, and as such, note that the usage of the semicolon as statement delimiter is mandatory in the init, action and reverse code blocks. The result is an expression and, as such, it does not admit ';'. If the user uses any other dialect, he must comply to that dialect’s specific syntax.
As mentioned before, the reverse code is optional, but it is strongly recommended that the user writes it in order to benefit from the improved performance on update and delete.
The
accumulate
CE can be used to execute any action on source objects. The following example instantiates and populates a custom object:rule "Accumulate using custom objects" when $person : Person( $likes : likes ) $cheesery : Cheesery( totalAmount > 100 ) from accumulate( $cheese : Cheese( type == $likes ), init( Cheesery cheesery = new Cheesery(); ), action( cheesery.addCheese( $cheese ); ), reverse( cheesery.removeCheese( $cheese ); ), result( cheesery ) ); then // do something end
-
eval
-
The conditional element
eval
is essentially a catch-all which allows any semantic code (that returns a primitive boolean) to be executed. This code can refer to variables that were bound in the conditions of the rule and functions in the rule package. Overuse ofeval
reduces the declarativeness of your rules and can result in a poorly performing Drools engine. Whileeval
can be used anywhere in the patterns, it is typically added as the last conditional element in the conditions of a rule.Figure 70. EvalInstances of
eval
cannot be indexed and thus are not as efficient as Field Constraints. However this makes them ideal for being used when functions return values that change over time, which is not allowed within Field Constraints.For those who are familiar with Drools 2.x lineage, the old Drools parameter and condition tags are equivalent to binding a variable to an appropriate type, and then using it in an
eval
node.p1 : Parameter() p2 : Parameter() eval( p1.getList().containsKey( p2.getItem() ) )
p1 : Parameter() p2 : Parameter() // call function isValid in the LHS eval( isValid( p1, p2 ) )
4.1.8.9. OOPath syntax with graphs of objects in DRL rule conditions
OOPath is an object-oriented syntax extension of XPath that is designed for browsing graphs of objects in DRL rule condition constraints. OOPath uses the compact notation from XPath for navigating through related elements while handling collections and filtering constraints, and is specifically useful for graphs of objects.
When the field of a fact is a collection, you can use the from
condition element (keyword) to bind and reason over all the items in that collection one by one. If you need to browse a graph of objects in the rule condition constraints, the extensive use of the from
condition element results in a verbose and repetitive syntax, as shown in the following example:
from
rule "Find all grades for Big Data exam"
when
$student: Student( $plan: plan )
$exam: Exam( course == "Big Data" ) from $plan.exams
$grade: Grade() from $exam.grades
then
// Actions
end
In this example, the domain model contains a Student
object with a Plan
of study. The Plan
can have zero or more Exam
instances and an Exam
can have zero or more Grade
instances. Only the root object of the graph, the Student
in this case, needs to be in the working memory of the Drools engine for this rule setup to function.
As a more efficient alternative to using extensive from
statements, you can use the abbreviated OOPath syntax, as shown in the following example:
rule "Find all grades for Big Data exam"
when
Student( $grade: /plan/exams[course == "Big Data"]/grades )
then
// Actions
end
Formally, the core grammar of an OOPath expression is defined in extended Backus-Naur form (EBNF) notation in the following way:
OOPExpr = [ID ( ":" | ":=" )] ( "/" | "?/" ) OOPSegment { ( "/" | "?/" | "." ) OOPSegment } ;
OOPSegment = ID ["#" ID] ["[" ( Number | Constraints ) "]"]
In practice, an OOPath expression has the following features and capabilities:
-
Starts with a forward slash
/
or with a question mark and forward slash?/
if it is a non-reactive OOPath expression (described later in this section). -
Can dereference a single property of an object with the period
.
operator. -
Can dereference multiple properties of an object with the forward slash
/
operator. If a collection is returned, the expression iterates over the values in the collection. -
Can filter out traversed objects that do not satisfy one or more constraints. The constraints are written as predicate expressions between square brackets, as shown in the following example:
Constraints as a predicate expressionStudent( $grade: /plan/exams[ course == "Big Data" ]/grades )
-
Can downcast a traversed object to a subclass of the class declared in the generic collection. Subsequent constraints can also safely access the properties declared only in that subclass, as shown in the following example. Objects that are not instances of the class specified in this inline cast are automatically filtered out.
Constraints with downcast objectsStudent( $grade: /plan/exams#AdvancedExam[ course == "Big Data", level > 3 ]/grades )
-
Can backreference an object of the graph that was traversed before the currently iterated graph. For example, the following OOPath expression matches only the grades that are above the average for the passed exam:
Constraints with backreferenced objectStudent( $grade: /plan/exams/grades[ result > ../averageResult ] )
-
Can recursively be another OOPath expression, as shown in the following example:
Recursive constraint expressionStudent( $exam: /plan/exams[ /grades[ result > 20 ] ] )
-
Can access objects by their index between square brackets
[]
, as shown in the following example. To adhere to Java convention, OOPath indexes are 0-based, while XPath indexes are 1-based.Constraints with access to objects by indexStudent( $grade: /plan/exams[0]/grades )
OOPath expressions can be reactive or non-reactive. The Drools engine does not react to updates involving a deeply nested object that is traversed during the evaluation of an OOPath expression.
To make these objects reactive to changes, modify the objects to extend the class org.drools.core.phreak.ReactiveObject
. After you modify an object to extend the ReactiveObject
class, the domain object invokes the inherited method notifyModification
to notify the Drools engine when one of the fields has been updated, as shown in the following example:
public void setCourse(String course) {
this.course = course;
notifyModification(this);
}
With the following corresponding OOPath expression, when an exam is moved to a different course, the rule is re-executed and the list of grades matching the rule is recomputed:
Student( $grade: /plan/exams[ course == "Big Data" ]/grades )
You can also use the ?/
separator instead of the /
separator to disable reactivity in only one sub-portion of an OOPath expression, as shown in the following example:
Student( $grade: /plan/exams[ course == "Big Data" ]?/grades )
With this example, the Drools engine reacts to a change made to an exam or if an exam is added to the plan, but not if a new grade is added to an existing exam.
If an OOPath portion is non-reactive, all remaining portions of the OOPath expression also become non-reactive. For example, the following OOPath expression is completely non-reactive:
Student( $grade: ?/plan/exams[ course == "Big Data" ]/grades )
For this reason, you cannot use the ?/
separator more than once in the same OOPath expression. For example, the following expression causes a compilation error:
Student( $grade: /plan?/exams[ course == "Big Data" ]?/grades )
Another alternative for enabling OOPath expression reactivity is to use the dedicated implementations for List
and Set
interfaces in Drools. These implementations are the ReactiveList
and ReactiveSet
classes. A ReactiveCollection
class is also available. The implementations also provide reactive support for performing mutable operations through the Iterator
and ListIterator
classes.
The following example class uses these classes to configure OOPath expression reactivity:
public class School extends AbstractReactiveObject {
private String name;
private final List<Child> children = new ReactiveList<Child>(); (1)
public void setName(String name) {
this.name = name;
notifyModification(); (2)
}
public void addChild(Child child) {
children.add(child); (3)
// No need to call `notifyModification()` here
}
}
1 | Uses the ReactiveList instance for reactive support over the standard Java List instance. |
2 | Uses the required notifyModification() method for when a field is changed in reactive support. |
3 | The children field is a ReactiveList instance, so the notifyModification() method call is not required. The notification is handled automatically, like all other mutating operations performed over the children field. |
4.1.9. Rule actions in DRL (THEN)
The then
part of the rule (also known as the Right Hand Side (RHS) of the rule) contains the actions to be performed when the conditional part of the rule has been met. Actions consist of one or more methods that execute consequences based on the rule conditions and on available data objects in the package. For example, if a bank requires loan applicants to have over 21 years of age (with a rule condition Applicant( age < 21 )
) and a loan applicant is under 21 years old, the then
action of an "Underage"
rule would be setApproved( false )
, declining the loan because the applicant is under age.
The main purpose of rule actions is to to insert, delete, or modify data in the working memory of the Drools engine. Effective rule actions are small, declarative, and readable. If you need to use imperative or conditional code in rule actions, then divide the rule into multiple smaller and more declarative rules.
rule "Underage"
when
application : LoanApplication()
Applicant( age < 21 )
then
application.setApproved( false );
application.setExplanation( "Underage" );
end
4.1.9.1. Supported rule action methods in DRL
DRL supports the following rule action methods that you can use in DRL rule actions. You can use these methods to modify the working memory of the Drools engine without having to first reference a working memory instance. These methods act as shortcuts to the methods provided by the KnowledgeHelper
class in your Drools distribution.
For all rule action methods, see the Drools KnowledgeHelper.java page in GitHub.
set
-
Use this to set the value of a field.
set<field> ( <value> )
Example rule action to set the values of a loan application approval$application.setApproved ( false ); $application.setExplanation( "has been bankrupt" );
modify
-
Use this to specify fields to be modified for a fact and to notify the Drools engine of the change. This method provides a structured approach to fact updates. It combines the
update
operation with setter calls to change object fields.modify ( <fact-expression> ) { <expression>, <expression>, ... }
Example rule action to modify a loan application amount and approvalmodify( LoanApplication ) { setAmount( 100 ), setApproved ( true ) }
update
-
Use this to specify fields and the entire related fact to be updated and to notify the Drools engine of the change. After a fact has changed, you must call
update
before changing another fact that might be affected by the updated values. To avoid this added step, use themodify
method instead.update ( <object, <handle> ) // Informs the Drools engine that an object has changed update ( <object> ) // Causes `KieSession` to search for a fact handle of the object
Example rule action to update a loan application amount and approvalLoanApplication.setAmount( 100 ); update( LoanApplication );
If you provide property-change listeners, you do not need to call this method when an object changes. For more information about property-change listeners, see Property-change settings and listeners for fact types. insert
-
Use this to insert a
new
fact into the working memory of the Drools engine and to define resulting fields and values as needed for the fact.insert( new <object> );
Example rule action to insert a new loan applicant objectinsert( new Applicant() );
insertLogical
-
Use this to insert a
new
fact logically into the Drools engine. The Drools engine is responsible for logical decisions on insertions and retractions of facts. After regular or stated insertions, facts must be retracted explicitly. After logical insertions, the facts that were inserted are automatically retracted when the conditions in the rules that inserted the facts are no longer true.insertLogical( new <object> );
Example rule action to logically insert a new loan applicant objectinsertLogical( new Applicant() );
delete
-
Use this to remove an object from the Drools engine. The keyword
retract
is also supported in DRL and executes the same action, butdelete
is typically preferred in DRL code for consistency with the keywordinsert
.delete( <object> );
Example rule action to delete a loan applicant objectdelete( Applicant );
4.1.9.2. Other rule action methods from drools
and kcontext
variables
In addition to the standard rule action methods, the Drools engine supports methods in conjunction with the predefined drools
and kcontext
variables that you can also use in rule actions.
You can use the drools
variable to call methods from the KnowledgeHelper
class in your Drools distribution, which is also the class that the standard rule action methods are based on. For all drools
rule action options,
see the Drools KnowledgeHelper.java page in GitHub.
The following examples are common methods that you can use with the drools
variable:
-
drools.halt()
: Terminates rule execution if a user or application has previously calledfireUntilHalt()
. When a user or application callsfireUntilHalt()
, the Drools engine starts in active mode and evaluates rules continually until the user or application explicitly callshalt()
. Otherwise, by default, the Drools engine runs in passive mode and evaluates rules only when a user or an application explicitly callsfireAllRules()
. -
drools.getWorkingMemory()
: Returns theWorkingMemory
object. -
drools.setFocus( "<agenda_group>" )
: Sets the focus to a specified agenda group to which the rule belongs. -
drools.getRule().getName()
: Returns the name of the rule. -
drools.getTuple()
,drools.getActivation()
: Returns theTuple
that matches the currently executing rule and then delivers the correspondingActivation
. These calls are useful for logging and debugging purposes.
You can use the kcontext
variable with the getKieRuntime()
method to call other methods from the KieContext
class and, by extension, the RuleContext
class in your Drools distribution. The full Knowledge Runtime API is exposed through the kcontext
variable and provides extensive rule action methods. For all kcontext
rule action options,
see the Drools RuleContext.java page in GitHub.
The following examples are common methods that you can use with the kcontext.getKieRuntime()
variable-method combination:
-
kcontext.getKieRuntime().halt()
: Terminates rule execution if a user or application has previously calledfireUntilHalt()
. This method is equivalent to thedrools.halt()
method. When a user or application callsfireUntilHalt()
, the Drools engine starts in active mode and evaluates rules continually until the user or application explicitly callshalt()
. Otherwise, by default, the Drools engine runs in passive mode and evaluates rules only when a user or an application explicitly callsfireAllRules()
. -
kcontext.getKieRuntime().getAgenda()
: Returns a reference to the KIE sessionAgenda
, and in turn provides access to rule activation groups, rule agenda groups, and ruleflow groups.Example call to access agenda group "CleanUp" and set the focuskcontext.getKieRuntime().getAgenda().getAgendaGroup( "CleanUp" ).setFocus();
This example is equivalent to
drools.setFocus( "CleanUp" )
. -
kcontext.getKieRuntime().getQueryResults(<string> query)
: Runs a query and returns the results. This method is equivalent todrools.getKieRuntime().getQueryResults()
. -
kcontext.getKieRuntime().getKieBase()
: Returns theKieBase
object. The KIE base is the source of all the knowledge in your rule system and the originator of the current KIE session. -
kcontext.getKieRuntime().setGlobal()
,~.getGlobal()
,~.getGlobals()
: Sets or retrieves global variables. -
kcontext.getKieRuntime().getEnvironment()
: Returns the runtimeEnvironment
, similar to your operating system environment.
4.1.9.3. Advanced rule actions with conditional and named consequences
In general, effective rule actions are small, declarative, and readable. However, in some cases, the limitation of having a single consequence for each rule can be challenging and lead to verbose and repetitive rule syntax, as shown in the following example rules:
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
A partial solution to the repetition is to make the second rule extend the first rule, as shown in the following modified example:
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
As a more efficient alternative, you can consolidate the two rules into a single rule with modified conditions and labelled corresponding rule actions, as shown in the following consolidated example:
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
This example rule uses two actions: the usual default action and another action named giveDiscount
. The giveDiscount
action is activated in the condition with the keyword do
when a customer older than 60 years old is found in the KIE base, regardless of whether or not the customer owns a car.
You can configure the activation of a named consequence with an additional condition, such as the if
statement in the following example. The condition in the if
statement is always evaluated on the pattern that immediately precedes it.
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
You can also evaluate different rule conditions using a nested if
and else if
construct, as shown in the following more complex example:
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
This example rule gives a 10% discount and free parking to Golden customers over 60, but only a 5% discount without free parking to Silver customers. The rule activates the consequence named giveDiscount5
with the keyword break
instead of do
. The keyword do
schedules a consequence in the Drools engine agenda, enabling the remaining part of the rule conditions to continue being evaluated, while break
blocks any further condition evaluation. If a named consequence does not correspond to any condition with do
but is activated with break
, the rule fails to compile because the conditional part of the rule is never reached.
4.1.10. Comments in DRL files
DRL supports single-line comments prefixed with a double forward slash //
and multi-line comments enclosed with a forward slash and asterisk /* … */
. You can use DRL comments to annotate rules or any related components in DRL files. DRL comments are ignored by the Drools engine when the DRL file is processed.
rule "Underage"
// This is a single-line comment.
when
$application : LoanApplication() // This is an in-line comment.
Applicant( age < 21 )
then
/* This is a multi-line comment
in the rule actions. */
$application.setApproved( false );
$application.setExplanation( "Underage" );
end
The hash symbol # is not supported for DRL comments.
|
4.1.11. Error messages for DRL troubleshooting
Drools provides standardized messages for DRL errors to help you troubleshoot and resolve problems in your DRL files. The error messages use the following format:
-
1st Block: Error code
-
2nd Block: Line and column in the DRL source where the error occurred
-
3rd Block: Description of the problem
-
4th Block: Component in the DRL source (rule, function, query) where the error occurred
-
5th Block: Pattern in the DRL source where the error occurred (if applicable)
Drools supports the following standardized error messages:
- 101: no viable alternative
-
Indicates that the parser reached a decision point but could not identify an alternative.
Example rule with incorrect spelling1: rule "simple rule" 2: when 3: exists Person() 4: exits Student() // Must be `exists` 5: then 6: end
Error message[ERR 101] Line 4:4 no viable alternative at input 'exits' in rule "simple rule"
Example rule without a rule name1: package org.drools.examples; 2: rule // Must be `rule "rule name"` (or `rule rule_name` if no spacing) 3: when 4: Object() 5: then 6: System.out.println("A RHS"); 7: end
Error message[ERR 101] Line 3:2 no viable alternative at input 'when'
In this example, the parser encountered the keyword
when
but expected the rule name, so it flagswhen
as the incorrect expected token.Example rule with incorrect syntax1: rule "simple rule" 2: when 3: Student( name == "Andy ) // Must be `"Andy"` 4: then 5: end
Error message[ERR 101] Line 0:-1 no viable alternative at input '<eof>' in rule "simple rule" in pattern Student
A line and column value of 0:-1
means the parser reached the end of the source file (<eof>
) but encountered incomplete constructs, usually due to missing quotation marks"…"
, apostrophes'…'
, or parentheses(…)
. - 102: mismatched input
-
Indicates that the parser expected a particular symbol that is missing at the current input position.
Example rule with an incomplete rule statement1: rule simple_rule 2: when 3: $p : Person( // Must be a complete rule statement
Error message[ERR 102] Line 0:-1 mismatched input '<eof>' expecting ')' in rule "simple rule" in pattern Person
A line and column value of 0:-1
means the parser reached the end of the source file (<eof>
) but encountered incomplete constructs, usually due to missing quotation marks"…"
, apostrophes'…'
, or parentheses(…)
.Example rule with incorrect syntax1: package org.drools.examples; 2: 3: rule "Wrong syntax" 4: when 5: not( Car( ( type == "tesla", price == 10000 ) || ( type == "kia", price == 1000 ) ) from $carList ) // Must use `&&` operators instead of commas `,` 6: then 7: System.out.println("OK"); 8: end
Error messages[ERR 102] Line 5:36 mismatched input ',' expecting ')' in rule "Wrong syntax" in pattern Car [ERR 101] Line 5:57 no viable alternative at input 'type' in rule "Wrong syntax" [ERR 102] Line 5:106 mismatched input ')' expecting 'then' in rule "Wrong syntax"
In this example, the syntactic problem results in multiple error messages related to each other. The single solution of replacing the commas
,
with&&
operators resolves all errors. If you encounter multiple errors, resolve one at a time in case errors are consequences of previous errors. - 103: failed predicate
-
Indicates that a validating semantic predicate evaluated to
false
. These semantic predicates are typically used to identify component keywords in DRL files, such asdeclare
,rule
,exists
,not
, and others.Example rule with an invalid keyword1: package nesting; 2: 3: import org.drools.compiler.Person 4: import org.drools.compiler.Address 5: 6: Some text // Must be a valid DRL keyword 7: 8: rule "test something" 9: when 10: $p: Person( name=="Michael" ) 11: then 12: $p.name = "other"; 13: System.out.println(p.name); 14: end
Error message[ERR 103] Line 6:0 rule 'rule_key' failed predicate: {(validateIdentifierKey(DroolsSoftKeywords.RULE))}? in rule
The
Some text
line is invalid because it does not begin with or is not a part of a DRL keyword construct, so the parser fails to validate the rest of the DRL file.This error is similar to 102: mismatched input
, but usually involves DRL keywords. - 104: trailing semi-colon not allowed
-
Indicates that an
eval()
clause in a rule condition uses a semicolon;
but must not use one.Example rule witheval()
and trailing semicolon1: rule "simple rule" 2: when 3: eval( abc(); ) // Must not use semicolon `;` 4: then 5: end
Error message[ERR 104] Line 3:4 trailing semi-colon not allowed in rule "simple rule"
- 105: did not match anything
-
Indicates that the parser reached a sub-rule in the grammar that must match an alternative at least once, but the sub-rule did not match anything. The parser has entered a branch with no way out.
Example rule with invalid text in an empty condition1: rule "empty condition" 2: when 3: None // Must remove `None` if condition is empty 4: then 5: insert( new Person() ); 6: end
Error message[ERR 105] Line 2:2 required (...)+ loop did not match anything at input 'WHEN' in rule "empty condition"
In this example, the condition is intended to be empty but the word
None
is used. This error is resolved by removingNone
, which is not a valid DRL keyword, data type, or pattern construct.
4.1.12. Rule units in DRL rule sets
Rule units are groups of data sources, global variables, and DRL rules that function together for a specific purpose. You can use rule units to partition a rule set into smaller units, bind different data sources to those units, and then execute the individual unit. Rule units are an enhanced alternative to rule-grouping DRL attributes such as rule agenda groups or activation groups for execution control.
Rule units are helpful when you want to coordinate rule execution so that the complete execution of one rule unit triggers the start of another rule unit and so on. For example, assume that you have a set of rules for data enrichment, another set of rules that processes that data, and another set of rules that extract the output from the processed data. If you add these rule sets into three distinct rule units, you can coordinate those rule units so that complete execution of the first unit triggers the start of the second unit and the complete execution of the second unit triggers the start of third unit.
To define a rule unit, implement the RuleUnit
interface as shown in the following example:
package org.mypackage.myunit;
public static class AdultUnit implements RuleUnit {
private int adultAge;
private DataSource<Person> persons;
public AdultUnit( ) { }
public AdultUnit( DataSource<Person> persons, int age ) {
this.persons = persons;
this.age = age;
}
// A data source of `Persons` in this rule unit:
public DataSource<Person> getPersons() {
return persons;
}
// A global variable in this rule unit:
public int getAdultAge() {
return adultAge;
}
// Life-cycle methods:
@Override
public void onStart() {
System.out.println("AdultUnit started.");
}
@Override
public void onEnd() {
System.out.println("AdultUnit ended.");
}
}
In this example, persons
is a source of facts of type Person
. A rule unit data source is a source of the data processed by a given rule unit and represents the entry point that the Drools engine uses to evaluate the rule unit. The adultAge
global variable is accessible from all the rules belonging to this rule unit. The last two methods are part of the rule unit life cycle and are invoked by the Drools engine.
The Drools engine supports the following optional life-cycle methods for rule units:
Method | Invoked when |
---|---|
|
Rule unit execution starts |
|
Rule unit execution ends |
|
Rule unit execution is suspended (used only with |
|
Rule unit execution is resumed (used only with |
|
The consequence of a rule in the rule unit triggers the execution of a different rule unit |
You can add one or more rules to a rule unit. By default, all the rules in a DRL file are automatically associated with a rule unit that follows the naming convention of the DRL file name. If the DRL file is in the same package and has the same name as a class that implements the RuleUnit
interface, then all of the rules in that DRL file implicitly belong to that rule unit. For example, all the rules in the AdultUnit.drl
file in the org.mypackage.myunit
package are automatically part of the rule unit org.mypackage.myunit.AdultUnit
.
To override this naming convention and explicitly declare the rule unit that the rules in a DRL file belong to, use the unit
keyword in the DRL file. The unit
declaration must immediately follow the package declaration and contain the name of the class in that package that the rules in the DRL file are part of.
package org.mypackage.myunit
unit AdultUnit
rule Adult
when
$p : Person(age >= adultAge) from persons
then
System.out.println($p.getName() + " is adult and greater than " + adultAge);
end
Do not mix rules with and without a rule unit in the same KIE base. Mixing two rule paradigms in a KIE base results in a compilation error. |
You can also rewrite the same pattern in a more convenient way using OOPath notation, as shown in the following example:
package org.mypackage.myunit
unit AdultUnit
rule Adult
when
$p : /persons[age >= adultAge]
then
System.out.println($p.getName() + " is adult and greater than " + adultAge);
end
OOPath is an object-oriented syntax extension of XPath that is designed for browsing graphs of objects in DRL rule condition constraints. OOPath uses the compact notation from XPath for navigating through related elements while handling collections and filtering constraints, and is specifically useful for graphs of objects. |
In this example, any matching facts in the rule conditions are retrieved from the persons
data source defined in the DataSource
definition in the rule unit class. The rule condition and action use the adultAge
variable in the same way that a global variable is defined at the DRL file level.
To execute one or more rule units defined in a KIE base, create a new RuleUnitExecutor
class bound to the KIE base, create the rule unit from the relevant data source, and run the rule unit executer:
// Create a `RuleUnitExecutor` class and bind it to the KIE base:
KieBase kbase = kieContainer.getKieBase();
RuleUnitExecutor executor = RuleUnitExecutor.create().bind( kbase );
// Create the `AdultUnit` rule unit using the `persons` data source and run the executor:
RuleUnit adultUnit = new AdultUnit(persons, 18);
executor.run( adultUnit );
Rules are executed by the RuleUnitExecutor
class. The RuleUnitExecutor
class creates KIE sessions and adds the required DataSource
objects to those sessions, and then executes the rules based on the RuleUnit
that is passed as a parameter to the run()
method.
The example execution code produces the following output when the relevant Person
facts are inserted in the persons
data source:
org.mypackage.myunit.AdultUnit started.
Jane is adult and greater than 18
John is adult and greater than 18
org.mypackage.myunit.AdultUnit ended.
Instead of explicitly creating the rule unit instance, you can register the rule unit variables in the executor and pass to the executor the rule unit class that you want to run, and then the executor creates an instance of the rule unit. You can then set the DataSource
definition and other variables as needed before running the rule unit.
executor.bindVariable( "persons", persons );
.bindVariable( "adultAge", 18 );
executor.run( AdultUnit.class );
The name that you pass to the RuleUnitExecutor.bindVariable()
method is used at run time to bind the variable to the field of the rule unit class with the same name. In the previous example, the RuleUnitExecutor
inserts into the new rule unit the data source bound to the "persons"
name and inserts the value 18
bound to the String "adultAge"
into the fields with the corresponding names inside the AdultUnit
class.
To override this default variable-binding behavior, use the @UnitVar
annotation to explicitly define a logical binding name for each field of the rule unit class. For example, the field bindings in the following class are redefined with alternative names:
@UnitVar
package org.mypackage.myunit;
public static class AdultUnit implements RuleUnit {
@UnitVar("minAge")
private int adultAge = 18;
@UnitVar("data")
private DataSource<Person> persons;
}
You can then bind the variables to the executor using those alternative names and run the rule unit:
executor.bindVariable( "data", persons );
.bindVariable( "minAge", 18 );
executor.run( AdultUnit.class );
You can execute a rule unit in passive mode by using the run()
method (equivalent to invoking fireAllRules()
on a KIE session)
or in active mode using the runUntilHalt()
method (equivalent to invoking fireUntilHalt()
on a KIE session). By default, the Drools engine runs in passive mode and evaluates rule units only when a user or an application explicitly calls run()
(or fireAllRules()
for standard rules). If a user or application calls runUntilHalt()
for rule units (or fireUntilHalt()
for standard rules), the Drools engine starts in active mode and evaluates rule units continually until the user or application explicitly calls halt()
.
If you use the runUntilHalt()
method, invoke the method on a separate execution thread to avoid blocking the main thread:
runUntilHalt()
on a separate threadnew Thread( () -> executor.runUntilHalt( adultUnit ) ).start();
4.1.12.1. Data sources for rule units
A rule unit data source is a source of the data processed by a given rule unit and represents the entry point that the Drools engine uses to evaluate the rule unit. A rule unit can have zero or more data sources and each DataSource
definition declared inside a rule unit can correspond to a different entry point into the rule unit executor. Multiple rule units can share a single data source, but each rule unit must use different entry points through which the same objects are inserted.
You can create a DataSource
definition with a fixed set of data in a rule unit class, as shown in the following example:
DataSource<Person> persons = DataSource.create( new Person( "John", 42 ),
new Person( "Jane", 44 ),
new Person( "Sally", 4 ) );
Because a data source represents the entry point of the rule unit, you can insert, update, or delete facts in a rule unit:
// Insert a fact:
Person john = new Person( "John", 42 );
FactHandle johnFh = persons.insert( john );
// Modify the fact and optionally specify modified properties (for property reactivity):
john.setAge( 43 );
persons.update( johnFh, john, "age" );
// Delete the fact:
persons.delete( johnFh );
4.1.12.2. Rule unit execution control
Rule units are helpful when you want to coordinate rule execution so that the execution of one rule unit triggers the start of another rule unit and so on.
To facilitate rule unit execution control, the Drools engine supports the following rule unit methods that you can use in DRL rule actions to coordinate the execution of rule units:
-
drools.run()
: Triggers the execution of a specified rule unit class. This method imperatively interrupts the execution of the rule unit and activates the other specified rule unit. -
drools.guard()
: Prevents (guards) a specified rule unit class from being executed until the associated rule condition is met. This method declaratively schedules the execution of the other specified rule unit. When the Drools engine produces at least one match for the condition in the guarding rule, the guarded rule unit is considered active. A rule unit can contain multiple guarding rules.
As an example of the drools.run()
method, consider the following DRL rules that each belong to a specified rule unit. The NotAdult
rule uses the drools.run( AdultUnit.class )
method to trigger the execution of the AdultUnit
rule unit:
drools.run()
package org.mypackage.myunit
unit AdultUnit
rule Adult
when
Person(age >= 18, $name : name) from persons
then
System.out.println($name + " is adult");
end
package org.mypackage.myunit
unit NotAdultUnit
rule NotAdult
when
$p : Person(age < 18, $name : name) from persons
then
System.out.println($name + " is NOT adult");
modify($p) { setAge(18); }
drools.run( AdultUnit.class );
end
The example also uses a RuleUnitExecutor
class created from the KIE base that was built from these rules and a DataSource
definition of persons
bound to it:
RuleUnitExecutor executor = RuleUnitExecutor.create().bind( kbase );
DataSource<Person> persons = executor.newDataSource( "persons",
new Person( "John", 42 ),
new Person( "Jane", 44 ),
new Person( "Sally", 4 ) );
In this case, the example creates the DataSource
definition directly from the RuleUnitExecutor
class and binds it to the "persons"
variable in a single statement.
The example execution code produces the following output when the relevant Person
facts are inserted in the persons
data source:
Sally is NOT adult
John is adult
Jane is adult
Sally is adult
The NotAdult
rule detects a match when evaluating the person "Sally"
, who is under 18 years old. The rule then modifies
her age to 18
and uses the drools.run( AdultUnit.class )
method to trigger the execution of the AdultUnit
rule unit. The AdultUnit
rule unit contains a rule that can now be executed for all of the 3 persons
in the DataSource
definition.
As an example of the drools.guard()
method, consider the following BoxOffice
class and BoxOfficeUnit
rule unit class:
BoxOffice
classpublic class BoxOffice {
private boolean open;
public BoxOffice( boolean open ) {
this.open = open;
}
public boolean isOpen() {
return open;
}
public void setOpen( boolean open ) {
this.open = open;
}
}
BoxOfficeUnit
rule unit classpublic class BoxOfficeUnit implements RuleUnit {
private DataSource<BoxOffice> boxOffices;
public DataSource<BoxOffice> getBoxOffices() {
return boxOffices;
}
}
The example also uses the following TicketIssuerUnit
rule unit class to keep selling box office tickets for the event as long as at least one box office is open. This rule unit uses DataSource
definitions of persons
and tickets
:
TicketIssuerUnit
rule unit classpublic class TicketIssuerUnit implements RuleUnit {
private DataSource<Person> persons;
private DataSource<AdultTicket> tickets;
private List<String> results;
public TicketIssuerUnit() { }
public TicketIssuerUnit( DataSource<Person> persons, DataSource<AdultTicket> tickets ) {
this.persons = persons;
this.tickets = tickets;
}
public DataSource<Person> getPersons() {
return persons;
}
public DataSource<AdultTicket> getTickets() {
return tickets;
}
public List<String> getResults() {
return results;
}
}
The BoxOfficeUnit
rule unit contains a BoxOfficeIsOpen
DRL rule that uses the drools.guard( TicketIssuerUnit.class )
method to guard the execution of the TicketIssuerUnit
rule unit that distributes the event tickets, as shown in the following DRL rule examples:
drools.guard()
package org.mypackage.myunit;
unit TicketIssuerUnit;
rule IssueAdultTicket when
$p: /persons[ age >= 18 ]
then
tickets.insert(new AdultTicket($p));
end
rule RegisterAdultTicket when
$t: /tickets
then
results.add( $t.getPerson().getName() );
end
package org.mypackage.myunit;
unit BoxOfficeUnit;
rule BoxOfficeIsOpen
when
$box: /boxOffices[ open ]
then
drools.guard( TicketIssuerUnit.class );
end
In this example, so long as at least one box office is open
, the guarded TicketIssuerUnit
rule unit is active and distributes event tickets. When no more box offices are in open
state, the guarded TicketIssuerUnit
rule unit is prevented from being executed.
The following example class illustrates a more complete box office scenario:
DataSource<Person> persons = executor.newDataSource( "persons" );
DataSource<BoxOffice> boxOffices = executor.newDataSource( "boxOffices" );
DataSource<AdultTicket> tickets = executor.newDataSource( "tickets" );
List<String> list = new ArrayList<>();
executor.bindVariable( "results", list );
// Two box offices are open:
BoxOffice office1 = new BoxOffice(true);
FactHandle officeFH1 = boxOffices.insert( office1 );
BoxOffice office2 = new BoxOffice(true);
FactHandle officeFH2 = boxOffices.insert( office2 );
persons.insert(new Person("John", 40));
// Execute `BoxOfficeIsOpen` rule, run `TicketIssuerUnit` rule unit, and execute `RegisterAdultTicket` rule:
executor.run(BoxOfficeUnit.class);
assertEquals( 1, list.size() );
assertEquals( "John", list.get(0) );
list.clear();
persons.insert(new Person("Matteo", 30));
// Execute `RegisterAdultTicket` rule:
executor.run(BoxOfficeUnit.class);
assertEquals( 1, list.size() );
assertEquals( "Matteo", list.get(0) );
list.clear();
// One box office is closed, the other is open:
office1.setOpen(false);
boxOffices.update(officeFH1, office1);
persons.insert(new Person("Mark", 35));
executor.run(BoxOfficeUnit.class);
assertEquals( 1, list.size() );
assertEquals( "Mark", list.get(0) );
list.clear();
// All box offices are closed:
office2.setOpen(false);
boxOffices.update(officeFH2, office2); // Guarding rule is no longer true.
persons.insert(new Person("Edson", 35));
executor.run(BoxOfficeUnit.class); // No execution
assertEquals( 0, list.size() );
4.1.12.3. Rule unit identity conflicts
In rule unit execution scenarios with guarded rule units, a rule can guard multiple rule units and at the same time a rule unit can be guarded and then activated by multiple rules. For these two-way guarding scenarios, rule units must have a clearly defined identity to avoid identity conflicts.
By default, the identity of a rule unit is the rule unit class name and is treated as a singleton class by the RuleUnitExecutor
. This identification behavior is encoded in the getUnitIdentity()
default method of the RuleUnit
interface:
RuleUnit
interfacedefault Identity getUnitIdentity() {
return new Identity( getClass() );
}
In some cases, you may need to override this default identification behavior to avoid conflicting identities between rule units.
For example, the following RuleUnit
class contains a DataSource
definition that accepts any kind of object:
Unit0
rule unit classpublic class Unit0 implements RuleUnit {
private DataSource<Object> input;
public DataSource<Object> getInput() {
return input;
}
}
This rule unit contains the following DRL rule that guards another rule unit based on two conditions (in OOPath notation):
GuardAgeCheck
DRL rule in the rule unitpackage org.mypackage.myunit
unit Unit0
rule GuardAgeCheck
when
$i: /input#Integer
$s: /input#String
then
drools.guard( new AgeCheckUnit($i) );
drools.guard( new AgeCheckUnit($s.length()) );
end
The guarded AgeCheckUnit
rule unit verifies the age of a set of persons
. The AgeCheckUnit
contains a DataSource
definition of the persons
to check, a minAge
variable that it verifies against, and a List
for gathering the results:
AgeCheckUnit
rule unitpublic class AgeCheckUnit implements RuleUnit {
private final int minAge;
private DataSource<Person> persons;
private List<String> results;
public AgeCheckUnit( int minAge ) {
this.minAge = minAge;
}
public DataSource<Person> getPersons() {
return persons;
}
public int getMinAge() {
return minAge;
}
public List<String> getResults() {
return results;
}
}
The AgeCheckUnit
rule unit contains the following DRL rule that performs the verification of the persons
in the data source:
CheckAge
DRL rule in the rule unitpackage org.mypackage.myunit
unit AgeCheckUnit
rule CheckAge
when
$p : /persons{ age > minAge }
then
results.add($p.getName() + ">" + minAge);
end
This example creates a RuleUnitExecutor
class, binds the class to the KIE base that contains these two rule units, and creates
the two DataSource
definitions for the same rule units:
RuleUnitExecutor executor = RuleUnitExecutor.create().bind( kbase );
DataSource<Object> input = executor.newDataSource( "input" );
DataSource<Person> persons = executor.newDataSource( "persons",
new Person( "John", 42 ),
new Person( "Sally", 4 ) );
List<String> results = new ArrayList<>();
executor.bindVariable( "results", results );
You can now insert some objects into the input data source and execute the Unit0
rule unit:
ds.insert("test");
ds.insert(3);
ds.insert(4);
executor.run(Unit0.class);
[Sally>3, John>3]
In this example, the rule unit named AgeCheckUnit
is considered a singleton class and then executed only once, with the minAge
variable set to 3
. Both the String "test"
and the Integer 4
inserted into the input data source can also trigger a second execution with the minAge
variable set to 4
. However, the second execution does not occur because another rule unit with the same identity has already been evaluated.
To resolve this rule unit identity conflict, override the getUnitIdentity()
method in the AgeCheckUnit
class to include also the minAge
variable in the rule unit identity:
AgeCheckUnit
rule unit to override the getUnitIdentity()
methodpublic class AgeCheckUnit implements RuleUnit {
...
@Override
public Identity getUnitIdentity() {
return new Identity(getClass(), minAge);
}
}
With this override in place, the previous example rule unit execution produces the following output:
[John>4, Sally>3, John>3]
The rule units with minAge
set to 3
and 4
are now considered two different rule units and both are executed.
4.1.13. Performance tuning considerations with DRL
The following key concepts or suggested practices can help you optimize DRL rules and Drools engine performance. These concepts are summarized in this section as a convenience and are explained in more detail in the cross-referenced documentation, where applicable. This section will expand or change as needed with new releases of Drools.
- Define the property and value of pattern constraints from left to right
-
In DRL pattern constraints, ensure that the fact property name is on the left side of the operator and that the value (constant or a variable) is on the right side. The property name must always be the key in the index and not the value. For example, write
Person( firstName == "John" )
instead ofPerson( "John" == firstName )
. Defining the constraint property and value from right to left can hinder Drools engine performance.For more information about DRL patterns and constraints, see Rule conditions in DRL (WHEN).
- Use equality operators more than other operator types in pattern constraints when possible
-
Although the Drools engine supports many DRL operator types that you can use to define your business rule logic, the equality operator
==
is evaluated most efficiently by the Drools engine. Whenever practical, use this operator instead of other operator types. For example, the patternPerson( firstName == "John" )
is evaluated more efficiently thanPerson( firstName != "OtherName" )
. In some cases, using only equality operators might be impractical, so consider all of your business logic needs and options as you use DRL operators. - List the most restrictive rule conditions first
-
For rules with multiple conditions, list the conditions from most to least restrictive so that the Drools engine can avoid assessing the entire set of conditions if the more restrictive conditions are not met.
For example, the following conditions are part of a travel-booking rule that applies a discount to travelers who book both a flight and a hotel together. In this scenario, customers rarely book hotels with flights to receive this discount, so the hotel condition is rarely met and the rule is rarely executed. Therefore, the first condition ordering is more efficient because it prevents the Drools engine from evaluating the flight condition frequently and unnecessarily when the hotel condition is not met.
Preferred condition order: hotel and flightwhen $h:hotel() // Rarely booked $f:flight()
Inefficient condition order: flight and hotelwhen $f:flight() $h:hotel() // Rarely booked
For more information about DRL patterns and constraints, see Rule conditions in DRL (WHEN).
- Avoid iterating over large collections of objects with excessive
from
clauses -
Avoid using the
from
condition element in DRL rules to iterate over large collections of objects, as shown in the following example:Example conditions withfrom
clausewhen $c: Company() $e : Employee ( salary > 100000.00) from $c.employees
In such cases, the Drools engine iterates over the large graph every time the rule condition is evaluated and impedes rule evaluation.
Alternatively, instead of adding an object with a large graph that the Drools engine must iterate over frequently, add the collection directly to the KIE session and then join the collection in the condition, as shown in the following example:
Example conditions withoutfrom
clausewhen $c: Company(); Employee (salary > 100000.00, company == $c)
In this example, the Drools engine iterates over the list only one time and can evaluate rules more efficiently.
For more information about the
from
element or other DRL condition elements, see Supported rule condition elements in DRL (keywords). - Use Drools engine event listeners instead of
System.out.println
statements in rules for debug logging -
You can use
System.out.println
statements in your rule actions for debug logging and console output, but doing this for many rules can impede rule evaluation. As a more efficient alternative, use the built-in Drools engine event listeners when possible. If these listeners do not meet your requirements, use a system logging utility supported by the Drools engine, such as Logback, Apache Commons Logging, or Apache Log4j.For more information about supported Drools engine event listeners and logging utilities, see Drools engine event listeners and debug logging.
4.2. Domain Specific Languages
Domain Specific Languages (or DSLs) are a way of creating a rule language that is dedicated to your problem domain. A set of DSL definitions consists of transformations from DSL "sentences" to DRL constructs, which lets you use of all the underlying rule language and engine features. Given a DSL, you write rules in DSL rule (or DSLR) files, which will be translated into DRL files.
DSL and DSLR files are plain text files, and you can use any text editor to create and modify them. But there are also DSL and DSLR editors, both in the IDE as well as in the web based BRMS, and you can use those as well, although they may not provide you with the full DSL functionality.
4.2.1. When to Use a DSL
DSLs can serve as a layer of separation between rule authoring (and rule authors) and the technical intricacies resulting from the modelling of domain object and the Drools engine’s native language and methods. If your rules need to be read and validated by domain experts (such as business analysts, for instance) who are not programmers, you should consider using a DSL; it hides implementation details and focuses on the rule logic proper. DSL sentences can also act as "templates" for conditional elements and consequence actions that are used repeatedly in your rules, possibly with minor variations. You may define DSL sentences as being mapped to these repeated phrases, with parameters providing a means for accommodating those variations.
DSLs have no impact on the Drools engine at runtime, they are just a compile time feature, requiring a special parser and transformer.
4.2.2. DSL Basics
The Drools DSL mechanism allows you to customise conditional expressions and consequence actions. A global substitution mechanism ("keyword") is also available.
[when]Something is {colour}=Something(colour=="{colour}")
In the preceding example, [when]
indicates the scope of the expression, i.e., whether it is valid for the LHS or the RHS of a rule.
The part after the bracketed keyword is the expression that you use in the rule; typically a natural language expression, but it doesn’t have to be.
The part to the right of the equal sign ("=") is the mapping of the expression into the rule language.
The form of this string depends on its destination, RHS or LHS.
If it is for the LHS, then it ought to be a term according to the regular LHS syntax; if it is for the RHS then it might be a Java statement.
Whenever the DSL parser matches a line from the rule file written in the DSL with an expression in the DSL definition, it performs three steps of string manipulation.
First, it extracts the string values appearing where the expression contains variable names in braces (here: {colour}
). Then, the values obtained from these captures are then interpolated wherever that name, again enclosed in braces, occurs on the right hand side of the mapping.
Finally, the interpolated string replaces whatever was matched by the entire expression in the line of the DSL rule file.
Note that the expressions (i.e., the strings on the left hand side of the equal sign) are used as regular expressions in a pattern matching operation against a line of the DSL rule file, matching all or part of a line. This means you can use (for instance) a '?' to indicate that the preceding character is optional. One good reason to use this is to overcome variations in natural language phrases of your DSL. But, given that these expressions are regular expression patterns, this also means that all "magic" characters of Java’s pattern syntax have to be escaped with a preceding backslash ('\').
It is important to note that the compiler transforms DSL rule files line by line. In the above example, all the text after "Something is " to the end of the line is captured as the replacement value for "{colour}", and this is used for interpolating the target string. This may not be exactly what you want. For instance, when you intend to merge different DSL expressions to generate a composite DRL pattern, you need to transform a DSLR line in several independent operations. The best way to achieve this is to ensure that the captures are surrounded by characteristic text - words or even single characters. As a result, the matching operation done by the parser plucks out a substring from somewhere within the line. In the example below, quotes are used as distinctive characters. Note that the characters that surround the capture are not included during interpolation, just the contents between them.
As a rule of thumb, use quotes for textual data that a rule editor may want to enter.
You can also enclose the capture with words to ensure that the text is correctly matched.
Both is illustrated by the following example.
Note that a single line such as Something is "green" and
another solid thing
is now correctly expanded.
[when]something is "{colour}"=Something(colour=="{colour}")
[when]another {state} thing=OtherThing(state=="{state})"
It is a good idea to avoid punctuation (other than quotes or apostrophes) in your DSL expressions as much as possible. The main reason is that punctuation is easy to forget for rule authors using your DSL. Another reason is that parentheses, the period and the question mark are magic characters, requiring escaping in the DSL definition.
In a DSL mapping, the braces "{" and "}" should only be used to enclose a variable definition or reference, resulting in a capture. If they should occur literally, either in the expression or within the replacement text on the right hand side, they must be escaped with a preceding backslash ("\"):
[then]do something= if (foo) \{ doSomething(); \}
If braces "{" and "}" should appear in the replacement string of a DSL definition, escape them with a backslash ('\'). |
# This is a comment to be ignored.
[when]There is a person with name of "{name}"=Person(name=="{name}")
[when]Person is at least {age} years old and lives in "{location}"=
Person(age >= {age}, location=="{location}")
[then]Log "{message}"=System.out.println("{message}");
[when]And = and
Given the above DSL examples, the following examples show the expansion of various DSLR snippets:
There is a person with name of "Kitty"
==> Person(name="Kitty")
Person is at least 42 years old and lives in "Atlanta"
==> Person(age >= 42, location="Atlanta")
Log "boo"
==> System.out.println("boo");
There is a person with name of "Bob" And Person is at least 30 years old and lives in "Utah"
==> Person(name="Bob") and Person(age >= 30, location="Utah")
Don’t forget that if you are capturing plain text from a DSL rule line and want to use it as a string literal in the expansion, you must provide the quotes on the right hand side of the mapping. |
You can chain DSL expressions together on one line, as long as it is clear to the parser where one ends and the next one begins and where the text representing a parameter ends. (Otherwise you risk getting all the text until the end of the line as a parameter value.) The DSL expressions are tried, one after the other, according to their order in the DSL definition file. After any match, all remaining DSL expressions are investigated, too.
The resulting DRL text may consist of more than one line.
Line ends are in the replacement text are written as \n
.
4.2.3. Adding Constraints to Facts
A common requirement when writing rule conditions is to be able to add an arbitrary combination of constraints to a pattern. Given that a fact type may have many fields, having to provide an individual DSL statement for each combination would be plain folly.
The DSL facility allows you to add constraints to a pattern by a simple convention: if your DSL expression starts with a hyphen (minus character, "-") it is assumed to be a field constraint and, consequently, is is added to the last pattern line preceding it.
For an example, lets take look at class Cheese
, with the following fields: type, price, age and country.
We can express some LHS condition in normal DRL like the following
Cheese(age < 5, price == 20, type=="stilton", country=="ch")
The DSL definitions given below result in three DSL phrases which may be used to create any combination of constraint involving these fields.
[when]There is a Cheese with=Cheese()
[when]- age is less than {age}=age<{age}
[when]- type is '{type}'=type=='{type}'
[when]- country equal to '{country}'=country=='{country}'
You can then write rules with conditions like the following:
There is a Cheese with
- age is less than 42
- type is 'stilton'
The parser will pick up a line beginning with "-" and add it as a constraint to the preceding pattern, inserting a comma when it is required. For the preceding example, the resulting DRL is:
Cheese(age<42, type=='stilton')
Combining all numeric fields with all relational operators (according to the DSL expression "age is less than…" in the preceding example) produces an unwieldy amount of DSL entries. But you can define DSL phrases for the various operators and even a generic expression that handles any field constraint, as shown below. (Notice that the expression definition contains a regular expression in addition to the variable name.)
[when][]is less than or equal to=<=
[when][]is less than=<
[when][]is greater than or equal to=>=
[when][]is greater than=>
[when][]is equal to===
[when][]equals===
[when][]There is a Cheese with=Cheese()
[when][]- {field:\w*} {operator} {value:\d*}={field} {operator} {value}
Given these DSL definitions, you can write rules with conditions such as:
There is a Cheese with
- age is less than 42
- rating is greater than 50
- type equals 'stilton'
In this specific case, a phrase such as "is less than" is replaced by <
, and then the line matches the last DSL entry.
This removes the hyphen, but the final result is still added as a constraint to the preceding pattern.
After processing all of the lines, the resulting DRL text is:
Cheese(age<42, rating > 50, type=='stilton')
The order of the entries in the DSL is important if separate DSL expressions are intended to match the same line, one after the other. |
4.2.4. Developing a DSL
A good way to get started is to write representative samples of the rules your application requires, and to test them as you develop. This will provide you with a stable framework of conditional elements and their constraints. Rules, both in DRL and in DSLR, refer to entities according to the data model representing the application data that should be subject to the reasoning process defined in rules. Notice that writing rules is generally easier if most of the data model’s types are facts.
Given an initial set of rules, it should be possible to identify recurring or similar code snippets and to mark variable parts as parameters. This provides reliable leads as to what might be a handy DSL entry. Also, make sure you have a full grasp of the jargon the domain experts are using, and base your DSL phrases on this vocabulary.
You may postpone implementation decisions concerning conditions and actions during this first design phase by leaving certain conditional elements and actions in their DRL form by prefixing a line with a greater sign (">"). (This is also handy for inserting debugging statements.)
During the next development phase, you should find that the DSL configuration stabilizes pretty quickly. New rules can be written by reusing the existing DSL definitions, or by adding a parameter to an existing condition or consequence entry.
Try to keep the number of DSL entries small. Using parameters lets you apply the same DSL sentence for similar rule patterns or constraints. But do not exaggerate: authors using the DSL should still be able to identify DSL phrases by some fixed text.
4.2.5. DSL and DSLR Reference
A DSL file is a text file in a line-oriented format. Its entries are used for transforming a DSLR file into a file according to DRL syntax.
-
A line starting with "" or "//" (with or without preceding white space) is treated as a comment. A comment line starting with "/" is scanned for words requesting a debug option, see below.
-
Any line starting with an opening bracket ("[") is assumed to be the first line of a DSL entry definition.
-
Any other line is appended to the preceding DSL entry definition, with the line end replaced by a space.
A DSL entry consists of the following four parts:
-
A scope definition, written as one of the keywords "when" or "condition", "then" or "consequence", "*" and "keyword", enclosed in brackets ("[" and "]"). This indicates whether the DSL entry is valid for the condition or the consequence of a rule, or both. A scope indication of "keyword" means that the entry has global significance, i.e., it is recognized anywhere in a DSLR file.
-
A type definition, written as a Java class name, enclosed in brackets. This part is optional unless the next part begins with an opening bracket. An empty pair of brackets is valid, too.
-
A DSL expression consists of a (Java) regular expression, with any number of embedded variable definitions, terminated by an equal sign ("="). A variable definition is enclosed in braces ("{" and "}"). It consists of a variable name and two optional attachments, separated by colons (":"). If there is one attachment, it is a regular expression for matching text that is to be assigned to the variable; if there are two attachments, the first one is a hint for the GUI editor and the second one the regular expression.
Note that all characters that are "magic" in regular expressions must be escaped with a preceding backslash ("\") if they should occur literally within the expression.
-
The remaining part of the line after the delimiting equal sign is the replacement text for any DSLR text matching the regular expression. It may contain variable references, i.e., a variable name enclosed in braces. Optionally, the variable name may be followed by an exclamation mark ("!") and a transformation function, see below.
Note that braces ("{" and "}") must be escaped with a preceding backslash ("\") if they should occur literally within the replacement string.
Debugging of DSL expansion can be turned on, selectively, by using a comment line starting with "#/" which may contain one or more words from the table presented below. The resulting output is written to standard output.
Word | Description |
---|---|
result |
Prints the resulting DRL text, with line numbers. |
steps |
Prints each expansion step of condition and consequence lines. |
keyword |
Dumps the internal representation of all DSL entries with scope "keyword". |
when |
Dumps the internal representation of all DSL entries with scope "when" or "*". |
then |
Dumps the internal representation of all DSL entries with scope "then" or "*". |
usage |
Displays a usage statistic of all DSL entries. |
Below are some sample DSL definitions, with comments describing the language features they illustrate.
# Comment: DSL examples
#/ debug: display result and usage
# keyword definition: replaces "regula" by "rule"
[keyword][]regula=rule
# conditional element: "T" or "t", "a" or "an", convert matched word
[when][][Tt]here is an? {entity:\w+}=
${entity!lc}: {entity!ucfirst} ()
# consequence statement: convert matched word, literal braces
[then][]update {entity:\w+}=modify( ${entity!lc} )\{ \}
The transformation of a DSLR file proceeds as follows:
-
The text is read into memory.
-
Each of the "keyword" entries is applied to the entire text. First, the regular expression from the keyword definition is modified by replacing white space sequences with a pattern matching any number of white space characters, and by replacing variable definitions with a capture made from the regular expression provided with the definition, or with the default (".*?"). Then, the DSLR text is searched exhaustively for occurrences of strings matching the modified regular expression. Substrings of a matching string corresponding to variable captures are extracted and replace variable references in the corresponding replacement text, and this text replaces the matching string in the DSLR text.
-
Sections of the DSLR text between "when" and "then", and "then" and "end", respectively, are located and processed in a uniform manner, line by line, as described below.
For a line, each DSL entry pertaining to the line’s section is taken in turn, in the order it appears in the DSL file. Its regular expression part is modified: white space is replaced by a pattern matching any number of white space characters; variable definitions with a regular expression are replaced by a capture with this regular expression, its default being ".*?". If the resulting regular expression matches all or part of the line, the matched part is replaced by the suitably modified replacement text.
Modification of the replacement text is done by replacing variable references with the text corresponding to the regular expression capture. This text may be modified according to the string transformation function given in the variable reference; see below for details.
If there is a variable reference naming a variable that is not defined in the same entry, the expander substitutes a value bound to a variable of that name, provided it was defined in one of the preceding lines of the current rule.
-
If a DSLR line in a condition is written with a leading hyphen, the expanded result is inserted into the last line, which should contain a pattern CE, i.e., a type name followed by a pair of parentheses. if this pair is empty, the expanded line (which should contain a valid constraint) is simply inserted, otherwise a comma (",") is inserted beforehand.
If a DSLR line in a consequence is written with a leading hyphen, the expanded result is inserted into the last line, which should contain a "modify" statement, ending in a pair of braces ("{" and "}"). If this pair is empty, the expanded line (which should contain a valid method call) is simply inserted, otherwise a comma (",") is inserted beforehand.
It is currently not possible to use a line with a leading hyphen to insert text into other conditional element forms (e.g., "accumulate") or it may only work for the first insertion (e.g., "eval"). |
All string transformation functions are described in the following table.
Name | Description |
---|---|
uc |
Converts all letters to upper case. |
lc |
Converts all letters to lower case. |
ucfirst |
Converts the first letter to upper case, and all other letters to lower case. |
num |
Extracts all digits and "-" from the string. If the last two digits in the original string are preceded by "." or ",", a decimal period is inserted in the corresponding position. |
a?b/c |
Compares the string with string a, and if they are equal, replaces it with b, otherwise with c. But c can be another triplet a, b, c, so that the entire structure is, in fact, a translation table. |
The following DSL examples show how to use string transformation functions.
# definitions for conditions
[when][]There is an? {entity}=${entity!lc}: {entity!ucfirst}()
[when][]- with an? {attr} greater than {amount}={attr} <= {amount!num}
[when][]- with a {what} {attr}={attr} {what!positive?>0/negative?%lt;0/zero?==0/ERROR}
A file containing a DSL definition has to be put under the resources folder or any of its subfolders like any other drools artifact.
It must have the extension .dsl
, or alternatively be marked with type ResourceType.DSL
.
when programmatically added to a KieFileSystem
.
For a file using DSL definition, the extension .dslr
should be used, while it can be added to a KieFileSystem
with type ResourceType.DSLR
.
For parsing and expanding a DSLR file the DSL configuration is read and supplied to the parser. Thus, the parser can "recognize" the DSL expressions and transform them into native rule language expressions.
5. Decision Model and Notation (DMN)
5.1. Decision Model and Notation (DMN)
Decision Model and Notation (DMN) is a standard established by the Object Management Group (OMG) for describing and modeling operational decisions. DMN defines an XML schema that enables DMN models to be shared between DMN-compliant platforms and across organizations so that business analysts and business rules developers can collaborate in designing and implementing DMN decision services. The DMN standard is similar to and can be used together with the Business Process Model and Notation (BPMN) standard for designing and modeling business processes.
For more information about the background and applications of DMN, see the OMG Decision Model and Notation specification.
5.1.1. DMN conformance levels
The DMN specification defines three incremental levels of conformance in a software implementation. A product that claims compliance at one level must also be compliant with any preceding levels. For example, a conformance level 3 implementation must also include the supported components in conformance levels 1 and 2. For the formal definitions of each conformance level, see the OMG Decision Model and Notation specification.
The following list summarizes the three DMN conformance levels:
- Conformance level 1
-
A DMN conformance level 1 implementation supports decision requirement diagrams (DRDs), decision logic, and decision tables, but decision models are not executable. Any language can be used to define the expressions, including natural, unstructured languages.
- Conformance level 2
-
A DMN conformance level 2 implementation includes the requirements in conformance level 1, and supports Simplified Friendly Enough Expression Language (S-FEEL) expressions and fully executable decision models.
- Conformance level 3
-
A DMN conformance level 3 implementation includes the requirements in conformance levels 1 and 2, and supports Friendly Enough Expression Language (FEEL) expressions, the full set of boxed expressions, and fully executable decision models.
Drools provides design and runtime support for DMN 1.2 models at conformance level 3, and runtime-only support for DMN 1.1 and 1.3 models at conformance level 3. You can design your DMN models directly in Business Central or import existing DMN models into your Drools projects for deployment and execution. Any DMN 1.1 models that you import into Business Central, open in the DMN designer, and save are converted to DMN 1.2 models. DMN 1.3 models are not supported in the DMN designer in Business Central.
5.1.2. DMN decision requirements diagram (DRD) components
A decision requirements diagram (DRD) is a visual representation of your DMN model. This diagram consists of one or more decision requirements graphs (DRGs) that represent a particular domain of an overall DRD. The DRGs trace business decisions using decision nodes, business knowledge models, sources of business knowledge, input data, and decision services.
The following table summarizes the components in a DRD:
Component | Description | Notation | |
---|---|---|---|
Elements |
Decision |
Node where one or more input elements determine an output based on defined decision logic. |
|
Business knowledge model |
Reusable function with one or more decision elements. Decisions that have the same logic but depend on different sub-input data or sub-decisions use business knowledge models to determine which procedure to follow. |
||
Knowledge source |
External authorities, documents, committees, or policies that regulate a decision or business knowledge model. Knowledge sources are references to real-world factors rather than executable business rules. |
||
Input data |
Information used in a decision node or a business knowledge model. Input data usually includes business-level concepts or objects relevant to the business, such as loan applicant data used in a lending strategy. |
||
Decision service |
Top-level decision containing a set of reusable decisions published as a service for invocation. A decision service can be invoked from an external application or a BPMN business process. |
||
Requirement connectors |
Information requirement |
Connection from an input data node or decision node to another decision node that requires the information. |
|
Knowledge requirement |
Connection from a business knowledge model to a decision node or to another business knowledge model that invokes the decision logic. |
||
Authority requirement |
Connection from an input data node or a decision node to a dependent knowledge source or from a knowledge source to a decision node, business knowledge model, or another knowledge source. |
||
Artifacts |
Text annotation |
Explanatory note associated with an input data node, decision node, business knowledge model, or knowledge source. |
|
Association |
Connection from an input data node, decision node, business knowledge model, or knowledge source to a text annotation. |
The following table summarizes the permitted connectors between DRD elements:
Starts from | Connects to | Connection type | Example |
---|---|---|---|
Decision |
Decision |
Information requirement |
|
Business knowledge model |
Decision |
Knowledge requirement |
|
Business knowledge model |
|||
Decision service |
Decision |
Knowledge requirement |
|
Business knowledge model |
|||
Input data |
Decision |
Information requirement |
|
Knowledge source |
Authority requirement |
||
Knowledge source |
Decision |
Authority requirement |
|
Business knowledge model |
|||
Knowledge source |
|||
Decision |
Text annotation |
Association |
|
Business knowledge model |
|||
Knowledge source |
|||
Input data |
The following example DRD illustrates some of these DMN components in practice:
The following example DRD illustrates DMN components that are part of a reusable decision service:
In a DMN decision service node, the decision nodes in the bottom segment incorporate input data from outside of the decision service to arrive at a final decision in the top segment of the decision service node. The resulting top-level decisions from the decision service are then implemented in any subsequent decisions or business knowledge requirements of the DMN model. You can reuse DMN decision services in other DMN models to apply the same decision logic with different input data and different outgoing connections.
5.1.3. Rule expressions in FEEL
Friendly Enough Expression Language (FEEL) is an expression language defined by the Object Management Group (OMG) DMN specification. FEEL expressions define the logic of a decision in a DMN model. FEEL is designed to facilitate both decision modeling and execution by assigning semantics to the decision model constructs. FEEL expressions in decision requirements diagrams (DRDs) occupy table cells in boxed expressions for decision nodes and business knowledge models.
For more information about FEEL in DMN, see the OMG Decision Model and Notation specification.
5.1.3.1. Variable and function names in FEEL
Unlike many traditional expression languages, Friendly Enough Expression Language (FEEL) supports spaces and a few special characters as part of variable and function names. A FEEL name must start with a letter
, ?
, or _
element. The unicode letter characters are also allowed. Variable names cannot start with a language keyword, such as and
, true
, or every
. The remaining characters in a variable name can be any of the starting characters, as well as digits
, white spaces, and special characters such as +
, -
, /
, *
, '
, and .
.
For example, the following names are all valid FEEL names:
-
Age
-
Birth Date
-
Flight 234 pre-check procedure
Several limitations apply to variable and function names in FEEL:
- Ambiguity
-
The use of spaces, keywords, and other special characters as part of names can make FEEL ambiguous. The ambiguities are resolved in the context of the expression, matching names from left to right. The parser resolves the variable name as the longest name matched in scope. You can use
( )
to disambiguate names if necessary. - Spaces in names
-
The DMN specification limits the use of spaces in FEEL names. According to the DMN specification, names can contain multiple spaces but not two consecutive spaces.
In order to make the language easier to use and avoid common errors due to spaces, Drools removes the limitation on the use of consecutive spaces. Drools supports variable names with any number of consecutive spaces, but normalizes them into a single space. For example, the variable references
First Name
with one space andFirst Name
with two spaces are both acceptable in Drools.Drools also normalizes the use of other white spaces, like the non-breakable white space that is common in web pages, tabs, and line breaks. From a Drools FEEL engine perspective, all of these characters are normalized into a single white space before processing.
- The keyword
in
-
The keyword
in
is the only keyword in the language that cannot be used as part of a variable name. Although the specifications allow the use of keywords in the middle of variable names, the use ofin
in variable names conflicts with the grammar definition offor
,every
andsome
expression constructs.
5.1.3.2. Data types in FEEL
Friendly Enough Expression Language (FEEL) supports the following data types:
-
Numbers
-
Strings
-
Boolean values
-
Dates
-
Time
-
Date and time
-
Days and time duration
-
Years and months duration
-
Functions
-
Contexts
-
Ranges (or intervals)
-
Lists
The DMN specification currently does not provide an explicit way of declaring a variable as a function , context , range , or list , but Drools extends the DMN built-in types to support variables of these types.
|
The following list describes each data type:
- Numbers
-
Numbers in FEEL are based on the IEEE 754-2008 Decimal 128 format, with 34 digits of precision. Internally, numbers are represented in Java as
BigDecimals
withMathContext DECIMAL128
. FEEL supports only one number data type, so the same type is used to represent both integers and floating point numbers.FEEL numbers use a dot (
.
) as a decimal separator. FEEL does not support-INF
,+INF
, orNaN
. FEEL usesnull
to represent invalid numbers.Drools extends the DMN specification and supports additional number notations:
-
Scientific: You can use scientific notation with the suffix
e<exp>
orE<exp>
. For example,1.2e3
is the same as writing the expression1.2*10**3
, but is a literal instead of an expression. -
Hexadecimal: You can use hexadecimal numbers with the prefix
0x
. For example,0xff
is the same as the decimal number255
. Both uppercase and lowercase letters are supported. For example,0XFF
is the same as0xff
. -
Type suffixes: You can use the type suffixes
f
,F
,d
,D
,l
, andL
. These suffixes are ignored.
-
- Strings
-
Strings in FEEL are any sequence of characters delimited by double quotation marks.
Example:
"John Doe"
- Boolean values
-
FEEL uses three-valued boolean logic, so a boolean logic expression may have values
true
,false
, ornull
. - Dates
-
Date literals are not supported in FEEL, but you can use the built-in
date()
function to construct date values. Date strings in FEEL follow the format defined in the XML Schema Part 2: Datatypes document. The format is"YYYY-MM-DD"
whereYYYY
is the year with four digits,MM
is the number of the month with two digits, andDD
is the number of the day.Example:
date( "2017-06-23" )
Date objects have time equal to
"00:00:00"
, which is midnight. The dates are considered to be local, without a timezone. - Time
-
Time literals are not supported in FEEL, but you can use the built-in
time()
function to construct time values. Time strings in FEEL follow the format defined in the XML Schema Part 2: Datatypes document. The format is"hh:mm:ss[.uuu][(+-)hh:mm]"
wherehh
is the hour of the day (from00
to23
),mm
is the minutes in the hour, andss
is the number of seconds in the minute. Optionally, the string may define the number of milliseconds (uuu
) within the second and contain a positive (+
) or negative (-
) offset from UTC time to define its timezone. Instead of using an offset, you can use the letterz
to represent the UTC time, which is the same as an offset of-00:00
. If no offset is defined, the time is considered to be local.Examples:
time( "04:25:12" ) time( "14:10:00+02:00" ) time( "22:35:40.345-05:00" ) time( "15:00:30z" )
Time values that define an offset or a timezone cannot be compared to local times that do not define an offset or a timezone.
- Date and time
-
Date and time literals are not supported in FEEL, but you can use the built-in
date and time()
function to construct date and time values. Date and time strings in FEEL follow the format defined in the XML Schema Part 2: Datatypes document. The format is"<date>T<time>"
, where<date>
and<time>
follow the prescribed XML schema formatting, conjoined byT
.Examples:
date and time( "2017-10-22T23:59:00" ) date and time( "2017-06-13T14:10:00+02:00" ) date and time( "2017-02-05T22:35:40.345-05:00" ) date and time( "2017-06-13T15:00:30z" )
Date and time values that define an offset or a timezone cannot be compared to local date and time values that do not define an offset or a timezone.
If your implementation of the DMN specification does not support spaces in the XML schema, use the keyword dateTime
as a synonym ofdate and time
. - Days and time duration
-
Days and time duration literals are not supported in FEEL, but you can use the built-in
duration()
function to construct days and time duration values. Days and time duration strings in FEEL follow the format defined in the XML Schema Part 2: Datatypes document, but are restricted to only days, hours, minutes and seconds. Months and years are not supported.Examples:
duration( "P1DT23H12M30S" ) duration( "P23D" ) duration( "PT12H" ) duration( "PT35M" )
If your implementation of the DMN specification does not support spaces in the XML schema, use the keyword dayTimeDuration
as a synonym ofdays and time duration
. - Years and months duration
-
Years and months duration literals are not supported in FEEL, but you can use the built-in
duration()
function to construct days and time duration values. Years and months duration strings in FEEL follow the format defined in the XML Schema Part 2: Datatypes document, but are restricted to only years and months. Days, hours, minutes, or seconds are not supported.Examples:
duration( "P3Y5M" ) duration( "P2Y" ) duration( "P10M" ) duration( "P25M" )
If your implementation of the DMN specification does not support spaces in the XML schema, use the keyword yearMonthDuration
as a synonym ofyears and months duration
. - Functions
-
FEEL has
function
literals (or anonymous functions) that you can use to create functions. The DMN specification currently does not provide an explicit way of declaring a variable as afunction
, but Drools extends the DMN built-in types to support variables of functions.Example:
function(a, b) a + b
In this example, the FEEL expression creates a function that adds the parameters
a
andb
and returns the result. - Contexts
-
FEEL has
context
literals that you can use to create contexts. Acontext
in FEEL is a list of key and value pairs, similar to maps in languages like Java. The DMN specification currently does not provide an explicit way of declaring a variable as acontext
, but Drools extends the DMN built-in types to support variables of contexts.Example:
{ x : 5, y : 3 }
In this example, the expression creates a context with two entries,
x
andy
, representing a coordinate in a chart.In DMN 1.2, another way to create contexts is to create an item definition that contains the list of keys as attributes, and then declare the variable as having that item definition type.
The Drools DMN API supports DMN
ItemDefinition
structural types in aDMNContext
represented in two ways:-
User-defined Java type: Must be a valid JavaBeans object defining properties and getters for each of the components in the DMN
ItemDefinition
. If necessary, you can also use the@FEELProperty
annotation for those getters representing a component name which would result in an invalid Java identifier. -
java.util.Map
interface: The map needs to define the appropriate entries, with the keys corresponding to the component name in the DMNItemDefinition
.
-
- Ranges (or intervals)
-
FEEL has
range
literals that you can use to create ranges or intervals. Arange
in FEEL is a value that defines a lower and an upper bound, where either can be open or closed. The DMN specification currently does not provide an explicit way of declaring a variable as arange
, but Drools extends the DMN built-in types to support variables of ranges.The syntax of a range is defined in the following formats:
range := interval_start endpoint '..' endpoint interval_end interval_start := open_start | closed_start open_start := '(' | ']' closed_start := '[' interval_end := open_end | closed_end open_end := ')' | '[' closed_end := ']' endpoint := expression
The expression for the endpoint must return a comparable value, and the lower bound endpoint must be lower than the upper bound endpoint.
For example, the following literal expression defines an interval between
1
and10
, including the boundaries (a closed interval on both endpoints):[ 1 .. 10 ]
The following literal expression defines an interval between 1 hour and 12 hours, including the lower boundary (a closed interval), but excluding the upper boundary (an open interval):
[ duration("PT1H") .. duration("PT12H") ]
You can use ranges in decision tables to test for ranges of values, or use ranges in simple literal expressions. For example, the following literal expression returns
true
if the value of a variablex
is between0
and100
:x in [ 1 .. 100 ]
- Lists
-
FEEL has
list
literals that you can use to create lists of items. Alist
in FEEL is represented by a comma-separated list of values enclosed in square brackets. The DMN specification currently does not provide an explicit way of declaring a variable as alist
, but Drools extends the DMN built-in types to support variables of lists.Example:
[ 2, 3, 4, 5 ]
All lists in FEEL contain elements of the same type and are immutable. Elements in a list can be accessed by index, where the first element is
1
. Negative indexes can access elements starting from the end of the list so that-1
is the last element.For example, the following expression returns the second element of a list
x
:x[2]
The following expression returns the second-to-last element of a list
x
:x[-2]
Elements in a list can also be counted by the function
count
, which uses the list of elements as the parameter.For example, the following expression returns
4
:count([ 2, 3, 4, 5 ])
5.1.4. DMN decision logic in boxed expressions
Boxed expressions in DMN are tables that you use to define the underlying logic of decision nodes and business knowledge models in a decision requirements diagram (DRD) or decision requirements graph (DRG). Some boxed expressions can contain other boxed expressions, but the top-level boxed expression corresponds to the decision logic of a single DRD artifact. While DRDs with one or more DRGs represent the flow of a DMN decision model, boxed expressions define the actual decision logic of individual nodes. DRDs and boxed expressions together form a complete and functional DMN decision model.
The following are the types of DMN boxed expressions:
-
Decision tables
-
Literal expressions
-
Contexts
-
Relations
-
Functions
-
Invocations
-
Lists
Drools does not provide boxed list expressions in Business Central, but supports a FEEL list data type that you can use in boxed literal expressions. For more information about the list data type and other FEEL data types in Drools, see Data types in FEEL.
|
All Friendly Enough Expression Language (FEEL) expressions that you use in your boxed expressions must conform to the FEEL syntax requirements in the OMG Decision Model and Notation specification.
5.1.4.1. DMN decision tables
A decision table in DMN is a visual representation of one or more business rules in a tabular format. You use decision tables to define rules for a decision node that applies those rules at a given point in the decision model. Each rule consists of a single row in the table, and includes columns that define the conditions (input) and outcome (output) for that particular row. The definition of each row is precise enough to derive the outcome using the values of the conditions. Input and output values can be FEEL expressions or defined data type values.
For example, the following decision table determines credit score ratings based on a defined range of a loan applicant’s credit score:
The following decision table determines the next step in a lending strategy for applicants depending on applicant loan eligibility and the bureau call type:
The following decision table determines applicant qualification for a loan as the concluding decision node in a loan prequalification decision model:
Decision tables are a popular way of modeling rules and decision logic, and are used in many methodologies (such as DMN) and implementation frameworks (such as Drools).
Drools supports both DMN decision tables and Drools-native decision tables, but they are different types of assets with different syntax requirements and are not interchangeable. For more information about Drools-native decision tables in Drools, see Spreadsheet decision tables. |
Hit policies in DMN decision tables
Hit policies determine how to reach an outcome when multiple rules in a decision table match the provided input values. For example, if one rule in a decision table applies a sales discount to military personnel and another rule applies a discount to students, then when a customer is both a student and in the military, the decision table hit policy must indicate whether to apply one discount or the other (Unique, First) or both discounts (Collect Sum). You specify the single character of the hit policy (U, F, C+) in the upper-left corner of the decision table.
The following decision table hit policies are supported in DMN:
-
Unique (U): Permits only one rule to match. Any overlap raises an error.
-
Any (A): Permits multiple rules to match, but they must all have the same output. If multiple matching rules do not have the same output, an error is raised.
-
Priority (P): Permits multiple rules to match, with different outputs. The output that comes first in the output values list is selected.
-
First (F): Uses the first match in rule order.
-
Collect (C+, C>, C<, C#): Aggregates output from multiple rules based on an aggregation function.
-
Collect ( C ): Aggregates values in an arbitrary list.
-
Collect Sum (C+): Outputs the sum of all collected values. Values must be numeric.
-
Collect Min (C<): Outputs the minimum value among the matches. The resulting values must be comparable, such as numbers, dates, or text (lexicographic order).
-
Collect Max (C>): Outputs the maximum value among the matches. The resulting values must be comparable, such as numbers, dates or text (lexicographic order).
-
Collect Count (C#): Outputs the number of matching rules.
-
5.1.4.2. Boxed literal expressions
A boxed literal expression in DMN is a literal FEEL expression as text in a table cell, typically with a labeled column and an assigned data type. You use boxed literal expressions to define simple or complex node logic or decision data directly in FEEL for a particular node in a decision. Literal FEEL expressions must conform to FEEL syntax requirements in the OMG Decision Model and Notation specification.
For example, the following boxed literal expression defines the minimum acceptable PITI calculation (principal, interest, taxes, and insurance) in a lending decision, where acceptable rate
is a variable defined in the DMN model:
The following boxed literal expression sorts a list of possible dating candidates (soul mates) in an online dating application based on their score on criteria such as age, location, and interests:
5.1.4.3. Boxed context expressions
A boxed context expression in DMN is a set of variable names and values with a result value. Each name-value pair is a context entry. You use context expressions to represent data definitions in decision logic and set a value for a desired decision element within the DMN decision model. A value in a boxed context expression can be a data type value or FEEL expression, or can contain a nested sub-expression of any type, such as a decision table, a literal expression, or another context expression.
For example, the following boxed context expression defines the factors for sorting delayed passengers in a flight-rebooking decision model, based on defined data types (tPassengerTable
, tFlightNumberList
):
The following boxed context expression defines the factors that determine whether a loan applicant can meet minimum mortgage payments based on principal, interest, taxes, and insurance (PITI), represented as a front-end ratio calculation with a sub-context expression:
5.1.4.4. Boxed relation expressions
A boxed relation expression in DMN is a traditional data table with information about given entities, listed as rows. You use boxed relation tables to define decision data for relevant entities in a decision at a particular node. Boxed relation expressions are similar to context expressions in that they set variable names and values, but relation expressions contain no result value and list all variable values based on a single defined variable in each column.
For example, the following boxed relation expression provides information about employees in an employee rostering decision:
5.1.4.5. Boxed function expressions
A boxed function expression in DMN is a parameterized boxed expression containing a literal FEEL expression, a nested context expression of an external JAVA or PMML function, or a nested boxed expression of any type. By default, all business knowledge models are defined as boxed function expressions. You use boxed function expressions to call functions on your decision logic and to define all business knowledge models.
For example, the following boxed function expression determines airline flight capacity in a flight-rebooking decision model:
The following boxed function expression contains a basic Java function as a context expression for determining absolute value in a decision model calculation:
The following boxed function expression determines a monthly mortgage installment as a business knowledge model in a lending decision, with the function value defined as a nested context expression:
The following boxed function expression uses a PMML model included in the DMN file to define the minimum acceptable PITI calculation (principal, interest, taxes, and insurance) in a lending decision:
5.1.4.6. Boxed invocation expressions
A boxed invocation expression in DMN is a boxed expression that invokes a business knowledge model. A boxed invocation expression contains the name of the business knowledge model to be invoked and a list of parameter bindings. Each binding is represented by two boxed expressions on a row: The box on the left contains the name of a parameter and the box on the right contains the binding expression whose value is assigned to the parameter to evaluate the invoked business knowledge model. You use boxed invocations to invoke at a particular decision node a business knowledge model defined in the decision model.
For example, the following boxed invocation expression invokes a Reassign Next Passenger
business knowledge model as the concluding decision node in a flight-rebooking decision model:
The following boxed invocation expression invokes an InstallmentCalculation
business knowledge model to calculate a monthly installment amount for a loan before proceeding to affordability decisions:
5.1.5. DMN model example
The following is a real-world DMN model example that demonstrates how you can use decision modeling to reach a decision based on input data, circumstances, and company guidelines. In this scenario, a flight from San Diego to New York is canceled, requiring the affected airline to find alternate arrangements for its inconvenienced passengers.
First, the airline collects the information necessary to determine how best to get the travelers to their destinations:
- Input data
-
-
List of flights
-
List of passengers
-
- Decisions
-
-
Prioritize the passengers who will get seats on a new flight
-
Determine which flights those passengers will be offered
-
- Business knowledge models
-
-
The company process for determining passenger priority
-
Any flights that have space available
-
Company rules for determining how best to reassign inconvenienced passengers
-
The airline then uses the DMN standard to model its decision process in the following decision requirements diagram (DRD) for determining the best rebooking solution:
Similar to flowcharts, DRDs use shapes to represent the different elements in a process. Ovals contain the two necessary input data, rectangles contain the decision points in the model, and rectangles with clipped corners (business knowledge models) contain reusable logic that can be repeatedly invoked.
The DRD draws logic for each element from boxed expressions that provide variable definitions using FEEL expressions or data type values.
Some boxed expressions are basic, such as the following decision for establishing a prioritized waiting list:
Some boxed expressions are more complex with greater detail and calculation, such as the following business knowledge model for reassigning the next delayed passenger:
The following is the DMN source file for this decision model:
<dmn:definitions xmlns="https://www.drools.org/kie-dmn/Flight-rebooking" xmlns:dmn="http://www.omg.org/spec/DMN/20151101/dmn.xsd" xmlns:feel="http://www.omg.org/spec/FEEL/20140401" id="_0019_flight_rebooking" name="0019-flight-rebooking" namespace="https://www.drools.org/kie-dmn/Flight-rebooking">
<dmn:itemDefinition id="_tFlight" name="tFlight">
<dmn:itemComponent id="_tFlight_Flight" name="Flight Number">
<dmn:typeRef>feel:string</dmn:typeRef>
</dmn:itemComponent>
<dmn:itemComponent id="_tFlight_From" name="From">
<dmn:typeRef>feel:string</dmn:typeRef>
</dmn:itemComponent>
<dmn:itemComponent id="_tFlight_To" name="To">
<dmn:typeRef>feel:string</dmn:typeRef>
</dmn:itemComponent>
<dmn:itemComponent id="_tFlight_Dep" name="Departure">
<dmn:typeRef>feel:dateTime</dmn:typeRef>
</dmn:itemComponent>
<dmn:itemComponent id="_tFlight_Arr" name="Arrival">
<dmn:typeRef>feel:dateTime</dmn:typeRef>
</dmn:itemComponent>
<dmn:itemComponent id="_tFlight_Capacity" name="Capacity">
<dmn:typeRef>feel:number</dmn:typeRef>
</dmn:itemComponent>
<dmn:itemComponent id="_tFlight_Status" name="Status">
<dmn:typeRef>feel:string</dmn:typeRef>
</dmn:itemComponent>
</dmn:itemDefinition>
<dmn:itemDefinition id="_tFlightTable" isCollection="true" name="tFlightTable">
<dmn:typeRef>tFlight</dmn:typeRef>
</dmn:itemDefinition>
<dmn:itemDefinition id="_tPassenger" name="tPassenger">
<dmn:itemComponent id="_tPassenger_Name" name="Name">
<dmn:typeRef>feel:string</dmn:typeRef>
</dmn:itemComponent>
<dmn:itemComponent id="_tPassenger_Status" name="Status">
<dmn:typeRef>feel:string</dmn:typeRef>
</dmn:itemComponent>
<dmn:itemComponent id="_tPassenger_Miles" name="Miles">
<dmn:typeRef>feel:number</dmn:typeRef>
</dmn:itemComponent>
<dmn:itemComponent id="_tPassenger_Flight" name="Flight Number">
<dmn:typeRef>feel:string</dmn:typeRef>
</dmn:itemComponent>
</dmn:itemDefinition>
<dmn:itemDefinition id="_tPassengerTable" isCollection="true" name="tPassengerTable">
<dmn:typeRef>tPassenger</dmn:typeRef>
</dmn:itemDefinition>
<dmn:itemDefinition id="_tFlightNumberList" isCollection="true" name="tFlightNumberList">
<dmn:typeRef>feel:string</dmn:typeRef>
</dmn:itemDefinition>
<dmn:inputData id="i_Flight_List" name="Flight List">
<dmn:variable name="Flight List" typeRef="tFlightTable"/>
</dmn:inputData>
<dmn:inputData id="i_Passenger_List" name="Passenger List">
<dmn:variable name="Passenger List" typeRef="tPassengerTable"/>
</dmn:inputData>
<dmn:decision name="Prioritized Waiting List" id="d_PrioritizedWaitingList">
<dmn:variable name="Prioritized Waiting List" typeRef="tPassengerTable"/>
<dmn:informationRequirement>
<dmn:requiredInput href="#i_Passenger_List"/>
</dmn:informationRequirement>
<dmn:informationRequirement>
<dmn:requiredInput href="#i_Flight_List"/>
</dmn:informationRequirement>
<dmn:knowledgeRequirement>
<dmn:requiredKnowledge href="#b_PassengerPriority"/>
</dmn:knowledgeRequirement>
<dmn:context>
<dmn:contextEntry>
<dmn:variable name="Cancelled Flights" typeRef="tFlightNumberList"/>
<dmn:literalExpression>
<dmn:text>Flight List[ Status = "cancelled" ].Flight Number</dmn:text>
</dmn:literalExpression>
</dmn:contextEntry>
<dmn:contextEntry>
<dmn:variable name="Waiting List" typeRef="tPassengerTable"/>
<dmn:literalExpression>
<dmn:text>Passenger List[ list contains( Cancelled Flights, Flight Number ) ]</dmn:text>
</dmn:literalExpression>
</dmn:contextEntry>
<dmn:contextEntry>
<dmn:literalExpression>
<dmn:text>sort( Waiting List, passenger priority )</dmn:text>
</dmn:literalExpression>
</dmn:contextEntry>
</dmn:context>
</dmn:decision>
<dmn:decision name="Rebooked Passengers" id="d_RebookedPassengers">
<dmn:variable name="Rebooked Passengers" typeRef="tPassengerTable"/>
<dmn:informationRequirement>
<dmn:requiredDecision href="#d_PrioritizedWaitingList"/>
</dmn:informationRequirement>
<dmn:informationRequirement>
<dmn:requiredInput href="#i_Flight_List"/>
</dmn:informationRequirement>
<dmn:knowledgeRequirement>
<dmn:requiredKnowledge href="#b_ReassignNextPassenger"/>
</dmn:knowledgeRequirement>
<dmn:invocation>
<dmn:literalExpression>
<dmn:text>reassign next passenger</dmn:text>
</dmn:literalExpression>
<dmn:binding>
<dmn:parameter name="Waiting List"/>
<dmn:literalExpression>
<dmn:text>Prioritized Waiting List</dmn:text>
</dmn:literalExpression>
</dmn:binding>
<dmn:binding>
<dmn:parameter name="Reassigned Passengers List"/>
<dmn:literalExpression>
<dmn:text>[]</dmn:text>
</dmn:literalExpression>
</dmn:binding>
<dmn:binding>
<dmn:parameter name="Flights"/>
<dmn:literalExpression>
<dmn:text>Flight List</dmn:text>
</dmn:literalExpression>
</dmn:binding>
</dmn:invocation>
</dmn:decision>
<dmn:businessKnowledgeModel id="b_PassengerPriority" name="passenger priority">
<dmn:encapsulatedLogic>
<dmn:formalParameter name="Passenger1" typeRef="tPassenger"/>
<dmn:formalParameter name="Passenger2" typeRef="tPassenger"/>
<dmn:decisionTable hitPolicy="UNIQUE">
<dmn:input id="b_Passenger_Priority_dt_i_P1_Status" label="Passenger1.Status">
<dmn:inputExpression typeRef="feel:string">
<dmn:text>Passenger1.Status</dmn:text>
</dmn:inputExpression>
<dmn:inputValues>
<dmn:text>"gold", "silver", "bronze"</dmn:text>
</dmn:inputValues>
</dmn:input>
<dmn:input id="b_Passenger_Priority_dt_i_P2_Status" label="Passenger2.Status">
<dmn:inputExpression typeRef="feel:string">
<dmn:text>Passenger2.Status</dmn:text>
</dmn:inputExpression>
<dmn:inputValues>
<dmn:text>"gold", "silver", "bronze"</dmn:text>
</dmn:inputValues>
</dmn:input>
<dmn:input id="b_Passenger_Priority_dt_i_P1_Miles" label="Passenger1.Miles">
<dmn:inputExpression typeRef="feel:string">
<dmn:text>Passenger1.Miles</dmn:text>
</dmn:inputExpression>
</dmn:input>
<dmn:output id="b_Status_Priority_dt_o" label="Passenger1 has priority">
<dmn:outputValues>
<dmn:text>true, false</dmn:text>
</dmn:outputValues>
<dmn:defaultOutputEntry>
<dmn:text>false</dmn:text>
</dmn:defaultOutputEntry>
</dmn:output>
<dmn:rule id="b_Passenger_Priority_dt_r1">
<dmn:inputEntry id="b_Passenger_Priority_dt_r1_i1">
<dmn:text>"gold"</dmn:text>
</dmn:inputEntry>
<dmn:inputEntry id="b_Passenger_Priority_dt_r1_i2">
<dmn:text>"gold"</dmn:text>
</dmn:inputEntry>
<dmn:inputEntry id="b_Passenger_Priority_dt_r1_i3">
<dmn:text>>= Passenger2.Miles</dmn:text>
</dmn:inputEntry>
<dmn:outputEntry id="b_Passenger_Priority_dt_r1_o1">
<dmn:text>true</dmn:text>
</dmn:outputEntry>
</dmn:rule>
<dmn:rule id="b_Passenger_Priority_dt_r2">
<dmn:inputEntry id="b_Passenger_Priority_dt_r2_i1">
<dmn:text>"gold"</dmn:text>
</dmn:inputEntry>
<dmn:inputEntry id="b_Passenger_Priority_dt_r2_i2">
<dmn:text>"silver","bronze"</dmn:text>
</dmn:inputEntry>
<dmn:inputEntry id="b_Passenger_Priority_dt_r2_i3">
<dmn:text>-</dmn:text>
</dmn:inputEntry>
<dmn:outputEntry id="b_Passenger_Priority_dt_r2_o1">
<dmn:text>true</dmn:text>
</dmn:outputEntry>
</dmn:rule>
<dmn:rule id="b_Passenger_Priority_dt_r3">
<dmn:inputEntry id="b_Passenger_Priority_dt_r3_i1">
<dmn:text>"silver"</dmn:text>
</dmn:inputEntry>
<dmn:inputEntry id="b_Passenger_Priority_dt_r3_i2">
<dmn:text>"silver"</dmn:text>
</dmn:inputEntry>
<dmn:inputEntry id="b_Passenger_Priority_dt_r3_i3">
<dmn:text>>= Passenger2.Miles</dmn:text>
</dmn:inputEntry>
<dmn:outputEntry id="b_Passenger_Priority_dt_r3_o1">
<dmn:text>true</dmn:text>
</dmn:outputEntry>
</dmn:rule>
<dmn:rule id="b_Passenger_Priority_dt_r4">
<dmn:inputEntry id="b_Passenger_Priority_dt_r4_i1">
<dmn:text>"silver"</dmn:text>
</dmn:inputEntry>
<dmn:inputEntry id="b_Passenger_Priority_dt_r4_i2">
<dmn:text>"bronze"</dmn:text>
</dmn:inputEntry>
<dmn:inputEntry id="b_Passenger_Priority_dt_r4_i3">
<dmn:text>-</dmn:text>
</dmn:inputEntry>
<dmn:outputEntry id="b_Passenger_Priority_dt_r4_o1">
<dmn:text>true</dmn:text>
</dmn:outputEntry>
</dmn:rule>
<dmn:rule id="b_Passenger_Priority_dt_r5">
<dmn:inputEntry id="b_Passenger_Priority_dt_r5_i1">
<dmn:text>"bronze"</dmn:text>
</dmn:inputEntry>
<dmn:inputEntry id="b_Passenger_Priority_dt_r5_i2">
<dmn:text>"bronze"</dmn:text>
</dmn:inputEntry>
<dmn:inputEntry id="b_Passenger_Priority_dt_r5_i3">
<dmn:text>>= Passenger2.Miles</dmn:text>
</dmn:inputEntry>
<dmn:outputEntry id="b_Passenger_Priority_dt_r5_o1">
<dmn:text>true</dmn:text>
</dmn:outputEntry>
</dmn:rule>
</dmn:decisionTable>
</dmn:encapsulatedLogic>
<dmn:variable name="passenger priority" typeRef="feel:boolean"/>
</dmn:businessKnowledgeModel>
<dmn:businessKnowledgeModel id="b_ReassignNextPassenger" name="reassign next passenger">
<dmn:encapsulatedLogic>
<dmn:formalParameter name="Waiting List" typeRef="tPassengerTable"/>
<dmn:formalParameter name="Reassigned Passengers List" typeRef="tPassengerTable"/>
<dmn:formalParameter name="Flights" typeRef="tFlightTable"/>
<dmn:context>
<dmn:contextEntry>
<dmn:variable name="Next Passenger" typeRef="tPassenger"/>
<dmn:literalExpression>
<dmn:text>Waiting List[1]</dmn:text>
</dmn:literalExpression>
</dmn:contextEntry>
<dmn:contextEntry>
<dmn:variable name="Original Flight" typeRef="tFlight"/>
<dmn:literalExpression>
<dmn:text>Flights[ Flight Number = Next Passenger.Flight Number ][1]</dmn:text>
</dmn:literalExpression>
</dmn:contextEntry>
<dmn:contextEntry>
<dmn:variable name="Best Alternate Flight" typeRef="tFlight"/>
<dmn:literalExpression>
<dmn:text>Flights[ From = Original Flight.From and To = Original Flight.To and Departure > Original Flight.Departure and Status = "scheduled" and has capacity( item, Reassigned Passengers List ) ][1]</dmn:text>
</dmn:literalExpression>
</dmn:contextEntry>
<dmn:contextEntry>
<dmn:variable name="Reassigned Passenger" typeRef="tPassenger"/>
<dmn:context>
<dmn:contextEntry>
<dmn:variable name="Name" typeRef="feel:string"/>
<dmn:literalExpression>
<dmn:text>Next Passenger.Name</dmn:text>
</dmn:literalExpression>
</dmn:contextEntry>
<dmn:contextEntry>
<dmn:variable name="Status" typeRef="feel:string"/>
<dmn:literalExpression>
<dmn:text>Next Passenger.Status</dmn:text>
</dmn:literalExpression>
</dmn:contextEntry>
<dmn:contextEntry>
<dmn:variable name="Miles" typeRef="feel:number"/>
<dmn:literalExpression>
<dmn:text>Next Passenger.Miles</dmn:text>
</dmn:literalExpression>
</dmn:contextEntry>
<dmn:contextEntry>
<dmn:variable name="Flight Number" typeRef="feel:string"/>
<dmn:literalExpression>
<dmn:text>Best Alternate Flight.Flight Number</dmn:text>
</dmn:literalExpression>
</dmn:contextEntry>
</dmn:context>
</dmn:contextEntry>
<dmn:contextEntry>
<dmn:variable name="Remaining Waiting List" typeRef="tPassengerTable"/>
<dmn:literalExpression>
<dmn:text>remove( Waiting List, 1 )</dmn:text>
</dmn:literalExpression>
</dmn:contextEntry>
<dmn:contextEntry>
<dmn:variable name="Updated Reassigned Passengers List" typeRef="tPassengerTable"/>
<dmn:literalExpression>
<dmn:text>append( Reassigned Passengers List, Reassigned Passenger )</dmn:text>
</dmn:literalExpression>
</dmn:contextEntry>
<dmn:contextEntry>
<dmn:literalExpression>
<dmn:text>if count( Remaining Waiting List ) > 0 then reassign next passenger( Remaining Waiting List, Updated Reassigned Passengers List, Flights ) else Updated Reassigned Passengers List</dmn:text>
</dmn:literalExpression>
</dmn:contextEntry>
</dmn:context>
</dmn:encapsulatedLogic>
<dmn:variable name="reassign next passenger" typeRef="tPassengerTable"/>
<dmn:knowledgeRequirement>
<dmn:requiredKnowledge href="#b_HasCapacity"/>
</dmn:knowledgeRequirement>
</dmn:businessKnowledgeModel>
<dmn:businessKnowledgeModel id="b_HasCapacity" name="has capacity">
<dmn:encapsulatedLogic>
<dmn:formalParameter name="flight" typeRef="tFlight"/>
<dmn:formalParameter name="rebooked list" typeRef="tPassengerTable"/>
<dmn:literalExpression>
<dmn:text>flight.Capacity > count( rebooked list[ Flight Number = flight.Flight Number ] )</dmn:text>
</dmn:literalExpression>
</dmn:encapsulatedLogic>
<dmn:variable name="has capacity" typeRef="feel:boolean"/>
</dmn:businessKnowledgeModel>
</dmn:definitions>
5.2. DMN support in Drools
Drools provides design and runtime support for DMN 1.2 models at conformance level 3, and runtime-only support for DMN 1.1 and 1.3 models at conformance level 3. You can integrate DMN models with your Drools decision services in several ways:
-
Design your DMN models directly in Business Central using the DMN designer.
-
Import DMN files into your project in Business Central (Menu → Design → Projects → Import Asset). Any DMN 1.1 models that you import into Business Central, open in the DMN designer, and save are converted to DMN 1.2 models.
-
Package DMN files as part of your project knowledge JAR (KJAR) file without Business Central.
In addition to all DMN conformance level 3 requirements, Drools also includes enhancements and fixes to FEEL and DMN model components to optimize the experience of implementing DMN decision services with Drools. From a platform perspective, DMN models are like any other business asset in Drools, such as DRL files or spreadsheet decision tables, that you can include in your Drools project and deploy to KIE Server in order to start your DMN decision services.
For more information about including external DMN files with your Drools project packaging and deployment method, see Build, Deploy, Utilize and Run.
5.2.1. FEEL enhancements in Drools
Drools includes the following enhancements and other changes to FEEL in the current DMN implementation:
-
Space Sensitivity: This DMN implementation of the FEEL language is space insensitive. The goal is to avoid non-deterministic behavior based on the context and differences in behavior based on invisible characters, such as white spaces. This means that for this implementation, a variable named
first name
with one space is exactly the same asfirst name
with two spaces in it. -
List functions
or()
andand()
: The specification defines two list functions namedor()
andand()
. However, according to the FEEL grammar, these are not valid function names, asand
andor
are reserved keywords. This implementation renames these functions toany()
andall()
respectively, in anticipation for DMN 1.2. -
Keyword
in
cannot be used in variable names: The specification defines that any keyword can be reused as part of a variable name, but the ambiguities caused with thefor … in … return
loop prevent the reuse of thein
keyword. All other keywords are supported as part of variable names. -
Keywords are not supported in attributes of anonymous types: FEEL is not a strongly typed language and the parser must resolve ambiguity in name parts of an attribute of an anonymous type. The parser supports reusable keywords as part of a variable name defined in the scope, but the parser does not support keywords in attributes of an anonymous type. For example,
for item in Order.items return Federal Tax for Item( item )
is a valid and supported FEEL expression, where a function namedFederal Tax for Item(…)
can be defined and invoked correctly in the scope. However, the expressionfor i in [ {x and y : true, n : 1}, {x and y : false, n: 2} ] return i.x and y
is not supported because anonymous types are defined in the iteration context of thefor
expression and the parser cannot resolve the ambiguity. -
Support for date and time literals on ranges: According to the grammar rules #8, #18, #19, #34 and #62,
date and time
literals are supported in ranges (pages 110-111). Chapter 10.3.2.7 on page 114, on the other hand, contradicts the grammar and says they are not supported. This implementation chose to follow the grammar and supportdate and time
literals on ranges, as well as extend the specification to support any arbitrary expression (see extensions below). -
Invalid time syntax: Chapter 10.3.2.3.4 on page 112 and bullet point about
time
on page 131 both state that thetime
string lexical representation follows the XML Schema Datatypes specification as well as ISO 8601. According to the XML Schema specification (https://www.w3.org/TR/xmlschema-2/#time), the lexical representation of a time follows the patternhh:mm:ss.sss
without any leading character. The DMN specification uses a leading "T" in several examples, that we understand is a typo and not in accordance with the standard. -
Support for scientific and hexadecimal notations: This implementation supports scientific and hexadecimal notation for numbers. For example,
1.2e5
(scientific notation),0xD5
(hexadecimal notation). -
Support for expressions as end points in ranges: This implementation supports expressions as endpoints for ranges. For example,
[date("2016-11-24")..date("2016-11-27")]
-
Support for additional types: The specification only defines the following as basic types of the language:
-
number
-
string
-
boolean
-
days and time duration
-
years and month duration
-
time
-
date and time
For completeness and orthogonality, this implementation also supports the following types:
-
context
-
list
-
range
-
function
-
unary test
-
-
Support for unary tests: For completeness and orthogonality, unary tests are supported as first class citizens in the language. They are functions with an implicit single parameter and can be invoked in the same way as functions. For example,
UnaryTestAsFunction.feel{ is minor : < 18, Bob is minor : is minor( bob.age ) }
-
Support for additional built-in functions: The following additional functions are supported:
-
now()
: Returns the current local date and time. -
today()
: Returns the current local date. -
decision table()
: Returns a decision table function, although the specification mentions a decision table. The function on page 114 is not implementable as defined. -
string( mask, p… )
: Returns a string formatted as per the mask. See Java String.format() for details on the mask syntax. For example,string( "%4.2f", 7.1298 )
returns the string"7.12"
.
-
-
Support for additional date and time arithmetics: Subtracting two dates returns a day and time duration with the number of days between the two dates, ignoring daylight savings. For example,
DateArithmetic.feeldate( "2017-05-12" ) - date( "2017-04-25" ) = duration( "P17D" )
5.2.2. DMN model enhancements in Drools
Drools includes the following enhancements to DMN model support in the current DMN implementation:
-
Support for types with spaces on names: The DMN XML schema defines type refs such as QNames. The QNames do not allow spaces. Therefore, it is not possible to use types like FEEL
date and time
,days and time duration
oryears and months duration
. This implementation does parse such typerefs as strings and allows type names with spaces. However, in order to comply with the XML schema, it also adds the following aliases to such types that can be used instead:-
date and time
=dateTime
-
days and time duration
=duration
ordayTimeDuration
-
years and months duration
=duration
oryearMonthDuration
Note that, for the "duration" types, the user can simply use
duration
and the Drools engine will infer the proper duration, eitherdays and time duration
oryears and months duration
.
-
-
Lists support heterogeneous element types: Currently this implementation supports lists with heterogeneous element types. This is an experimental extension and does limit the functionality of some functions and filters. This decision will be re-evaluated in the future.
-
TypeRef link between Decision Tables and Item Definitions: On decision tables/input clause, if no values list is defined, the Drools engine automatically checks the type reference and applies the allowed values check if it is defined.
5.2.3. Configurable DMN properties in Drools
Drools provides the following DMN properties that you can configure when you execute your DMN models on KIE Server or on your client application:
- org.kie.dmn.strictConformance
-
When enabled, this property disables by default any extensions or profiles provided beyond the DMN standard, such as some helper functions or enhanced features of DMN 1.2 backported into DMN 1.1. You can use this property to configure the Drools engine to support only pure DMN features, such as when running the DMN Technology Compatibility Kit (TCK).
Default value:
false
-Dorg.kie.dmn.strictConformance=true
- org.kie.dmn.runtime.typecheck
-
When enabled, this property enables verification of actual values conforming to their declared types in the DMN model, as input or output of DRD elements. You can use this property to verify whether data supplied to the DMN model or produced by the DMN model is compliant with what is specified in the model.
Default value:
false
-Dorg.kie.dmn.runtime.typecheck=true
- org.kie.dmn.decisionservice.coercesingleton
-
By default, this property makes the result of a decision service defining a single output decision be the single value of the output decision value. When disabled, this property makes the result of a decision service defining a single output decision be a
context
with the single entry for that decision. You can use this property to adjust your decision service outputs according to your project requirements.Default value:
true
-Dorg.kie.dmn.decisionservice.coercesingleton=false
- org.kie.dmn.profiles.$PROFILE_NAME
-
When valorized with a Java fully qualified name, this property loads a DMN profile onto the Drools engine at start time. You can use this property to implement a predefined DMN profile with supported features different from or beyond the DMN standard. For example, if you are creating DMN models using the Signavio DMN modeller, use this property to implement features from the Signavio DMN profile into your DMN decision service.
-Dorg.kie.dmn.profiles.signavio=org.kie.dmn.signavio.KieDMNSignavioProfile
- org.kie.dmn.runtime.listeners.$LISTENER_NAME
-
When valorized with a Java fully qualified name, this property loads and registers a DMN Runtime Listener onto the Drools engine at start time. You can use this property to register a DMN listener in order to be notified of several events during DMN model evaluations. You can also configure this property in the
kmodule.xml
file in your project.-Dorg.kie.dmn.runtime.listeners.mylistener=org.acme.MyDMNListener
- org.kie.dmn.compiler.execmodel
-
When enabled, this property enables DMN decision table logic to be compiled into executable rule models during run time. You can use this property to evaluate DMN decision table logic more efficiently. This property is helpful when the executable model compilation was not originally performed during project compile time. Enabling this property may result in added compile time during the first evaluation by the Drools engine, but subsequent compilations are more efficient.
Default value:
false
-Dorg.kie.dmn.compiler.execmodel=true
5.3. Creating and editing DMN models in Business Central
You can use the DMN designer in Business Central to design DMN decision requirements diagrams (DRDs) and define decision logic for a complete and functional DMN decision model. Drools provides design and runtime support for DMN 1.2 models at conformance level 3, and includes enhancements and fixes to FEEL and DMN model components to optimize the experience of implementing DMN decision services with Drools. Drools also provides runtime-only support for DMN 1.1 and 1.3 models at conformance level 3, but any DMN 1.1 models that you import into Business Central, open in the DMN designer, and save are converted to DMN 1.2 models. DMN 1.3 models are not supported in the DMN designer in Business Central.
-
In Business Central, go to Menu → Design → Projects and click the project name.
-
Create or import a DMN file in your Business Central project.
To create a DMN file, click Add Asset → DMN, enter an informative DMN model name, select the appropriate Package, and click Ok.
To import an existing DMN file, click Import Asset, enter the DMN model name, select the appropriate Package, select the DMN file to upload, and click Ok.
The new DMN file is now listed in the DMN panel of the Project Explorer, and the DMN decision requirements diagram (DRD) canvas appears.
If you imported a DMN file that does not contain layout information, the imported decision requirements diagram (DRD) is formatted automatically in the DMN designer. Click Save in the DMN designer to save the DRD layout.
If an imported DRD is not automatically formatted, you can select the Perform automatic layout icon in the upper-right toolbar in the DMN designer to format the DRD.
-
Begin adding components to your new or imported DMN decision requirements diagram (DRD) by clicking and dragging one of the DMN nodes from the left toolbar:
Figure 92. Adding DRD componentsThe following DRD components are available:
-
Decision: Use this node for a DMN decision, where one or more input elements determine an output based on defined decision logic.
-
Business knowledge model: Use this node for reusable functions with one or more decision elements. Decisions that have the same logic but depend on different sub-input data or sub-decisions use business knowledge models to determine which procedure to follow.
-
Knowledge source: Use this node for external authorities, documents, committees, or policies that regulate a decision or business knowledge model. Knowledge sources are references to real-world factors rather than executable business rules.
-
Input data: Use this node for information used in a decision node or a business knowledge model. Input data usually includes business-level concepts or objects relevant to the business, such as loan applicant data used in a lending strategy.
-
Text annotation: Use this node for explanatory notes associated with an input data node, decision node, business knowledge model, or knowledge source.
-
Decision service: Use this node to enclose a set of reusable decisions implemented as a decision service for invocation. A decision service can be used in other DMN models and can be invoked from an external application or a BPMN business process.
-
-
In the DMN designer canvas, double-click the new DRD node to enter an informative node name.
-
If the node is a decision or business knowledge model, select the node to display the node options and click the Edit icon to open the DMN boxed expression designer to define the decision logic for the node:
Figure 93. Opening a new decision node boxed expressionFigure 94. Opening a new business knowledge model boxed expressionBy default, all business knowledge models are defined as boxed function expressions containing a literal FEEL expression, a nested context expression of an external JAVA or PMML function, or a nested boxed expression of any type.
For decision nodes, you click the undefined table to select the type of boxed expression you want to use, such as a boxed literal expression, boxed context expression, decision table, or other DMN boxed expression.
Figure 95. Selecting the logic type for a decision nodeFor business knowledge models, you click the top-left function cell to select the function type, or right-click the function value cell, select Clear, and select a boxed expression of another type.
Figure 96. Selecting the function or other logic type for a business knowledge model -
In the selected boxed expression designer for either a decision node (any expression type) or business knowledge model (function expression), click the applicable table cells to define the table name, variable data types, variable names and values, function parameters and bindings, or FEEL expressions to include in the decision logic.
You can right-click cells for additional actions where applicable, such as inserting or removing table rows and columns or clearing table contents.
The following is an example decision table for a decision node that determines credit score ratings based on a defined range of a loan applicant’s credit score:
Figure 97. Decision node decision table for credit score ratingThe following is an example boxed function expression for a business knowledge model that calculates mortgage payments based on principal, interest, taxes, and insurance (PITI) as a literal expression:
Figure 98. Business knowledge model function for PITI calculation -
After you define the decision logic for the selected node, click Back to "<MODEL_NAME>" to return to the DRD view.
-
For the selected DRD node, use the available connection options to create and connect to the next node in the DRD, or click and drag a new node onto the DRD canvas from the left toolbar.
The node type determines which connection options are supported. For example, an Input data node can connect to a decision node, knowledge source, or text annotation using the applicable connection type, whereas a Knowledge source node can connect to any DRD element. A Decision node can connect only to another decision or a text annotation.
The following connection types are available, depending on the node type:
-
Information requirement: Use this connection from an input data node or decision node to another decision node that requires the information.
-
Knowledge requirement: Use this connection from a business knowledge model to a decision node or to another business knowledge model that invokes the decision logic.
-
Authority requirement: Use this connection from an input data node or a decision node to a dependent knowledge source or from a knowledge source to a decision node, business knowledge model, or another knowledge source.
-
Association: Use this connection from an input data node, decision node, business knowledge model, or knowledge source to a text annotation.
Figure 99. Connecting credit score input to the credit score rating decision -
-
Continue adding and defining the remaining DRD components of your decision model. Periodically click Save in the DMN designer to save your work.
As you periodically save a DRD, the DMN designer performs a static validation of the DMN model and might produce error messages until the model is defined completely. After you finish defining the DMN model completely, if any errors remain, troubleshoot the specified problems accordingly. -
After you add and define all components of the DRD, click Save to save and validate the completed DRD.
To adjust the DRD layout, you can select the Perform automatic layout icon in the upper-right toolbar of the DMN designer.
The following is an example DRD for a loan prequalification decision model:
Figure 100. Completed DRD for loan prequalificationThe following is an example DRD for a phone call handling decision model using a reusable decision service:
Figure 101. Completed DRD for phone call handling with a decision serviceIn a DMN decision service node, the decision nodes in the bottom segment incorporate input data from outside of the decision service to arrive at a final decision in the top segment of the decision service node. The resulting top-level decisions from the decision service are then implemented in any subsequent decisions or business knowledge requirements of the DMN model. You can reuse DMN decision services in other DMN models to apply the same decision logic with different input data and different outgoing connections.
5.3.1. Defining DMN decision logic in boxed expressions in Business Central
Boxed expressions in DMN are tables that you use to define the underlying logic of decision nodes and business knowledge models in a decision requirements diagram (DRD) or decision requirements graph (DRG). Some boxed expressions can contain other boxed expressions, but the top-level boxed expression corresponds to the decision logic of a single DRD artifact. While DRDs with one or more DRGs represent the flow of a DMN decision model, boxed expressions define the actual decision logic of individual nodes. DRDs and boxed expressions together form a complete and functional DMN decision model.
You can use the DMN designer in Business Central to define decision logic for your DRD components using built-in boxed expressions.
-
A DMN file is created or imported in Business Central.
-
In Business Central, go to Menu → Design → Projects, click the project name, and select the DMN file you want to modify.
-
In the DMN designer canvas, select a decision node or business knowledge model node that you want to define and click the Edit icon to open the DMN boxed expression designer:
Figure 102. Opening a new decision node boxed expressionFigure 103. Opening a new business knowledge model boxed expressionBy default, all business knowledge models are defined as boxed function expressions containing a literal FEEL expression, a nested context expression of an external JAVA or PMML function, or a nested boxed expression of any type.
For decision nodes, you click the undefined table to select the type of boxed expression you want to use, such as a boxed literal expression, boxed context expression, decision table, or other DMN boxed expression.
Figure 104. Selecting the logic type for a decision nodeFor business knowledge model nodes, you click the top-left function cell to select the function type, or right-click the function value cell, select Clear, and select a boxed expression of another type.
Figure 105. Selecting the function or other logic type for a business knowledge model -
For this example, use a decision node and select Decision Table as the boxed expression type.
A decision table in DMN is a visual representation of one or more rules in a tabular format. Each rule consists of a single row in the table, and includes columns that define the conditions (input) and outcome (output) for that particular row.
-
Click the input column header to define the name and data type for the input condition. For example, name the input column Credit Score.FICO with a
number
data type. This column specifies numeric credit score values or ranges of loan applicants. -
Click the output column header to define the name and data type for the output values. For example, name the output column Credit Score Rating and next to the Data Type option, click Manage to go to the Data Types page where you can create a custom data type with score ratings as constraints.
Figure 106. Managing data types for a column header value -
On the Data Types page, click New Data Type to add a new data type or click Import Data Object to import an existing data object from your project that you want to use as a DMN data type.
If you import a data object from your project as a DMN data type and then that object is updated, you must re-import the data object as a DMN data type to apply the changes in your DMN model.
For this example, click New Data Type and create a Credit_Score_Rating data type as a
string
:Figure 107. Adding a new data type -
Click Add Constraints, select Enumeration from the drop-down options, and add the following constraints:
-
"Excellent"
-
"Good"
-
"Fair"
-
"Poor"
-
"Bad"
Figure 108. Adding constraints to the new data typeTo change the order of data type constraints, you can click the left end of the constraint row and drag the row as needed:
Figure 109. Dragging constraints to change constraint orderFor information about constraint types and syntax requirements for the specified data type, see the Decision Model and Notation specification.
-
-
Click OK to save the constraints and click the check mark to the right of the data type to save the data type.
-
Return to the Credit Score Rating decision table, click the Credit Score Rating column header, and set the data type to this new custom data type.
-
Use the Credit Score.FICO input column to define credit score values or ranges of values, and use the Credit Score Rating column to specify one of the corresponding ratings you defined in the Credit_Score_Rating data type.
Right-click any value cell to insert or delete rows (rules) or columns (clauses).
Figure 110. Decision node decision table for credit score rating -
After you define all rules, click the top-left corner of the decision table to define the rule Hit Policy and Builtin Aggregator (for COLLECT hit policy only).
The hit policy determines how to reach an outcome when multiple rules in a decision table match the provided input values. The built-in aggregator determines how to aggregate rule values when you use the COLLECT hit policy.
Figure 111. Defining the decision table hit policyThe following example is a more complex decision table that determines applicant qualification for a loan as the concluding decision node in the same loan prequalification decision model:
Figure 112. Decision table for loan prequalification
For boxed expression types other than decision tables, you follow these guidelines similarly to navigate the boxed expression tables and define variables and parameters for decision logic, but according to the requirements of the boxed expression type. Some boxed expressions, such as boxed literal expressions, can be single-column tables, while other boxed expressions, such as function, context, and invocation expressions, can be multi-column tables with nested boxed expressions of other types.
For example, the following boxed context expression defines the parameters that determine whether a loan applicant can meet minimum mortgage payments based on principal, interest, taxes, and insurance (PITI), represented as a front-end ratio calculation with a sub-context expression:
The following boxed function expression determines a monthly mortgage installment as a business knowledge model in a lending decision, with the function value defined as a nested context expression:
For more information and examples of each boxed expression type, see DMN decision logic in boxed expressions.
5.3.2. Creating custom data types for DMN boxed expressions in Business Central
In DMN boxed expressions in Business Central, data types determine the structure of the data that you use within an associated table, column, or field in the boxed expression. You can use default DMN data types (such as String, Number, Boolean) or you can create custom data types to specify additional fields and constraints that you want to implement for the boxed expression values.
Custom data types that you create for a boxed expression can be simple or structured:
-
Simple data types have only a name and a type assignment. Example:
Age (number)
. -
Structured data types contain multiple fields associated with a parent data type. Example: A single type
Person
containing the fieldsName (string)
,Age (number)
,Email (string)
.
-
A DMN file is created or imported in Business Central.
-
In Business Central, go to Menu → Design → Projects, click the project name, and select the DMN file you want to modify.
-
In the DMN designer canvas, select a decision node or business knowledge model for which you want to define the data types and click the Edit icon to open the DMN boxed expression designer.
-
If the boxed expression is for a decision node that is not yet defined, click the undefined table to select the type of boxed expression you want to use, such as a boxed literal expression, boxed context expression, decision table, or other DMN boxed expression.
Figure 115. Selecting the logic type for a decision node -
Click the cell for the table header, column header, or parameter field (depending on the boxed expression type) for which you want to define the data type and click Manage to go to the Data Types page where you can create a custom data type.
Figure 116. Managing data types for a column header valueYou can also set and manage custom data types for a specified decision node or business knowledge model node by selecting the Properties icon in the upper-right corner of the DMN designer:
Figure 117. Managing data types in decision requirements diagram (DRD) propertiesThe data type that you define for a specified cell in a boxed expression determines the structure of the data that you use within that associated table, column, or field in the boxed expression.
In this example, an output column Credit Score Rating for a DMN decision table defines a set of custom credit score ratings based on an applicant’s credit score.
-
On the Data Types page, click New Data Type to add a new data type or click Import Data Object to import an existing data object from your project that you want to use as a DMN data type.
If you import a data object from your project as a DMN data type and then that object is updated, you must re-import the data object as a DMN data type to apply the changes in your DMN model.
For this example, click New Data Type and create a Credit_Score_Rating data type as a
string
:Figure 118. Adding a new data typeIf the data type requires a list of items, enable the List setting.
-
Click Add Constraints, select Enumeration from the drop-down options, and add the following constraints:
-
"Excellent"
-
"Good"
-
"Fair"
-
"Poor"
-
"Bad"
Figure 119. Adding constraints to the new data typeTo change the order of data type constraints, you can click the left end of the constraint row and drag the row as needed:
Figure 120. Dragging constraints to change constraint orderFor information about constraint types and syntax requirements for the specified data type, see the Decision Model and Notation specification.
-
-
Click OK to save the constraints and click the check mark to the right of the data type to save the data type.
-
Return to the Credit Score Rating decision table, click the Credit Score Rating column header, set the data type to this new custom data type, and define the rule values for that column with the rating constraints that you specified.
Figure 121. Decision table for credit score ratingIn the DMN decision model for this scenario, the Credit Score Rating decision flows into the following Loan Prequalification decision that also requires custom data types:
Figure 122. Decision table for loan prequalification -
Continuing with this example, return to the Data Types window, click New Data Type, and create a Loan_Qualification data type as a
Structure
with no constraints.When you save the new structured data type, the first sub-field appears so that you can begin defining nested data fields in this parent data type. You can use these sub-fields in association with the parent structured data type in boxed expressions, such as nested column headers in decision tables or nested table parameters in context or function expressions.
For additional sub-fields, select the addition icon next to the Loan_Qualification data type:
Figure 123. Adding a new structured data type with nested fields -
For this example, under the structured Loan_Qualification data type, add a Qualification field with
"Qualified"
and"Not Qualified"
enumeration constraints, and a Reason field with no constraints. Add also a simple Back_End_Ratio and a Front_End_Ratio data type, both with"Sufficient"
and"Insufficient"
enumeration constraints.Click the check mark to the right of each data type that you create to save your changes.
Figure 124. Adding nested data types with constraintsTo change the order or nesting of data types, you can click the left end of the data type row and drag the row as needed:
Figure 125. Dragging data types to change data type order or nesting -
Return to the decision table and, for each column, click the column header cell, set the data type to the new corresponding custom data type, and define the rule values as needed for the column with the constraints that you specified, if applicable.
Figure 126. Decision table for loan prequalification
For boxed expression types other than decision tables, you follow these guidelines similarly to navigate the boxed expression tables and define custom data types as needed.
For example, the following boxed function expression uses custom tCandidate
and tProfile
structured data types to associate data for online dating compatibility:
5.3.3. Included models in DMN files in Business Central
In the DMN designer in Business Central, you can use the Included Models tab to include other DMN models and Predictive Model Markup Language (PMML) models from your project in a specified DMN file. When you include a DMN model within another DMN file, you can use all of the nodes and logic from both models in the same decision requirements diagram (DRD). When you include a PMML model within a DMN file, you can invoke that PMML model as a boxed function expression for a DMN decision node or business knowledge model node.
You cannot include DMN or PMML models from other projects in Business Central.
5.3.3.1. Including other DMN models within a DMN file in Business Central
In Business Central, you can include other DMN models from your project in a specified DMN file. When you include a DMN model within another DMN file, you can use all of the nodes and logic from both models in the same decision requirements diagram (DRD), but you cannot edit the nodes from the included model. To edit nodes from included models, you must update the source file for the included model directly. If you update the source file for an included DMN model, open the DMN file where the DMN model is included (or close an re-open) to verify the changes.
You cannot include DMN models from other projects in Business Central.
-
The DMN models are created or imported (as
.dmn
files) in the same project in Business Central as the DMN file in which you want to include the models.
-
In Business Central, go to Menu → Design → Projects, click the project name, and select the DMN file you want to modify.
-
In the DMN designer, click the Included Models tab.
-
Click Include Model, select a DMN model from your project in the Models list, enter a unique name for the included model, and click Include:
Figure 130. Including a DMN modelThe DMN model is added to this DMN file, and all DRD nodes from the included model are listed under Decision Components in the Decision Navigator view:
Figure 131. DMN file with decision components from the included DMN modelAll data types from the included model are also listed in read-only mode in the Data Types tab for the DMN file:
Figure 132. DMN file with data types from the included DMN model -
In the Model tab of the DMN designer, click and drag the included DRD components onto the canvas to begin implementing them in your DRD:
Figure 133. Adding DRD components from the included DMN modelTo edit DRD nodes or data types from included models, you must update the source file for the included model directly. If you update the source file for an included DMN model, open the DMN file where the DMN model is included (or close an re-open) to verify the changes.
To edit the included model name or to remove the included model from the DMN file, use the Included Models tab in the DMN designer.
When you remove an included model, any nodes from that included model that are currently used in the DRD are also removed.
5.3.3.2. Including PMML models within a DMN file in Business Central
In Business Central, you can include Predictive Model Markup Language (PMML) models from your project in a specified DMN file. When you include a PMML model within a DMN file, you can invoke that PMML model as a boxed function expression for a DMN decision node or business knowledge model node. If you update the source file for an included PMML model, you must remove and re-include the PMML model in the DMN file to apply the source changes.
You cannot include PMML models from other projects in Business Central.
-
The PMML models are imported (as
.pmml
files) in the same project in Business Central as the DMN file in which you want to include the models.
-
In your DMN project, add the following dependencies to the project
pom.xml
file to enable PMML evaluation:<!-- Required for the PMML compiler --> <dependency> <groupId>org.drools</groupId> <artifactId>kie-pmml</artifactId> <version>${drools.version}</version> <scope>provided</scope> </dependency> <!-- Alternative dependencies for JPMML Evaluator, override `kie-pmml` dependency --> <dependency> <groupId>org.kie</groupId> <artifactId>kie-dmn-jpmml</artifactId> <version>${drools.version}</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.jpmml</groupId> <artifactId>pmml-evaluator</artifactId> <version>1.4.9</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.jpmml</groupId> <artifactId>pmml-evaluator-extension</artifactId> <version>1.4.9</version> <scope>provided</scope> </dependency>
To access the project
pom.xml
file in Business Central, you can select any existing asset in the project and then in the Project Explorer menu on the left side of the screen, click the Customize View gear icon and select Repository View → pom.xml.If you want to use the full PMML specification implementation with the Java Evaluator API for PMML (JPMML), use the alternative set of JPMML dependencies in your DMN project. If the JPMML dependencies and the standard
kie-pmml
dependency are both present, thekie-pmml
dependency is disabled. For information about JPMML licensing terms, see Openscoring.io. -
If you added the JPMML dependencies in your DMN project to use the JPMML Evaluator, download the following JAR files and add them to the
~/kie-server.war/WEB-INF/lib
and~/business-central.war/WEB-INF/lib
directories in your Drools distribution:-
KIE JPMML Integration 7.33.0.Final JAR file from the online Maven repository
-
JPMML Evaluator 1.4.9 JAR file from the online Maven repository
-
JPMML Evaluator Extensions 1.4.9 JAR file from the online Maven repository
These artifacts are required to enable JPMML evaluation in KIE Server and Business Central.
-
-
In Business Central, go to Menu → Design → Projects, click the project name, and select the DMN file you want to modify.
-
In the DMN designer, click the Included Models tab.
-
Click Include Model, select a PMML model from your project in the Models list, enter a unique name for the included model, and click Include:
Figure 134. Including a PMML modelThe PMML model is added to this DMN file:
Figure 135. DMN file with included PMML model -
In the Model tab of the DMN designer, select or create the decision node or business knowledge model node in which you want to invoke the PMML model and click the Edit icon to open the DMN boxed expression designer:
Figure 136. Opening a new decision node boxed expressionFigure 137. Opening a new business knowledge model boxed expression -
Set the expression type to Function (default for business knowledge model nodes), click the top-left function cell, and select PMML.
-
In the document and model rows in the table, double-click the undefined cells to specify the included PMML document and the relevant PMML model within that document:
Figure 138. Adding a PMML model in a DMN business knowledge modelFigure 139. Example PMML definition in a DMN business knowledge modelIf you update the source file for an included PMML model, you must remove and re-include the PMML model in the DMN file to apply the source changes.
To edit the included model name or to remove the included model from the DMN file, use the Included Models tab in the DMN designer.
5.3.4. DMN model documentation in Business Central
In the DMN designer in Business Central, you can use the Documentation tab to generate a report of your DMN model that you can print or download as an HTML file for offline use. The DMN model report contains all decision requirements diagrams (DRDs), data types, and boxed expressions in your DMN model. You can use this report to share your DMN model details or as part of your internal reporting workflow.
5.3.5. DMN designer navigation and properties in Business Central
The DMN designer in Business Central provides the following additional features to help you navigate through the components and properties of decision requirements diagrams (DRDs).
- DMN file and diagram views
-
In the upper-left corner of the DMN designer, select the Project Explorer view to navigate between all DMN and other files or select the Decision Navigator view to navigate between the decision components, graphs, and boxed expressions of a selected DRD:
Figure 141. Project Explorer viewFigure 142. Decision Navigator viewThe DRD components from any DMN models included in the DMN file (in the Included Models tab) are also listed in the Decision Components panel for the DMN file. In the upper-right corner of the DMN designer, select the Explore diagram icon to view an elevated preview of the selected DRD and to navigate between the nodes of the selected DRD:
Figure 143. Explore diagram view - DRD properties and design
-
In the upper-right corner of the DMN designer, select the Properties icon to modify the identifying information, data types, and appearance of a selected DRD, DRD node, or boxed expression cell:
Figure 144. DRD node propertiesTo view the properties of the entire DRD, click the DRD canvas background instead of a specific node.
- DRD search
-
In the upper-right corner of the DMN designer, use the search bar to search for text that appears in your DRD. The search feature is especially helpful in complex DRDs with many nodes:
Figure 145. DRD search
5.4. DMN model execution
You can create or import DMN files in your Drools project using Business Central or package the DMN files as part of your project knowledge JAR (KJAR) file without Business Central. After you implement your DMN files in your Drools project, you can execute the DMN decision service by deploying the KIE container that contains it to KIE Server for remote access or by manipulating the KIE container directly as a dependency of the calling application. Other options for creating and deploying DMN knowledge packages are also available, and most are similar for all types of knowledge assets, such as DRL files or process definitions.
For information about including external DMN assets with your project packaging and deployment method, see Build, Deploy, Utilize and Run.
5.4.1. Embedding a DMN call directly in a Java application
A KIE container is local when the knowledge assets are either embedded directly into the calling program or are physically pulled in using Maven dependencies for the KJAR. You typically embed knowledge assets directly into a project if there is a tight relationship between the version of the code and the version of the DMN definition. Any changes to the decision take effect after you have intentionally updated and redeployed the application. A benefit of this approach is that proper operation does not rely on any external dependencies to the run time, which can be a limitation of locked-down environments.
Using Maven dependencies enables further flexibility because the specific version of the decision can dynamically change, (for example, by using a system property), and it can be periodically scanned for updates and automatically updated. This introduces an external dependency on the deploy time of the service, but executes the decision locally, reducing reliance on an external service being available during run time.
-
KIE Server is installed and configured, including a known user name and credentials for a user with the
kie-server
role. For installation options, see Installation and Setup (Core and IDE). -
You have built the DMN project as a KJAR artifact and deployed it to KIE Server. Ideally, you have built the DMN project as an executable model for more efficient execution:
mvn clean install -DgenerateDMNModel=yes
For more information about project packaging and deployment and executable models, see Build, Deploy, Utilize and Run.
-
In your client application, add the following dependencies to the relevant classpath of your Java project:
<!-- Required for the DMN runtime API --> <dependency> <groupId>org.kie</groupId> <artifactId>kie-dmn-core</artifactId> <version>${drools.version}</version> </dependency> <!-- Required if not using classpath KIE container --> <dependency> <groupId>org.kie</groupId> <artifactId>kie-ci</artifactId> <version>${drools.version}</version> </dependency>
The
<version>
is the Maven artifact version for Drools currently used in your project (for example, 7.33.0.Final-redhat-00002). -
Create a KIE container from
classpath
orReleaseId
:KieServices kieServices = KieServices.Factory.get(); ReleaseId releaseId = kieServices.newReleaseId( "org.acme", "my-kjar", "1.0.0" ); KieContainer kieContainer = kieServices.newKieContainer( releaseId );
Alternative option:
KieServices kieServices = KieServices.Factory.get(); KieContainer kieContainer = kieServices.getKieClasspathContainer();
-
Obtain
DMNRuntime
from the KIE container and a reference to the DMN model to be evaluated, by using the modelnamespace
andmodelName
:DMNRuntime dmnRuntime = KieRuntimeFactory.of(kieContainer.getKieBase()).get(DMNRuntime.class); String namespace = "http://www.redhat.com/_c7328033-c355-43cd-b616-0aceef80e52a"; String modelName = "dmn-movieticket-ageclassification"; DMNModel dmnModel = dmnRuntime.getModel(namespace, modelName);
-
Execute the decision services for the desired model:
DMNContext dmnContext = dmnRuntime.newContext(); (1) for (Integer age : Arrays.asList(1,12,13,64,65,66)) { dmnContext.set("Age", age); (2) DMNResult dmnResult = dmnRuntime.evaluateAll(dmnModel, dmnContext); (3) for (DMNDecisionResult dr : dmnResult.getDecisionResults()) { (4) log.info("Age: " + age + ", " + "Decision: '" + dr.getDecisionName() + "', " + "Result: " + dr.getResult()); } }
1 Instantiate a new DMN Context to be the input for the model evaluation. Note that this example is looping through the Age Classification decision multiple times. 2 Assign input variables for the input DMN context. 3 Evaluate all DMN decisions defined in the DMN model. 4 Each evaluation may result in one or more results, creating the loop. This example prints the following output:
Age 1 Decision 'AgeClassification' : Child Age 12 Decision 'AgeClassification' : Child Age 13 Decision 'AgeClassification' : Adult Age 64 Decision 'AgeClassification' : Adult Age 65 Decision 'AgeClassification' : Senior Age 66 Decision 'AgeClassification' : Senior
If the DMN model was not previously compiled as an executable model for more efficient execution, you can enable the following property when you execute your DMN models:
-Dorg.kie.dmn.compiler.execmodel=true
5.4.2. Executing a DMN service using the KIE Server Java client API
The KIE Server Java client API provides a lightweight approach to invoking a remote DMN service either through the REST or JMS interfaces of KIE Server. This approach reduces the number of runtime dependencies necessary to interact with a KIE base. Decoupling the calling code from the decision definition also increases flexibility by enabling them to iterate independently at the appropriate pace.
For more information about the KIE Server Java client API, see KIE Server Java client API for KIE containers and business assets.
-
KIE Server is installed and configured, including a known user name and credentials for a user with the
kie-server
role. For installation options, see Installation and Setup (Core and IDE). -
You have built the DMN project as a KJAR artifact and deployed it to KIE Server. Ideally, you have built the DMN project as an executable model for more efficient execution:
mvn clean install -DgenerateDMNModel=yes
For more information about project packaging and deployment and executable models, see Build, Deploy, Utilize and Run.
-
You have the ID of the KIE container containing the DMN model. If more than one model is present, you must also know the model namespace and model name of the relevant model.
-
In your client application, add the following dependency to the relevant classpath of your Java project:
<!-- Required for the KIE Server Java client API --> <dependency> <groupId>org.kie.server</groupId> <artifactId>kie-server-client</artifactId> <version>${drools.version}</version> </dependency>
The
<version>
is the Maven artifact version for Drools currently used in your project (for example, 7.33.0.Final-redhat-00002). -
Instantiate a
KieServicesClient
instance with the appropriate connection information.Example:
KieServicesConfiguration conf = KieServicesFactory.newRestConfiguration(URL, USER, PASSWORD); (1) conf.setMarshallingFormat(MarshallingFormat.JSON); (2) KieServicesClient kieServicesClient = KieServicesFactory.newKieServicesClient(conf);
1 The connection information: -
Example URL:
http://localhost:8080/kie-server/services/rest/server
-
The credentials should reference a user with the
kie-server
role.
2 The Marshalling format is an instance of org.kie.server.api.marshalling.MarshallingFormat
. It controls whether the messages will be JSON or XML. Options for Marshalling format are JSON, JAXB, or XSTREAM. -
-
Obtain a
DMNServicesClient
from the KIE server Java client connected to the related KIE Server by invoking the methodgetServicesClient()
on the KIE server Java client instance:DMNServicesClient dmnClient = kieServicesClient.getServicesClient(DMNServicesClient.class );
The
dmnClient
can now execute decision services on KIE Server. -
Execute the decision services for the desired model.
Example:
for (Integer age : Arrays.asList(1,12,13,64,65,66)) { DMNContext dmnContext = dmnClient.newContext(); (1) dmnContext.set("Age", age); (2) ServiceResponse<DMNResult> serverResp = (3) dmnClient.evaluateAll($kieContainerId, $modelNamespace, $modelName, dmnContext); DMNResult dmnResult = serverResp.getResult(); (4) for (DMNDecisionResult dr : dmnResult.getDecisionResults()) { log.info("Age: " + age + ", " + "Decision: '" + dr.getDecisionName() + "', " + "Result: " + dr.getResult()); } }
1 Instantiate a new DMN Context to be the input for the model evaluation. Note that this example is looping through the Age Classification decision multiple times. 2 Assign input variables for the input DMN Context. 3 Evaluate all the DMN Decisions defined in the DMN model: -
$kieContainerId
is the ID of the container where the KJAR containing the DMN model is deployed -
$modelNamespace
is the namespace for the model. -
$modelName
is the name for the model.
4 The DMN Result object is available from the server response. At this point, the
dmnResult
contains all the decision results from the evaluated DMN model.You can also execute only a specific DMN decision in the model by using alternative methods of the
DMNServicesClient
.If the KIE container only contains one DMN model, you can omit $modelNamespace
and$modelName
because the KIE Server API selects it by default. -
5.4.3. Executing a DMN service using the KIE Server REST API
Directly interacting with the REST endpoints of KIE Server provides the most separation between the calling code and the decision logic definition. The calling code is completely free of direct dependencies, and you can implement it in an entirely different development platform such as Node.js
or .NET
. The examples in this section demonstrate Nix-style curl commands but provide relevant information to adapt to any REST client.
For more information about the KIE Server REST API, see KIE Server REST API for KIE containers and business assets.
-
KIE Server is installed and configured, including a known user name and credentials for a user with the
kie-server
role. For installation options, see Installation and Setup (Core and IDE). -
You have built the DMN project as a KJAR artifact and deployed it to KIE Server. Ideally, you have built the DMN project as an executable model for more efficient execution:
mvn clean install -DgenerateDMNModel=yes
For more information about project packaging and deployment and executable models, see Build, Deploy, Utilize and Run.
-
You have the ID of the KIE container containing the DMN model. If more than one model is present, you must also know the model namespace and model name of the relevant model.
-
Determine the base URL for accessing the KIE Server REST API endpoints. This requires knowing the following values (with the default local deployment values as an example):
-
Host (
localhost
) -
Port (
8080
) -
Root context (
kie-server
) -
Base REST path (
services/rest/
)
Example base URL in local deployment:
http://localhost:8080/kie-server/services/rest/
-
-
Determine user authentication requirements.
When users are defined directly in the KIE Server configuration, HTTP Basic authentication is used and requires the user name and password. Successful requests require that the user have the
kie-server
role.The following example demonstrates how to add credentials to a curl request:
curl -u username:password <request>
If KIE Server is configured with Red Hat Single Sign-On, the request must include a bearer token:
curl -H "Authorization: bearer $TOKEN" <request>
-
Specify the format of the request and response. The REST API endpoints work with both JSON and XML formats and are set using request headers:
JSONcurl -H "accept: application/json" -H "content-type: application/json"
XMLcurl -H "accept: application/xml" -H "content-type: application/xml"
-
(Optional) Query the container for a list of deployed decision models:
[GET]
server/containers/{containerId}/dmn
Example curl request:
curl -u krisv:krisv -H "accept: application/xml" -X GET "http://localhost:8080/kie-server/services/rest/server/containers/MovieDMNContainer/dmn"
Sample XML output:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <response type="SUCCESS" msg="OK models successfully retrieved from container 'MovieDMNContainer'"> <dmn-model-info-list> <model> <model-namespace>http://www.redhat.com/_c7328033-c355-43cd-b616-0aceef80e52a</model-namespace> <model-name>dmn-movieticket-ageclassification</model-name> <model-id>_99</model-id> <decisions> <dmn-decision-info> <decision-id>_3</decision-id> <decision-name>AgeClassification</decision-name> </dmn-decision-info> </decisions> </model> </dmn-model-info-list> </response>
Sample JSON output:
{ "type" : "SUCCESS", "msg" : "OK models successfully retrieved from container 'MovieDMNContainer'", "result" : { "dmn-model-info-list" : { "models" : [ { "model-namespace" : "http://www.redhat.com/_c7328033-c355-43cd-b616-0aceef80e52a", "model-name" : "dmn-movieticket-ageclassification", "model-id" : "_99", "decisions" : [ { "decision-id" : "_3", "decision-name" : "AgeClassification" } ] } ] } } }
-
Execute the model:
[POST]
server/containers/{containerId}/dmn
Example curl request:
curl -u krisv:krisv -H "accept: application/json" -H "content-type: application/json" -X POST "http://localhost:8080/kie-server/services/rest/server/containers/MovieDMNContainer/dmn" -d "{ \"model-namespace\" : \"http://www.redhat.com/_c7328033-c355-43cd-b616-0aceef80e52a\", \"model-name\" : \"dmn-movieticket-ageclassification\", \"decision-name\" : [ ], \"decision-id\" : [ ], \"dmn-context\" : {\"Age\" : 66}}"
Example JSON request:
{ "model-namespace" : "http://www.redhat.com/_c7328033-c355-43cd-b616-0aceef80e52a", "model-name" : "dmn-movieticket-ageclassification", "decision-name" : [ ], "decision-id" : [ ], "dmn-context" : {"Age" : 66} }
Example XML request (JAXB format):
<?xml version="1.0" encoding="UTF-8"?> <dmn-evaluation-context> <model-namespace>http://www.redhat.com/_c7328033-c355-43cd-b616-0aceef80e52a</model-namespace> <model-name>dmn-movieticket-ageclassification</model-name> <dmn-context xsi:type="jaxbListWrapper" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <type>MAP</type> <element xsi:type="jaxbStringObjectPair" key="Age"> <value xsi:type="xs:int" xmlns:xs="http://www.w3.org/2001/XMLSchema">66</value> </element> </dmn-context> </dmn-evaluation-context>
Regardless of the request format, the request requires the following elements:
-
Model namespace
-
Model name
-
Context object containing input values
Example JSON response:
{ "type" : "SUCCESS", "msg" : "OK from container 'MovieDMNContainer'", "result" : { "dmn-evaluation-result" : { "messages" : [ ], "model-namespace" : "http://www.redhat.com/_c7328033-c355-43cd-b616-0aceef80e52a", "model-name" : "dmn-movieticket-ageclassification", "decision-name" : [ ], "dmn-context" : { "Age" : 66, "AgeClassification" : "Senior" }, "decision-results" : { "_3" : { "messages" : [ ], "decision-id" : "_3", "decision-name" : "AgeClassification", "result" : "Senior", "status" : "SUCCEEDED" } } } } }
Example XML (JAXB format) response:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <response type="SUCCESS" msg="OK from container 'MovieDMNContainer'"> <dmn-evaluation-result> <model-namespace>http://www.redhat.com/_c7328033-c355-43cd-b616-0aceef80e52a</model-namespace> <model-name>dmn-movieticket-ageclassification</model-name> <dmn-context xsi:type="jaxbListWrapper" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <type>MAP</type> <element xsi:type="jaxbStringObjectPair" key="Age"> <value xsi:type="xs:int" xmlns:xs="http://www.w3.org/2001/XMLSchema">66</value> </element> <element xsi:type="jaxbStringObjectPair" key="AgeClassification"> <value xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema">Senior</value> </element> </dmn-context> <messages/> <decisionResults> <entry> <key>_3</key> <value> <decision-id>_3</decision-id> <decision-name>AgeClassification</decision-name> <result xsi:type="xs:string" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">Senior</result> <messages/> <status>SUCCEEDED</status> </value> </entry> </decisionResults> </dmn-evaluation-result> </response>
-
6. Predictive Model Markup Language (PMML)
6.1. Predictive Model Markup Language (PMML)
Predictive Model Markup Language (PMML) is an XML-based standard established by the Data Mining Group (DMG) for defining statistical and data-mining models. PMML models can be shared between PMML-compliant platforms and across organizations so that business analysts and developers are unified in designing, analyzing, and implementing PMML-based assets and services.
For more information about the background and applications of PMML, see the DMG PMML specification.
6.1.1. PMML conformance levels
The PMML specification defines producer and consumer conformance levels in a software implementation to ensure that PMML models are created and integrated reliably. For the formal definitions of each conformance level, see the DMG PMML conformance page.
The following list summarizes the PMML conformance levels:
- Producer conformance
-
A tool or application is producer conforming if it generates valid PMML documents for at least one type of model. Satisfying PMML producer conformance requirements ensures that a model definition document is syntactically correct and defines a model instance that is consistent with semantic criteria that are defined in model specifications.
- Consumer conformance
-
An application is consumer conforming if it accepts valid PMML documents for at least one type of model. Satisfying consumer conformance requirements ensures that a PMML model created according to producer conformance can be integrated and used as defined. For example, if an application is consumer conforming for Regression model types, then valid PMML documents defining models of this type produced by different conforming producers would be interchangeable in the application.
Drools includes consumer conformance support for the following PMML 4.2.1 model types:
-
Mining models (with sub-types
modelChain
,selectAll
, andselectFirst
)
For a list of all PMML model types, including those not supported in Drools, see the DMG PMML specification.
6.2. PMML model examples
PMML defines an XML schema that enables PMML models to be used between different PMML-compliant platforms. The PMML specification enables multiple software platforms to work with the same file for authoring, testing, and production execution, assuming producer and consumer conformance are met.
The following are examples of PMML Regression, Scorecard, Tree, and Mining models. These examples illustrate the supported types of models that you can integrate with your decision services in Drools.
For more PMML examples, see the DMG PMML Sample Files page.
<PMML version="4.2" xsi:schemaLocation="http://www.dmg.org/PMML-4_2 http://www.dmg.org/v4-2-1/pmml-4-2.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.dmg.org/PMML-4_2">
<Header copyright="JBoss"/>
<DataDictionary numberOfFields="5">
<DataField dataType="double" name="fld1" optype="continuous"/>
<DataField dataType="double" name="fld2" optype="continuous"/>
<DataField dataType="string" name="fld3" optype="categorical">
<Value value="x"/>
<Value value="y"/>
</DataField>
<DataField dataType="double" name="fld4" optype="continuous"/>
<DataField dataType="double" name="fld5" optype="continuous"/>
</DataDictionary>
<RegressionModel algorithmName="linearRegression" functionName="regression" modelName="LinReg" normalizationMethod="logit" targetFieldName="fld4">
<MiningSchema>
<MiningField name="fld1"/>
<MiningField name="fld2"/>
<MiningField name="fld3"/>
<MiningField name="fld4" usageType="predicted"/>
<MiningField name="fld5" usageType="target"/>
</MiningSchema>
<RegressionTable intercept="0.5">
<NumericPredictor coefficient="5" exponent="2" name="fld1"/>
<NumericPredictor coefficient="2" exponent="1" name="fld2"/>
<CategoricalPredictor coefficient="-3" name="fld3" value="x"/>
<CategoricalPredictor coefficient="3" name="fld3" value="y"/>
<PredictorTerm coefficient="0.4">
<FieldRef field="fld1"/>
<FieldRef field="fld2"/>
</PredictorTerm>
</RegressionTable>
</RegressionModel>
</PMML>
<PMML version="4.2" xsi:schemaLocation="http://www.dmg.org/PMML-4_2 http://www.dmg.org/v4-2-1/pmml-4-2.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.dmg.org/PMML-4_2">
<Header copyright="JBoss"/>
<DataDictionary numberOfFields="4">
<DataField name="param1" optype="continuous" dataType="double"/>
<DataField name="param2" optype="continuous" dataType="double"/>
<DataField name="overallScore" optype="continuous" dataType="double" />
<DataField name="finalscore" optype="continuous" dataType="double" />
</DataDictionary>
<Scorecard modelName="ScorecardCompoundPredicate" useReasonCodes="true" isScorable="true" functionName="regression" baselineScore="15" initialScore="0.8" reasonCodeAlgorithm="pointsAbove">
<MiningSchema>
<MiningField name="param1" usageType="active" invalidValueTreatment="asMissing">
</MiningField>
<MiningField name="param2" usageType="active" invalidValueTreatment="asMissing">
</MiningField>
<MiningField name="overallScore" usageType="target"/>
<MiningField name="finalscore" usageType="predicted"/>
</MiningSchema>
<Characteristics>
<Characteristic name="ch1" baselineScore="50" reasonCode="reasonCh1">
<Attribute partialScore="20">
<SimplePredicate field="param1" operator="lessThan" value="20"/>
</Attribute>
<Attribute partialScore="100">
<CompoundPredicate booleanOperator="and">
<SimplePredicate field="param1" operator="greaterOrEqual" value="20"/>
<SimplePredicate field="param2" operator="lessOrEqual" value="25"/>
</CompoundPredicate>
</Attribute>
<Attribute partialScore="200">
<CompoundPredicate booleanOperator="and">
<SimplePredicate field="param1" operator="greaterOrEqual" value="20"/>
<SimplePredicate field="param2" operator="greaterThan" value="25"/>
</CompoundPredicate>
</Attribute>
</Characteristic>
<Characteristic name="ch2" reasonCode="reasonCh2">
<Attribute partialScore="10">
<CompoundPredicate booleanOperator="or">
<SimplePredicate field="param2" operator="lessOrEqual" value="-5"/>
<SimplePredicate field="param2" operator="greaterOrEqual" value="50"/>
</CompoundPredicate>
</Attribute>
<Attribute partialScore="20">
<CompoundPredicate booleanOperator="and">
<SimplePredicate field="param2" operator="greaterThan" value="-5"/>
<SimplePredicate field="param2" operator="lessThan" value="50"/>
</CompoundPredicate>
</Attribute>
</Characteristic>
</Characteristics>
</Scorecard>
</PMML>
<PMML version="4.2" xsi:schemaLocation="http://www.dmg.org/PMML-4_2 http://www.dmg.org/v4-2-1/pmml-4-2.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.dmg.org/PMML-4_2">
<Header copyright="JBOSS"/>
<DataDictionary numberOfFields="5">
<DataField dataType="double" name="fld1" optype="continuous"/>
<DataField dataType="double" name="fld2" optype="continuous"/>
<DataField dataType="string" name="fld3" optype="categorical">
<Value value="true"/>
<Value value="false"/>
</DataField>
<DataField dataType="string" name="fld4" optype="categorical">
<Value value="optA"/>
<Value value="optB"/>
<Value value="optC"/>
</DataField>
<DataField dataType="string" name="fld5" optype="categorical">
<Value value="tgtX"/>
<Value value="tgtY"/>
<Value value="tgtZ"/>
</DataField>
</DataDictionary>
<TreeModel functionName="classification" modelName="TreeTest">
<MiningSchema>
<MiningField name="fld1"/>
<MiningField name="fld2"/>
<MiningField name="fld3"/>
<MiningField name="fld4"/>
<MiningField name="fld5" usageType="predicted"/>
</MiningSchema>
<Node score="tgtX">
<True/>
<Node score="tgtX">
<SimplePredicate field="fld4" operator="equal" value="optA"/>
<Node score="tgtX">
<CompoundPredicate booleanOperator="surrogate">
<SimplePredicate field="fld1" operator="lessThan" value="30.0"/>
<SimplePredicate field="fld2" operator="greaterThan" value="20.0"/>
</CompoundPredicate>
<Node score="tgtX">
<SimplePredicate field="fld2" operator="lessThan" value="40.0"/>
</Node>
<Node score="tgtZ">
<SimplePredicate field="fld2" operator="greaterOrEqual" value="10.0"/>
</Node>
</Node>
<Node score="tgtZ">
<CompoundPredicate booleanOperator="or">
<SimplePredicate field="fld1" operator="greaterOrEqual" value="60.0"/>
<SimplePredicate field="fld1" operator="lessOrEqual" value="70.0"/>
</CompoundPredicate>
<Node score="tgtZ">
<SimpleSetPredicate booleanOperator="isNotIn" field="fld4">
<Array type="string">optA optB</Array>
</SimpleSetPredicate>
</Node>
</Node>
</Node>
<Node score="tgtY">
<CompoundPredicate booleanOperator="or">
<SimplePredicate field="fld4" operator="equal" value="optA"/>
<SimplePredicate field="fld4" operator="equal" value="optC"/>
</CompoundPredicate>
<Node score="tgtY">
<CompoundPredicate booleanOperator="and">
<SimplePredicate field="fld1" operator="greaterThan" value="10.0"/>
<SimplePredicate field="fld1" operator="lessThan" value="50.0"/>
<SimplePredicate field="fld4" operator="equal" value="optA"/>
<SimplePredicate field="fld2" operator="lessThan" value="100.0"/>
<SimplePredicate field="fld3" operator="equal" value="false"/>
</CompoundPredicate>
</Node>
<Node score="tgtZ">
<CompoundPredicate booleanOperator="and">
<SimplePredicate field="fld4" operator="equal" value="optC"/>
<SimplePredicate field="fld2" operator="lessThan" value="30.0"/>
</CompoundPredicate>
</Node>
</Node>
</Node>
</TreeModel>
</PMML>
<PMML version="4.2" xsi:schemaLocation="http://www.dmg.org/PMML-4_2 http://www.dmg.org/v4-2-1/pmml-4-2.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.dmg.org/PMML-4_2">
<Header>
<Application name="Drools-PMML" version="7.0.0-SNAPSHOT" />
</Header>
<DataDictionary numberOfFields="7">
<DataField name="age" optype="continuous" dataType="double" />
<DataField name="occupation" optype="categorical" dataType="string">
<Value value="SKYDIVER" />
<Value value="ASTRONAUT" />
<Value value="PROGRAMMER" />
<Value value="TEACHER" />
<Value value="INSTRUCTOR" />
</DataField>
<DataField name="residenceState" optype="categorical" dataType="string">
<Value value="AP" />
<Value value="KN" />
<Value value="TN" />
</DataField>
<DataField name="validLicense" optype="categorical" dataType="boolean" />
<DataField name="overallScore" optype="continuous" dataType="double" />
<DataField name="grade" optype="categorical" dataType="string">
<Value value="A" />
<Value value="B" />
<Value value="C" />
<Value value="D" />
<Value value="F" />
</DataField>
<DataField name="qualificationLevel" optype="categorical" dataType="string">
<Value value="Unqualified" />
<Value value="Barely" />
<Value value="Well" />
<Value value="Over" />
</DataField>
</DataDictionary>
<MiningModel modelName="SampleModelChainMine" functionName="classification">
<MiningSchema>
<MiningField name="age" />
<MiningField name="occupation" />
<MiningField name="residenceState" />
<MiningField name="validLicense" />
<MiningField name="overallScore" />
<MiningField name="qualificationLevel" usageType="target"/>
</MiningSchema>
<Segmentation multipleModelMethod="modelChain">
<Segment id="1">
<True />
<Scorecard modelName="Sample Score 1" useReasonCodes="true" isScorable="true" functionName="regression" baselineScore="0.0" initialScore="0.345">
<MiningSchema>
<MiningField name="age" usageType="active" invalidValueTreatment="asMissing" />
<MiningField name="occupation" usageType="active" invalidValueTreatment="asMissing" />
<MiningField name="residenceState" usageType="active" invalidValueTreatment="asMissing" />
<MiningField name="validLicense" usageType="active" invalidValueTreatment="asMissing" />
<MiningField name="overallScore" usageType="predicted" />
</MiningSchema>
<Output>
<OutputField name="calculatedScore" displayName="Final Score" dataType="double" feature="predictedValue" targetField="overallScore" />
</Output>
<Characteristics>
<Characteristic name="AgeScore" baselineScore="0.0" reasonCode="ABZ">
<Extension name="cellRef" value="$B$8" />
<Attribute partialScore="10.0">
<Extension name="cellRef" value="$C$10" />
<SimplePredicate field="age" operator="lessOrEqual" value="5" />
</Attribute>
<Attribute partialScore="30.0" reasonCode="CX1">
<Extension name="cellRef" value="$C$11" />
<CompoundPredicate booleanOperator="and">
<SimplePredicate field="age" operator="greaterOrEqual" value="5" />
<SimplePredicate field="age" operator="lessThan" value="12" />
</CompoundPredicate>
</Attribute>
<Attribute partialScore="40.0" reasonCode="CX2">
<Extension name="cellRef" value="$C$12" />
<CompoundPredicate booleanOperator="and">
<SimplePredicate field="age" operator="greaterOrEqual" value="13" />
<SimplePredicate field="age" operator="lessThan" value="44" />
</CompoundPredicate>
</Attribute>
<Attribute partialScore="25.0">
<Extension name="cellRef" value="$C$13" />
<SimplePredicate field="age" operator="greaterOrEqual" value="45" />
</Attribute>
</Characteristic>
<Characteristic name="OccupationScore" baselineScore="0.0">
<Extension name="cellRef" value="$B$16" />
<Attribute partialScore="-10.0" reasonCode="CX2">
<Extension name="description" value="skydiving is a risky occupation" />
<Extension name="cellRef" value="$C$18" />
<SimpleSetPredicate field="occupation" booleanOperator="isIn">
<Array n="2" type="string">SKYDIVER ASTRONAUT</Array>
</SimpleSetPredicate>
</Attribute>
<Attribute partialScore="10.0">
<Extension name="cellRef" value="$C$19" />
<SimpleSetPredicate field="occupation" booleanOperator="isIn">
<Array n="2" type="string">TEACHER INSTRUCTOR</Array>
</SimpleSetPredicate>
</Attribute>
<Attribute partialScore="5.0">
<Extension name="cellRef" value="$C$20" />
<SimplePredicate field="occupation" operator="equal" value="PROGRAMMER" />
</Attribute>
</Characteristic>
<Characteristic name="ResidenceStateScore" baselineScore="0.0" reasonCode="RES">
<Extension name="cellRef" value="$B$22" />
<Attribute partialScore="-10.0">
<Extension name="cellRef" value="$C$24" />
<SimplePredicate field="residenceState" operator="equal" value="AP" />
</Attribute>
<Attribute partialScore="10.0">
<Extension name="cellRef" value="$C$25" />
<SimplePredicate field="residenceState" operator="equal" value="KN" />
</Attribute>
<Attribute partialScore="5.0">
<Extension name="cellRef" value="$C$26" />
<SimplePredicate field="residenceState" operator="equal" value="TN" />
</Attribute>
</Characteristic>
<Characteristic name="ValidLicenseScore" baselineScore="0.0">
<Extension name="cellRef" value="$B$28" />
<Attribute partialScore="1.0" reasonCode="LX00">
<Extension name="cellRef" value="$C$30" />
<SimplePredicate field="validLicense" operator="equal" value="true" />
</Attribute>
<Attribute partialScore="-1.0" reasonCode="LX00">
<Extension name="cellRef" value="$C$31" />
<SimplePredicate field="validLicense" operator="equal" value="false" />
</Attribute>
</Characteristic>
</Characteristics>
</Scorecard>
</Segment>
<Segment id="2">
<True />
<TreeModel modelName="SampleTree" functionName="classification" missingValueStrategy="lastPrediction" noTrueChildStrategy="returnLastPrediction">
<MiningSchema>
<MiningField name="age" usageType="active" />
<MiningField name="validLicense" usageType="active" />
<MiningField name="calculatedScore" usageType="active" />
<MiningField name="qualificationLevel" usageType="predicted" />
</MiningSchema>
<Output>
<OutputField name="qualification" displayName="Qualification Level" dataType="string" feature="predictedValue" targetField="qualificationLevel" />
</Output>
<Node score="Well" id="1">
<True/>
<Node score="Barely" id="2">
<CompoundPredicate booleanOperator="and">
<SimplePredicate field="age" operator="greaterOrEqual" value="16" />
<SimplePredicate field="validLicense" operator="equal" value="true" />
</CompoundPredicate>
<Node score="Barely" id="3">
<SimplePredicate field="calculatedScore" operator="lessOrEqual" value="50.0" />
</Node>
<Node score="Well" id="4">
<CompoundPredicate booleanOperator="and">
<SimplePredicate field="calculatedScore" operator="greaterThan" value="50.0" />
<SimplePredicate field="calculatedScore" operator="lessOrEqual" value="60.0" />
</CompoundPredicate>
</Node>
<Node score="Over" id="5">
<SimplePredicate field="calculatedScore" operator="greaterThan" value="60.0" />
</Node>
</Node>
<Node score="Unqualified" id="6">
<CompoundPredicate booleanOperator="surrogate">
<SimplePredicate field="age" operator="lessThan" value="16" />
<SimplePredicate field="calculatedScore" operator="lessOrEqual" value="40.0" />
<True />
</CompoundPredicate>
</Node>
</Node>
</TreeModel>
</Segment>
</Segmentation>
</MiningModel>
</PMML>
6.3. PMML support in Drools
Drools includes consumer conformance support for the following PMML 4.2.1 model types:
-
Mining models (with sub-types
modelChain
,selectAll
, andselectFirst
)
For a list of all PMML model types, including those not supported in Drools, see the DMG PMML specification.
Drools does not include a built-in PMML model editor, but you can use an XML or PMML-specific authoring tool to create PMML models and then integrate the PMML models in your decision services in Drools. You can import PMML files into your project in Business Central (Menu → Design → Projects → Import Asset) or package the PMML files as part of your project knowledge JAR (KJAR) file without Business Central.
When you add a PMML file to a project in Drools, multiple assets are generated. Each type of PMML model generates a different set of assets, but all PMML model types generate at least the following set of assets:
-
A DRL file that contains all of the rules associated with your PMML model
-
At least two Java classes:
-
A data class that is used as the default object type for the model type
-
A
RuleUnit
class that is used to manage data sources and rule execution
-
If a PMML file has MiningModel
as the root model, multiple instances of each of these files are generated.
For more information about including assets such as PMML files with your project packaging and deployment method, see Build, Deploy, Utilize and Run.
6.3.1. PMML naming conventions in Drools
The following are naming conventions for generated PMML packages, classes, and rules:
-
If no package name is given in a PMML model file, then the default package name
org.kie.pmml.pmml_4_2
is prefixed to the model name for the generated rules in the format"org.kie.pmml.pmml_4_2"+modelName
. -
The package name for the generated
RuleUnit
Java class is the same as the package name for the generated rules. -
The name of the generated
RuleUnit
Java class is the model name withRuleUnit
added to it in the formatmodelName+"RuleUnit"
. -
Each PMML model has at least one data class that is generated. The package name for these classes is
org.kie.pmml.pmml_4_2.model
. -
The names of generated data classes are determined by the model type, prefixed with the model name:
-
Regression models: One data class named
modelName+"RegressionData"
-
Scorecard models: One data class named
modelName+"ScoreCardData"
-
Tree models: Two data classes, the first named
modelName+"TreeNode"
and the second namedmodelName+"TreeToken"
-
Mining models: One data class named
modelName+"MiningModelData"
-
The mining model also generates all of the rules and classes that are within each of its segments. |
6.3.2. PMML extensions in Drools
The PMML specification supports Extension
elements that extend the content of a PMML model. You can use extensions at almost every level of a PMML model definition, and as the first and last child in the main element of a model for maximum flexibility. For more information about PMML extensions, see the DMG PMML Extension Mechanism.
To optimize PMML integration, Drools supports the following additional PMML extensions:
-
modelPackage
: Designates a package name for the generated rules and Java classes. Include this extension in theHeader
section of the PMML model file. -
adapter
: Designates the type of construct (bean
ortrait
) that is used to contain input and output data for rules. Insert this extension in theMiningSchema
orOutput
section (or both) of the PMML model file. -
externalClass
: Used in conjunction with theadapter
extension in defining aMiningField
orOutputField
. This extension contains a class with an attribute name that matches the name of theMiningField
orOutputField
element.
6.4. PMML model execution
You can import PMML files into your Drools project using Business Central (Menu → Design → Projects → Import Asset) or package the PMML files as part of your project knowledge JAR (KJAR) file without Business Central. After you implement your PMML files in your Drools project, you can execute the PMML-based decision service by embedding PMML calls directly in your Java application or by sending an ApplyPmmlModelCommand
command to a configured KIE Server.
For more information about including PMML assets with your project packaging and deployment method, see Build, Deploy, Utilize and Run.
You can also include a PMML model as part of a Decision Model and Notation (DMN) service in Business Central. When you include a PMML model within a DMN file, you can invoke that PMML model as a boxed function expression for a DMN decision node or business knowledge model node. For more information about including PMML models in a DMN service, see Designing a decision service using DMN models. |
6.4.1. Embedding a PMML call directly in a Java application
A KIE container is local when the knowledge assets are either embedded directly into the calling program or are physically pulled in using Maven dependencies for the KJAR. You typically embed knowledge assets directly into a project if there is a tight relationship between the version of the code and the version of the PMML definition. Any changes to the decision take effect after you have intentionally updated and redeployed the application. A benefit of this approach is that proper operation does not rely on any external dependencies to the run time, which can be a limitation of locked-down environments.
Using Maven dependencies enables further flexibility because the specific version of the decision can dynamically change (for example, by using a system property), and it can be periodically scanned for updates and automatically updated. This introduces an external dependency on the deploy time of the service, but executes the decision locally, reducing reliance on an external service being available during run time.
-
A KJAR containing the PMML model to execute has been created. For more information about project packaging, see Build, Deploy, Utilize and Run.
-
In your client application, add the following dependencies to the relevant classpath of your Java project:
<!-- Required for the PMML compiler --> <dependency> <groupId>org.drools</groupId> <artifactId>kie-pmml</artifactId> <version>${drools.version}</version> </dependency> <!-- Required for the KIE public API --> <dependency> <groupId>org.kie</groupId> <artifactId>kie-api</artifactId> <version>${drools.version}</version> </dependencies> <!-- Required if not using classpath KIE container --> <dependency> <groupId>org.kie</groupId> <artifactId>kie-ci</artifactId> <version>${drools.version}</version> </dependency>
The
<version>
is the Maven artifact version for Drools currently used in your project (for example, 7.33.0.Final-redhat-00002). -
Create a KIE container from
classpath
orReleaseId
:KieServices kieServices = KieServices.Factory.get(); ReleaseId releaseId = kieServices.newReleaseId( "org.acme", "my-kjar", "1.0.0" ); KieContainer kieContainer = kieServices.newKieContainer( releaseId );
Alternative option:
KieServices kieServices = KieServices.Factory.get(); KieContainer kieContainer = kieServices.getKieClasspathContainer();
-
Create an instance of the
PMMLRequestData
class, which applies your PMML model to a set of data:public class PMMLRequestData { private String correlationId; (1) private String modelName; (2) private String source; (3) private List<ParameterInfo<?>> requestParams; (4) ... }
1 Identifies data that is associated with a particular request or result 2 The name of the model that should be applied to the request data 3 Used by internally generated PMMLRequestData
objects to identify the segment that generated the request4 The default mechanism for sending input data points -
Create an instance of the
PMML4Result
class, which holds the output information that is the result of applying the PMML-based rules to the input data:public class PMML4Result { private String correlationId; private String segmentationId; (1) private String segmentId; (2) private int segmentIndex; (3) private String resultCode; (4) private Map<String, Object> resultVariables; (5) ... }
1 Used when the model type is MiningModel
. ThesegmentationId
is used to differentiate between multiple segmentations.2 Used in conjunction with the segmentationId
to identify which segment generated the results.3 Used to maintain the order of segments. 4 Used to determine whether the model was successfully applied, where OK
indicates success.5 Contains the name of a resultant variable and its associated value. In addition to the normal getter methods, the
PMML4Result
class also supports the following methods for directly retrieving the values for result variables:public <T> Optional<T> getResultValue(String objName, String objField, Class<T> clazz, Object...params) public Object getResultValue(String objName, String objField, Object...params)
-
Create an instance of the
ParameterInfo
class, which serves as a wrapper for basic data type objects used as part of thePMMLRequestData
class:public class ParameterInfo<T> { (1) private String correlationId; private String name; (2) private String capitalizedName; private Class<T> type; (3) private T value; (4) ... }
1 The parameterized class to handle many different types 2 The name of the variable that is expected as input for the model 3 The class that is the actual type of the variable 4 The actual value of the variable -
Execute the PMML model based on the required PMML class instances that you have created:
public void executeModel(KieBase kbase, Map<String,Object> variables, String modelName, String correlationId, String modelPkgName) { RuleUnitExecutor executor = RuleUnitExecutor.create().bind(kbase); PMMLRequestData request = new PMMLRequestData(correlationId, modelName); PMML4Result resultHolder = new PMML4Result(correlationId); variables.entrySet().forEach( es -> { request.addRequestParam(es.getKey(), es.getValue()); }); DataSource<PMMLRequestData> requestData = executor.newDataSource("request"); DataSource<PMML4Result> resultData = executor.newDataSource("results"); DataSource<PMMLData> internalData = executor.newDataSource("pmmlData"); requestData.insert(request); resultData.insert(resultHolder); List<String> possiblePackageNames = calculatePossiblePackageNames(modelName, modelPkgName); Class<? extends RuleUnit> ruleUnitClass = getStartingRuleUnit("RuleUnitIndicator", (InternalKnowledgeBase)kbase, possiblePackageNames); if (ruleUnitClass != null) { executor.run(ruleUnitClass); if ( "OK".equals(resultHolder.getResultCode()) ) { // extract result variables here } } } protected Class<? extends RuleUnit> getStartingRuleUnit(String startingRule, InternalKnowledgeBase ikb, List<String> possiblePackages) { RuleUnitRegistry unitRegistry = ikb.getRuleUnitRegistry(); Map<String,InternalKnowledgePackage> pkgs = ikb.getPackagesMap(); RuleImpl ruleImpl = null; for (String pkgName: possiblePackages) { if (pkgs.containsKey(pkgName)) { InternalKnowledgePackage pkg = pkgs.get(pkgName); ruleImpl = pkg.getRule(startingRule); if (ruleImpl != null) { RuleUnitDescr descr = unitRegistry.getRuleUnitFor(ruleImpl).orElse(null); if (descr != null) { return descr.getRuleUnitClass(); } } } } return null; } protected List<String> calculatePossiblePackageNames(String modelId, String...knownPackageNames) { List<String> packageNames = new ArrayList<>(); String javaModelId = modelId.replaceAll("\\s",""); if (knownPackageNames != null && knownPackageNames.length > 0) { for (String knownPkgName: knownPackageNames) { packageNames.add(knownPkgName + "." + javaModelId); } } String basePkgName = PMML4UnitImpl.DEFAULT_ROOT_PACKAGE+"."+javaModelId; packageNames.add(basePkgName); return packageNames; }
Rules are executed by the
RuleUnitExecutor
class. TheRuleUnitExecutor
class creates KIE sessions and adds the requiredDataSource
objects to those sessions, and then executes the rules based on theRuleUnit
that is passed as a parameter to therun()
method. ThecalculatePossiblePackageNames
and thegetStartingRuleUnit
methods determine the fully qualified name of theRuleUnit
class that is passed to therun()
method.
To facilitate your PMML model execution, you can also use a PMML4ExecutionHelper
class supported in Drools. For more information about the PMML helper class, see PMML execution helper class.
6.4.1.1. PMML execution helper class
Drools provides a PMML4ExecutionHelper
class that helps create the PMMLRequestData
class required for PMML model execution and that helps execute rules using the RuleUnitExecutor
class.
The following are examples of a PMML model execution without and with the PMML4ExecutionHelper
class, as a comparison:
PMML4ExecutionHelper
public void executeModel(KieBase kbase,
Map<String,Object> variables,
String modelName,
String correlationId,
String modelPkgName) {
RuleUnitExecutor executor = RuleUnitExecutor.create().bind(kbase);
PMMLRequestData request = new PMMLRequestData(correlationId, modelName);
PMML4Result resultHolder = new PMML4Result(correlationId);
variables.entrySet().forEach( es -> {
request.addRequestParam(es.getKey(), es.getValue());
});
DataSource<PMMLRequestData> requestData = executor.newDataSource("request");
DataSource<PMML4Result> resultData = executor.newDataSource("results");
DataSource<PMMLData> internalData = executor.newDataSource("pmmlData");
requestData.insert(request);
resultData.insert(resultHolder);
List<String> possiblePackageNames = calculatePossiblePackageNames(modelName,
modelPkgName);
Class<? extends RuleUnit> ruleUnitClass = getStartingRuleUnit("RuleUnitIndicator",
(InternalKnowledgeBase)kbase,
possiblePackageNames);
if (ruleUnitClass != null) {
executor.run(ruleUnitClass);
if ( "OK".equals(resultHolder.getResultCode()) ) {
// extract result variables here
}
}
}
protected Class<? extends RuleUnit> getStartingRuleUnit(String startingRule, InternalKnowledgeBase ikb, List<String> possiblePackages) {
RuleUnitRegistry unitRegistry = ikb.getRuleUnitRegistry();
Map<String,InternalKnowledgePackage> pkgs = ikb.getPackagesMap();
RuleImpl ruleImpl = null;
for (String pkgName: possiblePackages) {
if (pkgs.containsKey(pkgName)) {
InternalKnowledgePackage pkg = pkgs.get(pkgName);
ruleImpl = pkg.getRule(startingRule);
if (ruleImpl != null) {
RuleUnitDescr descr = unitRegistry.getRuleUnitFor(ruleImpl).orElse(null);
if (descr != null) {
return descr.getRuleUnitClass();
}
}
}
}
return null;
}
protected List<String> calculatePossiblePackageNames(String modelId, String...knownPackageNames) {
List<String> packageNames = new ArrayList<>();
String javaModelId = modelId.replaceAll("\\s","");
if (knownPackageNames != null && knownPackageNames.length > 0) {
for (String knownPkgName: knownPackageNames) {
packageNames.add(knownPkgName + "." + javaModelId);
}
}
String basePkgName = PMML4UnitImpl.DEFAULT_ROOT_PACKAGE+"."+javaModelId;
packageNames.add(basePkgName);
return packageNames;
}
PMML4ExecutionHelper
public void executeModel(KieBase kbase,
Map<String,Object> variables,
String modelName,
String modelPkgName,
String correlationId) {
PMML4ExecutionHelper helper = PMML4ExecutionHelperFactory.getExecutionHelper(modelName, kbase);
helper.addPossiblePackageName(modelPkgName);
PMMLRequestData request = new PMMLRequestData(correlationId, modelName);
variables.entrySet().forEach(entry -> {
request.addRequestParam(entry.getKey(), entry.getValue);
});
PMML4Result resultHolder = helper.submitRequest(request);
if ("OK".equals(resultHolder.getResultCode)) {
// extract result variables here
}
}
When you use the PMML4ExecutionHelper
, you do not need to specify the possible package names nor the RuleUnit
class as you would in a typical PMML model execution.
To construct a PMML4ExecutionHelper
class, you use the PMML4ExecutionHelperFactory
class to determine how instances of PMML4ExecutionHelper
are retrieved.
The following are the available PMML4ExecutionHelperFactory
class methods for constructing a PMML4ExecutionHelper
class:
- PMML4ExecutionHelperFactory methods for PMML assets in a KIE base
-
Use these methods when PMML assets have already been compiled and are being used from an existing KIE base:
public static PMML4ExecutionHelper getExecutionHelper(String modelName, KieBase kbase) public static PMML4ExecutionHelper getExecutionHelper(String modelName, KieBase kbase, boolean includeMiningDataSources)
- PMML4ExecutionHelperFactory methods for PMML assets on the project classpath
-
Use these methods when PMML assets are on the project classpath. The
classPath
argument is the project classpath location of the PMML file:public static PMML4ExecutionHelper getExecutionHelper(String modelName, String classPath, KieBaseConfiguration kieBaseConf) public static PMML4ExecutionHelper getExecutionHelper(String modelName,String classPath, KieBaseConfiguration kieBaseConf, boolean includeMiningDataSources)
- PMML4ExecutionHelperFactory methods for PMML assets in a byte array
-
Use these methods when PMML assets are in the form of a byte array:
public static PMML4ExecutionHelper getExecutionHelper(String modelName, byte[] content, KieBaseConfiguration kieBaseConf) public static PMML4ExecutionHelper getExecutionHelper(String modelName, byte[] content, KieBaseConfiguration kieBaseConf, boolean includeMiningDataSources)
- PMML4ExecutionHelperFactory methods for PMML assets in a
Resource
-
Use these methods when PMML assets are in the form of an
org.kie.api.io.Resource
object:public static PMML4ExecutionHelper getExecutionHelper(String modelName, Resource resource, KieBaseConfiguration kieBaseConf) public static PMML4ExecutionHelper getExecutionHelper(String modelName, Resource resource, KieBaseConfiguration kieBaseConf, boolean includeMiningDataSources)
The classpath, byte array, and resource PMML4ExecutionHelperFactory methods create a KIE container for the generated rules and Java classes. The container is used as the source of the KIE base that the RuleUnitExecutor uses. The container is not persisted. The PMML4ExecutionHelperFactory method for PMML assets that are already in a KIE base does not create a KIE container in this way.
|
6.4.2. Executing a PMML model using KIE Server
You can execute PMML models that have been deployed to KIE Server by sending the ApplyPmmlModelCommand
command to the configured KIE Server. When you use this command, a PMMLRequestData
object is sent to the KIE Server and a PMML4Result
result object is received as a reply. You can send PMML requests to KIE Server through the KIE Server REST API from a configured Java class or directly from a REST client.
-
KIE Server is installed and configured, including a known user name and credentials for a user with the
kie-server
role. For installation options, see Installation and Setup (Core and IDE). -
A KIE container is deployed in KIE Server in the form of a KJAR that includes the PMML model. For more information about project packaging, see Build, Deploy, Utilize and Run.
-
You have the container ID of the KIE container containing the PMML model.
-
In your client application, add the following dependencies to the relevant classpath of your Java project:
<!-- Required for the PMML compiler --> <dependency> <groupId>org.drools</groupId> <artifactId>kie-pmml</artifactId> <version>${drools.version}</version> </dependency> <!-- Required for the KIE public API --> <dependency> <groupId>org.kie</groupId> <artifactId>kie-api</artifactId> <version>${drools.version}</version> </dependencies> <!-- Required for the KIE Server Java client API --> <dependency> <groupId>org.kie.server</groupId> <artifactId>kie-server-client</artifactId> <version>${drools.version}</version> </dependency> <!-- Required if not using classpath KIE container --> <dependency> <groupId>org.kie</groupId> <artifactId>kie-ci</artifactId> <version>${drools.version}</version> </dependency>
The
<version>
is the Maven artifact version for Drools currently used in your project (for example, 7.33.0.Final-redhat-00002). -
Create a KIE container from
classpath
orReleaseId
:KieServices kieServices = KieServices.Factory.get(); ReleaseId releaseId = kieServices.newReleaseId( "org.acme", "my-kjar", "1.0.0" ); KieContainer kieContainer = kieServices.newKieContainer( releaseId );
Alternative option:
KieServices kieServices = KieServices.Factory.get(); KieContainer kieContainer = kieServices.getKieClasspathContainer();
-
Create a class for sending requests to KIE Server and receiving responses:
public class ApplyScorecardModel { private static final ReleaseId releaseId = new ReleaseId("org.acme","my-kjar","1.0.0"); private static final String containerId = "SampleModelContainer"; private static KieCommands commandFactory; private static ClassLoader kjarClassLoader; (1) private RuleServicesClient serviceClient; (2) // Attributes specific to your class instance private String rankedFirstCode; private Double score; // Initialization of non-final static attributes static { commandFactory = KieServices.Factory.get().getCommands(); // Specifications for kjarClassLoader, if used KieMavenRepository kmp = KieMavenRepository.getMavenRepository(); File artifactFile = kmp.resolveArtifact(releaseId).getFile(); if (artifactFile != null) { URL urls[] = new URL[1]; try { urls[0] = artifactFile.toURI().toURL(); classLoader = new KieURLClassLoader(urls,PMML4Result.class.getClassLoader()); } catch (MalformedURLException e) { logger.error("Error getting classLoader for "+containerId); logger.error(e.getMessage()); } } else { logger.warn("Did not find the artifact file for "+releaseId.toString()); } } public ApplyScorecardModel(KieServicesConfiguration kieConfig) { KieServicesClient clientFactory = KieServicesFactory.newKieServicesClient(kieConfig); serviceClient = clientFactory.getServicesClient(RuleServicesClient.class); } ... // Getters and setters ... // Method for executing the PMML model on KIE Server public void applyModel(String occupation, int age) { PMMLRequestData input = new PMMLRequestData("1234","SampleModelName"); (3) input.addRequestParam(new ParameterInfo("1234","occupation",String.class,occupation)); input.addRequestParam(new ParameterInfo("1234","age",Integer.class,age)); CommandFactoryServiceImpl cf = (CommandFactoryServiceImpl)commandFactory; ApplyPmmlModelCommand command = (ApplyPmmlModelCommand) cf.newApplyPmmlModel(request); (4) ServiceResponse<ExecutionResults> results = ruleClient.executeCommandsWithResults(CONTAINER_ID, command); (5) if (results != null) { (6) PMML4Result resultHolder = (PMML4Result)results.getResult().getValue("results"); if (resultHolder != null && "OK".equals(resultHolder.getResultCode())) { this.score = resultHolder.getResultValue("ScoreCard","score",Double.class).get(); Map<String,Object> rankingMap = (Map<String,Object>)resultHolder.getResultValue("ScoreCard","ranking"); if (rankingMap != null && !rankingMap.isEmpty()) { this.rankedFirstCode = rankingMap.keySet().iterator().next(); } } } } }
1 Defines the class loader if you did not include the KJAR in your client project dependencies 2 Identifies the service client as defined in the configuration settings, including KIE Server REST API access credentials 3 Initializes a PMMLRequestData
object4 Creates an instance of the ApplyPmmlModelCommand
5 Sends the command using the service client 6 Retrieves the results of the executed PMML model -
Execute the class instance to send the PMML invocation request to KIE Server.
Alternatively, you can use JMS and REST interfaces to send the
ApplyPmmlModelCommand
command to KIE Server. For REST requests, you use theApplyPmmlModelCommand
command as aPOST
request tohttp://SERVER:PORT/kie-server/services/rest/server/containers/instances/{containerId}
in JSON, JAXB, or XStream request format.Example POST endpointhttp://localhost:8080/kie-server/services/rest/server/containers/instances/SampleModelContainer
Example JSON request body{ "commands": [ { "apply-pmml-model-command": { "outIdentifier": null, "packageName": null, "hasMining": false, "requestData": { "correlationId": "123", "modelName": "SimpleScorecard", "source": null, "requestParams": [ { "correlationId": "123", "name": "param1", "type": "java.lang.Double", "value": "10.0" }, { "correlationId": "123", "name": "param2", "type": "java.lang.Double", "value": "15.0" } ] } } } ] }
Example curl request with endpoint and bodycurl -X POST "http://localhost:8080/kie-server/services/rest/server/containers/instances/SampleModelContainer" -H "accept: application/json" -H "content-type: application/json" -d "{ \"commands\": [ { \"apply-pmml-model-command\": { \"outIdentifier\": null, \"packageName\": null, \"hasMining\": false, \"requestData\": { \"correlationId\": \"123\", \"modelName\": \"SimpleScorecard\", \"source\": null, \"requestParams\": [ { \"correlationId\": \"123\", \"name\": \"param1\", \"type\": \"java.lang.Double\", \"value\": \"10.0\" }, { \"correlationId\": \"123\", \"name\": \"param2\", \"type\": \"java.lang.Double\", \"value\": \"15.0\" } ] } } } ]}"
Example JSON response{ "results" : [ { "value" : {"org.kie.api.pmml.DoubleFieldOutput":{ "value" : 40.8, "correlationId" : "123", "segmentationId" : null, "segmentId" : null, "name" : "OverallScore", "displayValue" : "OverallScore", "weight" : 1.0 }}, "key" : "OverallScore" }, { "value" : {"org.kie.api.pmml.PMML4Result":{ "resultVariables" : { "OverallScore" : { "value" : 40.8, "correlationId" : "123", "segmentationId" : null, "segmentId" : null, "name" : "OverallScore", "displayValue" : "OverallScore", "weight" : 1.0 }, "ScoreCard" : { "modelName" : "SimpleScorecard", "score" : 40.8, "holder" : { "modelName" : "SimpleScorecard", "correlationId" : "123", "voverallScore" : null, "moverallScore" : true, "vparam1" : 10.0, "mparam1" : false, "vparam2" : 15.0, "mparam2" : false }, "enableRC" : true, "pointsBelow" : true, "ranking" : { "reasonCh1" : 5.0, "reasonCh2" : -6.0 } } }, "correlationId" : "123", "segmentationId" : null, "segmentId" : null, "segmentIndex" : 0, "resultCode" : "OK", "resultObjectName" : null }}, "key" : "results" } ], "facts" : [ ] }
7. Experimental Features
7.1. Declarative Agenda
Declarative Agenda is experimental, and all aspects are highly likely to change in the future. @Eager and @Direct are temporary annotations to control the behaviour of rules, which will also change as Declarative Agenda evolves. Annotations instead of attributes where chosen, to reflect their experimental nature. |
The declarative agenda allows to use rules to control which other rules can fire and when. While this will add a lot more overhead than the simple use of salience, the advantage is it is declarative and thus more readable and maintainable and should allow more use cases to be achieved in a simpler fashion.
This feature is off by default and must be explicitly enabled, that is because it is considered highly experimental for the moment and will be subject to change, but can be activated on a given KieBase by adding the declarativeAgenda='enabled' attribute in the corresponding kbase tag of the kmodule.xml file as in the following example.
<kmodule xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.drools.org/xsd/kmodule">
<kbase name="DeclarativeKBase" declarativeAgenda="enabled">
<ksession name="KSession">
</kbase>
</kmodule>
The basic idea is:
-
All rule’s Matches are inserted into WorkingMemory as facts. So you can now do pattern matching against a Match. The rule’s metadata and declarations are available as fields on the Match object.
-
You can use the kcontext.blockMatch( Match match ) for the current rule to block the selected match. Only when that rule becomes false will the match 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.
-
A match may have multiple blockers and a count is kept. All blockers must became false for the counter to reach zero to enable the Match to be eligible for firing.
-
kcontext.unblockAllMatches( Match match ) is an over-ride rule that will remove all blockers regardless
-
An activation may also be cancelled, so it never fires with cancelMatch
-
An unblocked Match is added to the Agenda and obeys normal salience, agenda groups, ruleflow groups etc.
-
The @Direct annotations allows a rule to fire as soon as it’s matched, this is to be used for rules that block/unblock matches, it is not desirable for these rules to have side effects that impact else where.
void blockMatch(Match match);
void unblockAllMatches(Match match);
void cancelMatch(Match match);
Here is a basic example that will block all matches from rules that have metadata @department('sales'). They will stay blocked until the blockerAllSalesRules rule becomes false, i.e. "go2" is retracted.
rule rule1 @Eager @department('sales') when
$s : String( this == 'go1' )
then
list.add( kcontext.rule.name + ':' + $s );
end
rule rule2 @Eager @department('sales') when
$s : String( this == 'go1' )
then
list.add( kcontext.rule.name + ':' + $s );
end
rule blockerAllSalesRules @Direct @Eager when
$s : String( this == 'go2' )
$i : Match( department == 'sales' )
then
list.add( $i.rule.name + ':' + $s );
kcontext.blockMatch( $i );
end
Further than annotate the blocking rule with @Direct, it is also necessary to annotate all the rules that could be potentially blocked by it with @Eager. This is because, since the Match has to be evaluated by the pattern matching of the blocking rule, the potentially blocked ones cannot be evaluated lazily, otherwise won’t be any Match to be evaluated. |
This example shows how you can use active property to count the number of active or inactive (already fired) matches.
rule rule1 @Eager @department('sales') when
$s : String( this == 'go1' )
then
list.add( kcontext.rule.name + ':' + $s );
end
rule rule2 @Eager @department('sales') when
$s : String( this == 'go1' )
then
list.add( kcontext.rule.name + ':' + $s );
end
rule rule3 @Eager @department('sales') when
$s : String( this == 'go1' )
then
list.add( kcontext.rule.name + ':' + $s );
end
rule countActivateInActive @Direct @Eager when
$s : String( this == 'go2' )
$active : Number( this == 1 ) from accumulate( $a : Match( department == 'sales', active == true ), count( $a ) )
$inActive : Number( this == 2 ) from accumulate( $a : Match( department == 'sales', active == false ), count( $a ) )
then
kcontext.halt( );
end
7.2. Traits
WARNING : this feature is still experimental and subject to changes
The same fact may have multiple dynamic types which do not fit naturally in a class hierarchy. Traits allow to model this very common scenario. A trait is an interface that can be applied (and eventually removed) to an individual object at runtime. To create a trait rather than a traditional bean, one has to declare them explicitly as in the following example:
declare trait GoldenCustomer
// fields will map to getters/setters
code : String
balance : long
discount : int
maxExpense : long
end
At runtime, this declaration results in an interface, which can be used to write patterns, but can not be instantiated directly. In order to apply a trait to an object, we provide the new don keyword, which can be used as simply as this:
when
$c : Customer()
then
GoldenCustomer gc = don( $c, GoldenCustomer.class );
end
when a core object dons a trait, a proxy class is created on the fly (one such class will be generated lazily for each core/trait class combination). The proxy instance, which wraps the core object and implements the trait interface, is inserted automatically and will possibly activate other rules. An immediate advantage of declaring and using interfaces, getting the implementation proxy for free from the Drools engine, is that multiple inheritance hierarchies can be exploited when writing rules. The core classes, however, need not implement any of those interfaces statically, also facilitating the use of legacy classes as cores. In fact, any object can don a trait, provided that they are declared as @Traitable. Notice that this annotation used to be optional, but now is mandatory.
import org.drools.core.factmodel.traits.Traitable;
declare Customer
@Traitable
code : String
balance : long
end
The only connection between core classes and trait interfaces is at the proxy level: a trait is not specifically tied to a core class. This means that the same trait can be applied to totally different objects. For this reason, the trait does not transparently expose the fields of its core object. So, when writing a rule using a trait interface, only the fields of the interface will be available, as usual. However, any field in the interface that corresponds to a core object field, will be mapped by the proxy class:
when
$o: OrderItem( $p : price, $code : custCode )
$c: GoldenCustomer( code == $code, $a : balance, $d: discount )
then
$c.setBalance( $a - $p*$d );
end
In this case, the code and balance would be read from the underlying Customer object. Likewise, the setAccount will modify the underlying object, preserving a strongly typed access to the data structures. A hard field must have the same name and type both in the core class and all donned interfaces. The name is used to establish the mapping: if two fields have the same name, then they must also have the same declared type. The annotation @org.drools.core.factmodel.traits.Alias allows to relax this restriction. If an @Alias is provided, its value string will be used to resolve mappings instead of the original field name. @Alias can be applied both to traits and core beans.
import org.drools.core.factmodel.traits.*;
declare trait GoldenCustomer
balance : long @Alias( "org.acme.foo.accountBalance" )
end
declare Person
@Traitable
name : String
savings : long @Alias( "org.acme.foo.accountBalance" )
end
when
GoldenCustomer( balance > 1000 ) // will react to new Person( 2000 )
then
end
More work is being done on relaxing this constraint (see the experimental section on "logical" traits later). Now, one might wonder what happens when a core class does NOT provide the implementation for a field defined in an interface. We call hard fields those trait fields which are also core fields and thus readily available, while we define soft those fields which are NOT provided by the core class. Hidden fields, instead, are fields in the core class not exposed by the interface.
So, while hard field management is intuitive, there remains the problem of soft and hidden fields. Hidden fields are normally only accessible using the core class directly. However, the "fields" Map can be used on a trait interface to access a hidden field. If the field can’t be resolved, null will be returned. Notice that this feature is likely to change in the future.
when
$sc : GoldenCustomer( fields[ "age" ] > 18 ) // age is declared by the underlying core class, but not by GoldenCustomer
then
Soft fields, instead, are stored in a Map-like data structure that is specific to each core object and referenced by the proxy(es), so that they are effectively shared even when an object dons multiple traits.
when
$sc : GoldenCustomer( $c : code, // hard getter
$maxExpense : maxExpense > 1000 // soft getter
)
then
$sc.setDiscount( ... ); // soft setter
end
A core object also holds a reference to all its proxies, so that it is possible to track which type(s) have been added to an object, using a sort of dynamic "instanceof" operator, which we called isA. The operator can accept a String, a class literal or a list of class literals. In the latter case, the constraint is satisfied only if all the traits have been donned.
$sc : GoldenCustomer( $maxExpense : maxExpense > 1000,
this isA "SeniorCustomer", this isA [ NationalCustomer.class, OnlineCustomer.class ]
)
Eventually, the business logic may require that a trait is removed from a wrapped object. To this end, we provide two options. The first is a "logical don", which will result in a logical insertion of the proxy resulting from the traiting operation. The TMS will ensure that the trait is removed when its logical support is removed in the first place.
then
don( $x, // core object
Customer.class, // trait class
true // optional flag for logical insertion
)
The second is the use of the "shed" keyword, which causes the removal of any type that is a subtype (or equivalent) of the one passed as an argument. Notice that, as of version 5.5, shed would only allow to remove a single specific trait.
then
Thing t = shed( $x, GoldenCustomer.class ) // also removes any trait that
This operation returns another proxy implementing the org.drools.core.factmodel.traits.Thing interface, where the getFields() and getCore() methods are defined. Internally, in fact, all declared traits are generated to extend this interface (in addition to any others specified). This allows to preserve the wrapper with the soft fields which would otherwise be lost.
A trait and its proxies are also correlated in another way. Starting from version 5.6, whenever a core object is "modified", its proxies are "modified" automatically as well, to allow trait-based patterns to react to potential changes in hard fields. Likewise, whenever a trait proxy (matched by a trait pattern) is modified, the modification is propagated to the core class and the other traits. Moreover, whenever a don operation is performed, the core object is also modified automatically, to reevaluate any "isA" operation which may be triggered.
Potentially, this may result in a high number of modifications, impacting performance (and correctness) heavily. So two solutions are currently implemented. First, whenever a core object is modified, only the most specific traits (in the sense of inheritance between trait interfaces) are updated and an internal blocking mechanism is in place to ensure that each potentially matching pattern is evaluated once and only once. So, in the following situation:
declare trait GoldenCustomer end
declare trait NationalGoldenCustomer extends GoldenCustomer end
declare trait SeniorGoldenCustomer extends GoldenCustomer end
a modification of an object that is both a GoldenCustomer, a NationalGoldenCustomer and a SeniorGoldenCustomer wold cause only the latter two proxies to be actually modified. The first would match any pattern for GoldenCustomer and NationalGoldenCustomer; the latter would instead be prevented from rematching GoldenCustomer, but would be allowed to match SeniorGoldenCustomer patterns. It is not necessary, instead, to modify the GoldenCustomer proxy since it is already covered by at least one other more specific trait.
The second method, up to the user, is to mark traits as @PropertyReactive. Property reactivity is trait-enabled and takes into account the trait field mappings, so to block unnecessary propagations.
7.2.1. Cascading traits
WARNING : This feature is extremely experimental and subject to changes
Normally, a hard field must be exposed with its original type by all traits donned by an object, to prevent situations such as
declare Person
@Traitable
name : String
id : String
end
declare trait Customer
id : String
end
declare trait Patient
id : long // Person can't don Patient, or an exception will be thrown
end
Should a Person don both Customer and Patient, the type of the hard field id would be ambiguous. However, consider the following example, where GoldenCustomers refer their best friends so that they become Customers as well:
declare Person
@Traitable( logical=true )
bestFriend : Person
end
declare trait Customer end
declare trait GoldenCustomer extends Customer
refers : Customer @Alias( "bestFriend" )
end
Aside from the @Alias, a Person-as-GoldenCustomer’s best friend might be compatible with the requirements of the trait GoldenCustomer, provided that they are some kind of Customer themselves. Marking a Person as "logically traitable" - i.e. adding the annotation @Traitable( logical = true ) - will instruct the Drools engine to try and preserve the logical consistency rather than throwing an exception due to a hard field with different type declarations (Person vs Customer). The following operations would then work:
Person p1 = new Person();
Person p2 = new Person();
p1.setBestFriend( p2 );
...
Customer c2 = don( p2, Customer.class );
...
GoldenCustomer gc1 = don( p1, GoldenCustomer.class );
...
p1.getBestFriend(); // returns p2
gc1.getRefers(); // returns c2, a Customer proxy wrapping p2
Notice