Monday, July 27, 2009

What's the ideal way to get JSR223 work on OSGi?

After I wrote the entry, JSR 223 JRuby Engine won't work on OSGi platform, a workaround and an opposition to the workaround were posted to jruby-users ml, which is archived http://www.nabble.com/running-jruby-in-an-osgi-container-td24379565.html. The workaround Hasan found out worked well. But, Tommy opposed because the workaround would cause a tangle of references on some conainter that has multiple types of applications. To avoid this, Tommy advised me to add "Import-Bundle: org.jruby.jruby" to my bundle configuration. I tried Tommy's advise, but it didn't work for me. What's wrong with it?

Still, I haven't figured out how to get JSR 223 JRuby engine on OSGi platform "ideally." Still, I need a help, suggestion, advise, and whatever I can find the ideal way. For ease of tracking the discussion down, I'm going to write what I did along with it.

Versions:

  • Java for Mac OS X 10.5 Update 4

  • java version "1.5.0_19" for making a bundle

    java version "1.6.0_13" for starting the bundles

  • JRuby 1.3.1

  • JSR 223 JRuby Engine 1.1.7

  • Apache Felix 1.8.0


Take one: Original Test Program

The first one is the original test program that raises java.lang.ClassNotFoundException: org.jruby.javasupport.Java. I simplified this from the old one seeing Hasan's sample.

- pom.xml

1 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
3 <modelVersion>4.0.0</modelVersion>
4 <groupId>hickory.example</groupId>
5 <artifactId>Hickory</artifactId>
6 <packaging>bundle</packaging>
7 <version>1.0-SNAPSHOT</version>
8 <name>Hickory</name>
9 <url>http://maven.apache.org</url>
10 <build>
11 <plugins>
12 <plugin>
13 <groupId>org.apache.felix</groupId>
14 <artifactId>maven-bundle-plugin</artifactId>
15 <extensions>true</extensions>
16 <configuration>
17 <instructions>
18 <Bundle-Activator>hickory.example.Activator</Bundle-Activator>
19 </instructions>
20 </configuration>
21 </plugin>
22 <plugin>
23 <groupId>org.apache.maven.plugins</groupId>
24 <artifactId>maven-compiler-plugin</artifactId>
25 <configuration>
26 <source>1.5</source>
27 <target>1.5</target>
28 </configuration>
29 </plugin>
30 </plugins>
31 </build>
32 <repositories>
33 <repository>
34 <id>maven2-repository.dev.java.net</id>
35 <name>Java.net Repository for Maven</name>
36 <url>http://download.java.net/maven/2/</url>
37 <layout>default</layout>
38 </repository>
39 </repositories>
40 <dependencies>
41 <dependency>
42 <groupId>org.apache.felix</groupId>
43 <artifactId>org.osgi.core</artifactId>
44 <version>1.3.0-SNAPSHOT</version>
45 </dependency>
46 <dependency>
47 <groupId>org.livetribe</groupId>
48 <artifactId>livetribe-jsr223</artifactId>
49 <version>2.0.5</version>
50 </dependency>
51 <dependency>
52 <groupId>com.sun.script.jruby</groupId>
53 <artifactId>jruby-engine</artifactId>
54 <version>1.1.7</version>
55 </dependency>
56 <dependency>
57 <groupId>junit</groupId>
58 <artifactId>junit</artifactId>
59 <version>3.8.1</version>
60 <scope>test</scope>
61 </dependency>
62 </dependencies>
63 </project>

- Snippet

1 package hickory.example;
2
3 import com.sun.script.jruby.JRubyScriptEngineFactory;
4 import javax.script.ScriptEngine;
5 import javax.script.ScriptEngineFactory;
6 import org.osgi.framework.BundleActivator;
7 import org.osgi.framework.BundleContext;
8
9 public class Activator implements BundleActivator {
10
11 public void start(BundleContext context) throws Exception {
12 System.out.println("Poor Activator");
13 ScriptEngineFactory factory = (ScriptEngineFactory) new JRubyScriptEngineFactory();
14 ScriptEngine engine = factory.getScriptEngine();
15
16 System.out.println("Everything should be ready.");
17 engine.eval("puts \"Yeaaaaaah! See?\"");
18 }
19
20 public void stop(BundleContext context) {
21 System.out.println("Bye!");
22 }
23 }

- On Apache Felix

cd felix-1.8.0
java -jar bin/felix.jar

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

