Including Field Value In Validation Message Using Spring Validation framework for JSR-303

This blog post demonstrates how to include the value of the field in the error message when using Spring Validation framework support for JSR-303. This has been made possible in Bean Validation API 1.1 with the new support of error message interpolation using EL expression. In particular, the use of the parameter “validatedValue” in the message. This post is inspired by the blog “Better Error Messages With Bean Validation 1.1 In Spring MVC Application” and will focus on the Spring Validation framework alone and demonstrate alternative ways to setup message keys using validateValue

Application Setup

I am using Spring 4 and Hibernate Validator 5.1.1 in the example here.

Spring Configuration

Nothing special with the spring configuration file (app-config.xml) here. The messageAccessor bean is used by the test class to get the validation error messages.

<?xml version=”1.0″ encoding=”UTF-8″?>
<beans xmlns=”http://www.springframework.org/schema/beans&#8221;
xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance&#8221; xmlns:mvc=”http://www.springframework.org/schema/mvc&#8221;
xmlns:context=”http://www.springframework.org/schema/context&#8221;
xsi:schemaLocation=”

http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-4.0.xsd

http://www.springframework.org/schema/context

http://www.springframework.org/schema/context/spring-context-4.0.xsd”&gt;

<!– Creates the JSR-303 Validator –>
<bean id=”validator” class=”org.springframework.validation.beanvalidation.LocalValidatorFactoryBean” >
<property name=”validationMessageSource” ref=”messageSource”/>
</bean>

<bean id=”messageSource” class=”org.springframework.context.support.ResourceBundleMessageSource”>
<property name=”basenames”>
<list>
<value>messages</value>
</list>
</property>
</bean>

<bean id=”messageAccessor” class=”org.springframework.context.support.MessageSourceAccessor”>
<constructor-arg index=”0″ ref=”messageSource”/>
</bean>

</beans>

Order Class

Let say we have the following class we need to validate against. For simplicity, all the 3 fields have to be of at least 10 characters. The javax validation annotation @Size(min = 10) is hence used.


public class Order {

@Size(min = 10, message = “{Size.order.customerName.custom} ${validatedValue}”)
private String customerName;

@Size(min = 10, message =”phone number entered [${validatedValue}] is invalid. It must have at least {min} digits”)
private String phoneNumber;

@Size(min = 10)
private String address;

// getter and setter omitted here …

messages.properties file

The error messages are defined in the messages.properties file (shown below) and are used by the validator to resolve the message keys against.

javax.validation.constraints.Size.message=Invalid size for input: ${validatedValue}
Size.order.customerName.custom=Invalid customer name:

Test Class

Now we are ready to write some tests

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(“/spring/app-config.xml”)
public class OrderValidationTest {

@Autowired
private Validator validator;

@Autowired
private MessageSourceAccessor messageSourceAccessor;

@Test
public void testValidateOrder() {

Order order = new Order();
order.setCustomerName(“a”);
order.setPhoneNumber(“1234″);
order.setAddress(“b”);
BindingResult errors = new BeanPropertyBindingResult(order, “order”);
validator.validate(order, errors);
assertEquals(“Invalid customer name: a”, getValidationErrorMessage(errors, “customerName”));
assertEquals(“phone number entered [1234] is invalid. It must have at least 10 digits”, getValidationErrorMessage(errors, “phoneNumber”));
assertEquals(“Invalid size for input: b”, getValidationErrorMessage(errors, “address”));

}

private String getValidationErrorMessage(BindingResult result, String field) {

if (result.hasErrors()) {
FieldError fieldError = result.getFieldError(field);
return messageSourceAccessor.getMessage(fieldError);
}
return “”;

}

}

The example classes above demonstrate 3 different ways to include the value of the invalid field in the error message, all using the validatedValue parameter supported by Bean Validation 1.1 API.

1. Update default message for the validator

First option is to modify the default message key used by the validation annotation as shown in the field address in the messages.properties file as shown below:

@Size(min = 10)
private String address;

// messages.properties

javax.validation.constraints.Size.message=Invalid size for input: ${validatedValue}

2.  Add message attribute to the validation annotation

To include validatedValue only for certain field in a class, add message attribute to the validator annotation:

@Size(min = 10, message =”phone number entered [${validatedValue}] is invalid. It must have at least {min} digits”)
private String phoneNumber;

3. Add custom message key in the message attribute

It is possible to use message key by enclosing with {} if you do not want to hard coded message text in the class:

@Size(min = 10, message = “{Size.order.customerName.custom} ${validatedValue}”)
private String customerName;

// messages.properties

Size.order.customerName.custom=Invalid customer name:

You cannot use the same key as expected by Spring, i.e. Size.order.customerName. Note also you cannot just add “${validatedValue}” in the text in messages.properties file.

That’s it. Hope this post helps.

Resources

