JBoss.orgCommunity Documentation
A RuleFlow is a process that describes the order in which a series of steps need to be executed, using a flow chart. A process consists of a collection of nodes that are linked to each other using connections. Each of the nodes represents one step in the overall process while the connections specify how to transition from one node to the other. A large selection of predefined node types have been defined. This chapter describes how to define such processes and use them in your application.
Processes can be created by using one of the following three methods:
The graphical RuleFlow editor is a editor that allows you to create a process by dragging and dropping different nodes on a canvas and editing the properties of these nodes. The graphical RuleFlow editor is part of the Drools plug-in for Eclipse. Once you have set up a Drools project (check the IDE chapter if you do not know how to do this), you can start adding processes. When in a project, launch the "New" wizard: use Ctrl+N or right-click the directory you would like to put your ruleflow in and select "New", then "Other...". Choose the section on "Drools" and then pick "RuleFlow file". This will create a new .rf file.
Next you will see the graphical RuleFlow editor. Now would be a good time to switch to the Drools Perspective (if you haven't done so already). This will tweak the user interface so that it is optimal for rules. Then, ensure that you can see the Properties View down the bottom of the Eclipse window, as it will be necessary to fill in the different properties of the elements in your process. If you cannot see the properties view, open it using the menu "Window", then "Show View" and "Other...", and under the "General" folder select the Properties View.
The RuleFlow editor consists of a palette, a canvas and an Outline View. To add new elements to the canvas, select the element you would like to create in the palette and then add them to the canvas by clicking on the preferred location. For example, click on the "RuleFlowGroup" icon in the "Components" palette of the GUI: you can then draw a few rule flow groups. Clicking on an element in your rule flow allows you to set the properties of that element. You can connect the nodes (as long as it is permitted by the different types of nodes) by using "Connection Creation" from the "Components" palette.
You can keep adding nodes and connections to your process until it represents the business logic that you want to specify. You'll probably need to check the process for any missing information (by pressing the green "Check" icon in the IDE menu bar) before trying to use it in your application.
It is also possible to specify processes using the underlying XML directly. The syntax of these XML processes is defined using an XML Schema definition. For example, the following XML fragment shows a simple process that contains a sequence of a Start node, an Action node that prints "Hello World" to the console, and an End node.
<?xml version="1.0" encoding="UTF-8"?> <process xmlns="http://drools.org/drools-5.0/process" xmlns:xs="http://www.w3.org/2001/XMLSchema-instance" xs:schemaLocation="http://drools.org/drools-5.0/process drools-processes-5.0.xsd" type="RuleFlow" name="ruleflow" id="com.sample.ruleflow" package-name="com.sample" > <header> </header> <nodes> <start id="1" name="Start" x="16" y="16" /> <actionNode id="2" name="Hello" x="128" y="16" > <action type="expression" dialect="mvel" >System.out.println("Hello World");</action> </actionNode> <end id="3" name="End" x="240" y="16" /> </nodes> <connections> <connection from="1" to="2" /> <connection from="2" to="3" /> </connections> </process>
The process XML file should consist of exactly one <process> element. This element contains parameters related to the process (its type, name, id and package name), and consists of three subsections: a <header> (where process-level information like variables, globals, imports and swimlanes can be defined), a <nodes> section that defines each of the nodes in the process, and a <connections> section that contains the connections between all the nodes in the process. In the nodes section, there is a specific element for each node, defining the various parameters and, possibly, sub-elements for that node type.
While it is recommended to define processes using the graphical editor or
the underlying XML (to shield yourself from internal APIs), it is also possible
to define a process using the Process API directly. The most important process
elements are defined in the packages org.drools.workflow.core
and
org.drools.workflow.core.node
. A "fluent API" is provided that
allows you to easily construct processes in a readable manner using factories.
At the end, you can validate the process that you were constructing manually.
Some examples about how to build processes using this fluent API are added below.
This is a simple example of a basic process with a ruleset node only:
RuleFlowProcessFactory factory = RuleFlowProcessFactory.createProcess("org.drools.HelloWorldRuleSet"); factory // Header .name("HelloWorldRuleSet") .version("1.0") .packageName("org.drools") // Nodes .startNode(1).name("Start").done() .ruleSetNode(2) .name("RuleSet") .ruleFlowGroup("someGroup").done() .endNode(3).name("End").done() // Connections .connection(1, 2) .connection(2, 3); RuleFlowProcess process = factory.validate().getProcess();
You can see that we start by calling the static createProcess()
method from the RuleFlowProcessFactory
class. This method creates
a new process with the given id and returns the RuleFlowProcessFactory
that can be used to create the process. A typical process consists of three parts.
The header part comprises global elements like the name of the process, imports,
variables, etc. The nodes section contains all the different nodes that are part of the
process. The connections section finally links these nodes to each other
to create a flow chart.
In this example, the header contains the name and the version of the process and the package name. After that, you can start adding nodes to the current process. If you have auto-completion you can see that you have different methods to create each of the supported node types at your disposal.
When you start adding nodes to the process, in this example by calling
the startNode()
, ruleSetNode()
and endNode()
methods, you can see that these methods return a specific NodeFactory
,
that allows you to set the properties of that node. Once you have finished
configuring that specific node, the done()
method returns you to the
current RuleFlowProcessFactory
so you can add more nodes, if necessary.
When you are finished adding nodes, you must connect them by creating
connections between them. This can be done by calling the method
connection
, which will link previously created nodes.
Finally, you can validate the generated process by calling the
validate()
method and retrieve the created
RuleFlowProcess
object.
This example is using Split and Join nodes:
RuleFlowProcessFactory factory = RuleFlowProcessFactory.createProcess("org.drools.HelloWorldJoinSplit"); factory // Header .name("HelloWorldJoinSplit") .version("1.0") .packageName("org.drools") // Nodes .startNode(1).name("Start").done() .splitNode(2).name("Split").type(Split.TYPE_AND).done() .actionNode(3).name("Action 1") .action("mvel", "System.out.println(\"Inside Action 1\")").done() .actionNode(4).name("Action 2") .action("mvel", "System.out.println(\"Inside Action 2\")").done() .joinNode(5).type(Join.TYPE_AND).done() .endNode(6).name("End").done() // Connections .connection(1, 2) .connection(2, 3) .connection(2, 4) .connection(3, 5) .connection(4, 5) .connection(5, 6); RuleFlowProcess process = factory.validate().getProcess();
This shows a simple example using Split and Join nodes. As you can see, a Split node can have multiple outgoing connections, and a Join node multiple incoming connections. To understand the behavior of the different types of Split and Join nodes, take a look at the documentation for each of these nodes.
Now we show a more complex example with a ForEach node, where we have nested nodes:
RuleFlowProcessFactory factory = RuleFlowProcessFactory.createProcess("org.drools.HelloWorldForeach"); factory // Header .name("HelloWorldForeach") .version("1.0") .packageName("org.drools") // Nodes .startNode(1).name("Start").done() .forEachNode(2) // Properties .linkIncomingConnections(3) .linkOutgoingConnections(4) .collectionExpression("persons") .variable("child", new ObjectDataType("org.drools.Person")) // Nodes .actionNode(3) .action("mvel", "System.out.println(\"inside action1\")").done() .actionNode(4) .action("mvel", "System.out.println(\"inside action2\")").done() // Connections .connection(3, 4) .done() .endNode(5).name("End").done() // Connections .connection(1, 2) .connection(2, 5); RuleFlowProcess process = factory.validate().getProcess();
Here you can see how we can include a ForEach node with nested action nodes.
Note the linkIncomingConnections()
and
linkOutgoingConnections()
methods that are
called to link the ForEach node with the internal action node.
These methods are used to specify the first and last nodes inside the ForEach
composite node.
There are two things you need to do to be able to execute processes from within your application: (1) you need to create a Knowledge Base that contains the definition of the process, and (2) you need to start the process by creating a session to communicate with the process engine and start the process.
Creating a Knowledge Base: Once you have a valid process, you can add the process to the Knowledge Base. Note that this is almost identical to adding rules to the Knowledge Base, except for the type of knowledge added:
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder(); kbuilder.add( ResourceFactory.newClassPathResource("MyProcess.rf"), ResourceType.DRF );
After adding all your knowledge to the builder (you can add more than one process, and even rules), you should probably check whether the process (and rules) have been parsed correctly and write out any errors like this:
KnowledgeBuilderErrors errors = kbuilder.getErrors(); if (errors.size() > 0) { for (KnowledgeBuilderError error: errors) { System.err.println(error); } throw new IllegalArgumentException("Could not parse knowledge."); }
Next, you need to create the Knowledge Base that contains all the necessary processes (and rules) like this:
KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase(); kbase.addKnowledgePackages(kbuilder.getKnowledgePackages());
Starting a process: Processes are
only executed if you explicitly state that they should be executed. This
is because you could potentially define a lot of processes in your
Knowledge Base and the engine has no way to know when you would like
to start each of these. To activate a particular process, you will need
to start it by calling the startProcess
method on your session.
For example:
StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession(); ksession.startProcess("com.sample.MyProcess");
The parameter of the startProcess
method represents the id
of the process that needs to be started. This process id needs to be
specified as a property of the process, shown in the Properties View
when you click the background canvas of your process. If your process
also requires the execution of rules during the execution of the process,
you also need to call the ksession.fireAllRules()
method to
make sure the rules are executed as well. That's it!
You may specify additional parameters that are used to pass
on input data to the process, using the
startProcess(String processId, Map parameters)
method, which
takes an additional set of parameters as name-value pairs. These parameters
are then copied to the newly created process instance as top-level variables
of the process.
You can also start a process from within a rule consequence, using
kcontext.getKnowledgeRuntime().startProcess("com.sample.MyProcess");
A ruleflow process is a flow chart where different types of nodes are linked using connections. The process itself exposes the following properties:
Id: The unique id of the process.
Name: The display name of the process.
Version: The version number of the process.
Package: The package (namespace) the process is defined in.
Variables: Variables can be defined to store data during the execution of your process. See section “Data” for details.
Swimlanes: Specify the actor responsible for the execution of human tasks. See chapter “Human Tasks” for details.
Exception Handlers: Specify the behaviour when a fault occurs in the process. See section “Exceptions” for details.
Connection Layout: Specify how the connections are visualized on the canvas using the connection layout property:
'Manual' always draws your connections as lines going straight from their start to end point (with the possibility to use intermediate break points).
'Shortest path' is similar, but it tries to go around any obstacles it might encounter between the start and end point, to avoid lines crossing nodes.
'Manhattan' draws connections by only using horizontal and vertical lines.
A RuleFlow process supports different types of nodes:
Start: The start of the ruleflow. A ruleflow should have exactly one start node, which cannot have incoming connections and should have one outgoing connection. Whenever a RuleFlow process is started, execution will start at this node and automatically continue to the first node linked to this start node, and so on. It contains the following properties:
Id: The id of the node (which is unique within one node container).
Name: The display name of the node.
Triggers: A Start node can also specify additional triggers that can be used to automatically start the process. Examples are a "constraint" trigger that automatically starts the process if a given rule or constraint is satisfied, or an "event" trigger that automatically starts the process if a specific event is signalled.
End: The end of the ruleflow. A ruleflow should have one or more End nodes. The End node should have one incoming connection and cannot have outgoing connections. It contains the following properties:
Id: The id of the node (which is unique within one node container).
Name: The display name of the node.
Terminate: An End node can be terminating for the entire process (default) or just for the path. If the process is terminated, all nodes that are still active (on parallel paths) in this ruleflow are cancelled. Non-terminating End nodes are simply ends for some path, while other parallel paths still continue.
RuleFlowGroup: Represents a set
of rules that need to be evaluated. The rules are evaluated when the node
is reached. A RuleFlowGroup node should have one incoming connection and
one outgoing connection. Rules can become part of a specific ruleflow group
using the ruleflow-group
attribute in the header. When a RuleFlowGroup node
is reached in the ruleflow, the engine will start executing rules that are
part of the corresponding ruleflow-group (if any). Execution will
automatically continue to the next node if there are no more active rules in
this ruleflow group. This means that, during the execution of a ruleflow group,
it is possible that new activations belonging to the currently active
ruleflow group are added to the Agenda due to changes made to the facts by
the other rules. Note that the ruleflow will immediately continue with the
next node if it encounters a ruleflow group where there are no active rules
at that time. If the ruleflow group was already active, the ruleflow group
will remain active and exeution will only continue if all active rules of the
ruleflow group has been completed. It contains the following properties:
Id: The id of the node (which is unique within one node container).
Name: The display name of the node.
RuleFlowGroup: The name of the ruleflow group that represents the set of rules of this RuleFlowGroup node.
Timers: Timers that are linked to this node. See section “Timers” for details.
Split: Allows you to create branches in your ruleflow. A Split node should have one incoming connection and two or more outgoing connections. There are three types of Split nodes currently supported:
AND means that the control flow will continue in all outgoing connections simultaneously.
XOR means that exactly one of the outgoing connections will be chosen. The decision is made by evaluating the constraints that are linked to each of the outgoing connections. Constraints are specified using the same syntax as the left-hand side of a rule. The constraint with the lowest priority number that evaluates to true is selected. Note that you should always make sure that at least one of the outgoing connections will evaluate to true at runtime (the ruleflow will throw an exception at runtime if it cannot find at least one outgoing connection). For example, you could use a connection which is always true (default) with a high priority number to specify what should happen if none of the other connections can be taken.
OR means that all outgoing connections whose condition evaluates to true are selected. Conditions are similar to the XOR split, except that no priorities are taken into account. Note that you should make sure that at least one of the outgoing connections will evaluate to true at runtime because the ruleflow will throw an exception at runtime if it cannot determine an outgoing connection.
It contains the following properties:
Id: The id of the node (which is unique within one node container).
Name: The display name of the node.
Type: The type of the split node, i.e., AND, XOR or OR (see above).
Constraints: The constraints linked to each of the outgoing connections (in case of an (X)OR split).
Join: Allows you to synchronize multiple branches. A join node should have two or more incoming connections and one outgoing connection. There are four types of splits currently supported:
AND means that is will wait until all incoming branches are completed before continuing.
XOR means that it continues as soon as one of its incoming branches has been completed. If it is triggered from more than one incoming connection, it will trigger the next node for each of those triggers.
Discriminator means that it continues if one of its incoming branches has been completed. Completions of other incoming branches are registered until all connections have completed. At that point, the node will be reset, so that it can trigger again when one of its incoming branches has been completed once more.
n-of-m means that it continues if n of its m incoming branches have been completed. The variable n could either be hardcoded to a fixed value, or refer to a process variable that will contain the number of incoming branches to wait for.
It contains the following properties:
Id: The id of the node (which is unique within one node container).
Name: The display name of the node.
Type: The type of the Join node, i.e. AND, XOR or Discriminator (see above).
n: The number of incoming connections to wait for (in case of a n-of-m join).
EventWait (or Milestone): Represents a wait state. An EventWait should have one incoming connection and one outgoing connection. It specifies a constraint which defines how long the process should wait in this state before continuing. For example, a constraint in an order entry application might specify that the process should wait until no more errors are found in the given order. Constraints are specified using the same syntax as the left-hand side of a rule. When a Wait node is reached in the ruleflow, the engine will check the associated constraint. If the constraint evaluates to true directly, the flow will continue imediately. Otherwise, the flow will continue if the constraint is satisfied later on, for example when a fact is inserted, updated or removed from the working memory. It contains the following properties:
Id: The id of the node (which is unique within one node container).
Name: The display name of the node.
Constraint: Defines when the process can leave this state and continue.
SubFlow: represents the invocation of another process from within this process. A sub-process node should have one incoming connection and one outgoing connection. When a SubFlow node is reached in the ruleflow, the engine will start the process with the given id. It contains the following properties:
Id: The id of the node (which is unique within one node container).
Name: The display name of the node.
ProcessId: The id of the process that should be executed.
Wait for completion: If this property is true, the SubFlow node will only continue if that SubFlow process has terminated its execution (completed or aborted); otherwise it will continue immediately after starting the subprocess.
Independent: If this property is true, the subprocess is started as an independent process, which means that the SubFlow process will not be terminated if this process reaches an end node; otherwise the active sub-process will be cancelled on termination (or abortion) of the process.
On-entry and on-exit actions: Actions that are executed upon entry or exit of this node, respectively.
Parameter in/out mapping: A SubFlow node can also define in- and out-mappings for variables. The value of variables in this process with variable names given in the "in" mapping will be used as parameters (with the associated parameter name) when starting the process. The value of the variables in the subprocess with the given variable name in the "out" mappings will be copied to the variables of this process when the subprocess has been completed. Note that you can use "out" mappings only when "Wait for completion" is set to true.
Timers: Timers that are linked to this node. See section “Timers” for details.
Action: represents an action that
should be executed in this ruleflow. An Action node should have one incoming
connection and one outgoing connection. The associated action specifies what
should be executed, the dialect used for coding the action (i.e., Java or MVEL),
and the actual action code. This code can access any globals, the predefined
variable drools
referring to a KnowledgeHelper
object
(which can, for example,
be used to retrieve the Working Memory by calling
drools.getWorkingMemory()
), and the variable context
that references the ProcessContext
object (which can,
for example, be used to access the current ProcessInstance
or
NodeInstance
, and to get and set variables). When an Action node
is reached in the ruleflow, it will execute the action and then continue with the
next node. It contains the following properties:
Id: The id of the node (which is unique within one node container).
Name: The display name of the node.
Action: The action associated with this action node.
Timer: represents a timer that can trigger one or multiple times after a given period of time. A Timer node should have one incoming connection and one outgoing connection. The timer delay specifies how long (in milliseconds) the timer should wait before triggering the first time. The timer period specifies the time between two subsequent triggers. A period of 0 means that the timer should only be triggered once. When a Timer node is reached in the ruleflow, it will start the associated timer. The timer is cancelled if the timer node is cancelled (e.g., by completing or aborting the process). Consult the section “Timers” for more information. - The Timer node contains the following properties:
Id: The id of the node (which is unique within one node container).
Name: The display name of the node.
Timer delay: The delay (in milliseconds) that the node should wait before triggering the first time.
Timer period: The period (in milliseconds) between two subsequent triggers. If the period is 0, the timer should only be triggered once.
Fault: A Fault node can be used to signal an exceptional condition in the process. It should have one incoming connection and no outgoing connections. When a Fault node is reached in the ruleflow, it will throw a fault with the given name. The process will search for an appropriate exception handler that is capable of handling this kind of fault. If no fault handler is found, the process instance will be aborted. A Fault node contains the following properties:
Id: The id of the node (which is unique within one node container).
Name: The display name of the node.
FaultName: The name of the fault. This name is used to search for appriopriate exception handlers that is capable of handling this kind of fault.
FaultVariable: The name of the variable that contains the data associated with this fault. This data is also passed on to the exception handler (if one is found).
Event: An Event node can be used to respond to internal or external events during the execution of the process. An Event node should have no incoming connections and one outgoing connection. It specifies the type of event that is expected. Whenever that type of event is detected, the node connected to this Event node will be triggered. It contains the following properties:
Id: The id of the node (which is unique within one node container).
Name: The display name of the node.
EventType: The type of event that is expected.
VariableName: The name of the variable that will contain the data associated with this event (if any) when this event occurs.
Scope: An event could be used to listen
to internal events only, i.e., events that are signalled to this
process instance directly, using
processInstance.signalEvent(String type, Object data)
.
When an Event node is defined as external, it will also be listening
to external events that are signalled to the process engine directly,
using workingMemory.signalEvent(String type, Object event)
.
Human Task: Processes can also involve tasks that need to be executed by human actors. A Human Task node represents an atomic task to be executed by a human actor. It should have one incoming connection and one outgoing connection. Human Task nodes can be used in combination with Swimlanes to assign multiple human tasks to similar actors. Refer to chapter “Human Tasks” for more details. A Human Task node is actually nothing more than a specific type of work item node (of type "Human Task"). A Human Task node contains the following properties:
Id: The id of the node (which is unique within one node container).
Name: The display name of the node.
TaskName: The name of the human task.
Priority: An integer indicating the priority of the human task.
Comment: A comment associated with the human task.
ActorId: The actor id that is responsible for executing the human task. A list of actor id's can be specified using a comma (',') as separator.
Skippable: Specifies whether the human task can be skipped, i.e., whether the actor may decide not to execute the task.
Content: The data associated with this task.
Swimlane: The swimlane this human task node is part of. Swimlanes make it easy to assign multiple human tasks to the same actor. See the human tasks chapter for more detail on how to use swimlanes.
Wait for completion: If this property is true, the human task node will only continue if the human task has been terminated (i.e., by completing or reaching any other terminal state); otherwise it will continue immediately after creating the human task.
On.entry and on-exit actions: Actions that are executed upon entry and exit of this node, respectively.
Parameter mapping: Allows copying the value of process variables to parameters of the human task. Upon creation of the human tasks, the values will be copied.
Result mapping: Allows copying the value of result parameters of the human task to a process variable. Upon completion of the human task, the values will be copied. Note that you can use result mappings only when "Wait for completion" is set to true. A human task has a result variable "Result" that contains the data returned by the human actor. The variable "ActorId" contains the id of the actor that actually executed the task.
Timers: Timers that are linked to this node. Consult the section “Timers” for details.
Composite: A Composite node is a node that can contain other nodes so that it acts as a node container. This allows not only the embedding of a part of the flow within such a Composite node, but also the definition of additional variables and exception handlers that are accessible for all nodes inside this container. A Composite node should have one incoming connection and one outgoing connection. It contains the following properties:
Id: The id of the node (which is unique within one node container).
Name: The display name of the node.
StartNodeId: The id of the node (within this node container) that should be triggered when this node is triggered.
EndNodeId: The id of the node (within this node container) that represents the end of the flow contained in this node. When this node is completed, the composite node will also be completed and trigger its outgoing connection. All other executing nodes within this composite node will be cancelled.
Variables: Additional variables can be defined to store data during the execution of this node. See section “Data” for details.
Exception Handlers: Specify the behavior when a fault occurs in this node container. See section “Exceptions” for details.
ForEach: A ForEach node is a special kind of composite node that allows you to execute the contained flow multiple times, once for each element in a collection. A ForEach node should have one incoming connection and one outgoing connection. A ForEach node awaits the completion of the embedded flow for each of the collection''s elements before continuing. It contains the following properties:
Id: The id of the node (which is unique within one node container).
Name: The display name of the node.
StartNodeId: The id of the node (within this node container) that should be triggered for each of the elements in a collection.
EndNodeId: The id of the node (within this node container) that represents the end of the flow contained in this node. When this node is completed, the execution of the ForEach node will also be completed for the current collection element. The outgoing connection is triggered if the collection is exhausted. All other executing nodes within this composite node will be cancelled.
CollectionExpression: The name of a
variable that represents the collection of elements that should
be iterated over. The collection variable should be of type
java.util.Collection
.
VariableName: The name of the variable to contain the current element from the collection. This gives nodes within the composite node access to the selected element.
WorkItem: Represents an (abstract) unit of work that should be executed in this process. All work that is executed outside the process engine should be represented (in a declarative way) using a WorkItem node. Different types of work items are predefined, e.g., sending an email, logging a message, etc. Users can define domain-specific work items, using a unique name and by defining the parameters (input) and results (output) that are associated with this type of work. Refer to the chapter “Domain-specific processes” for a detailed explanation and illustrative examples of how to define and use work items in your processes. When a WorkItem node is reached in the process, the associated work item is executed. A WorkItem node should have one incoming connection and one outgoing connection.
Id: The id of the node (which is unique within one node container).
Name: The display name of the node.
Wait for completion: If the property "Wait for completion" is true, the WorkItem node will only continue if the created work item has terminated (completed or aborted) its execution; otherwise it will continue immediately after starting the work item.
Parameter mapping: Allows copying the value of process variables to parameters of the work item. Upon creation of the work item, the values will be copied.
Result mapping: Allows copying the value
of result parameters of the work item to a process variable. Each
type of work can define result parameters that will (potentially)
be returned after the work item has been completed. A result
mapping can be used to copy the value of the given result parameter
to the given variable in this process. For example, the "FileFinder"
work item returns a list of files that match the given search
criteria within the result parameter Files
. This list
of files can then be bound to a process variable for use within the
process. Upon completion of the work item, the values will be copied.
Note that you can use result mappings only when "Wait for completion"
is set to true.
On-entry and on-exit actions: Actions that are executed upon entry or exit of this node, respectively.
Timers: Timers that are linked to this node. See the section “Timers” for details.
Additional parameters: Each type of work
item can define additional parameters that are relevant for that
type of work. For example, the "Email" work item defines additional
parameters such as From
, To
, Subject
and Body
. The user can either provide values for these
parameters directly, or define a
parameter mapping that will copy the value of the given variable
in this process to the given parameter; if both are specified, the
mapping will have precedence. Parameters of type String
can use
#{expression}
to embed a value in the
string. The value will be retrieved when creating the work item, and the
substitution expression will be replaced by the result of calling
toString()
on the variable. The expression could
simply be the name of a variable (in which case it resolves
to the value of the variable), but more advanced MVEL expressions
are possible as well, e.g., #{person.name.firstname}
.
While the flow graph focusses on specifying the control flow of the process, it is usually also necessary to look at the process from a data perspective. Throughout the execution of a process, data can retrieved, stored, passed on and used.
For storing runtime data, during the execution of the process, you use variables. A variable is defined by a name and a data type. This could be a basic data type, such as boolean, int, or String, or any kind of Object subclass. Variables can be defined inside a variable scope. The top-level scope is the variable scope of the process itself. Subscopes can be defined using a Composite node. Variables that are defined in a subscope are only accessible for nodes within that scope.
Whenever a variable is accessed, the process will search for the appropriate variable scope that defines the variable. Nesting of variable scopes is allowed. A node will always search for a variable in its parent container. If the variable cannot be found, it will look in that one's parent container, and so on, until the process instance itself is reached. If the variable cannot be found, a read access yields null, and a write access produces an error message, with the process continuing its execution.
Variables can be used in various ways:
startProcess
method. These parameters will be set as
variables on the process scope.
// call method on the process variable "person" person.setAge(10);Changing the value of a variable can be done through the Knowledge Context:
kcontext.setVariable(variableName, value);
#{expression}
. The results of a WorkItem
can also be copied to a variable using a result mapping.
Finally, processes and rules all have access to globals, i.e., globally defined variables that are considered immutable with regard to rule evaluation, and data in the Knowledge Session. The Knowledge Session can be accessed in actions using the Knowledge Context:
kcontext.getKnowledgeRuntime().insert( new Person(...) );
Constraints can be used in various locations in your processes, for example in a Split node using OR or XOR decisions, or as a constraint for an EventWait. Drools Flow supports two types of constraints:
person
being a variable
in the process:
return person.getAge() > 20;A similar example of a valid MVEL code constraint is:
return person.age > 20;
Person( age > 20 )This tests for a person older than 20 being in the Working Memory.
Rule constraints do not have direct access to variables defined
inside the process. It is however possible to refer to the current process
instance inside a rule constraint, by adding the process instance to the
Working Memory and matching for the process instance in your rule
constraint. We have added special logic to make sure that a variable
processInstance
of type WorkflowProcessInstance
will only match to the current process instance and not to other process
instances in the Working Memory. Note that you are however responsible
yourself to insert the process instance into the session and, possibly,
to update it, for example, using Java code or an on-entry or on-exit or
explicit action in your process. The following example of a rule
constraint will search for a person with the same name as the value
stored in the variable "name" of the process:
processInstance : WorkflowProcessInstance() Person( name == ( processInstance.getVariable("name") ) ) # add more constraints here ...
Actions can be used in different ways:
Actions have access to globals and the variables that are defined
for the process and the predefined variable context
. This
variable is of type
org.drools.runtime.process.ProcessContext
and can be used for
several tasks:
NodeInstance node = context.getNodeInstance(); String name = node.getNodeName();
WorkflowProcessInstance proc = context.getProcessInstance(); proc.signalEvent( type, eventObject );
Drools currently supports two dialects, Java and MVEL.
Java actions should be valid Java code. MVEL actions can use the business
scripting language MVEL to express the action. MVEL accepts any valid Java
code but additionally provides support for nested accesses of parameters
(e.g., person.name
instead of person.getName()
),
and many other scripting improvements. Thus, MVEL expressions are more
convenient for the business user. For example, an action that prints out
the name of the person in the "requester" variable of the process would
look like this:
// Java dialect System.out.println( person.getName() ); // MVEL dialect System.out.println( person.name );
During the execution of a process, the process engine makes sure that all the relevant tasks are executed according to the process plan, by requesting the execution of work items and waiting for the results. However, it is also possible that the process should respond to events that were not directly requested by the process engine. Explicitly representing these events in a process allows the process author to specify how the process should react to such events.
Events have a type and possibly data associated with them. Users are free to define their own event types and their associated data.
A process can specify how to respond to events by using Event nodes. An Event node needs to specify the type of event the node is interested in. It can also define the name of a variable, which will receive the data that is associated with the event. This allows subsequent nodes in the process to access the event data and take appropriate action based on this data.
An event can be signalled to a running instance of a process in a number of ways:
context.getProcessInstance().signalEvent(type, eventData);
processInstance.signalEvent(type, eventData);
workingMemory.signalEvent(type, eventData);
Events could also be used to start a process. Whenever a Start node defines an event trigger of a specific type, a new process instance will be started every time that type of event is signalled to the process engine.
Whenever an exceptional condition occurs during the execution of a process, a fault could be raised to signal the occurrence of this exception. The process will then search for an appropriate exception handler that is capable of handling such a fault.
Similar to events, faults also have a type and possibly data associated with the fault. Users are free to define their own types of faults, together with their data.
Faults are effected by a Fault node, generating a fault of the given type, indicated by the fault name. If the Fault node specifies a fault variable, the value of the given variable will be associated with the fault.
Whenever a fault is created, the process will search for an appropriate exception handler that is capable of handling the given type of fault. Processes and Composite nodes both can define exception handlers for handling faults. Nesting of exception handlers is allowed; a node will always search for an appropriate exception handler in its parent container. If none is found, it will look in that one's parent container, and so on, until the process instance itself is reached. If no exception handler can be found, the process instance will be aborted, resulting in the cancellation of all nodes inside the process.
Exception handlers can also specify a fault variable. The data associated with the fault (if any) will be copied to this variable whenever an exception handler is selected to handle a fault. This allows subsequent Action nodes in the process to access the fault data and take appropriate action based on this data.
Exception handlers need to define an action that specifies how to respond to the given fault. In most cases, the behavior that is needed to react to the given fault cannot be expressed in one action. It is therefore recommended to have the exception handler signal an event of a specific type (in this case "Fault") using
context.getProcessInstance().signalEvent("FaultType", context.getVariable("FaultVariable");
Timers wait for a predefined amount of time, before triggering, once or repeatedly. They cou be used to specify time supervision, or to trigger certain logic after a certain period, or to repeat some action at regular intervals.
A Timer node is set up with a delay and a period. The delay specifies the amount of time (in milliseconds) to wait after node activation before triggering the timer the first time. The period defines the time between subsequent trigger activations. A period of 0 results in a one-shot timer.
The timer service is responsible for making sure that timers get triggered at the appropriate times. Timers can also be cancelled, meaning that the timer will no longer be triggered.
Timers can be used in two ways inside a process:
By default, the Drools engine is a passive component, meaning that
it will start processing only if you tell it to. Typically, you first
insert the necessary data and then tell the engine to start processing.
In passive mode, a timer that has been triggered will be put on the
action queue. This means that it will either be executed automatically
if the engine is still running, or it will become delayed until the engine
is told to start executing by the user (by calling
fireAllRules()
).
When using timers, it does usually make sense to let the Drools
engine operate as an active component, so that it will execute actions
whenever they become available, without the need to wait until the user
tells it to resume execution. Thus, a timer would become effective as
soon as it triggers. To make the engine fire all actions continuously,
you must call the method fireUntilHalt()
, whereupon the
engine operates until halt()
is called. Note that you should call
fireUntilHalt()
in a separate thread as it will only
return if the engine has been halted, either by the user or some some
logic calling halt()
on the session. The following
code snippet shows how to do this.
new Thread(new Runnable() { public void run() { ksession.fireUntilHalt(); } }).start(); // starting a new process instance ksession.startProcess("..."); // any timer that triggers will now be executed automatically
Drools already provides some functionality to define the order in which rules should be executed, like salience, activation groups, etc. When dealing with potentially many large rule-sets, managing the order in which rules are evaluated might become complex. Ruleflow allows you to specify the order in which rule sets should be evaluated by using a flow chart. This allows you to define which rule sets should be evaluated in sequence or in parallel, to specify conditions under which rule sets should be evaluated. This chapter contains a few ruleflow examples.
A ruleflow is a graphical description of a sequence of steps that the rule engine needs to take, where the order is important. The ruleflow can also deal with conditional branching, parallelism, and synchonization.
To use a ruleflow to describe the order in which rules should be
evaluated, you should first group rules into ruleflow groups using the
ruleflow-group
rule attribute ("options" in the GUI). Then you
should create a ruleflow graph (which is a flow chart) that graphically
describe the order in which the rules should be considered, by specifying
the order in which the ruleflow-groups should be evaluated.
rule 'YourRule' ruleflow-group 'group1' when ... then ... end
This rule belongs to the ruleflow-group called "group1".
The above rule flow specifies that the rules in the group "Check Order" must be executed before the rules in the group "Process Order". This means that first only rules which are marked as having a ruleflow-group of "Check Order" will be considered, and then, only if there aren't any more of those, the rules of "Process Order". That's about it. You could achieve similar results with either using salience, but this is harder to maintain and makes the time-relationship implicit in the rules, or Agenda groups. However, using a ruleflow makes the order of processing explicit, in a layer on top of the rule structure, so that managing more complex situations becomes much easier.
In practice, if you are using ruleflow, you will most likely be doing more than setting a simple sequence of groups to progress though. You'll use Split and Join nodes for modeling branches of processing, and define the flows of control by connections, from the Start to ruleflow groups, to Splits and then on to more groups, Joins, and so on. All this is done in a grphic editor.
The above flow is a more complex example, representing the rule flow for processing an insurance claim. Initially the claim data validation rules are processed, checking for data integrity, consistency and completeness. Next, in a Split node, there is a decision based on a condition based on the value ofthe claim. Processing will either move on to an "auto-settlement" group, or to another Split node, which checks whether there was a fatality in the incident. If so, it determines whether the "regular" of fatality specific rules should take effect, with more processing to follow. Based on a few conditions, many different control flows are possible. Note that all the rules can be in one package, with the control flow definition being separated from the actual rules.
To edit Split nodes you click on the node, which will show you a properties panel as shown above. You then have to choose the type: AND, OR, and XOR. If you choose OR, then any of the "outputs" of the split can happen, so that processing can proceed, in parallel, along two or more paths. If you chose XOR, then only one path is chosen.
If you choose OR or XOR, the "Constraints" row will have a square button on the right hand side. Clickin on this button opens the Constraint editor, where you set the conditions deciding which outgoing path to follow.
Choose the output path you want to set the constraints for (e.g. Autosettlement), and then you should see the following constraint editor:
This is a text editor where the constraints - which are like the condition part of a rule - are entered. These constraints operate on facts in the working memory. In the above example, there is a check for claims with a value of less than 250. Should this condition be true, then the associated path will be followed.
The XML format that was used in Drools4 to store RuleFlow processes was generated automatically, using XStream. As a result, it was hard to read by human readers and difficult to maintain and extend. The new Drools Flow XML format has been created to simplify this. This however means that, by default, old RuleFlow processes cannot simply be executed on the Drools5 engine.
We do however provide a Rule Flow Migrator that allows you to transform
your old .rf file to the new format. It uses an XSLT transformation to
generate the new XML based on the old content. You can use this class to
manually transform your old processes to the new format once when upgrading
from Drools4.x to Drools5.x. You can however also let the KnowledgeBuilder
automatically upgrade your processes to the new format when they are
loaded into the Knowledge Base. While this requires a conversion every time
the process is loaded into the Knowledge Base, it does support a more
seamless upgrade. To enact this automatic upgrade you need to set the
"drools.ruleflow.port" system property to "true", for example by adding
-Ddrools.ruleflow.port=true
when starting your application,
or by calling System.setProperty("drools.ruleflow.port", "true")
.
The Drools Eclipse plugin also automatically detects if an old RuleFlow file is opened. At that point, it will automatically perform the conversion and show the result in the graphical editor. You then need to save this result, either in a new file or overwriting the old one, to retain the old process in the new format. Note that the plugin does not support editing and saving processes in the old Drools4.x format.