Edition 5.2.0
Mono-spaced Bold
To see the contents of the filemy_next_bestselling_novelin your current working directory, enter thecat my_next_bestselling_novelcommand at the shell prompt and press Enter to execute the command.
Press Enter to execute the command.Press Ctrl+Alt+F2 to switch to the first virtual terminal. Press Ctrl+Alt+F1 to return to your X-Windows session.
mono-spaced bold. For example:
File-related classes includefilesystemfor file systems,filefor files, anddirfor directories. Each class has its own associated set of permissions.
Choose → → from the main menu bar to launch Mouse Preferences. In the Buttons tab, click the Left-handed mouse check box and click to switch the primary mouse button from the left to the right (making the mouse suitable for use in the left hand).To insert a special character into a gedit file, choose → → from the main menu bar. Next, choose → from the Character Map menu bar, type the name of the character in the Search field and click . The character you sought will be highlighted in the Character Table. Double-click this highlighted character to place it in the Text to copy field and then click the button. Now switch back to your document and choose → from the gedit menu bar.
Mono-spaced Bold Italic or Proportional Bold Italic
To connect to a remote machine using ssh, typesshat a shell prompt. If the remote machine isusername@domain.nameexample.comand your username on that machine is john, typessh john@example.com.Themount -o remountcommand remounts the named file system. For example, to remount thefile-system/homefile system, the command ismount -o remount /home.To see the version of a currently installed package, use therpm -qcommand. It will return a result as follows:package.package-version-release
Publican is a DocBook publishing system.
mono-spaced roman and presented thus:
books Desktop documentation drafts mss photos stuff svn books_tests Desktop1 downloads images notes scripts svgs
mono-spaced roman but add syntax highlighting as follows:
package org.jboss.book.jca.ex1; import javax.naming.InitialContext; public class ExClient { public static void main(String args[]) throws Exception { InitialContext iniCtx = new InitialContext(); Object ref = iniCtx.lookup("EchoBean"); EchoHome home = (EchoHome) ref; Echo echo = home.create(); System.out.println("Created Echo"); System.out.println("Echo.echo('Hello') = " + echo.echo("Hello")); } }
JBoss Enterprise BRMS Platform 5 and the component doc-BRMS_Rules_Reference_Guide. The following link will take you to a pre-filled bug report for this product: http://bugzilla.redhat.com/.
Description field. Be as specific as possible when describing the issue; this will help ensure that we can fix it quickly.
Document URL: Section Number and Name: Describe the issue: Suggestions for improvement: Additional information:
| Feature | Change |
|---|---|
| Section 2.2.4, “ Inference” and Section 2.2.5, “ Inference and TruthMaintenance” | New sections added about inference, and inference and truth maintenance has been added. |
| Section 4.8.2, “ Timers and Calendars ” | A new section providing details about timers and calendars has been added. |
| Section 4.10, “ Domain-Specific Languages ” | The section about Domain-Specific languages has updated with new material. |
| Section 7.2, “ Drools Runtimes” | A new section has been added to the JBoss Developer Studio chapter providing details about adding a Drools Runtime. |
when <conditions> then <actions>
ReteOO. This is an enhanced and optimized implementation of the Rete algorithm specifically for Object Oriented systems.
agenda manages these situations by using a conflict resolution strategy to dictate the order in which they are to be executed.



decision tree.
package com.company.license; public class Applicant { private String name; private int age; private boolean valid; public Applicant (String name, int age, boolean valid) { this.name = name; this.age = age; this.valid = valid; } //add getters & setters here }
package com.company.license;
rule "Is of valid age"
when
$a : Applicant( age < 18 )
then
$a.setValid( false );
endApplicant object is inserted into the rule engine, each rule's constraints evaluate it, looking for a match. (Note that there is always an implied constraint of "object type" after which there can be any number of explicit field constraints.)
Is of valid age rule there are two constraints:
Applicant.
Age must be less than eighteen.
$a is a binding variable. It exists to make possible a reference to the matched object in the rule's consequence (from which place the object's properties can be updated.)
$) is optional. It helps to differentiate between variable names and field names.
KnowledgeBuilder.
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder(); kbuilder.add( ResourceFactory.newClassPathResource( "licenseApplication.drl", getClass() ), ResourceType.DRL ); if ( kbuilder.hasErrors() ) { System.err.println( kbuilder.getErrors().toString() ); }
newClassPathResource() method to search the class-path for the licenseApplication.drl file. The ResourceType is written in the Drools Rule Language.
KnowledgeBuilder for any errors. If there are none, one is now ready to build the session.
KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase(); kbase.addKnowledgePackages( kbuilder.getKnowledgePackages() ); StatelessKnowledgeSession ksession = kbase.newStatelessKnowledgeSession(); Applicant applicant = new Applicant( "Mr John Smith", 16, true ); assertTrue( applicant.isValid() ); ksession.execute( applicant ); assertFalse( applicant.isValid() );
iterable, such as a collection. In this next example, one will be taught how to add another class called Application, which contains the date of the driver's license application. Another skill one will be taught is how to move the Boolean field entitled valid to the Application class.
public class Applicant { private String name; private int age; public Applicant (String name, int age) { this.name = name; this.age = age; } // getter and setter methods here } public class Application { private Date dateApplied; private boolean valid; public Application (boolean valid) { this.valid = valid; } // getter and setter methods here }
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 );
enditerable interface, so use the JDK converter methodinstead. (This method commences with the line, Arrays.asList(...).)
iterable list. Every collection element is inserted before any matched rules are fired:
StatelessKnowledgeSession ksession = kbase.newStatelessKnowledgeSession(); kbase.addKnowledgePackages( kbuilder.getKnowledgePackages() ); Applicant applicant = new Applicant( "Mr John Smith", 16 ); Application application = new Application(true); assertTrue( application.isValid() ); ksession.execute( Arrays.asList( new Object[] {application, applicant} )); assertFalse( application.isValid() );
execute(Object object) and execute(Iterable objects) methods are actually wrappers around a further method called execute(Command command) which comes from the BatchExecutor interface.
CommandFactory to create instructions, so that the following is equivalent to execute( Iterable it ):
ksession.execute( CommandFactory.newInsertElements(Arrays.asList(new Object[] {application,applicant})) );
BatchExecutor and CommandFactory are particularly useful when working with many different commands and result output identifiers:
List<Command> cmds = new ArrayList<Command>(); cmds.add( CommandFactory.newInsertObject(new Person("Mr John Smith"), "mrSmith")); cmds.add( CommandFactory.newInsertObject(new Person( "Mr John Doe" ), "mrDoe" )); ExecutionResults results = ksession.execute( CommandFactory.newBatchExecution(cmds) ); assertEquals( new Person("Mr John Smith"), results.getValue("mrSmith") );
CommandFactory supports many other commands that can be used in the BatchExecutor. Some of these are StartProcess, Query and SetGlobal.
dispose() method is called afterward running a stateful session. This is to ensure that there are no memory leaks. This is due to the fact that knowledge bases will obtain references to stateful knowledge sessions when they are created.
StatelessKnowledgeSession, the StatefulKnowledgeSession supports the BatchExecutor interface, the only difference being that, in this case, the FireAllRules command is not automatically called at the end.
public class Room { private String name // getter and setter methods here } public class Sprinkler { private Room room; private boolean on; // getter and setter methods here } public class Fire { private Room room; // getter and setter methods here } public class Alarm { }
Fire class. Insert the instance into the session.
Fire object's room field to constrain matches. This so that only the sprinkler for that room is checked. When this rule fires and the consequence executes, the sprinkler activates:
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());
endmodify statement. (It acts much like a "with" statement.)
"setters" that have been selected by the modify statement's control expression. These setters modify the data and then make the engine aware of the changes so that it can "reason" its way through them once more. This process is known as inference and it is the key to understanding how a stateful session's operates. (By contrast, stateless sessions do not use inference, so the engine does not need to be aware of changes to data.)
not matches only when something does not exist.
rule "When the fire is gone turn off the sprinkler"
when
$room : Room( )
$sprinkler : Sprinkler( room == $room, on == true )
not Fire( room == $room )
then
modify( $sprinkler ) { setOn( false ) };
System.out.println("Turn off the sprinkler for room "+$room.getName());
endAlarm object is created when there is a fire, but only one Alarm is needed for the entire building, no matter how many fires there might be. not's complement, exists can now be introduced. It matches one or more instances of a category:
rule "Raise the alarm when we have one or more fires"
when
exists Fire()
then
insert( new Alarm() );
System.out.println( "Raise the alarm" );
endnot again:
rule "Cancel the alarm when all the fires have gone"
when
not Fire()
$alarm : Alarm()
then
retract( $alarm );
System.out.println( "Cancel the alarm" );
endrule "Status output when things are ok"
when
not Alarm()
not Sprinkler( on == true )
then
System.out.println( "Everything is ok" );
endfireAlarm.drl. Save this file in a sub-directory on the class-path. Now build a knowledge base, using the new name, fireAlarm.drl:
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder(); kbuilder.add( ResourceFactory.newClassPathResource( "fireAlarm.drl", getClass() ), ResourceType.DRL ); if ( kbuilder.hasErrors() ) System.err.println( kbuilder.getErrors().toString() ); StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession();
ksession.fireAllRules(). This grants the matched rules permission to run but, since there is no fire, they will merely produce the health message:
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 Okay
fires now in the engine, call fireAllRules(). The alarm will be raised and the respective sprinklers will be turned on:
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.retract( kitchenFireHandle ); ksession.retract( officeFireHandle ); ksession.fireAllRules();
> Turn off the sprinkler for room office > Turn off the sprinkler for room kitchen > Cancel the alarm > Everything is ok
> Turn off the sprinkler for room office > Turn off the sprinkler for room kitchen > Cancel the alarm > Everything is ok
public void helloWorld(Person person) { if ( person.getName().equals( "Chuck" ) ) { System.out.println( "Hello Chuck" ); } }
rule "Hello World"
when
Person( name == "Chuck" )
then
System.out.println( "Hello Chuck" );
endengine
rule "show sprinklers in rooms"
when
$room : Room()
$sprinkler : Sprinkler()
then
System.out.println( "room:" + $room.getName() +
" sprinkler:" + $sprinkler.getRoom().getName() );
endselect * from Room, Sprinkler, which instructs every row in the Room table to join every row in the Sprinkler table, thereby resulting in the following output:
room:office sprinker:office room:office sprinkler:kitchen room:office sprinkler:livingroom room:office sprinkler:bedroom room:kitchen sprinkler:office room:kitchen sprinkler:kitchen room:kitchen sprinkler:livingroom room:kitchen sprinkler:bedroom room:livingroom sprinkler:office room:livingroom sprinkler:kitchen room:livingroom sprinkler:livingroom room:livingroom sprinkler:bedroom room:bedroom sprinkler:office room:bedroom sprinkler:kitchen room:bedroom sprinkler:livingroom room:bedroom sprinkler:bedroom
rule "show sprinklers in rooms"
when
$room : Room()
$sprinkler : Sprinkler( room == $room )
then
System.out.println( "room:" + $room.getName() +
" sprinkler:" + $sprinkler.getRoom().getName() );
endSprinkler assigned to each Room. As written in SQL, the corresponding query would be select * from Room, Sprinkler where Room == Sprinkler.room
room:office sprinkler:office room:kitchen sprinkler:kitchen room:livingroom sprinkler:livingroom room:bedroom sprinkler:bedroom
rule engine will need a way to manage the execution of outcomes. JBoss Rules achieves this using activations, agendas and a conflict resolution strategy.
knowledge bases and populating a StatefulKnowledgeSession with facts, so that code will not be repeated here.
rule engine at key stages.
Cashflow, Account and AccountPeriod:
public class Cashflow { private Date date; private double amount; private int type; long accountNo; // getter and setter methods here } public class Account { private long accountNo; private double balance; // getter and setter methods here } public AccountPeriod { private Date start; private Date end; // getter and setter methods here }
knowledge bases and how to instantiate facts to populate the StatefulKnowledgeSession. Therefore, tables will be used to show the state of the inserted data, as this makes things clearer for illustrative purposes. The tables below show that a single fact was inserted for the Account. A series of debits and credits extending over two quarters were also inserted into the Account as Cashflow objects.
Account fact was inserted along with four Cashflow facts.

rule "increase balance for credits"
when
ap : AccountPeriod()
acc : Account( $accountNo : accountNo )
CashFlow( type == CREDIT,
accountNo == $accountNo,
date >= ap.start && <= ap.end,
$amount : amount )
then
acc.setBalance(acc.getBalance() + $amount);
endrule "decrease balance for debits"
when
ap : AccountPeriod()
acc : Account( $accountNo : accountNo )
CashFlow( type == DEBIT,
accountNo == $accountNo,
date >= ap.start && <= ap.end,
$amount : amount )
then
acc.setBalance(acc.getBalance() - $amount);
endCashflow objects for credit and one for debit.

fireAllRules() method is called. Unless specified otherwise, the activations are executed one after another in an arbitrary order.


agenda.


agenda, they are said to be "in conflict", and a conflict resolution strategy is used to determine the order of execution. At the simplest level, the default strategy uses salience to determine rule priority. Each rule has a default salience value of zero and the higher the value, the higher the priority shall be. The salience can also be a negative value. This lets one order the execution of rules relative to each other.
rule "Print balance for AccountPeriod"
salience -50
when
ap : AccountPeriod()
acc : Account()
then
System.out.println( acc.getAccountNo() + " : " + acc.getBalance() );
endagenda. The three debit and credit rules are shown to be in arbitrary order, while the print rule is ranked last, so that it will execute afterwards.

Person fact with an age field and a rule that provides age policy control, we can infer whether a Person is an adult or a child and act on this.
rule "Infer Adult" when $p : Person( age >= 18 ) then insert( new IsAdult( $p ) ) end
Person who is 18 or over will have an instance of IsAdult inserted for them. This kind of fact is known as a relation. Rules can use this inferred relation in any rule:
$p : Person() IsAdult( person == $p )


IsAdult fact, as discussed previously, is inferred from the policy rules. Because the central government now maintains the IsAdult fact, the ID department only needs to know if the person is an adult or not, and do not need to maintain their rules to stay inline with current policy.

logicalInsert) can be used to provide a separation of concerns. The following example issues either a child or adult bus pass:
rule "Issue Child Bus Pass" when $p : Person( age < 16 ) then insert(new ChildBusPass( $p ) ); end rule "Issue Adult Bus Pass" when $p : Person( age >= 16 ) then insert(new AdultBusPass( $p ) ); end
logicalInsert:
rule "Infer Child" when
$p : Person( age < 16 )
then
logicalInsert( new IsChild( $p ) )
end
rule "Infer Adult" when
$p : Person( age >= 16 )
then
logicalInsert( new IsAdult( $p ) )
endwhen clause. If the truth of the when clause changes to false, the fact is automatically retracted. This works well for rules that are mutually exclusive, for instance, when the person's age changes from 15 to 16, the IsChild fact is automatically retracted and the IsAdult fact is inserted.
rule "Issue Child Bus Pass" when
$p : Person( )
IsChild( person =$p )
then
logicalInsert(new ChildBusPass( $p ) );
end
rule "Issue Adult Bus Pass" when
$p : Person( age >= 16 )
IsAdult( person =$p )
then
logicalInsert(new AdultBusPass( $p ) );
endLogicalInsert can be combined with the not conditional element to handle notifications, in this situation a request could be sent for the return of the bus pass. When the ChildBusPass object is retracted a rule triggers and sends a request to the person:
rule "Return ChildBusPass Request "when
$p : Person( )
not( ChildBusPass( person == $p ) )
then
requestChildBusPass( $p );
endknowledge bases. They have done so by manually adding each rule. JBoss Rules also provide a means to declare the resources to be added to a knowledge base through an XML file. This feature is called a change-set.
XML file contains a list of the rule resources that can be added to a knowledge base. One can also point this file to another.
<change-set xmlns='http://drools.org/drools-5.0/change-set' xmlns:xs='http://www.w3.org/2001/XMLSchema-instance' xs:schemaLocation='http://drools.org/drools-5.0/change-set drools-change-set-5.0.xsd' > <add> <resource source='http://hostname/myrules.drl' type='DRL' /> </add> </change-set>
classpath can also be used. This protocol refers to the current processes class-path for the resource.
CHANGE_SET.
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder(); kbuilder.add( ResourceFactory.newClasspathResource( "myChangeSet.xml", getClass() ), ResourceType.CHANGE_SET ); if ( kbuilder.hasErrors() ) { System.err.println( kbuilder.getErrors().toString() ); }
<change-set xmlns='http://drools.org/drools-5.0/change-set' xmlns:xs='http://www.w3.org/2001/XMLSchema-instance' xs:schemaLocation='http://drools.org/drools-5.0/change-set.xsd' > <add> <resource source='http://hostname/myrules.drl' type='DRL' /> <resource source='classpath:data/IntegrationTest.xls' type="DTABLE"> <decisiontable-conf input-type="XLS" worksheet-name="Tables_2" /> </resource> </add> </change-set>
<change-set xmlns='http://drools.org/drools-5.0/change-set' xmlns:xs='http://www.w3.org/2001/XMLSchema-instance' xs:schemaLocation='http://drools.org/drools-5.0/change-set.xsd' > <add> <resource source='file://rules/' type='DRL' /> </add> </change-set>
KnowledgeAgent automatically loads, re-loads and caches rule resources. Configure it via its properties file.
knowledge base change, the KnowledgeAgent can update or rebuild it. To set a strategy for these updates, re-configure the KnowledgeAgentFactory:
KnowledgeAgent kagent = KnowledgeAgentFactory.newKnowledgeAgent("MyAgent"); kagent.applyChangeSet( ResourceFactory.newUrlResource( url ) ); KnowledgeBase kbase = kagent.getKnowledgeBase();
KnowledgeAgent scans every resource added, the default polling interval being sixty seconds. If the "last-modified" date of a resource has changed, the KnowledgeAgent will rebuild the knowledge base. (If a directory has been set as one of the resources, then every contents of that directory will be scanned for changes.)
knowledge base reference will still exist after change, so one must call getKnowledgeBase() to access the newly-built version.
KnowledgeAgent and knowledge bases interact. The reader should also now have a a more comprehensive understanding of stateless and stateful sessions.

