Java Development News:

Part 4 - Web Services and J2EE

By Zdenek Svoboda

01 Feb 2002 | TheServerSide.com

Welcome to the fourth installment in our series of articles explaining how to use and consume Web services. In previous articles we've learned how to develop, deploy, and use simple Web services. We also looked at some advanced Web services concepts including stateful Web services, remote references, and Web services security. In this article we'll begin to explore how Web services work within the J2EE environment.


Understanding Web Services & J2EE Integration Basics

Today, many applications implement third-tier business logic as standard J2EE components. Exposing these components as SOAP Web services makes them almost universally accessible - and provides a simple mechanism for integrating these components. The modular J2EE architecture makes this process relatively easy.

In this article we'll show how to expose J2EE components as Web services and how to use the Java Message Service (JMS) to send SOAP messages reliably. We will mainly speak about Enterprise Java Beans (EJBs), because they are the most widely used J2EE components for business logic implementation, but all demonstrated techniques are also applicable to other J2EE components, such as JDBC data sources and JMS queues.


Standard J2EE Processing

Let's first summarize some important facts about the J2EE platform. Traditionally, the J2EE client application uses JNDI to find J2EE components on the server-side. For example, the client application looks up the EJB reference in JNDI and receives an EJB client proxy in return, which the client later uses to access the EJB component. All J2EE communication normally occurs over RMI.



Figure 1: Standard JNDI usage

J2EE - Basic Approaches

There are two basic approaches to accessing J2EE resources via SOAP.

We'll start with the most obvious approach, which is to create a Web service wrapper around the EJB. This approach is particularly appropriate in situations where the Web service application doesn't map directly to the capabilities of an individual EJB and requires some additional orchestration of the J2EE components.

n our second example we will introduce a code-less, transparent integration approach. Its main goal is to expose existing J2EE applications as Web services as quickly and as dynamically as possible. This approach allows us to effectively access existing J2EE applications over SOAP without writing or modifying any code.


The Simple Stock Quote EJB Wrapper Demo

In this demo we will introduce the EJB wrapper Web service approach to access a simple stateless session bean: the stock quote EJB. The wrapper approach is very simple, and it is widely used by many SOAP frameworks. There are only slight differences among the various implementations, which generally pertain to the level of automation of the development process. The wrapper approach requires development of a Web service that wraps one or more existing J2EE components. This wrapper acts as a bridge between the SOAP world and the RMI world. Clients send SOAP requests to the wrapper, and the wrapper translates them into RMI requests to the EJB components. This approach is recommended for use mainly with stateless J2EE resources, such as stateless session beans. To access stateful resources using this technique you would need to set up additional lifecycle services to manage the proper removal of orphaned stateful resources.

We first need to perform some simple installation and configuration steps.

NOTE: If you haven't already downloaded the software used to create the tutorial examples, please refer to the installation chapter in Part One. You'll also need to download the demo sources. We assume that you've unpacked this archive into the c:wasp_demo directory. All Java sources mentioned in the tutorial examples can be found in the src subdirectory of the unpacked demo sources archive. They all reside in the com.systinet.demos package. Similarly all scripts used in the demo are located in the bin subdirectory. You don't need to download and use the software to understand these articles, but we strongly recommend it.

ADDITIONAL INSTALLATION STEPS: We will use the Sun J2EE 1.3 reference implementation for our J2EE environment. It's available for download from Sun's Java website. After you've installed the J2EE 1.3 RI, you have to configure the WASP Web service runtime to use the Sun J2EE RI by making a couple of changes in the env.bat script that is located in the bin subdirectory of your WASP Advanced installation. First comment out the following line (place rem at the beginning of the line):

set INSTALLATION_TYPE=standalone

Then uncomment the following line in the same script file (remove rem):

set INSTALLATION_TYPE=j2ee

You also need to modify the env.bat script located in the c:wasp_demobin directory. Please specify the correct values for the J2EE_HOME, WASP_HOME and WASP_DEMO environment variables.

