We’ve had a lot of luck using JRuby on various different projects. One of the great advantages of JRuby is that you have the whole library of existing Java utilities at your disposal, plus all of the existing Ruby gems. I recently was able to integrate OpenSAML into a Ruby on Rails app. This was a great win because we needed functionality that wasn’t implemented in any of the Ruby SAML libraries.
However, what really struck me about it was that, even conflating the styles and conventions of both Ruby and Java together, the resulting code was more clean, readable, and terse than the original Java (in my opinion, at least.)
Below an example of creating a SAML message in Ruby using OpenSAML.
In no small part, this follows this OpenSAML example, but I like the way the Ruby turned out. I’ve also skipped some of the really boring bits like Ruby require statements and Java imports.
1. Define a Helper
First, we define a helper to build things:
def create(type)
if type.kind_of?(Class) or type.kind_of?(Module)
type = type.DEFAULT_ELEMENT_NAME
end
org.opensaml.xml::Configuration.builder_factory.get_builder(type).build_object(type)
end
Here’s the Java this was based on, though I’ve added a bit to my version:
@SuppressWarnings ("unchecked")
public T create (Class cls, QName qname)
{
return (T) ((XMLObjectBuilder)
Configuration.getBuilderFactory ().getBuilder (qname)).buildObject (qname);
}
The Ruby duck typing does shorten things a lot. While there are legitimate criticisms about duck typings usefulness the OpenSAML API’s heavy use of factories encourage casting anyway, so it is nice to save ourselves some typing.
2. Bootstrap the Library
Now, bootstrap the library:
DefaultBootstrap.bootstrap()
3. Instantiate Objects
Next, we can instantiate some objects to represent the SAML messages:
name_id = create(NameID)
name_id.format = NameIDType.TRANSIENT
name_id.value = "anonymous-1234567890"
subject = create(Subject)
subject.name_id = name_id
issuer = create(Issuer)
issuer.value = "myissuer.com"
assertion = create(Assertion)
assertion.setID "some_id_whatever"
assertion.subject = subject
response = create(Response)
response.signature = signature
response.issuer = issuer
response.assertions << assertion
response.status = create(Status)
response.status.status_code = create(StatusCode)
response.status.status_code.value = StatusCode.SUCCESS_URI
This is the stage where I feel like we've had a big win: We've been able to remove a lot of extraneous text and reduced this down to the things we really care the most about — the message we want to send. Ruby's ability to pass a reference around to just about anything means I was able to simplify the use of the factories to create each node, and JRuby's translation of accessors into attr_accessor-like behavior means we've cut out a lot of the most repetitive pieces.
4. Create XML Objects
And to make it ready to use, we can marshall it into a series of XML objects:
document = document_builder.newDocument
marshaller_factory.get_marshaller(response).marshall response, document
tf = TransformerFactory.newInstance()
transformer = tf.newTransformer()
writer = StringWriter.new()
transformer.transform(DOMSource.new(document), StreamResult.new(writer))
response_string = writer.buffer.to_s
Rails may not be the right tool for every job, but it provides a lot of nice capabilities. Combined with the huge ecosystem of mature Java libraries, it can be a powerful tool.