Knowledge Builder is responsible for taking source data and turning it into a knowledge package. A knowledge package contains rule and process definitions which the Knowledge Base then consumes.
ResourceType object class indicates the type of resource being built.
ResourceFactory provides the capability to load a resource from a number of sources, including a reader, a class-path, a uniform resource locator, a file or a ByteArray.

Knowledge Builder is created by the KnowledgeBuilderFactory.

Knowledge Builder by using the default configuration:
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();KnowledgeBuilderFactory. Such a configuration allows one to modify the behavior of the Knowledge Builder.
Knowledge Builder object to resolve classes that are not in the default path.
KnowledgeBuilderConfiguration kbuilderConf =
KnowledgeBuilderFactory.newKnowledgeBuilderConfiguration(
null, classLoader );
KnowledgeBuilder kbuilder =
KnowledgeBuilderFactory.newKnowledgeBuilder(kbuilderConf);.drl file is added.
Knowledge Builder can now handle multiple name-spaces, which was not the case with JBoss Rules 4.0 Package Builder. Therefore, one can just keep adding resources, regardless of the name-space.
kbuilder.add( ResourceFactory.newFileResource( "/project/myrules.drl" ), ResourceType.DRL);
hasErrors() method after making an addition. Do not add more resources or retrieve the Knowledge Packages if there are errors. (getKnowledgePackages() returns an empty list if there are errors.)
if( kbuilder.hasErrors() ) { System.out.println( kbuilder.getErrors() ); return; }
Knowledge Packages. (This is termed a "collection" because there is one Knowledge Package per package name-space.) These Knowledge Packages are serializable and are often used as a unit of deployment.
Collection<KnowledgePackage> kpkgs = kbuilder.getKnowledgePackages();KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder(); if( kbuilder.hasErrors() ) { System.out.println( kbuilder.getErrors() ); return; } KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder(); kbuilder.add( ResourceFactory.newFileResource( "/project/myrules1.drl" ), ResourceType.DRL); kbuilder.add( ResourceFactory.newFileResource( "/project/myrules2.drl" ), ResourceType.DRL); if( kbuilder.hasErrors() ) { System.out.println( kbuilder.getErrors() ); return; } Collection<KnowledgePackage> kpkgs = kbuilder.getKnowledgePackages();
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="http://drools.org/drools-5.0/change-set" targetNamespace="http://drools.org/drools-5.0/change-set"> <xs:element name="change-set" type="ChangeSet"/> <xs:complexType name="ChangeSet"> <xs:choice maxOccurs="unbounded"> <xs:element name="add" type="Operation"/> <xs:element name="remove" type="Operation"/> <xs:element name="modify" type="Operation"/> </xs:choice> </xs:complexType> <xs:complexType name="Operation"> <xs:sequence> <xs:element name="resource" type="Resource" maxOccurs="unbounded"/> </xs:sequence> </xs:complexType> <xs:complexType name="Resource"> <xs:sequence> <!-- To be used with <resource type="DTABLE"...>> --> <xs:element name="decisiontable-conf" type="DecTabConf" minOccurs="0"/> </xs:sequence> <!-- java.net.URL, plus "classpath" protocol --> <xs:attribute name="source" type="xs:string"/> <xs:attribute name="type" type="ResourceType"/> <xs:attribute name="basicAuthentication" type="xs:string"/> <xs:attribute name="username" type="xs:string"/> <xs:attribute name="password" type="xs:string"/> </xs:complexType> <xs:complexType name="DecTabConf"> <xs:attribute name="input-type" type="DecTabInpType"/> <xs:attribute name="worksheet-name" type="xs:string" use="optional"/> </xs:complexType> <!-- according to org.drools.builder.ResourceType --> <xs:simpleType name="ResourceType"> <xs:restriction base="xs:string"> <xs:enumeration value="DRL"/> <xs:enumeration value="XDRL"/> <xs:enumeration value="DSL"/> <xs:enumeration value="DSLR"/> <xs:enumeration value="DRF"/> <xs:enumeration value="DTABLE"/> <xs:enumeration value="PKG"/> <xs:enumeration value="BRL"/> <xs:enumeration value="CHANGE_SET"/> </xs:restriction> </xs:simpleType> <!-- according to org.drools.builder.DecisionTableInputType --> <xs:simpleType name="DecTabInpType"> <xs:restriction base="xs:string"> <xs:enumeration value="XLS"/> <xs:enumeration value="CSV"/> </xs:restriction> </xs:simpleType> </xs:schema>
<resource /> element in BRMS 5.1 and above, but should not be included in BRMS 5.0.
brms-users.properties or jmx-console.properties.
basicAuthentication='enabled' username'admin' password='admin'
.drl file:
<change-set xmlns='http://drools.org/drools-5.0/change-set' xmlns:xs='http://www.w3.org/2001/XMLSchema-instance' xs:schemaLocation='http://drools.org/drools-5.0/change-set.xsd' > <add> <resource source='file:/project/myrules.drl' type='DRL' /> </add> </change-set>
java.net.URL, such as file and http, as well as an additional version of classpath.
ClassPath resource loader in Java to specify the class loader to be used to locate the resource (this is not possible in XML.) The class loader to be used will, by default, be that which is employed by the Knowledge Builder (unless the Change-Set XML is loaded by the ClassPath resource. If so, the class loader specified for that resource will be used instead.)
kbuilder.add(ResourceFactory.newUrlResource(url),ResourceType.CHANGE_SET);
<change-set xmlns='http://drools.org/drools-5.0/change-set' xmlns:xs='http://www.w3.org/2001/XMLSchema-instance' xs:schemaLocation='http://drools.org/drools-5.0/change-set.xsd' > <add> <resource source='http:org/domain/myrules.drl' type='DRL' /> <resource source='classpath:data/IntegrationExampleTest.xls' type="DTABLE"> <decisiontable-conf input-type="XLS" worksheet-name="Tables_2" /> </resource> </add> </change-set>
Knowledge Agent, as it provides change notification functionality and automatically rebuilds the Knowledge Base. (These features are covered in more detail under the sub-heading "Deploying" in the section on the Knowledge Agent.)
Knowledge Agent, it will continuously scan for changes to the resources. It will also rebuild the cached Knowledge Base.
Knowledge Agent. Refer to Section 3.2.6, “ KnowledgeAgent ” for more information.
<change-set xmlns='http://drools.org/drools-5.0/change-set' xmlns:xs='http://www.w3.org/2001/XMLSchema-instance' xs:schemaLocation='http://drools.org/drools-5.0/change-set.xsd' > <add> <resource source='file:/projects/myproject/myrules' type='DRL' /> </add> </change-set>
KnowledgePackage and Knowledge Definitions KnowledgePackage is created by the KnowledgeBuilder, as described in Section 3.1, “ Building ”. KnowledgePackages are self-contained and serializable. They form the current basic deployment unit.

KnowledgePackages are added to the Knowledge Base. However, it is important to understand that a KnowledgePackage instance cannot be re-used once this has occurred. To add it to another knowledge base, try serializing it first and using the "cloned" result. This limitation will be removed in a future version of JBoss Rules.

knowledge base is a repository that contains all of the application's knowledge definitions. It may contain rules, processes, functions and type models. The knowledge base itself does not contain "instance" data, (known as facts.) Instead, sessions are created from the Knowledge Base into which facts can be inserted and from which process instances can be commenced.
knowledge base is a rather resource-intensive process, whereas creating a session is not. Therefore, Red Hat recommends caching knowledge bases where possible to facilitate the repeated creation of sessions.
knowledge base object is also serializable so it may be preferable to build it and then store it. By so doing, one can treat it, rather than the knowledge packages, as the unit of deployment.
knowledge base by using the KnowledgeBaseFactory class:

KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();class-loader in conjunction with the Knowledge Builder to resolve types that were not in the default loader, then set it on the Knowledge Base. (The technique for this is the same as that which applies to the Knowledge Builder.)
Class-LoaderKnowledgeBaseConfiguration kbaseConf =
KnowledgeBaseFactory.newKnowledgeBaseConfiguration( null, cl );
KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase( kbaseConf );knowledge base that is residing in the same Java Virtual Machine.
drools-${module}-${version}.jar files are on the class-path when using this approach.
Knowledge Packages to a Knowledge BaseCollection<KnowledgePackage> kpkgs = kbuilder.getKnowledgePackages(); KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase(); kbase.addKnowledgePackages( kpkgs );
addKnowledgePackages(kpkgs) method can be called on an iterative basis. Do so in order to add additional knowledge.
Knowledge Base and the KnowledgePackage are units of deployment. They can, therefore, be serialized. This means that one can assign one machine to undertake any necessary building that requires drools-compiler.jar, and have another machine reserved to deploy and execute everything. This second machine will only require drools-core.jar.
KnowledgePackage to an Output StreamObjectOutputStream out = new ObjectOutputStream( new FileOutputStream( fileName ) ); out.writeObject( kpkgs ); out.close();
KnowledgePackage from an Input StreamObjectInputStream in = new ObjectInputStream( new FileInputStream( fileName ) ); // The input stream might contain an individual // package or a collection. @SuppressWarnings( "unchecked" ) Collection<KnowledgePackage> kpkgs = ()in.readObject( Collection<KnowledgePackage> ); in.close(); KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase(); kbase.addKnowledgePackages( kpkgs );
knowledge base itself is also serializable, so one may prefer to build and store it rather than the knowledge packages.
knowledge packages to a uniform resource location, it can use this address resource type to load them.
Knowledge Base creates and returns them. It also may, optionally, keep references to them. When the Knowledge Base is modified, these changes are applied to the data in the sessions. This is a weak, optional reference, controlled by a Boolean flag.
KnowledgeAgent is a class that provides automatic loading, caching and re-loading of resources. It is configured via a properties files. The KnowledgeAgent can update or rebuild the Knowledge Base, as the resources it uses are changed. The factory's configuration determines the strategy that will be used (normally, it will typically be pull-based and use regular polling.)
KnowledgeAgent continuously scans all of the added resources, using a default polling interval of sixty seconds. If the date of the last modification is updated, the cached Knowledge Base is automatically rebuilt using the new resources.

KnowledgeBuilderFactory object is used to create the Knowledge Builder. The agent must specify a name because this will be needed by the log files. (This is so that the log entries can be associated against the correct agents.)
KnowledgeAgentKnowledgeAgent kagent =
KnowledgeAgentFactory.newKnowledgeAgent( "MyAgent" );

