Semantics Provider Interface
Overview

The Semantic Provider Interface (SPI) allows for extending Drools to more natively support the semantics of your domain. The SPI defines a handful of interfaces that may be implemented to create new rule primitives. Rule primitives are the parts that are stitched together to form a concrete rule.

Each rule operates against some set of objects. Each object may be categorized in one of many ways. An XML documentment might be categorized based upon the root element when viewed within the semantics of XML. But with the semantics of the Java language, each would simply be an instance of some Document class, indifferentiable from others.

An ObjectType implementation is required to contain only a single method, matches(...), which serves the same role as the instanceof operator in Java. It inspects the given object and returns simple a boolean true if the object is considered to match the type or false if not.

For example, an object-type used within an insurance system for a the employees of a company might be used to differentiate young employees from old employees.

public boolean matches(Object object)
{
    if ( ! object instanceof Employee )
    {
        return false
    }

    Employee employee = (Employee) object;

    return ( employee.getAge() > 50 );
}

A set of Conditions is used by each rule to determine when it should fire. Each condition is given a set of objects to test against, returning either boolean true or false. The combinations of objects that are presented to the condition is a Tuple. A tuple is a map-like structure that maintains a correlation between a fact object and the Declaration that it is bound to.

For example, a rule might involve two Person objects, where the condition is "p1 is mother of p2". In this case, variable, p1, and p2, matches a declaration. Two objects, o1 and o2, can be bound to the declaration 4 different ways:

  1. { p1=o1, p2=o2 } ... Object #1 bound to p1, Object #2 bound to p2.
  2. { p1=o2, p2=o1 } ... Object #2 bound to p1, Object #1 bound to p2.
  3. { p1=o1, p2=o1 } ... Object #1 bound to both p1 and p2.
  4. { p1=o2, p2=o2 } ... Object #1 bound to both p1 and p2.

In this scenario, only one of the first two tuples will ever satisfy the condition. The particular binding is significant and each permutation must be tested.

public boolean isAllowed(Tuple tuple)
    throws ConditionException
{
    Person p1 = tuple.get( this.p1Decl );
    Person p2 = tuple.get( this.p2Decl );

    return p1.isMotherOf( p2 );
}

The Rete-OO algorithm requires knowledge about what declarations are being tested within each condition in order to build efficient datastructures. Each condition implementation therefore is required to implement the getRequiredTupleMembers() method. In the example above, the entire rule may invole a complex relationship between 12 people, while this particular condition is only relevant to objects p1 and p2, but has no need for p3 through p12.

public Declaration getRequiredTupleMembers()
{
    return new Declaration[] { this.p1Decl, this.p2Decl };
}

When a rule has been selected for execution, a Consequence determines what actions are taken. Like a Condition, each consequence is presented a Tuple of objects bound to declarations. Additionally it is provided the WorkingMemory so that it may, as a result of firing, manipulate facts.

public void invoke(Tuple tuple,
                   WorkingMemory workingMemory)
    throws ConsequenceException
{
    Cheese cheese = (Cheese) tuple.get( this.cheeseDecl );
    Toast  toast  = (Toast) tuple.get( this.toastDecl );

    workingMemory.assertObject( new Breakfast( cheese, toast ) );
}