Rule templates

Rule templates in Drools are a way to generate DRL rules on-the-fly using template files and tabular data sources. By tabular data sources, we mean data that can be expressed in a table. Typical examples of this kind of data source are spreadsheets and tables in databases.

Probably one of the most common questions in Drools mailing lists and forums is how to generate rules from data that is stored outside our application. The typical case is data inside a database. One of the ways to deal with this scenario is by using rule templates.

Another great advantage of rule templates is that the data and the structure of the rule are completely decoupled. The same template can be used for different data sets and the same data set can be used for different templates. This provides great flexibility in comparison with decision tables.

Rule template structure

A Drools rule template is a text file containing special keywords to demarcate the different sections of the template and to define what the variables inside a template are and where should they be used.

As an example, let's analyze the template file called customer-classification-simple.drt that can be found in the source bundle of this chapter:

template header
minAge
maxAge
previousCategory
newCategory

package chapter07.ruletemplate.simple;

import org.drools.devguide.eshop.model.Customer;

template "classification-rules"

rule "Categorize Customers_@{row.rowNumber}"
no-loop true
when
    $c: Customer(age >= @{minAge}, age <= @{maxAge}, category == Customer.Category.@{previousCategory})
then
    modify ($c){ setCategory(Customer.Category.@{newCategory})};
end
end template

The first line of the template file contains the keyword that marks the beginning of a template: template header. The four lines below the template header are the names of the variables this template will use. In a template, the names of the variables are defined inline and are not part of the data set. Each of the columns in the data set used with this template will be named according to the corresponding variable name. The relation between a column and a variable is the position it has in the data set. In this case, the first column will be named minAge, the second maxAge, the third previousCategory, and the fourth newCategory. The white space that follows the variable definitions marks the end of that section.

After the template variable definitions section comes the standard rule header text containing the package definition and import statement. In the event we want to include globals, type declarations, or functions in our templates, this section is also the place to do it.

The keyword template indicates the beginning of a rule template. For each of the rows in the data set, an instance of this template will be generated. The name of the template must be unique in the entire template file.

What follows next is the rule template itself. Inside the rule template, variables previously defined can be accessed by using the syntax @{var_name}. For each row in the data set, the variables will be set and their placeholders substituted in the template. If any of the variables used in a rule template are empty, the entire template is omitted. A single template section can contain multiple rule definitions.

There is a special variable we can use in our templates called @{row.rowNumber}. This variable will contain the number of the row being processed and is useful, among other things, to avoid duplicated names in the generated rules.

Tip

One of the advantages of rule templates over a decision table is that the variables in a rule template can be used anywhere in a rule: as the class name of a pattern, an operator, a property name, and so on.

Another advantage of rule templates is that variables can be used in any order and can be used multiple times if needed.

To mark the end of a rule template, the keyword end template must be used.

Now that we understand the basics of the structure of a rule template, let's see how they are processed along with a data set in order to generate DRL rules.

Working with rule templates

Rule template-related classes are defined inside an individual project: drools-templates. This project contains the necessary classes to parse a template and create concrete DRL out of a data set. Four types of data source are already supported out of the box: spreadsheets, arrays, objects, and SQL result sets.

For spreadsheet-based templates, Drools supports their declarative definition in the kmodule.xml file using the special <ruleTemplate> configuration element of a KIE Base:

<kbase name="template-simple-KBase" packages="chapter07.template-dtable">
        <ruleTemplate
            dtable="chapter07/template-dtable/template-data.xls"
            template="chapter07/template-dtable/customer-classification-simple.drt"
            row="3" col="2"/>
        <ksession name="templateSimpleKsession"/>
</kbase>

The previous code snippet shows how a template named customer-classification-simple.drt with a data source file named template-data.xls is included in the template-simple-KBase KIE Base. This code snippet is part of the source bundle associated with this chapter.