KnowledgeAgentFactory knowledge base from the specified change-set.
KnowledgeAgent polls the resources added from the change set every sixty seconds, (the default interval), to see if they are updated. Whenever changes are found, it will construct a new Knowledge Base. In addition, if a directory has been specified as the resource, its contents will be scanned.
KnowledgePackage to an Output StreamKnowledgeAgent kagent =
KnowledgeAgentFactory.newKnowledgeAgent( "MyAgent" );
kagent.applyChangeSet( ResourceFactory.newUrlResource( url ) );
KnowledgeBase kbase = kagent.getKnowledgeBase();
ResourceFactory.
ResourceFactory.getResourceChangeNotifierService().start(); ResourceFactory.getResourceChangeScannerService().start();
ResourceChangeScannerService class. (An updated ResourceChangeScannerConfiguration object is passed to the service's configure() method, thereby allowing for the service to be reconfigured on demand.)
ResourceChangeScannerConfiguration sconf =
ResourceFactory.getResourceChangeScannerService().
newResourceChangeScannerConfiguration();
// Set the disk scanning interval to 30s, default is 60s.
sconf.setProperty( "drools.resource.scanner.interval", "30" );
ResourceFactory.getResourceChangeScannerService().configure( sconf );
KnowledgeAgents can handle both empty and populated Knowledge Bases. If a populated Knowledge Base is provided, the KnowledgeAgent will run an iterator from within it and subscribe to each resource that it finds.
KnowledgeBuilder build all of the resources in a directory, that information it will then lose that information. This means that those directories will not be continuously scanned. Only directories specified via the applyChangeSet(Resource) method are monitored.
Knowledge Base as the starting point is that one can provide it with a KnowledgeBaseConfiguration class. When resource changes are detected and a new Knowledge Base is instantiated, it will use the KnowledgeBaseConfiguration class belonging to the previous Knowledge Base object.
KnowledgeBaseConfiguration kbaseConf =
KnowledgeBaseFactory.newKnowledgeBaseConfiguration( null, cl );
KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase( kbaseConf );
// Populate kbase with resources here.
KnowledgeAgent kagent =
KnowledgeAgentFactory.newKnowledgeAgent( "MyAgent", kbase );
KnowledgeBase kbase = kagent.getKnowledgeBase();
getKnowledgeBase() method returns the same Knowledge Base instance until resource changes are detected and a new Knowledge Base is built. When this happens, it is done with the KnowledgeBaseConfiguration that was provided to the previous Knowledge Base.
<change-set xmlns='http://drools.org/drools-5.0/change-set' xmlns:xs='http://www.w3.org/2001/XMLSchema-instance' xs:schemaLocation='http://drools.org/drools-5.0/change-set.xsd' > <add> <resource source='file:/projects/myproject/myrules' type='PKG' /> </add> </change-set>
drools-compiler dependency is not needed for the resource type entitled PKG, as the KnowledgeAgent is able to handle those with drools-core alone.
KnowledgeAgentConfiguration to modify a KnowledgeAgent's default behavior. Do this to load the resources from a directory, whilst inhibiting the continuous scan of that directory for changes.
KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase(); KnowledgeAgentConfiguration kaconf = KnowledgeAgentFactory.newKnowledgeAgentConfiguration(); // Do not scan directories, just files. kaconf.setProperty( "drools.agent.scanDirectories", "false" ); KnowledgeAgent kagent = KnowledgeAgentFactory.newKnowledgeAgent( "test agent", kaconf );
Knowledge Packages through a uniform resource location and also how the Change-Set XML can handle both URLs and packages. Taken together, these form an important deployment scenario for the Knowledge Agent.
Knowledge Base itself does not contain instance data, (known as facts.) Instead, sessions are created from the KnowledgeBase into which facts can be inserted and from where process instances may be started.
Knowledge Base creation is a resource-intensive process, whereas session creation is not. Cache Knowledge Bases whenever possible to facilitate repeated session creation.
KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();StatefulKnowledgeSession stores and executes the run-time data. It is created from the KnowledgeBase.

StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession();WorkingMemoryEntryPoint provides the methods for inserting, updating and retrieving facts.
working memory and one can choose into which of these the facts will be inserted. However this use case is aimed at event-processing and most rule-based applications will only make use of the default entry point.
KnowledgeRuntime interface provides the main interaction with the engine and is available in rule consequences and process actions. While the focus is on the methods and interfaces related to rules, you'll notice that the KnowledgeRuntime inherits methods from both the WorkingMemory and the ProcessRuntime. This provides a unified API to work with processes and rules. When working with rules three interfaces form the KnowledgeRuntime: WorkingMemoryEntryPoint, WorkingMemory, and the KnowledgeRuntime itself.

WorkingMemory about a fact. (Here is an example: ksession.insert(yourObject).) As they are inserted, the system examines each fact for matches against rules. All of the decisions about whether or not to fire a rule happen at the time of insertion. However, no rule is executed until fireAllRules() is called. Do this only after inserting all of the facts.
fireAllRules() is called.
FactHandle is the token used to represent the inserted object within the working memory. It is also used for interactions with the working memory when objects are modified or retracted.
Cheese stilton = new Cheese("stilton"); FactHandle stiltonHandle = ksession.insert( stilton );
working memory can operate in either one of these two assertion modes: equality or identity. (Identity is the default one.)
Identity is used, the working memory utilizes an IdentityHashMap to store all of the asserted objects. New instance assertions always result in the return of a new FactHandle. Repeated insertions of the same instance will simply return the original fact handle.
Equality is used, the working memory utilizes a HashMap to store all of the asserted objects. New instance assertions will only return a new FactHandle if no equal objects have been asserted.
working memory. The fact will no longer be tracked or matched to rules. Furthermore, any rules that are activated and dependent on that fact will be cancelled. Retraction is achieved via utilization of the FactHandle that was returned at the time of assertion.
not and exist keywords) that will fire when certain facts do not exist. In these cases, retracting a fact may cause the rule to activate.
Cheese stilton = new Cheese("stilton"); FactHandle stiltonHandle = ksession.insert( stilton ); ksession.retract( stiltonHandle );
rule engine must be notified of modified facts, so that it can reprocess them. When a fact which is identified as having been updated, it is automatically retracted from the working memory and inserted again.
working memory itself, use the update method to do so. The update method always takes the modified object as a secondary parameter. This allows one to specify new instances for immutable objects.
update method can only be used with objects for which shadow proxies have been turned on.
update method is only for use in conjunction with Java code. Within a rule, use the modify keyword as this provides calls to an object's "setter" methods.
Cheese stilton = new Cheese("stilton"); FactHandle stiltonHandle = workingMemory.insert( stilton ); ... stilton.setPrice( 100 ); workingMemory.update( stiltonHandle, stilton );
working memory provides access to the agenda, permits query executions and allows one to access named entry points.

Knowlege Base, from which place they are called to return the matching results. Whilst iterating over the result collection, any bound identifier in the query can be accessed using the get(String identifier) method. Any FactHandle for that identifier can be retrieved using getFactHandle(String identifier).


QueryResults results =
ksession.getQueryResults( "my query", new Object[] { "string" } );
for ( QueryResultsRow row : results ) {
System.out.println( row.get( "varName" ) );
}final List updated = new ArrayList(); final List removed = new ArrayList(); final List added = new ArrayList(); ViewChangedEventListener listener = new ViewChangedEventListener() { public void rowUpdated(Row row) { updated.add( row.get( "$price" ) ); } public void rowRemoved(Row row) { removed.add( row.get( "$price" ) ); } public void rowAdded(Row row) { added.add( row.get( "$price" ) ); } }; // Open the LiveQuery LiveQuery query = ksession.openLiveQuery( "cheeses", new Object[] { "cheddar", "stilton" }, listener ); ... ... query.dispose() // make sure you call dispose when you want the query to close
KnowledgeRuntime provides further methods applicable to both rules and processes. Some examples are those for setting globals and registering ExitPoints.

rule engine. There is no need to insert them. Most often they are used for static information, or for services that are used in the right-hand side of a rule, or perhaps as a means to return objects from the rule engine.
rules file before it is set on the session:
global java.util.List list
Knowlege Base now aware of the global identifier and its type, call ksession.setGlobal for any session.
ksession.setGlobal(identifier, value):
List list = new ArrayList(); ksession.setGlobal("list", list);
NullPointerException exception will be thrown.
StatefulRuleSession is inherited by the StatefulKnowledgeSession. It provides the rule-related methods that are applicable outside the engine.


filter interface. Use them to allow or deny an activation the right to fire. (That which can be filtered is entirely dependent upon the implementation.)
fireAllRules(). The following example permits only rules those ending in the string Test to fire. It filters out all others:
ksession.fireAllRules( new RuleNameEndsWithAgendaFilter( "Test" ) );
working memory, rules may become fully matched and, therefore, eligible for execution. A single working memory action can result in multiple rules being made eligible. When a rule is fully matched an activation is created. This references both the rule and the matched facts, and is placed onto the Agenda. The Agenda then determines the order of these Activations via a conflict resolution strategy.
engine then cycles repeatedly through two phases:
fireAllRules() the engine switches to the Agenda second phase.

agenda is cleared, at which time control is returned to the calling application.

agenda a conflict resolution strategy is required. As the firing of a rule may have an impact upon the working memory, the rule engine needs to know in which order the rules are to be executed. (For example, firing ruleA may cause ruleB to be removed from the agenda.)
working memory's action counter value, with each rule created during the same action receiving the same value. (If a set of firings has the same priority value, the execution order will be arbitrary.)

agenda. At any time, only one group can have "focus", and only the activations belonging to that group will be able to take effect.
Agenda groups are most commonly used to define one or more subsets of rules that apply to specific circumstances, (such as phases of processing), and to control as to when these sets of rules can be applied.
agenda group will become focused when it is matched.)
setFocus() is called, it pushes an agenda group onto a stack. When the focus group is empty, it is removed from the stack and the next focus group (now the topmost one) is permitted to evaluate.
agenda group can appear in multiple locations on the stack.
ksession.getAgenda().getAgendaGroup( "Group A" ).setFocus();
agenda group group is called MAIN. It is the first group on the stack and, hence, initially has the focus. Any rule without an agenda group is automatically placed in this group.

activation group is set of rules bound together by the activation-group rule attribute. In this group only one rule can fire. After that rule has fired, all of the other rules are cancelled.
clear() method at any time, to cancel all of the activations before any have had a chance to fire.
ksession.getAgenda().getActivationGroup( "Group B" ).clear();
event package notifies one of rule engine events. Use it to separate logging and auditing activities from the main part of the application and from the rules.
KnowledgeRuntimeEventManager interface is implemented by the KnowledgeRuntime class which provides two interfaces, WorkingMemoryEventManager and ProcessEventManager.
WorkingMemoryEventManager.

WorkingMemoryEventManager to add and remove listeners. Adding a listener enables one to "listen" to events affecting the working memory and the agenda.

agenda listener and attached it to a session. It prints activations after they have fired.
ksession.addEventListener( new DefaultAgendaEventListener() { public void afterActivationFired(AfterActivationFiredEvent event) { super.afterActivationFired( event ); System.out.println( event ); } });
DebugWorkingMemoryEventListener and DebugAgendaEventListener which implement each method with a debug print statement. To print every working memory event, add one of these listeners.
ksession.addEventListener( new DebugWorkingMemoryEventListener() );
KnowledgeRuntimeEvent interface in order to retrieve the KnowledgeRuntime from which the event originated.

| ActivationCreatedEvent | ActivationCancelledEvent |
| BeforeActivationFiredEvent | AfterActivationFiredEvent |
| AgendaGroupPushedEvent | AgendaGroupPoppedEvent |
| ObjectInsertEvent | ObjectRetractedEvent |
| ObjectUpdatedEvent | ProcessCompletedEvent |
| ProcessNodeLeftEvent | ProcessNodeTriggeredEvent |
| ProcessStartEvent |
KnowledgeRuntimeLogger uses JBoss Rules's event systems to create an audit log each time an application is executed. Inspect this log with a tools such as the JBoss Rules IDE's Audit Viewer.

KnowledgeRuntimeLogger logger =
KnowledgeRuntimeLoggerFactory.newFileLogger(ksession, "logdir/mylogfile");
...
logger.close();newFileLogger() method to automatically append the file extension, .log, to any file.
StatelessKnowledgeSession wraps the StatefulKnowledgeSession. It is used in relation to decision service-type scenarios. Its presence mitigates the need to call dispose().
fireAllRules() method from Java code when using stateless sessions. The execute() method instantiates a StatefullKnowledgeSession internally, adds all of the user data and executes user commands. It then calls the fireAllRules() and dispose() methods.
BatchExecution command (as supported by the CommandExecutor interface.) However, two convenience methods have also been provided. Use these when only simple object insertion is required. (The CommandExecutor and BatchExecution are discussed in detail in their own sections.)

stateless session executing using the Convenience API to execute a given collection of Java objects . It iterates the collection, inserting each element in turn.
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder(); kbuilder.add( ResourceFactory.newFileResource( fileName ), ResourceType.DRL ); if (kbuilder.hasErrors() ) { System.out.println( kbuilder.getErrors() ); } else { KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase(); kbase.addKnowledgePackages( kbuilder.getKnowledgePackages() ); StatelessKnowledgeSession ksession = kbase.newStatelessKnowledgeSession(); ksession.execute( collection ); }
ksession.execute( CommandFactory.newInsertElements( collection ) );
CommandFactory.newInsert(collection).
CommandFactory contains details of the supported commands. To marshal any of them use XStream and the BatchExecutionHelper. Also use BatchExecutionHelper to learn details of the XML format being utilized. Use JBoss Rules Pipeline to automatically marshal the BatchExecution and ExecutionResults.
StatelessKnowledgeSession allows one to scope globals in a number of ways. The first of these is the non-command way. Commands are scoped to a specific execution call. (Globals can be resolved in three ways.)
StatelessKnowledgeSession's getGlobals() method returns a Globals instance. As its name implies, this provides access to the session's globals. These are shared for all execution calls.
StatelessKnowledgeSession ksession = kbase.newStatelessKnowledgeSession(); // sets a global hibernate session, 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 );
setGlobal(String, Object)) results in the value being stored in an internal collection, the purpose of which is to map identifiers to values. These identifiers will have priority over any delegate supplied: only if an identifier cannot be found will the delegate global (if, indeed, there is any) be used.
CommandExecutor.
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" );
engine can be operated in a simplified way. Follow these steps:
rule terminal node).
Left Input Adapter Node propagation and let a command object refer to the object and the nodes. Added this command object to a list in the working memory for later execution.
LeftInputAdapterNode no longer creates a tuple, adds the object or propagates the tuple. Rather, a command object is created and added to a list in the working memory. This object contains references to both the LeftInputAdapterNode and the propagated object. This stops any left-input propagations from occurring at insertion time which means that no right-input propagation will ever attempt a join with the left-inputs (thereby removing the need for left-input memory).
LeftInputAdapterNode command objects by calling each in turn. They will be passed down the network and attempt to join with the right-input objects, but will not be remembered in the left input because there will be no further object assertions or propagations into the right-input memory.
agenda with a priority queue to schedule the tuples; instead, there is simply an array for the number of rules. The RuleTerminalNode's sequence number indicates the element within the array upon which to place the activation.
RuleTerminalNode being given a sequence number. This number is based on a salience number and the order in which it has been added to the network.
RuleBaseConfiguration.setSequential(true), or set the Rule base Configuration's drools.sequential property to true.
setSequentialAgenda with SequentialAgenda.DYNAMIC to make the sequential mode fall back to a dynamic agenda. One may also set the drools.sequential.agenda property to sequential or dynamic.
working memory with which one can work iteratively over time. A stateless session is a one-off execution of a working memory with a provided data-set. It may return some results, and the session is disposed at the end, prohibiting further iterative interactions. Think of stateless sessions as a way in which to treat a rule engine as a function call with optional return results.


