Ray, I think we have all of us misunderstood at first what it is you
were
trying to do. For my own part, I somehow read the emails out of order
which
is why I was confused. Ive been playing with Drools off-and-on since
Saturday, trying to figure out an answer for you.
I was expecting more what Groovy has where groovy classes are first
class Java classes. So my questions are:
-
Are the JRuby class methods, etc. not translated to Java byte code?
-
What about the toJava method? It looks interesting but it expects a
Java class object that matches exactly the JRuby class. It has failed
when I try.
-
Is there not a way to simply run the methods or access the JRuby
class
instance variables without creating a separate JRuby session and
having
it parse the object ?
-
Its different from Groovy. Im not the person to explain how or why.
-
They are JVM bytecode, but my understanding is that it looks quite
different from java bytecode under ordinary circumstances.
-
Not sure exactly what you mean here?
-
If you call into Java from JRuby, passing a Ruby object, the Java
class
can definitely call the methods. Sometimes it takes a little bit of
work to
get this to work, either using interfaces, or calling become_java! on
the ruby class, or manipulating the java_signature of the ruby
object.
This is all detailed in the book which you should have by now.
For example:
//ApplicantProto.java
public interface ApplicantProto {
public String getName();
public void setName(String name);
public int getAge();
public void setAge(int age);
}
//TakeApplicant.java
public class TakeApplicant {
public static void alter(ApplicantProto proto) {
proto.setAge(18);
}
}
Then this ruby:
applicant.rb
require 'java’require ‘jruby/core_ext’
class Applicant
attr_accessor :name, :age, :valid
def initialize(name, age)
@name = name
@age = age
@valid = true
end
end
main.rb
require_relative ‘./applicant’
import Java::TakeApplicant
applicant = Applicant.new(“Bob”, 88)TakeApplicant.alter(applicant)
puts applicant.age
#output# => 18
You could also create a method name on the object and interface that
doesnt get translated, aka from setAge() to age=(), just as easily.
But as for Drools
It is very tricky, because Drools uses lots of conventions to know where
to
look for things, and lots of heavy reflection magic, so figuring out
what,
exactly, it needs to see from JRuby was not easy.
I think, initially, what you were looking for is Generating Java Classes
at
Runtime https://github.com/jruby/jruby/wiki/GeneratingJavaClasses.
Unfortunately, that by itself doesnt seem to be enough. Drools actually
looks for Java classes in the filesystem, or at least on the classpath,
before it will run at all, and just having the same name isnt enough.
Drools uses heavy reflection and manipulation of the classloader, and
while
it certainly must be possible to figure it out, the only way I could get
it
to work with Ruby objects was if the Ruby class extended a Java class
that
Drools was actually looking for. Like this:
Folder Structure:
SampleDrools
|
src
|
main
|
java
|
org
|
edubkendo
|
JApplicant.java
|
jruby
|
applicant.rb
main.rb
|
resources
|
META-INF
|
maven
|
pom.properties
|
kmodule.xml
|
rules
|
licenseApplication.drl
/SampleDrools/src/main/java/org/edubkendo/JApplicant.java
package org.edubkendo;
public class JApplicant {
private String name;
private int age;
private boolean valid;
public JApplicant(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public boolean isValid() {
return valid;
}
public void setValid(boolean valid) {
this.valid = valid;
}
}
/SampleDrools/src/main/jruby/applicant.rb
require 'java’require ‘jruby/core_ext’
class Applicant < org.edubkendo.JApplicant
attr_accessor :name, :age, :valid
def initialize(name, age)
@name = name
# Proving we are using the Ruby class, not the Java parent
@age = age - 1
@valid = true
end
def getAge
puts @age
@age
end
def setAge(age)
@age = age
end
def getName
@name
end
def setName(name)
@name = name
end
def isValid
@valid
end
def setValid(valid)
@valid = valid
end
end
Applicant.become_java!
puts Applicant.java_class
/SampleDrools/src/main/jruby/Main.rb
require_relative ‘./applicant’
import Java::org.kie.api.KieServices
import Java::org.kie.api.runtime.KieContainer
import Java::org.kie.api.runtime.KieSession
kie_services = KieServices::Factory.get()
kcontainer = kie_services.getKieClasspathContainer()
ksession = kcontainer.newKieSession(“ksession-rules”)
applicant = Applicant.new(“John S.”, 17)
ksession.insert(applicant.to_java)
ksession.fireAllRules()
puts “Applicant is valid? #{applicant.valid}”
puts applicant.name
/SampleDrools/src/main/resources/rules/licenseApplication.drl
package org.edubkendo
import org.edubkendo.JApplicant
rule “Is of valid age”
when
$a : JApplicant( age < 18 )
then
$a.setValid( false );
System.out.println(“Setting Valid False”);
end
Running Main.rb then outputs:
/home/eric/.rbenv/versions/jruby-1.7.6/bin/jruby -J-cp
/home/eric/IdeaProjects/SampleDrools/out/production/SampleDrools:/home/eric/installs/drools/drools-compiler.jar:/home/eric/installs/drools/antlr-runtime.jar:/home/eric/installs/drools/drools-core.jar:/home/eric/installs/drools/org.eclipse.jdt.core_3.9.1.v20130905-0837.jar:/home/eric/installs/drools/slf4j-api.jar:/home/eric/installs/drools/xpp3_min.jar:/home/eric/installs/drools/drools-decisiontables.jar:/home/eric/installs/drools/kie-internal.jar:/home/eric/installs/drools/poi-ooxml.jar:/home/eric/installs/drools/drools-jsr94.jar:/home/eric/installs/drools/junit.jar:/home/eric/installs/drools/poi.jar:/home/eric/installs/drools/jbpm-bpmn2.jar:/home/eric/installs/drools/jsr94.jar:/home/eric/installs/drools/mvel2.jar:/home/eric/installs/drools/drools-templates.jar:/home/eric/installs/drools/protobuf-java.jar:/home/eric/installs/drools/jbpm-flow-builder.jar:/home/eric/installs/drools/jbpm-flow.jar:/home/eric/installs/drools/kie-api.jar:/home/eric/installs/drools/xstream.jar:/home/eric/installs/drools/xmlpull.jar:/home/eric/installs/slf4j-1.7.5/slf4j-simple-1.7.5.jar
–1.9 -e $stdout.sync=true;$stderr.sync=true;load($0=ARGV.shift)
/home/eric/IdeaProjects/SampleDrools/src/main/jruby/Main.rb
org.edubkendo.JApplicant
[main] INFO org.drools.compiler.kie.builder.impl.ClasspathKieProject -
Found kmodule:
file:/home/eric/IdeaProjects/SampleDrools/out/production/SampleDrools/META-INF/kmodule.xml
[main] INFO org.drools.compiler.kie.builder.impl.KieRepositoryImpl -
KieModule was added:FileKieModule[
ReleaseId=1:1:1file=/home/eric/IdeaProjects/SampleDrools/out/production/SampleDrools]
16
Setting Valid False
Applicant is valid? false
John S.
Hope this helps, I tried using interfaces, which would be the best
approach, but like I mentioned, kept getting errors because of some of
the
reflection Drools is doing. For example, given a similar structure:
src/main/java/org/edubkendo/JIApplicant.java
public interface JIApplicant {
public String getName();
public void setName(String name);
public int getAge();
public void setAge(int age);
public boolean isValid();
public void setValid(boolean valid);
}
src/main/java/rubyobj/TestApplicant.java
package rubyobj;
import org.edubkendo.JIApplicant;
public class TestApplicant implements JIApplicant {
private String name;
private int age;
private boolean valid;
public TestApplicant(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public boolean isValid() {
return valid;
}
public void setValid(boolean valid) {
this.valid = valid;
}
}
src/main/jruby/Main.rb
require_relative ‘./applicant’
import Java::org.kie.api.KieServices
import Java::org.kie.api.runtime.KieContainer
import Java::org.kie.api.runtime.KieSession
kie_services = KieServices::Factory.get()
kcontainer = kie_services.getKieClasspathContainer()
ksession = kcontainer.newKieSession(“ksession-rules”)
applicant = Java::rubyobj::TestApplicant.new(“Johny ParseRight”, 15)
ksession.insert(applicant)
ksession.fireAllRules()
puts “Applicant is valid? #{applicant.valid}”
puts applicant.name
puts applicant.classputs applicant.java_class
src/main/resources/rules/licenseApplication.drl
package org.edubkendo
import org.edubkendo.JIApplicant
rule “Is of valid age”
when
$a : JIApplicant( age < 18 )
then
$a.setValid( false );
System.out.println(“Setting Valid False”);
end
We get the output we expect:
/home/eric/.rbenv/versions/jruby-1.7.6/bin/jruby -J-cp
/home/eric/IdeaProjects/SampleDrools/out/production/SampleDrools:/home/eric/installs/drools/drools-compiler.jar:/home/eric/installs/drools/antlr-runtime.jar:/home/eric/installs/drools/drools-core.jar:/home/eric/installs/drools/org.eclipse.jdt.core_3.9.1.v20130905-0837.jar:/home/eric/installs/drools/slf4j-api.jar:/home/eric/installs/drools/xpp3_min.jar:/home/eric/installs/drools/drools-decisiontables.jar:/home/eric/installs/drools/kie-internal.jar:/home/eric/installs/drools/poi-ooxml.jar:/home/eric/installs/drools/drools-jsr94.jar:/home/eric/installs/drools/junit.jar:/home/eric/installs/drools/poi.jar:/home/eric/installs/drools/jbpm-bpmn2.jar:/home/eric/installs/drools/jsr94.jar:/home/eric/installs/drools/mvel2.jar:/home/eric/installs/drools/drools-templates.jar:/home/eric/installs/drools/protobuf-java.jar:/home/eric/installs/drools/jbpm-flow-builder.jar:/home/eric/installs/drools/jbpm-flow.jar:/home/eric/installs/drools/kie-api.jar:/home/eric/installs/drools/xstream.jar:/home/eric/installs/drools/xmlpull.jar:/home/eric/installs/slf4j-1.7.5/slf4j-simple-1.7.5.jar
–1.9 -e $stdout.sync=true;$stderr.sync=true;load($0=ARGV.shift)
/home/eric/IdeaProjects/SampleDrools/src/main/jruby/Main.rb
org.edubkendo.JApplicant
[main] INFO org.drools.compiler.kie.builder.impl.ClasspathKieProject -
Found kmodule:
file:/home/eric/IdeaProjects/SampleDrools/out/production/SampleDrools/META-INF/kmodule.xml
[main] INFO org.drools.compiler.kie.builder.impl.KieRepositoryImpl -
KieModule was added:FileKieModule[
ReleaseId=1:1:1file=/home/eric/IdeaProjects/SampleDrools/out/production/SampleDrools]
Setting Valid False
Applicant is valid? false
Johny ParseRight
Java::Rubyobj::TestApplicant
rubyobj.TestApplicant
Process finished with exit code 0
But if I change Main.rb:
require_relative ‘./applicant’
import Java::org.kie.api.KieServices
import Java::org.kie.api.runtime.KieContainer
import Java::org.kie.api.runtime.KieSession
kie_services = KieServices::Factory.get()
kcontainer = kie_services.getKieClasspathContainer()
ksession = kcontainer.newKieSession(“ksession-rules”)
applicant = Applicant.new(“John S.”, 17)
ksession.insert(applicant.to_java)
ksession.fireAllRules()
puts “Applicant is valid? #{applicant.valid}”
puts applicant.name
and then change applicant.rb to implement the JIApplicant interface,
instead of extending the JApplicant class:
require 'java’require ‘jruby/core_ext’
class Applicant < org.edubkendo.JApplicantclass Applicant
include org.edubkendo.JIApplicant
attr_accessor :name, :age, :valid
def initialize(name, age)
@name = name
# Proving we are using the Ruby class, not the Java parent
@age = age - 1
@valid = true
end
def getAge
puts @age
@age
end
def setAge(age)
@age = age
end
def getName
@name
end
def setName(name)
@name = name
end
def isValid
@valid
end
def setValid(valid)
@valid = valid
end
end
Applicant.become_java!
puts Applicant.java_class
We get the following stacktrace:
/home/eric/.rbenv/versions/jruby-1.7.6/bin/jruby -J-cp
/home/eric/IdeaProjects/SampleDrools/out/production/SampleDrools:/home/eric/installs/drools/drools-compiler.jar:/home/eric/installs/drools/antlr-runtime.jar:/home/eric/installs/drools/drools-core.jar:/home/eric/installs/drools/org.eclipse.jdt.core_3.9.1.v20130905-0837.jar:/home/eric/installs/drools/slf4j-api.jar:/home/eric/installs/drools/xpp3_min.jar:/home/eric/installs/drools/drools-decisiontables.jar:/home/eric/installs/drools/kie-internal.jar:/home/eric/installs/drools/poi-ooxml.jar:/home/eric/installs/drools/drools-jsr94.jar:/home/eric/installs/drools/junit.jar:/home/eric/installs/drools/poi.jar:/home/eric/installs/drools/jbpm-bpmn2.jar:/home/eric/installs/drools/jsr94.jar:/home/eric/installs/drools/mvel2.jar:/home/eric/installs/drools/drools-templates.jar:/home/eric/installs/drools/protobuf-java.jar:/home/eric/installs/drools/jbpm-flow-builder.jar:/home/eric/installs/drools/jbpm-flow.jar:/home/eric/installs/drools/kie-api.jar:/home/eric/installs/drools/xstream.jar:/home/eric/installs/drools/xmlpull.jar:/home/eric/installs/slf4j-1.7.5/slf4j-simple-1.7.5.jar
–1.9 -e $stdout.sync=true;$stderr.sync=true;load($0=ARGV.shift)
/home/eric/IdeaProjects/SampleDrools/src/main/jruby/Main.rb
rubyobj.Applicant
[main] INFO org.drools.compiler.kie.builder.impl.ClasspathKieProject -
Found kmodule:
file:/home/eric/IdeaProjects/SampleDrools/out/production/SampleDrools/META-INF/kmodule.xml
[main] INFO org.drools.compiler.kie.builder.impl.KieRepositoryImpl -
KieModule was added:FileKieModule[
ReleaseId=1:1:1file=/home/eric/IdeaProjects/SampleDrools/out/production/SampleDrools]
ClassFieldAccessorCache.java:124:in getClass': org.drools.core.RuntimeDroolsException: Unable to resolve class 'rubyobj.Applicant' from ClassFieldAccessorCache.java:46:in
getClassObjectType’
from ClassObjectTypeConf.java:89:in <init>' from ObjectTypeConfigurationRegistry.java:71:in
getObjectTypeConf’
from NamedEntryPoint.java:164:in insert' from AbstractWorkingMemory.java:1149:in
insert’
from AbstractWorkingMemory.java:1093:in insert' from StatefulKnowledgeSessionImpl.java:308:in
insert’
from NativeMethodAccessorImpl.java:-2:in invoke0' from NativeMethodAccessorImpl.java:62:in
invoke’
from DelegatingMethodAccessorImpl.java:43:in invoke' from Method.java:483:in
invoke’
from JavaMethod.java:455:in invokeDirectWithExceptionHandling' from JavaMethod.java:316:in
invokeDirect’
from InstanceMethodInvoker.java:61:in call' from CachingCallSite.java:326:in
cacheAndCall’
from CachingCallSite.java:170:in call' from CallOneArgNode.java:57:in
interpret’
from NewlineNode.java:105:in interpret' from BlockNode.java:71:in
interpret’
from RootNode.java:129:in interpret' from ASTInterpreter.java:121:in
INTERPRET_ROOT’
from Ruby.java:837:in runInterpreter' from Ruby.java:2722:in
loadFile’
from ExternalScript.java:66:in load' from LoadService.java:359:in
load’
from RubyKernel.java:1109:in loadCommon' from RubyKernel.java:1101:in
load19’
from RubyKernel$INVOKER$s$0$1$load19.gen:-1:in call' from DynamicMethod.java:210:in
call’
from DynamicMethod.java:206:in call' from MethodHandle.java:636:in
invokeWithArguments’
from InvocationLinker.java:155:in invocationFallback' from -e:1:in
file’
from -e:-1:in load' from Ruby.java:810:in
runScript’
from Ruby.java:803:in runScript' from Ruby.java:672:in
runNormally’
from Ruby.java:521:in runFromMain' from Main.java:395:in
doRunFromMain’
from Main.java:290:in internalRun' from Main.java:217:in
run’
from Main.java:197:in main' from NativeMethodAccessorImpl.java:-2:in
invoke0’
from NativeMethodAccessorImpl.java:62:in invoke' from DelegatingMethodAccessorImpl.java:43:in
invoke’
from Method.java:483:in invoke' from Main.java:117:in
invoke’
from Main.java:88:in start' from Main.java:64:in
main’
Process finished with exit code 1
You can take a look at
ProjectClassLoader.javahttps://github.com/droolsjbpm/drools/blob/master/drools-core/src/main/java/org/drools/core/common/ProjectClassLoader.java#L78to
see the source of the problem. Or at least, part of it. I dont know
much about manipulating ClassLoaders in Java, and really cant justify
putting in more time to figure it out. But hopefully this gets you
started.
Personally, Id take an approach of making a java class that drools can
find, then just pulling the data from the ruby class and instantiating
instances of the java classes from jruby, and feeding those to drools,
somewhat like I did in the middle example. Seems the easiest route, imo.
Ive pushed my repo to github as an IDEA
projecthttps://github.com/edubkendo/SampleDroolsif you want to poke
around.