Ruby on Rails: anyType, soap4r and handsoap to the rescue

Perl makes the easy things easy and the hard things possible. - Larry Wall

This sentiment has been applied to many different languages, frameworks and systems. It not surprising; it's the underlying goal of most software. Ruby on Rails is a little different. It makes the easy things extremely easy. But god help you if you want to get off the train in between stops.

After playing with rails a few months ago, I wanted to upgrade my toy project by getting some data from a SOAP web-service. Google pointed me to soap4r, which seems to be the common solution. However, I quickly ran into problems.

It seems that the WSDL I was consuming made judicious use of the anyType primitive, which Java uses to expose an Object parameter in a method. However, the remote method end-points assumed you were going to tell it at runtime what the type of the object you're passing is. In practice, it was an integer.

<?xml version="1.0" encoding="utf-8" ?>
<env:Envelope xmlns:xsd=""
    <n1:find xmlns:n1="">
      <id xmlns:xsi="" xmlns:xs="" xsi:type="xs:int">67</id>

The soap4r generated classes output a packet without the namespaces on the id tag, which resulted in the following exception.

java.lang.IllegalArgumentException: Provided id of the wrong type for class com.bullhorn.entity.job.JobOrder.
Expected: class java.lang.Integer, got class org.apache.xerces.dom.ElementNSImpl
 at org.hibernate.ejb.AbstractEntityManagerImpl.find(
 at com.bullhorn.dataservice.jpa.BhEntityManagerImpl.find(
 at com.bullhorn.dataservice.serviceImpl.BaseService.find(
Caused by: org.hibernate.TypeMismatchException: Provided id of the wrong type for class com.bullhorn.entity.job.JobOrder.
Expected: class java.lang.Integer, got class org.apache.xerces.dom.ElementNSImpl
 at org.hibernate.event.def.DefaultLoadEventListener.onLoad(
 at org.hibernate.impl.SessionImpl.fireLoad(
 at org.hibernate.impl.SessionImpl.get(
 at org.hibernate.impl.SessionImpl.get(
 at org.hibernate.ejb.AbstractEntityManagerImpl.find(
 ... 48 more

You could rightly say that this was a Java problem, or at least a problem with the API of this web-service. It's a good illustration of why you should try to use primitives only for web-service parameters. However, in this case I could not control the web-service, so I needed to solve this in Ruby.

I briefly tried to download the WSDL and muck with it. SoapUI, by the way, has the ability to download a multi-part (think 50 parts) WSDL and save all the pieces locally. But I soon exhausted my expertise in hand-editing WSDL. Besides, ideally this needed to be done at runtime to support types other than integers.

Having "gone off the rails", I turned to a library called handsoap. Their philosophy is that you often need a higher level of control over the SOAP packets themselves.

...soap4r has problems. It's incomplete and buggy. If you try to use it for any real-world services, you quickly run into compatibility issues... Handsoap tries to do better by taking a minimalistic approach. Instead of a full abstraction layer, it is more like a toolbox with which you can write SOAP bindings. -troelskn

As promised, I did have to do the SOAP binding myself. But I also got the opportunity to do anything I wanted to the XML DOM object, including set these pesky required namespaces.

require 'handsoap'

Handsoap.http_driver = :httpclient
Handsoap::Service.logger = $stdout

class ApiService < Handsoap::Service

  def on_create_document(doc)
    # register namespaces for the request
    doc.alias 'tns', ''

  def on_response_document(doc)
    # register namespaces for the response
    doc.add_namespace 'ns', ''

  def start_session!(state)
    soap_action = ''
    response = invoke('tns:startSession', soap_action) do |message|
      message.add "username", state[:username]
      message.add "password", state[:password]
      message.add "apiKey", state[:apiKey]
    node = response/"//return"
    { :client => (node/"//client").to_s, :corporationId => (node/"//corporationId").to_s , :userId => (node/"//userId").to_s}

  def find(state)
    soap_action = ''
    response = invoke('tns:find', soap_action) do |message|

      session = state[:session]

      message.add "session" do |i|
        i.add "client", session[:client]
        i.add "corporationId", session[:corporationId]
        i.add "userId", session[:userId]

      message.add "entityName", state[:entityName]

      # ids are set to "anyType" in the WSDL, and our end-point enforces that these namespaces be in the post
      message.add "id", state[:id] do |i|
        i.set_attr("xmlns:xsi", "")
        i.set_attr("xmlns:xs", "")
        i.set_attr("xsi:type", "xs:int")



PS - Installing handsoap was a pita. I could not get the curb gem, a ruby binding for the Linux curl utility, installed. So this example uses httpclient, instead.

Tags: ,