CommandFactory allows one to execute commands on stateful and stateless sessions, (the only difference being that the stateless knowledge session executes fireAllRules() at the end before it is disposed.) These commands are currently supported:
| FireAllRules | GetGlobal |
| SetGlobal | InsertObject |
| InsertElements | Query |
| StartProcess | BatchExecution |
InsertObject inserts a single object, with an optional out identifier. InsertElements runs through an iterable object, inserting each of the elements. As a result, one is no longer limited to just inserting objects into a stateless knowledge session, but can now start processes or execute queries and do this in any order.
StatelessKnowledgeSession ksession = kbase.newStatelessKnowledgeSession(); ExecutionResults bresults = ksession.execute( CommandFactory.newInsert( new Cheese( "stilton" ), "stilton_id" ) ); Stilton stilton = bresults.getValue( "stilton_id" );
ExecutionResults instance. This allows one access to any command result if an out identifier, such as the "stilton_id" above, has been specified.
StatelessKnowledgeSession ksession = kbase.newStatelessKnowledgeSession(); Command cmd = CommandFactory.newInsertElements( Arrays.asList(new Object[] { new Cheese("stilton"), new Cheese("brie"), new Cheese("cheddar")} )); ExecutionResults bresults = ksession.execute( cmd );
BatchExecution is a composite command that takes a list of instructions and iterates and execute each of these in turn. This means one can insert some objects, start a process, call fireAllRules and execute a query all in a single execute(...) call, making it much more powerful.
fireAllRules() method automatically as it finishes processing. However, the FireAllRules command is also allowed, and using it will disable the automatic execution at the end - it is a manual override.
ExecutionResults instance that is returned. Here is an example that depicts the way in which it works:
StatelessKnowledgeSession ksession = kbase.newStatelessKnowledgeSession(); 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" );
ExecutionResults. The query command uses the same identifier as the query name by default, but it can also be mapped to a different identifier.
XStream marshaller can be used in conjunction with the JBoss Rules Pipeline to provide XML scripting, making it ideal for services. Here are two simple XML samples, one for the BatchExecution and the other for the ExecutionResults.
<batch-execution> <insert out-identifier='outStilton'> <org.drools.Cheese> <type>stilton</type> <price>25</price> <oldPrice>0</oldPrice> </org.drools.Cheese> </insert> </batch-execution>
<execution-results> <result identifier='outStilton'> <org.drools.Cheese> <type>stilton</type> <oldPrice>25</oldPrice> <price>30</price> </org.drools.Cheese> </result> </execution-results>
stage objects. Combine these to more easily move data into and out of sessions.
stage that implements the CommandExecutor interface. Use this to make the pipeline script either a stateful, or a stateless, session. Configure it in this way:
Action executeResultHandler = PipelineFactory.newExecuteResultHandler();
Action assignResult = PipelineFactory.newAssignObjectAsResult();
assignResult.setReceiver( executeResultHandler );
Transformer outTransformer =
PipelineFactory.newXStreamToXmlTransformer(
BatchExecutionHelper.newXStreamMarshaller() );
outTransformer.setReceiver( assignResult );
KnowledgeRuntimeCommand cmdExecution =
PipelineFactory.newCommandExecutor();
batchExecution.setReceiver( cmdExecution );
Transformer inTransformer =
PipelineFactory.newXStreamFromXmlTransformer(
BatchExecutionHelper.newXStreamMarshaller() );
inTransformer.setReceiver( batchExecution );
Pipeline pipeline =
PipelineFactory.newStatelessKnowledgeSessionPipeline( ksession );
pipeline.setReceiver( inTransformer );
BatchExecutionHelper is used to provide a specially-configured XStream with custom converters for command objects and the new BatchExecutor stage.
pipeline, provide an implementation of the ResultHandler. This called when the pipeline executes the ExecuteResultHandler stage.

public static class ResultHandlerImpl implements ResultHandler { Object object; public void handleResult(Object object) { this.object = object; } public Object getObject() { return this.object; } }
ResultHandler resultHandler = new ResultHandlerImpl(); pipeline.insert( inXml, resultHandler );
BatchExecution created earlier to insert some objects and execute a query. The XML representation to be used with the pipeline example is shown below. Parameters have been added to the query:
<batch-execution> <insert out-identifier="stilton"> <org.drools.Cheese> <type>stilton</type> <price>1</price> <oldPrice>0</oldPrice> </org.drools.Cheese> </insert> <query out-identifier='cheeses2' name='cheesesWithParams'> <string>stilton</string> <string>cheddar</string> </query> </batch-execution>
CommandExecutor returns the ExecutionResults, and this is handled by the pipeline code snippet as well.
<execution-results> <result identifier="stilton"> <org.drools.Cheese> <type>stilton</type> <price>2</price> </org.drools.Cheese> </result> <result identifier='cheeses2'> <query-results> <identifiers> <identifier>cheese</identifier> </identifiers> <row> <org.drools.Cheese> <type>cheddar</type> <price>2</price> <oldPrice>0</oldPrice> </org.drools.Cheese> </row> <row> <org.drools.Cheese> <type>cheddar</type> <price>1</price> <oldPrice>0</oldPrice> </org.drools.Cheese> </row> </query-results> </result> </execution-results>
BatchExecutionHelper provides a pre-configured XStream instance. Use it to support the marshalling of batch executions, (for which the resulting XML can be used as a message format, as shown above.) Only commands supported via the Command Factory have pre-configured converters. One can add other converters for user objects. (This is very useful when scripting for stateless or stateful knowledge sessions, especially when services are involved.)
drools-transformer-xstream module has a unit test called XStreamBatchExecutionTest. The root element is named <batch-execution> and it can contain any number of command elements:
<batch-execution> ... </batch-execution>
Command Factory The most basic of these is the <insert> element, which inserts objects. The contents of the insert element is the user object, as dictated by XStream.
<batch-execution> <insert> ...<!-- any user object --> </insert> </batch-execution>
insert element features an attribute called out-identifier. This demands that the inserted object be returned as part of the result payload.
<batch-execution> <insert out-identifier='userVar'> ... </insert> </batch-execution>
out-identifier. (The org.domain.UserClass is just an illustrative user object that XStream can serialize.)
<batch-execution> <insert-elements> <org.domain.UserClass> ... </org.domain.UserClass> <org.domain.UserClass> ... </org.domain.UserClass> <org.domain.UserClass> ... </org.domain.UserClass> </insert-elements> </batch-execution>
<batch-execution> <set-global identifier='userVar'> <org.domain.UserClass> ... </org.domain.UserClass> </set-global> </batch-execution>
<batch-execution> <set-global identifier='userVar1' out='true'> <org.domain.UserClass> ... </org.domain.UserClass> </set-global> <set-global identifier='userVar2' out-identifier='alternativeUserVar2'> <org.domain.UserClass> ... </org.domain.UserClass> </set-global> </batch-execution>
<batch-execution> <get-global identifier='userVar1' /> <get-global identifier='userVar2' out-identifier='alternativeUserVar2'/> </batch-execution>
<batch-execution> <query out-identifier='cheeses' name='cheeses'/> <query out-identifier='cheeses2' name='cheesesWithParams'> <string>stilton</string> <string>cheddar</string> </query> </batch-execution>
<start-process> command accepts optional parameters.
<batch-execution> <startProcess processId='org.drools.actions'> <parameter identifier='person'> <org.drools.TestVariable> <name>John Doe</name> </org.drools.TestVariable> </parameter> </startProcess> </batch-execution
<signal-event process-instance-id='1' event-type='MyEvent'> <string>MyValue</string> </signal-event>
<complete-work-item id='" + workItem.getId() + "' >
<result identifier='Result'>
<string>SomeOtherString</string>
</result>
</complete-work-item>
<abort-work-item id='21' />
MarshalerFactory to marshal and unmarshall stateful knowledge sessions.

MarshalerFactory:
// ksession is the StatefulKnowledgeSession // kbase is the KnowledgeBase ByteArrayOutputStream baos = new ByteArrayOutputStream(); Marshaller marshaller = MarshallerFactory.newMarshaller( kbase ); marshaller.marshall( baos, ksession ); baos.close();
ObjectMarshalingStrategy interface has been added to provide this. Two implementations of this interface are provided, and users can create their own additional ones. The two that are supplied are called:
SerializeMarshalingStrategy is the default (it was used in the example above.) It simply calls either the Serializable or the Externalizable method on a user instance.
IdentityMarshalingStrategy creates an integer identification number for each user object and stores these in a map, whilst the identification is written to the stream. Whilst unmarshalling, it accesses the IdentityMarshalingStrategy map to retrieve the instance. (Hence, if the IdentityMarshalingStrategy is used, it remains stateful for the life of the Marshaller instance and will create identifiers and keep references to every objects that it attempts to marshal.) Here is the code to use with an IdentityMarshalingStrategy:
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectMarshallingStrategy oms = MarshallerFactory.newIdentityMarshallingStrategy() Marshaller marshaller = MarshallerFactory.newMarshaller( kbase, new ObjectMarshallingStrategy[]{ oms } ); marshaller.marshall( baos, ksession ); baos.close();
ObjectMarshalingStrategyAcceptor interface has also been provided. Each Object Marshaling Strategy contains this interface. The Marshaler has a chain of strategies, and when it attempts to read or write to or from a user object, it iterates the strategies, "asking" them if they accept responsibility for marshalling the user object. One of the implementations provided is called the ClassFilterAcceptor. It allows strings and wild cards to be used to match classnames. The default is *.* so, in the above example, the IdentityMarshalingStrategy to be used is that which has the default *.* acceptor.
ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectMarshallingStrategyAcceptor identityAcceptor = MarshallerFactory.newClassFilterAcceptor( new String[] { "org.domain.pkg1.*" } ); ObjectMarshallingStrategy identityStrategy = MarshallerFactory.newIdentityMarshallingStrategy( identityAcceptor ); ObjectMarshallingStrategy sms = MarshallerFactory.newSerializeMarshallingStrategy(); Marshaller marshaller = MarshallerFactory.newMarshaller( kbase, new ObjectMarshallingStrategy[]{ identityStrategy, sms } ); marshaller.marshall( baos, ksession ); baos.close();
DRL.g but this is not required. If you use the Rule Workbench, a lot of the rule structure is done for you with content assistance, for example, type "ru" and press ctrl+space, and it will build the rule structure for you.
.drl extension. In a DRL file you can have multiple rules, queries and functions, as well as some resource declarations like imports, globals and attributes that are assigned and used by your rules and queries. However, you are also able to spread your rules across multiple rule files and in that case, the extension .rule is suggested but not required. Spreading rules across files can help with managing large numbers of rules. A DRL file is simply a text file.
package package-name imports globals functions queries rules
rule "name"
attributes
when
LHS
then
RHS
end
| true | false | accumulate |
| collect | from | null |
| over | then | when |
| lock-on-active | date-effective | date-expires |
| no-loop | auto-focus | activation-group |
| agenda-group | ruleflow-group | entry-point |
| duration | package | import |
| dialect | salience | enabled |
| attributes | rule | extend |
| template | query | declare |
| function | global | eval |
| not | in | or |
| and | exists | forall |
| action | reverse | result |
| end | init |
notSomething() and accumulateSomething().
Holiday( `when` == "july" )
rule "validate holiday by eval"
dialect "mvel"
when
h1 : Holiday( )
eval( h1.when == "july" )
then
System.out.println(h1.name + ":" + h1.when);
end
rule "validate holiday"
dialect "mvel"
when
h1 : Holiday( `when` == "july" )
then
System.out.println(h1.name + ":" + h1.when);
end
rule engine. Upon encountering them, it strips them out, unless they are inside semantic code blocks, like the right-hand side of a rule. There are two kinds, these being single-line comments and multi-line comments.

# or // to create single-line comments. The parser will ignore anything in the line after the comment symbol.
rule "Testing Comments"
when
# this is a single line comment
// this is also a single line comment
eval( true ) # this is a comment in the same line of a pattern
then
// this is a comment inside a semantic code block
# this is another comment in a semantic code block
end

rule "Test Multi-line Comments"
when
/* this is a multi-line comment
in the left hand side of a rule */
eval( true )
then
/* and this is a multi-line comment
in the right hand side of a rule */
end

