How to Implement Custom Consequence Exception Handling

Summary

The Default Consequence Exception Handler provides a minimum of information: the rule, where it happened, and the cause, with the Java stack dumps, which usually doen't tell you much. Luckily, Drools lets you set up your own handler, making better use of the available information.

Substituting Your Own Consequence Exception Handler

Overriding the default consequence exception handler is done by setting the Consequence Exception Handler Option of the Knowledge Base Configuration with which the Knowledge Base is created.

    import org.drools.KnowledgeBaseConfiguration;
    import org.drools.KnowledgeBaseFactory;
    import org.drools.conf.ConsequenceExceptionHandlerOption;
    import org.drools.runtime.rule.ConsequenceExceptionHandler;

    KnowledgeBaseConfiguration kBaseConfig =
        KnowledgeBaseFactory.newKnowledgeBaseConfiguration();
    @SuppressWarnings("unchecked")
    Class ehClass = Class)MyConsequenceExceptionHandler.class;
    ConsequenceExceptionHandlerOption cehOption = ConsequenceExceptionHandlerOption.get( ehClass );
    kBaseConfig.setOption( cehOption );
    KnowledgeBase kBase = KnowledgeBaseFactory.newKnowledgeBase( kBaseConfig ); 

A Better Consequence Exception Handler

Looking at the Default Consequence Exception Handler shows that the Activation object of the rule firing that went down the drain is available. Therefore, we'll throw a Runtime Exception of our own making, which will take this Activation as an argument. The remainder is copied from the default.

    package rss.drools.monitor;

    import java.io.Externalizable;
    import org.drools.runtime.rule.ConsequenceExceptionHandler;

    public class MyConsequenceExceptionHandler
        implements ConsequenceExceptionHandler, Externalizable {

        public void readExternal( ObjectInput in ) throws IOException, ClassNotFoundException {}
        public void writeExternal( ObjectOutput out ) throws IOException {}

        public void handleException( Activation activation,
                                     WorkingMemory workingMemory,
                                     Exception exception ){
        throw new MyConsequenceException( exception,
                                          workingMemory, 
                                          activation );
    }
}
So far, this doesn't amount to much, as we have left the hard work to our custom Consequence Exception. The next section shows what we can do with the Activation passed to that Runtime Exception.

A Better Consequence Exception

The constructor takes the cause that's passed to the Exception Handler shown in the previous section, the Working Memory and the Activation of the rule, with the latter two being stored in the extension. These two objects are used in several methods.

public class MyConsequenceException extends RuntimeException {
    private static final long serialVersionUID = 510l;
    private WorkingMemory workingMemory;
    private Activation    activation;

    public ConsequenceException( final Throwable rootCause,
                                 final WorkingMemory workingMemory,
                                 final Activation activation ){
        super( rootCause );
        this.workingMemory = workingMemory;
        this.activation = activation;
    }

    @Override
    public String getMessage() {
        StringBuilder sb = new StringBuilder( "Exception executing consequence for " );
        Rule rule = null;
        
        if( activation != null && ( rule = activation.getRule() ) != null ){
            String packageName = rule.getPackageName();
            String ruleName = rule.getName();
            sb.append( "rule \"" ).append( ruleName ).append( "\" in " ).append( packageName );
        } else {
            sb.append( "rule, name unknown" );
        }
        sb.append( ": " ).append( super.getMessage() );
        return sb.toString();
    }

    public void printFactDump(){
        printFactDump( System.err );
    }

    public void  printFactDump( PrintStream pStream ){
        Collection handles = activation.getFactHandles();
        for( FactHandle handle: handles ){
            Object object = workingMemory.getObject( handle );
            if( object != null ){
                pStream.println( "   Fact " + object.getClass().getSimpleName() +
                                 ": " + object.toString() );
            }
        }
    }

    @Override
    public String toString() {
        return getMessage();
    }
}