SOAP WS - make @WebParam optional
Asked Answered
F

2

16

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.

Fell answered 17/1, 2014 at 6:14 Comment(0)
Y
17

@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

Yippie answered 17/1, 2014 at 9:16 Comment(7)
Thank you. That's the way I want to take.Fell
@Roman Vottner Very nice explanation.Can you suggest a good place to start learning Contract first development approach in web services? about WSDL,SOAP, XSD? any books?Motor
@Octopus Often wikipedia entries (WSDL, SOAP, XSD) are good places to start. Also Google and SO provides more than enough good links on that matter. Reading the WSDL spec might help too, but its not compulsory. F.e: IBM and Oracle have helpful information tooYippie
@RomanVottner Can you please explain how to do this in rpc/literal as well?. I am currently working on project that needs to be in rpc.Polygon
Once I have an wsdl, how to generate the java service. I have tried all the tutorial found on internet but none works. I'm using JBoss eap 6.4Rakia
@Rakia what version of WSDL are you using? For version 1.2 most of the tooling should generate valid Java classes (i.e. Apache CXF, Java's wsimport). Note that the tooling support for WSDL 2.0 is rather bad. I managed to generate classes via Axis2 for one of the tutorials in the SO documentation project, which is now unfortunately gone; not sure if it is still available through archivesYippie
@RomanVottner sorry Im new on java enterprise, I have writed the wsdl by hand so I dont have idea of what versio it is :). Maby give a look at this that I posted #49051620Rakia
P
0

It all depends on your implementation class, where you are using these parameters. At endpoint interface simply add this parameter as webparam.

Make sure in your implementation class If you are using this parameter anywhere, add alternative code (Else part) to perform your operation or execution without this parameter also.

I think all parameters treat as optional until unless you validate them or use them in your implementation class as recommended.

Parker answered 17/1, 2014 at 6:53 Comment(4)
Hm, it's not that simple. This method is not even entered, as it fails before the method call.Fell
Couldn't understand you, i just explained concept wise. Feed me some more input about problem of yours.Parker
I edited the source, and added "newParam" there. I want my method (annotated @WebMethod) to be called in both cases, with both SOAP snippets I included. Now it is called only when I include "<newParam>" in the SOAP message (first XML snippet). When it is not included (second XML snippet) my method is not even called. And I'm afraid what I want to achieve is not possible, as answer to this question (#8325347) suggests.Fell
just wait a min, let me try this, although above answer seems justified but there should be a way around.Parker

© 2022 - 2024 — McMap. All rights reserved.