rule one
when
exists Foo()
exits Bar()
then
end
[ERR 101] Line 4:4 no viable alternative at input 'exits' in rule one
exits != exists.
package org.drools;
rule
when
Object()
then
System.out.println("A RHS");
end
[ERR 101] Line 3:2 no viable alternative at input 'WHEN'
rule simple_rule
when
Student( name == "Andy )
then
end
[ERR 101] Line 0:-1 no viable alternative at input '<eof>' in rule simple_rule in pattern Student
0:-1 position. If so, check that quotes quotes, apostrophes and parentheses have all been closed.
rule simple_rule
when
foo3 : Bar(
[ERR 102] Line 0:-1 mismatched input '<eof>' expecting ')' in rule simple_rule in pattern Bar
0:-1 position, means that the parser has reached the end of source.
package org.drools;
rule "Avoid NPE on wrong syntax"
when
not(Cheese((type=="stilton",price==10)||(type=="brie",price==15))
from $cheeseList)
then
System.out.println("OK");
end
[ERR 102] Line 5:36 mismatched input ',' expecting ')' in rule
"Avoid NPE on wrong syntax" in pattern Cheese
[ERR 101] Line 5:57 no viable alternative at input 'type' in
rule "Avoid NPE on wrong syntax"
[ERR 102] Line 5:106 mismatched input ')' expecting 'then' in
rule "Avoid NPE on wrong syntax",) with an && operator.
false. Usually, these semantic predicates are used to identify "soft" keywords. This example shows that exact situation:
package nesting;
dialect "mvel"
import org.drools.Person
import org.drools.Address
fdsfdsfds
rule "test something"
when
p: Person( name=="Michael" )
then
p.name = "other";
System.out.println(p.name);
end
[ERR 103] Line 7:0 rule 'rule_key' failed predicate:
{(validateIdentifierKey(DroolsSoftKeywords.RULE))}? in rulefdsfdsfds is invalid text and the parser could not identify it as the soft keyword rule.
102: Mismatched input, but usually involves soft keywords.
rule simple_rule
when
eval(abc();)
then
end
[ERR 104] Line 3:4 trailing semi-colon not allowed in rule simple_rule
recognizer encounters a sub-rule in the grammar that does not match any alternative. In other words, it means that the parser has entered a branch from which there is no way out. Here is an example that illustrates this scenario:
template test_error
aa s 11;
end
[ERR 105] Line 2:2 required (...)+ loop did not match anything at input 'aa' in template test_error


java.lang.

global java.util.List myGlobalList;
rule "Using a global"
when
eval( true )
then
myGlobalList.add( "Hello World" );
end
List list = new ArrayList(); WorkingMemory wm = rulebase.newStatefulSession(); wm.setGlobal( "myGlobalList", list );

helper classes (in fact, the compiler generates the helper class "behind the scenes") but their main advantage stems from the fact that one can use them to keep all of the logic all in one place. Also one can change functions as one's needs alter (this can be both good and bad.)
function String hello(String name) {
return "Hello "+name+"!";
}
function keyword is used, even though it is not actually a part of Java. Parameters are, to the function, just like normal methods (and one does not have to use parameters if they are not required.) Return type is just like a normal method.
Foo.hello(). JBoss Rules allows one to use function imports. This code sample shows how to do so:
import function my.package.Foo.hello
rule "using a static function"
when
eval( true )
then
System.out.println( hello( "Bob" ) );
end


declare Address number : int streetName : String city : String end
declare Person name : String dateOfBirth : java.util.Date address : Address end
java.util.Date, from the Java API, while address is of the previously defined fact type Address.
import java.util.Date declare Person name : String dateOfBirth : Date address : Address end
public class Person implements Serializable { private String name; private java.util.Date dateOfBirth; private Address address; // getters and setters // equals/hashCode // toString }
rule "Using a declared Type" when $p : Person( name == "Bob" ) then System.out.println( "The name of the person is "+ ) // lets insert Mark, that is Bob's mate Person mark = new Person(); mark.setName("Mark"); insert( mark ); end
@matadata_key( metadata_value )
@author( Bob )
import java.util.Date declare Person @author( Bob ) @dateOfCreation( 01-Feb-2009 ) name : String @key @maxLength( 30 ) dateOfBirth : Date address : Address end
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
package org.drools.examples import java.util.Date declare Person name : String dateOfBirth : Date address : Address end
// get a reference to a knowledge base with a declared type:
KnowledgeBase kbase = ...
// get the declared FactType
FactType personType = kbase.getFactType( "org.drools.examples",
"Person" );
// handle the type as necessary:
// create instances:
Object bob = personType.newInstance();
// set attributes values
personType.set( bob,
"name",
"Bob" );
personType.set( bob,
"age",
42 );
// insert fact into a session
StatefulKnowledgeSession ksession = ...
ksession.insert( bob );
ksession.fireAllRules();
// read attributes
String name = personType.get( bob, "name" );
int age = personType.get( bob, "age" );

rule "<name>"
<attribute>*
when
<conditional element>*
then
<action>*
endrule "Approve if not rejected"
salience -100
agenda-group "approval"
when
not Rejection()
p : Policy(approved == false, policyState:status)
exists Driver(age > 25)
Process(status == policyState)
then
log("APPROVED: due to no objections.");
p.setApproved(true);
endRuleflow. To obtain the most value from JBoss Rules it is beneficial to gain a thorough understanding of each attribute. Read this section to do just that.

| Attribute | Default Value | Type | Comments |
|---|---|---|---|
| no-loop |
false
| Boolean |
When the rule's consequence modifies a fact it may cause the rule to activate again, causing recursion. Set no-loop to
true so that the attempt to create the activation for the current set of data will be ignored.
|
| ruleflow-group |
N/A
| string |
Use the rule-flow feature to exercise control over the firing of rules. (Rules that are assembled by the same ruleflow-group identifier will only fire when their group is active.)
|
| lock-on-active |
False
| Boolean |
Whenever a ruleflow-group becomes active or an agenda-group receives the focus, any rule within that group that has lock-on-active set to true will not be activated any more; irrespective of the origin of the update, the activation of a matching rule is discarded. This is a stronger version of no-loop, because the change could now be caused not only by the rule itself. It's ideal for calculation rules where you have a number of rules that modify a fact and you don't want any rule re-matching and firing again. Only when the ruleflow-group is no longer active or the agenda-group loses the focus those rules with lock-on-active set to true become eligible again for their activations to be placed onto the agenda.
|
| salience |
0
| integer |
Each rule has a salience attribute that can be assigned an integer number, the default being zero. Salience is a form of priority whereby rules with higher values are given higher priority when ordered onto the
activation queue.
|
| agenda-group |
MAIN
| string |
Agenda groups allow the user to partition the Agenda providing more execution control. Only rules in the agenda group that has acquired the focus are allowed to fire.
|
| auto-focus |
false
| Boolean |
If a rule, for which the
auto-focus value is true, is activate and if the rule's agenda group does not yet have focus, then focus is granted to it, allowing the rule to potentially fire.
|
| activation-group |
N/A
| string |
This string value identifies rules that belong to the same
activation group. Rules in such a group will only fire exclusively of each other. In other words, the first rule in an activation group to fire will cancel the other rules' activations, stopping them from firing in turn.
Note
This was once called the Xor group but, strictly speaking, it does not meet the definition of Xor.
|
| dialect |
as specified by the package
|
string, with possible values being
java and mvel
|
Use this attribute to specify the language to be used for any code expression on either the left-hand side or the right-hand side. Currently two dialects are available, Java and the MVFLEX Expression Language. (Whilst the dialect can also be specified at the package level, this attribute allows one to override the package definition for a rule.)
|
| date-effective |
N/A
|
string, containing date and time definitions
|
A rule can only activate if the current date and time indicate it is after the timestamp set in this attribute.
|
| date-expires |
N/A
|
string, containing date and time definitions
|
A rule will not activate if the current date and time indicate it is after the timestamp set in this attribute.
|
| duration |
no default value
|
long
|
Use this attribute to dictate that the rule will fire after a specified duration, provided that it is still
true.
|
rule "my rule" salience 42 agenda-group "number 1" when ...
timer ( int: <initial delay> <repeat interval>? ) timer ( int: 30s ) timer ( int: 30s 5m ) timer ( cron: <cron expression> ) timer ( cron:* 0/15 * * * ? )
rule "Send SMS every 15 minutes"
timer (cron:* 0/15 * * * ?)
when
$a : Alarm( on == true )
then
channels[ "sms" ].insert( new Sms( $a.mobileNumber, "The alarm is still on" ));
endCalendar weekDayCal = QuartzHelper.quartzCalendarAdapter(org.quartz.Calendar quartzCal)
ksession.getCalendars().set( "week day", weekDayCal );
rule "weekdays are high priority"favors
calendars "weekday"
timer (int:0 1h)
when
Alarm()
then
send( "priority high - we have an alarm” );
end
rule "weekend are low priority"
calendars "weekend"
timer (int:0 4h)
when
Alarm()
then
send( "priority low - we have an alarm” );
endeval(true). This means that the rule's condition will always remain true.)
working memory session is created.

conditional element:
rule "no CEs"
when
then
<action>*
end
rule "no CEs"
when
eval( true )
then
<action>*
end
and, which is implied when there are multiple, totally-unrelated patterns in the left-hand side of a rule.
or pattern, an and cannot have a leading declaration binding. This is because a declaration can only refer to a single fact, and when the and is "satisfied", it matches more than one fact, hence it would not know to which of these it should be bound.
conditional element. The entity relationship diagram below provides an overview of the various parts that make up the pattern's constraints and shows how they work together. Later in this section each part is covered in more detail with further diagrams and sample code.


Cheese, which means that the pattern will match against every Cheese object in the working memory.
Cheese( )
$c, to refer to the matched object. A helpful option is to use make use of the $ prefix. This can be advantageous when dealing with complex rules as it helps one to differentiate between variables and fields more easily.
$c : Cheese( )
, && or || to separate constraints. However, note that they have slightly different abilities.



,) is used to separate constraint groups. It has implicit and connective semantics:
# Cheese type is stilton and price < 10 and age is mature. Cheese( type == "stilton", price < 10, age == "mature" )
type == "stilton".
price < 10.
age == "mature".
&& and || separators allow groups to have multiple constraints. Here is an example:
// Cheese type is "stilton" and price < 10, and age is mature Cheese( type == "stilton" && price < 10, age == "mature" ) // Cheese type is "stilton" or price < 10, and age is mature Cheese( type == "stilton" || price < 10, age == "mature" )
# Cheese type is stilton and ( price is less than 20 or age is mature ). Cheese( type == "stilton" && ( price < 20 || age == "mature" ) )
|| connective is evaluated before the && connective.
&& and , connectives are resolved with different priorities. Hence , cannot be embedded in a composite constraint expression:
// invalid as ',' cannot be embedded in an expression: Cheese( ( type == "stilton", price < 10 ) || age == "mature" ) // valid as '&&' can be embedded in an expression: Cheese( ( type == "stilton" && price < 10 ) || age == "mature")


getXXX method or the isXXX method, in which cases these methods take no arguments but return something.
getType is accessed as type. (JBoss Rules uses the standard Java Development Kit Introspector class to undertake this mapping process.)
Cheese class example, the Cheese(type == "brie") pattern applies the getType() method to a Cheese instance. If a field name cannot be found, the compiler will resort to using the name as a method without arguments. Thus, the toString() is called due to a Cheese(toString == "cheddar") constraint. In this case, use the full name of the method with correct capitalization but without parentheses. Ensure that methods being accessed do not take parameters, and that they are, in fact, accessors which will not change the state of the object in a way that affects the rules. (Remember that the rule engine caches the results of its matching in between invocations for performance reasons.)




== and != in the usual way. The literal null keyword, (as in Cheese(type != null), whereby the evaluator will not throw an exception will return true if the value is null.)
ten as a string in a numeric evaluator will cause an exception, whereas providing 10 will coerce to a numeric ten. (Coercion always favors the field type, not the value type.)


== and != operators are valid for all types. Other relational operators may be used whenever the type values are ordered; for date fields, < means "before." The pair of matches and not matches only apply to string fields, whereas contains and not contains require the field to be that of a Collection type. (It will attempt to coerce it to the correct value for the evaluator and the field.)
matches OperatorCheese( type matches "(Buffalo)?\S*Mozarella" )
not matches Operator true if the string does not match the regular expression. The same rules apply as for the matches operator. Here is an example of its use:
Cheese( type not matches "(Buffulo)?\S*Mozerella" )
contains Operator CheeseCounter( cheeses contains "stilton" ) // contains with a String literal CheeseCounter( cheeses contains $var ) // contains with a variable
not contains Operator CheeseCounter( cheeses not contains "cheddar" ) // not contains with a String literal CheeseCounter( cheeses not contains $var ) // not contains with a variable
excludes operator is still supported. It is synonymous with not contains.
memberOf OperatorCheeseCounter( cheese memberOf $matureCheeses )
not memberOf OperatorCheeseCounter( cheese not memberOf $matureCheeses )
soundslike Operatormatches but it checks as to whether or not a word has almost the same sound as the given value. To do so, it uses the the Soundex algorithm and is based on English pronunciations of the words.
// match cheese "fubar" or "foobar" Cheese( name soundslike 'foobar' )

Cheese( quantity == 5 )
dd-mmm-yyyy. To change this, provide an alternative date format mask for the drools.dateformat property. (If more control is required, use the inline-eval constraint.)
Cheese( bestBefore < "27-Oct-2013" )
Cheese( type == "stilton" )
true or false; 0 and 1 are not acceptable. A lone Boolean field (as in Cheese( smelly ) is not permitted; it must be compared to a Boolean literal.
Cheese( smelly == true )
enums are supported but the latter can only be used with a JDK 5 environment.
Cheese( smelly == SomeClass.TRUE )

field constraints. A bound variable is called a declaration. The type of the field being constrained determines which operators are valid; coercion will be attempted where possible. Use the == operator to bind variable restrictions for very fast performance.
Person( likes : favouriteCheese ) Cheese( type == likes )
likes variable is bound to the favouriteCheese field of every matching Person instance. (It then constrains the type of Cheese in the next pattern.) Any valid Java variable name can be used, and it may be prefixed with $, which is often used to help differentiate declarations from fields.
$stilton, bound to the object matching the first pattern. It is used in conjunction with a contains operator. (Note the optional use of $.)
$stilton : Cheese( type == "stilton" ) Cheesery( cheeses contains $stilton )

Person( girlAge : age, sex == "F" ) Person( age == ( girlAge + 2) ), sex == 'M' )
in and not in evaluators support this.)
evaluators are actually "syntactic sugar", internally rewritten as a list of multiple restrictions using the != and == operators.

Person( $cheese : favouriteCheese ) Cheese( type in ( "stilton", "cheddar", $cheese )
&& or || separators.) Grouping via parentheses is permitted; this will result in a recursive syntactical pattern.


// Simple multi restriction using a single && Person( age > 30 && < 40 ) // Complex multi restriction using groupings of multi restrictions Person( age ( (> 30 && < 40) || (> 20 && < 25) ) ) // Mixing muti restrictions with constraint connectives Person( age > 30 && < 40 || location == "london" )

age variable is auto-created in the second pattern by the auto-vivification process.
Person( girlAge : age, sex = "F" ) Person( eval( age == girlAge + 2 ), sex = 'M' )
inline-eval constraints. )
nested accessors as the working memory is not aware of any of the nested values and does not know when they change. Always regard them as immutable whilst any of their parent references are in working memory.
modify construct and its block setters to write the nested accessor assignments whilst retracting and inserting the the root parent object as required. (Nested accessors can be used on either side of the operator symbol.)
// Find a pet older than its owners first-born child $p : Person( ) Pet( owner == $p, age > $p.children[0].age )
inline eval:
// Find a pet older than its owners first-born child $p : Person( ) Pet( owner == $p, eval( age > $p.children[0].age ) )
nested accessors carefully as they have a much greater performance impact than direct field accesses.
and Conditional Element and conditional element to group other conditional elements into a logical conjunction.
root element of the left-hand side is an implicit prefix and. It does not need to be specified. JBoss Rules supports both and as both a prefix and as an infix but the prefix is the preferred option as its implicit grouping eliminates confusion.

(and Cheese( cheeseType : type ) Person( favouriteCheese == cheeseType ) )
when Cheese( cheeseType : type ) Person( favouriteCheese == cheeseType )
and is supported, along with explicit grouping via parentheses.
&& symbol can be used as an alternative to and. This is deprecated but is still currently available for legacy support reasons.

//infixAnd Cheese( cheeseType : type ) and Person( favouriteCheese == cheeseType ) //infixAnd with grouping ( Cheese( cheeseType : type ) and ( Person( favouriteCheese == cheeseType ) or Person( favouriteCheese == cheeseType ) )
or Conditional Element or conditional element to group other conditional element into a logical disjunction.
or as either a prefix or as an infix, but the prefix is the preferred option as its implicit grouping avoids confusion.
conditional element is different to that of the connective || for field constraints and restrictions. The engine actually has no understanding of or; rather, via a number of different logic transformations, a rule that uses or is rewritten as a number of sub-rules. This results in a rule that has a single root node or and one sub-rule for each of its conditional elements. Each sub-rule can activate and fire like any normal rule; there is no special behavior or interaction between them, a fact which sometimes confuses new developers.

(or Person( sex == "f", age > 60 ) Person( sex == "m", age > 65 )
or is supported along with explicit grouping with parentheses, should it be needed.
|| symbol can be used as an alternative to or. This is deprecated but it is still currently available for legacy support reasons.

//infixOr Cheese( cheeseType : type ) or Person( favouriteCheese == cheeseType ) //infixOr with grouping ( Cheese( cheeseType : type ) or ( Person( favouriteCheese == cheeseType ) and Person( favouriteCheese == cheeseType ) )
or. This means that each resulting sub-rule will bind to the pattern. Each pattern must be bound separately, using eponymous variables, as in this example:
(or pensioner : Person( sex == "f", age > 60 ) pensioner : Person( sex == "m", age > 65 ) )
or is used, one for each possible outcome. The simple example shown above will generate two rules. These will function independently of each other within the working memory, meaning that they can both match, activate and fire. No short-cuts are taken.
or as a way to generate two or more similar rules. A single rule may have multiple activations if two or more terms of the disjunction are true.
eval Conditional Element 
eval conditional element is essentially a "catch-all" which allows one to execute any semantic code that returns a primitive Boolean. This code can refer either to variables that were bound to the left-hand side of the rule, or to functions in the rule package.
eval because it reduces the declarative nature of the rules which can lead to a poorly performing engine. Whilst eval can be used anywhere in the patterns, best practice dictates that one should add it as the last conditional element in the left-hand side of a rule.
Evals cannot be indexed and, thus, are not as efficient as field constraints. However they are ideal for use as functions that return values which are subject change over time, (an ability which field constraints do not possess.)
p1 : Parameter() p2 : Parameter() eval( p1.getList().containsKey(p2.getItem()) ) // call function isValid in the LHS eval( isValid(p1, p2) )
not Conditional Element 
not conditional element is the first-order logic's non-existential quantifier. Its purpose is to check that something does not exist in the working memory.
not keyword must be followed by conditional element that are, themselves, in parentheses. (In the simplest use cases, these parentheses can be omitted.)
not Bus()
// Brackets are optional: not Bus(color == "red") // Brackets are optional: not ( Bus(color == "red", number == 42) ) // "not" with nested infix and - two patterns, // brackets are requires: not ( Bus(color == "red") and Bus(color == "blue") )
exists Conditional Element 
exists conditional element is the first order logic's existential quantifier. Its purpose is to check for the existence of something in the working memory. Some programmers find it easiest to think of exists as meaning "there is at least one". (It is different from just having the pattern on its own, which is more like saying "for each one of.")
exists is used with a pattern, the rule will only activate once, regardless of how much data there is in working memory that matches its condition. Since only the very existence matters, no bindings will be established.
exists keyword must be followed by the conditional elements to which it applies. These must be contained within parentheses. (In the simplest of single patterns, like that depicted below, one may has the option of omitting the parentheses.)
exists Bus()
exists Bus(color == "red") // brackets are optional: exists ( Bus(color == "red", number == 42) ) // "exists" with nested infix and, // brackets are required: exists ( Bus(color == "red") and Bus(color == "blue") )
forall Conditional Element 
forall conditional element completes the first-order logic support in JBoss Rules. forall evaluates as true when all of the facts that match the first pattern also match every remaining pattern. Here is an example:
rule "All English buses are red"
when
forall( $bus : Bus( type == 'english')
Bus( this == $bus, color = 'red' ) )
then
# all english buses are red
end
Bus object for which the type is english. Then, for each fact that matches this pattern, the following patterns are evaluated. If they, too, match, the forall conditional element will evaluate as true.
rule "All Buses are Red"
when
forall( Bus( color == 'red' ) )
then
# all asserted Bus facts are red
end
rule "all employees have health and dental care programs"
when
forall( $emp : Employee()
HealthCare( employee == $emp )
DentalCare( employee == $emp )
)
then
# all employees have health and dental care
end
conditional elements such as not.
rule "not all employees have health and dental care"
when
not ( forall( $emp : Employee()
HealthCare( employee == $emp )
DentalCare( employee == $emp ) )
)
then
# not all employees have health and dental care
end
not( forall( p1 p2 p3...)) is equivalent to this piece of code:
not(p1 and not(and p2 p3...))
forall is a scope delimiter. Therefore, it can use any previously bound variable but no such variable bound within it will be available for use outside of it.
from Conditional Element 
from conditional element to specify an arbitrary source for data to be matched by left-hand side patterns. Doing so allows the engine to "reason over" data not found in the working memory. The data source could be a sub-field on a bound variable or the result of a method call.
rule "validate zipcode"
when
Person( $personAddress : address )
Address( zipcode == "23920W") from $personAddress
then
# zip code is ok
end
rule "validate zipcode"
when
$p : Person( )
$a : Address( zipcode == "23920W") from $p.address
then
# zip code is ok
end
from on object sources to return a collection of objects. In this case, from will iterate over every objects in the collection and try to match each of them individually. Here is an example, featuring a rule designed to a ten percent discount to every item in an order:
rule "apply 10% discount to all items over $ 100,00 in an order"
when
$order : Order()
$item : OrderItem( value > 100 ) from $order.items
then
# apply discount to $item
end
from, especially in conjunction with the lock-on-active rule attribute as it may produce unexpected results. Consider the example provided earlier, but now slightly modified as follows:
rule "Assign people in Queensland (QLD) to sales region 1"
ruleflow-group "test"
lock-on-active true
when
$p : Person( )
$a : Address( state == "QLD") from $p.address
then
modify ($p) {} #Assign person to sales region 1 in a modify block
end
rule "Apply a discount to people in the city of Brisbane"
ruleflow-group "test"
lock-on-active true
when
$p : Person( )
$a : Address( city == "Brisbane") from $p.address
then
modify ($p) {} #Apply discount to person in a modify block
end
from, for all intents and purposes, returns a new fact each time it is evaluated.)
working memory and other rules downstream of in rule-flow in question and needs must be reevaluated, the use of modify is critical. Do not, however, make other rules in the same rule-flow group place activations on one another recursively.
from when it is possible to assert all facts into working memory or use nested object references in the constraint expressions (shown below).
modify block as the last sentence in the left-hand side condition.
from when it is possible to assert all facts directly into working memory.
Person and the Address instances can be asserted into working memory. Because the graph is fairly simple, an even easier solution is to modify the rules in the following way:
rule "Assign people in Queensland (QLD) to sales region 1"
ruleflow-group "test"
lock-on-active true
when
$p : Person(address.state == "QLD" )
then
modify ($p) {} #Assign person to sales region 1 in a modify block
end
rule "Apply a discount to people in the city of Brisbane"
ruleflow-group "test"
lock-on-active true
when
$p : Person(address.city == "Brisbane" )
then
modify ($p) {} #Apply discount to person in a modify block
end
Person holds one or more Addresses and one wishes to use an existential quantifier to match people with at least one address that meets certain conditions. In this case, one will have to resort to the use of from to reason over the collection.
from in the following way causes both rules to fire as expected:
rule "Assign people in Queensland (QLD) to sales region 1"
ruleflow-group "test"
lock-on-active true
when
$p : Person($addresses : addresses)
exists (Address(state == "QLD") from $addresses)
then
modify ($p) {} #Assign person to sales region 1 in a modify block
end
rule "Apply a discount to people in the city of Brisbane"
ruleflow-group "test"
lock-on-active true
when
$p : Person($addresses : addresses)
exists (Address(city == "Brisbane") from $addresses)
then
modify ($p) {} #Apply discount to person in a modify block
end
rule "Assign people in Queensland (QLD) to sales region 1"
ruleflow-group "test"
lock-on-active true
when
$assessment : Assessment()
$p : Person()
$addresses : List() from $p.addresses
exists (Address( state == "QLD") from $addresses)
then
modify ($assessment) {} #Modify assessment in a modify block
end
rule "Apply a discount to people in the city of Brisbane"
ruleflow-group "test"
lock-on-active true
when
$assessment : Assessment()
$p : Person()
$addresses : List() from $p.addresses
exists (Address( city == "Brisbane") from $addresses)
then
modify ($assessment) {} #Modify assessment in a modify block
end
from returns the $addresses variable. This example also introduces a new object, assessment, which points the way to a possible solution. If the $addresses variable is moved so that it becomes the last condition in each rule, both fire as expected.
from with lock-on-active without loss of rule activations, they carry the drawback of being dependent on the placement order of conditions on the left-hand side. In addition, the solutions present greater complexity for the rule author, who must suddenly keep track of conditions with the potential to cause issues.
working memory. In this case, once the person's addresses are asserted into working memory it will no longer be necessary to use from.
working memory will not be practical and other solutions will need to be found.
working memory has been modified. For example, to use the example above once more, one would add a condition to the rule that checks whether the discount has already been applied and, if so, ensures that the rule does not activate.
collect Conditional Element 
collect conditional element to make rules "reason" over a collection of objects that have been obtained from either a given source or from the working memory.
import java.util.ArrayList
rule "Raise priority if system has more than 3 pending alarms"
when
$system : System()
$alarms : ArrayList( size >= 3 )
from collect( Alarm( system == $system, status == 'pending' ) )
then
# Raise priority, because system $system has
# 3 or more alarms pending. The pending alarms
# are $alarms.
end
working memory for any pending alarms for each given system. It then groups them in ArrayLists. If it finds three or more alarms for a given system, it fires.
collect's result pattern can be any "concrete" class that implements the java.util.Collection interface and provides a default public constructor with no arguments. This means that one can use Java collections like ArrayList, LinkedList, HashSet or even a custom class, as long as it implements the java.util.Collection as long as it meets these requirements.
collect conditional element are in the scope of both the source and the result patterns. Use them to constrain these patterns. However, note that collect is a scope delimiter for bindings, so that any binding made inside of it is not available for use outside of it.
collect can accept nested from conditional elements. Hence, the following example is a valid use of collect:
import java.util.LinkedList;
rule "Send a message to all mothers"
when
$town : Town( name == 'Paris' )
$mothers : LinkedList()
from collect(
Person( gender == 'F', children > 0 )
from $town.getPeople()
)
then
# send a message to all mothers
end
accumulate Conditional Element 
accumulate conditional element is a more flexible and powerful form of collect. It allows a rule to iterate over a collection of objects, executing custom actions for each of the elements. Upon completion, it returns a result object.
accumulate conditional element:
<result pattern> from accumulate(<source pattern>, init( <init code> ), action( <action code> ), reverse( <reverse code> ), result( <result expression> ) )
<source pattern>: this is a regular pattern that the engine attempts to match with each of the source objects.
<init code>: this is a semantic block of code in the selected dialect. It is 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 is executed for each of the source objects.
<reverse code>: this is an optional semantic block of code in the selected dialect. If present, it is executed for each source object that no longer matches the source pattern. The objective is to undo any calculation performed in the <action code> block, so that the engine can do a decremental calculation when a source object is modified or retracted. This improves performance of these operations quite dramatically.
<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 engine tries to match with the object returned from the <result expression>. If it matches, the accumulate conditional element evaluates it as true and the engine proceeds to evaluate the next conditional element in the rule.
accumulate conditional element evaluates it as false and the engine stops evaluating conditional elements for that rule.
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
engine executes the init code for each order in the working memory. This initializes the total variable as zero.
OrderItem objects for that order, executing the action for each one (in this case, it sums the total value of all of the items and puts this into the total variable.)
result expression (the value of variable total.)
Number pattern, and if the double value is greater than 100, the rule fires.
init, action and reverse code blocks. The result is an expression and, as such, it does not admit ;. If using any other dialect, always comply with its specific syntax.
reverse code is optional, but Red Hat strongly recommends using it in order to benefit from improved performance when using update and retract.
accumulate conditional element to execute any action on source objects. The example in the next section instantiates and populates a custom object.
accumulate conditional element is very powerful CE, but it is particularly easy to use when utilizing those predefined functions known as accumulate functions. They work almost exactly like accumulate with the difference that, instead of explicitly writing custom code in every accumulate conditional element, one can use the predefined code for common operations.
accumulate functions:
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
sum is an accumulate function. As its name implies, it sums the $value of every OrderItem and returns the result.
accumulate functions:
average
min
max
count
sum
average function like this:
rule "Average profit"
when
$order : Order()
$profit : Number()
from accumulate( OrderItem( order == $order, $cost : cost, $price : price )
average( 1 - $cost / $price ) )
then
# average profit for $order is $profit
end
accumulate function is pluggable. This means that, if needed, customized, domain-specific functions can be added to the engine quite easily. The rules can then start to use them without any restrictions. To implement a new accumulate function all one needs to do is:
org.drools.base.acumulators.AccumulateFunction interface.
engine know about the new function.
average function:
/* * Copyright 2007 JBoss Inc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Created on Jun 21, 2007 */ package org.drools.base.accumulators; /** * An implementation of an accumulator capable of calculating average values * * @author etirelli * */ public class AverageAccumulateFunction implements AccumulateFunction { protected static class AverageData { public int count = 0; public double total = 0; } /* (non-Javadoc) * @see org.drools.base.accumulators.AccumulateFunction#createContext() */ public Object createContext() { return new AverageData(); } /* (non-Javadoc) * @see org.drools.base.accumulators.AccumulateFunction#init(java.lang.Object) */ public void init(Object context) throws Exception { AverageData data = (AverageData) context; data.count = 0; data.total = 0; } /* (non-Javadoc) * @see org.drools.base.accumulators.AccumulateFunction#accumulate(java.lang.Object, * java.lang.Object) */ public void accumulate(Object context, Object value) { AverageData data = (AverageData) context; data.count++; data.total += ((Number) value).doubleValue(); } /* (non-Javadoc) * @see org.drools.base.accumulators.AccumulateFunction#reverse(java.lang.Object, * java.lang.Object) */ public void reverse(Object context, Object value) throws Exception { AverageData data = (AverageData) context; data.count--; data.total -= ((Number) value).doubleValue(); } /* (non-Javadoc) * @see org.drools.base.accumulators.AccumulateFunction#getResult(java.lang.Object) */ public Object getResult(Object context) throws Exception { AverageData data = (AverageData) context; return new Double( data.count == 0 ? 0 : data.total / data.count ); } /* (non-Javadoc) * @see org.drools.base.accumulators.AccumulateFunction#supportsReverse() */ public boolean supportsReverse() { return true; } }
engine.
engine, add it to the configuration file:
drools.accumulate.function.average =
org.drools.base.accumulators.AverageAccumulateFunction
drools.accumulate.function. prefix.
average dictates the way in which function will be used in the rule file, whilst org.drools.base.accumulators.AverageAccumulateFunction is the fully-qualified name of the class that implements the behavior of the function.
working memory data. That is its purpose. To assist with this, one can take advantage of the following convenience methods that modify working memory without the need for one to firstly reference a working memory instance:
update(object, handle) to tell the engine that an object (that has been bound to something on the left-hand side) has changed and the rules may, therefore need to be "reconsidered."
update(object) to make the Knowledge Helper look up the facthandle required. It does so by using identity-checking the passed object. (If one is providing the Java beans with property change listeners, one is inserting them into the engine, so there is no need to call update() when the object changes.)
insert(new Something ()) to place a newly-created object in working memory.
insertLogical(new Something()) is similar to insert, with the difference that the object is automatically retracted when there are no more facts to support the truth of the currently-firing rule.
retract(handle) to removes an object from working memory.
convenience methods are, in fact, just macros that provide short cuts to the Knowledge Helper instance. By doing so, they allow one to access the working memory from the rules files.
drools.halt() to terminate rule execution immediately. Do this to return control to the point at which the current session was started with fireUntilHalt().
insert(Object o), update(Object o) and retract(Object o) methods can be called as well. (Due to their frequent use they can be called without the object reference.)
drools.getWorkingMemory() to return the working memory object.
drools.setFocus( String s) to set the focus upon the specified agenda group.
drools.getRule().getName() to return the name of the rule.
drools.getTuple() to return the tuple that matches the currently executing rule. drools.getActivation() returns the corresponding activation. (One will find these calls useful during the debugging process.)
Knowlege Runtime application programming interface is exposed through another predefined variable, kcontext, which is of the type KnowledgeContext. Its getKnowledgeRuntime() method delivers an object of the type KnowledgeRuntime, which, in turn, provides access to numerous methods, many of which are quite useful for coding right-hand side logic.
kcontext.getKnowledgeRuntime().halt() call to terminate rule execution immediately.
getAgenda() accessor to return a reference to this session's agenda. This, in turn will provide access to the various activation, agenda and rule-flow groups. A relatively common use is the activation of some agenda group, demonstrated here:
// give focus to the agenda group CleanUp kcontext.getKnowledgeRuntime().getAgenda().getAgendaGroup( "CleanUp" ).setFocus();
drools.setFocus( "CleanUp" ).
getQueryResults(String query), after which one may process the results in the ways explained in Section 4.9, “Query”.
event management that lets one add and remove working memory and agenda event listeners.
getKnowledgeBase() method to return the KnowledgeBase object, which is the "backbone" of the system and, indeed, the originator of the current session.
setGlobal(...), getGlobal(...) and getGlobals().
getEnvironment() to return the run-time's environment. (This is much like an operating system's environment.)
fact updates. It combines the update operation with a number of setter calls that change the object's fields. Here is the syntactical schema for it:
modify ( <fact-expression> ) { <expression> [ , <expression> ]* }
<fact-expression> must yield a fact-object reference. Ensure that the block's expression list consists of setter calls for the given object. (These will be written without the usual object reference, which is automatically prep-ended by the compiler.)
rule "modify stilton"
when
$stilton : Cheese(type == "stilton")
then
modify( $stilton ){
setPrice( 20 ),
setAge( "overripe" )
}
end

