I am making some additions to the serialize method
of SerializationContext. (I will commit the
changes after I do some more testing.)
I thought it would be a good idea to explain why the
additions are useful.
So here is the new serialize method signature.
(The old signature is retained and delegates
to the new method).
public void serialize(QName elemQName,
Attributes attributes,
Object value,
Class javaType,
QName xmlType, // new
boolean sendNull, // new
boolean sendType) // new
Here is a description of the old parameters:
elemQName - This is the name of the element.
Nothing fancy here.
attributes - These are additional attributes
that are to be serialized on the element.
Normally one would pass in null. The
serialize method is in charge of
adding attributes to deal with types,
null values and multi-ref values.
value - This is the value to be serialized.
Note that it is an Object, so if the
value that you want serialized is a
primitive it must be wrapped.
javaType - This is a parameter that I added several
months ago. The javaType is the class
of the value. Why not just do a value.getClass()?
Well...the value could be null or the value
could be a wrapped primitive. So the
javaType is necessary to know the class
of the value and is very useful for distinguishing
between actual primitives and wrappers.
------------------------
Problem 1:
Isn't there something missing ? How does the serializer know
what QName to use for the xsi:type attribute ?
Prior to my change, it used the javaType information to get
a type QName from the TypeMappingRegistry. However this
algorithm has its flaws. For example a javaType=String.class
actually has 2 type QNames (xsd:string and soapenc:string).
The runtime will always pick xsd:string. This may cause
a remote service to fail if it was expecting a soapenc:string.
Solution 1:
A new xmlType parameter:
xmlType - This new parameter is the preferred type QName.
If present, the serialize method will
use the (javaType, xmlType) pair to find
the appropriate serializer. If a serializer is
not found, the code uses the old serializer search
algorithm.
This allows a custom serializer or meta data aware serializer
to give the serialize method the proper xmlType from the wsdl/xml
schema. Pretty cool!
------------------------
Problem 2:
XML has two ways of dealing with missing information.
1) An element could be defined with the attribute xsi:nillable="true",
which means that if the value is missing, the element should be
serialized with the xsi:nil="true" flag.
2) An element could be defined with the attribute minOccurs="0",
which means that if the value is missing, don't serialize it at all.
The runtime (before my change) treats everything as (1).
Solution 2:
sendNull - The sendNull flag indicates how null values should
be processed. The default (true) handles situation (1).
And sendNull=false handles situation (2).
This allows a custom serializer or meta data aware serializer
to tell the serialize method how to deal with null values.
---------------
Problem 3:
Ever notice that xsi:types are serialized on every single element.
It looks pretty silly to see an array that has arrayType="xsd:string[100]"
followed by 100 items that each have xsi:type="xsd:string".
There needs to be a way to turn off the xsi:type in a reasonable way.
(Aside: I know there is a sendXSIType global property. But this doesn't
cut the mustard. There are a number of problems with using the sendXSIType
property:
1) When serialize is called, is the value serialized ? Uh No. If the
item is an object it is simply stored away and serialized later.
So toggling sendXSIType doesn't help much here.
2) We really want to only send the xsi:type when it is necessary.
Lets say that we have an array with arrayType="my:Foo[100]".
The xsi:type setting is not necessary for the array elements
that are of type "my:Foo". However the xsi:type setting is
necessary for the array elements that are derived from "my:Foo".
)
Solution 3:
sendType - The sendType flag works with the xmlType parameter.
The default, sendType=true, is the current behavior, which
cause xsi:type to be set for everything.
The setting sendType=false, indicates that the xsi:type
should not be sent if a serializer was found for
the (javaType, xmlType) pair (i.e. if the value is
not serialized in the expected way, attach an xsi:type.)
I changed the ArraySerializer to use sendType=true so that it
only uses xsi:type for array elements when truly necessary. Cool!
There are other reasons to set sendType=true.
i) Elements with anonymous types don't have a type QName. Axis
sends a make-believe type qname over the wire in such cases.
It would be nice to turn off this behavior!
ii) The BeanDeserializer deserializes property values even
if the xsi:type is not set. It automatically deserializes into the
property type. So it might be an improvement to not serialize
the xmi:types for bean properties when the javaType matches the
bean property type.
Aside: I ran the interop tests to verify my ArraySerializer changes.
All of the remote services correctly handled the StringArray and
IntegerArray
without the xsi:types. However, some of the services could not handle
StructArray...perhaps because the Struct array items are treated as
multiref
elements. So I made a small change to SerializationContext to make sure
xsi:types are generated for multiref elements.
----------------------
Rich Scheuerle
XML & Web Services Development
512-838-5115 (IBM TL 678-5115)