Once you have completed the installation and configuration steps outlined above, start the J2EE server and WASP Web services runtime using the startJ2EE and startserver scripts. Next run the deploy_j2ee script to compile the Java sources and deploy the EJBs that we will use in our demos.

NOTE: You'll need to restart the J2EE server after you deploy the EJBs.

You can view the Java sources in the com.systinet.demos.stock package to find that the StockQuote, StockQuoteHome and StockQuoteBean classes implement a fairly simple stateless session bean with one simple getQuote method. We've already deployed this EJB by invoking the deploy script. You can make sure that all EJBs are correctly deployed using the J2EE administration tool. Invoke the J2EEAdmin script from the demo bin directory to start the administration tool.

Now let's concentrate on the wrapper Web service implementation, listed in Figure 2. It implements a getQuote method, which contains a simple EJB invocation. First it retrieves the EJB home reference from JNDI and creates an EJB instance. Then it invokes the getQuote method on the EJB, and removes the EJB. Finally the invocation result is returned back to the Web service client. You can see these steps in the code below:

package com.systinet.demos.stock;

import javax.naming.InitialContext;
import javax.naming.Context;
import javax.naming.NamingException;
import java.rmi.RemoteException;


public class StockQuoteService {

    public double getQuote(String symbol) throws Exception {

        // get the JNDI initial context
        System.err.println("Getting J2EE initial context");
        Context jndiContext = new InitialContext();
        // lookup the EJB home
        System.err.println("Looking up EJB Home");
        Object homeRef = jndiContext.lookup("Stock");
        StockQuoteHome home =
        (StockQuoteHome)javax.rmi.PortableRemoteObject.narrow(
        homeRef, StockQuoteHome.class);
        // create the EJB instance
        System.err.println("Creating EJB");
        StockQuote ejb = home.create();
        // call the getQuote method
        System.err.println("Calling getQuote");
        double quote = ejb.getQuote("SUNW");
        System.err.println("SUNW "+quote);
        // remove the EJB
        System.err.println("Removing EJB");
        ejb.remove();
        return quote;
    }

}



Figure 2: Simple Web service EJB wrapper (StockQuoteService.java)


We can now deploy the EJB wrapper Web service by running the deploy_service script. Then run the run_wrapper script to start the Web service client that invokes the EJB through the wrapper Web service.

NOTE: We've made this demo simple to illustrate the basic principles of the wrapper approach; however, real-life applications are usually a bit more complex. A wrapper service is often used to assemble functionality from a number of EJBs and other J2EE resources. In such cases, the wrapper service usually exposes different programmatic interfaces than the original beans.


Transparent J2EE integration

Another way to access J2EE resources is to use a transparent integration framework. By transparent we mean that it isn't necessary to write a wrapper service or to change the original J2EE code. This approach is most useful if you have existing J2EE resources that you want to make available to SOAP clients or if you have J2EE clients that need to access J2EE resources across the Internet.

The transparent J2EE integration framework described below exploits the strengths of the JNDI architecture, which provides an abstract mechanism to access J2EE resources. As we said earlier, in normal J2EE processing, a J2EE client calls the JNDI lookup method, and the client's JNDI provider passes this request over RMI to the JNDI service within the J2EE server. JNDI returns a J2EE proxy to the client, which uses this proxy to invoke methods on the remote J2EE resource over RMI. In this example, we will use a JNDI provider on the client side that speaks SOAP rather than RMI. As you can see in Figure 3, when the client issues a JNDI call using this provider, the request is sent over SOAP to a JNDI web service. This JNDI web service performs the actual lookup in the application server JNDI, obtaining the J2EE proxy. The JNDI web service then returns to the client a SOAP-based remote reference to the J2EE proxy. The client application can then use this remote reference to invoke methods on the J2EE resource. Each method invocation is transported over SOAP to the J2EE proxy, which redirects the request to the actual J2EE resource. You'll notice that no code modifications are required in either the J2EE resource or in the client code. Only a configuration change is required in the client to point to the SOAP-based JNDI provider.



Figure 3: Web service access to JNDI


