Connecting Infrastructure, Connecting Research

Tomcat/Axis WebServices - Server

Please note that due to the current funding situation, the NGS no longer supports a Web Service interface to its resources, but we have kept this tutorial online in case it is of use for users of other Grids.

Web service using Tomcat/Axis (Part II - Server)

1. Introduction

In this exercise, we'll write our own web service and deploy it on the Tomcat web server with Axis addon. The first web service we are going to develop is called RomanNumbers, which provides the service of converting a decimal integer into a Roman numeral string. The source code is given for this exercise.

If Tomcat/Axis is not installed on your host, please consult Part I of this practical for instructions. Please make sure you set the following environment variables:

  • CATALINA_HOME
  • AXIS_HOME
  • ANT_HOME
  • export PATH=$PATH:$ANT_HOME/bin

We can now start Tomcat.

$ $CATALINA_HOME/bin/startup.sh ... ... INFO: Server startup in 2926 ms

To test Tomcat and Axis, open your browser and enter http://localhost:8080/axis/happyaxis.jsp. If you can see the Axis Happiness Page, then Tomcat is ready to handle web services.

2. Build our web service

To write a web service, we must follow certain rules. First, we need to write a WSDL document to expose the information about the service including the data types, messages and the port type etc. Then we need to write Java classes that implements the predefined interfaces such as Remote. And finally, we write the implementation of the service code.

Fortunately, we have the Axis toolset which can simplify the task. In previous exercises, we have used WSDL2Java tool to generate Java stub classes. And there is also a tool to generate WSDL from Java interfaces. Therefore, we can now write a Java interface first to define our web service and the methods as messages, and then use Java2WSDL to generate the WSDL document.

(i) Create our web service project

Let's create a subdirectory for the new project.

$ mkdir ~/number

And we need a new Ant build file with more targets to build our web service. You can browse the given build file

here

, and save it as

build.xml

in the ~/number directory.

(ii) Write our Java interface

Create a Java interface for our web service, let the service name be RomanNumbers, and a method convert. Let's use a namespace 'number' which is also the Java package for this project.

$ cd ~/number $ mkdir interface $ vi interface/RomanNumbers.java

and paste the following code into this file.

package number;

public interface RomanNumbers
{
  public String convert(int number);
}

Save this file, and compile it before generating WSDL document because Java2WSDL tool works on compiled .class files rather than .java source files.

$ ant compile-interface Buildfile: build.xml compile-interface: [javac] Compiling 1 source file to build BUILD SUCCESSFUL Total time: 0 seconds

You'll find the generated RomanNumbers.class in the build/number subdirectory.

(iii) Generate WSDL from the Java interface

 

$ ant gen-wsdl Buildfile: build.xml gen-wsdl: [axis-java2wsdl] Java2WSDL number.RomanNumbers BUILD SUCCESSFUL Total time: 0 seconds

If you're interested to learn about the generated WSDL, read the generated file

number.wsdl

in wsdl subdirectory.

(iv) Generate Java stubs from the WSDL

We used Java2WSDL to generate WSDL document from our Java interface, and now we must use WSDL2Java to generate the Java stubs for the remoting interface. So we use the gen-serverstub target we defined in our build file.

$ ant gen-serverstub Buildfile: build.xml gen-serverstub: [axis-wsdl2java] WSDL2Java number.wsdl BUILD SUCCESSFUL Total time: 0 seconds

Several Java class files will be generated and saved in the

src/number

subdirectory, where

number

is the namespace in the WSDL document and also the package name of the Java classes. You'll also find there're two

.wsdd

files. These are for deployment purposes as we'll see later.

(v) Write our web service implementation code

We have seen several generated Java files, and here we only care about one of them, which is named RomanNumbersSoapBindingImpl.java. This file should contain the actual implementation of the web service business logic. So let's open it in an editor and add our implementation of the service. The following code should be placed inside the convert method.

  Convertor c = new Convertor();
  return c.convert(in0);

And the Convertor.java file is here. Get this file and save it in the same directory with the stubs.

Before deploying our web service, let's compile to see whether there's any errors.

