Copyright © 2004 Chaur G. Wu. All rights reserved. 
 Eclipse Corner Article

 

Modeling Rule-Based Systems with EMF

Summary

There are examples of meta-models defined in ECore for modeling objects and relational data. However, not much has been said about how to model rules. This article will define a meta-model in ECore for modeling rule-based systems. We will then use the meta-model to model the solution of a logical problem. Then we will compose some JET templates and generate code from the model, run the generated code through a rule engine and see that the logical problem is correctly solved.

By Chaur G. Wu, Independent Consultant (cha_urwu@hotmail.com)
November 30, 2004


Before we look at this article’s example, let’s set the stage by going through some background information and make clear some terms that we will use throughout the article. In this part, we will give very brief introduction of rule-based systems and meta-modeling.

Rule-Based Systems

So what do we mean by rule-based? In the evolution course of computing, a field called artificial intelligence was developed in the 70s and 80s with an aim to make computers reason like human beings. Rule-based programming paradigm emerged at that time as ways to implement systems that appear to think and reason like human beings. Examples of rule-based systems are expert systems that have the knowledge of a doctor or a tax advisor and can answer complex questions people would normally ask those professionals.

The idea of rule-based programming is to represent a domain expert’s knowledge in a form called rules. That’s why it’s called rule-based. Besides rules, another important ingredient in a rule-based system is facts. Here’s an example. Say John is a weather reporter who gives advice to people on TV channels based on weather conditions. Here’s John’s knowledge about weather:

  1. If it’s a rainy day, advise people to bring umbrellas when they go out.
  2. If it’s a rainy day, the road would be slippery because of the rain and hence warn people to drive more carefully.

How do we represent the knowledge in a rule-based system? We represent it as rules and facts like this:

  Rule 1: If it’s a rainy day, advise people to bring umbrellas when they go out. 
  Rule 2: If the road is slippery, warn people to drive more carefully. 
  Rule 3: If it’s a rainy day, road is slippery.
  Fact 1: It’s a rainy day. 
  Fact 2: Road is slippery.

Here’s how our imaginary expert system mimics the reasoning capability of weather reporter John. If by looking out of the window, we see it’s raining, we tell our expert system that it’s raining by asserting fact number 1. Immediately the expert system tries to match the asserted fact with its three rules. Rules 1 and 3 are fired because the asserted fact satisfies their conditions (the if clause). When rule one is fired (a more accurate term is activated), the system advises people to bring umbrella as if it were a real weather reporter. Firing rule 3 asserts fact 2. And because fact 2 satisfies the conditions of rule 2, rule 2 is fired and the system advises people to drive more carefully. All these chain reactions happen in sequence as the result of asserting fact number 1.

Although the example is nowhere close to the complexity of a practical rule-based system, it shows the following key points:

  1. A rule-based system consists mainly of three things: facts, rules and an engine that acts on them.
  2. Rules represent knowledge and facts represent data. A rule-based system solves problems by applying rules on facts (i.e. matching facts with rules’ if clauses). 
  3. A rule consists of two parts: conditions (if clauses) and actions. 
  4. The action part of a rule might assert new facts that fire other rules.

Among the most popular rule engines, JESS (Java Expert System Shell) is probably of the most interest to Java developers. It is the reference implementation of JSR 094 Java Rule Engine API and it has plug-ins to support development of rule systems in Eclipse. Therefore, the rule code we will see in this article will be in JESS's syntax. Before we leave this section, it’s helpful to introduce JESS and take a glimpse of its programming syntax. JESS is software that interprets rules and facts expressed in its programming language. Just as Java is a language for expressing objects and Java compiler is software that interprets Java code, JESS has a language for expressing rules and facts and a compiler to interpret the code. JESS is developed in Java by Ernest Friedman-Hill at Sandia National Laboratories. Here’s what the rules and facts in our weather example look like in JESS code.

