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