Drools – Tutorial on Writing DSL Template

Few months ago I wrote a post that describes an example that uses source DRL in conjunction with DSL template. In the current post, I want to describe and show with few examples how to write DSL template – whats allowed and whats not.

Now, having said that, I want to say that I am not planning to repeat the whole JBoss Rules manual – the Drools team done a great job. I just want to share from my personal experience, what I have come across while working with Drools when writing DSLs.

There are few upcoming changes coming in the future release of Drools (version 5) in terms of DSL. For example DSL will become more powerful in terms of regular expressions – it will be possible to include regexp in the DSL tokens. Edson Tirelli posted an article written by Matt Geis on Drools Blog, that talks about it and gives few examples.

Since I am working with Drools 4.0.7 these days, I will describe in the current post how to write DSLs for Drools 4.0.7.

Within a scope of Drools, DSL’s job to map (expand) DRL expressions written in natural language into underlying programming code.

When writing DSL there several basic DO’s and DONT’s you should be aware of:

  1. Don’t put closing semi-column at the end of DSL expression line:
    [when]Message status is {1} = m : Message(status == {1});
  2. Avoid different token names on the left hand side (before the “=”) and right hand side (after the “=”) of the DSL expansion:
    [when]Message status is {3} = m:Message(status == {1})
    [then]Log “{message}” = System.out.println(“{bobo}”);
  3. DSL expansion should be on one line. You can even have several expressions on the same line, but you cannot break the line. By putting expression on a new line will cause an exception during parsing:
    [when]Message id is {id} = m:Message(id == {id})
    [then]LogUpdate “{2}” = System.out.println(“{2}”);
    update(m);
    [when]Message status is {s} = m:Message(status == {s})
    [then]Log “{2}” =
    System.out.println(“{2}”);
  4. Avoid having DSLs with the same name. It can create a confusion. Remember that DSL is parsed from top to bottom, so when two DSLs with the same name exist, the top one will apply during expansion since it will be matched first:
    [then]SetStatus {s} = System.out.println({s});
    [then]SetStatus {s} = m.setStatus({s});
  5. If token has quotes on the left hand side of the DSL, then the token must have quotes on the right hand side of the DSL:
    [then]SetMessage {msg} = m.setStatus({msg});
  6. There is no room for typos:rule “set-hello”
    when
    True
    then
    SetMessage “Hello”
    end[then]SetMessag “{msg}” = m.setStatus(“{msg}”);
  7. Token names can be literal or numeric characters, or both:
    [then]Print1 “{1}” = System.out.println(“{1}”);
    [then]Print2 “{msg}” = System.out.println(“{msg}”);
    [then]Print3 “{23msg}” = System.out.println(“{23msg}”);
  8. If token on the left hand side has no quotes, and expression/function on the right hand side can accept parameter of type String, then the token can be placed between quotes on the right hand side:
    [then]PrintInteger {1} = System.out.println({1});
    [then]PrintInteger2 {1} = printingFunction({1});
  9. If you want to use declared variable in right hand side, make sure it is declared on the left hand side of the rule. Remember, DSL template is ONLY a template, it is a not a place to declare variables, declaration occurs only in DRL, DSL contains the expansion. Keep in mind that declaration and usage of the variable is per-rule basis:rule “set-hello”
    when
    Message
    then
    SetMessage “Hello”
    end[when]Message = m : Message() //declare and initialize ‘m’
    [then]SetMessage “{msg}” = m.setStatus(“{msg}”);
  10. White spaces before the “=” and after are allowed when writing a DSL line:
    [then]SetMessage “{msg}” = m.setStatus(“{msg}”);
  11. If you are using a function in the right hand side of the DSL of the expansion, then make sure you provide necessary imports in DRL source for all the objects the function is using. For example if your function gets a proxy to a JMS Queue from JBoss, then you have to include imports for all the objects associated with this operation, like you normally would do it in normal Java class. Also the function itself must be present in source DRL.
  12. Left hand side of the rule, can evaluate boolean function instead of expression:rule “CheeseType”
    when
    Cheese is of type “silton”
    then
    DoSomething
    endfunction boolean someEvaluatingFunction(Cheese cheese,String type) {
    if (cheese.getType().equals(type)) return true;
    else return false;
    }

    [when]Cheese is of type “{1}” = chz:Cheese() eval(someEvaluatingFunction(chz, “{1}”))
    [then]DoSomething = System.out.println(“Something”);

If you interested in additional examples of the DRL+DSL, you can have a look at another short example that I wrote sometime ago. Also, please dont forget to have a look at the JBoss Rules manual that i mentioned earlier.

Cheers

Drools – Stop Executing Current Agenda Group and All Rules

Sometimes, depends on your business rules in your application, there is a need to stop current agenda group or all rules from continuing to execute. It wont help setting a focus to another agenda group, since previous agenda will still remain in a stack. So in this post I want to show how to prevent rules in a particular agenda group from continuing to execute by clearing the agenda and also how to stop all rules totally.

Please note, in this example I am using agenda-groups to drive execution flow.

I created a short DRL file and a DSL template to demonstrate how clearing agenda and stopping of all rules can be achieved.