(defrule rule-1 (rainy-day) = > (advise-people bring-umbrella))
(defrule rule-2 (road-slippery) => (advise-people drive-carefully)) 
(defrule rule-3 (rainy-day) => (assert road-slippery))
(road-slippery)
(rainy-day)

The last two lines in the list above are the two facts. The first three lines are the three rules. Rules in JESS take this form: (defrule rule-name conditions => actions) Rules are defined using the defrule construct. rule-1, rule-2, and rule-3 are the names of the rules respectively. rule-1 has a condition: (rainy-day). In this case, (rainy-day) is also called a pattern. It is so called because the rule engine treats it as a pattern for matching facts. The pattern (rainy-day) in rule-1 will match only one fact: the (rainy-day) fact in the last line of the code list. When it's matched, the action (advise-people bring-umbrella) of rule-1 will be executed.

There are a lot more to the syntax of a rule language like JESS. We will only explain those necessary for following along this article. For more details, you can refer to the documentation that comes with JESS.

Model, Meta-model, Meta-metamodel

Sometimes it’s helpful to the discussion if we categorize models into different levels. We call a model a meta-model if it is used to define models. We call a model a meta-metamodel if it is used to define meta-models.

For example, ECore is a model. It can be a meta-model or a meta-metamodel depending on how we use it. When we use ECore to define models of a purchase order system, it plays the role of a meta-model. The Rule Meta Model we will develop later in this article is another example of meta-model. It is a meta-model because we will use it to define a model for a logical problem.

The Rule Meta Model we will develop later is defined in ECore. In this sense, ECore is a meta-metamodel because it is used to define a meta-model. Another example of ECore as a meta-metamodel is EMF’s Gen Model. EMF’s Gen Model is a meta-model that defines models concerning how code is generated. ECore is the meta-metamodel that defines Gen Model.

When we say model A defines model B, in a practical sense, we mean that all model elements in B are instances of model elements in A. We will see this in effect later when we reach the article’s example. For the purpose of this article, the important facts to note are:

  1. Categorization of meta levels is not absolute.
  2. We will use ECore as a meta-metamodel to define the Rule Meta Model.
  3. Model elements in the Rule Meta Model will be instances of model elements in ECore.
  4. We will use the Rule Meta Model to define a model for a logical problem. Elements in the model will be instances of model elements in the Rule Meta Model.
  5. Meta-metamodels are also referred to as M3 models; meta-models as M2 models; models like a bank application model as M1 models.

Problem Statement

The logical problem we will solve with this article’s Rule Meta Model is this:

There are four boxes, labeled from 1 to 4. We have four balls in four colors: red, blue, green and yellow. Our task is to put one ball in each box according to the following three constraints.

  1. If red ball is in box 1, blue ball must be in box 3.
  2. If yellow ball is not in box 2, green ball must be in box 3.
  3. Green ball cannot be in box 4 and Red ball cannot be in box 2.

Set up Environment and Run the Example Code

The solution to the problem is in RuleMetaModel.zip and BallPlacementRuleModel.zip that come with this article. The code in the zip files requires the following software.

Java Runtime or SDK (The one I use is Java SDK 1.4.2 from Sun Microsystems) 
Eclipse (The version I use is Eclipse SDK 3.0) 
EMF (The version I use is EMF SDK 2.0.1 RC2)
JESS 7.0a1

Installing JESS is simple. You can download JESS from http://herzberg.ca.sandia.gov/jess/download.shtml. Then unzip the downloaded file to a folder, add jess.jar to your java classpath and you are ready to go. JESS is not free software. After clicking the link above, you’ll be asked to enter your name, company and email. After that, you’ll be taken to the page where you can download various versions of JESS. The one to download for this article is trial version 7.01a1. This version of JESS comes with some Eclipse plug-ins for editing JESS code in the Eclipse IDE. Those plug-ins are not needed for running the code of this article. Installing them is optional.

To install and run the example for this article, unzip RuleMetaModel.zip into your plugins/ subdirectory. This zip file contains three plug-ins. They are the model, edit, editor plug-ins EMF generates for our Rule Meta Model. The other zip file, BallPlacementRuleModel.zip, contains two files: BallPlacement.rulemetamodel and BallPlacement.clp. BallPlacement.rulemetamodel is the model that models our logical problem. It is defined using Rule Meta Model. The other file BallPlacement.clp is JESS code generated from BallPlacement.rulemetamodel. Below is a pictorial view of all the components in the two zip files and how they relate to EMF and rule engine.

Figure 1. Overall picture of the example code.

We will explain each component as we go along. For now, since we already have JESS rule engine set up, let's run BallPlacement.clp through the JESS rule engine (the yellow part in Figure 1). Assume that you unzipped the two files in BallPlacementRuleModel.zip to C:\ and that jess.jar is in your java classpath. Here is the command to run BallPlacement.clp through the JESS rule engine:

java jess.Main C:\BallPlacement.clp

The result you will see in the command console will look like the screenshot below. Dumping the result to a console window like this is not pretty but it serves our purpose just fine. The last four facts listed in the screenshot are solutions to the logical problem. That is, out of the 24 possible placements of the balls in the boxes, only four placements satisfy the three constraints posed by our problem.  

BallPlacement.clp

Before we look more closely at the Rule Meta Model and Ball Placement Model, let’s see what constitutes a rule language by taking a look at BallPlacement.clp first. This knowledge will help us make sense of the meta-model and model later. BallPlacement.clp defines two fact templates:  placement and  ball

(deftemplate placement 
	(slot box_one)
	(slot box_two)
	(slot box_three)
	(slot box_four)) 
	
(deftemplate ball (slot color))

A fact template as its name suggests is a template of facts and is defined by the deftemplate construct in JESS. Earlier we wrote the weather fact like this: (rainy-day). If we want to specify temperature in addition to weather, we’ll probably write a fact like this: (rainy-day 50F). Facts like these are called ordered facts because they require their elements to be in a specific order. (rainy-day 50F) is a different fact from (50F rainy-day) because the elements are in a different order. Sometimes it’s desirable to have some structure in the way we state facts so that we can express elements in a fact regardless of their positions. This is where fact templates come into play. With fact templates, we can state facts like this: (weather-report (weather rainy-day) (temperature 50F)). We say that the weather-report fact has two slots: weather and temperature. Slot weather has a value of rainy-day and slot temperature has a value of 50F. It doesn’t matter if you put the temperature slot before or after the weather slot. Facts like this are called unordered facts because the order of their elements is not relevant.

In our example, the placement fact template defines four slots, one for each box. The ball fact template defines one slot: color. With these two fact templates, we can state the fact that "green ball is in box one" in JESS language like this: (placement (box_one Green)). Besides fact templates, BallPlacement.clp has a few rules. Here's the rule that describes the first constraint about ball placement.

(defrule placement_constraint1 
	?placement <-  (placement (box_one ?box_one) (box_two ?box_two) (box_three ?box_three) (box_four ?box_four))  
	(and (placement (box_one ?box_one&Red) (box_two ?box_two) (box_three ?box_three) (box_four ?box_four))  
	(placement (box_one ?box_one) (box_two ?box_two) (box_three ?box_three&~Blue) (box_four ?box_four))) 
=> 
 
	(retract ?placement))

We have seen rules in the weather report metaphor. We know placement_constraint1 is the name of the rule.   are the conditions of the rule and  is the action. Condition  assigns what looks like a pattern to the ?placement variable. In fact, what actually gets assigned to the ?placement variable is a fact that matches the pattern. In JESS, variable names start with ? . ?box_one, ?box_two in the code list above are all variables. Variables are not typed. You can assign strings, integers, facts and values of other data types to them. Assigning a value to a variable is usually done through the bind function like this:

(bind ?variable "some string value")

However, using bind function to assign a value to a variable is an action not a condition. That's why in , instead of using the bind function in the condition part of a rule, we use a special form called pattern binding <- to assign a fact to the ?placement variable. 