KnowledgeBase, so do not add queries of the same name to different knowledge packages for the same RuleBase.
ksession.getQueryResults("name"), where "name" is the query's name. This returns a list of query results, which allow you to retrieve the objects that matched the query.
query "people over the age of 30"
person : Person( age > 30 )
endquery "people over the age of x" (int x, String y)
person : Person( age > x, location == y )
endQueryResultsRow which we can use to access each of the columns in the tuple. These columns can be accessed by bound declaration name or index position.
QueryResults results = ksession.getQueryResults( "people over the age of 30" );
System.out.println( "we have " + results.size() + " people over the age of 30" );
System.out.println( "These people are are over 30:" );
for ( QueryResultsRow row : results ) {
Person person = ( Person ) row.get( "person" );
System.out.println( person.getName() + "\n" );
}engine operates. This is useful if rules need to be read and validated by domain experts (such as business analysts) who are not programmers. DSLs hide implementation details and focuses on the rule logic.
[when]Something is {color}=Something(color=="{color}")[when] keyword indicates the scope of the expression, i.e., whether it is valid for the LHS or the RHS of a rule.
= 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 should to be a term according to the regular LHS syntax; if it is for the RHS then it might be a Java statement.
{color}). Then, the values obtained from these captures are 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.
? to indicate that the preceding character is optional. This helps to overcome variations in the natural language phrases of your DSL. however, as 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 ('\').
[when]This is "{something}" and "{another}"=Something(something=="{something}", another=="{another}")
[when]This is {also} valid=Another(something=="{also}"){ 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(); \}{ 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 = andThere 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 "Atlanta"
==> Person(name="kitty") and Person(age > 30, location="Atlanta")- to the beginning of the DSL expression. If the expression starts with a hyphen it is assumed to be a field constraint and is added to the last pattern line preceding it.
Cheese class, which has the following fields: type, price, age, and country, it is possible to express some left-hand side conditions in a normal DRL file as follows:
Cheese(age < 5, price == 20, type=="stilton", country=="ch")
[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}'There is a Cheese with
- age is less than 42
- type is 'stilton'- and add it as a constraint to the preceding pattern, inserting a comma when it is required. For Example 4.26, “Writing Constraints” example, the resulting DRL is:
Cheese(age<42, type=='stilton')
[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()
- followed by Ctrl+Space, and then choose an item from this list.)
[when][org.drools.Cheese]- age is less than {age}. Do the same to all of the other items in the example above.
[org.drools.Cheese] code indicates that the sentence only applies to the main constraint directly above it (which, in this case reads There is a Cheese with.)
Cheese and a constraint is being added via the Content Assistance approach, then only those items marked with an object-scope of com.yourcompany.Something are valid, so only they will appear in the list. This is entirely optional.
# 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.
[ is assumed to be the first line of a DSL entry definition.
[condition] or [when]
[consequence] or [then]
[keyword], for instance rule or end.
=. A variable definition is enclosed in curly 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.
\ if they should occur literally within the expression.
! and a transformation function, see below.
{ and } must be escaped with a preceding backslash \ if they should occur literally within the replacement string.
#/ 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. |
# 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} )\{ \}
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.
when and then, and then and end, respectively, are located and processed in a uniform manner, line by line, as described below.
.*?. If the resulting regular expression matches all or part of the line, the matched part is replaced by the suitably modified replacement text.
, is inserted beforehand.
modify statement, ending in a pair of curly 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.
accumulate) or it may only work for the first insertion (e.g., eval).
| 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. |
# 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}.dsl. It is passed to the Knowledge Builder with ResourceType.DSL. For a file using DSL definition, the extension .dslr should be used. The Knowledge Builder expects ResourceType.DSLR. The IDE, however, relies on file extensions to correctly recognize and work with your rules file.
KnowledgeBuilder kBuilder = new KnowledgeBuilder(); Resource dsl = ResourceFactory.newClassPathResource( dslPath, getClass() ); kBuilder.add( dsl, ResourceType.DSL ); Resource dslr = ResourceFactory.newClassPathResource( dslrPath, getClass() ); kBuilder.add( dslr, ResourceType.DSLR );
{token}.
drools-ant task, or alternatively, incorporate the code shown in Section 4.11, “XML Rule Language”.
<?xml version="1.0" encoding="UTF-8"?> <package name="com.sample" xmlns="http://drools.org/drools-4.0" xmlns:xs="http://www.w3.org/2001/XMLSchema-instance" xs:schemaLocation="http://drools.org/drools-4.0 drools-4.0.xsd"> <import name="java.util.HashMap" /> <import name="org.drools.*" /> <global identifier="x" type="com.sample.X" /> <global identifier="yada" type="com.sample.Yada" /> <function return-type="void" name="myFunc"> <parameter identifier="foo" type="Bar" /> <parameter identifier="bada" type="Bing" /> <body>System.out.println("hello world");</body> </function> <rule name="simple_rule"> <rule-attribute name="salience" value="10" /> <rule-attribute name="no-loop" value="true" /> <rule-attribute name="agenda-group" value="agenda-group" /> <rule-attribute name="activation-group" value="activation-group" /> <lhs> <pattern identifier="foo2" object-type="Bar" > <or-constraint-connective> <and-constraint-connective> <field-constraint field-name="a"> <or-restriction-connective> <and-restriction-connective> <literal-restriction evaluator=">" value="60" /> <literal-restriction evaluator="<" value="70" /> </and-restriction-connective> <and-restriction-connective> <literal-restriction evaluator="<" value="50" /> <literal-restriction evaluator=">" value="55" /> </and-restriction-connective> </or-restriction-connective> </field-constraint> <field-constraint field-name="a3"> <literal-restriction evaluator="==" value="black" /> </field-constraint> </and-constraint-connective> <and-constraint-connective> <field-constraint field-name="a"> <literal-restriction evaluator="==" value="40" /> </field-constraint> <field-constraint field-name="a3"> <literal-restriction evaluator="==" value="pink" /> </field-constraint> </and-constraint-connective> <and-constraint-connective> <field-constraint field-name="a"> <literal-restriction evaluator="==" value="12"/> </field-constraint> <field-constraint field-name="a3"> <or-restriction-connective> <literal-restriction evaluator="==" value="yellow"/> <literal-restriction evaluator="==" value="blue" /> </or-restriction-connective> </field-constraint> </and-constraint-connective> </or-constraint-connective> </pattern> <not> <pattern object-type="Person"> <field-constraint field-name="likes"> <variable-restriction evaluator="==" identifier="type"/> </field-constraint> </pattern> <exists> <pattern object-type="Person"> <field-constraint field-name="likes"> <variable-restriction evaluator="==" identifier="type"/> </field-constraint> </pattern> </exists> </not> <or-conditional-element> <pattern identifier="foo3" object-type="Bar" > <field-constraint field-name="a"> <or-restriction-connective> <literal-restriction evaluator="==" value="3" /> <literal-restriction evaluator="==" value="4" /> </or-restriction-connective> </field-constraint> <field-constraint field-name="a3"> <literal-restriction evaluator="==" value="hello" /> </field-constraint> <field-constraint field-name="a4"> <literal-restriction evaluator="==" value="null" /> </field-constraint> </pattern> <pattern identifier="foo4" object-type="Bar" > <field-binding field-name="a" identifier="a4" /> <field-constraint field-name="a"> <literal-restriction evaluator="!=" value="4" /> <literal-restriction evaluator="!=" value="5" /> </field-constraint> </pattern> </or-conditional-element> <pattern identifier="foo5" object-type="Bar" > <field-constraint field-name="b"> <or-restriction-connective> <return-value-restriction evaluator="==" > a4 + 1 </return-value-restriction> <variable-restriction evaluator=">" identifier="a4" /> <qualified-identifier-restriction evaluator="=="> org.drools.Bar.BAR_ENUM_VALUE </qualified-identifier-restriction> </or-restriction-connective> </field-constraint> </pattern> <pattern identifier="foo6" object-type="Bar" > <field-binding field-name="a" identifier="a4" /> <field-constraint field-name="b"> <literal-restriction evaluator="==" value="6" /> </field-constraint> </pattern> </lhs> <rhs> if ( a == b ) { assert( foo3 ); } else { retract( foo4 ); } System.out.println( a4 ); </rhs> </rule> </package>
import elements import the types you wish to use in the rule.
global elements define global objects that can be referred to in the rules.
function contains a function declaration, for a function to be used in the rules. You have to specify a return type, a unique name and parameters, in the body goes a snippet of code.
<rule name="simple_rule"> <rule-attribute name="salience" value="10" /> <rule-attribute name="no-loop" value="true" /> <rule-attribute name="agenda-group" value="agenda-group" /> <rule-attribute name="activation-group" value="activation-group" /> <lhs> <pattern identifier="cheese" object-type="Cheese"> <from> <accumulate> <pattern object-type="Person"></pattern> <init> int total = 0; </init> <action> total += $cheese.getPrice(); </action> <result> new Integer( total ) ); </result> </accumulate> </from> </pattern> <pattern identifier="max" object-type="Number"> <from> <accumulate> <pattern identifier="cheese" object-type="Cheese"></pattern> <external-function evaluator="max" expression="$price"/> </accumulate> </from> </pattern> </lhs> <rhs> list1.add( $cheese ); </rhs> </rule>
XmlDumper - for exporting XML. DrlDumper - for exporting DRL. DrlParser - reading DRL. XmlPackageReader - reading XML.
CSV or .XLS.



agenda when a rule fires and simulate a very simple decision table at the point where the first match exists. Decision tables are simply a tool to generate DRL packages automatically.


RuleTable keyword to indicate the starting row and column of a rule table. (Other keywords used to define other package level attributes are covered later in this chapter.) It is important to keep the keywords in the one column. By convention, the second column ("B") is used for this, but it can be any column (it is also a convention is to leave a margin on the left for notes). In the following diagram, "C" is actually the column where it starts. Everything to the left of this is ignored.

RuleSet keyword indicates the name to be used in the rule package under which all of the rules are to be grouped. (Not that the name is optional. It will have a default but the RuleSet keyword must be present.) The other keywords visible in Column C are Import and Sequential, which will are covered later in this chapter. At this stage, just note that, in general, the keywords make up name/value pairs.
RuleTable keyword is important as it indicates that a group of rules will follow, and that these will be based on some rule templates.
RuleTable keyword there is a name. This is used as a prefix of the rules names that are generated. (The row numbers are appended to create unique rule names.) The RuleTable column indicates the column in which the rules start (the columns to the left of it are ignored.)
CONDITION and ACTION keywords in Row 14 indicate that the data in the columns below is either for the LHS or the RHS part of a rule. (There are other attributes that can also be optionally set in this way.)
ObjectTypes. The content in this row is optional, leave a blank row if you do not want to use it. When this row is used, the values in the cells below (in Row 16) become constraints on that object type. In the above case, it will generate: Person(age=="42") (where 42 comes from Row 18). In the above example, the == is implicit, if you just put a field name, it will assume that you are looking for exact matches.
ObjectType declaration span columns (by merging cells). This results in all of those columns below the merged range being combined into a single set of constraints.
$para place holder to indicate where data from the cells will be populated. Use $param, or $1, $2 and so forth to indicate parameters from a comma-separated list located in a cell below.)
RuleTables in a sheet.)
RuleTable or RuleSet keywords appear (in this case column C has been chosen but one can use Column A if this is preferred.)
ObjectType row is being used):
//row 18
rule "Cheese_fans_18"
when
Person(age=="42")
Cheese(type=="stilton")
then
list.add("Old man stilton");
end
[age=="42"] and [type=="stilton"] are interpreted as single constraints to be added to the respective ObjectTypes in the cell above (if the cells above were spanned, then there would be multiple constraints on one "column".)
CONDITION column and ACTION column. In most cases, it is identical to "vanilla" DRL for the LHS or RHS respectively. This means in the LHS, the constraint language must be used and, in the RHS, it is a snippet of code intended for execution.
$param place holder is used in templates to indicate where data form the cell will be interpolated. You can also use $1 to the same effect. If the cell contains a comma separated list of values, the symbols $1, $2, etc. may be used to indicate which positional parameter from the list of values in the cell will be used. The forall(DELIMITER){SNIPPET} function can be used to loop over all available comma separated values.
If the templates is [Foo(bar == $param)] and the cell is [ 42 ] then the result will be [Foo(bar == 42)] If the template is [Foo(bar < $1, baz == $2)] and the cell is [42,43] then the result will be [Foo(bar > 42, baz ==43)]
ObjectType declarations in the row above. If they are present, the snippets are rendered as individual constraints on that ObjectType. If there are not any, they are simply rendered as is (with values substituted.) If a plain field (as in the example above) is entered, it will assume this means equality. If another operator is placed at the end of the snippet, the values will be interpolated at the end of the constraint, otherwise it will look for $param as outlined previously.

