Embedding a Complete JRuby Interpreter

Posted by Nick Sieger Tue, 28 Nov 2006 04:24:00 GMT

The other day I was setting up a demo installation of FishEye at work. I don’t have root on the box where it’s running, so none of the built-in authentication schemes were acceptable. Hey, there is already user and password information stored in the CVSROOT directory of the repository (we use the pserver access method). But, ughh, I’d have to read and parse files in Java, and grab a public domain crypt implementation since it’s not included in the Java standard library. Certainly do-able, but not my idea of fun.

So why not use Ruby for this? Painless IO and text-munging? Check. Crypt? Check. In Java? Since Charlie’s Ruby-in-a-Jar, this one is a slam-dunk!

For the implementation, have a look at CVSPasswdFisheyeAuthenticator.java and authenticator.rb. The basic idea is to use the Java class as the embedding wrapper and leave all the main logic in Ruby. The Java wrapper is still a little heavy, since the actual interface is implemented by the Java class, and Ruby is invoked by evaluating a statement inside each interface method. Some notable elements:

  • CVSPasswdFisheyeAuthenticator#init: The interpreter is initialized here. I pass authenticator properties to the interpreter by way of globals here -- not the cleanest, but easy. The Ruby code is loaded as a classpath resource, a nice feature of JRuby’s require that allows Ruby scripts to be jarred up with the rest of your code.
  • CVSPasswdFisheyeAuthenticator#rubyEval: This is your boilerplate-invoking-the-interpreter code here.
  • CVS::Authenticator#initialize: Reading and parsing of the CVS passwd and users files is done here in five succinct lines, much better than you could do with Java.
  • CVS::AuthToken: Here I’m implementing a Java interface in Ruby, and passing it back to Java! (See the CVS::Authenticator#recreate_auth method.) This is probably the nicest single aspect of JRuby’s Java integration, and it will only get better.

What would be nice for a future implementation is to make the Java class a factory and a delegate, and let JRuby implement the entire interface itself directly. This would avoid some ugly boundary-crossing issues (see the hand-crafted Ruby strings passed into the intepreter and rubyToJava conversions on the way out). As soon as JRuby gets better support for multiple interfaces and extending abstract classes, this sort of pattern could probably be templated rather easily.

If you want to build the whole thing, check out the source with Subversion and follow the README file for instructions.

For another example of this kind of embedding of JRuby in the wild, see the JRuby support in NanoContainer.

Tags ,  | no comments | no trackbacks