The examples we will cover in the rest of this section will all use a programmatic way to process a template, create DRL out of it, and then, with the generated DRL, create a KIE Base.

All the tests associated with this section (look for the RuleTemplatesTest class) use a helper method to create a KIE Session from a String containing DRL code. To avoid repetition of this method in the following sections of this chapter, let's analyze this method here:

    private KieSession createKieSessionFromDRL(String drl){
        KieHelper kieHelper = new KieHelper();
        kieHelper.addContent(drl, ResourceType.DRL);
        
        Results results = kieHelper.verify();
        
        if (results.hasMessages(Message.Level.WARNING, Message.Level.ERROR)){
            List<Message> messages = results.getMessages(Message.Level.WARNING, Message.Level.ERROR);
            for (Message message : messages) {
                System.out.println("Error: "+message.getText());
            }
            
            throw new IllegalStateException("Compilation errors were found. Check the logs.");
        }
        
        return kieHelper.build().newKieSession();
    }

The implementation of the method is straightforward. It takes a String containing DRL syntax as a parameter and it uses the KieHelper utility class to compile it and create a KIE Base from it. This method also checks for errors or warnings during the DRL compilation. Once a KIE Base is built, a new KIE Session is returned.

Note

The KieHelper utility class is not part of Drools' public API. This class provides some convenient methods to avoid most of the boilerplate code required to get a Kie Base or KIE Session up and running. Given that this class is not part of the kie-api artifact, it may suffer from backwards-incompatible changes in the future.

Let's now take a detailed look at the four data source types supported by Drools rule templates.

Spreadsheet data source

The first type of data source we are going to cover resembles, in some ways, a decision table. When working with rule templates, the data we want to use to generate the concrete rules can be stored in a spreadsheet file. We have already discussed the benefits of using spreadsheets, especially for non-technical users.

As the input of our rule template, a spreadsheet like the following one could be used:

Spreadsheet data source

The preceding spreadsheet contains only the necessary data for the template, plus some useful headers for the person who has to edit this table. The spreadsheet does not contain any information regarding the template that has to be used nor the structure of the rules that need to be generated.

In order to convert this spreadsheet into DRL, we are going to use the template file we have previously introduced (customer-classification-simple.drt) and the createKieSessionFromDRL()helper function:

        InputStream template = RuleTemplatesTest.class.getResourceAsStream("/chapter07/template-dtable/customer-classification-simple.drt");
        InputStream data = RuleTemplatesTest.class.getResourceAsStream("/chapter07/template-dtable/template-data.xls");        
        ExternalSpreadsheetCompiler converter = new ExternalSpreadsheetCompiler();
        String drl = converter.compile(data, template, 3, 2);        
        KieSession ksession = this.createKieSessionFromDRL(drl);

The first two lines of the code are getting the template and data files as InputStream instances. The fourth line is using an instance of the helper class ExternalSpreadsheetCompiler to convert the template file and the data in the spreadsheet into DRL. The compile() method in ExternalSpreadsheetCompiler takes four arguments: the data, the template, and the row and column inside the spreadsheet where the data starts. In this case, the data starts in row 3 and column 2 (B).

Array data source

Another way to provide the data to a template is by using a two-dimensional array of Strings. In this case, the first dimension of the array is used as the row, and the second dimension as the column:

        InputStream template = RuleTemplatesTest.class.getResourceAsStream("/chapter07/template-dtable/customer-classification-simple.drt");
        
        DataProvider dataProvider = new ArrayDataProvider(new String[][]{
            new String[]{"18", "21", "NA", "NA"},
            new String[]{"22", "30", "NA", "BRONZE"},
            new String[]{"31", "40", "NA", "SILVER"},
            new String[]{"41", "150", "NA", "GOLD"},
        });
        
        DataProviderCompiler converter = new DataProviderCompiler();
        String drl = converter.compile(dataProvider, template);
                
        KieSession ksession = this.createKieSessionFromDRL(drl);

