Creating Web Services
Building an XML-RPC style web service using the J2EE 1.4 platform involves five steps:
- Design and code the web service endpoint interface.
- Implement the interface.
- Write a configuration file.
- Generate the necessary files.
- Use the
deploytool
to package the service in a WAR file and deploy it.
1. Design and Code the Service Endpoint Interface
The first step in creating a web service is to design and code its endpoint interface, in which you declare the methods that a web service remote client may invoke on the service. When developing such an interface, ensure that:
- It extends the
java.rmi.Remote
interface
- It does not have constant declarations such as
public static final
- Its methods throw the
java.rmi.RemoteException
(or one of its subclasses)
- Its method parameters and return data types are supported JAX-RPC types
To get started, create a directory of your choice. For the following example, I created an apps
directory under c:sunAppServer
and a subdirectory of apps called build
. The apps
directory contains the .java
files, and the build
directory contains the .class
, as well as other files that will be automatically generated.
The web service I will be developing for the rest of this article provides a summation service for adding two numbers. Nothing fancy, but as you will see, it will demonstrate how to develop, deploy, and use web services. Code Sample 1 shows the service endpoint interface, which is a regular Java language interface that extends the java.rmi.Remote
interface. The MathFace
interface declares one method, add
, which takes two integer values and returns an integer value representing the sum of the two integer parameters:
Code Sample 1: MathFace.java
package math;
import java.rmi.Remote; import java.rmi.RemoteException;
public interface MathFace extends Remote { public int add(int a, int b) throws RemoteException; }
|
2. Implement the Service Endpoint Interface
The next step is to implement the MathFace
interface defined in Code Sample 1, which is quite straightforward, as shown in Code Sample 2.
Code Sample 2: MathImpl.java
package math;
import java.rmi.RemoteException;
public class MathImpl implements MathFace { public int add(int a, int b) throws RemoteException { return a + b; } }
|
Now, compile the .java
files specifying the .class
files to be written to the build
directory created above. The -d
option instructs the compiler to write the output .class
files into the build
directory:
prompt> javac -d build Math*.java
3. Write a Configuration File
The next step is to define a configuration file to be passed to the wscompile
tool. Here I call the file config.xml
(and save it under the apps
directory). In this configuration file, I describe the name of the service, its namespace, the package name (math
in this case) and the name of the interface (MathFace
). Code Sample 3 shows the configuration file:
Code Sample 3: config.xml
<?xml version="1.0" encoding="UTF-8"?> <configuration xmlns="http://java.sun.com/xml/ns/jax-rpc/ri/config"> <service name="MyFirstService" targetNamespace="urn:Foo" typeNamespace="urn:Foo" packageName="math"> <interface name="math.MathFace"/> </service> </configuration>
|
This file tells wscompile
to create a WSDL file with the following information:
- The service name is
MyFirstService
.
- The WSDL namespace is
urn:Foo
.
- The classes for the service are in the
math
package under the build
directory.
- The service endpoint interface is
math.MathFace
.
4. Generate the Necessary Mapping Files
Now, use the wscompile
tool to generate the necessary files. Consider the following command executed from the apps
directory:
prompt> wscompile -define -mapping build/mapping.xml -d build -nd build -classpath build config.xml
This command, which reads the config.xml
file created earlier, creates the MyFirstService.wsdl
file and mapping.xml
in the build
directory. The command line options or flags are:
-define
: instructs the tool to read the service endpoint interface and create a WSDL file.
-mapping
: specifies the mapping file and where it should be written.
-d
and -nd
: specifies where to place generated output files and non-class output files, respectively.
Believe it or not, you have now built a web service that is ready to be packaged and deployed.
The WSDL file MyFirstService.wsdl
, generated by the wscompile
tool, is shown in Code Sample 4. This file provides an XML description (based on WSDL) of the service that clients can invoke. To understand the details of the file you need some knowledge of WSDL -- but you don't need to understand all the details, so don't worry if you have no knowledge of WSDL.
Code Sample 4: MyFirstService.wsdl
<?xml version="1.0" encoding="UTF-8"?>
<definitions name="MyFirstService" targetNamespace="urn:Foo" xmlns:tns="urn:Foo" xmlns="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"> <types/> <message name="MathFace_add"> <part name="int_1" type="xsd:int"/> <part name="int_2" type="xsd:int"/></message> <message name="MathFace_addResponse"> <part name="result" type="xsd:int"/></message> <portType name="MathFace"> <operation name="add" parameterOrder="int_1 int_2"> <input message="tns:MathFace_add"/> <output message="tns:MathFace_addResponse"/></operation></portType> <binding name="MathFaceBinding" type="tns:MathFace"> <soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="rpc"/> <operation name="add"> <soap:operation soapAction=""/> <input> <soap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" use="encoded" namespace="urn:Foo"/> </input> <output> <soap:body encodingStyle="http://schemas.xmlsoap.org/soap/encoding/" use="encoded" namespace="urn:Foo"/> </output> </operation></binding> <service name="MyFirstService"> <port name="MathFacePort" binding="tns:MathFaceBinding"> <soap:address location="REPLACE_WITH_ACTUAL_URL"/> </port> </service> </definitions>
|
The mapping file, mapping.xml
, generated by the wscompile
tool is shown in Figure 5. This file follows the JSR 109 standard for Java <-> WSDL mappings. As you can see, the structure of the JAX-RPC mapping file matches closely with the structure of a WSDL file -- note the relationship between Java packages and XML namespaces. Each service offered is represented as a service-interface-mapping
element. This element contains the mapping for the fully qualified class name of the service interface, WSDL service names, and WSDL port names. In addition, the JAX-RPC mapping file provides mappings for WSDL bindings, WSDL port types, WSDL messages, and so forth. And once again, the good news is that you needn't worry about the WSDL file (Code Sample 4) of the JAX-RPC mapping file (Code Sample 5) in order to develop, deploy, and use web services.
Code Sample 5: mapping.xml
<?xml version="1.0" encoding="UTF-8"?> <java-wsdl-mapping version="1.1" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://www.ibm.com/webservices/xsd/j2ee_jaxrpc_mapping_1_1.xsd"> <package-mapping> <package-type>math</package-type> <namespaceURI>urn:Foo</namespaceURI> </package-mapping> <package-mapping> <package-type>math</package-type> <namespaceURI>urn:Foo</namespaceURI> </package-mapping> <service-interface-mapping> <service-interface>math.MyFirstService</service-interface> <wsdl-service-name xmlns:serviceNS="urn:Foo">serviceNS:MyFirstService</wsdl-service-name> <port-mapping> <port-name>MathFacePort</port-name> <java-port-name>MathFacePort</java-port-name> </port-mapping> </service-interface-mapping> <service-endpoint-interface-mapping> <service-endpoint-interface>math.MathFace</service-endpoint-interface> <wsdl-port-type xmlns:portTypeNS="urn:Foo">portTypeNS:MathFace</wsdl-port-type> <wsdl-binding xmlns:bindingNS="urn:Foo">bindingNS:MathFaceBinding</wsdl-binding> <service-endpoint-method-mapping> <java-method-name>add</java-method-name> <wsdl-operation>add</wsdl-operation> <method-param-parts-mapping> <param-position>0</param-position> <param-type>int</param-type> <wsdl-message-mapping> <wsdl-message xmlns:wsdlMsgNS="urn:Foo">wsdlMsgNS:MathFace_add</wsdl-message> <wsdl-message-part-name>int_1</wsdl-message-part-name> <parameter-mode>IN</parameter-mode> </wsdl-message-mapping> </method-param-parts-mapping> <method-param-parts-mapping> <param-position>1</param-position> <param-type>int</param-type> <wsdl-message-mapping> <wsdl-message xmlns:wsdlMsgNS="urn:Foo">wsdlMsgNS:MathFace_add</wsdl-message> <wsdl-message-part-name>int_2</wsdl-message-part-name> <parameter-mode>IN</parameter-mode> </wsdl-message-mapping> </method-param-parts-mapping> <wsdl-return-value-mapping> <method-return-value>int</method-return-value> <wsdl-message xmlns:wsdlMsgNS="urn:Foo">wsdlMsgNS:MathFace_addResponse</wsdl-message> <wsdl-message-part-name>result</wsdl-message-part-name> </wsdl-return-value-mapping> </service-endpoint-method-mapping> </service-endpoint-interface-mapping> </java-wsdl-mapping>
|
5. Packaging and Deploying the Service
A JAX-RPC web service is really a servlet (or a web component, in J2EE terminology), and hence you can use deploytool
to package and generate all the necessary configuration files, and deploy the service. If you haven't yet used the deploytool
to package and deploy applications, start the J2EE application server (or default domain) and then follow these instructions to package and deploy the math service. For the rest of the article, I will assume that the service can be accessed using the URL http://localhost:8080/math-service/math
.
Creating Web Service Clients
Now, let's create a client that accesses the math service you have just deployed. A client invokes a web service in the same way it invokes a method locally. There are three types of web service clients:
- Static Stub: A Java class that is statically bound to a service endpoint interface. A stub, or a client proxy object, defines all the methods that the service endpoint interface defines. Therefore, the client can invoke methods of a web service directly via the stub. The advantage of this is that it is simple and easy to code. The disadvantage is that the slightest change of web service definition lead to the stub being useless... and this means the stub must be regenerated. Use the static stub technique if you know that the web service is stable and is not going to change its definition. Static stub is tied to the implementation. In other words, it is implementation-specific.
- Dynamic Proxy: Supports a service endpoint interface dynamically at runtime. Here, no stub code generation is required. A proxy is obtained at runtime and requires a service endpoint interface to be instantiated. As for invocation, it is invoked in the same way as a stub. This is useful for testing web services that may change their definitions. The dynamic proxy needs to be re-instantiated but not re-generated as is the case with stub.
- Dynamic Invocation Interface (DII): Defines
javax.xml.rpc.Call
object instance for dynamic invocation. Unlike stub and proxy, it must be configured before it can be used. A client needs to provide: operation name, parameter names, types, modes, and port type. As you can tell, much more coding is involved here. The major benefit is that since Call
is not bound to anything, there is no impact of changes on the client side (whenever the web service definition changes). The DII client is outside the scope of this article.
1. Static Stub Client
Let's develop a stand-alone client that calls the add
method of MyFirstService
. It makes the call through a stub, or a local object that acts as a client proxy to the remote service. It is called a static stub because the stub is generated before runtime by the wscompile
tool. Before developing the Java client itself, you need to write a configuration file (in XML) that describes the location of the WSDL file (MyFirstService.wsdl
). The configuration file is shown in Code Sample 6. I suggest that you create a new subdirectory under apps
call it static-stub
(where you save the .xml
and .java
files) and under that a build
subdirectory.
Code Sample 6: config-wsdl.xml
<?xml version="1.0" encoding="UTF-8"?> <configuration xmlns="http://java.sun.com/xml/ns/jax-rpc/ri/config"> <wsdl location="http://localhost:8080/math-service/math?WSDL" packageName="sstub"/> </configuration>
|
As you can see, the URL http://localhost:8080/math-service/math?WSDL
identifies the location of the WSDL file for MyFirstService
. If you try this URL, you'd see something similar to Figure 3 (assuming you have deployed the web service developed above).
Once you have written the configuration file, you're ready to generate client stubs, using the following command:
prompt> wscompile -gen:client -d build -classpath build config-wsdl.xml
This commands reads the MyFirstService.wsdl
(the location of which is specified in the config-wsdl.xml), then generates files based on the information in the WSDL file and on the command-line flags. The -gen:client
instructs wscompile
to generate the stubs, as well as other needed runtime files such as serializers and value types. The -d
flags tells the tool to write the output to the build
subdirectory.
Now, you're ready to develop the client. A sample implementation is shown in Code Sample 7. Note that the stub generated earlier is used, and a service endpoint address is used to locate the service http://localhost:8080/math-service/math
. As you can see, a stub object is created using the MathFirstService_Impl
object generated by the wscompile
tool; the endpoint address that the stub uses to access the service is set; and then the stub is cast to the MathFace service endpoint interface; finally, the add
method is invoked.
Code Sample 7: MathClient.java
package sstub;
import javax.xml.rpc.Stub;
public class MathClient {
private String endpointAddress;
public static void main(String argv[]) { try { // Invoke createProxy() to create a stub object Stub stub = createProxy();
// Set the endpoint address the stub uses to access the service stub._setProperty(javax.xml.rpc.Stub.ENDPOINT_ADDRESS_PROPERTY, "http://localhost:8080/math-service/math");
// Cast the stub to the service endpoint interface (MathFace) MathFace math = (MathFace) stub;
// Invoke the add method System.out.println(math.add(12, 24)); } catch (Exception ex) { ex.printStackTrace(); } }
private static Stub createProxy() { // Create a stub object // Note that MyFirstService_Impl, generated by wscompile, is implementation-specific return (Stub) (new MyFirstService_Impl().getMathFacePort()); } }
|
You can now compile MathClient.java
and write the output to the build
directory:
C:sunAPPSER~1appsstatic-stub> javac -classpath build -d build MathClient.java
Note: If you don't already use Ant, I'd recommend that you learn how to use it. In this article, however, and for the benefit of those who do not use it, I am not using Ant. The easiest way to get things running is to copy all lib/*.jar
and lib/endorsed/*.jar
to jdkjrelibext
; this way, you don't have to worry about the classpath for the JAX-RPC classes.
Run the client:
C:sunAPPSER~1appsstatic-stub> java -classpath build sstub.MathClient