BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage Articles Deploying JRuby applications with Java Web Start

Deploying JRuby applications with Java Web Start

This item in japanese

There's one command you need to enter the united world of Ruby and Java: include Java .

This will allow you to instantiate Java classes, call their methods and even derive from them as if they were just ordinary Ruby objects. There are a few subtle differences, this article will show you how to manage them so you can quickly devise new applications and deploy them to your users at lightning-speed.

This article is based on a sample application that implements a simple ObjectSpace browser using JRuby and Swing. Ruby's ObjectSpace feature provides a way to access all objects of the system. For example, all living strings can be printed like this:

ObjectSpace.each_object(String) do |string|
 puts string
end
This yields about 28000 strings when run from within my irb session. Using Swing and JRuby, we can now display the different classes, their instances and the available methods in a nice graphical interface. You can even invoke parameterless methods by clicking on them in the rightmost panel:

ObjectSpace support in JRuby is disabled by default because of the performance penalty it imposes on the runtime, but more on that later.

I'd like to point out a few interesting details of the implementation and give some hints on how to get started on using the Java integration features of JRuby.

Java Integration

Once you've included Java into your script, you can start subclassing existing Java classes. This can be done by simply specifying the fully qualified name of the Java class. In the example application, the main window extends JFrame. It also includes the javax.swing and java.awt packages into the class' scope so you don't have to specify the full name for every usage:

class MainWindow < javax.swing.JFrame
include_package 'javax.swing'
include_package 'java.awt' ...

Alternatively, you can also include specific classes using the include_class function, which doesn't pollute the namespace with constants for classes you'll never use.

Calling super constructors works just as with plain Ruby code, which means that the frame title can be set by calling super("JRuby Object Browser") on the first line of the initialize-method.

With the whole javax.swing package included into the class, instantiating Java classes is straightforward:

list_panel = JPanel.new
list_panel.layout = GridLayout.new(0, 3)

If you take a closer look at the second line, one might think that we directly access the layout-field of the JPanel, but that's not case. JRuby adds some convenience methods to Java objects, so the above statement could also be written in the accustomed way:

list_panel.setLayout(GridLayout.new(0, 3)) 

Instead of using getters and setters, you can seemingly access fields directly. There is even more syntactic sugar JRuby adds to Java objects that makes the whole experience feel more Ruby-like: you can use the snake_case notation instead of the actually defined camel case names for any method calls. From this follows the third way to call the setLayout method:

list_panel.set_layout(GridLayout.new(0, 3)) 

Be cautious when calling setter-methods from within their own class, Ruby might think you want to create a local variable, so you have to call it explicitely with self as the receiver:

self.default_close_operation = WindowConstants::EXIT_ON_CLOSE 

Another difference between Java and Ruby is the accessing of constants, like EXIT_ON_CLOSE in the previous snippet. Just remember to replace all . accesses with :: if you're translating some code from Java to Ruby.

Until now, most changes JRuby brings to Swing development don't seem to be very revolutionary, but we haven't covered one important aspect yet: listeners. In Java, hooking up a listener with an event often means implementing an interface in an anonymous class:

button.addActionListener(new ActionListener() {
 public void actionPerformed(ActionEvent e) {
 ...
}
});

