soap4r orders multi-element sequences incorrectly?

  • Thread starter Justin Zipperle
  • Start date
J

Justin Zipperle

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:eek:p>EQUALS</n3:eek:p>
<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:eek:p>NOT_ONE_OF</n3:eek:p>
<n3:value>gold,platinum</n3:value>
</n3:condition>
<n3:condition>
<n3:column>table.level</n3:column>
<n3:eek:p>EQUALS</n3:eek:p>
<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
 
J

James O'meara

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
 
J

Justin Zipperle

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
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,537
Members
45,022
Latest member
MaybelleMa

Latest Threads

Top