NOTE: Most Web service runtime servers operate in the same context as the application server, so the redirected method invocation is very fast and won't degrade performance.

This approach also works for non-Java clients. Since the JNDI Web service is a standard Web service, any SOAP client can take advantage of its transparent invocation framework. For example, a Microsoft Visual Basic client can call the lookup method on the JNDI Web service and obtain a Web service proxy to the requested J2EE resource.

The JNDI Web service performs automatic remote garbage collection of all components created in the Web service runtime. Most of those components are discarded on demand when the client application discards the remote component explicitly, but there is no guarantee of proper removal in the loosely coupled world of Web services. That's why all dynamically created resources are tracked and managed by a LifeCycle service.

The major advantage of this approach is that it provides immediate and transparent SOAP access to any J2EE resource registered in JNDI, including all types of EJB components (stateless and stateful session beans, entity beans, and message-driven beans), plus JMS, JDBC, and other J2EE resources. No modifications or wrappers need to be made for the J2EE resources. This approach is obviously very useful to provide quick and easy access to existing systems via SOAP. Let's look at an example.


Transparent J2EE Integration Demo

This demo shows a Web service calling an EJB running in Sun's J2EE 1.3 Reference Implementation engine.

Let's first check out the server-side EJB code. As you can see in the following code listing, it's a standard stateful session bean that keeps the state of a simple counter.

package com.systinet.demos.counter;


import javax.ejb.CreateException;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;
import javax.ejb.SessionSynchronization;

public class CounterEJB implements SessionBean {

    private SessionContext context;

    private int count = 0;

    /**
     * No argument constructor required by container.
     */
    public CounterEJB() {
    }

    /**
     * Create method specified in EJB 1.1 section 6.10.3
     */
    public void ejbCreate() {
    }

    /* Methods required by SessionBean Interface. EJB 1.1 section 6.5.1. */

    /**
     * @see javax.ejb.SessionBean#setContext(javax.ejb.SessionContext)
     */
    public void setSessionContext(SessionContext context){
        this.context = context;
    }

    /**
     * @see javax.ejb.SessionBean#ejbActivate()
     */
    public void ejbActivate() {
    }

    /**
     * @see javax.ejb.SessionBean#ejbPassivate()
     */
    public void ejbPassivate() {
    }

    /**
     * @see javax.ejb.SessionBean#ejbRemove()
     */
    public void ejbRemove() {
    }

    public long getCount() {
        return count++;
    }

}


Figure 4: Simple Counter EJB (CounterEJB.java) - the server-side EJB code


The other EJB sources are fairly obvious. You can see the code of the Counter remote and CounterHome home interfaces. We've already deployed the EJB in the first demo by invoking the deploy_j2ee command. Please note that we don't need to deploy any specific Web service to the Web service runtime.

The client application is a standard EJB client with the exception that it uses different JNDI properties in the getInitialContext method.

package com.systinet.demos.counter;

import javax.naming.InitialContext;
import javax.naming.Context;
import javax.naming.NamingException;
import java.rmi.RemoteException;

public class CounterClient {

    public CounterClient() {
    }

   public static void main(String [] args){
        try {

           // get the JNDI initial context
           System.err.println("Getting J2EE initial context");
           Context jndiContext = getInitialContext();
           // lookup the EJB home
           System.err.println("Looking up EJB Home");
           Object homeRef = jndiContext.lookup("Counter");
           CounterHome home = (CounterHome)javax.rmi.PortableRemoteObject.narrow(homeRef, CounterHome.class);
           // create the EJB instance
           System.err.println("Creating EJB");
           Counter ejb = home.create();
           System.out.println("Calling count "+ejb.getCount());
           System.out.println("Calling count "+ejb.getCount());
           System.out.println("Calling count "+ejb.getCount());
           // remove the EJB
           System.err.println("Removing EJB");
           ejb.remove();

        }
        catch(java.rmi.RemoteException re) {
            re.printStackTrace();
        }
        catch(Throwable t) {
            t.printStackTrace();
        }

    }

