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://wiki.jboss.org/wiki/Wiki.jsp?page=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> vb 
        <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. Monitoring webservices requests

When processing web services requests, it is often useful to be able and observe the actual messages being passed between the client and the server. JBoss logs this information in the org.jboss.axis.transport.http.AxisServlet category. To enable web services logging, add the following debug category to the log4j.xml file:

<category name="org.jboss.axis.transport.http.AxisServlet">
    <priority value="DEBUG"/>
</category>

When enabled, all SOAP requests and responses will be logged to the server.log file. Here is a log of a call to the getAccountsOfCustomer method.

2005-05-16 17:50:43,479 DEBUG [org.jboss.axis.transport.http.AxisServlet] 
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
  xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 <soapenv:Body>
  <ns1:getAccountsOfCustomer xmlns:ns1="http://ebank.jboss.com">
   <in0>200</in0>
  </ns1:getAccountsOfCustomer>
 </soapenv:Body>
</soapenv:Envelope>
...
2005-05-16 17:50:44,240 DEBUG [org.jboss.axis.transport.http.AxisServlet] 
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 
                  xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
                  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
 <soapenv:Body>
  <ns1:getAccountsOfCustomerResponse xmlns:ns1="http://ebank.jboss.com">
   <getAccountsOfCustomerResponse>
    <accounts>5005</accounts>
    <accounts>5006</accounts>
    <accounts>5007</accounts>
    <accounts>5008</accounts>
   </getAccountsOfCustomerResponse>
  </ns1:getAccountsOfCustomerResponse>
 </soapenv:Body>
</soapenv:Envelope>