The DRL file:

expander template.dsl;

/*****************************************
 First default rule to kick in
******************************************/
rule "baserule"
salience 100000
auto-focus true
agenda-group "first-group"
when
      True
then

end

/*****************************************
 Second rule to kick in
******************************************/
rule "1"
salience 1000
agenda-group "first-group"
when
      True
then
      Goto agenda "second-group"
end

/*****************************************
 Third rule to kick in
******************************************/
rule "2"
salience 1000
agenda-group "second-group"
when
      True
then
      Do something
end

/*****************************************
 Fourth rule to kick in

 Current rule stops agenda "second-group"
 from continuing to execute, and the
 focus will be returned back to
 agenda "first-group"
******************************************/
rule "3"
salience 900
agenda-group "second-group"
when
      True
then
      Stop agenda "second-group" //clear current agenda
end

/*****************************************
 This rule never kicks in, since agenda
 "second-group" was cleared by rule "3"
******************************************/
rule "4"
salience 800
agenda-group "second-group"
when
      True
then
      Do something
end

/*****************************************
 Fifth rule to kick in

 After clearing agenda "second-group", the
 focus will be returned here
******************************************/
rule "5"
salience 900
agenda-group "first-group"
when
      True
then
      Do something
end

/*****************************************
 Sixth rule to kick in
 Current rule stops execution of all rules
******************************************/
rule "6"
salience 800
agenda-group "first-group"
when
      True
then
      Stop all
end

/*****************************************
 This rule never kicks in, since all rules
 execution was stopped by rule "6"
******************************************/
rule "7"
salience 700
agenda-group "first-group"
when
      True
then
      Do something
end

The DSL template:

[when]True=eval(true)
[then]Do something=
      System.out.println("Doing something");
[then]Stop all=drools.halt();
[then]Goto agenda "{agenda}"=
      drools.setFocus( "{agenda}" );
[then]Stop agenda "{agenda}"=
      drools.getWorkingMemory().
		clearAgendaGroup("{agenda}");

Basically what happens is: once the execution reaches rule “3″, I am calling for clearAgendaGroup() method to clear agenda, hence to remove it from the stack. As a result of the clearing, rule “4″ will never be executed.

When focus is returned back to agenda “first-group”, it lands on rule “5″. When the execution reaches rule “6″, I am calling for drools.halt() method to stop execution of all rules.

Keep in mind that in Drools 3 there is no halt() method. It came with Drools 4. If you want to achieve the above result of stopping all rules in Drools 3, you can call for drools.clearAgenda();

You noticed that I am using “drools” object and calling some methods on that object in my DSL template. Well “drools” object is actually KnowledgeHelper class, which is part of Drools library. It provides several APIs, for example method halt(), setFocus() and getWorkingMemory().

Keep in mind that I did not actually tested these particular DRL and DSL, but this example is based on real scenario that I have tested.

Comments/questions/flames are welcomed

Drools – Working with Stateless Session

Drools (now it is also called JBoss Rules) is an amazing open source framework which allows you to create business rules management system for your application. I got introduced to Drools while working on a project at my current company.

It is very easy to use and implement it and it is very efficient. For example instead of having dozens of if-else statements for some application business rules, you can use Drools to create a rule engine with your defined rules and pass your objects through the rule engine.

For example, in your application that deals with student objects, you can create a rule that checks whether the student has paid his fees for the next semester, if not – send him/her reminder email… etc..

In this example i want to show how to work with Stateless drools session to retrieve results from the global variable. I know that at this point its a bit not clear, so i will try to explain as I go… or you can simply visit their website, the link is under “Useful Links” section on the right hand side…

In addition to that you can always join their IRC channel #drools, the drools team is very helpful and i owe my special thanks to a fellas name mic_hat and conan there, that had a lot of patience for me ;)

For my example i prepared a simple POJO, DRL and DSL files and a test client.
DRL is the file that contains my rules. DSL is the expandable template for DRL.

Drools allow you to write your rules using plain human language in DRL, and then in DSL template you can specify to what programming code the human sentence corresponds to. The following explains what I mean:

My DRL file with 2 rules in it:

package com.test.drools.rules;

expander mydsl.dsl;

import com.test.drools.entities.Pojo;

global java.util.List list;

rule "1"
salience 1000
auto-focus true

when
      The blog name is "Java Beans dot Asia"
then
      Log "The blog name was matched"
end

rule "2"
salience 900

when
      This post was created in "May"
then
      Log "The blog post month was matched"
end

“expander mydsl.dsl” – file name of my DSL template.
“global java.util.List list” – a global variable, which is the type of List. Global variable you can use for storing some results, log messages and even objects.
“salience” – the priority which rules should be executed first.
“auto-focus” – the rule that has auto-focus will get executed first, basically the starting point of execution.

My DSL template file for my DRL:

[condition][]The blog name is "{name}"= poj : Pojo( blogName == "{name}")
[condition][]This post was created in "{month}"= poj : Pojo( postMonth == "{month}")
[consequence][]Log "{message}"= list.add(new String("{message}"));

