June 22, 2016

Getting Drools 5.x to Operate Smoothly with Java 8

As mentioned in my earlier post, “Getting JasperReports 5.x to Operate Smoothly with Java 8

Upgrading from Java 7 to 8 can involve a lot of moving parts, and there are a number of versions of common libraries that may not work well with Java 8, but most of them can be made to work with Java 8 with a little effort.

Here is another library – Drools 5.x – with upgrade issues. The standard suggestion is to upgrade to Drools 6, but there are enough changes involved in that effort that you may not wish to do that upgrade at the same time. This post will allow you to effectively decouple your Java upgrade from your Drools upgrade. Before going into more details of the workaround or proposed solution, let’s see what the out-of-the-box run-time behavior of Drools 5.x is with Java 8 when compiling rule templates / decision tables where data providers are spreadsheets:

    Caused by: Exception in thread "main" java.lang.RuntimeException: java.lang.RuntimeException: wrong class format
    at org.drools.template.parser.DefaultTemplateRuleBase.readRule
    (DefaultTemplateRuleBase.java:148)
    at org.drools.template.parser.DefaultTemplateRuleBase.
    (DefaultTemplateRuleBase.java:62)
    at org.drools.template.parser.TemplateDataListener.
    (TemplateDataListener.java:74)
    at org.drools.decisiontable.ExternalSpreadsheetCompiler.compile
    (ExternalSpreadsheetCompiler.java:95)
    at org.drools.decisiontable.ExternalSpreadsheetCompiler.compile
    (ExternalSpreadsheetCompiler.java:81)
    at org.drools
    at com.copyright.rup.apc.manuscript.service.impl.rule.
    RulesCompiler.compile(RulesCompiler.java:67)
    ... 1 more
    Caused by: org.eclipse.jdt.internal.compiler.classfmt.
    ClassFormatException
    at org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader.
    (ClassFileReader.java:372)

If you observe the exception stack trace, the exception message “wrong class format” and a little more detail on the root cause – “org.eclipse.jdt.internal.compiler.classfmt.ClassFormatException” – clearly indicates that the exception is thrown by a class file reader when encountering an error in decoding information contained in a .class file. Now, let’s check who is responsible for this compilation process. If we look at the package for the exception class and also look at the dependency graph for Drools libraries:

    +--- org.drools:drools-core:5.5.0.Final
    | +--- org.mvel:mvel2:2.1.3.Final
    | +--- org.drools:knowledge-api:5.5.0.Final
    | | \--- org.slf4j:slf4j-api:1.6.4 -> 1.7.12
    | +--- org.drools:knowledge-internal-api:5.5.0.Final
    | | +--- org.drools:knowledge-api:5.5.0.Final (*)
    | | \--- org.slf4j:slf4j-api:1.6.4 -> 1.7.12
    | \--- org.slf4j:slf4j-api:1.6.4 -> 1.7.12
    +--- org.drools:drools-compiler:5.5.0.Final
    | +--- org.drools:drools-core:5.5.0.Final (*)
    | +--- org.eclipse.jdt.core.compiler:ecj:3.5.1
    | +--- org.mvel:mvel2:2.1.3.Final
    | \--- org.slf4j:slf4j-api:1.6.4 -> 1.7.12

There are two libraries with issues in this tree: JDT Core Batch Compiler version 3.5.1 and MVEL version 2.1.3.Final. The issue with JDT Core is more straightforward - Java 8 requires at least version 4.4.

Here is gradle script snippet with the fix we tried which works to some extent.

compile 'org.eclipse.jdt.core.compiler:ecj:4.4.+'
compile ("org.drools:drools-core:5.+")
compile ("org.drools:drools-compiler:5.+")
compile ("org.drools:drools-decisiontables:5.+")

Technically, we are asking gradle to resolve “org.eclipse.jdt.core.compiler:ecj” to the most current version in the 4.4.x series. You can read the Gradle documentation for more details on dependency resolution.

So, this fixes the first issues with JDT Core, but it just surfaces another.

    Exception in thread "main" java.lang.VerifyError: (class:
    ASMAccessorImpl_4458843621386333353870, method: 
    getKnownEgressType
    signature: ()Ljava/lang/Class;) Illegal type in constant pool at
    java.lang.Class.getDeclaredConstructors0(Native Method) at
    java.lang.Class.privateGetDeclaredConstructors(Class.java:2650) 
    at
    java.lang.Class.getConstructor0(Class.java:2956) at
    java.lang.Class.newInstance(Class.java:403) at
    org.mvel2.optimizers.impl.asm.ASMAccessorOptimizer._initializeAccessor
    ASMAccessorOptimizer.java:725) at
    org.mvel2.optimizers.impl.asm.ASMAccessorOptimizer.
    compileAccessor(ASMccessorOptimizer.java:859) at
    org.mvel2.optimizers.impl.asm.ASMAccessorOptimizer.
    optimizeAccessor(ASMAccessorOptimizer.java:243)

At this point, we considered replacing JDT completely with Janino, but we looked at the bottom of the stack trace, and decided to see what we could fix at the MVEL level. And digging through MVEL code identified the root cause in the lines highlighted below.

image

Because of this code when running with Java 8, the OPCODES_VERSION been considered as OpCodes.V1_2, which is causing the above exception. Actually, this is handled correctly in very recent versions of MVEL code, but we cannot go ahead with MVEL latest version - MVEL2.2.8 or later, as it may not work well with Drools 5.x. So, we updated the code as below. We can do this because MVEL source code is released under the Apache license, which allows us to modify the code:

image

If you observe, we are checking to see whether the Java version is 1.6 or 1.7 or 1.8; we are considering OPCODES_VERSION as 1.6, which is compatible and works in this context and it’s not necessary to pull all complete MVEL Java 8 specific implementations. There are already a few requests to MVEL to release our proposed fix as part of 2.1.x series but there are no actions yet taken. So, we decided to build the patched version of MVEL 2.1.3.Final with the below fix and published as “MVEL-2.1.3.Final-Patch” in our enterprise repository and started consuming it along with the above-mentioned fix across CCC projects.

Here is the final Gradle script snippet with above-mentioned approach.

compile 'org.eclipse.jdt.core.compiler:ecj:4.4.+'
compile ("org.drools:drools-core:5.+"){exclude group: 'org.mvel' }
compile ("org.drools:drools-compiler:5.+") {exclude group: 'org.mvel' }
compile ("org.drools:drools-decisiontables:5.+") {exclude group: 'org.mvel' }
compile ("org.mvel:mvel2:2.1.3.Final-Patch")

With this approach, we de-coupled the Java 8 upgrade from Drools 6 upgrade. I hope this solution may help people who may be stuck with these issues and have run into challenges upgrading to Drools 6 along with Java 8.

References:

https://github.com/mikebrock/mvel/issues/27 https://github.com/mvel/mvel/pull/84 https://bugzilla.redhat.com/show_bug.cgi?id=1078146 https://bugzilla.redhat.com/show_bug.cgi?id=1199965

Mohan Kornipati

Software Architect