Soap4r orders multi-element sequences incorrectly?

Hi all-

I'm building an app using soap4r v1.5.8 and I've generated client stubs
using wsdl2ruby. I can retrieve a complex object but when I try to
'put' the same object I get an error from the API:

“Unmarshalling Error: cvc-complex-type.2.4.a: Invalid content was found
starting with element ‘n3:logicalOperation’. One of
’{“http://www.strongmail.com/services/2009/03/02/schema”:condition}’ is
expected.”

When I examine the raw XML I see differences in the structure where the
error is being reported. Here is the part of the XML returned for a
'get' operation:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
    <getResponse
xmlns="http://www.strongmail.com/services/2009/03/02/schema">
      <success>true</success>
      <fault xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:nil="true" />
      <getResponse>
        <baseObject
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Rule">
          <!-- snip -->
          <ifPart>
            <condition1>
              <column>table.code</column>
              <op>EQUALS</op>
              <value>IC</value>
            </condition1>
            <logicalOperation>AND</logicalOperation>
            <condition>
              <column>table.level</column>
              <op>NOT_ONE_OF</op>
              <value>gold,platinum</value>
            </condition>
            <logicalOperation>OR</logicalOperation>
            <condition>
              <column>table.level</column>
              <op>EQUALS</op>
              <value>none</value>
            </condition>
          </ifPart>
          <!-- snip -->
        </baseObject>
      </getResponse>
    </getResponse>
  </soap:Body>
</soap:Envelope>

And here's the XML generated by soap4r for the corresponding 'put'
request to the same API:

<env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <env:Body>
    <n3:create
xmlns:n3="http://www.strongmail.com/services/2009/03/02/schema">
      <n3:baseObject xsi:type="n3:Rule">
        <!-- snip -->
        <n3:ifPart>
          <n3:condition1>
            <n3:column>table.code</n3:column>
            <n3:op>EQUALS</n3:op>
            <n3:value>IC</n3:value>
          </n3:condition1>
          <n3:logicalOperation>AND</n3:logicalOperation>
          <n3:logicalOperation>OR</n3:logicalOperation> <!-- TOO SOON
-->
          <n3:condition> <!-- TOO LATE -->
            <n3:column>table.level</n3:column>
            <n3:op>NOT_ONE_OF</n3:op>
            <n3:value>gold,platinum</n3:value>
          </n3:condition>
          <n3:condition>
            <n3:column>table.level</n3:column>
            <n3:op>EQUALS</n3:op>
            <n3:value>none</n3:value>
          </n3:condition>
        </n3:ifPart>
        <!-- snip -->
      </n3:baseObject>
    </n3:create>
  </env:Body>
</env:Envelope>

I create this request using the same Ruby object created by the original
response. I would expect soap4r to generate identical XML for the
object, but notice how the ordering of elements differs - it should be
"condition1, logicalOperation(AND), condition, logicalOperation(OR),
condition".

When I inspect the Ruby object I see that logicalOperation and condition
are both Arrays containing the original values in the right order. I
assume this is a correct transformation from the original XML using the
XSD/WSDL... I'm hoping someone here knows more about the inner workings
of soap4r and can help me find a workaround.

Here's the relevant section from the XSD:

  <xs:complexType name="RuleIfPart">
    <xs:sequence>
      <xs:element name="condition1" type="tns:RuleIfPartCondition"/>
      <xs:sequence minOccurs="0" maxOccurs="unbounded">
        <xs:element name="logicalOperation"
type="tns:LogicalOperation"/>
        <xs:element name="condition" type="tns:RuleIfPartCondition"/>
      </xs:sequence>
    </xs:sequence>
  </xs:complexType>

And here's the class generated by wsdl2ruby:

# {http://www.strongmail.com/services/2009/03/02/schema}RuleIfPart
# condition1 - RuleIfPartCondition
# logicalOperation - LogicalOperation
# condition - RuleIfPartCondition
class RuleIfPart
  attr_accessor :condition1
  attr_accessor :logicalOperation
  attr_accessor :condition

  def initialize(condition1 = nil, logicalOperation = [], condition =
[])
    @condition1 = condition1
    @logicalOperation = logicalOperation
    @condition = condition
  end
end

The full WSDL and XSD are more than 4k lines - I can send them directly
if someone wants to peel them apart. Any thoughts on how I can
workaround this issue?

Many thanks in advance!

-Justin

···

--
Posted via http://www.ruby-forum.com/.

Hi Justin,

Were you able to resolve this issue? I have run into a similar problem
and I am having a tough time working around it.

Thanks,
James

···

--
Posted via http://www.ruby-forum.com/.

Hi James-

I ended up writing a workaround that (so far) fixes the issue in my
project, though someone may be able to implement this more cleanly. I
put this in a separate file in my project rather than mangle the soap4r
source:

module SOAP
  module Mapping
    class LiteralRegistry
      def multielement_sequence_size(obj, definition)
        num_arrays = 0
        use_arrays = 0
        mes_size = 0

        definition.each do |e|
          num_arrays += 1 if e.respond_to?('as_array?') and e.as_array?

          if e.respond_to?('varname') and Mapping.get_attribute(obj,
e.varname).class == Array
            use_arrays += 1 if Mapping.get_attribute(obj,
e.varname).length > 1
            mes_size = Mapping.get_attribute(obj, e.varname).length
          end
        end

        if definition.class == SchemaSequenceDefinition and
definition.size > 1 and num_arrays == definition.size and use_arrays ==
definition.size
          mes_size
        else
          0
        end
      end

      def stubobj2soap_elements(obj, ele, definition, is_choice = false)
        added = false
        case definition
        when SchemaSequenceDefinition, SchemaEmptyDefinition
          # check for multi-element sequence
          mes_size = multielement_sequence_size(obj, definition)
          if mes_size > 0
            # maintain order of multi-element sequences
            added = true
            (0...mes_size).each do |idx|
              definition.each do |e|
                ele.add(definedobj2soap(Mapping.get_attribute(obj,
e.varname)[idx], e))
              end
            end
          else
            definition.each do |eledef|
              ele_added = stubobj2soap_elements(obj, ele, eledef,
is_choice)
              added = true if ele_added
            end
          end
        when SchemaChoiceDefinition
          definition.each do |eledef|
            added = stubobj2soap_elements(obj, ele, eledef, true)
            break if added
          end
        else
          added = true
          if definition.as_any?
            any = Mapping.get_attributes_for_any(obj)
            SOAPElement.from_objs(any).each do |child|
              ele.add(child)
            end
          elsif obj.respond_to?(:each) and definition.as_array?
            obj.each do |item|
              ele.add(definedobj2soap(item, definition))
            end
          else
            child = Mapping.get_attribute(obj, definition.varname)
            if child.nil? and (is_choice or definition.minoccurs == 0)
              added = false
            else
              if child.respond_to?(:each) and definition.as_array?
                if child.empty?
                  added = false
                else
                  child.each do |item|
                    ele.add(definedobj2soap(item, definition))
                  end
                end
              else
                ele.add(definedobj2soap(child, definition))
              end
            end
          end
        end
        added
      end
    end
  end
end

YMMV

-Justin

···

--
Posted via http://www.ruby-forum.com/.

Thanks Justin. Very much appreciated.

···

--
Posted via http://www.ruby-forum.com/.