JRuby Bridge Tutorial - How to create the bridge

The ScriptingContainer

Java provides a standardized way to integrate with other programming languages, the so-called JS223 scripting standard. This standard centers around the interface ScriptEngine.

While you can work on the ScriptEngine level directly, it is more convenient to use the Java class ScriptingContainer, which is part of the JRuby API. Basically, a ScriptingContainer object let's you execute Ruby source code. The value of this execution - remember, that every block of Ruby code returns some value! - can be retrieved in Java, and all global variables allocated during the execution, persist and are available on the Java side as well.

In the simplest way, the scripting container executes just a single Ruby expression: The allocation of some Ruby object, which then is known on the Java side, and which can be used to passing information forth and back.

# Ruby - the simplest (useful) script executed by the scripting container
require 'myrubydir/myapplication' # Load class definition
MyApplication.new                # Return it to Java

(This assumes, that we will include a file myrubydir/myapplication.rb into our jar file)

Of course nobody keeps you from more creating more elaborate interactions, and the example presented here, returns not only one object, but also creates a global Ruby valuable, which then is accessed on the Java side.

While the class ScriptingContainer also provides a default constructor, we will in general use the constructor with the signature ScriptingContainer(LocalContextScope). The LocalContextScope parameter is an enumeration type and determines the behaviour in a multithreading environment, as explained in the next chapter.

The IsolatedScriptingContainer

If the application runs from a Jar file *and* all gems are inside the Jar file, it is advisable to use an IsolatedScriptingContainer instead of a ScriptingContainer. This is a plugin replacement, which sets GEM_HOME and GEM_PATH in a suitable way. See the chapter on gems for more details.

Threadsafety issues

Container, Runtime and Variable Map

First, a few concepts need to be explained:

When we start executing a Ruby program - and this applies to Ruby in general, not just JRuby -, we have a top-level context. This is represented by an object of class Object, and if we, for instance, define a Ruby function, this function becomes a member function of this top-level object. This can easiliy seen by running the single-line program

  puts self.class # Ruby

which outputs Object.

Now to JRuby: A ScriptingContainer contains a Ruby Runtime, and a Ruby Runtime contains a Variable Map. These things are explained in the following:

A ScriptingContainer comprises of the full implementation of the Ruby language - compiler, runtime, standard library, all standard gems which are inclued in the distribution. This is necessary, because we don't know what features will be used, before executing our program. An application can have more than one containers, for example each one containing a different Ruby version.

The Ruby Runtime - in Java implemented as object of type org.jruby.Ruby contains th top level Ruby object, which I have mentioned earlier. For example, Ruby global variables are stored here.

JRuby also offers the possibility to share local variables between the Ruby and the Java world, and use Java objects on the Ruby side. For example, you can create a variable on some Java class type on the Java side, pass it to Ruby (via a method of some Ruby object) and work with this Java-object on the Ruby side. This is done by storing these bindings in a Variable Map, implemented in Java as object of type org.jruby.embed.internal.BiVariableMap.

LocalContextScope

JRuby gives the user some control over the extent of sharing, respectively replicating the container, the runtime, and the variable map. For example, if the application is configured to share the ScriptingContainer, the container will actually become a singleton, and each allocation of the container will in fact return the same object. The policy of sharing is encoded in the enumeration type LocalContextScope, and 4 sharing models are currently offered:

scope enum name meaning
singleton LocalContextScope.SINGLETON We can have many ScriptingContainer instances, but they share a common Runtime and Variable Map. The containers are thread-agnostic.
threadsafe LocalContextScope.THREADSAFE We have a single ScriptingContainer shared for all threads, but each thread has its own Runtime and Variable Map.
singlethread LocalContextScope.SINGLETHREAD Nothing is shared, but this is still thread-agnostic. If we use the same container in several threads, we have to care explicitly about concurrency.
concurrent LocalContextScope.CONCURRENT The container and the runtime is shared between threads, but each thread has its own variable map.

There are two ways to establish the local context scope for an application:

Example: Setting the local context scope to concurrent:

System.setProperty("org.jruby.embed.localcontext.scope", "concurrent"); // Java

Local Variable Behaviour

JRuby lets you choose, whether local variables should really be local (as it is according to Ruby semantics), or whether they should be shared too between Java and Ruby and hence persist between evaluations of scripts. I have yet to see an application where I would really want to have this behaviour, and I strongly prefer that locals behave as the Ruby-God intended, that is: locally. To ensure that this is the case, set the following system property:

System.setProperty("org.jruby.embed.localvariable.behavior", "transient"); // Java

Create the bridge, finally

This would be the code for creating our bridge in a concurrent context - our bridge is established on the Ruby side by executing the Ruby script bridge.rb, which in turn creates some global variables (which will be available on the Java side through the scripting container's Ruby Runtime) and returns explicitly an instance of the Ruby class Asahi:

// Java
import org.jruby.Ruby;
import org.jruby.RubyObject;
import org.jruby.embed.ScriptingContainer;
import org.jruby.embed.LocalContextScope;
....
  ScriptingContainer container = new ScriptingContainer(LocalContextScope.CONCURRENT);
  RubyObject asahi = (RubyObject)container.runScriptlet(org.jruby.embed.PathType.CLASSPATH,"rbsrc/bridge.rb");

Several points are worth mentioning: