October, 2009

Oct 09

Java multiple class loaders issue

I was facing a rather subtle bug building a decision engine using Drools. Not to get into details of drools here, but the following mostly applies to many rules engines. The basic architecture of the decisions engine is that rules are written using a drools-based rule DSL. These rules are then applied to “facts”, which in the context of java, are just POJOs. These POJOs can be defined using standard java classes, or using Drools declare facilities which allow you to declare fact types in the actual rules file and then query it using JavaBean like helper utilities in the java application. In our decisions engine, we allow to define types either using drools declare and/or using groovy to define the fact object, which is then parsed, defined, and linked by GroovyClassLoader at runtime, making it available to the working memory of the drools engine as well as its rule parser.

Everything seemed like it was working using Drools defined facts using declare. I was able to dynamically create and query the fact types using reflection. This was great, as we can inject different rules and deploy functionality on the fly. I then created a groovy class, parsed it and injected into the drools package builder as well as its working memory.

Code I used to test the rule inference on the fact type.

def domainClass = '''
  package com.buycentives.types
  import com.buycentives.incengine.*
  class Input {
    String name
    int age
    boolean valid
Class inputClass = new GroovyClassLoader().parseClass(domainClass)
def input = inputClass.newInstance()
input.name = "Ilya"
input.age = 25

/// Here we inject the fact and fire the rules, see the following java code snippet

// This code was originally failing
assertEquals input.valid, true

Here is the code I used to assemble the working memory. This code is more modularized in my code and I tried to compile it into a snippet, though I might of missed something and some object instantiations are not shown.

ClassLoader loader = new GroovyClassLoader()
PackageBuilderConfiguration config = new PackageBuilderConfiguration();

KnowledgeBuilder kBldr = KnowledgeBuilderFactory.newKnowledgeBuilder(config);
kBldr.add(ruleResource, ResourceType.DRL);

if (kBldr.hasErrors())
  throw new RuntimeException("Unable to compile rules. " + kBldr.getErrors().toString());

KnowledgeBase kb = KnowledgeBaseFactory.newKnowledgeBase(new RuleBaseConfiguration(loader));
StatelessKnowledgeSession kSession = kBase.newStatelessKnowledgeSession();

The rule that was being asserted

package com.buycentives.incengine.rules

import java.util.Date
import com.buycentives.types.Input

rule "Assert for age"
    $input: Input( age >= 20, age < 30 )

To make the story short, the rule never fired, although I verified that the Input object instance was injected into the working memory, though never changing the fact’s valid property. I beat my head against the wall over and over and over and finally I realized what was going on. It’s java’s class loader caveat. It’s not really a bug, rather a feature I guess. It’s not really documented in the javadocs, but I’ve seen references to this caveat before.

When you load the same class using two different class loaders, inside the jvm, they are considered two different classes, although logically they are the same, therefore failing the Class.isAssignableFrom and instanceof conditional test.

So the following test would fail…

def domain = "class Test {}"

GroovyClassLoader loader1 = new GroovyClassLoader()
Class clazz1 = loader1.parseClass(domain)

GroovyClassLoader loader2 = new GroovyClassLoader()
Class clazz2 = loader2.parseClass(domain)

assertTrue clazz1.isAssignableFrom(clazz2)

So the lesson here, beware of injecting class loaders, especially when you’re comparing classes that were loaded with a different class loader than the class loader of the thread that’s inferring equality.

Also, beware of GroovyClassLoader.parseClass method. It parses, defines, and links the class, but if you then use it again on the same class, the resulting classes are different.

The following test fails…

GroovyClassLoader loader = new GroovyClassLoader()
Class clazz1 = loader.parseClass(domain)
Class clazz2 = loader.parseClass(domain)

assertTrue clazz1.isAssignableFrom(clazz2)

Also, this link from “Java Specialist Newsletter” is a good source of some interesting class loader information.