How to Develop a J2EE-Compliant Web Service (endpoint + client)

Introduction

This Howto describes how to create a Web Service Endpoint and a J2EE client that uses this web service. The endpoint will provide a method for computing the compound interest from an initial investment, an interest rate (percent) and a time quantity.

Server side

Coding

Service Endpoint Interface

The first task is to create a Java interface (aka Service Endpoint Interface, SEI). It must extend java.rmi.Remote.

The SEI will indicate the methods that will be used throughout the web service: that is, the web service interface (in the same context that an EJB has a Local or Remote interface) or, in effect, the Business interface.

 package org.objectweb.interest;

 import java.rmi.Remote;
 import java.rmi.RemoteException;

 public interface Interest extends Remote {
     double getCompoundInterest(double principal, double interest, int terms) throws RemoteException;
     double getCompoundInterest(InterestBean ib) throws RemoteException;
 }

Implementation

Once the SEI is created, an implementation for this interface must be provided. For the sake of simplicity, this Howto will expose the web service as a JAX-RPC Endpoint (i.e., a simple class, with no EJBs).

 package org.objectweb.interest;

 import java.rmi.RemoteException;

 public class InterestImpl implements Interest {

     public double getCompoundInterest(double principal, double interest, int terms) throws RemoteException {
         double total = principal;
         for (int i = 0; i < terms; i++) {
             total = total * (1 + interest / 100.0);
         }
         return total - principal;
     }

     public double getCompoundInterest(InterestBean ib) throws RemoteException {
         // delegate to the real implementation :)
         return this.getCompoundInterest(ib.getPrincipal(), ib.getInterest(), ib.getTerms());
     }

 }

The JavaBean that will be used in the SEI must be written. This JavaBean is not an interface; it is an actual Java Bean, with default constructor, setters, and getters:

package org.objectweb.interest;

public class InterestBean {

    private double principal;
    private double interest;
    private int terms;

    public double getInterest() {return interest;}
    public void setInterest(double interest) {this.interest = interest;}

    public double getPrincipal() {return principal;}
    public void setPrincipal(double principal) {this.principal = principal;}

    public int getTerms() {return terms;}
    public void setTerms(int terms) {this.terms = terms;}
}

Server Side XML Descriptors

The difficult part is bringing together all the XML/WSDL elements.

Since this example uses a JAX-RPC endpoint, the web service must be packaged inside a web application.

WSDL Definition

First, generate or write (whichever is preferred based on the level of familiarity with WSDL) the WSDL Definitions associated with the Interest interface. This WSDL will be updated and published by JOnAS, to be usable from one or more clients.

There is a helpful Axis ant task that takes an interface as parameter and generates a WSDL based on that interface:

    <taskdef name="axis-java2wsdl"
             classname="org.apache.axis.tools.ant.wsdl.Java2WsdlAntTask">
      <classpath refid="axis.classpath" />
    </taskdef>

    <axis-java2wsdl classname="org.objectweb.interest.Interest"
                    location="http://dummy-url"
                    serviceportname="InterestPort"
                    namespace="urn:objectweb:demo:interest"
                    typeMappingVersion="1.3"
                    output="${xml.dir}/wsdl/Interest.wsdl" />
classname
Fully-qualified class name of the exported interface
location
Endpoint URL address (must be set to a dummy value: will be updated at deploy time by JOnAS)
serviceportname
wsdl:service[@name] just set here to have a visible value
namespace
targetNamespace of the WSDL Definition (all exported objects will be in this namespace: XML types, messages, portTypes, ...)
typeMappingVersion
must use "1.3" aka JAX-RPC Mapping (J2EE WS must adhere to this mapping)
output
the WSDL to be created

JAX-RPC Mapping File

The JAX-RPC mapping file is used to control the mapping from WSDL to Java artifacts and vice versa. For example, in this type of file, the JAX-RPC Runtime can be instructed to map an xml type name {urn:someNamespaceURI}xml-type-name to a Java class (for example, someNamespaceURI.XmlTypeName). However, it is not limited to type mapping. It is also possible to specify:

A simple mapping file is sufficent for this example:

 <?xml version="1.0"?>
 <java-wsdl-mapping 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"
                    version="1.1">
     <package-mapping>
         <package-type>org.objectweb.interest</package-type>
         <namespaceURI>urn:objectweb:demo:interest</namespaceURI>
     </package-mapping>
     <java-xml-type-mapping>
         <java-type>org.objectweb.interest.InterestBean</java-type>
         <root-type-qname xmlns:ns="urn:objectweb:demo:interest">ns:InterestBean</root-type-qname>
         <qname-scope>complexType</qname-scope>
     </java-xml-type-mapping>
 </java-wsdl-mapping>

Web Application XML

At this time, JOnAS must be told the name of the class that implements the actual service (InterestImpl). For a JAX-RPC endpoint, the JSR 921 (WS for J2EE 1.4 ) indicates that the implementation, fully-qualified classname goes into a servlet statement.

That servlet statement looks the same as a servlet definition:

 <?xml version="1.0" encoding="ISO-8859-1"?>
 <web-app 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://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
          version="2.4">
   <display-name>Interest WebServices packaged in WebApp Sample</display-name>
   <servlet>
     <display-name>Interest web service</display-name>
     <servlet-name>InterestServlet</servlet-name>
     <servlet-class>org.objectweb.interest.InterestImpl</servlet-class>
   </servlet>
 </web-app>

WebServices XML

The core webservice descriptor, webservices.xml, should now be placed in WEB-INF for a JAX-RPC endpoint and in META-INF for an exposed SessionBean.

This is the file in which JOnAS is told how to link a wsdl:port and its implementation bean (service-impl-bean).

Start by creating a webservice-description element, giving it a unique name (Interest Webservice). Then add the wsdl-file tag with the path (inside the webapp) to the generated WSDL. Do the same thing for jaxrpc-mapping-file.

Notice that the WSDL file MUST be located in WEB-INF/wsdl (or META-INF/wsdl for SessionBean).

Once this is completed, it is necessary to describe how the WSDL will be mapped into a JOnAS component. This is done via the port-component; it maps a wsdl-port (give its QName, {urn:objectweb:demo:interest}InterestPort, which is the port name identified in the WSDL) to the SEI (service-endpoint-interface). Then a link (servlet-link or ejb-link) must be added for the component that actually performs the job. In this case, the job is done by the "servlet" named InterestServlet.

 <?xml version="1.0"?>
 <webservices 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_web_services_1_1.xsd"
              version="1.1">
   <display-name>Interest Sample</display-name>
   <webservice-description>
     <webservice-description-name>Interest Webservice</webservice-description-name>

     <wsdl-file>WEB-INF/wsdl/Interest.wsdl</wsdl-file>
     <jaxrpc-mapping-file>WEB-INF/InterestMapping.xml</jaxrpc-mapping-file>
     <port-component>
       <port-component-name>InterestPC</port-component-name>
       <wsdl-port xmlns:ns="urn:objectweb:demo:interest">ns:InterestPort</wsdl-port>
       <service-endpoint-interface>org.objectweb.interest.Interest</service-endpoint-interface>
       <service-impl-bean>
         <servlet-link>InterestServlet</servlet-link>
       </service-impl-bean>
     </port-component>
   </webservice-description>
 </webservices>

Important: The URL where the web service will be available depends on the values sets in this file:

A URL will look like the following:

http://{hostname}:{port}/{webapp-context}/{webservice-description-name}/{port-component-name} http://{hostname}:{port}/{webapp-context}/{port-component-name}/{wsdl-port:local-part} All of the XML files are now complete.

Deployment

Packaging

Now it is time to package the webapp.

As usual, the compiled classes go into WEB-INF/classes; web.xml is located directly in WEB-INF.

News that comes from webservices.xml will also go into WEB-INF, along with the JAX-RPC mapping file. And, as mentioned above, the WSDL goes into WEB-INF/wsdl.

Package all of these elements inside a war.

Webapp content:

 interest.war
     |
     +-- WEB-INF
           |
           +-- web.xml
           +-- webservices.xml
           +-- InterestMapping.xml
           |
           +-- classes/
           |      |
           |      +-- org.objectweb.interest.Interest*
           |
           +-- wsdl/
                 |
                 +-- Interest.wsdl

Artifacts Generation

Now that a generic webapp has been created, all the server-specific files can be generated. For those familiar with EJB, there is a GenIC-like step for web services; all J2EE modules that deal with web services (in a standard way) must be prepared before the installation into JOnAS.

