Chapter 4
Accepting multiple
parameters
What's in this chapter?
In this chapter you'll learn how to accept multiple parameters in your implementation class.
Splitting the XML element into multiple parameters
For the moment, for the concat operation, the incoming <concatRequest> is converted into a single Java object (ConcatRequest) as pass to the method as the single argument (see below). But wouldn't it be nicer if each child element such as <s1> and <s2> is individually converted into Java objects to be passed as individual arguments? This would make it a little bit easier to write the concat() method as you wouldn't need to call getS1() and getS2() anymore:
To tell Apache CXF to do that, you need to make two changes to the WSDL file:
<foo:concatRequest xmlns:foo="http://ttdev.com/ss">
<s1>abc</s1>
<s2>123</s2>
</foo:concatRequest>
SOAP message
ConcatResponse concat(ConcatRequest r) { } ...
The whole XML element is converted into a single Java object.
<foo:concatRequest xmlns:foo="http://ttdev.com/ss">
<s1>abc</s1>
<s2>123</s2>
</foo:concatRequest>
SOAP message
String concat(String s1, String s2) { ...
}
Each child element is converted into a Java object.
Similarly, for the output message, you hope to have Apache CXF wrap the return value as a child element:
To do that, in the WSDL file, the element name of the output message must be the name of the operation with the word "Response" appended and it must be a sequence (containing a single child element):
<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions ...>
<wsdl:types>
<xsd:schema ...>
<xsd:element name="concatRequest concat">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="s1" type="xsd:string" />
<xsd:element name="s2" type="xsd:string" />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="concatResponse" type="xsd:string" />
</xsd:schema>
</wsdl:types>
<wsdl:message name="concatRequest">
<wsdl:part name="parameters" element="tns:concatRequest concat" />
</wsdl:message>
<wsdl:message name="concatResponse">
<wsdl:part name="parameters" element="tns:concatResponse" />
</wsdl:message>
<wsdl:portType name="SimpleService">
<wsdl:operation name="concat">
<wsdl:input message="tns:concatRequest" />
<wsdl:output message="tns:concatResponse" />
</wsdl:operation>
</wsdl:portType>
</wsdl:definitions>...
Make sure the element name of that single part in the input message is the same as that of the operation.
The element must be a sequence, which is indeed the case here.
<foo:concatResponse xmlns:foo="http://ttdev.com/ss">
<r>abc123</r>
</foo:concatResponse>
SOAP message
String concat(String s1, String s2) { ...
} The return value is
converted into a child element.
This style of parameter handling is called wrapped style or wrapper style. In contrast, passing the whole XML element as the single parameter is called the bare style.
Note that this service described by this WSDL file is still a 100% document style service. The clients can still call it the same way (except that <concatRequest>
is changed to <concat>). The difference is how the Apache CXF runtime calls your implementation and how it handles your return value. There is no difference seen by the client.
To implement this idea, modify the SimpleService.wsdl file (in the src/main/resources folder):
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
<xsd:element name="s1" type="xsd:string" />
<xsd:element name="s2" type="xsd:string" />
</xsd:sequence>
</xsd:complexType>
<?xml version="1.0" encoding="UTF-8"?>
<wsdl:definitions ...>
<xsd:element name="s1" type="xsd:string" />
<xsd:element name="s2" type="xsd:string" />
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="concatResponse" type="xsd:string" >
<xsd:complexType>
<xsd:sequence>
<xsd:element name="r" type="xsd:string" />
</xsd:sequence>
<wsdl:part name="parameters" element="tns:concat" />
</wsdl:message>
<wsdl:message name="concatResponse">
<wsdl:part name="parameters" element="tns:concatResponse" />
</wsdl:message>
<wsdl:portType name="SimpleService">
<wsdl:operation name="concat">
<wsdl:input message="tns:concatRequest" />
<wsdl:output message="tns:concatResponse" />
</wsdl:operation>
</wsdl:portType>
...
</wsdl:definitions>
The element name must be
"concat" + "Response", which happens to be the case already.
It must not be a simple type such as string. It must be a sequence.
The sequence must contain a single element.
The element name (<r>
here) is unimportant.
</xsd:element>
<xsd:element name="concatResponse">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="r" type="xsd:string">
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>
</wsdl:types>
<wsdl:message name="concatRequest">
<wsdl:part element="tns:concat" name="parameters" />
</wsdl:message>
<wsdl:message name="concatResponse">
<wsdl:part element="tns:concatResponse" name="parameters" />
</wsdl:message>
<wsdl:portType name="WrappedService">
<wsdl:operation name="concat">
<wsdl:input message="tns:concatRequest" />
<wsdl:output message="tns:concatResponse" />
</wsdl:operation>
</wsdl:portType>
<wsdl:binding name="WrappedServiceSOAP" type="tns:WrappedService">
<soap:binding style="document"
transport="http://schemas.xmlsoap.org/soap/http" />
<wsdl:operation name="concat">
<soap:operation soapAction="http://ttdev.com/ss/NewOperation" />
<wsdl:input>
<soap:body use="literal" />
</wsdl:input>
<wsdl:output>
<soap:body use="literal" />
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<wsdl:service name="WrappedService">
<wsdl:port binding="tns:WrappedServiceSOAP" name="p1">
<soap:address location="http://localhost:8080/ss/p1" />
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
Delete the whole com.ttdev.ss package and then run the CodeGenerator program again. Note the SEI generated (the WrappedService interface):
To implement the service, create a WrappedServiceImpl class:
@WebService(endpointInterface="com.ttdev.ss.WrappedService") public class WrappedServiceImpl implements WrappedService {
@Override
public String concat(String s1, String s2) { return s1 + s2;
} }
To create a client, copy the SimpleClient project and paste it as WrappedClient.
Copy the SimpleService.wsdl from the WrappedService project into the WrappedClient project (in the src/main/resources folder). Delete the whole com.ttdev.ss package and then run the CodeGenerator program again. Modify the client code in WrappedService_P1_Client:
public final class WrappedService_P1_Client {
...public static void main(String args[]) throws Exception { ...
WrappedService_Service ss = new WrappedService_Service(wsdlURL, SERVICE_NAME);
WrappedService port = ss.getP1();
{
System.out.println("Invoking concat...");
java.lang.String _concat_s1 = "abc";
java.lang.String _concat_s2 = "123";
java.lang.String _concat__return = port.concat(_concat_s1, _concat_s2);
System.out.println("concat.result=" + _concat__return);
}System.exit(0);
} }
Note that the concat() method in the service stub is now accepting two Strings, not a complex data structure.
Finally, run the service and then run the client. The client should print the
"abc123" successfully.
@WebService(targetNamespace = "http://ttdev.com/ss", name = "WrappedService")
@XmlSeeAlso( { ObjectFactory.class }) public interface WrappedService {
@WebResult(name = "r", targetNamespace = "")
@RequestWrapper(
localName = "concat",
targetNamespace = "http://ttdev.com/ss", className = "com.ttdev.ss.Concat")
@ResponseWrapper(
localName = "concatResponse",
targetNamespace = "http://ttdev.com/ss", className = "com.ttdev.ss.ConcatResponse")
@WebMethod(action = "http://ttdev.com/ss/NewOperation") public java.lang.String concat(
@WebParam(name = "s1", targetNamespace = "") java.lang.String s1,
@WebParam(name = "s2", targetNamespace = "") java.lang.String s2);
}
Now the arguments are Strings, not a complex data structure.
It tells CXF that when a <concat>
element is received, unwrap it to get its child elements and pass them to this method as individual arguments.
Similar to request handling, it tells the CXF runtime to wrap the return value into a <concatResponse>
element.
Using the wrapped style in Axis2
To do it in Axis2, copy the Axis2SimpleService and paste it as Axis2WrappedService, copy the Axis2SimpleClient and paste it as Axis2WrappedClient. Copy the SimpleService.wsdl file from the WrappedService project into both of these two new projects.
Next, modify the CodeGenerator class in the Axis2WrappedService project to enable unwrapping:
public static void main(String[] args) throws Exception { WSDL2Code.main(new String[] {
"-ss",
"-uri", "src/main/resources/SimpleService.wsdl" });
System.out.println("Done!");
} }
Do the same thing in the Axis2WrappedClient project:
public class CodeGenerator {
public static void main(String[] args) throws Exception { WSDL2Code.main(new String[] {
"-uw",
"-S", "src/main/java",
"-R", "src/main/resources/META-INF",
"-ns2p", "http://ttdev.com/ss=com.ttdev.ss",
"-uri", "src/main/resources/SimpleService.wsdl" });
System.out.println("Done!");
} }
Delete the com.ttdev.ss package and the src/main/resources/META-INF folder in both projects. Then run CodeGenerator in both projects. Fill in the code in the SimpleServiceSkeleton class:
public class SimpleServiceSkeleton {
public java.lang.String concat(java.lang.String s1, java.lang.String s2) { return s1 + s2;
} }
Note that the concat() method is now taking two Strings and returning a String.
Run the project as Maven package. It will still create a SimpleService-0.0.1-SNAPSHOT.jar file in the target folder because the pom.xml file still uses SimpleService as the artifact ID:
<project ...>
Then, in the client project, create a WrappedClient class:
public class WrappedClient {
public static void main(String[] args) throws RemoteException { WrappedServiceStub service = new WrappedServiceStub(
"http://localhost:8080/axis2/services/WrappedService");
System.out.println(service.concat("abc", "123"));
} }
Run it and it should print "abc123" successfully.
Interoperability
The wrapped style is a good idea. It is the only kind of web service supported by the .NET framework. Obviously CXF and Axis2 have also implemented this style. The good news is, from the viewpoint of the caller, it is just a document+literal style service. So if the caller doesn't understand the wrapped convention, it can still access it as a regular document style service.
Summary
The wrapped parameter style means that the web service runtime should extract the child XML elements in the input message and pass them as individual arguments to your method. It does the opposite when it receives the return value: wrap it as a child XML element in the output message.
To allow the wrapped style, the XML element in the input message should be a sequence and should have the same name as the operation. For the XML element in the output message, it should be a sequence (containing one element only) and should have the same name as the operation with the word
"Response" appended.
The code generation tool in CXF will recognize this pattern automatically and uses the wrapped style. To enable the wrapped style in Axis2, you need to specify an option to the code generation tool.
The clients understanding the wrapped style can also call the service using multiple parameters. For those not understanding it, they can still call it as a regular document style service.
To ensure interoperability with .NET, you should use the wrapped style.