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
