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

true






table.code
EQUALS
IC

AND

table.level
NOT_ONE_OF
gold,platinum

OR

table.level
EQUALS
none






</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”>

n3:ifPart
n3:condition1
n3:columntable.code</n3:column>
n3:opEQUALS</n3:op>
n3:valueIC</n3:value>
</n3:condition1>
n3:logicalOperationAND</n3:logicalOperation>
n3:logicalOperationOR</n3:logicalOperation>
n3:condition
n3:columntable.level</n3:column>
n3:opNOT_ONE_OF</n3:op>
n3:valuegold,platinum</n3:value>
</n3:condition>
n3:condition
n3:columntable.level</n3:column>
n3:opEQUALS</n3:op>
n3:valuenone</n3:value>
</n3:condition>
</n3:ifPart>

</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

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

Thanks Justin. Very much appreciated.

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