    static public Context getInitialContext() throws javax.naming.NamingException {

        java.util.Properties jndiProperties = new java.util.Properties();

        jndiProperties.put("java.naming.factory.initial","com.idoox.jndi.InitialContextFactoryImpl");
        jndiProperties.put("java.naming.provider.url","http://localhost:6060");

        return new InitialContext(jndiProperties);
    }

}

Figure 5: Simple Counter EJB Client (CounterClient.java)


Please notice that for the sake of simplicity we've hard-coded all JNDI specific parameters. There are two parameters that need to be defined in order to redirect the JNDI query to the JNDI Web service: java.naming.factory.initial and java.naming.provider.url. Those parameters are usually stored in an application .properties file rather than hardcoded in the client application. In such case, recompilation of the code would not be necessary.

The next step is to compile and run the J2EE client application using the run script. You should see the EJB's getCount method called three times. All communication between the client application and the server-side is through SOAP messages. You can also see that the state (counter value) is properly maintained.


Sending Reliable SOAP Messages over JMS

Today most Web services use the HTTP transport protocol for communications. HTTP is very suitable for many applications. Its main advantage is flexibility of application integration through the HTTP proxy and firewalls. But for some applications, HTTP might not be sufficient. HTTP is unidirectional and lacks many enterprise-class features, such as reliability, persistence, and transactions. Also, its support for asynchronous message routing scenarios isn't ideal. One approach to addressing these issues is to use JMS to transport SOAP messages. JMS can provide substantial benefits for enterprise application communication since it supports guaranteed message delivery, transactional enqueueing and dequeueing of messages, and synchronous and asynchronous messaging semantics. JMS also offers much better performance and scalability than the HTTP protocol.

This next and final example demonstrates that SOAP is truly transport protocol independent. We'll access the same Web service that we developed in the first example, but this time we'll send the SOAP messages over JMS. The implementation is very simple. We only need to change the lookup url passed to the the client-side lookup method:

package com.systinet.demos.jms;

import org.idoox.wasp.Context;
import org.idoox.webservice.client.WebServiceLookup;


public final class StockClient {

  /**
   * @param args  not used.
   */
    public static void main( String[] args ) throws Exception {

      System.setProperty("java.naming.factory.initial","com.idoox.jndi.InitialContextFactoryImpl");
      System.setProperty("java.naming.provider.url","http://localhost:6060");
      System.setProperty("idoox.demo.transport.j2ee", "true");

      // lookup service
      WebServiceLookup lookup = (WebServiceLookup)Context.getInstance(Context.WEBSERVICE_LOOKUP);
      // bind to StockQuoteService
      StockQuoteServiceProxy quoteService = (StockQuoteServiceProxy)lookup.lookup(
"jms://jms/Queue@jms/QueueConnectionFactory~/StockEJBService/",StockQuoteServiceProxy.class);


      // use StockQuoteService

      System.out.println("Getting SUNW quote");
      System.out.println("------------------------");
      System.out.println("SUNW "+quoteService.getQuote("SUNW"));
      System.out.println("");


    }

}


Figure 6: Simple JMS client (StockClient.java)


We'll compile and run the JMS client example by running the run_jms script. The SOAP framework provides a completely transparent mapping to the underlying transport protocol, so all previously mentioned features (e.g stateful Web services, remote references, SOAP faults, etc.) will work over JMS without any Java code changes.

NOTE: The Web service runtime is pre-configured to listen for SOAP messages on the jms/Queue JMS queue.


Cleanup

Now that we've completed our demos, use the undeploy_j2ee and undeploy_service scripts to remove the EJB from the application server and the Web service from the Web service runtime.

Review

In this part of the Web services tutorial we learned about two ways to integrate Web services with J2EE. We introduced the basic wrapper Web service and the transparent integration framework and explained the situations in which each approach provides substantial advantages. We've also shown that SOAP messages can be sent over the JMS protocol.

In the next installment we'll demonstrate one of the major strengths of Web services, interoperability between various Web services technologies. See you then.