I am trying to use a ClassLoader to load classes from .class files at runtime and use them in Drools rules (Drools 7.52.0). I am using this custom ClassLoader which reads from a file and uses ClassLoader.defineClass()
to load a class. It’s similar to a URLClassLoader:
package example; import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.FileInputStream; import java.io.IOException; public class DynamicClassLoader extends ClassLoader { public DynamicClassLoader(ClassLoader parent) { super(parent); } /** * Define a class from a .class file and return it * @param filepath path to a .class file * @return new Class or null if error */ public Class<?> classFromFile(String filepath) { try { // Read .class file and write bytes to buffer BufferedInputStream input = new BufferedInputStream(new FileInputStream(filepath)); // Write file contents to buffer ByteArrayOutputStream buffer = new ByteArrayOutputStream(); int data = input.read(); while(data != -1){ buffer.write(data); data = input.read(); } byte[] classData = buffer.toByteArray(); // contents of .class file input.close(); return defineClass(null, classData, 0, classData.length); } catch (IOException | ClassFormatError e) { e.printStackTrace(); } return null; } }
I can use the ClassLoader to load a class, construct an instance, and access its methods. However, even though I pass the ClassLoader to Drools through the methods KieServices.newKieBuilder
and KieServices.newKieContainer
, Drools can’t compile the rule. Here is my Main.java:
package example; import java.io.IOException; import org.kie.api.builder.model.KieBaseModel; import org.kie.api.builder.model.KieModuleModel; import org.kie.api.runtime.KieSession; import org.kie.api.KieServices; import org.kie.api.builder.KieBuilder; import org.kie.api.builder.KieFileSystem; import org.kie.api.builder.Message; public class Main { private static KieServices kServices = KieServices.Factory.get(); private static KieFileSystem kFileSys; private static KieSession kSession; private static DynamicClassLoader classLoader = new DynamicClassLoader(Main.class.getClassLoader()); public static void main(String[] args) throws IOException, ClassNotFoundException { // Load Person class classLoader.classFromFile("Person.class"); // Instantiate Person Class<?> personClass = classLoader.loadClass("facts.Person"); Object person = null; try { // Instantiate person and access methods using reflection person = personClass.getConstructor(String.class, Integer.class).newInstance("Alice", 49); System.out.println(person.getClass() + ": name = " + (String) personClass.getMethod("getName").invoke(person)); } catch (Exception e) { System.out.println("Error instantiating person"); e.printStackTrace(); } // Create a KieSession with a rule that uses the Person class String drl = String.join("n", "import facts.Person;", "rule "person"", " when", " $p : Person()", " then", " System.out.println($p.getName());", "end" ); initializeKieSession(drl); kSession.insert(person); kSession.fireAllRules(); } /** * Create a KieSession using the given ruleset * @param drl a ruleset string in Drools Rule Language */ private static void initializeKieSession(String drl) { // Create module model KieModuleModel kModMod = kServices.newKieModuleModel(); KieBaseModel kBaseMod = kModMod.newKieBaseModel("KBase_std").setDefault(true); kBaseMod.newKieSessionModel("KSession_std").setDefault(true); // Create file system with module model kFileSys = kServices.newKieFileSystem(); kFileSys.writeKModuleXML(kModMod.toXML()); // Write rules kFileSys.write("src/main/resources/person.drl", drl); KieBuilder kBuilder = kServices.newKieBuilder(kFileSys, classLoader).buildAll(); boolean errors = kBuilder.getResults().hasMessages(Message.Level.ERROR); if (errors) { for (Message message : kBuilder.getResults().getMessages()) System.out.println(message.getText()); } // new KieSession kSession = kServices.newKieContainer( kServices.getRepository().getDefaultReleaseId(), classLoader).getKieBase().newKieSession(); } }
Compiling this rule (using KieBuilder.buildAll()
) gives the error
Rule Compilation error Only a type can be imported. facts.Person resolves to a package. facts.Person cannot be resolved to a type.
If I don’t pass the ClassLoader to the KieBuilder, I get two additional errors:
Unable to resolve ObjectType 'Person'. $p cannot be resolved.
So my ClassLoader is doing something, but not giving Drools full access to any classes it has loaded. How can I fix this? I have spent days on this problem, and I can’t find anything that helps.
Advertisement
Answer
It looks like the contents of any loaded class files also need to be written to the KieFileSystem before compiling.
So to give Drools full access to a class, the following is required:
ClassLoader classLoader; // Use classLoader to load external classes ... // Copy class definitions to the KieFileSystem for (/*each loaded class*/) { String filename = packageName + '/' + className + ".class"; kFileSys.write(filename, byteArrayOfClassFileContents); } // Pass classLoader to both newKieBuilder and newKieContainer KieServices kServices = KieServices.Factory.get(); KieBuilder kBuilder = kServices.newKieBuilder(kFileSys, classLoader).buildAll(); KieContainer kContainer = kServices.newKieContainer( kServices.getRepository().getDefaultReleaseId(), classLoader);
Note that it is important to write class files inside their packages in the root of the KieFileSystem. For example, the definition for class foo.bar.Baz
should be written to "foo/bar/Baz.class"