You've hit one of those "funny" things with JAXB that I've never quite
understood. Officially, JAXB does not support Maps. However, the JAXB
runtime does kind of handle them as long as you don't specifically tell JAXB
about them by annotating it with and JAXB annotation. As soon as you do, it
barfs.
The only way to handle this is to write an XmlAdapter that would map the Map
into a class that JAXB would understand and then wire that in with
@XmlJavaTypeAdapter along with your XmlElement annotation.
You can see an example of how to handle maps like that in the java_first_jaxws
sample in the CXF distribution.
Dan
On Wednesday, December 07, 2011 10:10:05 AM Default SPAM wrote:
> Hi all,
>
> I would like to submit a question since I have noticed an inconsistency
> between WSDL generated by "org.apache.cxf.tools.java2ws.JavaToWS" and
> response sent by CXF.
>
> > I have a service like this:
> @WebService(endpointInterface = "be.eft.cbkv3.ws.WSExampleService",
> serviceName = "WSExampleService")
> public class WSExampleServiceImpl implements WSExampleService{
> protected final Log log= LogFactory.getLog(this.getClass());
>
> public WSExample getExample(String exampleId){
> return new WSExample();
> };
> }
>
> > This service is returning a WSExample that is a simple bean that
> > contains
>
> a Map:
>
> public class WSExample implements Serializable{
>
> /** identifier field */
> private Long id;
>
> /**
> * @return Returns the id.
> * @hibernate.id column = "ID" length = "19"
> * generator-class = "native"
> */
> public Long getId() {
> return id;
> }
> public void setId(Long id) {
> this.id = id;
> }
>
> /** An object containing all business data related to the auto-action
> */
> private Map<String,String> arguments;
>
> /**
> * @hibernate.map cascade= "all" table= "CBK_AUTOACTIONARGS"
> * @hibernate.key column= "CBK_AUTOACTIONID"
> foreign-key="AAARG_CBKAAID_FK"
> * @hibernate.index column= "NAME" type= "java.lang.String"
> * @hibernate.element column= "VALUE" type= "java.lang.String"
> * @return the arguments
> */
> public Map<String, String> getArguments() {
> return arguments;
> }
> public void setArguments(Map<String, String> arguments) {
> this.arguments = arguments;
> }
> }
>
> > The WSDL generated looks like:
> <?xml version="1.0" encoding="UTF-8"?>
> <wsdl:definitions name="ExampleService" targetNamespace="
> http://ws.cbkv3.eft.be/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
> xmlns:tns="http://ws.cbkv3.eft.be/" xmlns:xsd="
> http://www.w3.org/2001/XMLSchema" xmlns:soap="
> http://schemas.xmlsoap.org/wsdl/soap/">
> <wsdl:types>
> <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns="
> http://ws.cbkv3.eft.be/" attributeFormDefault="unqualified"
> elementFormDefault="unqualified" targetNamespace="http://ws.cbkv3.eft.be/">
> <xs:complexType name="wsExample">
> <xs:sequence>
> <xs:element name="arguments">
> <xs:complexType>
> <xs:sequence>
> <xs:element maxOccurs="unbounded" minOccurs="0" name="entry">
> <xs:complexType>
> <xs:sequence>
> <xs:element minOccurs="0" name="key" type="xs:string"/>
> <xs:element minOccurs="0" name="value" type="xs:string"/>
> </xs:sequence>
> </xs:complexType>
> </xs:element>
> </xs:sequence>
> </xs:complexType>
> </xs:element>
> <xs:element minOccurs="0" name="id" type="xs:long"/>
> </xs:sequence>
> </xs:complexType>
> <xs:element name="getExample" type="getExample"/>
> <xs:complexType name="getExample">
> <xs:sequence>
> <xs:element minOccurs="0" name="exampleId" type="xs:string"/>
> </xs:sequence>
> </xs:complexType>
> <xs:element name="getExampleResponse" type="getExampleResponse"/>
> <xs:complexType name="getExampleResponse">
> <xs:sequence>
> <xs:element minOccurs="0" name="return" type="wsExample"/>
> </xs:sequence>
> </xs:complexType>
> </xs:schema>
> </wsdl:types>
> <wsdl:message name="getExampleResponse">
> <wsdl:part name="parameters" element="tns:getExampleResponse">
> </wsdl:part>
> </wsdl:message>
> <wsdl:message name="getExample">
> <wsdl:part name="parameters" element="tns:getExample">
> </wsdl:part>
> </wsdl:message>
> <wsdl:portType name="WSExampleService">
> <wsdl:operation name="getExample">
> <wsdl:input name="getExample" message="tns:getExample">
> </wsdl:input>
> <wsdl:output name="getExampleResponse"
> message="tns:getExampleResponse">
> </wsdl:output>
> </wsdl:operation>
> </wsdl:portType>
> <wsdl:binding name="WSExampleServiceSoapBinding"
> type="tns:WSExampleService">
> <soap:binding style="document" transport="
> http://schemas.xmlsoap.org/soap/http"/>
> <wsdl:operation name="getExample">
> <soap:operation soapAction="" style="document"/>
> <wsdl:input name="getExample">
> <soap:body use="literal"/>
> </wsdl:input>
> <wsdl:output name="getExampleResponse">
> <soap:body use="literal"/>
> </wsdl:output>
> </wsdl:operation>
> </wsdl:binding>
> <wsdl:service name="ExampleService">
> <wsdl:port name="WSExampleServiceImplPort"
> binding="tns:WSExampleServiceSoapBinding">
> <soap:address location="http://localhost:9090/WSExampleServiceImplPort
> "/>
> </wsdl:port>
> </wsdl:service>
> </wsdl:definitions>
>
> > That's looks more or less fine, but once we receive a response from the
>
> server when there is no arguments (the WSExample#arguments is null) then we
> receive something like:
> <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
> xmlns:ws="http://ws.cbkv3.eft.be/">
> <soapenv:Header/>
> <soapenv:Body>
> <ws:getExampleResponse>
> <return>
> <id>myId</id>
> </return>
> </ws:getExampleResponse>
> </soapenv:Body>
> </soapenv:Envelope>
>
> > This response is not "compliant" with the WSDL since <arguments> should
>
> be present, we should receive from server (running CXF):
> <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
> xmlns:ws="http://ws.cbkv3.eft.be/">
> <soapenv:Header/>
> <soapenv:Body>
> <ws:getExampleResponse>
> <return>
> <arguments>
> </arguments>
> <id>myId</id>
> </return>
> </ws:getExampleResponse>
> </soapenv:Body>
> </soapenv:Envelope>
>
> To workaround this, I can explicitely (and manually) add 'minOccurs="0"
> nillable="true"' to 'arguments' element:
> <xs:element name="arguments" minOccurs="0" nillable="true">
>
> That way, my WSDL is consistent with what CXF is returning.
>
> But, it means we also cannot rely on the WSDL dynamically generated (with
> ?wsdl) once my service is deployed.
>
> I tried to add "@XmlElement(required=false, nillable=true)" annotation to
> getArguments() function but I got an error like:
> Caused by: com.sun.xml.bind.v2.runtime.IllegalAnnotationsException: 2
> counts of IllegalAnnotationExceptions
> [java] java.util.Map is an interface, and JAXB can't handle interfaces.
> [java] this problem is related to the following location: [java]
> at java.util.Map
> [java] at public java.util.Map
> be.eft.cbkv3.data.schema.WSExample.getArguments()
> [java] at be.eft.cbkv3.data.schema.WSExample
> [java] java.util.Map does not have a no-arg default constructor.
> [java] this problem is related to the following location:
> [java] at java.util.Map
> [java] at public java.util.Map
> be.eft.cbkv3.data.schema.WSExample.getArguments()
> [java] at be.eft.cbkv3.data.schema.WSExample
> [java] at
> com.sun.xml.bind.v2.runtime.IllegalAnnotationsException$Builder.check(Illega
> lAnnotationsException.java:106) [...]
>
> It sounds logic that if "arguments can be null" (and it can) we can get
> SOAP response with no <arguments> tag then CXF response sounds correct to
> me.
> But WSDL generated should then contains <... minOccurs="0" nillable="true">
> instead of nothing (knowing that when such arguments are not specified,
> defaults are <... minOccurs="1" nillable="false">.
>
> Any idea how to fix this inconsistency?
>
> Regards.
> Johann
>
> NB: Tried with CXF 2.2.9 and CXF 2.5.0.
--
Daniel Kulp
[email protected] - http://dankulp.com/blog
Talend Community Coder - http://coders.talend.com