namespace issue on web service with Apache CXF

2020-05-29 02:07发布

问题:

I'm using Apache CXF 2.7.3, and running into a namespace issue that I really don't understand. I've tried extensively to search on this, but most of the results that I find are for different behaviors. The issue is when invoking the web service, it will fail if the parameter element is namespace qualified. All the rest of the elements in the message are qualified, and it accepts that, just not the parameter element. Here is the precise behavior:

request WITHOUT parameter element qualified:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
                  xmlns:ord="http://www.example.org/order">
<soapenv:Header/>
   <soapenv:Body>
      <ord:getOrder>
         <id>a</id>
      </ord:getOrder>
   </soapenv:Body>
</soapenv:Envelope>

results in success:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
   <soap:Body>
      <ns2:getOrderResponse xmlns:ns2="http://www.example.org/order">
         <return>
            <ns2:errorCode/>
            <ns2:errorMessage/>
            <ns2:order>
               <ns2:orderNumber>ABC123</ns2:orderNumber>
               <ns2:lastName>Smith</ns2:lastName>
            </ns2:order>
         </return>
      </ns2:getOrderResponse>
   </soap:Body>
</soap:Envelope>

request WITH parameter qualified:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
                  xmlns:ord="http://www.example.org/order">
   <soapenv:Header/>
   <soapenv:Body>
      <ord:getOrder>
         <ord:id>a</ord:id>
      </ord:getOrder>
   </soapenv:Body>
</soapenv:Envelope>

results in exception from JAXB:

<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
   <soap:Body>
      <soap:Fault>
         <faultcode>soap:Client</faultcode>
         <faultstring>Unmarshalling Error: unexpected element (uri:"http://www.example.org/order", local:"id"). Expected elements are &lt;{}id></faultstring>
      </soap:Fault>
   </soap:Body>
</soap:Envelope>

In all the research I have done, this usually means that a namespace doesn't match up somewhere. But I have checked it thoroughly, and the namespace is identical everywhere, including the ObjectFactory.class, package-info.class, and the cxf-servlet.xml configuration file as well as the @WebService annotation. Can anyone point me in the right direction as to what I am missing here?

回答1:

The source of the problem is wsgen, and I think this is a bug. It does not make the wsdl and the jaxb generated classes compatible. In the jaxb generated classes, the elements are not default form qualified, which puts the parameter element into a null namespace. However in the WSDL, it IS default form qualified, and therein lies the problem. There are probably a number of ways to solve this, the most quick and dirty way I found is to set the targetNamespace on the @WebParam annotation. Here are code snippets to demonstrate what I mean, and I hope this helps someone else who runs into this.

Here is what I had originally for the bottom-up implementation class:

@WebService(serviceName="OrderService")
public class OrderService {

    public OrderResponse getOrder(@WebParam(name="id", targetNamespace="http://www.example.org/order") String id)  {

This will result in the following generated JAXB classes. As you can see it sets the namespace for the root but its not form qualified, and it does not generate a package-info file either.

@XmlRootElement(name = "getOrder", namespace = "http://www.example.org/order")
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "getOrder", namespace = "http://www.example.org/order")

public class GetOrder {

    @XmlElement(name = "id")
    private java.lang.String id;

Then I changed the service implementation class, to add the namespace to the @WebParam:

@WebService(serviceName="OrderService", targetNamespace="http://www.example.org/order")
public class OrderService {

    public OrderResponse getOrder(@WebParam(name="id", targetNamespace="http://www.example.org/order") String id)  {

While that doesn't make it default form qualified, it does add the namespace to the element in the generated JAXB class:

@XmlRootElement(name = "getOrder", namespace = "http://www.example.org/order")
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "getOrder", namespace = "http://www.example.org/order")

public class GetOrder {

    @XmlElement(name = "id", namespace = "http://www.example.org/order")
    private java.lang.String id;