RubyObject.toJava

I am trying to pass a JRuby Object to Java. I need to make sure the
class name that Java sees is recognizable and not just RubyObject. The
point is to be able to call methods. Without a recognizable class, how
can I call the actual methods and access the instance variables from
Java.

It seems most access Java from JRuby and not the other way around.

Ray

Ray -

Not a direct answer to your question perhaps, but…

I have an example of Ruby classes called by Java in my Game of Life
Swing
app written in JRuby (see
https://github.com/keithrbennett/life_game_viewer/blob/master/lib/life_game_viewer/view/life_game_viewer_frame.rbfor
examples). Many/most of the classes in this file are Ruby
implementations of Java (Swing) interfaces or Ruby subclasses of Java
classes.

The Java Swing framework has no problem calling their methods. I doubt
that the Ruby class names make it to the Java side, but it doesn’t seem
to
matter.

As far as accessing the instance variables go, I don’t think you can
even
do that in Ruby – you need methods (e.g. those generated by
attr_accessor
and company) to do that. (Well, you could use metaprogramming stuff
like
instance_variable_get, etc., but it’s not a great idea for other classes
to
know its implementation details anyway.)

Which brings me to another point I think Charlie made a while back. Try
to
minimize the traffic (and therefore adaptation/conversion code) across
the
Java/Ruby divide. I suggest limiting Java’s access to as small a surface
area as possible. This low coupling / high cohesion is a good idea in
general anyway, but especially here.

  • Keith

Ray -

Sorry, I just reread your message about needing to have a specific class
name for Drools…I guess my answer won’t help you then.

  • Keith

I am using the scripting container to pass java objects into ruby and
using
ruby objects with java. but as you realized already the ruby side is
straight forward, the java side is not.

https://github.com/sonatype/nexus-ruby-support/tree/master/nexus-ruby-tools

-christian

Eric, I think you have hit on what I am looking for. I did order the
book, though it was sold out at Pragmatic so I got it from Amazon…
hopefully I will see “shipped” shortly…

Yes I could create wrapper class. I will have to see what they suggest
in the book, but the idea would be to have standard interface with
standard code that could be extended. The base interface would allow
dynamic access to the JRuby class methods or instance variables. The
reason for extending it would be to make the Drools rules expressive
(i.e., match meaningful names). It would be make method calls a bit
more verbose in that the first parameter would be the method name
followed by parameters. The interface to the Drools engine would
receive the object, place the class in the named extension and insert
it.

I will have read some more and experiment but that is what I am thinking
for now.

Thanks,

Ray

Eric, I am looking for the book to come today. I did realize though
that a lot of the examples of Embedding Ruby require running another
instance of JRuby and context. In my case, I am looking to consume
JRuby objects created by Rails in a Java program.

  1. I wrote a Java program to do reflection and dynamically execute
    named methods or get/set instance variables.
  2. This works great with a standard Java object.
  3. When I pass a Ruby class to the Java program though, JRuby converts
    it to a RubyObject.
  4. Apparently the Ruby class objects and instance variable are NOT
    standard Java methods or instance variables. They appear in the list
    returned by “public_methods” method of the RubyObject class. I can’t
    seem to find examples of how to run these methods (or access the JRuby
    instance variables) using standard Java.

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 …?

Thanks for any further tips.

Ray

What you may need to do for your case, is create a Java class with the
correct names and methods for Drools to use it, then populate it with
information you draw from your Ruby object.

As far as the more general question goes, Ive never found a great deal
of
good, clear information about embedding JRuby in Java online. Most of
what
there is can all be found on this page [RedBridge: JRuby Embedding[(
RedBridge · jruby/jruby Wiki · GitHub) with some more complex
examples found here: RedBridge
Exampleshttps://github.com/jruby/jruby/wiki/RedBridgeExamples. What
you really need is to get a copy of the Using
JRuby http://pragprog.com/book/jruby/using-jruby book. Chapters 3 and
4
give excellent, clear illustrations on the kind of Java interop you are
asking about.

Eric, Perfect. That works!! I will have to explore how best to handle
insert data from Mongoid and ActiveRecord, but at least I have an
approach that works. I can create Java classes that match what I plan
to pass. I just have to be able to extend those classes with my
intended JRuby class… may be a problem with Mongoid and ActiveRecord
instances already extend ActiveRecord, so I may need to create a wrapper
class.

Thanks a lot. This is great.

Ray

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.