By Qusay H. Mahmoud,
July 26, 2005
For many mission-critical applications, the process of automating business policies, procedures, and business logic is simply too dynamic to manage effectively as application source code. Using business rules can help you develop more agile applications. The Business Rules Group defines a business rule as a statement that defines or constrains some aspect of the business; a business rule is intended to assert business structure or to control or influence the business's behavior. A rule engine evaluates and executes rules, which are expressed as if-then statements. The power of business rules lies in their ability both to separate knowledge from its implementation logic and to be changed without changing source code.
The specification for the Java Rule Engine API (JSR 94), developed through the Java Community Process (JCP) program, defines a Java runtime API for rule engines by providing a simple API to access a rule engine from a Java Platform, Standard Edition (Java SE, formerly known as J2SE) or a Java Platform, Enterprise Edition (Java EE, formerly known as J2EE) Java technology client. This article provides an overview of JSR 94 and discusses how to fit business rule technology into Java technology applications. Sample code gives a flavor of the effort involved in developing rule-based applications.
Many business applications have to deal with the dynamic changes of market economics. For example, applications for use in the insurance and banking industries must be able to accommodate the inevitable market changes that no one can predict or plan for during design. A solution is to have a rule engine, which is basically a set of tools that enable business analysts and developers to build decision logic based on an organization's data. The rule engine applies rules and actions as defined by end users without affecting how the application runs. The application is built to deal with the rules, which are designed separately.
Examples of rule engines include Drools, Fair Isaac Blaze Advisor, ILOG JRules, and Jess, to name a few. The lack of standards, however, may be a major factor in deterring businesses from using rule-based applications. Most rule engines have proprietary APIs, making them difficult to integrate with applications. If a rule engine is no longer supported and the business decides to adopt another rule engine, most of the application code will need to be rewritten. JSR 94 is an attempt to standardize rule engine implementations for Java technology. The four rule engines mentioned earlier support JSR 94.
JSR 94 provides guidelines for the rule administration and rule runtime APIs, but it defines no guidelines for what language to use to define the rules and actions. Efforts are under way to standardize a common rule language, including the Rule Markup Language (RuleML).
The underlying idea of a rule engine is to externalize the business or application logic. A rule engine can be viewed as a sophisticated interpreter of if-then statements. The if-then statements are the rules. A rule is composed of two parts, a condition and an action: When the condition is met, the action is executed. The if portion contains conditions (such as amount >=$100
), and the then portion contains actions (such as offer discount 5%
). The inputs to a rule engine are a collection of rules called a rule execution set and data objects. The outputs are determined by the inputs and may include the original input data objects with modifications, new data objects, and possible side effects (such as sending email to the customer).
Rule engines should be used for applications with highly dynamic business logic and for applications that allow end users to author business rules. A rule engine is a great tool for efficient decision making because it can make decisions based on thousands of facts quickly, reliably, and repeatedly.
Rule engines are used in applications to replace and manage some of the business logic. They are best used in applications where the business logic is too dynamic to be managed at the source code level -- that is, where a change in a business policy needs to be immediately reflected in the application. Applications in domains such as insurance (for example, insurance rating), financial services (loans, fraud detection, claims routing and management), government (application process and tax calculations), telecom customer care and billing (promotions for long distance calls that needs to be integrated into the billing system), ecommerce (personalizing the user's experience), and so on benefit greatly from using rule engines.
Rule-based applications communicate with the rule engine by passing in the set of rules to be executed. Then, the application can inspect the results and either display them to the end user or perform further processing. The rule engine determines when to evaluate each rule based on the input required for the rule as well as the results obtained from the evaluation of previous rules. You do not need to specify the order or the dependencies of the rules.
In a Java EE enterprise application, for example, rules can fit into applications such as the following:
Adopting a rule-based approach for your applications has the following advantages:
These benefits, however, are not without cost. As with any tool, the decision to integrate a rule engine into your application should be based on cost versus benefits. The cost includes the learning curve and the effort involved in building an interface between the application and the rule engine. In addition, different rule engines use different format and syntax for defining rules. Therefore, if an organization decides to move from one rule engine to another, business analysts and developers must learn and understand the operation of yet another tool.
JSR 94 defines a simple API to access a rule engine from a Java SE or Java EE client. It provides APIs to
Note that JSR 94 does not standardize the following:
In other words, it doesn't standardize the semantics of rule execution.
The APIs are defined in two main packages:
javax.rules.admin
package, provides classes that can be used to load rules and associated actions as execution sets. A rule execution set is a collection of rules. Rules can be loaded from external resources such as URIs, an InpuTStream
, an XML Element
, a binary abstract syntax tree, or a Reader
. It also provides methods to register and unregister rule execution sets. This package can also be used to define the permissions on execution sets to provide access authorization. javax.rules
package, provides classes to be used by clients to run the rules and get results. Only the rules that have been registered using the Rules Administrator API are accessible. This API enables clients to acquire rule sessions and execute rules within that session.The JSR 94 Expert Group made the decision to have two separate packages to reinforce the distinction between (1) executing a rule execution set that an administrator API has previously loaded and registered into the runtime environment and (2) the dynamic loading and execution of external resources (which can be performed only by using the Rules Administrator API). In addition, the separation allows a more fine-grained control of the user population, permitting some users to execute rules but not to administer them.
The Rules Administrator API
This API uses the RuleServiceProvider
class to get an instance of the RuleAdministrator
interface, which provides methods to register and unregister execution sets. The high-level capabilities of the administrator API are the following:
RuleAdministrator
interface through the RuleServiceProvider
classRuleExecutionSet
from external serializable or nonserializable resources including
org.w3c.dom.Element
for reading from an XML subdocumentjava.io.InputStream
for reading from binary streamsjava.lang.Object
for reading from vendor-specific abstract syntax treesjava.io.Reader
for reading from character streamsjava.lang.String
for reading from a URIRuleExecutionSet
object against a URI for use from the RuleRuntime
.RuleExecutionSet
object from a URI so it is no longer accessible from the RuleRuntime
.Rule
objects from the RuleExecutionSet
.The Runtime Client API
This API provides access to vendor implementations of the Rule Engine API, in a way similar to Java DataBase Connectivity (JDBC) software. Vendors expose their rule engine implementations to clients through the RuleServiceProvider
class. This class provides access to the runtime and administration APIs. The vendor provides a rule service provider URL that uniquely identifies the implementation. All rule service providers should be registered with a RuleServiceProviderManager
object in order to be accessible to clients.
At the heart of this API is the RuleRuntime
interface, which provides methods to allow clients to create a RuleSession
used to run the rules. A rule session is a runtime connection between a client and a rule engine; it is associated with a single rule execution set and may consume rule engine resources, but the rule session must be explicitly released when the client no longer requires the rule session. Thus, the rule session does two things:
statelessRuleSession
works on a per-client request basis.statefulRuleSession
is a dedicated session in which objects are not lost as long as the session is maintained between the client and the rules engine.The Runtime Client API's high-level capabilities are the following:
RuleServiceManager
class.RuleRuntime
interface through the RuleServiceProvider
class.RuleSession
through the RuleRuntime
.java.util.List
of registered URIs.RuleSession
.RuleSession
through the RuleExecutionSetMetadata
interface.ObjectFilter
interface to filter the results of executing a RuleExecutionSet
.Handle
instances to access objects added to a statefulRuleSession
.Because the JSR 94 reference implementation is built as a wrapper over the Jess rule engine, you will need Jess 6.1a3 or later in order to work with the reference implementation. Jess, which stands for Java Expert System Shell, is a rule engine and a scripting environment. Note that Jess is not freeware: It can be licensed for commercial use, and it is available for academic use. The JSR 94 reference implementation license is governed under the Jess license agreement. The Jess language and scripting environment evolved from the CLIPS expert system shell, which was initially developed by NASA. The CLIPS system language syntax and structure are similar to the LISP functional programming language.
To test the JSR 94 reference implementation, do the following:
jess.jar
from the Jess installation directory to the lib
directory of your JSR 94 reference implementation installation.lib
under your JSR 94 reference implementation installationjava -jar jsr94-example.jar
If all goes well, you will see output similar to the following. This output was generated by the sample application that we will discuss next.
C:\jsr94-1.0\lib>java -jar jsr94-example.jar
Administration API
Acquired RuleAdministrator: org.jcp.jsr94.jess.RuleAdministratorImpl@9304b1
Acquired InputStream to RI tck_res_1.xml: sun.net.www.protocol.jar.JarURLConnect
ion$JarURLInputStream@c17164
Loaded RuleExecutionSet: org.jcp.jsr94.jess.RuleExecutionSetImpl@cd2c3c
Bound RuleExecutionSet to URI: RuleExecutionSet1
Runtime API
Acquired RuleRuntime: org.jcp.jsr94.jess.RuleRuntimeImpl@1d99a4d
Got Stateless Rule Session: org.jcp.jsr94.jess.StatelessRuleSessionImpl@7a84e4
Calling rule session with the following data
Customer credit limit input: 5000
Invoice 1 amount: 2000 status: unpaid
Called executeRules on Stateless Rule Session: org.jcp.jsr94.jess.StatelessRuleS
essionImpl@7a84e4
Result of calling executeRules: 2 results.
Customer credit limit result: 3000
Invoice 1 amount: 2000 status: paid
Released Stateless Rule Session.
Got Stateful Rule Session: org.jcp.jsr94.jess.StatefulRuleSessionImpl@1e51060
Calling rule session with the following data
Customer credit limit input: 3000
Invoice 1 amount: 2000 status: paid
Invoice 2 amount: 1750 status: unpaid
Called addObject on Stateful Rule Session: org.jcp.jsr94.jess.StatefulRuleSessio
nImpl@1e51060
Called executeRules
Result of calling getObjects: 3 results.
Customer credit limit result: 1250
Invoice 1 amount: 2000 status: paid
Invoice 2 amount: 1750 status: paid
Released Stateful Rule Session.
To get a flavor of the effort involved in using the Java Rule Engine API, Code Sample 1 shows an example that comes with the reference implementation; the output above was generated from this sample application. This example loads a set of rules from an external XML resource file. The source code is fully commented. This example demonstrates the life cycle of developing applications using the Java Rule Engine API, and it shows several usage scenarios.
Code Sample 1: RuleExample.java
public class RuleExample {
// The rule service provider URI as defined by the reference implementation.
private static final String RULE_SERVICE_PROVIDER = "org.jcp.jsr94.jess";
/**
* Main entry point.
*/
public static void main(String[] args) {
try {
//
Load the rule service provider of the reference implementation.
// Loading this class will automatically register this provider with the
// provider manager.
Class.forName("org.jcp.jsr94.jess.RuleServiceProviderImpl");
//
Get the rule service provider from the provider manager.
RuleServiceProvider serviceProvider =
RuleServiceProviderManager.getRuleServiceProvider(RULE_SERVICE_PROVIDER);
//
Get the rule administrator.
RuleAdministrator ruleAdministrator = serviceProvider.getRuleAdministrator();
System.out.println("\nAdministration API\n");
System.out.println("Acquired RuleAdministrator: " + ruleAdministrator);
//
Get an input stream to a test XML ruleset.
// This rule execution set is part of the TCK.
InputStream inStream =
org.jcp.jsr94.tck.model.Customer.class.getResourceAsStream(
"/org/jcp/jsr94/tck/tck_res_1.xml");
System.out.println("Acquired InputStream to RI tck_res_1.xml: " + inStream);
//
Parse the ruleset from the XML document.
RuleExecutionSet res1 =
ruleAdministrator.getLocalRuleExecutionSetProvider(
null).createRuleExecutionSet( inStream, null );
inStream.close();
System.out.println("Loaded RuleExecutionSet: " + res1);
//
Register the rule execution set.
String uri = res1.getName();
ruleAdministrator.registerRuleExecutionSet(uri, res1, null);
System.out.println("Bound RuleExecutionSet to URI: " + uri);
//
Get a RuleRuntime and invoke the rule engine.
System.out.println("\nRuntime API\n");
RuleRuntime ruleRuntime = serviceProvider.getRuleRuntime();
System.out.println("Acquired RuleRuntime: " + ruleRuntime);
//
Create a statelessRuleSession.
StatelessRuleSession statelessRuleSession =
(StatelessRuleSession) ruleRuntime.createRuleSession(uri,
new HashMap(), RuleRuntime.STATELESS_SESSION_TYPE);
System.out.println("Got Stateless Rule Session: " + statelessRuleSession);
//
Create a customer as specified by the TCK documentation.
//
Then call executeRules on the input objects.
Customer inputCustomer = new Customer("test");
inputCustomer.setCreditLimit(5000);
//
Create an invoice as specified by the TCK documentation.
Invoice inputInvoice = new Invoice("Invoice 1");
inputInvoice.setAmount(2000);
//
Create an input list.
List input = new ArrayList();
input.add(inputCustomer);
input.add(inputInvoice);
//
Print the input.
System.out.println("Calling rule session with the following data");
System.out.println("Customer credit limit input: " +
inputCustomer.getCreditLimit());
System.out.println(inputInvoice.getDescription() +
" amount: " + inputInvoice.getAmount() +
" status: " + inputInvoice.getStatus());
//
Execute the rules without a filter.
List results = statelessRuleSession.executeRules(input);
System.out.println("Called executeRules on Stateless Rule Session: " +
statelessRuleSession);
System.out.println("Result of calling executeRules: " + results.size() +
" results.");
//
Loop over the results.
Iterator itr = results.iterator();
while(itr.hasNext()) {
Object obj = itr.next();
if (obj instanceof Customer)
System.out.println("Customer credit limit result: " +
((Customer) obj).getCreditLimit());
if (obj instanceof Invoice)
System.out.println(((Invoice) obj).getDescription() +
" amount: " + ((Invoice) obj).getAmount() + " status: " +
((Invoice) obj).getStatus());
}
//
Release the session.
statelessRuleSession.release();
System.out.println("Released Stateless Rule Session.");
//
Create a statefulRuleSession.
StatefulRuleSession statefulRuleSession =
(StatefulRuleSession) ruleRuntime.createRuleSession(uri,
new HashMap(), RuleRuntime.STATEFUL_SESSION_TYPE);
System.out.println("Got Stateful Rule Session: " + statefulRuleSession);
//
Add another invoice.
Invoice inputInvoice2 = new Invoice("Invoice 2");
inputInvoice2.setAmount(1750);
input.add(inputInvoice2);
System.out.println("Calling rule session with the following data");
System.out.println("Customer credit limit input: " +
inputCustomer.getCreditLimit());
System.out.println(inputInvoice.getDescription() +
" amount: " + inputInvoice.getAmount() +
" status: " + inputInvoice.getStatus());
System.out.println(inputInvoice2.getDescription() +
" amount: " + inputInvoice2.getAmount() +
" status: " + inputInvoice2.getStatus());
//
Add an object to the statefulRuleSession.
statefulRuleSession.addObjects(input);
System.out.println("Called addObject on Stateful Rule Session: "+
statefulRuleSession);
statefulRuleSession.executeRules();
System.out.println("Called executeRules");
//
Extract the objects from the statefulRuleSession.
results = statefulRuleSession.getObjects();
System.out.println("Result of calling getObjects: " + results.size() +
" results.");
//
Loop over the results.
itr = results.iterator();
while(itr.hasNext()) {
Object obj = itr.next();
if (obj instanceof Customer)
System.out.println("Customer credit limit result: " +
((Customer) obj).getCreditLimit());
if (obj instanceof Invoice)
System.out.println(((Invoice) obj).getDescription() +
" amount: " + ((Invoice) obj).getAmount() +
" status: " + ((Invoice) obj).getStatus());
}
//
Release the statefulRuleSession.
statefulRuleSession.release();
System.out.println( "Released Stateful Rule Session." );
System.out.println();
} catch (NoClassDefFoundError e) {
if (e.getMessage().indexOf("JessException") != -1) {
System.err.println("Error: The RI Jess could not be found.");
} else {
System.err.println("Error: " + e.getMessage());
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
The rules loaded in this example are part of the use cases that come with the JSR 94 Technology Compatibility Kit (TCK). The rules are written in Jess. If you are familiar with a functional programming language such as Lisp or Haskell then the Jess code will be familiar. Note that other rule engines, such as Drools, have adopted XML syntax for describing rules. Code Sample 2 shows the rules file.
The definition of a rule execution set is not within the scope of JSR 94. The implementation given in this file is written for the reference implementation. A rule engine vendor verifying its rule engine should modify this file to match the vendor's specific needs.
This rule execution set will be invoked by the TCK in a stateless manner.
The rule execution set must have support for the following business object model:
Customer
class: The Customer
business object is a simple business object that contains a name and credit limit property. The definition of this class can be found in org.jcp.jsr94.tck.model.Customer
.Invoice
class: The Invoice
business object is a simple business object that contains a description, amount, and status property. The definition of this class can be found in org.jcp.jsr94.tck.model.Invoice
.The rule execution set has the following definition:
Customer
and Invoice
business objects.Rule 1: If the customer's credit limit is greater than the invoice amount and the status of the invoice is "unpaid," then decrement the credit limit with the invoice amount and set the status of the invoice to "paid."
Note that additional physical rules may be defined to accomplish the requirements mentioned earlier.
The rule execution set has the following semantics.
Input:
The rule execution should produce the following output:
Code Sample 2: rules.xml
<?xml version="1.0" encoding="UTF-8"?>
<!--
/*
* J A V A C O M M U N I T Y P R O C E S S
*
* J S R 9 4
*
* Technology Compatibility Kit
*
*/
-->
<rule-execution-set>
<name>RuleExecutionSet1</name>
<description>Stateless RuleExecutionSet for the TCK for Jess</description>
<code>
(defclass org.jcp.jsr94.tck.model.Customer
org.jcp.jsr94.tck.model.Customer)
(defclass org.jcp.jsr94.tck.model.Invoice
org.jcp.jsr94.tck.model.Invoice)
(defrule rule-1
?customer <- (org.jcp.jsr94.tck.model.Customer
(creditLimit ?limit) (OBJECT ?C))
?invoice <- (org.jcp.jsr94.tck.model.Invoice
(amount ?amt&:(> ?limit ?amt))
(status "unpaid") (OBJECT ?I))
=>
(modify ?customer (creditLimit (- ?limit ?amt)))
; (printout t "The credit limit of the customer is "
; (get ?C creditLimit) crlf)
(modify ?invoice (status paid))
; (printout t "The status of the invoice is "
; (get ?I status) crlf)
)
</code>
</rule-execution-set>
Business rule technology provides a solution to manage dynamic business logic. You can use rule engines in both the application tier to manage dynamic business logic and in the presentation tier to customize the page flow. JSR 94 provides a vendor-neutral Java SE or Java EE interface to access a rule engine. You can create agile enterprise applications by combining Java EE and business rule technology, managing the business behavior outside the source code.
JSR 94 is an attempt to standardize rules engine implementations for Java technology. The specification provides guidelines on how to implement the Runtime Client and Rules Administrator APIs as well as guidelines on how developers can integrate a rules engine into an application by means of an API -- without limiting how rules and actions should be created or what language the developer should use.
As with any tool, the decision to integrate a rule engine into your application should be based on cost versus benefits. But as JSR 94 gains momentum, application servers may start offering JDBC-like support for multiple vendor implementations of the API, just as they provide support for multiple databases.
Acknowledgments
Special thanks to Larry Freeman of Sun Microsystems, whose feedback helped me improve this article.