Forum: JRuby RubyObject.toJava

6b611063e1aec3f09bdca7e406b3a765?d=identicon&s=25 Ray H. (ray_h)
on 2013-12-05 03:11
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
486ca04f06d968004643ce5b47376ded?d=identicon&s=25 Keith B. (keith_b)
on 2013-12-05 06:52
(Received via mailing list)
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/...
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
486ca04f06d968004643ce5b47376ded?d=identicon&s=25 Keith B. (keith_b)
on 2013-12-05 07:09
(Received via mailing list)
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
B05d3cbc64b0031a24c2887fb6ddc173?d=identicon&s=25 christian (Guest)
on 2013-12-05 12:07
(Received via mailing list)
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/tre...

-christian
C983ad33b47479f17a28e083e1bb3d8b?d=identicon&s=25 Eric West (edubkendo)
on 2013-12-05 16:00
(Received via mailing list)
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[(
https://github.com/jruby/jruby/wiki/RedBridge) with some more complex
examples found here: RedBridge
Examples<https://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.
6b611063e1aec3f09bdca7e406b3a765?d=identicon&s=25 Ray H. (ray_h)
on 2013-12-05 16:50
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
6b611063e1aec3f09bdca7e406b3a765?d=identicon&s=25 Ray H. (ray_h)
on 2013-12-06 16:43
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
C983ad33b47479f17a28e083e1bb3d8b?d=identicon&s=25 Eric West (edubkendo)
on 2013-12-10 15:19
(Received via mailing list)
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/GeneratingJava....
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 Smith", 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 Smith

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 Smith", 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.java<https://github.com/droolsjbpm/drools/blob/master/d...
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
project<https://github.com/edubkendo/SampleDrools>if you want to poke
around.
6b611063e1aec3f09bdca7e406b3a765?d=identicon&s=25 Ray H. (ray_h)
on 2013-12-11 03:48
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
Please log in before posting. Registration is free and takes only a minute.
Existing account

NEW: Do you have a Google/GoogleMail, Yahoo or Facebook account? No registration required!
Log in with Google account | Log in with Yahoo account | Log in with Facebook account
No account? Register here.