As you can see “This post was created in “arg”" will expands into “poj : Pojo( postMonth == “{month}”)”, where the value of “arg” will be compared to the value of postMonth variable in my POJO.

Keep in mind that you do not have to use DSL template, you can use only DRL file if you want to and have your source code there. Using the template makes your rules very readable.

My POJO:

package com.test.drools.entities;

import java.io.Serializable;

public class Pojo implements Serializable {

	private String blogName;
	private String postMonth;

	public Pojo() {

	}

	public String getBlogName() {
		return blogName;
	}

	public void setBlogName(String blogName) {
		this.blogName = blogName;
	}

	public String getPostMonth() {
		return postMonth;
	}

	public void setPostMonth(String postMonth) {
		this.postMonth = postMonth;
	}
}

My client:

package com.test.drools.client;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.drools.RuleBase;
import org.drools.RuleBaseFactory;
import org.drools.StatelessSession;
import org.drools.StatelessSessionResult;
import org.drools.base.CopyIdentifiersGlobalExporter;
import org.drools.compiler.DroolsError;
import org.drools.compiler.DroolsParserException;
import org.drools.compiler.PackageBuilder;
import org.drools.compiler.PackageBuilderErrors;

import com.test.drools.entities.Pojo;

public class Client {

//path to the DRL file inside my JAR
final static String DRL_URL =
		"/com/test/drools/rules/mydrl.drl";
//path to the DSL file inside my JAR
final static String DSL_URL =
		"/com/test/drools/rules/mydsl.dsl";

public static void main(String[] args) {

   //Instantiate and initialize the POJO.
   Pojo p1 = new Pojo();
   p1.setBlogName("Java Beans dot Asia");
   p1.setPostMonth("May");

   //Calling for private method to compile a RuleBase
   RuleBase ruleBase = getRuleBase();

   //Instantiating StatelessSession
   StatelessSession session =
			ruleBase.newStatelessSession();

   //Setting global variable:
   //the name 'list' is the same name mentioned in DRL:
   //global java.util.List list;
   session.setGlobal("list", list);

   //specifying the global name that should be exported
   session.setGlobalExporter(
   new CopyIdentifiersGlobalExporter(
				new String[]{"list"} ) );

   //executeWithResults() - stores execution results in
   //StatelessSessionResult object. That objects will
   //contain our global variable with results, that we
   //can use after the execution of stateless
   //session is finished.
   StatelessSessionResult result =
			session.executeWithResults(p1);

   //get global variable and cast back to
   //the type of List
   List retrievedList = (List) result.getGlobal("list");

   if (retrievedList != null &&
			retrievedList.size() > 0) {

   for (Iterator i = retrievedList.iterator();
					i.hasNext();) {
	System.out.println((String) i.next());
   }
}

}

private static RuleBase getRuleBase() {

//Create a new package builder
PackageBuilder builder = new PackageBuilder();

try {

//call for private method to get the DRL
Reader drl = getSourceDrl();

//call for private method to get the DSL
Reader dsl = getDsl();

//Add rule package to the builder using drl and
//dsl Reader objects
builder.addPackageFromDrl(drl, dsl);

//Check whether our DRL and DSL files had any
//errors when trying to create a rule package.
//If DRL and/or DSL had any errors we wont be able
//to create a rule package and a new RuleBase.
PackageBuilderErrors errors = builder.getErrors();

DroolsError[] error = errors.getErrors();

if (error.length > 0) {
for (DroolsError err : error) {
System.out.println("Errors are: " + err.getMessage());
}
}

} catch (DroolsParserException e1) {
e1.printStackTrace();
} catch (IOException e1) {
e1.printStackTrace();
}

//Get new RuleBase object. RuleBase is where
//we will get Stateless session object from.
RuleBase ruleBase = RuleBaseFactory.newRuleBase();

try {
//Add package with our rules to the RuleBase.
//This is when the RuleBase is actually compiled
ruleBase.addPackage(builder.getPackage());
} catch (Exception e1) {
e1.printStackTrace();
}

return ruleBase;
}

private static Reader getDsl()
			throws IOException {
return new InputStreamReader(Client.class
.getResourceAsStream(DSL_URL));
}

private static Reader getSourceDrl()
			throws IOException {
return new InputStreamReader(Client.class
.getResourceAsStream(DRL_URL));
}

}

I will try to give now a brief explanation what is actually happening:
When session.executeWithResults(p1); is executes, rule engine will apply the rules on a p1 POJO object. If rules will be matched, then the result will be stored in the global variable.

For example:
If value of “blogName” variable inside my p1 POJO object will be equal to “Java Beans dot Asia”, then the rule#1 in my DRL will be matched, and the result “The blog name was matched” will be stored in my global List.

The final output of the program will be as follows:

"The blog name was matched"
"The blog post month was matched"

This example was very simple, I had only two rules where i did comparison of String literals. But Drools definitely has the capability to create a friendly business rule system with thousands of rules if needed, while staying user friendly for both developers and business clients. I think its worth while checking it out :)

I’ve included a source code and jUnit test case for this tutorial if you want to have a look at it and try it your self.

drools – working with stateless session sourcecode