Drools supports creation of pluggable operators. This is a small tutorial on how to create a “str” operator that can be used to compare string values in LHS patterns. The operator adds the ability to write patterns such as:
$m : Message( routingValue str[startsWith] "R1" )
$m : Message( routingValue str[endsWith] "R2" )
or
$m : Message( routingValue str[length] 17 )
First thing that needs to be done is to write your evaluator definition. Your Definition class has to implement org.drools.base.evaluators.EvaluatorDefinition which contains all methods needed to work with this new “str” operator, so we have:
public class StrEvaluatorDefinition implements EvaluatorDefinition {
public static final Operator STR_COMPARE = Operator.addOperatorToRegistry(
"str", false);
public static final Operator NOT_STR_COMPARE = Operator
.addOperatorToRegistry("str", true);
private static final String[] SUPPORTED_IDS = { STR_COMPARE
.getOperatorString() };
public enum Operations {
startsWith, endsWith, length;
}
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) {
StrEvaluator evaluator = new StrEvaluator(type, isNegated);
evaluator.setParameterText(parameterText);
return evaluator;
}
@Override
public String[] getEvaluatorIds() {
return SUPPORTED_IDS;
}
@Override
public Target getTarget() {
return Target.FACT;
}
@Override
public boolean isNegatable() {
return true;
}
@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);
}
...
}
The first thing StrEvaluatorDefinition does is registers two operators STR_COMPARE, and NOT_STR_COMPARE using the operator id and a boolean flag which determines if this operator can be negated or not. It also defines the Operations enum which lists all possible operations we can perform (startsWith, endsWith, length). These operations are passed to the operator through angled brackets, similar to what you are already familiar with when writing Drools Fusion patterns, such as for example
this after[0, 3m] $t
The last getEvaluator method gets passed parameters type (type of the operator’s operands), operatorId (self explanatory), isNegated (specifies if you can negate, or use “not” with this operator), parameterText (input parameters in the angle brackets), leftTarget (target fact on left), rightTarget (target fact on right).
This method then creates a new instance of the actual Evaluator Implemention class (StrEvaluator) and passes it the parameterText.
Next thing as you guessed it is to create the actual Evaluator implementation code which has to extend org.drools.base.BaseEvaluator:
public static class StrEvaluator extends BaseEvaluator {
private Operations parameter;
public void setParameterText(String parameterText) {
this.parameter = Operations.valueOf(parameterText);
}
public Operations getParameter() {
return parameter;
}
public StrEvaluator(final ValueType type, final boolean isNegated) {
super(type, isNegated ? NOT_STR_COMPARE : STR_COMPARE);
}
@Override
public boolean evaluate(InternalWorkingMemory workingMemory,
InternalReadAccessor extractor, Object object, FieldValue value) {
final Object objectValue = extractor
.getValue(workingMemory, object);
switch (parameter) {
case startsWith:
return this.getOperator().isNegated() ^ (((String)objectValue).startsWith( (String)value.getValue() ));
case endsWith:
return this.getOperator().isNegated() ^ (((String)objectValue).endsWith( (String)value.getValue() ));
case length:
return this.getOperator().isNegated() ^ (((String)objectValue).length() == ((Long) value.getValue()).longValue() );
default:
throw new IllegalAccessError("Illegal str comparison parameter");
}
}
...
}
The implementation code of an Evaluator defines a number of “evaluate” methods is different circumstances. You can get a more detailed description of this by looking at the code for org.drools.base.BaseEvaluator.
Next thing to do is to actually let our KnowledgeBuilder know about this new operator. For this we can just use KnowledgeBuilderConfiguration and pass it to the kbuilder instance:
KnowledgeBuilderConfiguration builderConf = KnowledgeBuilderFactory
.newKnowledgeBuilderConfiguration();
builderConf.setOption(EvaluatorOption.get("str",
new StrEvaluatorDefinition()));
KnowledgeBuilder kbuilder = KnowledgeBuilderFactory
.newKnowledgeBuilder(builderConf);
Another way of configuring this is using a configuration file in the classpath with the following path and name:
META-INF/drools.packagebuilder.conf
Doing this has the advantage of the Drools Eclipse plugin being able to discover our new operator definition and supporting it in the rules. The file itself is a regular properties file and to configure our str operator we can do:
drools.evaluator.str = com.myproject.StrEvaluatorDefinition
And that’s it. Now you can use the new operator in your rules, for example:
rule routeToR1
when
$m : Message( routingValue str[startsWith] "R1" )
then
# routing to destination R1
end
or
rule routeToDefault
when
$m : Message( routingValue not str[startsWith] "R1" )
then
# routing to default destination
end
or even
rule routeToSpecial
when
$m : Message( routingValue str[startsWith] "R1" && str[endsWith] "R2" && str[length] 17)
then
# routing to super special destination
end