Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SOAP WS - make @WebParam optional

I have quite a simple method, which I use in WS API by JAX-WS annotations:

@WebMethod
public MyResponse sendSingle2(
    @WebParam(name="username") String username,
    @WebParam(name="password") String password,
    @WebParam(name="newParam") String newParam) {
        // the code
    }

Now I want newParam to be optional. I mean I want the method still to work not only when parameter is empty in passed xml:

<ws:sendSingle2>
    <username>user</username>
    <password>pass</password>
    <newParam></newParam>
</ws:sendSingle2>

but also when it is absent:

<ws:sendSingle2>
    <username>user</username>
    <password>pass</password>
</ws:sendSingle2>

I need it not to break existing API, which works without the new param.

like image 494
amorfis Avatar asked Jan 17 '14 06:01

amorfis


1 Answers

@WebParam maps a message part to a parameter, and parts can't be optional. See Optional Message Parts in WSDL. Therefore the short answer is that precisely what you're asking can't be done. But if you can refactor this method, you can use one of the approaches described below.

Usually the optionality of a parameter is set via the schema minOccurs=0. Furthermore, instead of using multiple parameters you could define one Request parameter in your schema which you define as parameter for your WebMethod. The optionality is now encapsulated within the parameter and the same method is invoked for a call with or without the optional parameter(s).

I prefer defining the contract first instead of relying on automatically generated files. Once you figured out how XSD, SOAP and WSDL play in-together, you hardly want to use annotation/code-first based definitions any longer as you are more flexible the other way around.

Code-Example:

<xs:schema
    targetNamespace="http://your.namespace.com"
    xmlns:tns="http://your.namespace.com"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    elementFromDefault="qualified"
    attributeFromDefault="qualified">

...

<xs:element name="MyRequest" type="tns:MyRequestType" />
<xs:element name="MyResponse" type="tns:MyResponseType" />

<xs:complexType name"MyRequestType">
    <xs:sequence>
        <xs:element name="username" type="xs:string" minOccurs="1" maxOccurs="1" />
        <xs:element name="password" type="xs:string" minOccurs="1" maxOccurs="1" />
        <xs:element name="newParam" type="xs:string" minOccurs="0" maxOccurs="1" />
    </xs:sequence>
</xs:complexType>

...

</xs:schema>

In your WSDL file you define the message like that:

<wsdl:definitions
    xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
    xmlns:msg="http://your.namespace.com"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    xmlns:http="http://schemas.xmlsoap.org/wsdl/http/"
    xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/"
    xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
    targetNamespace="http://your.namespace.com">

    <wsdl:types>
        <xs:schema>
            <!-- either import the externalized schema -->
            <xs:import namespace="http://your.namespace.com"
                       schemaLocation="someDir/yourMessageSchema.xsd" />
        </xs:schema>
        <!-- or define the schema within the WSDL - just copy the schema here -->
        <xs:schema
            targetNamespace="http://your.namespace.com"
            xmlns:tns="http://your.namespace.com"
            xmlns:xs="http://www.w3.org/2001/XMLSchema"
            elementFromDefault="qualified"
            attributeFromDefault="qualified">
                ...
        </xs:schema>
    </wsdl:types>

    ...

    <wsdl:message name="sendSingle2Request">
        <wsdl:part name="in" element="msg:MyRequest" />
    </wsdl:message>

    <wsdl:message name="sendSingle2Response">
        <wsdl:part name="out" element="msg:MyResponse" />
    </wsdl:message>

    ...

    <wsdl:portType name="YourServiceEndpoint">
        <wsdl:operation name="sendSingle2">
            <wsdl:input message="tns:sendSingle2Request" />
            <wsdl:output message="tns:sendSingle2Response" />
        </wsdl:operation>
        ...
    </wsdl:portType>

    <wsdl:binding name="YourServiceBinding" type="YourServiceEndpoint">
        <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http" />
        <wsdl:operation name=""sendSingle2">
            <soap:operation soapAction="http://your.namespace.com/SendSingle2" style="document" />
            <wsdl:input>
                <soap:body parts="in" use="literal" />
            </wsdl:input>
            <wsdl:output>
                <soap:body parts="out" use="literal" />
            </wsdl:output>
        </wsdl:operation>
        ...
    </wsdl:binding>

    <wsdl:service name="YourService">
        <wsdl:port name="YourServicePort binding="tns:YourServiceBinding">
            <soap:address location="http://your.server:port/path/to/the/service" />
        </wsdl:port>
    </wsdl:service>
</wsdl:definitions>

The WSDL contract here defines to use style: document/literal and with the help of the schema the actual SOAP message will be document/literal wrapped which is furthermore WS-I compliant.

Your method will therefore change to public MyResponse sendSinge2(MyRequest request) where request now encapsulates username, passowrd and newParam. In case newParam was not send with the SOAP request it simply will return null, so better check if first before you use it.

If you stick to the code-first approach, you will need to define your MyRequest class first which you use as request parameter instead of those 2 or 3 values.

@XmlAccessorType(XmlAccessType.FIELD)
@XmlRootElement(name = "MyRequest", namespace="http://your.namespace.com")
public class MyRequest implements Serializable
{
   @XmlElement(name = "username", required = true)
   protected String username;
   @XmlElement(name = "password", required = true)
   protected String password;
   @XmlElement(name = "newParam", required = false)
   protected String newParam;
   ...
}

The same should be done for MyResult if you haven't done it yet. The web method may now look something like that:

@WebMethod(operationName = "sendSingle2")
@WebResult(name = "sendSingle2Response", targetNamespace = "http://your.namespace.com")
public MyResult sendSingle2(@WebParam(name = "sendSingle2Request") MyRequest request)
{
   ...
}

Again, request encapsulates the 3 parameters where you fist should check if the optional parameters are null.

HTH

like image 124
Roman Vottner Avatar answered Nov 15 '22 23:11

Roman Vottner