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:
- Create a Java interface describing the methods we will create
- Convert this interface to WSDL using Java2WSDL
- Convert this WSDL to Java stub code using WSDL2Java
- Implement the Service
- Package and deploy the Service
- 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
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