-> ps
START LEVEL 1
ID State Level Name
[ 0] [Active ] [ 0] System Bundle (1.8.0)
[ 1] [Active ] [ 1] Apache Felix Shell Service (1.2.0)
[ 2] [Active ] [ 1] Apache Felix Shell TUI (1.2.0)
[ 3] [Active ] [ 1] Apache Felix Bundle Repository (1.4.0)
-> 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:///Users/yoko/NetBeansProjects/Hickory/target/Hickory-1.0-SNAPSHOT.jar
Poor Activator
Warning: JRuby home "/4.0:1/META-INF/jruby.home" does not exist, using /var/folders/xY/xYuRYl0RHjy7p6SeA0nHVU+++TI/-Tmp-/
org.osgi.framework.BundleException: Activator start error in bundle hickory.example.Hickory [6].
at org.apache.felix.framework.Felix.startBundle(Felix.java:1506)
at org.apache.felix.framework.BundleImpl.start(BundleImpl.java:779)
at org.apache.felix.shell.impl.StartCommandImpl.execute(StartCommandImpl.java:105)
at org.apache.felix.shell.impl.Activator$ShellServiceImpl.executeCommand(Activator.java:291)
at org.apache.felix.shell.tui.Activator$ShellTuiRunnable.run(Activator.java:177)
at java.lang.Thread.run(Thread.java:637)
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
->

Take two: Applying Hasan's workaround

According to Hasan's analysis, the snippet above doesn't work 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.

So, my second version became below:

- pom.xml
I changed the Activator's class name form Activator to Activator1.

18 <Bundle-Activator>hickory.example.Activator1</Bundle-Activator>

- Snippet

1 package hickory.example;
2
3 import com.sun.script.jruby.JRubyScriptEngineFactory;
4 import javax.script.ScriptEngine;
5 import javax.script.ScriptEngineFactory;
6 import org.osgi.framework.BundleActivator;
7 import org.osgi.framework.BundleContext;
8
9 public class Activator1 implements BundleActivator {
10
11 public void start(BundleContext context) throws Exception {
12 System.out.println("Activator1");
13 ScriptEngineFactory factory = (ScriptEngineFactory) new JRubyScriptEngineFactory();
14 final ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
15 Thread.currentThread().setContextClassLoader(null);
16 ScriptEngine engine = factory.getScriptEngine();
17 Thread.currentThread().setContextClassLoader(oldClassLoader);
18
19 System.out.println("Everything should be ready.");
20 engine.eval("puts \"Yeaaaaaah! See?\"");
21 }
22
23 public void stop(BundleContext context) {
24 System.out.println("Bye!");
25 }
26 }

Lines 14, 15, and 17 were added to the first one.

- On Apache Felix
After recreating the bundle, I tried this.

-> shutdown

rm -rf felix-cache
java -jar bin/felix.jar

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

-> 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:///Users/yoko/NetBeansProjects/Hickory/target/Hickory-1.0-SNAPSHOT.jar
Activator1
Warning: JRuby home "/4.0:1/META-INF/jruby.home" does not exist, using /var/folders/xY/xYuRYl0RHjy7p6SeA0nHVU+++TI/-Tmp-/
Everything should be ready.
Yeaaaaaah! See?
->

It worked!
I dare to remove Apache Felix's cache and restart it every time before I try modified bundles. The cache seems to remember something worked before, so I've gotten a different result before and after I removed the cache. It is a bit annoying, but needs to have accurate results.

Take three: Using a defined Java class in Ruby

Everything seems fine, but Tommy brought another problem that the workaround does not work when a java class is used in Ruby script. To try this, I defined the class, hickory.example.YellOut, in the same package as the Activator. Now, test programs are as in below:

- pom.xml

18 <Bundle-Activator>hickory.example.Activator2</Bundle-Activator>

- Snippet

1 package hickory.example;
2
3 import com.sun.script.jruby.JRubyScriptEngineFactory;
4 import javax.script.ScriptEngine;
5 import javax.script.ScriptEngineFactory;
6 import org.osgi.framework.BundleActivator;
7 import org.osgi.framework.BundleContext;
8
9 public class Activator2 implements BundleActivator {
10
11 public void start(BundleContext context) throws Exception {
12 System.out.println("Activator2");
13 ScriptEngineFactory factory = (ScriptEngineFactory) new JRubyScriptEngineFactory();
14 final ClassLoader oldClassLoader = Thread.currentThread().getContextClassLoader();
15 Thread.currentThread().setContextClassLoader(null);
16 ScriptEngine engine = factory.getScriptEngine();
17 Thread.currentThread().setContextClassLoader(oldClassLoader);
18
19 System.out.println("Everything should be ready.");
20 engine.eval("include Java\nputs Java::hickory.example.YellOut.new.whats");
21 }
22
23 public void stop(BundleContext context) {
24 System.out.println("Bye!");
25 }
26 }

1 package hickory.example;
2
3 public class YellOut {
4 public String whats() {
5 return "I made it!!!";
6 }
7 }

- On Apache Felix

-> shutdown
-> Bye!