$ ant compile Buildfile: build.xml compile: [javac] Compiling 6 source files to build [javac] Note: Some input files use unchecked or unsafe operations. [javac] Note: Recompile with -Xlint:unchecked for details. BUILD SUCCESSFUL Total time: 1 second

4. Deploy our web service

We need to make a jar file of all the compiled classes before deployment. We can run the make-jar task in the Ant build file.

$ ant make-jar Buildfile: build.xml make-jar: [jar] Building jar: number.jar BUILD SUCCESSFUL Total time: 1 seconds

After the web service code is packed into a jar file, we need to copy it into the Tomcat/Axis lib directory, and then register the web service on Tomcat/Axis. This can be done with the help of Axis toolset. Again we have a target in the Ant build file for this task.

$ ant deploy Buildfile: build.xml deploy: [copy] Copying 1 file to apache-tomcat-6.0.18/webapps/axis/WEB-INF/lib [java] Processing file number/deploy.wsdd [java] Done processing BUILD SUCCESSFUL Total time: 1 seconds

Let's verify that the web service is deployed. Open your browser and enter http://localhost:8080/axis/services/RomanNumbers?wsdl, and you will see the WSDL for our service. If not, restart Tomcat server and try again.

5. Write a client for our service

Let's use a subdirectory 'client' for our client program(s). Since we have made number.jar file which contains the web service interface (stub), we can also use it as the client link interface (as in our ant target). Otherwise, we can generate the client-side stub from the WSDL at http://localhost:8080/axis/services/RomanNumbers?WSDL.

Here is the Client.java code:

import number.*;
public class Client {
  public static void main(String[] args) throws Exception{
    RomanNumbersServiceLocator locator = new RomanNumbersServiceLocator();
    RomanNumbers service = locator.getRomanNumbers();
    for (int i=0; i<10; i++){
      System.out.format("%d -> '%s'\n",i,service.convert(i));
    }
  }
}

Compile and run the client program:

$ ant compile-client $ ant run-client [java] 0 -> '' [java] 1 -> 'I' [java] 2 -> 'II' [java] 3 -> 'III' [java] 4 -> 'IV' [java] 5 -> 'V' [java] 6 -> 'VI' [java] 7 -> 'VII' [java] 8 -> 'VIII' [java] 9 -> 'IX'

6. Build a more complex web service

Next, we are going to develop a web service to query books from a database. The parameter or return data type is a complex structure rather than a primitive data type such as an integer or a string. The datasets are stored in MySQL database, but the access code is provided. To help understand WSDL better, we will write the WSDL document by hand instead of using Java2WSDL generator tool.

(1). Create the project

Create a project subdirectory 'biblio', copy over the build file from the 'number' project. Open it with an editor for modification. Comment off the targets compile-interface and gen-wsdl as we won't need them in this project. Change the package name from 'number' to 'biblio', and 'RomanNumbers' to 'Biblio'. We also need to grab MySQL JDBC driver and place it both in $CATALINA_HOME/lib for runtime use and in $AXIS_HOME/lib for compile purpose. Download it here.

(2). Write the WSDL document

While we can still generate WSDL from Java interface, this time we will see how to write a WSDL document by hand. Let's copy the file number.wsdl over, rename it as biblio.wsdl and modify it. After modification, we can validate the document against the WSDL schema using xmllint.

Here is the list of changes we are going to make:

  • Replace all cases of "number" with "biblio" for the namespace/package
  • Replace all "RomanNumbers" with "Bibio" for the portType/service name
  • Replace all "convert" with "query" for the operation name
  • Locate the part element of queryRequest message, change the name from "in0" to "queryType" and type from "xsd:int" to "xsd:string"
  • Add <wsdl:part name="queryString" type="xsd:string"/> as the second element
  • Add queryString to the parameterOrder attribute in portType operation: parameterOrder="queryType queryString"
  • Change type attribute of the part element of queryResponse message to element "impl:BookType"

We will define a query operation that accepts two parameters/messages, one is the type of query which can be "isbn","title", or "author", and another is the string to query such as the ISBN string, book title and author name.

The output will be a complex data type of Book that we define like the following:(You don't need to save this because it will be generated from WSDL)

public class BookType implements Serializable
{
	private String isbn;
	private String title;
	private String author;
	private int year;
	private int pages;
	public String getIsbn() {
		return this.isbn;
	}
	public void setIsbn(String isbn) {
		this.isbn = isbn;
	}
	//other getters and setters here
}

And this data class will be defined as a complexType of XMLSchema in the WSDL document. So add the following fragment before the first message element. The complex type should match the Java class BookType we defined earlier. And the element we referenced in the biblioResponse message is also defined here.

<wsdl:types>
 <xsd:schema>
  <xsd:complexType name="BookType">
    <xsd:sequence>
      <xsd:element name="isbn" type="xsd:string"/>
      <xsd:element name="title" type="xsd:string"/>
      <xsd:element name="author" type="xsd:string"/>
      <xsd:element name="year" type="xsd:int"/>
      <xsd:element name="pages" type="xsd:int"/>
    </xsd:sequence>
  </xsd:complexType>
  <xsd:element name="bookReturn" type="impl:BookType"/>
 </xsd:schema>
</wsdl:types>

(3). Build the web service

It is easier for us to use Ant build file for the routine tasks. Since we have written our WSDL document, we start from wsdl2java.

  • generate server-side stubs from the WSDL document
  • implement the BiblioSoapBindingImpl.java class like this:
           if (queryType.equalsIgnoreCase("isbn"))
    	{
    		try 
    		{
    		Dao d = new Dao();
    		BookType[] books = d.queryByIsbn(queryString);
    		if (books.length > 0)
    			return books[0];
    		} catch (Exception e) {
    			throw new java.rmi.RemoteException(e.getMessage());
    		}
    	}
    	return null; 
    
  • compile all source code (if src directory not created, run ant init
  • make a jar file of compiled class files
  • deploy the service in Tomcat
  • Restart Tomcat if necessary

The Dao class is the data access object that provides access to the database. The source code is given here: Dao.java.

(4). Create the database

We use MySQL to store the Biblio datasets. Create a text file dsinit.sql and paste the following statements:

DROP TABLE IF EXISTS Biblio;
CREATE TABLE Biblio (
  isbn varchar(32) NOT NULL PRIMARY KEY,
  title varchar(128) NOT NULL,
  author varchar(128) NOT NULL,
  year int NOT NULL,
  pages int NOT NULL
);
INSERT INTO Biblio VALUES('0130449687','SOA Using Java Web Services','Mark D. Hansen',2007,608);
INSERT INTO Biblio VALUES('0596001754','Java and SOAP','Robert Englander',2002,276);
INSERT INTO Biblio VALUES('0596000952','Programming Web Services with SOAP','James Snell,Doug Tidwell,Pavel Kulchenko',2001,264);
INSERT INTO Biblio VALUES('0596003994','Java Web Services in a Nutshell','Kim Topley',2003,600);
INSERT INTO Biblio VALUES('0672321815','Building Web Services with Java: Making Sense of XML, SOAP, WSDL and UDDI','Steve Graham,Simeon Simeonov,Toufic Boubez,Glen Daniels,Doug Davis,Yuichi Nakamura,Ryo Neyama',2001,450);

Use the following command to create the table with some initial data in your database:

$ mysql $USER < dsinit.sql

(5). Write your test client

Create the client program in the client subdirectory. Compile and run it using Ant build commands. The client program should accept an ISBN from the above dataset and calls the web service for the book title, authors and so on. Modify the build file for run-client target:

  <target name="run-client">
    <property name="classname" value="Client"/>
    <property name="isbn" value="0130449687"/>
    <java classname="${classname}" fork="yes">
        <classpath>
            <pathelement location="${client.dir}"/>
	    <pathelement location="${package}.jar"/>
            <path refid="axis.classpath"/>
        </classpath>
        <arg line="${isbn}"/>
    </java>
  </target>

And run it like this:

$ ant -Disbn=1234567890

(6). More work

For this web service, only one query operation is implemented. You may want to try to create an add operation to insert a new book. You can also try to query by title or author and return an array of BookType objects.