Condition  has two fact matching patterns enclosed in a logical and. The logical and is called a conditional element in a rule language. Don't worry if the two conditions still look cryptic. Let's mimic the reasoning process of a rule engine and you will see how the conditions function. Let's see what happens if we put the following fact in the rule engine:

(placement (box_one Red) (box_two Blue) (box_three Green) (box_four Yellow))

The rule engine tries to see if the fact satisfies the two conditions of placement_constraint1. It first tries to match the fact with the pattern in . The pattern in  has one variable in each of the four slots of the placement fact. That means the pattern matches any value in those four slots. So the fact satisfies the first condition and Red is assigned to ?box_one, Blue to ?box_two, Green to ?box_three and Yellow to ?box_four.

But this is not enough to trigger the execution of placement_constraint1's action. The rule engine sees that there's a second condition in the rule. So it tries to see if the same fact matches the second condition's patterns. The first pattern in  says that box_one must be ?box_one and Red (?box_one&Red) and the other three slots can have any values that are assigned to the three variables ?box_two, ?box_three and ?box_four respectively. The rule engine already knows from the first condition that ?box_one is Red. So (?box_one&Red) is no contradiction (Red is Red). The fact satisfies the first pattern of . Because of the and conditional element, the second pattern in  needs to match the fact too for the whole condition to be considered satisfied. The second pattern in  says that box_three must be ?box_three and not Blue (?box_three&~Blue) and the other three slots can have any values that are assigned to the three variables ?box_one, ?box_two and ?box_four respectively. The rule engine already know from the first condition that ?box_three is Green. So (?box_three&~Blue) is no contradiction (Green is not Blue). The fact satisfies the second pattern of  as well.

So all conditions are satisfied. The action of placement_constraint1 will execute because of the assertion of the fact. What the action does is remove the matching fact from the rule engine. Why? Because all placement facts that satisfy the conditions of any one of the three constraint rules in BallPlacement.clp are not solutions to our logical problem. We remove them from the rule engine's fact list so that the ones remaining will be our solutions.

Rule Meta Model

Now that we know the basic constructs such as patterns, actions, rules, facts of a rule language, it's time to see how we model them in a meta-model; that is, the blue part in Figure 1. Our Rule Meta Model consists of a package, a few data types, enumerations and classes. As we said earlier, Rule Meta Model is defined in ECore and by that we mean all model elements in Rule Meta Model are instances of model elements in ECore. The one and only package in the Rule Meta Model is an instance of ECore’s EPackage; the data types instances of ECore’s EDataType; the enumerations instances of ECore’s EEnum and the classes instances of ECore’s EClass.

Below is a screenshot of how Rule Meta Model looks like in Eclipse. In the following sections, we'll go through the data types, enumerations and classes one by one. 

Figure 2. Rule Meta Model.

Data Types

Unlike Java, a rule language is untyped. In the code excerpt of BallPlacement.clp in previous section, we don’t see the types of ball, color, and placement. Although the language itself is untyped, underneath the language in the rule engine, things are still typed. For example, ball, color, placement are of a type called Symbol. Values like "something" are of type String and values like 2.3 are of type Float. Types like Symbol, String, and Float are called primitive types or data types. Most rule languages support a common set of primitive types. However, there are types available in one language but you don’t see them in others. It's important not to confuse data types like String and Float in a rule language with similar data types java.lang.String and java.lang.Float in java. They are totally different types used in different languages interpreted by different virtual machines. Types in a rule language are interpreted by a rule engine whereas java types are interpreted by a Java virtual machine. This is why when we model these data types in the Rule Meta model, we cannot simply use java.lang.String or java.lang.Float but need to implement our own types.

There are six data types in Rule Meta Model. Each of them corresponds to a java class. You can tell which java class a data type corresponds to by looking at the screenshot in Figure 2 or at the Instance Class Name property of the EDataType instance that defines the data type. The table below summarizes the correspondences.