  1. Hibernate Validator 5.1. documentation – See this chapter for more details on message interpolation.

Set up a full broker for Spring 4 STOMP over WebSocket messaging using ActiveMQ

Spring 4 websocket comes with a built-in “simple” broker for handling messaging in memory. In this blog, I will demonstrate how to configure Spring 4 to use a “full” broker, i.e. ActiveMQ, to support STOMP over WebSocket messaging.

1. Enable ActiveMQ for STOMP

First, we need to enable STOMP protocol support in ActiveMQ. This can be done by adding the connector in the activemq.xml file as shown below.

 <transportConnectors>
       <transportConnector name="openwire" uri="tcp://0.0.0.0:61616"/> 
       <transportConnector uri="stomp://localhost:61613"/>
 </transportConnectors>

You may also setup security as mentioned in the ActiveMQ documentation here.

2. Add reactor-tcp jar files

Add to following dependencies to the pom.xml file:

 <!-- Reactor for websocket relay to MQ-->
 <dependency>
      <groupId>org.projectreactor</groupId>
      <artifactId>reactor-core</artifactId>
      <version>1.0.0.RELEASE</version>
 </dependency>
 <dependency>
      <groupId>org.projectreactor</groupId>
      <artifactId>reactor-tcp</artifactId>
      <version>1.0.0.RELEASE</version>
 </dependency>

Or download the jar files reactor-core-1.0.0.RELEASE.jar and reactor-tcp-1.0.0.RELEASE.jar into your classpath

3. Update Spring WebSocket config

Replace the simple broker config below

 <websocket:message-broker application-destination-prefix="/app">
       <websocket:stomp-endpoint path="/hello">
             <websocket:sockjs/>
       </websocket:stomp-endpoint>
       <websocket:simple-broker prefix="/topic,/queue"/>
 </websocket:message-broker>

with a full broker:

 <websocket:message-broker application-destination-prefix="/app">
       <websocket:stomp-endpoint path="/hello">
            <websocket:sockjs/>
       </websocket:stomp-endpoint>
       <websocket:stomp-broker-relay prefix="/topic,/queue"
           relay-host="localhost" relay-port="61613"
           heartbeat-send-interval="20000" heartbeat-receive-interval="20000"/>
 </websocket:message-broker>

If you setup security in ActiveMQ, you will also need to include the attributes client-login, client-passcode, system-login and system-passcode for connecting to  the broker on behalf of the client and the application respectively. By default, they are set as “guest”

Putting in altogether

That’s it. Now to test it out. first fire off ActiveMQ, you should see in the logger messages something like this:

INFO | Listening for connections at: stomp://messageserver:61613
INFO | Connector stomp://localhost:61613 Started

Now start the web server, I am using jetty 9. You should see the following logger message from reactor-tcp:

INFO : 12 Apr 2014 10:47:13,023 (reactor-tcp-io-3) reactor.tcp.netty.NettyTcpClient - CONNECT: [id: 0x71a8bfb1, /127.0.0.1:49617 =>       localhost/127.0.0.1:61613]
 ...
[INFO] Started Jetty Server

Now we are ready to send some messages to the full broker. I use the hello world application in my previous blog to send a message to the topic “greetings”. You should then see the topic created in the ActiveMQ admin console, e.g. http://localhost:8161/admin/topics.jsp.

Setup Spring transactions for MySQL Replication

This post describes how to setup Spring transaction to connect to MySQL database with Replication to direct all write operations to the master and read operations to both master and slaves.

Database setup

The easiest way to setup MySQL database with replication for testing is via Amazon AWS. Create a RDS instance with MySQL as a master and then create a Read Replica from the master. Note AWS uses native MySQL replication to propagate database changes from  the master to the slaves.

JDBC connection

To connect to MySQL Replication using JDBC, made the following 2 changes to your spring config:

  1. Replace the jdbc driver class, e.g. com.mysql.jdbc.Driver, with com.mysql.jdbc.ReplicationDriver.
  2. Modify jdbc connection string to format

             jdbc:mysql:replication://<master db url>,<slave 1 db url>/<dbname>

See MySQL documentation here for list of configuration properties that can be appended to the jdbc URL.

Note the ReplicationDriver wraps one read and one write jdbc connection and can be used transparently with jdbc connection pools such as cp30.

Spring transaction

Use the readOnly attribute of the @Transaction annotation of Spring to direct a transaction to either the master or slave.

For write operations, use @Transactional(readOnly = false) and the database operations will go to the master only

For read only operations, use @Transactional(readOnly = true) and the database operations can go to the slave.

Note:

  1. There are a few old articles on the web indicating the readOnly attribute is ignored. This seems to be outdated and the attribute is working as expected in Spring 3. I am using Spring 3.2.

To verify the setup, I have the following Spring test class:

@RunWith(SpringJUnit4ClassRunner.class)
@TransactionConfiguration(transactionManager="transactionManager", defaultRollback=true)
@ContextConfiguration("classpath:spring/app-config-test.xml")
@Transactional(readOnly = true)
@ActiveProfiles(profiles={"aws"})
public class ProductRepositoryImplTest {
@Autowired
 private SessionFactory sessionFactory;
@Autowired
 @Qualifier("productRepository")
 private IProductRepository repository;
// ... setup details omitted here
@Transactional(readOnly = false)
 @Test
 @Repeat(value = 100)
 public void testReplicationWrite() {
      repository.create(createEntity());
 }
 @Transactional(readOnly = true)
 @Test(expected = GenericJDBCException.class)
 public void testReplicationWriteFail() {
      repository.create(createEntity());
 }
 @Transactional(readOnly = true)
 @Test
 @Repeat(value = 100)
 public void testReplicationRead() {
      repository.findProductByName(RandomStringUtils.randomAlphabetic(10));
 }
}

Note:

  1. The test class above tests the productRepository bean which implements standard CRUD operations (details omitted here) for the entity Product.
  2. The first test method testReplicationWrite() will pass as the readOnly attribute of the @Transactional annotation is set to false.
  3. The second test method testReplicationWriteFail() will throw a GenericJDBCException, as expected by the test method. This confirms that the readOnly attribute works by sending the database operation to the slave and hence the exception. If you remove the “expected = GenericJDBCException.class”, the test will fail with the following error: “org.hibernate.exception.GenericJDBCException: Connection is read-only. Queries leading to data modification are not allowed”
  4. You can also verify on the MySQL master and slave by using the “show processlist” command while the tests are running. The @Repeat annotation runs the tests multiple (100) times to keep the connection process running. Of course, you can also enable the query log for this.

Using Spring 4 WebSocket, sockJS and Stomp support to implement two way server client communication

One exciting new feature of Spring 4 is the support for WebSocket, SockJS and STOMP messaging. This allows two way communication between the server and its clients in a Spring MVC web application using the standard point-to-point and publish-subscribe messaging protocols. In this post, I will demonstrate how to set up a basic boilerplate project to start using this new feature. It is in part based on this article.

Maven Setup

First we need to add the Spring messaging modules in the POM file:

<dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-messaging</artifactId>
 <version>4.0.0.RELEASE</version>
 </dependency>
 <dependency>
 <groupId>org.springframework</groupId>
 <artifactId>spring-websocket</artifactId>
 <version>4.0.0.RELEASE</version>
 </dependency>

Spring MVC Configuration

Next, we need to add the message broker config to the Spring MVC config XML file.

<beans
 ...
 xmlns:websocket="http://www.springframework.org/schema/websocket"
 xsi:schemaLocation="
 ...

http://www.springframework.org/schema/websocket

 http://www.springframework.org/schema/websocket/spring-websocket-4.0.xsd">
<websocket:message-broker application-destination-prefix="/app">
       <websocket:stomp-endpoint path="/hello">
            <websocket:sockjs/>
       </websocket:stomp-endpoint>
       <websocket:simple-broker prefix="/topic"/>
</websocket:message-broker>
<!-- Other MVC config omitted here-->

The main thing here is the set up of the message broker for handling the message exchange between the server and its clients. This is done via the <message-broker> and its child tags. The tag <websocket:simple-broker> indicates we are using in-memory message broker.

It is easy to understand together with the server and client codes so I will include them below first before attempting to give a bit more explanations by cross-referencing the client and server codes.

Spring MVC Controller

Below is my Spring MVC Controller

 @Controller
 public class MessageController {
      @MessageMapping("/hello")
      @SendTo("/topic/greetings")
      public Greeting greeting(HelloMessage message) throws Exception {
           return new Greeting("Hello, " + message.getName() + "!");
     }
}

The method argument HelloMessage and output Greeting are just POJOs representing the body of the messages being sent and returned.

public class Greeting {
    private String content;
    public Greeting(String content) {
           this.content = content;
    }
    public String getContent() {
      return content;
    }
}
public class HelloMessage {
    private String name;
    public String getName() {
        return name;
    }
}

Client sockJS and STOMP codes

On the client side, I use the sockJS protocol fallback option as outlined in the Spring documentation. The javascript codes are included below

// Create stomp client over sockJS protocol (see Note 1)
 var socket = new SockJS("/hello");
 var stompClient = Stomp.over(socket);

