Using JRuby in OSGi

Required Bundles

To execute a JRuby script, we need a JRuby runtime [1] and can use additionally the JRuby JSR223 engine [2] allowing simpler access to JRuby.

JRuby version 1.3.1 is available as a bundle at

http://repo1.maven.org/maven2/org/jruby/jruby-complete/1.3.1/jruby-complete-1.3.1.jar

and JRuby JSR223 engine version 1.1.7 is also available as a bundle at

http://download.java.net/maven/2/com/sun/script/jruby/jruby-engine/1.1.7/jruby-engine-1.1.7.jar

First Attempt

Let's try with a very simple ruby script printing "Hello World" which is to be evaluated in a bundle activator.

The pom.xml looks as follows:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <modelVersion>4.0.0</modelVersion>
    <groupId>org.trialox</groupId>
    <artifactId>org.trialox.example.jruby</artifactId>
    <packaging>bundle</packaging>
    <version>0.1</version>
    <name>Example - JRuby in OSGi</name>
    <description>A bundle to test running JRuby within an OSGi container</description>
    <dependencies>
        <dependency>
            <groupId>org.osgi</groupId>
            <artifactId>osgi_R4_core</artifactId>
            <version>1.0</version>
        </dependency>
        <dependency>
            <groupId>com.sun.script.jruby</groupId>
            <artifactId>jruby-engine</artifactId>
            <version>1.1.7</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.felix</groupId>
                <artifactId>maven-bundle-plugin</artifactId>
                <extensions>true</extensions>
                <configuration>
                    <instructions>
                        <Bundle-Activator>org.trialox.example.jruby.Activator</Bundle-Activator>
                    </instructions>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

And the class org.trialox.example.jruby.Activator.java contains the codes:

package org.trialox.example.jruby;

import com.sun.script.jruby.JRubyScriptEngineFactory;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;

public class Activator implements BundleActivator {

  public void start(BundleContext context) throws Exception {
    ScriptEngineFactory factory = (ScriptEngineFactory) new JRubyScriptEngineFactory();
    System.out.println("Getting engine");
    ScriptEngine engine = factory.getScriptEngine();
    if (engine == null) {
      System.out.println("Cannot get engine");
    } else {
      System.out.println("Evaluating script");
      engine.eval("puts \"Hello World.\"");
    }
  }

  public void stop(BundleContext context) throws Exception {
  }
}

We expect to see the following logs when the bundle get started in an OSGi environment:

Getting engine
Evaluating script
Hello World

However, when run within felix 1.4.0:

$ java -jar bin/felix.jar

Welcome to Felix.
=================

-> ps
START LEVEL 1
   ID   State         Level  Name
[   0] [Active     ] [    0] System Bundle (1.4.0)
[   1] [Active     ] [    1] Apache Felix Shell Service (1.0.2)
[   2] [Active     ] [    1] Apache Felix Shell TUI (1.0.2)
[   3] [Active     ] [    1] Apache Felix Bundle Repository (1.2.1)
-> start http://repo1.maven.org/maven2/org/jruby/jruby-complete/1.3.1/jruby-complete-1.3.1.jar
-> start http://download.java.net/maven/2/com/sun/script/jruby/jruby-engine/1.1.7/jruby-engine-1.1.7.jar
-> start file:///home/hasan/spikes/jruby-in-osgi/target/org.trialox.example.jruby-0.1.jar

we obtained:

Getting engine
Warning: JRuby home "/5.0:1/META-INF/jruby.home" does not exist, using /tmp
org.osgi.framework.BundleException: Activator start error in bundle org.trialox.org.trialox.example.jruby [8].
	at org.apache.felix.framework.Felix._startBundle(Felix.java:1701)
	at org.apache.felix.framework.Felix.startBundle(Felix.java:1578)
	at org.apache.felix.framework.BundleImpl.start(BundleImpl.java:382)
	at org.apache.felix.framework.BundleImpl.start(BundleImpl.java:363)
	at org.apache.felix.shell.impl.StartCommandImpl.execute(StartCommandImpl.java:82)
	at org.apache.felix.shell.impl.Activator$ShellServiceImpl.executeCommand(Activator.java:276)
	at org.apache.felix.shell.tui.Activator$ShellTuiRunnable.run(Activator.java:167)
	at java.lang.Thread.run(Thread.java:619)
Caused by: org.jruby.exceptions.RaiseException: library `java' could not be loaded: java.lang.ClassNotFoundException: org.jruby.javasupport.Java
	at (unknown).initialize(:1)
	at (unknown).(unknown)(:1)
org.jruby.exceptions.RaiseException: library `java' could not be loaded: java.lang.ClassNotFoundException: org.jruby.javasupport.Java

Analysis

The problem occurred because JRuby could not find the class org.jruby.javasupport.Java if run within OSGi environment. So, this is a class loading problem. Tracing the log "could not be loaded" took us from org/jruby/ext/LateLoadingLibrary.java to org/jruby/RubyInstanceConfig.java. In this class we found:

private ClassLoader contextLoader = Thread.currentThread().getContextClassLoader();
private ClassLoader loader = contextLoader == null ? RubyInstanceConfig.class.getClassLoader() : contextLoader;

In an OSGi environment, the Thread.currentThread().getContextClassLoader() of our bundle cannot find the abovementioned java class of JRuby.

Workaround

A possible workaround is by setting the context class loader to null before calling getScriptEngine:

package org.trialox.example.jruby;

import com.sun.script.jruby.JRubyScriptEngineFactory;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineFactory;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;

public class Activator implements BundleActivator {

	public void start(BundleContext context) throws Exception {
		ScriptEngineFactory factory = (ScriptEngineFactory) new JRubyScriptEngineFactory();
		System.out.println("Getting engine");

		final ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
		Thread.currentThread().setContextClassLoader(null);
		ScriptEngine engine = factory.getScriptEngine();
		Thread.currentThread().setContextClassLoader(oldClassLoader);

		if (engine == null) {
			System.out.println("Cannot get engine");
		} else {
			System.out.println("Evaluating script");
			engine.eval("puts \"Hello World.\"");
		}
	}

	public void stop(BundleContext context) throws Exception {
	}
}

And this is what we get when run within felix:

Getting engine
Warning: JRuby home "/5.0:1/META-INF/jruby.home" does not exist, using /tmp
Evaluating script
Hello World.


[1] http://jruby.codehaus.org/

[2] https://scripting.dev.java.net/servlets/ProjectDocumentList

Labels

 
(None)