Person ObjectType declaration spans two spreadsheet columns. Thus, both constraints will appear as Person(age == ... , type == ...). As before, only the field names are present in the snippet, implying an equality test.

Person(age == "42").)


ObjectType row, an example being a pre-condition for the columns that follow.)

| Keyword | Description | Inclusion Status |
|---|---|---|
| RuleSet | The cell to the right of this contains the ruleset name. | One only (if left out, it will default). |
| Sequential |
The cell to the right of this can be true or false. If true, then salience is used to ensure the rules fire from the top down.
| Optional |
| Import | The cell to the right contains a comma separated list of Java classes to import. | Optional |
| RuleTable |
RuleTable indicates the start of a rule table definition. (The actual rule table starts on the next row down.) The rule table is read from left to right, top to bottom, until the next blank row is encountered.
| At least one, if there are more, then they are all added to the one ruleset |
| CONDITION | Indicates that this column will be for rule conditions. | At least one per rule table |
| ACTION | Indicates that this column will be for rule consequences. | At least one per rule table |
| PRIORITY | Indicates that this column's values will set the 'salience' values for the rule row. Over-rides the 'Sequential' flag. | Optional |
| DURATION | Indicates that this column's values will set the duration values for the rule row. | Optional |
| NAME | Indicates that this column's values will set the name for the rule generated from that row. | Optional |
| Functions | The cell immediately to the right can contain functions which can be used in the rule snippets. JBoss Rules supports functions defined in the DRL, allowing logic to be embedded in the rule, and changed without hard coding, use with care. Same syntax as regular DRL. | Optional |
| Variables | The cell immediately to the right can contain global declarations which JBoss Rules supports. This is a type, followed by a variable name. If multiple variables are needed, separate them with commas. | Optional |
| No-loop or Unloop | Placed in the header of a table, no-loop or unloop will both complete the same function of not allowing a rule (row) to loop. For this option to function correctly, there must be a value (true or false) in the cell for the option to take effect. If the cell is left blank then this option will not be set for the row. | Optional |
| XOR-GROUP | Cell values in this column mean that the rule row belongs to the given Activation group . An Activation group means that only one rule in the named group will fire (i.e., the first one to fire cancels the other rules' activations). | Optional |
| AGENDA-GROUP | Cell values in this column mean that the rule row belongs to the given Agenda group. (This is one way of controlling flow between groups of rules - see also "rule flow"). | Optional |
| RULEFLOW-GROUP | Cell values in this column mean that the rule row belongs to the given rule-flow group. | Optional |
| Worksheet | By default, the first worksheet is only looked at for decision tables. | N/A |
HEADER keyword, which affects the rules generated for each row. Note that the header name itself is the most significant thing in most cases. If no value appears in the cells below it, the attribute will not be applied to that specific row.

Import (which is comma-delimited), Variables (which is a global and also comma-delimited) and function block (which can be comprised of multiple functions and uses the normal DRL syntax. It can appear in the same column as the RuleSet keyword or be below all of the rule rows.)

drools-decisiontables module. Only one class is of relevance, this being SpreadsheetCompiler. This class takes spreadsheets in various formats and generates DRL rules which can then be used in the normal way.
SpreadsheetCompiler can also be used to generate partial rule files which can later be assembled into a complete rule package. (Use this to separate the technical and non-technical aspects of the rules.)
Wizard to generate a spreadsheet from a template and then edit it with an XLS- compatible spreadsheet application.


rule engine application programming interface (API.) Multiple rule engines can be run with this single API. Read this chapter to learn more about the capabilities of this API.
rule engine.
globals and DRL, DSL and XML files), use property maps. Note that, by doing this, non-portable functionality is, of course, introduced. Furthermore, as JSR94 does not provide a rule language, one is only reducing complexity by a small fraction when switching rule engines. There is very little to gain from the move. Therefore, whilst Red Hat provides support for JSR94 if one insists upon using it, programmers are strongly recommended to use the JBoss Rules API instead.
Administrative API, which is used to build and register RuleExecutionSets. The second part is the run-time session, used to execute those same RuleExecutionSets.
RuleServiceProviderManager manages the registration and retrieval of RuleExecutionSets. The JBoss Rules RuleServiceProvider implementation is automatically registered via a static block when the class is loaded using Class.forNamem. (This occurs in much the same way as it does for JDBC drivers.)
// RuleServiceProviderImpl is registered to "http://drools.org/"
// via a static initialization block
Class.forName("org.drools.jsr94.rules.RuleServiceProviderImpl");
// Get the rule service provider from the provider manager.
RuleServiceProvider ruleServiceProvider =
RuleServiceProviderManager.getRuleServiceProvider("http://drools.org/");
RuleServiceProvider provides access to the RuleRuntime and RuleAdministration APIs. The RuleAdministration provides an administration API for the management of RuleExecutionSets. This makes it possible to register a RuleExecutionSet that can then be retrieved via the RuleRuntime.
RuleExecutionSet, follow these steps:
RuleExecutionSet; the RuleAdministrator provides factory methods to return either an empty LocalRuleExecutionSetProvider or RuleExecutionSetProvider.
LocalRuleExecutionSetProvider to load a RuleExecutionSet from a local, non-serialisable source, such as a stream.
RuleExecutionSetProvider can be used to load RuleExecutionSets from serializable sources, like DOM elements or knowledge packages.
ruleAdministrator.getLocalRuleExecutionSetProvider( null ); and ruleAdministrator.getRuleExecutionSetProvider( null ); methods both accept null as a parameter. This is because the properties map for these methods is not currently being used.
// Get the RuleAdministration
RuleAdministrator ruleAdministrator = ruleServiceProvider.getRuleAdministrator();
LocalRuleExecutionSetProvider ruleExecutionSetProvider =
ruleAdministrator.getLocalRuleExecutionSetProvider( null );
// Create a Reader for the drl
URL drlUrl = new URL("http://mydomain.org/sources/myrules.drl");
Reader drlReader = new InputStreamReader( drlUrl.openStream() );
// Create the RuleExecutionSet for the drl
RuleExecutionSet ruleExecutionSet =
ruleExecutionSetProvider.createRuleExecutionSet( drlReader, null );
ruleExecutionSetProvider.createRuleExecutionSet( reader, null ) takes a null parameter for theproperties map; (however, it can actually be used to provide configuration information for the incoming source.) When null is passed, the default is used to load the input from a DRL file. The keys which one is allowed to use for a map are source and dsl. source takes drl or xml as its value. Simply set source to drl to load a DRL file and, likewise, set it to xml to load an XML file. (xml will ignore any dsl key/value settings.) The dsl key can use either a reader or a string (the contents of the domain-specific language) as a value.
// Get the RuleAdministration
RuleAdministration ruleAdministrator = ruleServiceProvider.getRuleAdministrator();
LocalRuleExecutionSetProvider ruleExecutionSetProvider =
ruleAdministrator.getLocalRuleExecutionSetProvider( null );
// Create a Reader for the drl
URL drlUrl = new URL("http://mydomain.org/sources/myrules.drl");
Reader drlReader = new InputStreamReader( drlUrl.openStream() );
// Create a Reader for the dsl and a put in the properties map
URL dslUrl = new URL("http://mydomain.org/sources/myrules.dsl");
Reader dslReader = new InputStreamReader( dslUrl.openStream() );
Map properties = new HashMap();
properties.put( "source", "drl" );
properties.put( "dsl", dslReader );
// Create the RuleExecutionSet for the drl and dsl
RuleExecutionSet ruleExecutionSet =
ruleExecutionSetProvider.createRuleExecutionSet( reader, properties );
RuleExecutionSet must be specified when it is registered. (There is also a field intended to allow one to "pass" properties; as this is currently unused, just pass null.)
// Register the RuleExecutionSet with the RuleAdministrator String uri = ruleExecutionSet.getName(); ruleAdministrator.registerRuleExecutionSet(uri, ruleExecutionSet, null);
run-time is obtained from the RuleServiceProvider. It is used to create stateful and stateless rule engine sessions.
RuleRuntime ruleRuntime = ruleServiceProvider.getRuleRuntime();
RuleRuntime, namely RuleRuntime.STATEFUL_SESSION_TYPE or RuleRuntime.STATELESS_SESSION_TYPE.
RuleExecutionSet to be used to instantiate the RuleSession.
properties map to null or use it to specify globals. (This is shown in the next section.)
createRuleSession(....) method returns a RuleSession instance. Cast this to either StatefulRuleSession or StatelessRuleSession.
(StatefulRuleSession) session =
ruleRuntime.createRuleSession( uri,
null,
RuleRuntime.STATEFUL_SESSION_TYPE );
session.addObject( new PurchaseOrder( "lots of cheese" ) );
session.executeRules();
StatelessRuleSession has a very simple API; use it to call executeRules(List list) (which passes a list of objects) and, optionally, a filter. The resulting objects will then be returned.
(StatelessRuleSession) session =
ruleRuntime.createRuleSession( uri,
null,
RuleRuntime.STATELESS_SESSION_TYPE );
List list = new ArrayList();
list.add( new PurchaseOrder( "even more cheese" ) );
List results = new ArrayList();
results = session.executeRules( list );
properties map to the RuleSession factory. Firstly, define the globals in either the DRL or the XML file, lest an exception be thrown.
DRL or the XML file. The value of this key is the instance to use in the execution. In the following example, the results are collected in a global java.util.List list:
java.util.List globalList = new java.util.ArrayList( );
java.util.Map map = new java.util.HashMap( );
map.put( "list", globalList );
//Open a stateless Session
StatelessRuleSession srs =
(StatelessRuleSession) runtime.createRuleSession( "SistersRules",
map,
RuleRuntime.STATELESS_SESSION_TYPE );
...
// Persons added to List
// call executeRules( ) giving a List of Objects as parameter
// There are rules which will put Objects in the List
// fetch the list from the map
List list = (java.util.List) map.get("list");
DRL file. Do so in this way:
package SistersRules;
import org.drools.jsr94.rules.Person;
global java.util.List list
rule FindSisters
when
$person1 : Person ( $name1:name )
$person2 : Person ( $name2:name )
eval( $person1.hasSister($person2) )
then
list.add($person1.getName() + " and " + $person2.getName() +" are sisters");
assert( $person1.getName() + " and " + $person2.getName() +" are sisters");
end

DRL syntax aware-editor that provides content assistance functionality (including an outline view)
DRL or BRL files.
Hello World rule:

JBoss Rules Builder which automatically re-builds and validates rules every time the resources they use change. When projects are created using the , this feature is enabled by default. One can also enable it manually for any other kind of project.
.rule files. These files are ignored by the builder but one will need to run them in a unit test to validate the rules they contain.
.drl file extension or by using the Wizard. Invoke the Wizard's menu by pressing Control+N or simply click on the toolbar's JBoss Rules icon.

src/rules and add suitably named subdirectories. Note that the package name is mandatory and is similar to that for a package in Java (in other words, it establishes a name-space in which to group related rules.)


.drl or .rule extension. Usually these contain a number of related rules but it is also possible to have each rule in an individual file, grouped by virtue of being in the same package namespace.
DRL files is stored in plain text format.
expander keyword, which tells the rule compiler to look for a .dsl file of that name, the purpose of which is to resolve the rule language. Even with the domain-specific language available, the rules are still stored as plain text, mirroring what can be seen onscreen. This makes management of rules much more simple when, for instance, comparing versions.