rm -rf felix-cache
java -jar bin/felix.jar

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

-> 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:///Users/yoko/NetBeansProjects/Hickory/target/Hickory-1.0-SNAPSHOT.jar
Activator2
Warning: JRuby home "/4.0:1/META-INF/jruby.home" does not exist, using /var/folders/xY/xYuRYl0RHjy7p6SeA0nHVU+++TI/-Tmp-/
Everything should be ready.
org.osgi.framework.BundleException: Activator start error in bundle hickory.example.Hickory [6].
at org.apache.felix.framework.Felix.startBundle(Felix.java:1506)
at org.apache.felix.framework.BundleImpl.start(BundleImpl.java:779)
at org.apache.felix.shell.impl.StartCommandImpl.execute(StartCommandImpl.java:105)
at org.apache.felix.shell.impl.Activator$ShellServiceImpl.executeCommand(Activator.java:291)
at org.apache.felix.shell.tui.Activator$ShellTuiRunnable.run(Activator.java:177)
at java.lang.Thread.run(Thread.java:637)
Caused by: javax.script.ScriptException: org.jruby.exceptions.RaiseException: cannot load Java class hickory.example.YellOut
at com.sun.script.jruby.JRubyScriptEngine.evalNode(JRubyScriptEngine.java:509)
at com.sun.script.jruby.JRubyScriptEngine.eval(JRubyScriptEngine.java:184)
at javax.script.AbstractScriptEngine.eval(AbstractScriptEngine.java:247)
at hickory.example.Activator2.start(Activator2.java:20)
at org.apache.felix.framework.util.SecureAction.startActivator(SecureAction.java:589)
at org.apache.felix.framework.Felix.startBundle(Felix.java:1458)
... 5 more
Caused by: org.jruby.exceptions.RaiseException: cannot load Java class hickory.example.YellOut
at (unknown).(unknown)(/builtin/java/ast.rb:49)
at (unknown).get_proxy_or_package_under_package(/builtin/javasupport/java.rb:51)
at #.method_missing(:2)
at (unknown).(unknown)(:1)
javax.script.ScriptException: org.jruby.exceptions.RaiseException: cannot load Java class hickory.example.YellOut
->

JRuby failed to load hickory.example.YellOut even though this class is in the same bundle as the Activator. Class loading issue again. I need to let JRuby know where hickory.example.YellOut.class resides by doing something.

Take four: Applying the fix reported in http://jira.codehaus.org/browse/JRUBY-3792.

The filed issue came up in my mind. I thought this might have fix something this sort of problems. So, I recompiled JRuby.

- JRuby recompilation
I added "DynamicImport-Package: *" at end of jruby.bnd.template. Following is entire jruby.bnd.template file.

Export-Package: org.jruby.*;version="@JRUBY_VERSION@"
Import-Package: !org.jruby.*, *;resolution:=optional
Bundle-Version: @JRUBY_VERSION@
Bundle-Description: JRuby @JRUBY_VERSION@ OSGi bundle
Bundle-Name: JRuby @JRUBY_VERSION@
Bundle-SymbolicName: org.jruby.jruby
DynamicImport-Package: *

Then, recompiled JRuby by running "ant jar-complete."

- On Apache Felix

-> shutdown

rm -rf felix-cache
java -jar bin/felix.jar

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

-> start file:///Users/yoko/Tools/jruby-1.3.1/lib/jruby-complete.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:///Users/yoko/NetBeansProjects/Hickory/target/Hickory-1.0-SNAPSHOT.jar
Activator2
Warning: JRuby home "/4.0:1/META-INF/jruby.home" does not exist, using /var/folders/xY/xYuRYl0RHjy7p6SeA0nHVU+++TI/-Tmp-/
Everything should be ready.
I made it!!!
->


Worked!

However, Tommy posed the problem of this way of fixing bundles becuase "DynamicImport-Package: *" would be a culprit of linkage problems when multiple applications and bundles are deployed on a single OSGi container, especially differenct versions of JRuby bundles exists on it. The suggestion was

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.


Take five: Trying "Import-Bundle: org.jruby.jruby"

Adding "Import-Bundle: org.jruby.jruby" to application's bundle configuration is Tommy's advice. So, I also tried this.

- pom.xml

The line 19 is added to existing pom.xml. As in line 20, I also tried to give Import-Bundle configuration from a separate file, osgi.bnd. Osgi.bnd file has just a line, Import-Bundle: org.jruby.jruby, in it.


1 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
3 <modelVersion>4.0.0</modelVersion>
4 <groupId>hickory.example</groupId>
5 <artifactId>Hickory</artifactId>
6 <packaging>bundle</packaging>
7 <version>1.0-SNAPSHOT</version>
8 <name>Hickory</name>
9 <url>http://maven.apache.org</url>
10 <build>
11 <plugins>
12 <plugin>
13 <groupId>org.apache.felix</groupId>
14 <artifactId>maven-bundle-plugin</artifactId>
15 <extensions>true</extensions>
16 <configuration>
17 <instructions>
18 <Bundle-Activator>hickory.example.Activator2</Bundle-Activator>
19 <Import-Bundle>org.jruby.jruby</Import-Bundle>
20 <!--<_include>src/main/resources/osgi.bnd</_include>-->
21 </instructions>
22 </configuration>
23 </plugin>
24 <plugin>
25 <groupId>org.apache.maven.plugins</groupId>
26 <artifactId>maven-compiler-plugin</artifactId>
27 <configuration>
28 <source>1.5</source>
29 <target>1.5</target>
30 </configuration>
31 </plugin>
32 </plugins>
33 </build>
34 <repositories>
35 <repository>
36 <id>maven2-repository.dev.java.net</id>
37 <name>Java.net Repository for Maven</name>
38 <url>http://download.java.net/maven/2/</url>
39 <layout>default</layout>
40 </repository>
41 </repositories>
42 <dependencies>
43 <dependency>
44 <groupId>org.apache.felix</groupId>
45 <artifactId>org.osgi.core</artifactId>
46 <version>1.3.0-SNAPSHOT</version>
47 </dependency>
48 <dependency>
49 <groupId>org.livetribe</groupId>
50 <artifactId>livetribe-jsr223</artifactId>
51 <version>2.0.5</version>
52 </dependency>
53 <dependency>
54 <groupId>com.sun.script.jruby</groupId>
55 <artifactId>jruby-engine</artifactId>
56 <version>1.1.7</version>
57 </dependency>
58 <dependency>
59 <groupId>junit</groupId>
60 <artifactId>junit</artifactId>
61 <version>3.8.1</version>
62 <scope>test</scope>
63 </dependency>
64 </dependencies>
65 </project>

- On Apache Felix

-> shutdown
-> Bye!

rm -rf felix-cache
java -jar bin/felix.jar

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

-> 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:///Users/yoko/NetBeansProjects/Hickory/target/Hickory-1.0-SNAPSHOT.jar
Activator2
Warning: JRuby home "/4.0:1/META-INF/jruby.home" does not exist, using /var/folders/xY/xYuRYl0RHjy7p6SeA0nHVU+++TI/-Tmp-/
Everything should be ready.
org.osgi.framework.BundleException: Activator start error in bundle hickory.example.Hickory [6].
at org.apache.felix.framework.Felix.startBundle(Felix.java:1506)
at org.apache.felix.framework.BundleImpl.start(BundleImpl.java:779)
at org.apache.felix.shell.impl.StartCommandImpl.execute(StartCommandImpl.java:105)
at org.apache.felix.shell.impl.Activator$ShellServiceImpl.executeCommand(Activator.java:291)
at org.apache.felix.shell.tui.Activator$ShellTuiRunnable.run(Activator.java:177)
at java.lang.Thread.run(Thread.java:637)
Caused by: javax.script.ScriptException: org.jruby.exceptions.RaiseException: cannot load Java class hickory.example.YellOut
at com.sun.script.jruby.JRubyScriptEngine.evalNode(JRubyScriptEngine.java:509)
at com.sun.script.jruby.JRubyScriptEngine.eval(JRubyScriptEngine.java:184)
at javax.script.AbstractScriptEngine.eval(AbstractScriptEngine.java:247)
at hickory.example.Activator2.start(Activator2.java:20)
at org.apache.felix.framework.util.SecureAction.startActivator(SecureAction.java:589)
at org.apache.felix.framework.Felix.startBundle(Felix.java:1458)
... 5 more
Caused by: org.jruby.exceptions.RaiseException: cannot load Java class hickory.example.YellOut
at (unknown).(unknown)(/builtin/java/ast.rb:49)
at (unknown).get_proxy_or_package_under_package(/builtin/javasupport/java.rb:51)
at #.method_missing(:2)
at (unknown).(unknown)(:1)
javax.script.ScriptException: org.jruby.exceptions.RaiseException: cannot load Java class hickory.example.YellOut
->


Unfortunately, it didn't work for me though Tommy said it worked.


Tommy also talked about JSR 223 JRuby engine's bundle.

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.


So, what is the ideal solution in terms of OSGi?

1 comment:

Neil Bartlett said...

"Import-Bundle" is not an OSGi header. It is an extension supported only by SpringSource's dm Server. Presumably Tommy is running on dm Server.

I wish I could help you more as I know a bit about OSGi, unfortunately it doesn't seem possible to understand what you're doing without knowledge of Maven. It would help if you could show the OSGi MANIFEST.MF descriptors rather than Maven poms.