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
requirethat 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_authmethod.) 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.
For another example of this kind of embedding of JRuby in the wild, see the JRuby support in NanoContainer.