The Ruby Builder for XPages

Nov 7, 2012 1:50 PM

Tags: xpages ruby

After I got Ruby in XPages to the point where it's generally working enough to power this blog, I set my sights on an even-more-important goal: being able to write backing Java classes in Ruby. While replacing SSJS is quite handy, my general use of inline scripting like that has declined significantly in favor of Java classes.

Fortunately, JRuby has a language cross-"compiler" and some hooks to write Java-compatible Ruby classes. Unfortunately, I was repeatedly stymied by a couple things:

  • Having the conversion happen automatically
  • Having the resultant "compiled" classes use the right class loader, allowing them to access other classes in the app
  • Implementing Java Interfaces

The first one was the most work but also the first to be solved: I wrote an Eclipse builder. The latter two were tough until I realized yesterday that the answers were sitting under my nose the whole time. For the classloader, I was able to switch the compiled output from using the global JRuby runtime to a runtime stored in the application scope and set to use the loader from facesContext.getContextClassLoader(). For the interfaces, it turned out that JRuby already had another annotation for declaring implemented interfaces, unsurprisingly called "java_implements".

So the upshot of this is: now I can write classes in Ruby and have Designer automatically convert them to Java and then compile those classes, ready to be used like any "normal" Java class in an XPage app. For demonstration purposes, I whipped up a useless-in-reality class to implement DataObject:

require "java"

java_package "frostillicus"

class TestDataObject
	java_implements "com.ibm.xsp.model.DataObject"
	
	
	def initialize
		@values = {}
	end
	
	java_signature "Object getValue(Object key)"
	def [](key)
		@values[key] or "#{key.to_s} not found"
	end
	
	java_signature "void setValue(Object key, Object value)"
	def []=(key, value)
		@values[key] = value
	end
	
	java_signature "Class<?> getType(Object key)"
	def get_type(key)
		java.lang.Class.class
	end
	
	java_signature "boolean isReadOnly(Object key)"
	def read_only?(key)
		false
	end
end

Once you have that file, it gets automatically converted to Java and compiled (once you have your build path set up correctly), and then it's available for use in other Java classes, in SSJS (or Ruby-in-XPages), and as a managed bean.

As you might expect with something like this, particularly for a first draft, there are a number of caveats:

  • The implementation is ugly as sin. There's a part that actually contains Java code that writes Ruby code that writes Java. It's very much a product of a series of "I wonder if this would work..." tests.
  • I haven't tested it to look for weird conflicts or leaks, though I suspect that my use of application-specific runtimes will help head off the worst of those potential problems.
  • You can't write classes that extend other classes. While I think you can do this within JRuby itself, the resultant objects are children of RubyObject, and there's no multiple inheritance. Interfaces will work in many cases, but that still limits the applicability.
  • Since the building happens in Designer, you need to have the feature installed client-side, which is not a problem with Ruby-in-XPages.
  • All those Java annotations really harsh the buzz of writing Ruby code. In that example above, the Ruby class isn't really any cleaner than a pure-Java equivalent. However, for larger, more complex classes, the expressiveness benefits of Ruby  would start to show.
  • There's basically no IDE help to be had. I haven't written anything into the builder to highlight syntax errors yet and non-syntax errors (like implementing an Interface but not all the methods) only show up with build errors on the resultant Java class. Plus, there's no autocomplete, and you only get syntax highlighting if you specifically install Ruby syntax support (I think I got it from the Eclipse Ganymede update site). Some of those may be fixable with minor effort, but the really big stuff would go far beyond the amount of time I have to dedicate to this kind of thing.

For now, I tossed the first draft up on GitHub for the curious. It will take more work to be be considered anywhere near finished, but getting it this far means I can start using it in personal/demo apps and really start finding the rough edges in practical use.

New Comment