5 Techniques for Creating Java Web Services From WSDL
WSDL is a version of XML used to better work with web severs. In this post, we'll learn how to better use it alongside the Java language.
Join the DZone community and get the full member experience.
Join For FreeIn this article, I will implement a simple Document/Literal web service, from an existing WSDL file, using three different data binding frameworks and two low-level approaches. The goal is to compare the frameworks, mainly from the ease-of-use perspective. For demo purposes, I have chosen NetBeans IDE 6.1 with its standard Web Service functionality and its Axis2 plugin. The standard Web Service support in NetBeans IDE is based on the JAX-WS stack. These are the frameworks used in this article:
- JAX-WS 2.1 (Java artifacts generated with wsimport)
- JAX-WS 2.1 (Provider API implementation)
- Axis with ADB (Axis Databinding)
- AXIOM (Axis Object Model)
- Axis with Xmlbeans
WSDL File
Initially, I wanted to use the simple AddNumbers.html file from JAX-WS2.1 examples but I encountered problems related to Java artifacts generation with both Axis ADB and Xmlbeans. After some investigation, I found the reason: ADB and Xmlbeans have problems generating Java artifacts if (1) the wsdl namespace and schema namespace are identical and (2) the <wsdl:message> representing <wsdl:fault> has the same name as schema element. Though the wsdl file is WS-I compliant, the 2 data binding technologies failed. I also tested JiBX data binding, without success. To go on I had to modify the wsdl file a little: AddNumbers.wsdl (the schema namespace is changed).
Note: Even after this change the JiBX data binding failed to generate Java classes (I used the Axis2 wsdl4j task) so I decided not to use JiBX in this article.
JAX-WS 2.1 (Java Artifacts Generated With wsimport)
For this purpose, I've created new web application project and used a Web Service from WSDL wizard in the Web Services category. The implementation was easy:
@WebService(serviceName = "AddNumbersService", portName = "AddNumbersPort", endpointInterface = "org.example.duke.AddNumbersPortType", targetNamespace = "http://duke.example.org", wsdlLocation = "WEB-INF/wsdl/AddNumbersImpl/AddNumbers.wsdl") publicclass AddNumbersImpl implements AddNumbersPortType {
publicint addNumbers(int arg0, int arg1) throws AddNumbersFault {
int result = arg0 + arg1;
if (result < 0) {
org.example.duke.xsd.AddNumbersFault fault = new org.example.duke.xsd.AddNumbersFault();
fault.setMessage("the result is negative");
fault.setFaultInfo("negative result: " + result);
thrownew AddNumbersFault("error", fault);
} else {
return result;
}
}
publicvoid oneWayInt(int arg0) {
System.out.println("JAX-WS: oneWayInt request " + arg0);
}
}
The implementation is really easy, as wsimport
, by default, generates Web Service (SEI class) methods in the Wrapping Style mode, which means for requests and responses represented by a sequence of xsd primitive types, the WS method works directly with Java primitive types. For that reason, JAX-WS uses the @javax.xml.ws.RequestWrapper and @javax.xml.ws.ResponseWrapper annotations in generated SEI classes. The most complex, but not difficult, was the implementation of AddNumbersFault
: the exception is thrown when the result is negative. NetBeans Code Completion helped me greatly in this area.
JAX-WS 2.1 (Provider API Implementation)
To implement the low-level approach supporting by JAX-WS 2.1, I used the standard Web Service from WSDL wizard, and I checked the Use Provider checkbox (new NetBeans IDE 6.1 feature). The implementation requires one to know the structure of the XML request and XML response. See that the XML DOM API is used to process the request, while the response is written directly as plain XML text. The hardest part for me was to implement the Fault correctly.
@ServiceMode(value = javax.xml.ws.Service.Mode.PAYLOAD) @WebServiceProvider(serviceName = "AddNumbersService", portName = "AddNumbersPort", targetNamespace = "http://duke.example.org", wsdlLocation = "WEB-INF/wsdl/AddNumbersImpl/AddNumbers.wsdl") publicclass AddNumbersImpl implements javax.xml.ws.Provider < javax.xml.transform.Source > {
public javax.xml.transform.Source invoke(javax.xml.transform.Source source) {
try {
DOMResult dom = new DOMResult();
Transformer trans = TransformerFactory.newInstance().newTransformer();
trans.transform(source, dom);
Node node = dom.getNode();
Node root = node.getFirstChild();
Node first = root.getFirstChild();
int number1 = Integer.decode(first.getFirstChild().getNodeValue());
Node second = first.getNextSibling();
int number2 = Integer.decode(second.getFirstChild().getNodeValue());
int result = number1 + number2;
if (result < 0) {
return getFault(result);
} else {
return getResponse(result);
}
} catch (Exception e) {
thrownew RuntimeException("Error in provider endpoint", e);
}
}
private Source getResponse(int result) {
String body = "<ns:addNumbersResponse xmlns:ns=\"http://duke.example.org/xsd\"><ns:return>" + result + "</ns:return></ns:addNumbersResponse>";
Source source = new StreamSource(new ByteArrayInputStream(body.getBytes()));
return source;
}
private Source getFault(int result) {
String body = "<nsf:Fault xmlns:nsf=\"http://schemas.xmlsoap.org/soap/envelope/\">" + "<faultcode>nsf:Server</faultcode>" + "<faultstring>error</faultstring>" + "<detail>" + "<ns:AddNumbersFault xmlns:ns=\"http://duke.example.org/xsd\">" + "<ns:faultInfo>negative result " + result + "</ns:faultInfo>" + "<ns:message>the result is negative</ns:message>" + "</ns:AddNumbersFault>" + "</detail>" + "</nsf:Fault>";
Source source = new StreamSource(new ByteArrayInputStream(body.getBytes()));
return source;
}
}
Another option is to use JAXB data binding to process the request and/or to generate the response. JAXB can be used comfortably with the Provider API. The advantage of this approach is that the implementation code works with JAXB classes generated from the schema file rather than with low-level DOM objects. The dark side is that JAXB classes (derived from the schema file) should be generated in advance. I just copied the content of the org.example.duke.xsd package from the previous example:
public javax.xml.transform.Source invoke(javax.xml.transform.Source source) {
try {
JAXBContext jc = JAXBContext.newInstance("org.example.duke.xsd");
Unmarshaller unmarshaller = jc.createUnmarshaller();
JAXBElement < AddNumbers > requestEl = (JAXBElement) unmarshaller.unmarshal(source);
AddNumbers addNum = requestEl.getValue();
int result = addNum.getArg0() + addNum.getArg1();
if (result < 0) {
return getFault(result);
} else {
AddNumbersResponse response = new AddNumbersResponse();
response.setReturn(result);
JAXBElement < AddNumbersResponse > responseEl = new ObjectFactory().createAddNumbersResponse(response);
returnnew JAXBSource(jc, responseEl);
}
} catch (JAXBException e) {
thrownew RuntimeException("Error in provider endpoint", e);
}
}
Note: The biggest advantage of the JAX-WS Provider/Dispatcher API is the ability to implement/consume web services even for the cases where wsimport
fails (e.g. RPC/Encoded WSDL). See Accessing Google Web Service using JAX-WS. Another option would be to implement the invoke method with the SOAPMessage
parameter instead of javax.xml.transform.Source
. This is more convenient than DOM but requires one to work with the entire SOAP message rather than with the SOAP payload.
Axis With ADB (Axis Databinding)
To implement a web service using Axis ADB, I installed the Axis2 plugin on the top of NetBeans IDE 6.1. To set up NetBeans IDE with Axis2 support, see the tutorial: Creating Apache Axis2 Web Services on NetBeans IDE. To create web service from a WSDL file, I used the Axis Web Service from WSDL wizard from the Web Services category. In the wizard, the WSDL file can be selected in the Name and Location Panel and ADB data binding stack in Code Generator Options panel -> Databinding Technology combo box. The Axis2 wsdl4j utility is called from the wizard, and the skeleton class for web service is generated to implement. The implementation is fairly simple, intuitive, and straightforward. The code completions helps a lot :
publicclass AddNumbersImpl implements AddNumbersServiceSkeletonInterface {
public AddNumbersResponse2 addNumbers(AddNumbers1 addNumbers0) throws AddNumbersFault {
int result = addNumbers0.getAddNumbers().getArg0() + addNumbers0.getAddNumbers().getArg1();
if (result < 0) {
AddNumbersFault fault = new AddNumbersFault();
AddNumbersFault0 faultMessage = new AddNumbersFault0();
org.example.duke.xsd.AddNumbersFault fDetail = new org.example.duke.xsd.AddNumbersFault();
fDetail.setFaultInfo("negative result " + result);
fDetail.setMessage("the result is negative");
faultMessage.setAddNumbersFault(fDetail);
fault.setFaultMessage(faultMessage);
throw fault;
} else {
AddNumbersResponse resp = new AddNumbersResponse();
resp.set_return(result);
AddNumbersResponse2 response = new AddNumbersResponse2();
response.setAddNumbersResponse(resp);
return response;
}
}
publicvoid oneWayInt(org.example.duke.xsd.OneWayInt3 oneWayInt2) {
try {
OMElement request = oneWayInt2.getOMElement(OneWayInt3.MY_QNAME, OMAbstractFactory.getOMFactory());
System.out.println("ADB:oneWayInt request: " + request);
} catch (ADBException ex) {
ex.printStackTrace();
}
}
}
Note: Axis2 doesn't use the Wrapping Style, so the parameter of the AddNumbers
method, in the skeleton class, is the AddNumbers1
object instead of 2 int parameters (I didn't find this if axis2 is enabled to set up a wrapping style).
AXIOM (AxisObject Model)
This is a low-level technique, similar to the JAX-WS Provider/Dispatcher API. Working with OM nodes and elements is more comfortable than comparing them to the DOM but less so than comparing them to ADB or JAXB. I'd compare it to working with the SAAJ API. The implementation is also quite straightforward but requires the knowledge of the AXIOM API. The skeleton class can be generated by Axis Service from WSDL wizard by selecting the AXIOM Databinding Technology.
Again, I spent most of the time dealing with the Fault implementation:
publicclass AddNumbersImpl {
privatestaticfinal String SCHEMA_NAMESPACE = "http://duke.example.org/xsd";
private OMFactory omFactory = OMAbstractFactory.getOMFactory();
public OMElement addNumbers(OMElement requestElement) throws XMLStreamException {
int value1 = Integer.valueOf(getRequestParam(requestElement, "arg0")).intValue();
int value2 = Integer.valueOf(getRequestParam(requestElement, "arg1")).intValue();
int result = value1 + value2;
if (result < 0) {
OMNode text = omFactory.createOMText("negative result");
OMNode text1 = omFactory.createOMText("the result is negative");
OMNamespace omNs = omFactory.createOMNamespace(SCHEMA_NAMESPACE, "ns");
OMElement responseChildElement = omFactory.createOMElement("faultInfo", omNs);
responseChildElement.addChild(text);
OMElement responseChildElement1 = omFactory.createOMElement("message", omNs);
responseChildElement1.addChild(text1);
OMElement faultElement = omFactory.createOMElement("AddNumbersFault", omNs);
faultElement.addChild(responseChildElement);
faultElement.addChild(responseChildElement1);
SOAPFault fault = OMAbstractFactory.getSOAP11Factory().createSOAPFault();
SOAPFaultCode code = OMAbstractFactory.getSOAP11Factory().createSOAPFaultCode();
code.setText(fault.getNamespace().getPrefix() + ":Server");
SOAPFaultReason faultstring = OMAbstractFactory.getSOAP11Factory().createSOAPFaultReason();
faultstring.setText("negative result");
SOAPFaultDetail detail = OMAbstractFactory.getSOAP11Factory().createSOAPFaultDetail();
detail.addChild(faultElement);
fault.setCode(code);
fault.setReason(faultstring);
fault.setDetail(detail);
return fault;
} else {
String resultStr = String.valueOf(result);
OMNode response = omFactory.createOMText(resultStr);
return createResponse("addNumbersResponse", "return", response);
}
}
publicvoid oneWayInt(OMElement requestElement) throws XMLStreamException {
System.out.println("AXIOM:oneWayInt request: " + requestElement);
}
private String getRequestParam(OMElement requestElement, String requestChildName) {
OMElement requestChildElement = requestElement.getFirstChildWithName(new QName(SCHEMA_NAMESPACE, requestChildName));
return requestChildElement.getText();
}
private OMElement createResponse(String responseElementName, String responseChildName, OMNode response) {
OMNamespace omNs = omFactory.createOMNamespace(SCHEMA_NAMESPACE, "ns");
OMElement responseElement = omFactory.createOMElement(responseElementName, omNs);
OMElement responseChildElement = omFactory.createOMElement(responseChildName, omNs);
responseChildElement.addChild(response);
responseElement.addChild(responseChildElement);
return responseElement;
}
}
Axis With Xmlbeans
This is the WS Stack supporting also by Axis2 technology. The skeleton class can be generated by the Axis Service from WSDL wizard by selecting the Xmlbeans Databinding Technology.
The implementation is quite straightforward and similar to Axis2 ADB. The awkwardness of this approach is the number of classes (225) generated by selecting this option. I am not an expert in Xmlbeans so this number may be reduced somehow. This is the implementation class:
publicclass AddNumbersImpl implements AddNumbersServiceSkeletonInterface {
public AddNumbersResponseDocument addNumbers(org.example.duke.xsd.AddNumbersDocument addNumbers0) throws AddNumbersFault { //System.out.println("Xmlbeans: addNumbers request: "+addNumbers0);int result = addNumbers0.getAddNumbers().getArg0() + addNumbers0.getAddNumbers().getArg1(); if (result < 0) { AddNumbersFault fault = new AddNumbersFault(); AddNumbersFaultDocument faultDoc = AddNumbersFaultDocument.Factory.newInstance(); org.example.duke.xsd.AddNumbersFault fDetail = org.example.duke.xsd.AddNumbersFault.Factory.newInstance(); fDetail.setFaultInfo("negative result "+result); fDetail.setMessage("the result is negative"); faultDoc.setAddNumbersFault(fDetail); fault.setFaultMessage(faultDoc); throw fault; } AddNumbersResponseDocument response = AddNumbersResponseDocument.Factory.newInstance(); AddNumbersResponse resp = AddNumbersResponse.Factory.newInstance(); resp.setReturn(result); response.setAddNumbersResponse(resp); return response; } publicvoid oneWayInt(org.example.duke.xsd.OneWayIntDocument oneWayInt2) { //TODO implement this method System.out.println("Xmlbeans: oneWayInt request: "+oneWayInt2); }}
Summary
I tried to compare 5 different techniques of two popular WS Stacks (JAX-WS and Axis2) to implement a simple SOAP web service. The role of all these techniques is to process the XML payload of the request and generate the response. The high-level techniques like JAX_WS, Axis with ADB, or Axis with Xmlbeans look very similar in their essence and protect users from working with low-level XML APIs and from knowing the structure of SOAP messages. Both low-level implementation techniques documented here are difficult to use and cumbersome to some extent. On the other side, these techniques can be used even in cases when high-level techniques fail.
Finally, I've created a simple JAX-WS client on my local machine for measuring the average response time. I used JDK1.5 with Tomcat 6.0 web server.
This is the client code:
publicstaticvoid main(String[] args) {
addnumbers3.AddNumbersService3 service = new addnumbers3.AddNumbersService3();
addnumbers3.AddNumbersService3PortType port = service.getAddNumbersService3SOAP11PortHttp();
((BindingProvider) port).getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, // this value depends on real location of the service wsdl"http://localhost:8084/axis2/services/AddNumbersService3?wsdl"); long sum = 0L; Random random = new Random(System.currentTimeMillis()); for (int i=0;i<10000;i++) { try { // Call Web Service Operationint arg0 = random.nextInt(100); int arg1 = random.nextInt(100); // TODO process result herelong startTime = System.currentTimeMillis(); int result = port.addNumbers(arg0, arg1); long endTime = System.currentTimeMillis(); long time = (endTime - startTime); sum+=time; } catch (AddNumbersFault_Exception ex) { System.out.println("fault = "+ex.getFaultInfo().getFaultInfo()+":"+ex.getFaultInfo().getMessage()); } } double avrg = sum/10000.0; System.out.println("Avarage response time = "+avrg); }
Finally, this is the result table considering three aspects: Ease of Implementation, Response Time, and the Number of Generated Classes:
Technique | Ease of Implementation | Response Time | Generated Classes |
---|---|---|---|
JAX-WS (with wsimport) | ***** | 0.57 ms | 9 |
JAX-WS (Provider API without JAXB) | * | 1.15 ms | 0 |
JAX-WS (Provider API with JAXB) | *** | 2.36 ms | 6 |
Axis2 with ADB | **** | 0.62 ms | 14 |
Axis2 with Xmlbeans | **** | 0.69 ms | 225 |
AXIOM | ** | 0.65 ms | 0 |
Note: the response times are relative depending on hardware configuration. The Ease of Use aspect may be considered subjective depending on how much experience you have with particular WS Stack.
Opinions expressed by DZone contributors are their own.
Comments