# Running JRuby in an OSGi container

Posted on July 24, 2009 by Tommy McGuire
On the JRuby user mailing list, Hasan posted a link to a workaround for a problem running JRuby scripts using JSR 223. The problem is that a bundle which uses the JRubyScriptingEngineFactory to create an engine to run a JRuby script fails because it cannot see the org.jruby.javasupport.Java class. The workaround is to set the current thread's context classloader to null before creating the engine, thereby forcing the JRuby engine to use RubyInstanceConfig.class' classloader, which comes from the JRuby bundle and can see the org.jruby.javasupport package.

I pointed out in a subsequent email that, after doing that, the JRuby script would not be able to locate Java classes in the script's own bundle. Yoko Harada then suggested adding "DynamicImport-Package: *" to the headers (see JRUBY-3792) for the jruby-complete bundle as a way to work around the problem with the first workaround.

That is not something I would want to do.

In this case, you have three OSGi bundles: the jruby-complete bundle, the JSR223 engine bundle, and your script's bundle. In OSGi, each bundle has it's own classloader which either loads things or delegates to other bundles' classloaders based on the import and export directives in the bundles' MANIFEST.MF. The JSR223 engine bundle imports the packages it needs from the jruby-complete bundle, and the script bundle imports the packages it needs to start up the JSR223 engine from the JSR223 engine bundle.

In the original problem, the script bundle's classloader could not see the org.jruby.javasupport package (containing the Java class) because there was nothing imported into the script bundle from the jruby-complete bundle. When the code in the script bundle tried to initialize the jruby engine, the script bundle raised the ClassNotFound exception.

    ScriptEngineFactory factory = new JRubyScriptEngineFactory();    System.out.println("Getting engine");    final ClassLoader loader = Thread.currentThread().getContextClassLoader();    Thread.currentThread().setContextClassLoader(null);    ScriptEngine engine = factory.getScriptEngine();    Thread.currentThread().setContextClassLoader(loader);    if (engine == null)    {      System.out.println("Cannot get engine");    }    else    {      System.out.println("Evaluating script");      engine.eval(         "include Java\nJava::test.jrubyscript.Printer.print"      );    }

where Printer.print is a Java class and method that simply prints something
and that lives in the same bundle as the bundle activator that runs this code. This produces
org.jruby.exceptions.RaiseException: cannot load Java class \  test.jrubyscript.Printer

Adding "DynamicImport-Package: *" to the jruby-complete bundle by itself does not help unless you also export the gov.nasa.test.jrubyscript package that contains the Printer class from the script bundle. (That is smell #1, if you are into refactoring: exporting a package from a module that is only used within the module.) Then, the jruby-complete bundle establishes a runtime-only dependency on the script bundle when it tries to load the Printer class.

The problem is that the DynamicImport-Package establishes a permanent link to the script bundle. (There is smell #2: the jruby-complete bundle is wired as a client to the script bundle, rather than the other way around.) Presumably, if you had two apps running as script bundles in the same OSGi container, instead of each using their own Printer class they would both use the Printer class from the first app bundle to get installed. We are currently using the SpringSource dm Server to serve two JRuby on Rails apps, as well as a number of non-JRuby applications, so this kind of linkage will eventually cause me some headaches.

The better solution, IMHO, is for the script bundle to actually declare its dependency on the jruby-complete bundle either by using Import-Package to bring in all of the packages it needs, or using Import-Bundle to pull in everything exported from the jruby-complete bundle. The first is pretty clearly a non-starter, since there is no way for me to tell which packages from jruby-complete the script bundle is going to need. The second works fine, though, and even allows the script bundle to as for a particular version of the jruby-complete bundle, which is one of the weaknesses of DynamicImport-Package.

It might be better for the JSR223 bundle to import all of the jruby-complete packages and then re-export them, so bundles like the script bundle would just have to specify a dependency on the JSR223 engine. But that is a completely different question.

The bottom line here is that the best solution is to play by the rules when using a container like OSGi.

### Appendix

The MANIFEST.MF file for my script bundle looks like:
Manifest-Version: 1.0Bundle-Classpath: .Bundle-Version: 1.0.0Bundle-Name: JRubyScriptTestBundle-ManifestVersion: 2Bundle-Activator: gov.nasa.test.jrubyscript.ActivatorImport-Package: com.sun.script.jruby,javax.script,org.osgi.frameworkBundle-SymbolicName: JRubyScriptTestImport-Bundle: org.jruby.jruby

The bundle activator looks like:
package gov.nasa.test.jrubyscript;import javax.script.ScriptEngine;import javax.script.ScriptEngineFactory;import org.osgi.framework.BundleActivator;import org.osgi.framework.BundleContext;import com.sun.script.jruby.JRubyScriptEngineFactory;public class Activator implements BundleActivator{  @Override  public void start(BundleContext context) throws Exception  {    ScriptEngineFactory factory = new JRubyScriptEngineFactory();    System.out.println("Getting engine");    final ClassLoader loader = Thread.currentThread().getContextClassLoader();    System.out.println("Loader: " + loader);//    Hasan's workaround: set the context classloader to null//    Thread.currentThread().setContextClassLoader(null);    ScriptEngine engine = factory.getScriptEngine();    System.out.println("Engine classloader: " + engine.getClass().getClassLoader());//    Thread.currentThread().setContextClassLoader(loader);    if (engine == null)    {      System.out.println("Cannot get engine");    }    else    {      System.out.println("Evaluating script");      engine.eval("puts \"hello\"");      engine.eval("include Java\nJava::gov.nasa.test.jrubyscript.Printer.print");    }  }  @Override  public void stop(BundleContext context) throws Exception  {  }}

And my Printer class is:
package gov.nasa.test.jrubyscript;public class Printer{  public static void print()  {    System.out.println("Printer.print");    System.out.println("Printer classloader: " + Printer.class.getClassLoader());  }}

To run this, no changes to the jruby-complete bundle or the JRuby JSR223 engine bundle.

To get Hasan's workaround to run, add
DynamicImport-Package: *

to the jruby-complete bundle,
Export-Package: gov.nasa.test.jrubyscript

to the script bundle, remove the Import-Bundle, and uncomment the classloader manipulations in the script bundle's activator.

[Edit: Welcome InfoQ readers!] Some links to previous JRuby/OSGi posts, including my incomplete series on running JRuby on Rails applications on the SpringSource dm Server as well as some other OSGi-related platitudes, falsehoods, and misunderstandings:

Hi,

Thank you very much for your post, I read all of your jRbuby and OSGi blog. What about gems included in rails war file under WEB-INF/gems? Is there a way to share these gems from multiple rails apps?

Thanks again