The preceding code shows how an instance of the DataProviderCompiler class can be used to process a template using a two-dimensional array of Strings as the data source. The data is encapsulated inside an ArrayDataProvider instance. The ArrayDataProvider class implements the DataProvider interface. If you have a special, custom source of information that needs to be fed into your rule template, you could implement your own DataProvider and connect it with the template using a DataProviderCompiler.

Objects data source

A more object-oriented friendly way to present the data to a template is by using objects as the model. Instead of a two-dimensional array of Strings, we could use a collection of objects to hold the data required by our templates. When objects are used as the data source of a template, the name of the variables defined in the template must match the name of the attributes in our model class:

As an example, let's create a class to contain the data for our classification scenario:

public class ClassificationTemplateModel {
    
    private int minAge;
    private int maxAge;
    private Customer.Category previousCategory;
    private Customer.Category newCategory;

 //constructors, getters and setters
}

An instance of this class will correspond to a rule after the template is processed. Note that the names of the attributes of this class correspond to the name of the variables in the template header:

        InputStream template = RuleTemplatesTest.class.getResourceAsStream("/chapter07/template-dtable/customer-classification-simple.drt");
        
        List<ClassificationTemplateModel> data = new ArrayList<>();
        
        data.add(new ClassificationTemplateModel(18, 21, Customer.Category.NA, Customer.Category.NA));
        data.add(new ClassificationTemplateModel(22, 30, Customer.Category.NA, Customer.Category.BRONZE));
        data.add(new ClassificationTemplateModel(31, 40, Customer.Category.NA, Customer.Category.SILVER));
        data.add(new ClassificationTemplateModel(41, 150, Customer.Category.NA, Customer.Category.GOLD));
        
        ObjectDataCompiler converter = new ObjectDataCompiler();
        String drl = converter.compile(data, template);
                
        KieSession ksession = this.createKieSessionFromDRL(drl);

The preceding code shows how a List of ClassificationTemplateModel objects is used as the data source for the template. In this case, an instance of the ObjectDataCompiler class is used to process the template and the list of objects.

SQL result set data source

The last option we are going to be covering to process a template file is using SQL result sets as the data source. By SQL result sets we mean the java.sql.ResultSet class. A ResultSet class can be obtained in multiple ways using—for example, JDBC. Even if we could easily convert a ResultSet into a two-dimensional array, or a collection of objects, and use one of the previously introduced ways of processing a template, Drools already provides us with a way to deal directly with ResultSet instances.

Let's assume we have the following table, called ClassificationRules, inside a database:

id

minAge

maxAge

previousCategory

newCategory

1

18

21

NA

NA

2

22

30

NA

BRONZE

3

31

40

NA

SILVER

4

41

150

NA

GOLD

If we want to use the information in that table to generate DRL using a rule template, we can use the following code:

        Connection conn = //get a connection to our DB
        Statement sta = conn.createStatement();
        ResultSet rs = sta.executeQuery("SELECT minAge, maxAge, previousCategory, newCategory " +
                                        " FROM ClassificationRules");

        final ResultSetGenerator converter = new ResultSetGenerator();
        final String drl = converter.compile(rs, template);

The previous example uses standard JDBC classes, such as Connection, Statement, and ResultSet. This code executes a query against the ClassificationRules table and gets its result as a ResultSet. Then, using Drools' ResultSetGenerator class, the ResultSet and the template are converted into DRL.

It is important to notice that, even though Drools Templates comes with a handy set of functions, we can still use any other template engine, such as Velocity (https://velocity.apache.org/), or StringTemplate (http://www.stringtemplate.org/). Of course, we will not have any of the DataProvider classes, but remember that the ultimate goal of these classes is the generation of DRL code. And, after all, DRL is just plain text; so we can use whatever technique or framework we want.

Let's now move to the last topic of this chapter, which will teach us how to integrate Drools with PMML resources to allow non-rule based knowledge assets to be used inside the rule engine.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.179.59