data type implementing java class
RSymbol com.example.rule.ecore.rulemetamodel.RSymbol
RFactAddress com.example.rule.ecore.rulemetamodel.RFactAddress
RInteger com.example.rule.ecore.rulemetamodel.RInteger
RFloat com.example.rule.ecore.rulemetamodel.RFloat
RDataType com.example.rule.ecore.rulemetamodel.RDataType
RMultiField com.example.rule.ecore.rulemetamodel.RMultiField

When defining data types, besides implementing corresponding java classes, it's often appropriate and necessary to overwrite the generated code for converting strings to and from instances of the data types. In our example, we overwrite createRSymbolFromString() in com.example.rule.ecore.rulemetamodel.impl.RulemetamodelFactoryImpl.java for RSymbol and other five similar methods in the same file for the other five data types.

A meta model like ours should be as platform independent as possible. We don't want to tailor  Rule Meta Model too much to a rule language that models conforming to the Rule Meta Model can only generate code for that specific language. A complete Rule Meta Model should model data types common to most popular rule languages. Data types proprietary to a specific rule language can be modeled at M1 level. Because the example code for this article is more a demonstration than a full-fledged product, the set of data types we model here is by no means complete for a rule language, nor are the java classes implementing those data types.

Enumerations, Classes

There are three enumerations in the Rule Meta Model: RRuleType, RFunctionType, RCondElemEnum. The three enumerations and six data types are used in the definition of classes in Rule Meta Model. For example, the following diagram shows that class RFactTemplate has one attribute factName whose type is RSymbol, one of the data types we just defined. Besides attribute factName, RFactTemplate also has a containment reference to zero or multiple instances of RFactSlot. RFactTemplate models fact templates in a rule language. The class definition we see here is in line with our previous knowledge of fact templates: a fact template has a name and defines zero or multiple slots. As to RFactSlot, it models slots in fact templates. It has an attribute slotName of type RSymbol and an attribute slotValueRange of type RMultiField. We know from previous section that a slot has a name. What we didn't mention is that some rule languages allow you to specify the legitimate values of a slot, which is what slotValueRange is for.

The diagram below shows some more classes and their relations to each other. Class RRule models rules. It has an attribute ruleName that represents the name of a rule; a containment reference condition that represents conditional elements in a rule's condition part; a containment reference ceMatchPattern that represents patterns in a rule's condition part but not enclosed in conditional elements; and a reference actions that represents a rule's action part.

Class RCondElem models conditional elements. A conditional element can contain match patterns or more conditional elements. That's why our definition of RCondElem has one containment reference to itself and one containment reference to RPattern. The ceName attribute of RCondElem denotes what conditional element (and, or, not, etc) an instance of RCondElem is. If you look at RCondElemEnum, the type of the ceName attribute, you will see that there are three enumeration literals there: and, or, not. For the same reason we don't define a complete set of data types, the list of conditional elements in RCondElemEnum is not complete and in a more complete meta-model, RCondElemEnum should include conditional elements common in most popular rule languages.

Class RFunction models functions. A function like (retract (?placement)) we saw earlier has a name and takes zero or multiple instances of primitive data types as parameters. In this case, retract is the name of the function and ?placement is a parameter. Although not visible, the retract function returns a value. In JESS, it returns the symbol TRUE if it successfully removes a fact. In our definition of RFunction, we have an attribute functionName to represent the name of a function; an attribute returnValue of type RDataType to represent a function's return value; a reference to instances of RDataType to represent a function's parameters. The functionType attribute in RFunction denotes whether the function is provided by the rule engine or defined by users.

Ball Placement Rule Model

With the three plug-ins of Rule Meta Model in place, we can now use it to model our logical problem. Let's launch Eclipse and create a new project. Select File -> New -> Project and in the popup dialog, select Simple -> Project Click Next button and name the project Test or anything you like. With a project in place, we can now create a rule model or import BallPlacement.rulemetamodel that comes with this article's example code.