The tool that handles this for web services is called WsGen. WsGen is bundled with an Ant Task so that it can be easily used by developers. It is only necessary to specify 1) the srcdir attributes to tell the task where the archive(s) are located, and 2) the destdir attribute to indicate where the output files should be placed.

Note that WsGen will place a modified webapp inside ${destdir}/webapps. It also does this for other archive types (ejbjars, clients, apps)

    <wsgen srcdir="${temp.dir}"
           destdir="${dist.dir}"
      <include name="webapps/interest.war" />
    </wsgen>

Installation

Everything on the server side is now complete.

This last step is to install the newly-generated webapps into JOnAS:

 >$ jonas admin -a interest.war 

Make sure that a JOnAS instance is running before typing this command.

The WSDL for the deployed web service should be available at the following location:

http://localhost:9000/interest/Interest%20Webservice/InterestPort?JWSDL

It is a good idea to verify this.

If everything is working fine, go to the next step, which is the client programming.

Client Side

Introduction

In this section, a web-service client that uses the Interest endpoint will be created.

Getting WSDL

In a real-world client, it is the developer's responsibility to choose an appropriate web service endpoint. Once the web service has been selected, the developer must retrieve the technical description of the service, which is the WSDL.

In this example, the WSDL should be at the following location (see [#Install]]):

http://localhost:9000/interest/Interest%20Webservice/InterestPort?JWSDL

Use a Copy and Paste command to copy this URL into a browser. Save the WSDL on a hard disk. Note that this WSDL supplies the right URL in its soap:address[@location].

Coding

In this coding section one method of writing service clients will be employed and a few interfaces will be written.

Service Endpoint Interface

The service endpoint interface that will be used on the client side is exactly the same as the one that has already been written for the server side. Thus, in this particular example, simply copy the org.objectweb.interest.Interest class.

However, in a real-life client (with only the WSDL and no classes present yet), it would be necessary to either:

Important: When creating/generating SEI, the parameters for the operations must be identified. If the method uses a complex type, this type must be provided. The J2EE Container (i.e., JOnAS) will not generate it.
A simple rule to follow is: The SEI MUST be compilable, thus all dependencies must be available in the classpath.

Service Interface

The service interface can be compared to the Home/LocalHome interface of an EJB; it provides access to the SEI (that contains all the 'business' methods).

In most cases, just using the default service interface (javax.xml.rpc.Service) should be sufficent. However, a service interface (that extends javax.xml.rpc.Service) can be written with meaningful SEI accessors. In short, with the Service interface, the SEI is obtained via Service.getPort(...). However, it may be preferable to use MyService.getInterest().

Since this example is designed to be easy, a Service Interface will not be written; javax.xml.rpc.Service will be used.

WS Client

This section describes how to code the actual web-service client: the class that will make things useful with the web service.

A simple servlet will be developed that will read the parameters needed to call the Interest, get the port, and finally execute the request.

    private void processRequest(HttpServletRequest req, HttpServletResponse res) throws IOException, ServletException {

        // get the parameters
        String principal = req.getParameter("principal");
        String interest = req.getParameter("interest");
        String terms = req.getParameter("terms");

        // parse them
        double pDouble = Double.valueOf(principal).doubleValue();
        double iDouble = Double.valueOf(interest).doubleValue();
        int tInteger = Integer.valueOf(terms).intValue();

        // retrieve InitialContext
        InitialContext ictx = null;
        try {
            ictx = new InitialContext();
        } catch (NamingException e) {
            ServletException se = new ServletException("Cannot create InitialContext", e);
            throw se;
        }

        // perform the lookup on the Service instance
        Service service = null;
        String lookup = "java:comp/env/service/interest";
        try {
            service = (Service) ictx.lookup(lookup);
        } catch (NamingException ne) {
            ServletException se = new ServletException("Cannot lookup : " + lookup, ne);
            throw se;
        }

        // get the Port based on the Interface class
        Interest port = null;
        try {
            port = (Interest) service.getPort(Interest.class);
        } catch (ServiceException svce) {
            ServletException se = new ServletException("Cannot get Port from " + Interest.class.getName(), svce);
            throw se;
        }

        // perform the ws operation
        double result = port.getCompoundInterest(pDouble, iDouble, tInteger);

        // perform the ws operation with Complex Type
        InterestBean ib = new InterestBean();
        ib.setPrincipal(pDouble);
        ib.setInterest(iDouble);
        ib.setTerms(tInteger);
        double result2 = port.getCompoundInterest(ib);

        // print the result
        res.getWriter().print(header);
        res.getWriter().print("port.getCompoundInterest(" + principal + ", " + interest + ", " + terms +  ")=" + result);
        res.getWriter().print("port.getCompoundInterest(" + ib +  ")=" + result2);
        res.getWriter().print(footer);

    }

This servlet provides much useful information:

It is also a good idea to write a simple HTML page that will pass values to the servlet. However, that is not covered this Howto.

Client Side XML Descriptors

With the servlet coded, the next step is to write the XML descriptors.

WebApp Descriptors

The following servlet is the equivalent of the env-entry-ref for web services: 'service-ref'.

 <?xml version="1.0" encoding="ISO-8859-1"?>

 <web-app 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://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"
          version="2.4">

   <display-name>WebApplication using Distant WebServices</display-name>

   <servlet>
     <servlet-name>InterestClientServlet</servlet-name>
     <servlet-class>org.objectweb.interest.InterestClientServlet</servlet-class>
   </servlet>

   <servlet-mapping>
     <servlet-name>InterestClientServlet</servlet-name>
     <url-pattern>/interest.do</url-pattern>
   </servlet-mapping>

   <welcome-file-list id="WelcomeFileList">
     <welcome-file>index.html</welcome-file>
     <welcome-file>index.jsp</welcome-file>
   </welcome-file-list>


   <service-ref>
     <service-ref-name>service/interest</service-ref-name>
     <service-interface>javax.xml.rpc.Service</service-interface>
     <wsdl-file>WEB-INF/wsdl/InterestPort.wsdl</wsdl-file>

     <jaxrpc-mapping-file>WEB-INF/InterestMapping.xml</jaxrpc-mapping-file>
     <port-component-ref>
       <service-endpoint-interface>org.objectweb.interest.Interest</service-endpoint-interface>
     </port-component-ref>
   </service-ref>

 </web-app>

The servlet can be declared as usual (using servlet), along with its servlet-mapping.

service-ref must be added explicitly for the web service reference:

There is also a jonas-web.xml:

 <?xml version="1.0" encoding="UTF-8"?>

 <jonas-web-app xmlns="http://www.objectweb.org/jonas/ns"
                xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                xsi:schemaLocation="http://www.objectweb.org/jonas/ns
                http://www.objectweb.org/jonas/ns/jonas-web-app_4_1_2.xsd" >
   <jonas-service-ref>
     <service-ref-name>service/interest</service-ref-name>
     <jonas-port-component-ref>
       <service-endpoint-interface>org.objectweb.interest.Interest</service-endpoint-interface>
       <wsdl-port xmlns:ns="urn:objectweb:demo:interest">ns:InterestPort</wsdl-port>
     </jonas-port-component-ref>
   </jonas-service-ref>
 </jonas-web-app>

It is only used to tell JOnAS which wsdl:port should be returned when requesting a Port using the Class as parameter (service.getPort(Class)).

JAX-RPC Mapping File

Because such a file has already been written for the endpoint sample, it can be copied directly into the client.

Remember that, if the client is a real-world client with only WSDL, a mapping file MUST be written. Unfortunately, there is no free tool that can handle all the complexities of the mapping file.

Deployment

Packaging

The Web services and J2EE descriptors have now been completed.

The goal for this section is to build the webapp. This is all fairly standard.

Content of the webapp client:

 interest-client.war
     |
     +-- (all static stuff here : index.html, ...)
     |
     +-- WEB-INF
           |
           +-- web.xml
           +-- jonas-web.xml
           +-- InterestMapping.xml
           |
           +-- classes/
           |      |
           |      +-- org.objectweb.interest.Interest*
           |
           +-- wsdl/
                 |
                 +-- Interest.wsdl

Client Artifacts Generation

Run WsGen with the webapp to generate all the required elements (including the endpoint) for running the component inside JOnAS.

    <wsgen srcdir="${temp.dir}"
           destdir="${dist.dir}"
      <include name="webapps/interest-client.war" />

    </wsgen>

Installation

The final task is to install the client webapp inside JOnAS:

 >$ jonas admin -a interest-client.war

Make sure that a JOnAS instance is running before typing this command.

Testing

Go to: http://localhost:9000/interest-client and view the results.