Monday, April 15, 2013

Let us write a document style Web Service

You might be aware that there are mainly four different styles of web services we can make use of. They are as follows;


  • Document/Literal
  • Document/Literal Wrapped
  • RPC/Encoded
  • RPC/Literal
Of course the RPC/Encoded style is now deprecated. If you are interested you can read up on the different styles of web services and their pros on cons on this very comprehensive article found here.

Today we will see how to write a Document/Literal wrapped kind of a web service. The agenda of this post is as follows;

  • Write a simple web service based on Document/Literal wrapped
  • How to host the simple web service on a tomcat web container
  • A simple test client to test our service
So let us begin our journey;

  • Write a simple web service based on Document/Literal wrapped

package com.wsbindings;

import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import javax.jws.soap.SOAPBinding.ParameterStyle;
import javax.jws.soap.SOAPBinding.Style;
import javax.jws.soap.SOAPBinding.Use;

@WebService
@SOAPBinding(style = Style.DOCUMENT, use = Use.LITERAL, parameterStyle = ParameterStyle.WRAPPED)
public interface AddService {

 @WebMethod
 public int addIntegers(@WebParam(name = "intOne") int paramOne,
   @WebParam(name = "intTwo") int paramTwo);
}


So this is our basic web service. This is our base interface for our service. As you can see, we first annotate it with the @javax.jws.WebService to indicate that its a web service we are going to write. Then comes the interesting part where we define our SOAPBinding. Here we state that we want to write a DOCUMENT style web service which is LITERAL and is a WRAPPED style. One thing to note here is that all three attribute values specified within the Soap Binding annotation are the default values so you can get away without declaring them here explicitly. I have done so for the purpose of clarity of this article.

Moving on, let us see how the implementation will look like for this particular interface;


package com.wsbindings;

import javax.jws.WebService;

@WebService(endpointInterface="com.wsbindings.AddService")
public class AddServiceImpl implements AddService{

 public int addIntegers(int paramOne, int paramTwo) {
  return paramOne+paramTwo;
 }

}


Again nothing spectacular here in terms of what this service does. Just adds the two numbers passed in and send back the result of the addition. Note that here again we have to annotate the implementation class with the @WebService annotation.

Now that we have completed the initial part of writing our web service contract and the implementation, let us see how we can host this on a tomcat web container.


  • How to host the simple web service on a tomcat web container
As you know, by default tomcat does not come with a JAX-WS implementation unlike typical application servers such as JBoss, Glassfish. Hence to get it working you need to get an implementation of the JAX-WS specification. In this instance we will be using Metro. You can either copy the jar files from the download to your WEB-INF/lib directory or you can make use of Maven to do that for you, which is what i will be doing in this article. So to get the require jar files related to the Metro implementation i add the following dependency to my pom;


 <dependency>
   <groupId>com.sun.xml.ws</groupId>
   <artifactId>jaxws-rt</artifactId>
   <version>2.1.3</version>
   <exclusions>
    <exclusion>
     <groupId>com.sun.xml.stream</groupId>
     <artifactId>sjsxp</artifactId>

    </exclusion>
   </exclusions>
  </dependency>

Note that i have added one exclusion here for the sjsxp artifact since i needed a newer version than which was being drawn up from transitive dependency. Because else you will get the following exception;

 Could not initialize class javax.xml.stream.XMLStreamException: Underlying stream encoding UTF-8 and input paramter for writeStartDocument() method utf-8 do not match.

In order to overcome this issue i needed to add the following dependency to my pom;


 <dependency>
   <groupId>com.sun.xml.stream</groupId>
   <artifactId>sjsxp</artifactId>
   <version>1.0.1</version>
  </dependency>

I was able to find this solution thanks to this thread.

Moving on, we need to define a specific xml file which should go under the WEB-INF directory called sun-jaxws.xml. This XML specifies how we can access our web services and where the implmentation class is found. Lets look at the content of this file;


<?xml version="1.0" encoding="UTF-8"?>
<endpoints xmlns="http://java.sun.com/xml/ns/jax-ws/ri/runtime" version="2.0">
  <endpoint
     name="AddWS"
     implementation="com.wsbindings.AddServiceImpl"
     url-pattern="/addws"/>
</endpoints> 

Here we give the package in which our web service implementation class resides in as well as the URL pattern on how to access the particular web service. One last thing we should do is to add the following to our web.xml in order to host our web service successfully;


<listener>
  <listener-class>
   com.sun.xml.ws.transport.http.servlet.WSServletContextListener
  </listener-class>
 </listener>
 <servlet>
  <servlet-name>AddWS</servlet-name>
  <servlet-class>
   com.sun.xml.ws.transport.http.servlet.WSServlet
  </servlet-class>
 </servlet>

 <servlet-mapping>
  <servlet-name>AddWS</servlet-name>
  <url-pattern>/addws</url-pattern>
 </servlet-mapping>

Note that we have to define a context listener and a Servlet class which will handle our web service invocations. If you look at the source of the WSServletContextListner you will see it reads the sun-jaxws.xml file from the WEB-INF directory and creates class loaders accordingly for the web service context.

