Introduction
This tutorial addresses a most practical scenario which is being faced by a developer. Most of the time, We may need to expose some of our existing services as web services. This situation can be encountered in different stages of project life cycle. If It is the initial stage, then You are almost safe and You can well prepare for that. But, What will happen, this requirement comes just after half of the development has finished or the system is running in production environment.
This will be little tricky if the web services have not been taken into consideration for initial project architecture.
You may involve with different kind of project architectures and use different kind of technologies. As a developer, You are not allowed to change some architectural stuff and configurations since there may be lots of dependencies.
Most of the tutorials on the Internet explains the basic stuff of creating a web service. Some time, 'Hello world' application or sometime it may be simple calculator like that. These tutorials are good to have the basic understanding about the web services. But the real world scenario's are ten time complex than that and have to face difficulties when following those kind of tutorials.
Practical scenario
With this tutorial, I am going to explain, How We really address a real world requirement that came through your supervisor. I am going to explain the same kind of scenario, which I faced recently.
A health care organisation is running a plenty of pharmacies all around the island. They have a web application which handles all the inventories, pricing and billing, issuing pharmacy items etc. They needed to expose their pharmacy items prices through a web service so that their client application in the pharmacy can access those via the web service.
Their web application has been developed within struts2, spring and hibernated integrated environment. It has all the spring managed DAO classes and also the service classes. The application uses spring's auto wiring technique, component scanning, transaction management etc. With these kind of background, I needed to expose pharmacy item prices as a web service. That is some of the methods from our current pharmacy service are needed to be exposed to outside via a web service.
I will show you, How to achieve this kind of requirement with a minimal modification to our existing project.
Additional Libraries
I am going to implement the web service with
JAX-WS. I have used JAX-WS 2.2 for my project. You can download the required JAX-WS version from
here. This provides few tools that can be used to generate web service and it's client stuff. After downloading the required version of library, extract it some where in your local machine. I have placed it in my home folder.
ie: /home/semika/jaxws-ri-2.2
Implementing web service
I already have spring managed service classes and DAO classes for pharmacy item which are being used internally by the web application. Those are not exposed to out side. Suppose, We need to expose findAll() method which returns a list of 'PharmacyItem' of 'PharmacyService' interface as a web service.
For Your convenience, I will show you the "PharmacyServiceImpl" java class which is used to perform normal pharmacy item operations. This is a usual spring mange bean. Keep in mind, methods of this class can only be used for internal operations of our web application currently. Those are not exposed as web services.
PharmacyServiceImpl.java
/**
*
*/
package com.slauto.service.pharmacy.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.slauto.exceptions.ServiceException;
import com.slauto.model.pharmacy.PharmacyItem;
import com.slauto.persist.pharmacy.api.PharmacyItemDao;
import com.slauto.service.pharmacy.api.PharmacyItemService;
/**
* @author semika
*
*/
@Service("pharmacyItemService")
public class PharmacyItemServiceImpl implements PharmacyItemService {
@Autowired
private PharmacyItemDao pharmacyItemDao;
@Override
public List<Pharmacyitem> findAll() throws ServiceException {
return pharmacyItemDao.findAllPharmacyItems();
}
}
As You can see, I have auto wired instance of 'PharmacyItemDao' with in the implementation class. As We all know, this is a spring managed service implementation class.
Next, We will implement the web service end point class for above spring managed service bean to expose it's methods as a web service methods. For the clarity, I created a separate class as web service end point.
PharmacyItemServiceEndPoint.java
package com.slauto.service.ws;
import javax.jws.WebMethod;
import javax.jws.WebService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.slauto.exceptions.DataAccessException;
import com.slauto.model.pharmacy.PharmacyItem;
import com.slauto.service.pharmacy.api.PharmacyItemService;
/**
* @author semika
*
*/
@WebService(serviceName="pharmacyItemService")
public class PharmacyItemServiceEndPoint {
@WebMethod
public List<Pharmacyitem> findAll() throws DataAccessException {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
PharmacyItemService pharmacyItemService = (PharmacyItemService)context.getBean("pharmacyItemService");
return pharmacyItemService.findAll();
}
}
As You can see here, I am using "pharmacyItemService" bean which is used by our web application to access the relevant service method. The "pharmacyItemService" is a usual spring mange bean which is used to perform day to day pharmacy item operation. Nothing special on that. Specially note that @WebService and @WebMethod annotation which indicates this class works as a web service end point.
Here, I am getting service class instance via application context. Further, We can auto wire web service end point classes with spring by extending end point classes from "SpringBeanAutowiringSupport" provided by spring. In that case, We do not need to create application context instance as I have done above. I could not make that work, that is why, I used the above technique.
With "SpringBeanAutowiringSupport", when deploying web service (explain bellow), I encountered an exception which was hard for me to resolve that. So I choose this technique. Any way, I do not like, what I have used above :) .
Generating web service
I am using a
apt,
wsgen and
wsimport tools provided by JAX-WS to generate the portable artefacts used in JAX-WS services. The relevant ant targets for the '
build.xml' file will be as follows.
Your may need following property declared at the top of the 'build.xml' file.
Property:
<property name="tomcat.home" value="/home/semika/apache-tomcat-7.0.25" />
<property name="jaxws.home" value="/home/semika/jaxws-ri-2.2" />
<property name="build.classes.home" value="${basedir}/WEB-INF/classes" />
<property name="java.home" value="/home/semika/java/jdk1.6.0_30">
Class path:
<path id="project.class.path">
<pathelement location="${java.home}/../lib/tools.jar" />
<fileset dir="${jaxws.home}/lib">
<include name="*.jar" />
</fileset>
<pathelement location="${basedir}/WEB-INF/classes" />
<fileset dir="${basedir}/WEB-INF/lib" includes="*.jar" />
</path>
JAX-WS apt tool target:
<target name="apt" depends="javac">
<taskdef name="apt" classname="com.sun.tools.ws.ant.Apt">
<classpath refid="project.class.path" />
</taskdef>
<apt fork="true"
debug="true"
verbose="true"
destdir="${basedir}/WEB-INF/classes"
sourcedestdir="${basedir}/WEB-INF/src"
sourcepath="${basedir}/WEB-INF/src">
<classpath>
<path refid="project.class.path" />
</classpath>
<option key="r" value="${basedir}/WEB-INF" />
<source dir="${basedir}/WEB-INF/src">
<include name="**/*.java"/>
</source>
</apt>
</target>
If you want to know further about apt tool provided by JAX-WS, look into
this. When running apt target, it scans the source path (src folder) and generates the required *.class and *.java files for the classes annotated with @WebService. In this case, for 'PharmacyItemServiceEndPoint.java'. If You look into the package where 'PharmacyItemServiceEndPoint' is in, You can see, it has a new package called '
jaxws' created. Inside that package, I could see following three java files.
DataAccessExceptionBean.java
FindAll.java
FindAllResponse.java
These classes are generated by the tool and it varies based on your service implementation and dependent classes which you are involving to your web service. Actually, you don't need to much worry about these generated stuff.
Similarly, You can see the relevant *.class files under /WEB-INF/classes folder.
JAX-WS wsgen tool target:
<target name="wsgen" depends="apt">
<taskdef name="wsgen" classname="com.sun.tools.ws.ant.WsGen">
<classpath path="project.class.path"/>
</taskdef>
<wsgen
xendorsed="true"
sei="com.slauto.service.ws.PharmacyItemServiceEndPoint"
destdir="${basedir}/WEB-INF/classes"
resourcedestdir="${wsdl.dir}"
sourcedestdir="${basedir}/WEB-INF/src"
keep="true"
verbose="true"
genwsdl="true">
<classpath refid="project.class.path"/>
</wsgen>
</target>
The wsgen tool will generate the WSDL file for our end point web service class. After running this target, have a look on ${wsdl.dir} location. You can see our WSDL file has been generated. If you want to know further about wsgen tool provided by JAX-WS, look into
this.
Deploying Web service
I wanted to deploy the web service with the usual server start up. SoI had to add the following configuration into the
web.xml file.
<listener>
<listener-class>
com.sun.xml.ws.transport.http.servlet.WSServletContextListener
</listener-class>
</listener>
<servlet>
<servlet-name>pharmacyItemService</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>pharmacyItemService</servlet-name>
<url-pattern>/pharmacyItemService</url-pattern>
</servlet-mapping>
As I told you before when deploying the web service with the end point class extended from "SpringBeanAutowiringSupport", it gives an exception. That is why, I decided to get the service bean via application context. If you manage this situation, please just post it.
And also, You need to create sun-jaxws.xml under the WEB-INF folder and declare the web service end point as follows.
<?xml version="1.0" encoding="UTF-8"?>
<endpoints xmlns="http://java.sun.com/xml/ns/jax-ws/ri/runtime" version="2.0">
<endpoint
name="pharmacyItemService"
implementation="com.slauto.service.ws.PharmacyItemServiceEndPoint"
url-pattern="/pharmacyItemService"/>
</endpoints>
I am using apache tomcat 7.0.25 to deploy the web service. You will need to tell tomcat where it can find JAX-WS libraries when the tomcat is starting up. You can edit the 'catalina.properties' file located in CATALINA_HOME/conf folder. Look for the common.loader property. It will mostly look like follows.
common.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar
Modify it as follows.
common.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar,/home/semika/jaxws-ri-2.2/lib/*.jar
As You can see, I have added my JAX-WS library path at the end of comma separated list. These classes are made visible to both tomcat internal classes and to all web applications deployed in the tomcat container. Now, You can copy your .war file into tomcat's webapps folder and start the tomcat. Your web service should be deployed. To confirm your web service is deployed properly, You can check for it's WSDL. For WSDL file, You should check the URL in following format.
http://localhost:8080/<your context name>/pharmacyItemService?wsdl
If You can see your WSDL file on the browser, You are done, You have success fully deployed the web service.
Generating Web service client
Now, We have a deployed web service. Next, We will see how to write client class to access the web service with java program. You can create a simple java project and use the following build.xml file there. I have placed the complete build.xml file for client generation.
I am using wsimport tool coming with JAX-WS to generate the web service client artefacts.
<?xml version="1.0" encoding="utf-8" ?>
<project name="WS-client" default="wsimport" basedir=".">
<property name="jaxws.home" value="/home/semika/jaxws-ri-2.2" />
<property name="java.home" value="/home/semika/java/jdk1.6.0_30"/>
<path id="jaxws.classpath">
<pathelement location="${java.home}/../lib/tools.jar" />
<fileset dir="${jaxws.home}/lib">
<include name="*.jar" />
</fileset>
</path>
<target name="wsimport">
<taskdef name="wsimport" classname="com.sun.tools.ws.ant.WsImport">
<classpath path="jaxws.classpath"/>
</taskdef>
<wsimport xendorsed="true"
debug="true"
verbose="true"
keep="true"
destdir="src"
package="com.slauto.service"
wsdl="http://localhost:8080/slautomanage/pharmacyItemService?wsdl">
</wsimport>
</target>
</project>
If You want to know further about wsimport tool, you can look into
this. After running the above target, just have a look on the package where you have specified under wsimport attributes. You will see set of java files generated. You can compile and bundle these into a single
client.jar file and can be used with any of the java project which need this web service.
I have created very simple java class to fetch the pharmacy items information through the web service.
Client.java
package com.slauto.client;
import java.util.List;
import com.slauto.service.PharmacyItem;
import com.slauto.service.PharmacyItemService;
import com.slauto.service.PharmacyItemServiceEndPoint;
/**
* @author semika
*
*/
public class Client {
/**
* @param args
*/
public static void main(String[] args) {
PharmacyItemService p = new PharmacyItemService();
PharmacyItemServiceEndPoint ep = p.getPharmacyItemServiceEndPointPort();
List<PharmacyItem> pharmacyItems = ep.findAll();
for (PharmacyItem pharmacyItem : pharmacyItems) {
System.out.println(pharmacyItem.getCode());
}
}
}
It is wonderful isn't it ?. You will see list of pharmacy item codes displayed just after running this class. These pharmacy information are coming through web service deployed by a web application developed with heavily complicated environment.
That is it from this tutorial. I hope this will be helpful for you and if you pick something up from this tutorial, don't forget to put a comment.