Tuesday, June 2, 2009

Interfacing with Java from JRuby

Recently posted this on the user@jruby.codehaus.org mailing list, but thought I'd share here:

I had a java library that I recently integrated into a JRuby + Sinatra + Rack app. It was a client library I'd written that had service interface classes and implementations, etc.

At first I went down the road of messing with the Java interface from Ruby, or thinking about having a Ruby class that abstracted the Java layer, but I quickly decided just to use the Java service implementation class from Ruby, because JRuby already creates a Ruby class that abstracts the java classes, and in my past experience (a few years doing telecom API integrations) you end up with a lot more work long-term by putting too many layers in between the UI and the end-of-the-line service you are hitting that will inevitably change.

So for example in the ruby name_of_service.rb I have (I generified the names):

include Java

import 'pkg.name.of.client.service.impl.MyServiceV1Impl'
import 'pkg.name.of.client.service.impl.MyServiceV2Impl'
import 'pkg.name.of.client.service.model.MyServiceV1Method1Request'
import 'pkg.name.of.client.service.model.MyServiceV1Method1Response'
import 'pkg.name.of.client.service.model.MyServiceV1Method2Request'
import 'pkg.name.of.client.service.model.MyServiceV1Method2Response'
import 'pkg.name.of.client.service.model.MyServiceV2Method1Request'
import 'pkg.name.of.client.service.model.MyServiceV2Method1Response'

import 'java.util.ArrayList'

(then down in the code, here is a generified and modified example)

       my_service_v2 = MyServiceV2Impl.new
       ...             some_array_list = ArrayList.new()
       some_array_list.add(Java::JavaLang::Integer.new(2))
       some_array_list.add(Java::JavaLang::Integer.new(4))
       some_array_list.add(Java::JavaLang::Integer.new(6))
       ...
       my_req = MyServiceV2Method1Request.new
       my_req.setSomeArrayList(some_array_list)
       ...
       my_resp = my_service_v2.someMethod1(my_req)

Note that you can either use PascalCase or camelCase that would match the Java methods/classnames or you can use ruby_naming_that_has_underscores. I also didn't need the () after new above (not needed in Ruby), but left it there to show you could use it if you want. Notice that you can either use import to use the ClassName notation or the Java:PackageGoesHereInPascalCase::ClassName notation. You'll want to use the latter for String, Integer, and Float since those conflict with Ruby's and inevitably cause confusion (as they did for me).

Also be aware that the number conversions don't work exactly as you'd imagine (at least when I tested with 1.2.0- haven't tested conversions in 1.3.0RC2). More info on that:

http://stufftohelpyouout.blogspot.com/2009/05/jruby-numbers-and-their-corresponding.html
http://stufftohelpyouout.blogspot.com/2009/05/fun-with-jruby-irb-console.html
http://stufftohelpyouout.blogspot.com/2009/05/point-at-which-jruby-loses-precision-on.html

No comments: