These last weeks I have been working in an identity project that deals with Web Services. The customer wants the Identity Solution to integrate several applications via Web Services. Therefore the agreement of a WSDL between all parts was the first issue to work with. The WSDL (Web Services Description Language) is an XML language to describe and model Web Services, using simpler words, it is a way to define what methods to implement and what the data model exchanged is (without any doubt). Finishing the introduction JAX-WS (Java Api for XML Web Services) is the API that deals with Web Services as part of the Java EE platform specification (it gives annotations, WSDL parsing and many other features to simplify the development of Web Services in the Java platform).
It is important to explain that in this case my part (the identity side) would be the client whilst the different applications would be the Web Services server side. This way the Identity Software would invoke the methods of the agreement in order to provision a new account, to change some attributes or to delete a user in the integrated application. If a WSDL is defined previously no compatibility problems are expected, no matter the server software or the implementation language. Besides, as all methods and exchange objects had been deliberately defined generic, only one client would have been needed in the identity side for all applications. In fact the agreement document version 1.0 (with the first version of the WSDL inside too) was sent to customer and to the different application teams some months ago.
But people sometimes do not understand what a WSDL is and, even worse, developers think some methods with similar functionality are more than sufficient (which is basically true but it converts all the previous work in a waste of time and the scheduled timetable in a real bullshit). Of course these little modifications are considered not important enough to even advise the other parts involved. To sum the first application to integrate had developed their way Web Services. Besides the application team used a custom and abominable Web Services framework which did not generate the artifacts, so the XML data had to be manually parsed (oh my gosh!). Another of its features was every operation was bound to a different endpoint (a different URL). Needless to say the WSDL was not automatically generated and it had also to be written manually. So you can image the situation. After some crisis times I decided to tune their WSDL and implement the same file in a modern application server, mainly my idea was testing with a confident implementation and reporting errors to them as fast as I can.
But developing a multi-endpoint web service with JAX-WS was not as easy as I expected and this is the reason for this entry. I decided to use Metro implementation which is the JAX-WS reference and open-source implementation done by Oracle (formerly Sun). Actually metro is the complete Web Services stack for glassfish (and other application servers) and covers many other components like JAX-RS (see my ScatterPlot entry for more information for RESTful Web Services), JAXB, WSIT or SAAJ inside the Java EE. There are more implementations like Apache Axis 2, Apache CXF or Codehouse XFire but I have more experience with this one and I suppose basics are the same for all of them. I spent some time to create the Web Service and I want to save this information here.
For obvious reasons I cannot present the real WSDL file so I created a very simple user CRUD service for a supposed application, here it is the basic-crud.wsdl file. This WSDL is not generic but it is easier and better for learning purposes. The definition has four endpoints with one operation inside each (Create, Read, Update and Delete). If you download the JAX-WS Metro bundle there are some sample applications inside, and one of them explicitly shows how to create a jax-ws service from a given WSDL file (fromwsdl inside samples directory). Following the instructions the wsdl must be imported in order to create the interfaces and exchange objects:
$ wsimport -keep -Xnocompile -verbose -d ../../../src/java/ basic-crud.wsdl
parsing WSDL...
generating code...
sample/user/crud/basic/CreatePort.java
sample/user/crud/basic/CreateRequest.java
sample/user/crud/basic/CreateResponse.java
sample/user/crud/basic/DeletePort.java
sample/user/crud/basic/DeleteRequest.java
sample/user/crud/basic/DeleteResponse.java
sample/user/crud/basic/ObjectFactory.java
sample/user/crud/basic/ReadPort.java
sample/user/crud/basic/ReadRequest.java
sample/user/crud/basic/ReadResponse.java
sample/user/crud/basic/SampleUser.java
sample/user/crud/basic/Status.java
sample/user/crud/basic/StatusCode.java
sample/user/crud/basic/UpdatePort.java
sample/user/crud/basic/UpdateRequest.java
sample/user/crud/basic/UpdateResponse.java
sample/user/crud/basic/WsCRUDUserService.java
sample/user/crud/basic/package-info.java
The wsimport command parses the WSDL file and generates the Java portable artifacts (keep option does not delete the Java files and they are saved inside src directory). With this command all Java classes and the Web Service Interfaces are generated for all the ports. After that the different ports need to be coded and, following the fromwsdl example, the @WebService annotation must only have the endpointInterface property set in each. In my example all the ports just manage the users into memory (a memory example repository). The code for read operation is presented below.
package sample.user.crud.basic.implementation;
import javax.jws.WebService;
import sample.user.crud.basic.ReadRequest;
import sample.user.crud.basic.ReadResponse;
import sample.user.crud.basic.SampleUser;
import sample.user.crud.basic.Status;
import sample.user.crud.basic.StatusCode;
@WebService(endpointInterface = "sample.user.crud.basic.ReadPort")
public class ReadPort {
private UserMapSingleton map = UserMapSingleton.getSingleton();
public ReadResponse read(ReadRequest request) {
ReadResponse response = new ReadResponse();
Status status = new Status();
response.setStatus(status);
try {
if (request.getId() == null) {
status.setStatus(StatusCode.ERROR);
status.setMessage("No ID passed!");
} else {
SampleUser user = map.getMap().get(request.getId());
response.setUser(user);
status.setStatus(StatusCode.OK);
if (user == null) {
status.setMessage("User '" + request.getId() + "' not found!");
}
}
return response;
} catch (Throwable t) {
status.setStatus(StatusCode.ERROR);
status.setMessage(t.getMessage());
return response;
}
}
}
When the Web Services are created from a defined WSDL all the ports have to be added to the sun-jaxws.xml file manually (as I said previously only the endpointInterface attribute annotation is present in the Java class and all the configuration is defined via the sun-jaxws file). This file contains all the endpoints with the implementation and interface part. You have to think this is exactly the opposite of what is done when the WSDL is not previously defined (everything is annotated and nothing is set in the sun-jaxws.xml). Here it is the file for the four CRUD endpoints.
<endpoints
xmlns="http://java.sun.com/xml/ns/jax-ws/ri/runtime"
version="2.0">
<endpoint
name="wsCRUDUserServicePortTypeBindingCreate"
implementation="sample.user.crud.basic.implementation.CreatePort"
interface="sample.user.crud.basic.CreatePort"
wsdl="WEB-INF/wsdl/basic-crud.wsdl"
service="{http://basic.crud.user.sample}wsCRUDUserService"
port="{http://basic.crud.user.sample}wsCRUDUserServicePortTypeBindingCreate"
url-pattern="/wsCRUDUserServicePortTypeBindingCreate" />
<endpoint
name="wsCRUDUserServicePortTypeBindingRead"
implementation="sample.user.crud.basic.implementation.ReadPort"
interface="sample.user.crud.basic.ReadPort"
wsdl="WEB-INF/wsdl/basic-crud.wsdl"
service="{http://basic.crud.user.sample}wsCRUDUserService"
port="{http://basic.crud.user.sample}wsCRUDUserServicePortTypeBindingRead"
url-pattern="/wsCRUDUserServicePortTypeBindingRead" />
<endpoint
name="wsCRUDUserServicePortTypeBindingUpdate"
implementation="sample.user.crud.basic.implementation.UpdatePort"
interface="sample.user.crud.basic.UpdatePort"
wsdl="WEB-INF/wsdl/basic-crud.wsdl"
service="{http://basic.crud.user.sample}wsCRUDUserService"
port="{http://basic.crud.user.sample}wsCRUDUserServicePortTypeBindingUpdate"
url-pattern="/wsCRUDUserServicePortTypeBindingUpdate" />
<endpoint
name="wsCRUDUserServicePortTypeBindingDelete"
implementation="sample.user.crud.basic.implementation.DeletePort"
interface="sample.user.crud.basic.DeletePort"
wsdl="WEB-INF/wsdl/basic-crud.wsdl"
service="{http://basic.crud.user.sample}wsCRUDUserService"
port="{http://basic.crud.user.sample}wsCRUDUserServicePortTypeBindingDelete"
url-pattern="/wsCRUDUserServicePortTypeBindingDelete" />
</endpoints>
Finally the web.xml is modified to add JAX-WS listener and servlet and the servlet is mapped against all the URLs specified in the previous file.
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<listener>
<listener-class>
com.sun.xml.ws.transport.http.servlet.WSServletContextListener
</listener-class>
</listener>
<servlet>
<display-name>wsCRUDUserService</display-name>
<servlet-name>wsCRUDUserService</servlet-name>
<servlet-class>com.sun.xml.ws.transport.http.servlet.WSServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>wsCRUDUserService</servlet-name>
<url-pattern>/wsCRUDUserServicePortTypeBindingCreate</url-pattern>
<url-pattern>/wsCRUDUserServicePortTypeBindingRead</url-pattern>
<url-pattern>/wsCRUDUserServicePortTypeBindingUpdate</url-pattern>
<url-pattern>/wsCRUDUserServicePortTypeBindingDelete</url-pattern>
</servlet-mapping>
<session-config>
<session-timeout>30</session-timeoutv
</session-config>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
In theory at this point everything is set and the project is ready to be used. I deployed the war file inside a standard glassfish 2.1.1 application server. If you access to any of the four mapped endpoints Metro lists all the endpoints defined in the application (see screenshot below). If the WSDL is requested to any of the four endpoints the same XML file is returned (cos four endpoints are coded from the same WSDL file).
Finally I implemented a quick JUnit test case class which checks the four endpoints work as expected (it creates, updates and deletes a user doing some reads between modify operations to test the functionality).
Today's entry explains how to create a JAX-WS Web Service with several endpoints from a pre-defined WSDL definition file using reference Metro implementation. As you have stated it is not very difficult and you can directly follow fromwsdl sample instructions. But I wanted to summarize the process here to have a quick guide for the next time (I do not usually work with Web Services this way and I spent some time to achieve it). In my defense, it seems incredibly simpler once you have written the full instructions, these things always make me think I am really stupid . The complete NetBeans project can be download from here.
God is in the detail. Cheerio!
Comments