 // callback function to be called when stomp client is connected to server (see Note 2)
 var connectCallback = function() {
      alert("connected!");
      stompClient.subscribe('/topic/greetings', function(greeting){
           alert(JSON.parse(greeting.body).content);
      });
 }; 

 // callback function to be called when stomp client could not connect to server (see Note 3)
 var errorCallback = function(error) {
      // display the error's message header:
      alert(error.headers.message);
 };

 // Connect as guest (Note 4)
 stompClient.connect("guest", "guest", connectCallback, errorCallback);

Note

  1. The client starts by create a sockJS by specifying the endpoint (ie. /hello) to connect to and then a stomp client is created over the socket. The endpoint here should match that defined in the Spring MVC configuration in the lines. Note also the 2nd line referring to sockJS.

    <websocket:stomp-endpoint path=”/hello”>
    <websocket:sockjs/>
    </websocket:stomp-endpoint>

  2. Then a callback function is created and assigned to a variable connectCallback. This is called when a successful connection is made by the stomp client. This allows us to start making subscriptions to messages (as in codes, repeated below) and sending messages. Note the subscription is for the topic “/topic/greetings”

    stompClient.subscribe(‘/topic/greetings’, function(greeting){
    alert(JSON.parse(greeting.body).content);
    });

  3. A error callback function is defined if stomp client fails to connect to server.
  4. This line makes the connection registering the callback functions.

Now we are ready to send messages from the client, e.g. using the following javascript function

// function to send message
 function fnSayHi() {
       stompClient.send("/app/hello", {}, JSON.stringify({ 'name': 'Joe' }));
 }

The message will be sent to the Spring MVC message handler method greeting() as defined via the annotation @MessageMapping(“/hello”).

 <websocket:message-broker application-destination-prefix=”/app”>

Note the prefix “/app” is defined in the Spring config as  application-destination-prefix attribute of the message broker: Note also, the use of @SendTo annotation to direct the message to a given destination. I repeat the controller method below

 @MessageMapping("/hello")
 @SendTo("/topic/greetings")
 public Greeting greeting(HelloMessage message) throws Exception {
      return new Greeting("Hello, " + message.getName() + "!");
 }

That’s it for now.

Using JBoss Drools to Implement a E-commerce Promotion Rule Engine – Part 2

In my last post, I outline several common E-commerce promotion rules to be implemented with JBoss Drools Expert. I also describe the domain objects for representing the facts used by the rule engine as well as set up the unit test harness. I will finish off with this post by providing details on how each rule can be defined with the purpose of demonstrating some capabilities of Drools.

Drools Rules

Rule 1: Large order discount

Rule file

// 1. Apply discount if order is over a certain amount
 rule "$50 discount for order over $1000 but less than $2000"
 no-loop
 when
      $order : Order(
           $order.lineTotal >= 1000 &&
           $order.lineTotal < 2000
      )
 then
      modify($order) {
           setOrderDiscountAmount(50);
      }
      System.out.println("Apply $50 discount for order over $1000:");
      System.out.println($order);
 end
rule "200 discount for order over $2000"
 no-loop
 when
      $order : Order(
           $order.lineTotal >= 2000
      )
 then
      modify($order) {
           setOrderDiscountAmount(200);
      }
      System.out.println("Apply $200 discount for order over $2000:");
      System.out.println($order);
 end

Note

  1. 2 rules are defined here. The first one applies when the order total (lineTotal) is between $1000 and $2000. The second rule applies for any order over $2000.
  2. The modify keyword in the action (then) clause tells the rule engine that the rule modifies the fact, in this case the order. This would have triggered the same  rule again. Drools use the rule attribute no-loop to stop this from happening

Unit Test

@Test
 public void testLargeOrderDiscount_over1000() {
      // Add facts to working memory
      Order order = new Order();
      order.addOrderLine(new OrderLine("SKU 123", 1, 1200));
      ksession.insert(order);
      assertEquals(1, ksession.fireAllRules());
 }
Output:
Apply $50 discount for order over $1000:
 Order total(discount): 1200.0(50.0)
 [SKU 123] x1: 1200.0(-0.0) = 1200.0

Rule 2: Clearance products – 10% of list of defined products

Rule File

// 2. Clearance products
 rule "Clearance products"
 no-loop
 when
      $order : Order()
      $item : OrderLine() from $order.lines
      ClearanceProductList(skus contains $item.getSku())
 then
      System.out.println("Apply discount for clearance product " + $item.getSku() + ":");
      $item.setLineDiscountAmount(10);
      System.out.println($order);
 end

Note

