Simple Guide to deploying Java web services on Tomcat

Requirements

This has been tested with Java SDK 1.4.2, Apache Axis 1.1 and Tomcat 4.1.24.

Summary

In this Guide we will create and deploy a very simple Web Service. The basic steps are:
  1. Create a Java interface describing the methods we will create
  2. Convert this interface to WSDL using Java2WSDL
  3. Convert this WSDL to Java stub code using WSDL2Java
  4. Implement the Service
  5. Package and deploy the Service
  6. Test the Service using a simple client program

See the Axis User Guide for more details.

Getting started

Apart from installing the software in the Requirements section above, we need to set an environment variable $AXISCLASSPATH to the Axis libraries. E.g. for the C shell type (or put in .cshrc):
setenv AXISCLASSPATH $AXIS_LIB/servlet.jar:$AXIS_LIB/axis.jar:$AXIS_LIB/commons-discovery.jar:$AXIS_LIB/commons-logging.jar:$AXIS_LIB/jaxrpc.jar:$AXIS_LIB/saaj.jar: $AXIS_LIB/log4j-1.2.8.jar:$AXIS_LIB/xml-apis.jar:$AXIS_LIB/xercesImpl.jar:$AXIS_LIB/wsdl4j.jar:.
where $AXISLIB is the lib directory of the Axis installation. (N.B. servlet.jar is needed in only some implementations: it is copied from Tomcat's common/lib directory.)

We'll be working from a root directory which could be anywhere on your system, but I'll call it $ROOT_DIR.

Create a Java Interface

We're going to create our interface in the simplewebservice package, so create a directory called simplewebservice in $ROOT_DIR. Inside this directory create a file called SimpleWebService.java with the following content:

package simplewebservice;

public interface SimpleWebService
{
   // Method to get the length of a string
   public int getLength(String inputString);
   
   // Method to get a substring   
   public String getSubstring(String inputString, int startIndex, int endIndex);   
}

Notice that, as a Java interface (not a class), this does not contain any implementation at all, just a specification of the methods we're going to have in our Web Service.

Compile the interface from $ROOT_DIR:

javac simplewebservice.SimpleWebService.java
This should produce SimpleWebService.class inside $ROOT_DIR/simplewebservice.

Convert the interface to WSDL using Java2WSDL

WSDL (Web Service Description Language) does the same job as a Java interface: it describes the methods we're going to implement in terms of input parameters and return types. As WSDL and Java interfaces perfom much the same function it's no surprise that it's quite easy to convert between the two. To generate the WSDL for our Web Service, make sure you're in $ROOT_DIR and run:

java -cp $AXISCLASSPATH org.apache.axis.wsdl.Java2WSDL -o SimpleWebService.wsdl -l "http://hostname:port/axis/services/SimpleWebService" -n "urn:simplewebservice" -p"simplewebservice" "urn:simplewebservice" simplewebservice.SimpleWebService

(replacing hostname:port with the name of the host and the port number on which Tomcat is running. Usually the port number is 8080).

Things to note:

  • We're using the Axis libraries so we must make sure that they are on our classpath, hence the "-cp $AXISCLASSPATH" part (remember we set the $AXISCLASSPATH environment variable earlier).
  • "-o SimpleWebService.wsdl" means that the output file is going to be called SimpleWebService.wsdl
  • "-l "http://hostname:port/axis/services/SimpleWebService"" gives the location where the Web Service will eventually reside.
  • "-n "urn:simplewebservice"" gives the target namespace for the WSDL.
  • "p"simplewebservice" "urn:simplewebservice"" specifies that the package simplewebservice corresponds to the urn:simplewebservice namespace (i.e. the target namespace for the WSDL)

If all goes well, the WSDL file should now be sitting in $ROOT_DIR. If not, common problems include not setting the $AXISCLASSPATH correctly (make sure that $AXISCLASSPATH ends in ":." otherwise the current directory won't be in the classpath) and forgetting to compile SimpleWebService.java.

(Optional: edit the WSDL to give meaningful parameter names)

If you open the generated WSDL, you'll probably notice that the names of parameters have been changed to "in0", "in1", etc:

   <wsdl:message name="getSubstringRequest">
      <wsdl:part name="in0" type="xsd:string"/>
      <wsdl:part name="in1" type="xsd:int"/>
      <wsdl:part name="in2" type="xsd:int"/>
   </wsdl:message>
You don't have to do this, but it makes the WSDL clearer if you change the parameter names back to more meaningful ones:
   <wsdl:message name="getSubstringRequest">
      <wsdl:part name="inputString" type="xsd:string"/>
      <wsdl:part name="startIndex" type="xsd:int"/>
      <wsdl:part name="endIndex" type="xsd:int"/>
   </wsdl:message>
Note that you'll also have to do this in the "parameterOrder" property of each <wsdl:operation> :
   <wsdl:operation name="getLength" parameterOrder="inputString">
   ...
   <wsdl:operation name="getSubstring" parameterOrder="inputString startIndex endIndex">

Convert this WSDL to Java stub code using WSDL2Java

Now we need to create some stub code which Axis and Tomcat need to invoke our web service. All the information we need is in the WSDL so, once more, there's a tool provided to do the job for us, called WSDL2Java. The code to run (once more from $ROOT_DIR ) is:

java -cp $AXISCLASSPATH org.apache.axis.wsdl.WSDL2Java -o . -d Session -s -S true -Nurn:simplewebservice simplewebservice_ws SimpleWebService.wsdl

Notes:

  • "-o ." means "output in the current directory".
  • "-d Session -s -S true" I'm not sure what all this means wink but at least part of it means "output server-side code". Without this, WSDL2Java will just output stub code for a client application
  • "-Nurn:simplewebservice simplewebservice_ws" means that the material in the urn:simplewebservice namespace (i.e. the whole WSDL document) will be converted into code in the simplewebservice_ws package. We've chosen a different package from the simplewebservice package so that the original interface code (SimpleWebService.java) does not get overwritten.

This should create a directory called $ROOT_DIR/simplewebservice_ws containing several .java files. Now we're ready to...

Implement the Service

In the last step, we generated a load of Java stub code. The only file we need to worry about is $ROOT_DIR/simplewebservice_ws/SimpleWebServiceSoapBindingImpl.java. This class will contain the code that actually does the work of the service. The auto-generated class will contain code that will compile, but (probably) won't do what we want it to. Edit the code so that it does the intended job:

package simplewebservice_ws;

public class SimpleWebServiceSoapBindingImpl implements simplewebservice_ws.SimpleWebService
{
    public int getLength(java.lang.String inputString) throws java.rmi.RemoteException 
    {
        return inputString.length();
    }

    public java.lang.String getSubstring(java.lang.String inputString, 
        int startIndex, int endIndex) throws java.rmi.RemoteException 
    {
        return inputString.substring(startIndex, endIndex);
    }
}

Package and deploy the Service

Now we're ready to deploy the service (i.e. make it visible as a Web Service). The first thing to do is to compile all the files in the $ROOT_DIR/simplewebservice_ws directory:

javac -classpath $AXISCLASSPATH simplewebservice_ws/*.java

Now we make a JAR archive of all these classes (make sure you run this from the $ROOT_DIR directory:

jar -cf SimpleWebService.jar simplewebservice_ws/*.class

Now we've got to copy the archive to a place where Tomcat/Axis will pick it up. If $CATALINA_HOME is the Tomcat root directory, then we can execute:

cp SimpleWebService.jar $CATALINA_HOME/webapps/axis/WEB-INF/lib

The final thing to do is to tell the server that the new Web Service is present. To do this make sure that the Tomcat server is running (very important) and run this on the server (i.e. telnet or rlogin to the server):

java -cp $AXISCLASSPATH org.apache.axis.client.AdminClient $ROOT_DIR/simplewebservice_ws/deploy.wsdd

All done! You'll probably have to restart Tomcat now if it was running before:

=$CATALINA_HOME/bin/shutdown.sh=
=$CATALINA_HOME/bin/startup.sh=

Check that the service is deployed by pointing your browser at http://hostname:port/axis/services/SimpleWebService?wsdl : you should see the WSDL you generated earlier.

Creating a client for the Service

There are two ways of creating a client for the Service. One way is to create a client by constructing Call objects directly:
package client;

import org.apache.axis.client.Call;
import org.apache.axis.client.Service;

import javax.xml.namespace.QName;

public class TestClient
{
   public static void main(String [] args) {

       if (args.length < 3) {

           System.err.println("Usage: TestClient <string> <startIndex> <endIndex>");
           return;

       }
       try {

           String endpoint = 
                    "http://hostname:port/axis/services/SimpleWebService";
     
           Service  service = new Service();
           Call     call    = (Call) service.createCall();

           call.setTargetEndpointAddress( new java.net.URL(endpoint) );
           call.setOperationName(new QName("http://soapinterop.org/", "getSubstring") );

           String ret = (String)call.invoke(new Object[]{args[0], 
               new Integer(Integer.parseInt(args[1])), 
               new Integer(Integer.parseInt(args[2])) });

           System.out.println("Result = " + ret);

       } catch (Exception e) {
           System.err.println(e.toString());
       }
   }
}

This approach is a little ugly because of the way the method (getSubstring()) of the Service is invoked. The parameters have to be passed as an array of Objects to the method (which means that Integer objects, not int primitives must be passed). The return value of the Call needs to be cast to a String.

An alternative method which looks neater (although actually does much the same thing behind the scenes) is to use some of the classes that were generated by WSDL2Java in the steps above:

package client;

import simplewebservice_ws.SimpleWebServiceServiceLocator;
import simplewebservice_ws.SimpleWebServiceService;
import simplewebservice_ws.SimpleWebService;

public class SimplerTestClient {
   
    public static void main (String[] args) {
        
        if (args.length < 3) {
            System.err.println("Usage: SimplerTestClient <string> <startIndex> <endIndex>");
            return;
        }
    
        try {
            
            SimpleWebServiceService locator = new SimpleWebServiceServiceLocator();   
            SimpleWebService service = locator.getSimpleWebService();

            String result = service.getSubstring(
                args[0], 
                Integer.parseInt(args[1]),
                Integer.parseInt(args[2]) );

            System.out.println(result);
            
        } catch (Exception e) {
            e.printStackTrace();
        }        
    }   
}

This approach looks neater because the details of calling the Service are hidden; it just looks like calling any other class method. This approach could be used for any Web Service, even ones you don't write yourself. As long as you have the WSDL for the service, you can run WSDL2Java to create the stub classes, then use the classes to create a client in very few lines of code. Of course, it doesn't even matter if the Web Service in question wasn't written in Java.

-- JonBlower - 09 Mar 2004

Topic revision: r3 - 04 Aug 2004 - 10:16:00 - JonBlower
 
This site is powered by the TWiki collaboration platformCopyright &© by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding TWiki? Send feedback