Book Image

Drools JBoss Rules 5.X Developer's Guide

By : Michal Bali
Book Image

Drools JBoss Rules 5.X Developer's Guide

By: Michal Bali

Overview of this book

<p>Writing business rules has always been a challenging task. Business rules tend to change often leading to a maintenance nightmare. This book shows you various ways to code your business rules using Drools, the open source Business Rules Management System.<br /><br />Drools JBoss Rules 5.X Developer's Guide shows various features of the Drools platform by walking the reader through several real-world examples. Each chapter elaborates on different aspects of the Drools platform. The reader will also learn about the inner workings of Drools and its implementation of the Rete algorithm.<br /><br />The book starts with explaining rule basics, then builds on this information by going through various areas like human readable rules, rules for validation, and stateful rules, using examples from the banking domain. A loan approval process example shows the use of the jBPM module. Parts of a banking fraud detection system are implemented with the Drools Fusion module which is the complex event processing part of Drools. Finally, more technical details are shown detailing the inner workings of Drools, the implementation of the ReteOO algorithm, indexing, node sharing, and partitioning.</p>
Table of Contents (22 chapters)
Drools JBoss Rules 5.X Developer's Guide
Credits
About the Author
About the Reviewers
www.PacktPub.com
Preface
Setting Up the Development Environment
Creating Custom Operators
Dependencies of Sample Application
Index

Appendix B. Creating Custom Operators

We've already seen various operators that can be used within rule conditions. These include == and !=; relational operators such as >, <, and >= ; temporal operators such as after, during, and finishes; or others such as matches, which does regular expression matching. In this section we'll define our own custom operator.

The == operators uses the Object.equals or hashCode methods for comparing objects. However, sometimes we need to test if two objects are actually referring to the same instance. This is slightly faster than Object.equals or hashCode comparison (only slightly faster, because the hash code is calculated once for object and then it is cached).

Imagine that we have a rule, which matches on an Account fact and a Customer fact. We want to test if the owner property of Account contains the same instance of a Customer fact as the Customer fact that was matched. The rule might look like this:

rule accountHasCustomer
  when
    $customer : Customer( )    
    Account( owner instanceEquals $customer )
  then
    //..
end

Code listing 1: Rule with a custom operator such as instanceEquals in the custom_operator.drl file

From the previous rule we can see the use of a custom operator named instanceEquals. Most, if not all, Drools operators support a negated version with not:

Account( owner not instanceEquals $customer )

Code listing 2: Condition that uses the negated version of the custom operator

This condition will match on the Account fact whose owner property is of a different instance than the fact bound under the $customer binding.

Some operators support parameters. They can be passed within the angle brackets as we've already seen in Chapter 7, Complex Event Processing, when we were discussing temporal operators.

Based on our requirements we can now write the following unit test for our new instanceEquals operator:

  @Test
  public void instancesEqualsBeta() throws Exception {
    Customer customer = new Customer();
    Account account = new Account();

    session.execute(Arrays.asList(customer, account));
    assertNotFired("accountHasCustomer");

    account.setOwner(new Customer());
    session.execute(Arrays.asList(customer, account));
    assertNotFired("accountHasCustomer");

    account.setOwner(customer);
    session.execute(Arrays.asList(customer, account));
    assertFired("accountHasCustomer");
  }

Code listing 3: Unit test for the accountHasCustomer rule

It tests three use cases. The first one is an account with no customer. The test verifies that the rule didn't fire. In the second use case, the owner property of Account is set to a different customer than what is in the rule session. The rule isn't fired either. Finally, in the last use case, the owner property of Account is set to the right Customer object and the rule fires.

Before we can successfully execute this test, we have to implement our operator and tell Drools about it. We can tell Drools through the PackageBuilderConfiguration method. This configuration is fed into the familiar PackageBuilder instance. The following code listing, which is in fact the unit test setup method, shows how to do it:

@BeforeClass
public static void setUpClass() throws Exception {
  KnowledgeBuilderConfiguration builderConf =
   KnowledgeBuilderFactory.newKnowledgeBuilderConfiguration();
  builderConf.setOption(EvaluatorOption.get(
      "instanceEquals",
      new InstanceEqualsEvaluatorDefinition()));

  knowledgeBase = DroolsHelper.createKnowledgeBase(null,
      builderConf, "custom_operator.drl");
}

Code listing 4: Unit test setup for custom operator test

A new instance of the PackageBuilderConfiguration method is created and a new evaluator definition is added. It represents our new instanceEquals operator, InstanceEqualsEvaluatorDefinition. This configuration is then used to create a KnowledgeBase object.

We can now implement our operator. This will be done in two steps:

  1. Create EvaluatorDefinition, which will be responsible for creating evaluators based on actual rules.

  2. Create the actual evaluator (please note that the implementation should be stateless).

The evaluator definition will be used at rule compile time and the evaluator at rule runtime.

All evaluator definitions must implement the org.drools.base.evaluators.EvaluatorDefinition interface. It contains all methods that Drools needs to work with our operator. We'll now look at the InstanceEqualsEvaluatorDefinition interface. The contents of this class is as follows:

public class InstanceEqualsEvaluatorDefinition implements
    EvaluatorDefinition {
  public static final Operator INSTANCE_EQUALS = Operator
      .addOperatorToRegistry("instanceEquals", false);
  public static final Operator NOT_INSTANCE_EQUALS = Operator
      .addOperatorToRegistry("instanceEquals", true);

  private static final String[] SUPPORTED_IDS = { 
      INSTANCE_EQUALS.getOperatorString() };

  private Evaluator[] evaluator;

  @Override
  public Evaluator getEvaluator(ValueType type,
      Operator operator) {
    return this.getEvaluator(type, operator
        .getOperatorString(), operator.isNegated(), null);
  }

  @Override
  public Evaluator getEvaluator(ValueType type,
      Operator operator, String parameterText) {
    return this.getEvaluator(type, operator
        .getOperatorString(), operator.isNegated(),
        parameterText);
  }

  @Override
  public Evaluator getEvaluator(ValueType type,
      String operatorId, boolean isNegated,
      String parameterText) {
    return getEvaluator(type, operatorId, isNegated,
        parameterText, Target.FACT, Target.FACT);
  }

  @Override
  public Evaluator getEvaluator(ValueType type,
      String operatorId, boolean isNegated,
      String parameterText, Target leftTarget,
      Target rightTarget) {
    if (evaluator == null) {
      evaluator = new Evaluator[2];
    }
    int index = isNegated ? 0 : 1;
    if (evaluator[index] == null) {
      evaluator[index] = new InstanceEqualsEvaluator(type,
          isNegated);
    }
    return evaluator[index];
  }

  @Override
  public String[] getEvaluatorIds() {
    return SUPPORTED_IDS;
  }

  @Override
  public boolean isNegatable() {
    return true;
  }

  @Override
  public Target getTarget() {
    return Target.FACT;
  }

  @Override
  public boolean supportsType(ValueType type) {
    return true;
  }

  @Override
  public void readExternal(ObjectInput in)
      throws IOException, ClassNotFoundException {
    evaluator = (Evaluator[]) in.readObject();
  }

  @Override
  public void writeExternal(ObjectOutput out)
      throws IOException {
    out.writeObject(evaluator);
  }

Code listing 5: Implementation of custom EvaluatorDefinition

The InstanceEqualsEvaluatorDefinition instance contains various information that Drools requires, for example, the operator's ID—whether this operator can be negated and what types it supports.

At the beginning two operators are registered using the Operator.addOperatorToRegistry static method. The method takes two arguments: operatorId and a flag indicating whether this operator can be negated.

Then there are few getEvaluator methods. Drools will call these methods during the rule compilation step. The last getEvaluator method gets passed in the following arguments:

  • type: This is the type of operator's operands.

  • operatorId: This is the identifier of the operator (one evaluator definition can handle multiple IDs).

  • isNegated: This specifies whether this operator can be used with not

  • parameterText: This is essentially the text in angle brackets; the evaluator definition is responsible for parsing this text. In our case it is simply ignored.

  • leftTarget and rightTarget: These specify whether this operator operates on facts, fact handles, or both.

Then the method lazily initializes two implementations of the operator itself, InstanceEqualsEvaluator. Since our operator will operate only on facts and we don't care about the parameter text, we need to cater only for two cases: non-negated operations and negated operations. These evaluators are then cached for another use.

It is worth noting the supportsType method always returns true, since we want to compare any facts regardless of their type.

All Drools evaluators must extend the org.drools.spi.Evaluator interface. Drools provides an BaseEvaluator abstract that we can extend to simplify our implementation. Now we have to implement few evaluate methods for executing the operator under various circumstances. Using the operator with a literal (for example, Account( owner instanceEquals "some literal value" )) or variable (for example, Account( owner instanceEquals $customer )). The implementation of InstanceEqualsEvaluatoroperator is as follows (please note that it is implemented as a static inner class):

  public static class InstanceEqualsEvaluator extends
      BaseEvaluator {

    public InstanceEqualsEvaluator(final ValueType type,
        final boolean isNegated) {
      super(type, isNegated ? NOT_INSTANCE_EQUALS
          : INSTANCE_EQUALS);
    }

    @Override
    public boolean evaluate(
        InternalWorkingMemory workingMemory,
        InternalReadAccessor extractor, Object object,
        FieldValue value) {
      final Object objectValue = extractor.getValue(
          workingMemory, object);
      return this.getOperator().isNegated()
          ^ (objectValue == value.getValue());
    }

    @Override
    public boolean evaluate(
        InternalWorkingMemory workingMemory,
        InternalReadAccessor leftExtractor, Object left,
        InternalReadAccessor rightExtractor, Object right) {
      final Object value1 = leftExtractor.getValue(
          workingMemory, left);
      final Object value2 = rightExtractor.getValue(
          workingMemory, right);
      return this.getOperator().isNegated()
          ^ (value1 == value2);
    }

    @Override
    public boolean evaluateCachedLeft(
        InternalWorkingMemory workingMemory,
        VariableContextEntry context, Object right) {
      return this.getOperator().isNegated()
          ^ (right == ((ObjectVariableContextEntry) 
          context).left);
    }

    @Override
    public boolean evaluateCachedRight(
        InternalWorkingMemory workingMemory,
        VariableContextEntry context, Object left) {
      return this.getOperator().isNegated()
          ^ (left == ((ObjectVariableContextEntry) 
          context).right);
    }

    @Override
    public String toString() {
      return "InstanceEquals instanceEquals";
    }
  }

Code listing 6: Implementation of a custom Evaluator

The operator's implementation just defines various versions of the evaluate method. The first one is executed when evaluating alpha nodes with literal constraints. The extractor is used to extract the field from a fact and the value represents the actual literal. The ^ operator is the standard bitwise exclusive or a Java operator.

The second evaluate method is used when evaluating alpha nodes with variable bindings. In this case the input parameters include left/right extractor and left/right fact (please note that the left and right facts represent the same fact instance).

The third one, evaluateCachedLeft, and the fourth one, evaluateCachedRight, will be executed when evaluating beta node constraints.

For more information please refer to the API and parent class org.drools.base.BaseEvaluator.

Both the evaluator definition and the evaluator should be serializable.