  1. Note the use of keyword from in the when clause to iterate list of order items from the order
  2. Each item found in (1) is set in variable $item, which is then used to check in the next line ClearanceProductList(…) whether the line item is one of the discounted product list skus by using the contains keyword.
  3. Discount is applied to the item in the then clause  $item.setLineDiscountAmount(10);

Unit Test

@Test
 public void testClearanceProductDiscount() {
     // Add facts to working memory
     Order order = new Order();
     order.addOrderLine(new OrderLine("SKU 123", 1, 100));
     order.addOrderLine(new OrderLine("SKU 456", 1, 200));
     ClearanceProductList productList = new ClearanceProductList();
     productList.add("SKU 123");
     productList.add("SKU ABC");
     ksession.insert(order);
     ksession.insert(productList);
     assertEquals(2, ksession.fireAllRules());
}
Output:
Apply discount for clearance product SKU 123:
 Order total(discount): 290.0(0.0)
 [SKU 123] x1: 100.0(-10.0) = 90.0
 [SKU 456] x1: 200.0(-0.0) = 200.0

Rule 3: Time based sales – 10% off from a list of defined products within a certain date range, e.g. Christmas sales between 1/12 to 31/12.

Rule File

// 3. Time based sales
rule "Time based sales"
no-loop
 when
      $order : Order()
      $item : OrderLine() from $order.lines
      $sales : TimeBasedSales(
           fromDate < LocalDate.now().toDate(),
           toDate > LocalDate.now().toDate(),
           products contains $item.getSku()
     )
 then
      $item.setLineDiscountAmount($item.getUnitPrice() * $item.getQty() * 0.1);
      System.out.println("Apply time based discount for product " + $item.getSku() + ":");
      System.out.println($order);
end

Note:

  1. The date comparisons for fromDate and toDate in the last lines of the when clause.

Unit Test

@Test
 public void testTimeBasedSales() {
      // Add facts to working memory
      Order order = new Order();
      order.addOrderLine(new OrderLine("SKU 123", 1, 100));
      order.addOrderLine(new OrderLine("SKU 456", 1, 200));
      TimeBasedSales sales = new TimeBasedSales(
      LocalDate.now().minusDays(5).toDate(),
      LocalDate.now().plusDays(5).toDate());
      sales.addProduct("SKU 456");

      ksession.insert(order);
      ksession.insert(sales); 
      assertEquals(2, ksession.fireAllRules()); 
 }
Output:
Apply time based discount for product SKU 456:
Order total(discount): 280.0(0.0)
[SKU 123] x1: 100.0(-0.0) = 100.0
[SKU 456] x1: 200.0(-20.0) = 180.0

Note:

  1. I setup the TimeBasedSales object based on current date. Drools supports concept of pseudo system clock which allows you to test time related rules independent of current real time. See here for details.

Rule 4: Special Tuesday – everything 5% off on Tuesday

Rule File

// 4. 5% discount everything Tuesday
rule "5% discount everything tuesday"
calendars "tuesdays"
 when
      $order : Order()
 then
      System.out.println("Apply special Tuesday discount:");
      $order.setOrderDiscountAmount(0.05 * $order.getLineTotal());
System.out.println($order);
end

Note

  1. This rule makes use of attribute calendars to define the pre-condition that the rule will be considered. The value “tuesdays” correspond to the calendar of same name defined in the knowledge base session. Below is the code in the unit test. Note the use of Quartz to define days of week excluded.
private void getTuesdayCalendar() {
      //in this case I'm using a DailyCalendar but you can use whatever implementation of Calendar you want
       org.quartz.impl.calendar.WeeklyCalendar tuesdays = new org.quartz.impl.calendar.WeeklyCalendar();
       tuesdays.setDaysExcluded(new boolean[] {true, true, true, false, true, true, true, true}); // index 1 = Sunday, etc.
      //convert the calendar into a org.drools.time.Calendar
      org.drools.time.Calendar tuesdayCalendar = QuartzHelper.quartzCalendarAdapter(tuesdays);

      //Register the calendar in the session with a name. You must use this name in your rules.
      ksession.getCalendars().set( "tuesdays", tuesdayCalendar);
}

Unit Test

@Test
 public void testSpecialTuesdaysApplied() {
      // Move to next Tuesday
      advanceToDayOfWeek(2);
      // Add facts to working memory
      Order order = new Order();
      order.addOrderLine(new OrderLine("SKU 123", 1, 100));
      order.addOrderLine(new OrderLine("SKU 456", 1, 200));
      ksession.insert(order);
      assertEquals(2, ksession.fireAllRules());
 }

Output:
Apply special Tuesday discount:
Order total(discount): 300.0(15.0)
[SKU 123] x1: 100.0(-0.0) = 100.0
[SKU 456] x1: 200.0(-0.0) = 200.0

Note

