Hi, all. Charles Nutter requested via Twitter that I post up the
issues I'm having getting JRuby to play nice in an OSGi environment.

You can find all of the code in question at http://github.com/mstine/pgovm.

Basically, my goal is to demonstrate the ability to implement OSGi
services in alternative JVM languages without exposing the chosen
implementation language to consumers of those services. I do this by
providing a public API as a Java interface (see the pgo-vm-api
module). This API provides all of the data types necessary to consume
the service. I then implement the API in other bundles, and expose
those implementations as services, witht the service interface being
the Java interface from the public API. I've successfully done this so
far in:

- Java
- Groovy
- Scala

This week I set out to do the same in JRuby. First off, kudos to the
fact that the JRuby JARS available in the Codehaus Maven repo are
proper OSGi bundles. As it turns out, its actually quite easy to
execute some arbitrary Ruby code from within an OSGi bundle. All one
needs to do is make sure that the JRuby 1.4.0 bundle is properly
provisioned into the container and then use the new embeded script
runner to fire it off. The first problem occurs when trying to
implement the Java interface (or reference any Java code outside of
the JRuby bundle for that matter). The classloader currently in scope
cannot see the interface. The only workaround I've found so far that
seems to get anywhere is to add a directive to the JRuby jar manifest:

DynamicImport-Package: *

This isn't entirely desirable, as in basically cracks open the entire
world for JRuby to see. Any package that isn't explicitly imported by
JRuby can now potentially be found by scanning all available bundles,
and if found, access them. At this point, our first problem is solved.
If I use pax-provision (part of Pax Construct, see
http://wiki.ops4j.org/display/paxconstruct/Pax+Construct) to start up
the OSGi container and provision JRuby, the API bundle, and the JRuby
service implementation bundle, all seems to go well, as the service
bundle is started and the service registered:

osgi> ss

Framework is launched.

id      State       Bundle
0       ACTIVE      org.eclipse.osgi_3.5.1.R35x_v20090827
1       ACTIVE
com.mattstine.polyglotosgi.vendingmachine.pgo-vm-jruby-impl_1.0.0.SNAPSHOT
2       ACTIVE
com.mattstine.polyglotosgi.vendingmachine.pgo-vm-api_1.0.0.SNAPSHOT
3       ACTIVE      org.jruby.jruby_1.4.0

osgi> bundle 1
com.mattstine.polyglotosgi.vendingmachine.pgo-vm-jruby-impl_1.0.0.SNAPSHOT [1]
 Id=1, Status=ACTIVE      Data
Root=/Users/mstine/Projects/polyglot-osgi-talk/code/pgovm/pgo-vm-jruby-impl/runner/equinox/org.eclipse.osgi/bundles/1/data
 Registered Services
  {com.mattstine.polyglotosgi.vendingmachine.api.VendingMachine}={service.id=26}

You can see here that bundle 1 is the JRuby implementation bundle, and
that it exposes a service via the API Java interface.

On to the next issue....in-container testing. I have a set of tests
for this API that all of the various implementations should pass.
These are implemented via a JUnit 4 test case using Pax Exam, which is
a support framework for testing OSGi bundles (see
http://wiki.ops4j.org/display/paxexam/Pax+Exam). The pgo-vm-tests
module in the GitHub project contains the test cases as well as the
proper pom.xml for leveraging Pax Exam. Basically, Pax Exam starts up
an OSGi container of choice, provisions your requested bundles, and
then runs the unit tests against them. I've attempted this against the
three major containers (Equinox, Felix, and Knopflerfish), and I get
different results, both seeming related to classloading. On Equinox,
here you can see an excerpt of the test bootstrapping:

[        PaxRunnerTestContainer] - Test container (Pax Runner 0.17.2)
started in 25545 millis
STARTING com.mattstine.polyglotosgi.vendingmachine.jruby
REGISTER 
com.mattstine.polyglotosgi.vendingmachine.jruby.internal.VendingMachineJRubyImpl
[     RemoteBundleContextClient] - Remote bundle context found after 5887 millis
VM IMPL = org.jruby.gen.interfaceimpl1814023...@19ed13da
[        PaxRunnerTestContainer] - Shutting down the test container (Pax Runner)

STOPPING 
com.mattstine.polyglotosgi.vendingmachine.jruby.internal.VendingMachineJRubyImpl

[             DefaultJavaRunner] - Platform has been shutdown.

What's important to note is that the test class is actually able to
get an instance of the service, as evidenced by the println statement
on the 5th line of output above. That println occurs in the setup
method (annotated w/ @Before) in the test case. However, when we
actually try to execute the first test method, things blow up, as
below:

-------------------------------------------------------------------------------
Test set: 
com.mattstine.polyglotosgi.vendingmachine.tests.jruby.VendingMachineJRubyTests
-------------------------------------------------------------------------------
Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 32.766
sec <<< FAILURE!
testEmptyInventory
[equinox](com.mattstine.polyglotosgi.vendingmachine.tests.jruby.VendingMachineJRubyTests)
 Time elapsed: 32.706 sec  <<< ERROR!
java.lang.NoClassDefFoundError: org/jruby/java/MiniJava
      at 
org.jruby.gen.InterfaceImpl1814023411.showInventory(org/jruby/gen/InterfaceImpl1814023411.gen:5)
      at 
com.mattstine.polyglotosgi.vendingmachine.tests.jruby.VendingMachineJRubyTests.testEmptyInventory(VendingMachineJRubyTests.java:37)
      at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
      at 
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
      at 
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
      at java.lang.reflect.Method.invoke(Method.java:597)
      at 
org.ops4j.pax.exam.junit.extender.impl.internal.CallableTestMethodImpl.injectContextAndInvoke(CallableTestMethodImpl.java:128)
      at 
org.ops4j.pax.exam.junit.extender.impl.internal.CallableTestMethodImpl.call(CallableTestMethodImpl.java:96)
      at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
      at 
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
      at 
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
      at java.lang.reflect.Method.invoke(Method.java:597)
      at 
org.ops4j.pax.exam.rbc.internal.RemoteBundleContextImpl.remoteCall(RemoteBundleContextImpl.java:78)
      at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
      at 
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
      at 
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
      at java.lang.reflect.Method.invoke(Method.java:597)
      at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:305)
      at sun.rmi.transport.Transport$1.run(Transport.java:159)
      at java.security.AccessController.doPrivileged(Native Method)
      at sun.rmi.transport.Transport.serviceCall(Transport.java:155)
      at 
sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:535)
      at 
sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:790)
      at 
sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:649)
      at 
java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886)
      at 
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908)
      at java.lang.Thread.run(Thread.java:637)
Caused by: java.lang.ClassNotFoundException: org.jruby.java.MiniJava
      at java.net.URLClassLoader$1.run(URLClassLoader.java:200)
      at java.security.AccessController.doPrivileged(Native Method)
      at java.net.URLClassLoader.findClass(URLClassLoader.java:188)
      at java.lang.ClassLoader.loadClass(ClassLoader.java:315)
      at java.lang.ClassLoader.loadClass(ClassLoader.java:250)
      at java.lang.ClassLoader.loadClassInternal(ClassLoader.java:398)
      ... 27 more

On Felix (and very similar on Knopflerfish), we actually don't even
get an instance of the service, as it blows up at startup:

STARTING com.mattstine.polyglotosgi.vendingmachine.jruby
REGISTER 
com.mattstine.polyglotosgi.vendingmachine.jruby.internal.VendingMachineJRubyImpl
ERROR: Error starting
file:/var/folders/4r/4r4zqex3Fs8-koSqNtfdvU+++TM/-Tmp-/paxexam_runner_mstine/bundles/com.mattstine.polyglotosgi.vendingmachine.pgo-vm-jruby-impl_1.0.0.SNAPSHOT.jar
(org.osgi.framework.BundleException: Activator start error in bundle
com.mattstine.polyglotosgi.vendingmachine.pgo-vm-jruby-impl [7].)
:1: library `enumerator' could not be loaded:
java.lang.ClassNotFoundException:
org.jruby.libraries.EnumeratorLibrary (LoadError)
      ...internal jruby stack elided...
      from (unknown).(unknown)(:1)
[     RemoteBundleContextClient] - Remote bundle context found after 3430 millis
VM IMPL = null
[        PaxRunnerTestContainer] - Shutting down the test container (Pax Runner)


[             DefaultJavaRunner] - Platform has been shutdown.

In both cases, it seems as if the classloader cannot see portions of
JRuby. What's frustrating is that even if I bring in all of the JRuby
packages via a Require-Bundle directive, I still get this behavior in
all three containers.

Any ideas? Any help would be greatly appreciated.

Thanks!




Matt Stine
Java User Group Leader

Memphis/Mid-South Java User Group
Mail Stop 754
262 Danny Thomas Place
Memphis, TN 38105 USA
Phone: 901-214-JAVA
Email: [email protected]
http://www.linkedin.com/in/mattstine
http://www.mattstine.com


Want a signature like this?

---------------------------------------------------------------------
To unsubscribe from this list, please visit:

    http://xircles.codehaus.org/manage_email


Reply via email to