You are viewing a plain text version of this content. The canonical link for it is here.
Posted to java-user@axis.apache.org by Phil McCarthy <ph...@dessicated.org> on 2004/08/27 13:10:23 UTC

WSDL for no-arg operation in document style

Hello,

I'm trying to figure out what the correct way is to represent a zero-parameter
operation in a document-style webservice (for instance, a getCartContents() call
to a stateful e-commerce webservice). I haven't found anything explicit in the
docs, or in the list archives, though there is some special-case code in
providers.java.RPCProvider that suggests it's a legitimate thing to do:

    // special case code for a document style operation with no
    // arguments (which is a strange thing to have, but whatever)

Seems to me that the client should send an empty soap:Body, since any content
would be a document to be passed as a parameter to the method by Axis. That's
exactly what the special-case code in RPCProvider handles - a null body.

The WSDL to represent this could be:

<wsdl:operation name="getCart"> 
    <wsdl:input message="impl:getCartRequest" /> 
    <wsdl:output message="impl:getCartResponse"/> 
</wsdl:operation> 
 
<wsdl:message name="getCartRequest"> 
</wsdl:message> 
 
(A request-response operation requires an input element, but a message 
has minOccurs="0" for part, so can be empty).

I'm using the .NET SDK's wsdl.exe tool to create client stubs, and this WSDL
does indeed produce a nice zero-argument getCart() method in the generated code.

However, there's a slight problem when a request with an empty body is sent to Axis:

Request:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body></soap:Body>
</soap:Envelope>

Response:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
 <soapenv:Body>
  <getCartReturn xmlns=""><stuffInTheCart.../></getCartReturn>
 </soapenv:Body>
</soapenv:Envelope>

The problem here is that the namespace on the getCartReturn is "", so the client
doesn't see the {http://foobar/}getCartReturn it expects.

(Incidentally, RPCProvider routes this call by looking up the first zero-arg
method it can find on the service, and calling it - a problem if there's more
than one. The SOAPAction header gets ignored).

Looking for a workaround, I tried sending an empty getCart request to Axis (no
code changes), and this gets a saner response:

Request:
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
  <soap:Body>
  <getCart xmlns="http://foobar/"></getCart>
  </soap:Body>
</soap:Envelope>

Response:
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
 <soapenv:Body>
  <getCartReturn xmlns="http://foobar/"><stuffInTheCart.../></getCartReturn>
 </soapenv:Body>
</soapenv:Envelope>

So, doing it this way the namespace on the returned document is set correctly.

But ... what's the WSDL for the operation now?

<wsdl:message name="getCartRequest">
    <wsdl:part element="impl:getCart" name="in0"/>
</wsdl:message>

There are two suggestions I've seen for implementing this "void" getCart element:

1:
<element name="getCart" type="xsd:anyType" nillable="true"/>

If I go with the first approach, my problem is that the generated client stub
for this method expects to take an single, nullable parameter. So I have to call
it from client code with getCart(null) - a little clumsy,

2:
<element name="getCart" type="impl:voidType"/>
<complexType name="voidType>
    <complexContent>
        <restriction base="anyType"/>
    </complexContent>
</complexType>

Now wsdl.exe creates an extra, empty class definition called voidType, and the
getCart method signature requires a single parameter of type voidType.

IMHO, wdsl.exe is not at fault here - it seems to be doing exactly the right
thing in both cases. What I really want to be able to do is send an empty Body
and have Axis behave properly, not use nasty workarounds like this.

So, does anyone know how to send an empty soap:Body to Axis, and have it return
a response in the correct namespace?  I suspect I'm trying to do something a bit
weird here, and the spec/ implementation is a bit fuzzy. 

Cheers,
Phil

Parameter versus no parameter

Posted by to...@gsk.com.
Apologies for the length of this but I wanted to include WSDL and 
generated code to give a complete picture.

I've been trying to get simple Java client code generated for a service 
that has no input but does have some output. Defining an input message 
with a part that has an empty element produces Java code, via WSDL2Java, 
that works fine, provided I pass a null parameter. The service method in 
the generated stub is trying to pass back the Java object I want and the 
Axis code can deserialize it fine.

However, if I define an input message with no parts, the generated stub 
includes a no-arguments method, which is what I want, but it tries to 
return an object typed to a different class, the class generated to 
represent to SOAP response body. However, the actual response from the 
service is the same in both cases, so the no-arguments method fails with a 
class cast exception.

