Book Image

Drools JBoss Rules 5.0 Developer's Guide

By : Michal Bali
Book Image

Drools JBoss Rules 5.0 Developer's Guide

By: Michal Bali

Overview of this book

<p>Business rules can help your business by providing a level of agility and flexibility. As a developer, you will be largely responsible for implementing these business rules effectively, but implementing them systematically can often be difficult due to their complexity. Drools, or JBoss Rules, makes the process of implementing these rules quicker and handles the complexity, making your life a lot easier!<br /><br />This book guides you through all of the features of Drools, such as dynamic rules, the event model, and Rete implementation with high performance indexing. It will help you to set up the JBoss Rules platform and start creating your own business. It's easy to start developing with Drools if you follow its real-world examples that are intended to make your life easier.<br /><br />Starting with an introduction to the basic syntax that is essential for writing rules, the book will guide you through validation and human-readable rules that define, maintain, and support your business agility. As a developer, you will be expected to represent policies, procedures and. constraints regarding how an enterprise conducts its business; this book makes it easier by showing you it can be done.<br /><br />A real-life example of a banking domain allows you to see how the internal workings of the rules engine operate. A loan approval process example shows the use of the Drools Flow module. Parts of a banking fraud detection system are implemented with Drools Fusion module, which is the Complex Event Processing part of Drools. This in turn, will help developers to work on preventing fraudulent users from accessing systems in an illegal way.<br /><br />Finally, more technical details are shown on 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.0 Developer's Guide
Credits
Foreword
About the Author
About the Reviewers
Preface
Development Environment Setup
Custom Operator
Dependencies of Sample Application
Index

Appendix B. Custom Operator

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

The == operator uses Object.equals/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/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 Account owner property contains the same instance of Customer fact as the Customer fact that was matched. The rule is as follows:

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

Code listing 1: Rule with instanceEquals custom operator in custom_operator.drl file.

From the rule in code listing 1, we can see the use of the instanceEquals custom operator . 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 Account fact, whose owner property is of different instance than the fact bound under $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 (for example, this after[0, 3m] $event2).

Based on our requirements, we can now write this 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, Account owner 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, Account owner 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 KnowledgeBuilderConfiguration. This configuration is fed into the familiar KnowledgeBuilder. 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 KnowledgeBuilderConfiguration 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.

We can now implement our operator. This will be done it 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 in order to work with our operator. We'll now look at InstanceEqualsEvaluatorDefinition. The contents of this class are 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 class contains various information that Drools requires—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. This 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: It is the type of operator's operands.

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

  • isNegated : It is specified if this operator can be used with not.

  • parameterText : It 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 : It specifies if this operator operates on facts, fact handles, or both.

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

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

All Drools evaluators must extend the org.drools.spi.Evaluator interface (Note that this is the old API that will be replaced with org.drools.runtime.rule.Evaluator interface in the future). Drools provides an abstract BaseEvaluator that we can extend in order to simplify our implementation. Now, we have to implement a 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 a variable (for example, Account( owner instanceEquals $customer )). The InstanceEqualsEvaluator operator's implementation 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 custom Evaluator .

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

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

The third method (evaluateCachedLeft) and fourth method (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.