This really clutters your code if you need to attach a lot of listeners. With JRuby, this can be reduced to the following two lines of code (and you can even omit the event variable if you don't use it):

button.add_action_listener do |event|   
...
end

These are the basics you need to know to start using Swing with JRuby. Even though JRuby makes GUI development with Swing more comfortable, you still have to write a lot of code manually, especially when you need to use complex layouts. If you want to make the creation of Swing-UIs even simpler, take a look at Three approaches to JRuby GUI APIs.

Deployment of JRuby Applications Made Easy

Ruby applications and libraries are usually distributed with the help of RubyGems, but in order to take advantage of it, you already need to have Ruby and RubyGems installed, which likely isn't the case for the average end-user. This problem has already been solved for traditional (MRI / C-Ruby) programs in the form of RubyScript2Exe[1], which bundles your scripts and a Ruby interpreter into a nice package that can be run on multiple platforms. Users of JRuby don't have to feel left out in the rain, quite the contrary, they have an even mightier tool at hand to quickly deploy applications: Java Web Start.

Java Web Start to the Rescue

Java Web Start is included in the Java Runtime Environment and should therefore be present on most systems. Making an application ready for Web Start is rather easy, all that is needed is a Jar containing all the files and a JNLP (Java Network Launching Protocol) description file. We demonstrate the process of creating a web-startable Ruby application on the basis of the ObjectSpace browser application.

The prerequisite for Web Start is a Jar that contains the application, so we'll take a look at that first. JRuby provides two different libraries: the "minimal" jruby.jar and jruby-complete.jar, which bundles the whole Ruby standard library. If you don't use anything from the standard library, you can use the smaller jruby.jar and decrease the download by roughly one megabyte.

The simplest way to get your script running is to add the .rb files to jruby.jar. The following command adds our example, rob.rb, to the zip.

jar uf jruby.jar rob.rb  

To check if it worked, you can start your application with java, requiring our Ruby script. The application needs the ObjectSpace, which we need to enable by passing the jruby.objectspace.enabled=true property to Java:

java -Djruby.objectspace.enabled=true -jar jruby.jar -r rob 

The -r option automatically requires the specified file and thus runs our script.

Ahead of Time Compilation

One of the exciting new features of JRuby 1.1 is the ahead of time (AOT) compilation support. Currently, just in time compilation in JRuby is restricted to 2048 methods, ahead of time compiling can help to mitigate that restriction. jrubyc, the JRuby compiler, is still under development, so I'd advise to use the latest JRuby release available. Compiling a plain Ruby file to a classfile is as simple as invoking the compiler with the script(s) as arguments:

jrubyc rob.rb 

This will create a ruby directory containing a rob.class file. Instead of including the ruby directory into the jruby.jar as we did above, we're going to create a separate  Jar to hold the application. After all, modifying the existing Jar doesn't really seem to be such a neat solution. Jars can be made by the tool of the same name:

jar -cfe rob.jar ruby/rob.class ruby 
This will create a small Jar called rob.jar with our class inside, adding the ruby/rob.class as the main-class to be specified in the Manifest. This allows us to simplify the invocation, as we can now simply point to the class and don't have to speficy the require on the command line anymore. To execute it, we have to make sure that rob.jar is on the classpath:
java -Djruby.objectspace.enabled=true -cp rob.jar:jruby.jar ruby.rob 

Web Start

Before we can continue to writing the JNLP-File, we need to sign the Jars. That's a unfortunate necessity because JRuby uses reflection and thus needs to have extended permissions, see the JRuby Wiki for more details. The easiest way is to create yourself a test certificate using the keytool that comes with the JDK:

keytool -genkey -keystore myKeystore -alias myself
keytool -selfcert -alias myself -keystore myKeystore

From now on, every time you modify one of the Jars, you have to update the signature or you'll get a SecurityException when running it.

jarsigner -keystore myKeystore jruby.jar myself
jarsigner -keystore myKeystore rob.jar myself

Now that we have the Jars prepared, we can actually take a look at that JNLP file. The following presents a minimal configuration for the application. Some fields are required by the specification, like the title, vendor and j2se tags as well as the security section. The jar-tag denotes the final location of the Jars. It can also point to a local file, using a file://-URL, which can be convenient during development. The ObjectSpace property needs to be set here too, this can be done with the property-tag.

<?xml version="1.0" encoding="utf-8"?>
<jnlp>
<information>
<title>Ruby Object Browser</title>
<vendor>Mirko Stocker</vendor>
</information>
<resources>
<jar href="http://misto.ch/rob/rob.jar"/>
<jar href="http://misto.ch/rob/jruby.jar"/>
<j2se version="1.5+"/>
<property name="jruby.objectspace.enabled" value="true"/>
</resources>
<application-desc main-class="ruby.rob"/>
<security>
<all-permissions/>
</security>
</jnlp>

If you skipped the AOT section or just want to keep the one-jar way, then you'll have to modify the jnlp file to also include the -e arguments, so your application-desc will look like:

[...]
<application-desc>
<argument>-r</argument>
<argument>rob</argument>
</application-desc>
[...]

The final step is to upload the Jars and the JNLP file to the specified location. You should now be able to open the link in your browser or using the javaws tool directly from a shell.

Troubleshooting

In order for your browser to launch the application with Web Start, the JNLP file needs to be delivered with the application/x-java-jnlp-file MIME-Type. So if your browser just displays the content of the JNLP file and javaws isn't launched automatically, you need to adapt the webserver configuration. For example, Apache needs the following directive in mime.types:

application/x-java-jnlp-file jnlp 

Further Reading

The JRuby wiki is a great source for general information about JRuby.

  1. http://www.erikveen.dds.nl/rubyscript2exe/

Download the code for Ruby Object Browser.

Rate this Article

Adoption
Style

BT