It seems that the Axis code is trying to deserialize the response as 
though all the elements below the body root are attributes of the root 
class. I've noticed that the generated stub for the no-arguments version 
sets the operation as document/literal instead of wrapped/literal, and 
sets different return properties for the operation. If I alter the 
generated stub to match the stub generated for the single argument version 
(apart from keeping the no-argument version of the service method), then 
it works fine.

Does anyone know how to get the Axis WSDL2Java tool to generate working 
code for the no-arguments version? I've included the WSDL and generated 
code below, in case my explanation is not clear.

Thanks for any help.
Tony


The WSDL (without the XML/SOAP namespace declarations) for the single 
argument version is something like:

<?xml version="1.0" encoding="utf-8"?>
<definitions xmlns:services="uri:myServicesNameSpace" 
xmlns:mine="uri:myNameSpace" targetNamespace="uri:myServicesNameSpace">
  <types>
    <xs:schema targetNamespace="uri:myNameSpace" 
xmlns:my="uri:myNameSpace" elementFormDefault="qualified" 
attributeFormDefault="unqualified">
      <xs:complexType name="myObjectType">
        <xs:sequence>
          <xs:element name="id" type="xs:integer" nillable="false"/>
          <xs:element name="mf" type="xs:string" nillable="false"/>
        </xs:sequence>
      </xs:complexType>
      <xs:complexType name="myObjectList">
        <xs:sequence>
          <xs:element ref="my:thing" maxOccurs="unbounded"/>
        </xs:sequence>
      </xs:complexType>
      <xs:element name="thing" nillable="false" type="my:myObjectType"/>
      <xs:element name="things" nillable="false" type="my:myObjectList"/> 
    </xs:schema>
    <xs:schema elementFormDefault="qualified" 
targetNamespace="uri:myServicesNameSpace">
      <xs:complexType name="serviceResponse">
        <xs:sequence>
          <xs:element ref="mine:things"/>
        </xs:sequence>
      </xs:complexType>
      <xs:element name="getObjects">
      </xs:element>
      <xs:element name="objectDescriptions" 
type="services:serviceResponse">
      </xs:element>
    </xs:schema>
  </types>
  <message name="getObjectsRequest">
    <part name="getObjects" element="services:getObjects"/>
  </message>
  <message name="getObjectsReply">
    <part name="getObjects" element="services:objectDescriptions"/>
  </message>
  <portType name="objectsPortType">
    <operation name="getObjects">
      <input message="services:getObjectsRequest"/>
      <output message="services:getObjectsReply"/>
    </operation>
  </portType>
  <binding name="ObjectsBinding" type="services:objectsPortType">
    <soap:binding style="document" 
transport="http://schemas.xmlsoap.org/soap/http"/>
    <operation name="getObjects">
      <soap:operation soapAction="getObjects" style="document"/>
      <input>
        <soap:body use="literal"/>
      </input>
      <output>
        <soap:body use="literal"/>
      </output>
    </operation>
  </binding>
  <service name="ObjectsLookup">
    <port name="ObjectsPort" binding="services:ObjectsBinding">
      <soap:address 
location="http://my.com:8080/axis/services/ObjectsLookup"/>
    </port>
  </service>
</definitions>

The relevant sections of the generated stub are:

public class ObjectsBindingStub extends org.apache.axis.client.Stub 
implements myServicesNameSpace.ObjectsPortType {

    ...

    static {
        _operations = new org.apache.axis.description.OperationDesc[1];
        org.apache.axis.description.OperationDesc oper;
        oper = new org.apache.axis.description.OperationDesc();
        oper.setName("getObjects");
        oper.addParameter(new 
javax.xml.namespace.QName("uri:myServicesNameSpace", "getObjects"), new 
javax.xml.namespace.QName("http://www.w3.org/2001/XMLSchema", "anyType"), 
java.lang.Object.class, org.apache.axis.description.ParameterDesc.IN, 
false, false);
        oper.setReturnType(new 
javax.xml.namespace.QName("uri:myNameSpace", "myObjectList"));
        oper.setReturnClass(myNameSpace.MyObjectList.class);
        oper.setReturnQName(new 
javax.xml.namespace.QName("uri:myNameSpace", "things"));
        oper.setStyle(org.apache.axis.enum.Style.WRAPPED);
        oper.setUse(org.apache.axis.enum.Use.LITERAL);
        _operations[0] = oper;

    }

    ....

    public myNameSpace.MyObjectList getObjects(java.lang.Object 
getObjects) throws java.rmi.RemoteException {
        if (super.cachedEndpoint == null) {
            throw new org.apache.axis.NoEndPointException();
        }
        org.apache.axis.client.Call _call = createCall();
        _call.setOperation(_operations[0]);
        _call.setUseSOAPAction(true);
        _call.setSOAPActionURI("getObjects");
        _call.setEncodingStyle(null);
        _call.setProperty(org.apache.axis.client.Call.SEND_TYPE_ATTR, 
Boolean.FALSE);
        _call.setProperty(org.apache.axis.AxisEngine.PROP_DOMULTIREFS, 
Boolean.FALSE);
 
_call.setSOAPVersion(org.apache.axis.soap.SOAPConstants.SOAP11_CONSTANTS);
        _call.setOperationName(new 
javax.xml.namespace.QName("uri:myServicesNameSpace", "getObjects"));

        setRequestHeaders(_call);
        setAttachments(_call);
        java.lang.Object _resp = _call.invoke(new java.lang.Object[] 
{getObjects});

        if (_resp instanceof java.rmi.RemoteException) {
            throw (java.rmi.RemoteException)_resp;
        }
        else {
            extractAttachments(_call);
            try {
                return (myNameSpace.MyObjectList) _resp;
            } catch (java.lang.Exception _exception) {
                return (myNameSpace.MyObjectList) 
org.apache.axis.utils.JavaUtils.convert(_resp, 
myNameSpace.MyObjectList.class);
            }
        }
    }

}