One thing about Document style web services is that you need to generate some code for the request and response. If you do not do this, you will get the following error with the following message;

Have you run APT to generate them?

You can generate the required classes using the wsgen tool which comes bundled up with your JDK installation. You can also use Apache-CXF to generate these classes for you. We will use the latter approach by using the apache-cxf maven plugin which is available for us. Include the following to your pom and your good to go;


 <plugin>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-codegen-plugin</artifactId>
    <version>2.0.9</version>
    <dependencies>
     <dependency>
      <groupId>org.apache.cxf</groupId>
      <artifactId>cxf-rt-frontend-jaxws</artifactId>
      <version>2.0.9</version>
     </dependency>
    </dependencies>
    <executions>
     <execution>
      <id>generate-wsdl</id>
      <phase>process-classes</phase>
      <configuration>
       <className>com.wsbindings.AddServiceImpl</className>
       <argline>-classdir ${project.build.directory}/classes</argline>

      </configuration>

      <goals>
       <goal>java2wsdl</goal>
      </goals>
     </execution>
    </executions>
   </plugin>

Here we are using the java2wsdl command to generate the required request and response objects for our web service. As you can see i have used the <argline> attribute to specify where i want my generated classes to go to. Since the normal maven compile task which is run when building the war file will look in the classes directory, i have specified our classes to be included in the same path as well so that they will be bundled along with our web service class when the war is created. You can see all possible commands you can issue by going through the parameters specified here.

My pom was indicating an error when i included my apache-cxf maven plugin as follows;

Plugin execution not covered by lifecycle configuration

 and after some research on the problem i stumbled upon a solution as stated here.Hence to overcome this issue you have to include the following snippet under the <build> tag of your pom;


 
<pluginManagement>
   <plugins>
    <!--This plugin's configuration is used to store Eclipse m2e settings 
     only. It has no influence on the Maven build itself. -->
    <plugin>
     <groupId>org.eclipse.m2e</groupId>
     <artifactId>lifecycle-mapping</artifactId>
     <version>1.0.0</version>
     <configuration>
      <lifecycleMappingMetadata>
       <pluginExecutions>
        <pluginExecution>
         <pluginExecutionFilter>
          <groupId>org.apache.cxf</groupId>
          <artifactId>cxf-codegen-plugin</artifactId>
          <version>2.0.9</version>
          <goals>
           <goal>test-compile</goal>
           <goal>compile</goal>
          </goals>
         </pluginExecutionFilter>
         <action>
          <execute />
         </action>
        </pluginExecution>
       </pluginExecutions>
      </lifecycleMappingMetadata>
     </configuration>
    </plugin>
   </plugins>
  </pluginManagement>

That should get rid of that error for you though i cannot give you an exact reason to why that warning pops up. If any of you know the exact reason, i would appreciate if you could leave a comment for the benefit of us all.

After that you can simply generate the war file and copy it to the webapps directory of tomcat. Then you will be able to access the web service on the following path;

http://localhost:8080/ws-bindings/addws

Where 8080 is the port on which i have hosted tomcat on and ws-bindings is the name of my war file.

Lastly let us see how to generate the client stubs required for our service and then write a small client to test our web service.


  • A simple test client to test our service
We will yet again use the apache-cxf maven plugin to generate the client stubs using the wsdl2java command. Note that first we need to get the wsdl from the path where our web service is hosted. It will be located at;

http://localhost:8080/ws-bindings/addws?wsdl

Then i copied the content to a separate xml file and saved it under a resources directory on the separate maven project i created to generate the client stubs. Then all you need to do is add the configuration required to generate the stubs in the pom as follows;


 
<plugin>
    <groupId>org.apache.cxf</groupId>
    <artifactId>cxf-codegen-plugin</artifactId>
    <version>2.0.9</version>
    <executions>
     <execution>
      <id>generate-sources</id>
      <phase>generate-sources</phase>
      <configuration>
     
       <wsdlOptions>
        <wsdlOption>
         <wsdl>${project.basedir}/src/main/resources/AddService.wsdl</wsdl>
        </wsdlOption>
       </wsdlOptions>
      </configuration>
      <goals>
       <goal>wsdl2java</goal>
      </goals>
     </execution>
    </executions>
   </plugin>

This will generate the required stubs for you to test your web service. Lastly let us write a client to use the generated stub to access our web service;


 
import java.net.MalformedURLException;
import java.net.URL;

import javax.xml.namespace.QName;
import javax.xml.ws.Service;

import com.wsbindings.AddService;


public class DocWrapperClient {

 public static void main(String[] args) throws MalformedURLException {
   URL wsdlLocation = new URL("http://localhost:8080/ws-bindings/addws?wsdl");  
    
         QName qName = new QName("http://wsbindings.com/", "AddServiceImplService");  

         Service service = null;  
         service = Service.create(wsdlLocation, qName);  
         
         AddService ser = service.getPort(AddService.class);
         System.out.println(ser.addIntegers(1, 1));
 }
}


That is about it guys, and i hope you found the content useful. You can check out the example by downloading the server related maven project from here and the client stub generation maven project from here.

Thank you for reading and hope you have a lovely day ahead of your. Comments and criticisms are welcome as always.