To creat the model from scratch, right click on the project we just created and in the popup context menu, select New -> Other. And then in the popup window, select Example EMF Model Creation Wizards -> Rulemetamodel Model and click the Next button. In the next screen, name the model whatever you like but keep the file extension as .rulemetamodel. Click the Next button to go to the next screen, and select RModule from the top dropdown box. Then click the Finish button.

To import BallPlacement.rulemetamodel, right click on the project we just created and in the popup context menu, select Import. In the popup dialog window, select File System and click Next button. The Import dialog window will show up. Use the browse button in the dialog window to select the folder where you unzipped the files in BallPlacementRuleModel.zip. A list of files in that folder will show up in the Import dialog window. Check BallPlacement.rulemetamodel in the list and click the Finish button.

If you create the model from scratch, you can refer to BallPlacement.rulemetamodel for details of the model's elements. Below is a screenshot of how the model looks like in Eclipse. It has two fact templates: placement and ball. It has three rules for the three constraints posed in the logical problem. It has two other rules for putting initial facts into rule engine. Without facts, the rule engine will have nothing to apply the three constraint rules to. The model also has some functions. They are used in the action part of the rules. 

Code Generation with JET Template

If you select the RModule node in BallPlacement.rulemetamodel and see its property view, you'll see that there's property called Output File Path. The value of this property is where the model puts the generated code. Please change its value to an appropriate location on your computer and then right click on the RModule node. In the popup context menu, select Generate and check your file system to see if a file by the name you specified in the Output File Path property is generated. The generated file should be the same as BallPlacement.clp in BallPlacementRuleModel.zip.

The logic for generating rule language code from a rule model is in a JET (Java Emitter Templates) template and a java helper class. The JET template file is Module.clpjet in the templates folder of com.example.rule.ecore project, which can be unzipped from RuleMetaModel.zip. The java helper class is com.example.rule.ecore.templates.ModuleHelper.java. Readers unfamiliar with JET can refer to JET Tutorial Part 1 (Introduction to JET) and JET Tutorial Part 2 (Write Code that Writes Code) for an excellent and very readable guide.

Practically, a generation model might be necessary for storing complex settings that govern the generation of rule language code from a rule model just like EMF's Gen Model for generating code from ECore models. For simplicity, we modified the editor plug-in EMF generated from our Rule Meta Model and provide a menu option in the context menu that pops up when you right click the RModule node.

Conclusion

We have demonstrated how to define a meta-model with EMF, use the meta-model to define a model for a logical problem and use JET to generate rule language code from the model. We have also seen that the generated code correctly solves the non-trivial logical problem. The flexibility of EMF as well as Eclipse makes all the work a breeze. The EMF framework has all modeling foundation there and thanks to that, most of our time is spent in understanding rule languages and how rule engine works. 

The example code for this article is a demonstration. There are many things to improve. Modeling rule-based systems is still a field in progress. There are standards bodies working on specifications. In the Reference section at the end of this article, we provide some pointers to relevant OMG standards in progress such as Production Rule Representation RFP and Sridhar Iyengar's response to OMG's Business Rules in Models RFI. Also listed in the Reference section is a very popular rule engine called CLIPS, of which JESS and many other rule engines are variations.

Reference

Frank Budinsky, David Steinberg, Ed Merks, Raymond Ellersick, Timothy J. Grose, "Eclipse Modeling Framework", Addison Wesley, ISBN 0-1314-2542-0

Production Rule Representation RFP http://www.omg.org/cgi-bin/doc?br/2003-9-3

Business Semantics of Business Rules RFP http://www.omg.org/cgi-bin/doc?br/2003-6-3

Business Rules Management RFI http://www.omg.org/cgi-bin/doc?bei/2004-6-3

Java Specification Request 94, Java Rule Engine API http://www.jcp.org/jsr/detail/94.jsp

Business Rules in Models http://www.omg.org/technology/documents/Business_Rules_in_Models_RFI.htm

Sridhar Iyengar's response to OMG's Business Rules in Models RFI http://www.omg.org/cgi-bin/doc?ad/03-01-25

CLIPS 6.2 http://www.ghg.net/clips/CLIPS.html