This is all fine. However, when I alter the WSDL so that the input message 
becomes:

  <message name="getObjectsRequest">
  </message>

Then the relevant part of the generated stub is something like this:

public class ObjectsBindingStub extends org.apache.axis.client.Stub 
implements myServicesNameSpace.ObjectsPortType {

    ...

    static {
        _operations = new org.apache.axis.description.OperationDesc[1];
        org.apache.axis.description.OperationDesc oper;
        oper = new org.apache.axis.description.OperationDesc();
        oper.setName("getObjects");
        oper.setReturnType(new 
javax.xml.namespace.QName("uri:myServicesNameSpace", "serviceResponse"));
        oper.setReturnClass(myServicesNameSpace.ServiceResponse.class);
        oper.setReturnQName(new 
javax.xml.namespace.QName("uri:myServicesNameSpace", 
"objectDescriptions"));
        oper.setStyle(org.apache.axis.enum.Style.DOCUMENT);
        oper.setUse(org.apache.axis.enum.Use.LITERAL);
        _operations[0] = oper;

    }

    ...

    public myServicesNameSpace.ServiceResponse getObjects() throws 
java.rmi.RemoteException {
        if (super.cachedEndpoint == null) {
            throw new org.apache.axis.NoEndPointException();
        }
        org.apache.axis.client.Call _call = createCall();
        _call.setOperation(_operations[0]);
        _call.setUseSOAPAction(true);
        _call.setSOAPActionURI("getObjects");
        _call.setEncodingStyle(null);
        _call.setProperty(org.apache.axis.client.Call.SEND_TYPE_ATTR, 
Boolean.FALSE);
        _call.setProperty(org.apache.axis.AxisEngine.PROP_DOMULTIREFS, 
Boolean.FALSE);
 
_call.setSOAPVersion(org.apache.axis.soap.SOAPConstants.SOAP11_CONSTANTS);
        _call.setOperationName(new javax.xml.namespace.QName("", 
"getObjects"));

        setRequestHeaders(_call);
        setAttachments(_call);
        java.lang.Object _resp = _call.invoke(new java.lang.Object[] {});

        if (_resp instanceof java.rmi.RemoteException) {
            throw (java.rmi.RemoteException)_resp;
        }
        else {
            extractAttachments(_call);
            try {
                return (myServicesNameSpace.ServiceResponse) _resp;
            } catch (java.lang.Exception _exception) {
                return (myServicesNameSpace.ServiceResponse) 
org.apache.axis.utils.JavaUtils.convert(_resp, 
myServicesNameSpace.ServiceResponse.class);
            }
        }
    }

}

Re: WSDL for no-arg operation in document style

Posted by to...@gsk.com.
I'm just starting to use AXIS (and web services), apart from brief 
acquaintances 6 and 18 months ago. I've also been working with client 
code, going to a no-arguments web service. I'm using AXIS at both ends and 
noticed the same ugly code (having to pass null) as you did, for your 
workaround. I tried an empty message body in the WSDL and got the 
no-argument method generated (using WSDL2Java, from AXIS). Unfortunately, 
this caused a different error to the one you're seeing. The namespace is 
there but the deserialization of the response has gone wrong. The same 
response is deserialized fine when passing null, but isn't when a 
no-arguments method is used.

I haven't worked out why it's gone wrong, with the no-argument method, but 
the response looks fine to me.

Tony