Thursday, July 8, 2010

The JBossESB-Drools Integration in the JBoss SOA Platform

As I've mentioned in previous posts and articles, one of the great strengths of the JBoss SOA Platform is the large number of integrations that it supports. Some of these integrations take the form of support for JBoss and third-party supplied JMS and UDDI providers or support for multiple JDKs and databases. The SOA Platform also supports an integration to jBPM for business process orchestration, and JBDS for application development.

In this post, I want to take a look at the SOA Platform's integration with JBoss Rules. In an earlier blog post (http://jboss-soa-p.blogspot.com/2009/07/when-content-knows-way-content-based.html), I discussed using Rules to implement content based routing over the JBossESB. In this post, we'll look at another aspect of the JBossESB-Rules integration; the creation of rules-based services.

Before we examine this integration in detail, let's take a quick look at JBoss Rules.

Drools and Rules


JBoss Rules as packaged in the SOA Platform is the commercialized version of the open source "Drools" project. (You can learn more about Drools at the project web site here: http://www.jboss.org/drools/) Drools is a unified and integrated solution for Business Rules, Business Processes Management, and Event Processing. The Drools project is organized into a number of sub-projects. When we refer to JBoss Rules in the SOA Platform, what we're primarily talking about is the Drools "Expert" sub-project. This sub-project consists of the Rules API, the Rules engine, and Rules editing and debugging tools.

Rules-based programming, as its name implies, is built on the ability to define decision points and keep them separate from other program logic. OK, that sounds interesting, but why would I want to use this? In other words, what's the big deal? Here are two reasons why this is important:
  • First, it enables you to separate your application's business logic and decision point handling from the application code. This means that your business process specialists can concentrate on the business logic rules and your programmers can concentrate on the application code. This makes application development and maintenance easier and more effective.
  • Second, and don't take this as a personal criticism of your programming skills or mine, but the since the rules engine is designed and optimized to process rules, it is more efficient than any massive if-then-else statement that you can write. So, your application's performance can be improved. The JBoss Rules engine makes use of the Rete algorithm http://en.wikipedia.org/wiki/Rete_algorithm for efficient Rules processing.
When you define a rule, the model that you follow is not "if-then-else," but rather "when" and "then." The two constructs that you use in a rule are:
  • The Condition - This is the left hand side of the rule and covers the "when" aspects of the rule.
  • The Consequence - This is the right hand side of the rule and covers the "then" aspects of the rule.
Rules are written in the Drools Rule Language (drl). This language can be extended into a Domain Specific Language (dsl) to support application-specific requirements such as medical or financial procedures and terminology.

The general template for a rule is:
1:  rule “a simple rule”
2: when (LHS)
3: you need a rules-based app
4: then (RHS)
5: build it with JBoss Rules
How does a rule access information? Through Rules' working memory. The information that rules perform operations take the form of Java beans that are referred to as "facts." (The elements in these facts are accessed through the getter and setter methods.) What happens is that facts are inserted into working memory, updated in working memory, or removed from working memory, those changes to the facts in working memory can cause the Rules' "when" conditions to be true and the rules to be executed.

It's important to note that unlike procedural programming, changes to facts can cause more than one rule to reach true conclusions and be available to be executed at once. What happens then? Well, instead of hardcoding a sequence of rules, the rules engine adds each matching rule to its "Agenda" of rules to be executed. If the Agenda includes more than one rule, the rule engine performs conflict resolution on the rules and determines the sequence in which the rules should be executed. This conflict resolution is based on the rules' salience (you define this as a property when you write the rules), how often the rule has fired in the past, complexity (the more complex a rule the more likely the rule engine will consider it to apply to the current situation), and the order in which the rules are loaded.

Invoking Rules from a JBossESB Service in the SOA Platform

The Rules - JBossESB integration in the SOA Platform enables you to access rules from an service's actions. This is supported by the org.jboss.soa.esb.actions.BusinessRuleProcessor and the org.jboss.soa.esb.actions.DroolsRuleService action classes.

The BusinessRuleProcessor class uses rules loaded from rules files. Generally, you use this class for simple rules services as loading large numbers of rules from large numbers of rules files is difficult to manage and not efficient.

For production environments, where you will have complicated rules services that deal with hundreds or even thousands of rules, it's better to use the DroolsRuleService. This service uses the RuleAgent to either access packages of rules from files, or from a Business Rules Management System (BRMS) .

The JBoss BRMS Platform (http://www.jboss.com/products/platforms/brms/) combines a central repository for rules, with a web based rules authoring interface, and rules management that provides import/export/archiving, audit trail or edits, error log, automated test development and execution, rules analysis, status tracking, and version control. The BRMS Platform rules authoring "guided editor" enables non-programmers to more easily create rules while the Platform makes it easy for rules administrartors can maintain large numbers of rules and control their development and use by users. Here's a screenshot:

The SOA Platform supports rules services that are either stateless or stateful. In the stateless model, messages sent to the services contain all the facts to be inserted into the rules engine working memory, before the rules are executed. In the stateful model, where the execution of the rules may take place in a session over an extended time period, several messages may be sent to a rule service, and the rules may fire and update either the message or the facts until a final message causes the service to end the session. The best way to explain and illustrate the Rules - JBossESB integration in the SOA Platform is with one of the Platform's "quickstart" example programs. Let's take a look.

Rules Services in Action - The Quickstart

I never get tired of saying that one of the great features of the SOA Platform is its extensive, and always growing, set of "quickstart" programs. These programs go far beyond being simple examples as they clearly illustrate various features supported by the Platform. They also serve as a great resource for writing your own applications. For our example, we'll look at the "business_rules_service" quickstart. Before we walk through the configuration and execution of the quickstart, let's take a look at its (3) Rules files. It important to note that Rules are actually used in multiple ways in this quickstart as it simulates a customer making a purchase from an e-commerce site. The quickstart uses Rules to:
  • Calculate the priority of an incoming customer order
  • Calculate the discount to be applied to an order
  • And route the order to the appropriate service, based on the content of the order
It's also important to note that the quickstart illustrates how a message can be modified as it is processed through the SOA Platform's JBossESB action pipeline. Let's start with the "MyBusinessRules.drl" file, as it establishes the priority of the order. (Oh, wait. We'll jump ahead a bit here and explain that by default, every incoming order has a priority value of "1.")
1:  package com.jboss.soa.esb.routing.cbr
2:
3: #list any import classes here.
4: import org.jboss.soa.esb.message.Message;
5: import org.jboss.soa.esb.message.format.MessageType;
6: import org.jboss.soa.esb.samples.quickstart.businessrules.dvdstore.OrderHeader;
7: import org.jboss.soa.esb.samples.quickstart.businessrules.dvdstore.Customer;
8:
9: global java.util.List destinations;
10:
11:
12:
13: rule "Logging"
14: when
15: order: OrderHeader()
16: customer: Customer()
17: then
18: System.out.println("Customer Status: " + customer.getStatus());
19: System.out.println("Order Total: " + order.getTotalAmount());
20: end
21:
22: rule "Customer Platinum Status"
23: when
24: customer: Customer(status > 50)
25: order: OrderHeader(totalAmount > 50)
26: then
27: System.out.println("Platinum Customer - High Priority");
28: order.setOrderPriority(3);
29: end
30:
31: rule "Customer Gold Status"
32: when
33: customer: Customer(status > 10, status <= 50)
34: order: OrderHeader(totalAmount > 25)
35: then
36: System.out.println("Gold Customer - Medium Priority ");
37: order.setOrderPriority(2);
38: end
39:
Let's examine this Rules file line-by-line:
  • Line 1 - Similar to Java, a package is a related set of rules.
  • Lines 4-7 - These imports perform the same function as Java language imports. Note that we're importing both SOA Platform Message related packages and packages contained in the quickstart itself.
  • Line 9 - Make a mental note of this global definition as it will be used by the other Rules files and by the quickstart for content based routing.
  • Line 13-14 - The name for the first Rule and the start of its "when" clause.
  • Line 15 - This creates a Rule variable named "order" and initializes with the value of the OrderHeader fact (org.jboss.soa.esb.samples.quickstart.businessrules.dvdstore.OrderHeader) that is presented to the Rule.
  • Line 16 does the same for a Rule variable named customer and the Customer fact. Note that there is nothing conditional about these assignments. The rule will match every OrderHeader and Customer. This matching is actually an important concept to keep in mind as part of each "when" clause involves trying to match the facts passed to the rules.
  • Line 20 - A rule must always have an end statement.
  • Line 22 - This rule sets the status for the highest priority ("Platinum") customers.
  • Line 24-25 - Note the differences in these statements to those in lines 15 and 16. The rule will only be executed if:
    • Line 24 - The customer's status is greater that 50. If this is not the case, then the customer variable we set in line 16 will not be changed, and
    • Line 25 - The total amount of the order is also greater than 50. If this is not the case, then the order variable that we set in line 15 will not be changed.
  • Line 28 - Remember how I said that the default priority for each order was set to "1". Here's where we set the priority. Note that we are setting the value in the "order" Rule variable.
  • Lines 31-38 - This rule sets the status for the 2nd highest priority ("Gold") customers. What's interesting in this rule is line 33 as it includes two criteria that must both be met in order for the fact to match the rule.
The next Rules file that we'll look at picks up where MyBusinessRules.drl leaves off. This Rules file is named: MyBusinessRulesDiscount.drl

1:  package com.jboss.soa.esb.routing.cbr
2:
3: #list any import classes here.
4: import org.jboss.soa.esb.message.Message;
5: import org.jboss.soa.esb.samples.quickstart.businessrules.dvdstore.OrderHeader;
6: import org.jboss.soa.esb.samples.quickstart.businessrules.dvdstore.Customer;
7:
8: global java.util.List destinations;
9:
10:
11:
12: rule "Logging"
13: salience 10
14: when
15: order: OrderHeader()
16: customer: Customer()
17: then
18: System.out.println("Customer Status: " + customer.getStatus());
19: System.out.println("Order Total: " + order.getTotalAmount());
20: end
21:
22: rule "Customer Platinum Status"
23: salience 20
24: when
25: customer: Customer(status > 50)
26: order: OrderHeader(orderPriority == 3)
27: then
28: System.out.println("Platinum Customer - High Priority - Higher discount");
29: order.setOrderDiscount(8.5);
30: end
31:
32: rule "Customer Gold Status"
33: salience 20
34: when
35: customer: Customer(status > 10, status <= 50)
36: order: OrderHeader(orderPriority == 2)
37: then
38: System.out.println("Gold Customer - Medium Priority - discount ");
39: order.setOrderDiscount(3.4);
40: end
  • Lines 1-8 and the "Logging" rule should look familiar, so let's move on to the other rules defined in this file.
  • Line 13 - Remember how we talked about how rules are not executed in the exact sequence in which they can be viewed in a rules .drl file? This rule is assigned a salience property value of 10. Since this rule only prints out some logging information, it's assigned a lower salience than the other rules defined in the file.
  • Line 22 - This rule sets the discount level for the highest class of customers.
  • Line 23 - And, since we want to be sure that this rule fires, we assign it a salience property value of 20.
  • Lines 25-26 - This "when" clause is true when the rule is able to match both a Customer fact with a status greater than 50 and an OrderHeader fact with an orderPriority equal to 3. If both these conditions are true, then rule the customer and order variables are initialized from the Customer and and OrderHeader facts and the rule is fired.
  • Line 29 - Note that when the rule fires, the setOrderDiscount setter method is executed on the rule variable "order." The same setter method is executed on the OrderHeader fact in working memory. (Remember how we talked about how rules can both react to changes to facts in working memory and also cause changes on those facts? This is an example.) For the "Platinum" class of customers, we assign a generous discount.
  • Lines 32-39 - This rule follows the same pattern as the "Customer Platinum Status" rule. Note that we give the "Gold" class of customers a somewhat less generous discount. ;-)