  1. I use the psuedo system clock and method advanceToDayOfWeek(2) to move the clock to next Tuesday to trigger the rule.

Rules Interactions

In a real world commerce environment, a promotion engine will have a considerably bigger number of business rules. Importantly, those rules will be interdependent to each other. Typically, only one rule out of a number of different rules can apply. Alternatively, there is a specific order in which certain rules should apply. Rules can potentially be conflicting. This is where a rule engine like Drools comes in handy as it provides a comprehensive set of features to support these and other common scenarios.

salience

This rule attribute is used to define the priority in which a rule is fired in relation to other rules. For example, a large value discount (Rule 1) should only be applied after all the other line item discounts are applied.

activation-group

This rule attribute is used to group a number of related rules so that when one rule in the group fires, the other rules become inactive. For example, if a product is in clearance category, only the clearance discount is applied.

Final Thoughts

I hope this and last posts demonstrate how common ecommerce problem such as promotion can be solved using a business rule engine such as Drools. Obviously, the business rules will be more complex in the real world but this article should illustrate by example the benefits I listed in Part 1, which I explain in more details below:

  1. Declarative Programming – business rules are defined declaratively in pseudo human readable format (when … then) which promotes common understanding between business and developers.
  2. Separation of Data and Logic – all the logics are defined as rules. This is a significant advantage as it scales much better than having to implement the logics in codes. Also, business rules change often in real world and it is advantageous here as the rules can be modified without need of code changes.
  3. Centralization of Knowledge – the set of rules defined declaratively become the knowledge base of the system (and the business), which will faciliate its update and maintainence.
  4. Explanation of outcomes or actions – the rule engine can answer the why-questions as it knows what rules are fired based on what facts. This is considerably more difficult to implement without the Inference Engine implemented by the rule engine.

There are other ecommerce problems that could benefit from Drools. For example, the determination of product pricing based on order, customer, product costs, etc.

As for Drools, I only touch on the basic features. JBoss has good documentation which can be found here. A few features worth exploring include

  1. Decision table – instead of defining rules in drl files as shown here, drools also support writing rules in MS-Excel spreadsheets
  2. Rule template – instead of defining individual rules, one can define a template with variables and then create the individual rules by filling in the values of the variables from data in database or MS-Excel files.
  3. Event processing in the API

That’s all for now.

Using JBoss Drools to Implement a E-commerce Promotion Rule Engine – Part I

In this and the following articles, I will demonstrate how to implement a basic promotion engine using JBoss Drools framework. The objective here is not to provide a complete implementation but to show how Drools and business rule engine in general can be used to solve common ecommerce problems. Also to demonstrate some of the features provided by Drools Rule Engine.

Introduction

From a software system prospective, a business rule engine allows business to define their business rules declaratively. The system can then, via its inference engine, to match the rules defined with the “facts” it observes at run time. Some of the major advantages of using a rule engine are:

  1. Declarative Programming
  2. Separation of Data and Logic
  3. Centralization of Knowledge
  4. Explanation of outcomes or actions

A more comprehensive list of advantages and more detailed explanation can be found here.

E-commerce Promotion Rules

I define the following business rules for a ecommerce or brick-and-mortar store to decide when and what promotional discount(s) should  be applied for an order. They are fictitious rules that should be commonly understandable.

  1. Large order discount
    1. 5% discount for order total over $1000 and less than $2000
    2. 10% discount for order total over $2000
  2. Clearance products – 10% off from a list of defined products
  3. Time based sales – 10% off from a list of defined products within a certain date range, e.g. Christmas sales between 1/12 to 31/12.
  4. Special Tuesday – everything 5% off on Tuesday

Domain Objects (Facts)

The first step to do is to define the business domain objects which will act as the facts to be processed by the rule engine. Our domain consists of the following classes:

  • Order – represents a single sales order
  • OrderLine – represents an item in an order
  • ClearanceProductList – represents a list of discounted products
  • TimeBasedSales – represented a list of products to apply discount to when date of order falls within the defined date range.

A discount can be applied to each item (i.e. OrderLine) of an order. On top of that, an order discount can then be applied on the order total.

I include the code snippets below:

Order.java

public class Order {

     private double orderDiscountAmount; // order discount amount

     private List<OrderLine> lines = new ArrayList<OrderLine>();

 

     // calculate discounted total amount of all the items in this order
     public double getLineTotal() {
             double lineTotal = 0;
             for (OrderLine line : lines) {
                     lineTotal += line.getLineAmount();
              }
             return lineTotal;
     }

    // getter and setter methods below omitted here

OrderLine.java

public class OrderLine {

 

         private String sku;
         private int qty; // quantity bought
         private double unitPrice; // price for each unit
        private double lineDiscountAmount; // discount applied to this item

 

        public double getLineAmount() {
                 return unitPrice * qty – lineDiscountAmount;
        }

       // getter and setter methods below omitted here

ClearanceProductList

public class ClearanceProductList {

        private List<String> skus = new ArrayList<String>();

 

       // getter and setter methods below omitted here

TimeBasedSales

public class TimeBasedSales {

         private Date fromDate;
         private Date toDate;
         private List<String> products = new ArrayList<String>();

 

       // getter and setter methods below omitted here

 

JUnit Test class

Include below is the unit test class snippets that I will use to run various facts (i.e. orders) against the rule engine. It also demonstrates how to set up Drools.

public class BlogRuleTest {

     private Logger logger = Logger.getLogger(getClass());
     private StatefulKnowledgeSession ksession;

     @Before
      public void setUp() {
          KnowledgeBuilder knowledgeBuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
          knowledgeBuilder.add (

                     ResourceFactory.newClassPathResource(“com/drools/blog/blog.drl”,  getClass()),                                                  ResourceType.DRL);

            // verify rule file has no errors

           if (knowledgeBuilder.hasErrors()) {
                            Iterator<KnowledgeBuilderError> iterator = knowledgeBuilder.getErrors().iterator();
                            while (iterator.hasNext()) {
                                         logger.error(iterator.next().getMessage());
                             }
                            fail(“Rule file has error”);
            }

            KnowledgeSessionConfiguration config =                                                              KnowledgeBaseFactory.newKnowledgeSessionConfiguration();
            config.setOption( ClockTypeOption.get(“pseudo”) );

            KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();
            kbase.addKnowledgePackages(knowledgeBuilder.getKnowledgePackages());

             ksession = kbase.newStatefulKnowledgeSession(config, null);

             // Default to a non-Tuesday
            advanceToDayOfWeek(1);

            getTuesdayCalendar();
 }

 

@After
public void after() {
       ksession.dispose();
}

Note:

  1. The rule file blog.drl is expected to be in folder com/drools/blog/blog.drl under the classpath, e.g. under /src/main/resources.
  2. I create stateful knowledge session above. This is not really required for the task at hand. See Drools documentation for an explanation of the difference between stateless and stateful knowledge sessions.
  3. SessionPseudoClock is used here to allow us to test time based rule. More on this later when we define the “Special Tuesday” rule
  4. The session is disposed after each unit test is run in the after() method.

On to Drools Rules

OK. We are ready to implement the Drools rules. Let do this in the next post.

Setup and Configure Spring Data MongoDB

I recently start working on creating a new app and have to design a database schema for loading data (in csv files) into a database. I end up using MongoDB instead of relational databases like MySQL as the Document database schema of MongoDB fits well with my data. MongoDB provides its own client driver but as always, there is a Spring framework, in this case Spring Data MongoDB, to make things better. I will outline the steps I took to setup and configure a project for developing application using Spring Data MongoDB.

Download and Install MongoDB

Download MongoDB here and following the installation and setup instructions.

Getting Spring Data MongoDB

Add the following Maven dependency:

<!– Spring Data MongoDB –>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>1.3.2.RELEASE</version>
</dependency>

Update Spring config

You can use either annotation or xml files to configure Spring Data MongoDB. Below is my spring config xml file.

<?xml version=”1.0″ encoding=”UTF-8″?>
<beans xmlns=”http://www.springframework.org/schema/beans&#8221;
xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance&#8221; xmlns:mongo=”http://www.springframework.org/schema/data/mongo&#8221;
xmlns:context=”http://www.springframework.org/schema/context&#8221;
xsi:schemaLocation=”http://www.springframework.org/schema/beans

http://www.springframework.org/schema/beans/spring-beans-3.2.xsd

http://www.springframework.org/schema/data/mongo

http://www.springframework.org/schema/data/mongo/spring-mongo-1.0.xsd

http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd”&gt;

<beans profile=”local”>
<context:property-placeholder location=”classpath:mongo.properties”/>
</beans>

<beans>
<mongo:mongo host=”${mongo.host}” port=”${mongo.port}”>
</mongo:mongo>

<bean id=”mongoTemplate” class=”org.springframework.data.mongodb.core.MongoTemplate”>
<constructor-arg ref=”mongo” />
<constructor-arg value=”${mongo.dbname}” />
</bean>

<mongo:repositories base-package=”com.rlee.blog.repository” />

</beans>

My mongo.properties file for a local environment looks like below

mongo.host=localhost
mongo.port=27017
mongo.dbname=testdb

Implement the domain objects

You develop your POJOs with annotation in a similar way as in JPA/Hibernate beans. For example (from Getting Started guide of Spring Data MongoDB),

@Document(collection=”mycustomer”)
public class Customer {

@Id
private String id;

private String firstName;
private String lastName;

Note Spring Data does most work by convention. You don’t need to add the annotation @Document to the class – it will map the entity into a collection of name customer. Here I add the annotation to redirect it to a different collection called mycustomer. Similarly, Spring Data will look for a property id for use as id of the collection and will create one if it could not find one and there is no @Id annotation found in the class. The properties firstName and lastName are mapped into properties of same names in the database.

Implement the Repository

Again from Spring Data MongoDB’s own getting start guide

public interface CustomerRepository extends MongoRepository<Customer, String> {

public Customer findByFirstName(String firstName);
public List<Customer> findByLastName(String lastName);

}

I define an interface here by extending MongoRepository. Together with my setup in Spring config (below), Spring Data will create the implementation class to support standard Crud operations as well as simple query methods like the ones defined above.

<mongo:repositories base-package=”com.rlee.blog.repository” />

Note the method signature in the interface is important:

  • The first method findByFirstName will return the first customer found while the other findByLastName will return all customers with input lastName.
  • The method name is used to define the query to use. It is possible to search using more than one properties. For example, to search customer with given first and last name:

Customer findByFirstNameAndLastName(String firstName, String lastName);

  • You can also sort results by adding an input argument with class Sort. For example,

public List<Customer> findByLastName(String lastName, Sort sort);

Test

Now we are all set for running some tests.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(“classpath:app-config.xml”)
@ActiveProfiles(“local”)
public class CustomerRepositoryTest {

     @Autowired
protected CustomerRepository repository;

               @Before
public void before() {
repository.deleteAll();
// save a couple of customers
repository.save(new Customer(“Alice”, “Smith”));
repository.save(new Customer(“Bob”, “Smith”));
}

@Test
public void testGetByLastName() {
List<Customer> customers = repository.findByLastName(“Smith”);
assertEquals(2, customers.size());
}

}

One last thing. Turn on the debug mode in log4j, When running the above, you should see something like below

DEBUG: 17 Nov 2013 12:35:58,916 (main) org.springframework.data.mongodb.core.MongoDbUtils – Getting Mongo Database name=[testdb]
DEBUG: 17 Nov 2013 12:35:59,109 (main) org.springframework.data.mongodb.core.MongoTemplate – Remove using query: { } in collection: mycustomer.
DEBUG: 17 Nov 2013 12:35:59,116 (main) org.springframework.data.mongodb.core.MongoTemplate – Saving DBObject containing fields: [_class, _id, firstName, lastName]
DEBUG: 17 Nov 2013 12:35:59,117 (main) org.springframework.data.mongodb.core.MongoDbUtils – Getting Mongo Database name=[testdb]
DEBUG: 17 Nov 2013 12:35:59,119 (main) org.springframework.data.mongodb.core.MongoTemplate – Saving DBObject containing fields: [_class, _id, firstName, lastName]
DEBUG: 17 Nov 2013 12:35:59,119 (main) org.springframework.data.mongodb.core.MongoDbUtils – Getting Mongo Database name=[testdb]
DEBUG: 17 Nov 2013 12:35:59,138 (main) org.springframework.data.mongodb.repository.query.MongoQueryCreator – Created query Query: { “lastName” : “Smith”}, Fields: null, Sort: null
DEBUG: 17 Nov 2013 12:35:59,145 (main) org.springframework.data.mongodb.core.MongoTemplate – find using query: { “lastName” : “Smith”} fields: null for class: class com.madman.abs.domain.Customer in collection: mycustomer

That’s it.