Chapter 5. J2EE Web Services

From the start, web services have promised genuine interoperability by transmitting XML data using platform and language-independent protocols such as SOAP over HTTP. While the early days of multiple competing standards and general developer confusion may have made this more of a dream than a reality, web services have matured and standardized enough to have been incorporated into the J2EE 1.4 specification.

Keeping with the spirit of this guide, we'll assume you have some experience with web services already. If you don't, we would recommend you do some reading in advance. A good place to start would be http://www.jboss.org/wiki/Wiki.jsp?JBossWS on the JBoss wiki, which covers web services on JBoss in more depth. We also recommend J2EE Web Services by Richard Monson-Haefel for more general coverage of J2EE web services.

5.1. Web services in JBoss

JBossWS is the JBoss module responsible for providing web services in JBoss 4.0, replacing the previous JBoss.NET package. Like its predecessor, it is also based on Apache Axis (http://ws.apache.org/axis). However, JBossWS provides the complete set of J2EE 1.4 web services technologies, including SOAP, SAAJ, JAX-RPC and JAXR.

J2EE web services provides for two types of endpoints. If you think of a web service as a platform-independent invocation layer, then the endpoint is the object you are exposing the operations of and invoking operations on. Naturally, J2EE web services support exposing EJBs as web services, but only stateless session beans can be used. That makes sense given the stateless nature of web services requests. Additionally, J2EE web services provide for JAX-RPC service endpoints, (JSEs) which are nothing more than simple Java classes. We'll only be working with EJB endpoints in this example.

5.2. Duke’s Bank as a Web Service

We'll continue working with the Duke's Bank application from Chapter 4, The Duke’s Bank Application and create a simple web service for querying accounts and balances. The AccountController session bean provides this functionality to the Duke's Bank web application. Unfortunately the application uses stateful session beans as its external interface, so we can't expose the AccountController session bean directly. Instead, we'll create a new stateless session bean, the TellerBean, which will provide a more suitable web service endpoint.

Before we start, make sure that you have built and deployed Duke's Bank according to the instructions in Chapter 4, The Duke’s Bank Application. As with that example, we'll be working from the examples/bank directory. Although TellerBean will have already been compiled when you deployed Duke's Bank, you'll need to remember to invoke the compile target to compile any changes you might make.

ant -f jboss-build.xml compile

The magic of J2EE is in the deployment descriptors. We've seen how to deploy session beans already. Deploying a session bean as a web service is as simple as adding a service-endpoint element to the session bean definition in ejb-jar.xml. The service-endpoint specifies the class that provides the interface corresponding to the methods on the session bean being exposed as a web service.

<session>
    <ejb-name>TellerBean</ejb-name>
    <service-endpoint>com.jboss.ebank.TellerEndpoint</service-endpoint>
    <ejb-class>com.jboss.ebank.TellerBean</ejb-class>
    <session-type>Stateless</session-type>
    <transaction-type>Container</transaction-type>
    <ejb-ref>
        <ejb-ref-name>ejb/account</ejb-ref-name>
        <ejb-ref-type>Session</ejb-ref-type>
        <home>com.sun.ebank.ejb.customer.CustomerHome</home>
        <remote>com.sun.ebank.ejb.customer.Customer</remote>
    </ejb-ref>
    <security-identity>
        <run-as>
            <role-name>bankCustomer</role-name>
        </run-as>
    </security-identity>
</session>

You might have noticed that we didn't declare a home or remote interface for TellerBean. If your session bean is only accessed by the web services interface, you don't need one, so we've left them out here. Instead, we've declared the TellerEndpoint class as our endpoint interface. Our web service interface exposes two operations, both of which map onto the equivalent methods on TellerBean.

public interface TellerEndpoint
    extends Remote
{
    public String[] getAccountsOfCustomer(String customerId)
        throws RemoteException;

    public BigDecimal getAccountBalance(String accountID)
        throws RemoteException; 
}

We'll generate our WSDL, the interoperable web services definition, from this interface using java2wsdl, an Axis tool which comes with the JBossWS. The wsdl target does this.

ant -f jboss-build.xml wsdl

This generates the dd/ws/wsdl/teller.wsdl file representing our service. WSDL can be very verbose, so we won't duplicate the file here. But, we will point out two important things. First, the wsdlsoap:address in the wsdl:service is deliberately bogus.

<wsdl:service name="TellerService">
    <wsdl:port name="TellerEndpoint" binding="impl:TellerEndpointSoapBinding">
        <wsdlsoap:address location="http://this.value.is.replaced.by.jboss"/>
    </wsdl:port>
</wsdl:service>

JBoss will replace the wsdlsoap:address with the actual value when it deploys the web service, so there is no need to worry about it at this point.

The other detail to note from the generated WSDL file is that the namespace for our webservice is http://ebank.jboss.com. We'll need to make sure we map the namespaceURI in our JAX-RPC mapping file.

<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>com.jboss.ebank</package-type>
        <namespaceURI>http://ebank.jboss.com</namespaceURI>
    </package-mapping>
</java-wsdl-mapping>

The last piece of the deployment descriptor puzzle is the webservices.xml file, which associates our webservice with the WSDL and mapping files we've created.

<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">
    <webservice-description>
        <webservice-description-name>TellerService</webservice-description-name>
        <wsdl-file>META-INF/wsdl/teller.wsdl</wsdl-file>
        <jaxrpc-mapping-file>META-INF/mapping.xml</jaxrpc-mapping-file>
        <port-component>
            <port-component-name>Teller</port-component-name>
            <wsdl-port>TellerEndpoint</wsdl-port>
            <service-endpoint-interface>
                com.jboss.ebank.TellerEndpoint
            </service-endpoint-interface>
            <service-impl-bean>
                <ejb-link>TellerBean</ejb-link>
            </service-impl-bean>
        </port-component>
    </webservice-description>
</webservices>

Our web service is a simple session bean, so deploying it only requires us to package up the bean and the associated deployment descriptors into an EJB JAR file. The package-ws task accomplishes this, and the deploy-ws target deploys the EJB JAR to JBoss.

ant -f jboss-build.xml package-ws
ant -f jboss-build.xml deploy-ws

Once the service is deployed you can view the WSDL (Web Service Description Language) for it by browsing to the URL http://localhost:8080/bankws-ejb/TellerService?wsdl. In this example we generate the WSDL, but it would also have been possible to write the WSDL for the service by hand and then generate a Java endpoint interface for it using wsdl2java, which is also provided with JBossWS.

5.3. Running the Web Service Client

We’ve also supplied a Java client which accesses the web service from a non-J2EE environment.

public class WSClient {
    public static void main(String[] args)
        throws Exception
    {
        URL url =
                new URL("http://localhost:8080/bankws-ejb/TellerService?wsdl");
                
        QName qname = new QName("http://ebank.jboss.com",
                                "TellerService");

        ServiceFactory factory = ServiceFactory.newInstance();
        Service        service = factory.createService(url, qname);

        TellerEndpoint endpoint = (TellerEndpoint)
            service.getPort(TellerEndpoint.class);

        String   customer = "200";
        String[] ids      = endpoint.getAccountsOfCustomer(customer);

        System.out.println("Customer: " + customer);
        for (int i=0; i<ids.length; i++) {
            System.out.println("account[" + ids[i] +  "]  " +
                               endpoint.getAccountBalance(ids[i]));
        }
    }
}

The client can be run using the run-ws target.

ant -f jboss-build.xml run-ws

The client will display the balance for each account belonging to the given customer.

     [java] Customer: 200
     [java] account[5005]  3300.00
     [java] account[5006]  2458.32
     [java] account[5007]  220.03
     [java] account[5008]  59601.35

5.4. Network Traffic Analysis

JBoss comes with a very useful network monitoring utilities to help monitoring your web service traffic. The TCP monitor tool act as a TCP tunnel for connections between the client and server. It listens on one port for client connections, forwarding client requests to the server and returning responses on the client. From this man-in-the-middle position, it will print out all the traffic in both directions, so you can use it to view HTTP headers, SOAP messages or anything else you want to pass over a TCP connection. There’s nothing specific to web services involved. The tcpmon target will launch the tool.

ant -f jboss-build.xml tcpmon 

When tcpmon starts, it will present an initial configuration window. You just need to specify a local port to listen on (we chose 7070) and the information for the host and port to forward to. The defaults are localhost and 8080 respectively, so you shouldn’t need to change them.

To route your web service traffic through the TCP monitor, you'll need to change the port number used to lookup the WSDL file in the client. You'll also need to change the port number in the WSDL file that gets returned. If you recall, JBoss overwrites the wsdlsoap:address using the correct service name, host and port. To get JBoss to use port 7070 instead, you'll need to change the WebServicePort in jboss-ws4ee.sar/META-INF/jboss-service.xml to 7070 as shown below.

<mbean code="org.jboss.webservice.AxisService"
       name="jboss.ws4ee:service=AxisService">
    <depends>jboss:service=WebService</depends>
    <attribute name="WebServiceHost">localhost</attribute>
    <attribute name="WebServiceSecurePort">8443</attribute>
    <attribute name="WebServicePort">7070</attribute>
    <attribute name="ValidateWsdlRequest">false</attribute>
</mbean>

If everything is configured correctly, you can then run the client and view the output.

TCPMon output of Web Services Call

Figure 5.1. TCPMon output of Web Services Call

You can also make changes to the request message and resend it, making TCPMon an extremely useful debugging tool as well.