In this article, I will implement a simple Document/Literal web service, from an existing WSDL file, using three different databinding 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:
Initially I wanted to use the simple AddNumbers.html [1] 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) 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 databinding technologies failed. I also tested JiBX databinding, without success. To go on I had to modify the wsdl file a little: AddNumbers.wsdl [2] (the schema namespace is changed).
Note: Even after this change the JiBX databinding failed to generate Java classes (I used the Axis2 wsdl4j task) so I decided not to use JiBX in this article.
@WebService(serviceName = "AddNumbersService", portName = "AddNumbersPort", endpointInterface = "org.example.duke.AddNumbersPortType",
targetNamespace = "http://duke.example.org", wsdlLocation = "WEB-INF/wsdl/AddNumbersImpl/AddNumbers.wsdl")
public class AddNumbersImpl implements AddNumbersPortType {
public int 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);
throw new AddNumbersFault("error", fault);
} else {
return result;
}
}
public void 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 Wrapping Style mode, which means for requests, 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 class. 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.
@ServiceMode(value = javax.xml.ws.Service.Mode.PAYLOAD)Another option is to use JAXB data binding to process the request and/or to generate the response. JAXB can be used comfortably with Provider API. Advantage of this approach is that implementation code works with JAXB classes generated from schema file rather than with low level DOM objects. The dark side is that JAXB classes (derived from schema file) should be generated in advance. I just copied the content of org.example.duke.xsd package from previous example:
@WebServiceProvider(serviceName = "AddNumbersService", portName = "AddNumbersPort",
targetNamespace = "http://duke.example.org", wsdlLocation = "WEB-INF/wsdl/AddNumbersImpl/AddNumbers.wsdl")
public class 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) {
throw new 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;
}
}
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);
return new JAXBSource(jc, responseEl);
}
} catch (JAXBException e) {
throw new RuntimeException("Error in provider endpoint", e);
}
}
Note: The biggest advantage of 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 [3]. Another option would be to implement the invoke method with SOAPMessage parameter instead of javax.xml.transform.Source. This is more convenient than DOM but requires to work with entire SOAP message rather than with SOAP payload.
public class 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;
}
}
public void 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 AddNumbers method, in skeleton class, is AddNumbers1 object instead of 2 int parameters (I didn't find if axis2 enables to set up wrapping style).
public class AddNumbersImpl {
private static final 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);
}
}
public void 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;
}
}
public class 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;
}
public void oneWayInt(org.example.duke.xsd.OneWayIntDocument oneWayInt2) {
//TODO implement this method
System.out.println("Xmlbeans: oneWayInt request: "+oneWayInt2);
}
}
public static void 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 Operation
int arg0 = random.nextInt(100);
int arg1 = random.nextInt(100);
// TODO process result here
long 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 3 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 |
Links:
[1] http://blogs.sun.com/milan/resource/AddNumbers.html
[2] http://blogs.sun.com/milan/resource/AddNumbersService.html
[3] http://weblogs.java.net/blog/jitu/archive/2006/01/accessing_googl_1.html
[4] http://www.netbeans.org/kb/61/websvc/gs-axis.html