The third and final Rules file used by the quickstart controls the content-based routing used by the quickstart to route messages to services. In the SOA Platform, Rules is one of the supported mechanisms for implementing content based routing. In contrast to more static routing approaches, this form of message routing is relies on the content in the messages to dictate the route that a message takes. (For background on content based routing, please refer to this blog post: http://jboss-soa-p.blogspot.com/2009/07/when-content-knows-way-content-based.html )

This rules file is aptly named: MyRoutingRules.drl
1:  package com.jboss.soa.esb.routing.cbr
2:
3: #list any import classes here.
4: import org.jboss.soa.esb.message.Message;
5: import org.jboss.soa.esb.message.format.MessageType;
6: import org.jboss.soa.esb.samples.quickstart.businessrules.dvdstore.OrderHeader;
7:
8: #declare any global variables here
9: global java.util.List destinations;
10:
11:
12: rule "Highest Priority Orders"
13:
14: when
15: OrderHeader( orderPriority == 3 )
16: then
17: System.out.println("HIGHEST PRIORITY");
18: destinations.add("SuperSpecialCustomerService");
19:
20: end
21:
22: rule "Medium Priority Orders"
23:
24: when
25: OrderHeader( orderPriority == 2 )
26: then
27: System.out.println("Medium Priority");
28: destinations.add("SpecialCustomerService");
29: end
30:
31: rule "Low Priority Orders"
32:
33: when
34: OrderHeader( orderPriority == 1 )
35: then
36: System.out.println("Low Priority");
37: destinations.add("RegularCustomerService");
38: end
39:
  • Lines 1-6 - Once again, these import lines should look familiar.
  • Line 9 - Make note of the destinations List. We'll see this used when the messages are routed.
  • The rules in this rules file are pretty straight-forward. In each of the three rules we add the appropriate destination, based on the orderPriority in the OrderHeader, where that destination is a service that the quickstart deploys to the JBossESB in the SOA Platform.
OK, those are the rules that we'll use in the quickstart. Let's now step through the quickstart as it is run.

To deploy the quickstart, execute this ant target:
1:  ant deploy
And, we then see this written to the server log:
1:  22:20:57,568 INFO [QueueService] Queue[/queue/quickstart_Business_Rules_Request_GW] started, fullSize=200000, pageSize=2000, downCacheSize=2000
2: 22:20:57,580 INFO [QueueService] Queue[/queue/quickstart_Business_Rules_Request_ESB] started, fullSize=200000, pageSize=2000, downCacheSize=2000
3: 22:20:57,621 INFO [QueueService] Queue[/queue/quickstart_Business_Rules_ConciergeManager] started, fullSize=200000, pageSize=2000, downCacheSize=2000
4: 22:20:57,632 INFO [QueueService] Queue[/queue/quickstart_Business_Rules_DistributionManager] started, fullSize=200000, pageSize=2000, downCacheSize=2000
5: 22:20:57,643 INFO [QueueService] Queue[/queue/quickstart_Business_Rules_BasicShipping] started, fullSize=200000, pageSize=2000, downCacheSize=2000
6: 22:20:57,682 INFO [EsbDeployment] Starting ESB Deployment 'Quickstart_business_rules_service.esb'
And to run it, exceute this ant target:
1:  ant runtest
When the quickstart is run, here's what happens, step by step. Note that while we'll be examining most of the contents of the jboss-esb.xml file in detail, we'll be doing it in pieces or fragments so that it's easier to follow. The line numbers in each of these fragments will therefore be different from the actual (whole) file.

Step 1 - Create a Message and Pass it Through a Gateway to the Deployed Quickstart Application

Like many of the SOA Platform quickstarts, the business_rules_service quickstart initiates its actions when a message is inserted into a queue that is being watched by a gateway listener. What's a gateway? On the JBossESB in the SPA Platform, everything is either a service that generates or consumes messages, or a message. That is, a message that is in the form (org.jboss.soa.esb.message) that the ESB understands. Services that can understand messages in this form are referred to as being "ESB-aware."

How can you connect other, and potentially older, legacy applications over the ESB? By using gateways. A gateway (org.jboss.soa.esb.listeners.gateway) is a service that acts as a bridge between an ESB-aware and an ESB-unaware client and service. Gateways translate information between ESB and non-ESB message formats and EPRs. (EPR stands for endpoint reference.) Gateways are listener processes in that they "listen" for incoming communications. They are different from ESB-aware listeners in that they accept data in different formats such as objects in files or SQL tables. ESB-aware listeners can only accept messages in the org.jboss.soa.esb.message format.

The SOA Platform supports these gateways:
  • file gateways: local filesystem, ftp, sftp and ftps
  • JMS
  • HTTP/HTTPS
  • email (POP3)
  • SQL table
  • Hibernate
In the case of this quickstart, we'll use a JMS gateway to receive the JMS message. The gateway queue, and its corresponding ESB-aware queue are defined in the jms-provider section of the jboss-esb.xml file:
1:  <jms-bus busid="quickstartGwChannel">
2: <jms-message-filter dest-type="QUEUE"
3: dest-name="queue/quickstart_Business_Rules_Request_GW" />
4: </jms-bus>
5: <jms-bus busid="quickstartEsbChannel">
6: <jms-message-filter dest-type="QUEUE"
7: dest-name="queue/quickstart_Business_Rules_Request_ESB" />
8: </jms-bus>
And the listener is defined at the top of the "Business_Rules_Service" service definition:
1:   <service category="Business_RulesServices"
2: name="Business_Rules_Service" description="The main entry point">
3: <listeners>
4: <!-- Gateway -->
5: <jms-listener name="TheGateway"
6: busidref="quickstartGwChannel" is-gateway="true" />
7: <jms-listener name="TheESBChannel"
8: busidref="quickstartEsbChannel" >
9: </jms-listener>
10: </listeners>
11: <actions mep="OneWay">
  • Note that on line 6, we identify the listener as a gateway.
  • Also note that on line 11, we define the mep, or "message exchange pattern." In the case of this quickstart, the pattern is "OneWay" which indicates that the message pattern is asynchronous. We're sending messages, but not waiting around (or blocking) for a response.
How do we generate this message? Take a look at the "runtest" target, specifically the classname, in the quickstart's ant build.xml file:
1:  <target name="runtest" depends="compile"
2: description="willl receive JMS message to tigger the actions in the ESB">
3: <echo>Runs Test JMS Sender</echo>
4: <java fork="yes" classname="org.jboss.soa.esb.samples.quickstart.businessrules.test.SendJMSMessage" failonerror="true">
5: <classpath refid="exec-classpath" />
6: </java>
That's right - we're sending a JMS message with a program named "SendJMSMessage." It's hard to get simpler than that. ;-)

In order to simulate a realistic customer order in the message, SendJMSMessage builds the message from the quickstart's SampleOrder.xml file:
1:  <Order orderId="1" orderDate="Wed Nov 15 13:45:28 EST 2006" statusCode="0"
2: netAmount="59.97" totalAmount="64.92" tax="4.95">
3: <Customer userName="user1" firstName="Harry" lastName="Fletcher" state="SD"/>
4: <OrderLines>
5: <OrderLine position="1" quantity="1">
6: <Product productId="364" title="The 40-Year-Old Virgin " price="29.98"/>
7: </OrderLine>
8: <OrderLine position="2" quantity="1">
9: <Product productId="299" title="Pulp Fiction" price="29.99"/>
10: </OrderLine>
11: </OrderLines>
12: </Order>
The SendJMSMessage class reads this file, creates a message object of type javax.jms.ObjectMessage (remember, this is an ESB-unaware message), and writes it to the queue (queue/quickstart_Business_Rules_Request_GW) on which our JMS gateway is listening. The listener receives the message and the ESB converts it to an ESB-aware message and then passes it onto the ESB through the queue/quickstart_Business_Rules_Request_ESB queue.

Now it starts to get more interesting.

Remember that our rules rely on certain types of facts (which are JavaBeans) being available in working memory. Where do these facts come from? We'll create them out of that message.

Step 2 - Transform the Message into Beans

The problem is that we have to have a way to create those facts out of the information in the message. Luckily, one of the tasks that the JBossESB in the SOA Platform performs is "transformation." The next action in the quickstart's action pipeline uses the smooks processing engine (http://www.smooks.org/) and the JBossESB's out-of-the-box "SmooksAction" action to perform the transformation of the information in the message into facts:
1:  <action name="transform"
2: class="org.jboss.soa.esb.smooks.SmooksAction">
3: <property name="smooksConfig" value="/smooks-res.xml" />
4: <property name="resultType" value="JAVA" />
5: </action>
The source code for the facts (remember that these are JavaBeans and have getter and setter methods) is in these source files in the quickstart:

In: src/org/jboss/soa/esb/samples/quickstart/businessrules/dvdstore
  • Customer.java
  • OrderHeader.java
  • OrderItem.java
The quickstart uses the smooks-res.xml file to perform the transformation. Let's take a look at this file.
1:  <?xml version='1.0' encoding='UTF-8'?>
2: <smooks-resource-list xmlns="http://www.milyn.org/xsd/smooks-1.1.xsd"
3: xmlns:jb="http://www.milyn.org/xsd/smooks/javabean-1.2.xsd">
4:
5: <!-- Populate the OrderHeader -->
6: <jb:bean beanId="orderHeader" class="org.jboss.soa.esb.samples.quickstart.businessrules.dvdstore.OrderHeader" createOnElement="order">
7: <jb:value property="orderId" data="Order/@orderId" />
8: <jb:value property="orderDate" data="Order/@orderDate" decoder="Calendar">
9: <jb:decodeParam name="format">EEE MMM dd HH:mm:ss z yyyy</jb:decodeParam>
10: <jb:decodeParam name="locale-language">en</jb:decodeParam>
11: <jb:decodeParam name="locale-country">US</jb:decodeParam>
12: </jb:value>
13: <jb:value property="statusCode" data="Order/@statusCode" />
14: <jb:value property="netAmount" data="Order/@netAmount" />
15: <jb:value property="totalAmount" data="Order/@totalAmount" />
16: <jb:value property="tax" data="Order/@tax" />
17: </jb:bean>
18:
19: <!-- Populate the Customer -->
20: <jb:bean beanId="customer" class="org.jboss.soa.esb.samples.quickstart.businessrules.dvdstore.Customer" createOnElement="customer">
21: <jb:value property="userName" data="customer/@userName" />
22: <jb:value property="firstName" data="customer/@firstName" />
23: <jb:value property="lastName" data="customer/@lastName" />
24: <jb:value property="state" data="customer/@state" />
25: </jb:bean>
26:
27: <!-- Populate the OrderItem list -->
28: <jb:bean beanId="orderItemList" class="java.util.ArrayList" createOnElement="orderlines">
29: <jb:wiring beanIdRef="orderItem" />
30: </jb:bean>
31:
32: <!-- Populate the OrderItem instance -->
33: <jb:bean beanId="orderItem" class="org.jboss.soa.esb.samples.quickstart.businessrules.dvdstore.OrderItem" createOnElement="orderlines/orderline">
34: <jb:value property="position" data="orderline/@position" />
35: <jb:value property="quantity" data="orderline/@quantity" />
36: <jb:value property="productId" data="orderline/product/@productId" />
37: <jb:value property="title" data="orderline/product/@title" />
38: <jb:value property="price" data="orderline/product/@price" />
39: </jb:bean>
40:
41: </smooks-resource-list>
That's right - it's using XPath (http://www.w3.org/TR/xpath/) to parse the information in the message into Customer (starting at line NN), OrderHeader (starting at line NN), and OrderItem.java (starting at line NN) JavaBeans and a java.util.ArrayList object that contains a list of the orders.

Step 3 - And Add the Beans Back into the Message

What happens next? Well, we have the original message, and some JavaBeans. But, remember that on the JBossESB in the SOA Platform, everything is either a message or a service. What we need is some way to get those JavaBeans back into the message. The way that we do this is with the next action in the action pipeline:
1:  <action name="map_order_components" class="org.jboss.soa.esb.actions.scripting.GroovyActionProcessor">
2: <property name="script" value="/map_order_components.groovy" />
3: </action>
GroovyActionProcessor is another of the SOA Platform's out-of-the-box actions. The groovy script referenced by the action takes the JavaBeans that we just created and adds them (note the use of the beanId's that we defined in the smooks-res.xml file) back into the message with property names that match the bean IDs. The script is very short - lines 3 and 4 add the orderHeard and customer to the message body:
1:    // Need to map down the orderHeader and customer beans onto the message
2: // to make them available to the ObjectMapper...
3: message.getBody().add("orderHeader", message.getBody().get().get("orderHeader"));
4: message.getBody().add("customer", message.getBody().get().get("customer"));
Now we have the JavaBeans in the message. What happens next? The quickstart updates the customer status (it's set to a value of "0" in the JMS message that started the quickstart) with the "UpdateCustomerStatus" custom action:
1:  <!-- Update Customer Status -->
2: <action name="updateCustomerStatus"
3: class="org.jboss.soa.esb.samples.quickstart.businessrules.UpdateCustomerStatus">
4: <property name="status" value="60"/>
5: </action>
We'll set this to a value of 60 as the customer is in the platinum customer class. (Hint: For extra credit, try the quickstart with different status values.)

Here's the output in the log - this line is printed by the org.jboss.soa.esb.samples.quickstart.businessrules.UpdateCustomerStatus custom action:
1:  21:42:15,793 INFO [STDOUT] { Updated customer status to 60}
Step 4 - Process the Message with the BusinessRulesProcessor

OK - now we can see the BusinessRulesProcessor execute in the next action:
1:    <!-- Use the BRP to calculate the order priority -->
2: <action
3: class="org.jboss.soa.esb.actions.BusinessRulesProcessor"
4: name="BRP">
5: <property name="ruleSet"
6: value="MyBusinessRules.drl" />
7: <property name="ruleReload" value="true" />
8: <property name="object-paths">
9: <object-path esb="body.orderHeader" />
10: <object-path esb="body.customer" />
11: </property>
12: </action>
13:
14: <action name="reviewMessage1"
15: class="org.jboss.soa.esb.samples.quickstart.businessrules.ReviewMessage">
16: <property name="stuff" value="After Order Priority"/>
17: </action>
  • Line 1 - Comments are always good things! ;-)
  • Line 2 - Here's the start of the action that makes use of the BusinessRulesProcessor.
  • Line 3 - And here's the reference to the BusinessRulesProcessor class.
  • Lines 5-6 - And, here's the reference to the Rules file that we want to execute.
  • Line 7 - This property causes the Rule to be if the file changes.
  • Lines 8-11 - And, here are the objects that we added to the message - remember the map_order_components.groovy file?
So, what just happened? We made the orderHeader and customer objects available to the Rules defined in MyBusinessRules.drl and executed the Rules. The net effect of this is that the priority of the order defined in the message should have changed. The next action in the action pipeline writes this to the log:
1:  21:42:15,931 INFO [STDOUT] Platinum Customer - High Priority
2: 21:42:15,932 INFO [STDOUT] Customer Status: 60
3: 21:42:15,932 INFO [STDOUT] Order Total: 64.92
The first three lines are printed by the rules. Remember how the rules all included System.out.prinln statements? And how the logging rule that prints the customer status and order total has a lower value salience property than the rules that set and print the customer priority? That explains the order in which the statements are printed to the log.

And then these lines are printed by the org.jboss.soa.esb.samples.quickstart.businessrules.ReviewMessage cusom action:
1:  21:42:15,932 INFO [STDOUT] { ================ After Order Priority
2: 21:42:15,933 INFO [STDOUT] Customer: user1,Harry,Fletcher,SD,60
3: 21:42:15,933 INFO [STDOUT] Order Priority: 3
4: 21:42:15,933 INFO [STDOUT] Order Discount: 0.0
5: 21:42:15,933 INFO [STDOUT] Order Total: 64.92
6: 21:42:15,933 INFO [STDOUT] } ================ After Order Priority
The rule "Highest Priority Orders" defined in MyBusinessRules.drl has set the order priority to 3.

Next, the quickstart calls the BusinessRulesProcessor again, this time to determine the order discount:
1:   <!-- Use the BRP to calculate the order discount -->
2: <action
3: class="org.jboss.soa.esb.actions.BusinessRulesProcessor"
4: name="BRP2">
5: <property name="ruleSet"
6: value="MyBusinessRulesDiscount.drl" />
7: <property name="ruleReload" value="true" />
8: <property name="object-paths">
9: <object-path esb="body.orderHeader" />
10: <object-path esb="body.customer" />
11: </property>
12: </action>
13:
14: <action name="reviewMessage2"
15: class="org.jboss.soa.esb.samples.quickstart.businessrules.ReviewMessage">
16: <property name="stuff" value="After Order Discount"/>
17: </action>
  • Line 6 - The rule "Customer Platinum Status" defined in the MyBusinessRulesDiscount.drl file is executed as our customer has both the highest priority as well as the status value of 60. That rule and the logging rule print out these statements to the log in this order:
1:  21:42:16,062 INFO [STDOUT] Platinum Customer - High Priority - Higher discount
2: 21:42:16,063 INFO [STDOUT] Customer Status: 60
3: 21:42:16,063 INFO [STDOUT] Order Total: 64.92
Next, the org.jboss.soa.esb.samples.quickstart.businessrules.ReviewMessage custom action is executed to print these statements to the log:
1:  21:42:16,063 INFO [STDOUT] { ================ After Order Discount
2: 21:42:16,063 INFO [STDOUT] Customer: user1,Harry,Fletcher,SD,60
3: 21:42:16,064 INFO [STDOUT] Order Priority: 3
4: 21:42:16,064 INFO [STDOUT] Order Discount: 8.5
5: 21:42:16,064 INFO [STDOUT] Order Total: 64.92
6: 21:42:16,064 INFO [STDOUT] } ================ After Order Discount
And there we see our generous 8.5% discount.

Next, the quickstart prints the entire message to the log with this out of the box action:
1:  <action name="sout" class="org.jboss.soa.esb.actions.SystemPrintln" />
And here's how it appears in the server.log:
1:  21:42:16,064 INFO [STDOUT] Message structure:
2: 21:42:16,064 INFO [STDOUT] [{orderHeader=1, java.util.GregorianCalendar[time=1163616328000,areFieldsSet=true,areAllFieldsSet=false,lenient=true,zone=sun.util.calendar.ZoneInfo[id="US/Eastern",offset=-18000000,dstSavings=3600000,useDaylight=true,transitions=235,lastRule=java.util.SimpleTimeZone[id=US/Eastern,offset=-18000000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=?,YEAR=2006,MONTH=10,WEEK_OF_YEAR=?,WEEK_OF_MONTH=?,DAY_OF_MONTH=15,DAY_OF_YEAR=?,DAY_OF_WEEK=4,DAY_OF_WEEK_IN_MONTH=?,AM_PM=?,HOUR=?,HOUR_OF_DAY=13,MINUTE=45,SECOND=28,MILLISECOND=?,ZONE_OFFSET=-18000000,DST_OFFSET=0], 0, 59.97, 64.92, 4.95, , orderItemList=[1,1,364,The 40-Year-Old Virgin ,29.98, 2,1,299,Pulp Fiction,29.99], orderItem=2,1,299,Pulp Fiction,29.99, customer=user1,Harry,Fletcher,SD,60}].
That's a little hard to read. Let's take out the order date/time so that we can better see the customer and orderHeader Beans that we added to the message:
1:  {
2: orderHeader=1, 0, 59.97, 64.92, 4.95,
3: orderItemList=[1,1,364,The 40-Year-Old Virgin ,29.98, 2,1,299,Pulp Fiction,29.99],
4: orderItem=2,1,299,Pulp Fiction,29.99,
5: customer=user1,Harry,Fletcher,SD,60
6: }
Step 5 - Route the Message to its Destination Based on its Content

At this point, the quickstart has finished using the BusinessRulesProcessor, but it's not yet done using Rules. Remember how I said that on the JBossESB in the SOA Platform everything is either a message or a service? Well, one of the main functions performed by the ESB is to route messages to the correct service. These routes can be static, or they can be dynamic, based on the content of a message. Here's where content based routing with Rules comes in. The routing rules are defined in the MyRoutingRules.drl file. Remember how the rules in this file designated the "destinations" of the messages? This next action uses invokes the org.jboss.soa.esb.actions.ContentBasedRouter class to route the messages to their intended destinations.
1:  1  <!-- Use the CBR to route the "scored" order to the appropriate service team -->
2: 2 <action
3: 3 class="org.jboss.soa.esb.actions.ContentBasedRouter"
4: 4 name="ContentBasedRouter">
5: 5 <property name="ruleSet" value="MyRoutingRules.drl" />
6: 6 <property name="ruleReload" value="true" />
7: 7 <property name="destinations">
8: 8 <route-to
9: 9 destination-name="SuperSpecialCustomerService"
10: 10 service-category="ConciergeManager" service-name="ConciergeService" />
11: 11 <route-to
12: 12 destination-name="SpecialCustomerService"
13: 13 service-category="DistributionManager" service-name="DistributionService" />
14: 14 <route-to
15: 15 destination-name="RegularCustomerService"
16: 16 service-category="BasicShipping" service-name="ShipperService" />
17: 17 </property>
18: 18 <property name="object-paths">
19: 19 <object-path esb="body.orderHeader" />
20: 20 <object-path esb="body.customer" />
21: 21 </property>
22: 22 </action>
  • Lines 107-109, 110-112, and 113-115 define the routes. Since our message now has the highest priority, it is routed to the ConciergeService.
Here's what the log shows us. Again, the first line is printed by the rule:
1:  21:42:16,210 INFO [STDOUT] HIGHEST PRIORITY
And the remaining lines are printed by the org.jboss.soa.esb.samples.quickstart.businessrules.ReviewMessage custom action as invoked by the ConciergeService.
1:  21:42:16,309 INFO [STDOUT] { ================ Concierge
2: 21:42:16,309 INFO [STDOUT] Customer: user1,Harry,Fletcher,SD,60
3: 21:42:16,309 INFO [STDOUT] Order Priority: 3
4: 21:42:16,309 INFO [STDOUT] Order Discount: 8.5
5: 21:42:16,310 INFO [STDOUT] Order Total: 64.92
6: 21:42:16,310 INFO [STDOUT] } ================ Concierge
Well, the customer's order was delivered to the very posh ConciergeService, and the quickstart's execution is complete.

Closing Thoughts

OK, let's review what happened. The quickstart defines multiple business rules to examine and modify a message as it is processed by actions executed by ESB services, then it routes that message to the correct destination service. The rules are maintained in .drl files, separate from the services' custom action code, which makes it easier to maintain them.

And, notice what the quickstart did not have to do - the rules were executed and the content based routing was performed through out of the box actions provided by the SOA Platform's JBossESB. This made it possible for the quickstart to concentrate on the Rules business logic.

Acknowledgements

As always, I want to thank the JBoss SOA Platform team and community (especially Kevin Conner and Mark Proctor for their timely review input for this blog post).


References