XML Data-Binding for JRuby
Bob McWhirter
15 February 2009

In the process of binding a fancy SOAP stack to JBoss-Rails, I of course became frustrated working with DOM trees or StAX iterators.  If you're using 100% Pure Java(tm), you'd use JAXB, and compile up a nice tree matching the object-model described by the XSD in the WSDL.

I, on the other hand, am not using 100% Pure Java(tm) syntax and processes. I'm dynamically deploying WSDLs handled by dynamically reloadable Ruby classes.

So I need a dynamic data-binding to wire up to the Apache-CXF chain.  A CXF DataBinding is responsible for handling readers and writers capable of converting an XML stream into some other object.  And back again.  The data-binding is informed by the XML Schema used to define the web-service.

Now, some examples using the Amazon EC2 WSDL. 

The describe-instances operation is used to list information about running server instances on EC2.  The request message is defined by this XSD fragment:

<xs:element name="DescribeInstances" 
type="tns:DescribeInstancesType"/>

<xs:complexType name="DescribeInstancesType">
<xs:sequence>
<xs:element name="instancesSet"
type="tns:DescribeInstancesInfoType"/>
</xs:sequence>
</xs:complexType>

<xs:complexType name="DescribeInstancesInfoType">
<xs:sequence>
<xs:element name="item"
type="tns:DescribeInstancesItemType"
minOccurs="0"
maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>

<xs:complexType name="DescribeInstancesItemType">
<xs:sequence>
<xs:element name="instanceId"
type="xs:string" />
</xs:sequence>
</xs:complexType>

When deployed (at runtime) the Ruby data-binding swizzles that into some relatively simple Ruby classes:

class DescribeInstancesType
attr_accessor :instancesSet

def initialize()
@instancesSet = DescribeInstancesInfoType.new()
end
end

class DescribeInstancesInfoType < ::Array
end

class DescribeInstancesItemType
attr_accessor :instanceId

def initialize()
@instanceId = ''
end
end

Through some magic involving subclassing ::Array, when a portion of the tree is array-like, we end up with a very intuitive-to-use model.  We've borrowed this strategy from xsd2ruby by the soap4r guys.

Throwing some XML at it:

<ns1:DescribeInstances>
<ns1:instancesSet>
<ns1:item>
<ns1:instanceId>foo</ns1:instanceId>
</ns1:item>
<ns1:item>
<ns1:instanceId>bar</ns1:instanceId>
</ns1:item>
<ns1:item>
<ns1:instanceId>baz</ns1:instanceId>
</ns1:item>
</ns1:instancesSet>
</ns1:DescribeInstances>

Results in the Ruby object:

#<DescribeInstancesType:0x57581ef 
@instancesSet=[#<DescribeInstancesItemType:0x23356034
@instanceId="foo">,
#<DescribeInstancesItemType:0x242e34f2
@instanceId="bar">,
#<DescribeInstancesItemType:0x6dc22478
@instanceId="baz">]>

And using it is quite simple:

root.instancesSet.each{|e| puts e.instanceId }

Likewise, in a day or two, we'll have the reverse, capable of marshalling a Ruby object right back to some XSD-defined XML.

And all of this will be used in the JBoss-Rails web-services bits.  It's all driven by the Java StAX parsers, so no need to argue about "REXML is slow!" or whatnot.  All XML-handling occurs from Java, against fast Java XML parsers.

  • Next JBoss Cloud 1.0.0.Beta2 is out!
  • Previous JBoss Microcontainer enables the good sort of evil
Copyright 2008-2010 Red Hat, Inc.