javac, etc), and is not entirely baffled by XML.
JWSDP_HOME
|
Installation directory for JWSDP, chosen when running the installer |
SOURCE_ROOT
|
root of the source code directory; wherever you unpacked the source code package |
xrpcc supplied with
the JWSDP.
JWSDP_DIR/conf/server.xml,
but remember to change it in the build configuration file as described below.
build.xml
|
generic Ant build file; this contains no definitions that are particular to this example, and should work with other Web Services builds. All the project-specific definitions are in... |
build.properties
|
definitions of class and service names, classpath, etc., appropriate to this project and site |
config.xml
|
Web Service definition, used by the xrpcc tool to
generate the stubs and skeletons (plumbing), and the Web Service
configuration file that will be read by the server-side supporting
classes.
|
web.xml
|
Web application deployment descriptor; required by the Web server to configure the application. This maps URLs onto servlet classes, and indicates the location of the Web Service configuration file. |
src/server
|
source code for the implementation of the server-side target class |
src/shared
|
source code for the interface that describes the Web Service. This is required both by the client (which uses it), and the target class (which implements it). This is the only class that is required on both sides of the link, and forms the syntactic bridge between caller and target |
src/testclient
|
a simple, stand-alone test client for the service |
build
|
generated classes, WAR files, config files, etc., will go here |
dist
|
this directory will be used to store the JAR file that contains client-side supporting plumbing (stubs). In a practical implementation you may want to distribute this to systems that will be making calls on your service (hence the name `dist'). We will have more to say about this issue later. |
build.properties to reflect your working environment.
| script-suffix |
this is used by the Ant build script to run the right version of xrpcc.
Change it to `bat' on Windows systems.
|
| username, password | manager user name and password for the Tomcat Web server, assigned by the JWSDP installer. Set these to match whatever you told the installer. If these are wrong, or you did not select manager credentials, you won't be able to use the hot deployment feature of Tomcat |
build.properties.
java.rmi.Remote, and all methods must be declared to
throw RemoteException.
Interest,
in package com.web_tomorrow.xmlrpctut. It exposes one method,
getCompoundInterest. The full source is listed below; this needs to
be in the directory SOURCE_ROOT/src/shared/com/web_tomorrow/xmlrpctut.
package com.web_tomorrow.xmlrpctut;
import java.rmi.Remote;
import java.rmi.RemoteException;
/**
Service interface for the `Interest' Web Service
(c)2002 Kevin Boone/Web-Tomorrow
*/
public interface Interest extends Remote
{
public double getCompoundInterest
(double principal, double interest, int terms)
throws RemoteException;
}
The interface is straightforward to compile, as it is plain, standard Java; there is
no XML or RPC stuff in it. So, using command-line tools, and assuming that the
current directory is SOURCE_ROOT:
javac -d build/shared/ src/shared/com/web_tomorrow/xmlrpctut/Interest.javaThis creates the compiled class in
build/shared/.... Alternatively, using
Ant, do:
ant compile-sharedwhich should have the same effect.
SOURCE_ROOT/src/server/com/web_tomorrow/xmlrpctut/InterestImpl.java.
Note that although this class is in a different directory from the interface generated
in step 4, it is in the same Java package. Some developers like to specify different
packages for the interfaces and implementations, but I don't like this. In any event,
if it is in the same package, you can't call this class `Interest' as well, even though
its source code is in a different directory to the interface.
package com.web_tomorrow.xmlrpctut;
/**
Implementation class for the Interest Web Service; must implement
the service interface
(c)2002 Kevin Boone/Web-Tomorrow
*/
public class InterestImpl implements Interest
{
public double getCompoundInterest(double principal,
double interest, int terms)
{
double total = principal;
for (int i = 0; i < terms; i++)
total = total * (1 + interest/100.0);
return total - principal;
}
}
To compile this class, be aware that it refers to the interface Interest, which
we compiled in step 4. The classpath seen by the Java compiler must allow this interface
to be found. So:
javac -d build/server/ -classpath build/shared/ src/server/com/web_tomorrow/xmlrpctut/InterestImpl.java(Please note that I have written this command on two separate lines so as not to upset your Web browser; in practise, of course, it is one long command.) This command creates the compiled class in
build/shared/....
Alternatively, do
ant compile-serverwhich also compiles the interface if necessary.
xrpcc that comes with the JWSDP. This takes as its input
a configuration file in XML. A suitable file is listed below:
<?xml version="1.0" encoding="UTF-8"?>
<configuration
xmlns="http://java.sun.com/jax-rpc-ri/xrpcc-config">
<rmi name="InterestService"
targetNamespace="http://hello.org/wsdl"
typeNamespace="http://hello.org/types">
<service name="InterestService"
packageName="com.web_tomorrow.xmlrpctut">
<interface name="com.web_tomorrow.xmlrpctut.Interest"
servantName="com.web_tomorrow.xmlrpctut.InterestImpl"/>
</service>
</rmi>
</configuration>
This file is specific to the Sun JWSDP, and this job may be done differently in different
products; what is important is that the interface definition is product-independent.
In the XML file we specify the name of the service, and the Java package and classes that will
form the interface and implementation.
config.xml, so
run xrpcc like this:
xrpcc.sh -server -d build/server/ -classpath build/shared/ config.xmlOn Windows systems, you need
xrpcc.bat rather than xrpcc.sh.
In any case, you can use Ant instead:
ant xrpcc-serverThe `-d' switch to
xrpcc tells the tool where to put its output files.
These consist of compiled classes, and configuration files. In this
case we have specified build/server, to align with the compiled implementation
classes. In the end, we will be packaging all these classes into a WAR file for deployment,
so it's convenient if they're in the same directory. xrpcc also
generates two configuration files. InterestService.wsdl is a
`Web Services description language' (WSDL) file. This is a standard format for describing
Web Services in some sort of Services registry. We won't be using this in the current
example. In any event, you would need to modify it to indicate the true URL of your
Service (as it would be seen from the Internet).
The other file, InterestService_Config.properties, is going to be used;
the supporting classes on the Tomcat Web server will read this to determine which classes
provide the implementation of the Service. Later this is going to have to be
moved to a different directory, but we'll deal with this when the time comes.
WEB-INF (directory)
web.xml (file)
InterestService_Config.properties (file)
classes (directory)
... implementation, interface, and plumbing classes ...
That is, all the compiled classes must go in subdirectories of WEB-INF/classes.
In principle, the .properties file does not need to go in WEB-INF,
as its location is specified in web.xml (see below), but it's as good a
place as any. Note that this file structure is a J2EE matter, not a Web Services matter.
If your hosting server is not a J2EE-compliant Web server, the deployment process
may be different; it may not even be in the form of WAR files. In addition, if you were
creating your services using, say, a graphical development tool, the entire packaging
and deployment process may be invisible.
web.xml file, copy the classes and config files to the
right directories, package into a WAR file.
<servlet>
<servlet-name>JAXRPCEndpoint</servlet-name>
<servlet-class>com.sun.xml.rpc.server.http.JAXRPCServlet</servlet-class>
<init-param>
<param-name>configuration.file</param-name>
<param-value>/WEB-INF/InterestService_Config.properties</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>JAXRPCEndpoint</servlet-name>
<url-pattern>/jaxrpc/*</url-pattern>
</servlet-mapping>
The servlet section defines the name of a servlet (JAXRPCEndpoint) and
a class that implements it. The name is arbitrary, and the class is provided with the
JWSDP, so we don't need to know much about it. However, the configuration.file
parameter is important: this references the configuration file that was created by
xrpcc earlier, and this in turn tells the servlet which of your classes
to invoke when a request is received from a client. The servlet-mapping section
tells Tomcat which URL will be specified by clients for this service. Again, this is
arbitrary, but must agree with the URL that the client will specify (we'll see how to
tell the client about this later).
build/WEB-INF and copy in the config
files and the classes in directories build/server and build/shared
that were created in previous steps. This is an operating-system issue, and I will assume
that you know how to copy files on your system. Alternatively, use Ant:
ant setup-web-inf
jar utility:
jar cf build/InterestService.war -C build WEB-INFAlternatively, use Ant:
ant packagewhich does all the preceding steps as well if necessary.
JWSDP_DIR/webapps and re-start the
Tomcat server. I will assume that you will want to use the Ant method, which is
much faster. First, make sure the server is running.
Change to the directory JWSDP_DIR, and execute
./bin/startup.sh(startup.bat for Windows systems). To test whether Tomcat is running, point your Web browser at
http://localhost:8080/and you should get an welcome page. If not, you need to fix the problem before continuing; everything else is predicated on a working Tomcat.
ant installThe `install' task carries out all the preceding steps as well, if necessary. Note that if you have already deployed, you will need to do
ant remove installto undeploy the old version first.
http://localhost:8080/InterestService/jaxrpcYou should get an message of the form `a Web Service is installed at this URL...'. Any kind of exception represents a failure, and must be resolved. Common problems include badly-formed
web.xml, and missing or badly-formed
Web Service configuration file.
xrpcc comes
in (again):
xrpcc.sh -classpath build/shared/ -client -d build/client config.xmlNote that we need the `classpath' option so that
xrpcc can find
the interface Interest; this is what will be used to build the
client-side stub with the right method calls available. Using Ant, we can
instead do:
ant xrpcc-clientThis operation puts the generated classes in subdirectories of
build/client;
if you look at the generated files you will see classes with `SOAP' in the names; the
significance of this will be described later.
Technical note: you will also see that one of the generated
classes is called `InterestService.class'. This is a bit unfortunate
in a way: generating
a class with the same name as the service prevents us making the interface name the same
as the service name, because we would get two different Interest classes:
one from the interface and one from xrpcc. As we want to package up the
interface with the generated classes later, this is a slight irritation. This is why
I suggested earlier that the interface name should be different from the service name.
In the JWSDP examples supplied by Sun, they get around this problem by giving the
interface class an odd name (e.g., Service name `Hello', interface name `HelloIF'). I don't
approve of this, as it isn't customary in Java to name classes according to the
infrastructure roles they play. My preference is to use a standard class name for
the interface (Interest, in this case), and suffix it with Service
to get the service name.
To make life easy for the client developer, we want to produce a single JAR file
that contains everything that the client needs to make method calls on the remote
service. So this JAR should contain the contents of build/shared,
and the contents of build/client. Note that the JAR will NOT
contain the actual implementation of the service (from build/server);
the implementation lives on the server and is invisible to clients, which only
see the interface. Using Ant, we can build the client JAR like this:
ant jar-clientwhich also does the
xrpcc step if necessary. This gives us the
file InterestService.jar in the directory dist.
This is the only thing a client needs to make method calls on the remote service,
apart from the implementation of XML-RPC itself, which will be product-specific.
import com.web_tomorrow.xmlrpctut.*;
public class InterestClient
{
/**
Invoke this test client with the service URL as an
argument
*/
public static void main(String[] args) throws Exception
{
/*
get the stub for the Web Service and set its endpoint URL
*/
Interest interest =
(Interest)(new InterestService_Impl().getInterest());
((Interest_Stub)interest)._setProperty
(javax.xml.rpc.Stub.ENDPOINT_ADDRESS_PROPERTY, args[0]);
/*
standard Java from this point on...
*/
System.out.println
("Interest on $100 at 10% per term for 10 terms is $"
+ interest.getCompoundInterest(100.0, 10.0, 10));
}
}
It is intended to be invoked with the URL of the service as a command-line argument.
If you are familiar with EJB or RMI/IIOP development, you will notice that this
code has similarities and differences with what you are used to. In these other technologies,
the manipulation and configuration of the stub, although it has to be done, is usually
wrapped up inside JNDI API calls, and is less ugly. At present, as far as I know, there
is no JNDI implementation for Web Services stubs.
Interest, Interest_Stub, etc.
However, the compiler also need access to the supporting classes that are provided
with the JWSDP; the client JAR does not contain the complete XML-RPC implementation
itself (this would make for a very large file to distribute). So:
javac -d build/testclient/ -classpath dist/InterestService.jar:[jwsdp-classes] src/testclient/InterestClient.java[jwsdp-classes] should reference the essential JAR files that come with JWSDP. In the present version of the product, the two important JARs are
JWSDP_DIR/common/lib/jaxrpc-ri.jar and
JWSDP_DIR/common/lib/jaxrpc-api.jar. In later versions of the product this may
will change, and you may need to try adding JARs in the `lib' directory until all the
`class not found' messages go away.
ant compile-test-client
build.properties).
It will also be necessary to specify the Service URL on the command line. For
this example, the Service URL is
http://localhost:8080/InterestService/jaxrpc/InterestDon't forget to change the port number in
build.properties if you did not
use the default when you installed JWSDP.
ant runNote that this does not compile first; do the compile stage separately. All being well, the output should be:
Interest on $100 at 10% per term for 10 terms is $159.37
ant remove install ant runIf you have changed the interface, then you need to recompile the client support classes as well (and you will probably have to modify the test client and recompile it):
ant remove install ant jar-client ant compile-test-client ant runIt's as simple as that. This demonstrates the power of Ant: once the source tree and build files are set up, the edit-compile-deploy-test cycle become trivial.
Interest interface, it is really making a
call on the stub that implements that interface. Inside the stub is code that
serializes the method call information into a SOAP document. SOAP is a
widely-supported method for representing the structure of an object in XML.
In this example, the SOAP that is generated looks like this:
<env:Envelope ...>
<env:Body>
<ns0:getCompoundInterest>
<double_1 xsi:type="xsd:double">100.0</double_1>
<double_2 xsi:type="xsd:double">10.0< /double_2>
<int_3 xsi:type="xsd:int">10</int_3>
</ns0:getCompoundInterest>
</env:Body>
</env:Envelope>
This document specifies that the method call is to be made on a method called
getCompoundInterest, and that there are three parameters: two doubles
and an integer. The values of these parameters are specified in text format in
the XML. Notice that we aren't specifying the object or service that the method call
is directed to; this was specified by the URL that the client used to reach the
service in the first place.
.properties file that
got deployed in the WAR. This method (we hope) completes successfully and returns
a value. The servlet then serializes this value into another SOAP document,
which looks (in this case) like this:
<env:Envelope ...>
<env:Body>
<ns0:getCompoundInterestResponse>
<result xsi:type="xsd:double">159.37424601000026</result>
</ns0:getCompoundInterestResponse>
</env:Body>
</env:Envelope>
This says that there is one return element of type double, and gives its value.
The client then receives this XML in the response it obtains from the servlet. The
stub and its supporting classes parse the XML, extract the value, and pass it back
to the client, completing the method call.
©1994-2003 Kevin Boone, all rights reserved