Previous section   Next section

2.3 Using EJBs to Implement Web Services

So far, you have seen how to implement a web service and arrange for it to be hosted by a servlet in a web container. J2EE 1.4 extends this model by allowing you to implement web services as stateless session beans in the EJB container or, if you want to view it another way, to advertise the remote interface of an existing stateless session bean as a web service, provided that the interface methods meets the requirements of a JAX-RPC service endpoint interface.

2.3.1 Web Service Session Bean Requirements

Session beans typically have a combination of the following:

Session beans that host web services must provide and implement a service endpoint interface like the BookQuery interface defined for the book web service described in this chapter. This interface must, of course, satisfy the usual JAX-RPC requirements described in Section 2.1.2.1, earlier in this chapter, and will ultimately be published to clients in the form of a WSDL definition. The session bean may also have remote and/or local interfaces and the corresponding home and local home interfaces. However, a bean that is implemented simply in order to host a web service is not required to be accessible to either local or remote clients as an EJB and therefore is not required to provide the local and remote interfaces (or their home interface counterparts). Thus, although it is possible to extend an existing session bean so that it can be exposed as a web service by defining a service endpoint interface that advertises some or all of its existing business methods, it is not necessary to invent a local or remote interface or home interfaces for a new bean that is only intended to be used as a web service.

A web service session bean must provide a public no-argument constructor and all of the methods of the SessionBean interface. As you'll see later in this section, if the bean is purely providing a web service interface, most of these methods can be empty stubs. The bean must also provide implementations of all of the methods in the web service endpoint interface. However, the bean implementation class is not required to declare that it implements the endpoint interface.

2.3.2 EJB Web Service Programming Model

The lifecycle of a web service session bean is exactly the same as that of an ordinary stateless session bean. When a web service client invokes one of its methods, the container creates an instance and delegates the method invocation to it. Since the bean is stateless, the container is free to create a pool of bean instances to which it directs web service method invocations as required. The lifecycle of the bean is, therefore, decoupled from the lifecycle of the client applications that invoke its methods.

The overall lifecycle is as follows:

  1. The container invokes the bean implementation class's zero-argument constructor.

  2. The container calls the bean's setSessionContext( ) method, passing it a SessionContext object that gives it access to the container environment.

  3. The container calls the bean's ejbCreate( ) method. Since the bean is created by the container rather than in response to a create( ) call from a home interface, and there is no state available that could be passed as method arguments, the bean is required to provide a zero-argument ejbCreate( ) method.

  4. The container calls one or more methods from the web service endpoint interface as a result of receiving and decoding SOAP messages from clients.

  5. After some time, the container may choose to destroy the bean instance. At this point, it calls its ejbRemove( ) method, after which no further web service calls will be delegated to it.

  6. Eventually, the bean is garbage-collected. However, the implementation class is not permitted to override the finalize( ) method and cannot, therefore, use this method to tidy up its state. Any required cleanup should, therefore, be performed in the ejbRemove( ) method.

The fact that the container calls the ejbCreate( ) and ejbRemove( ) methods makes it possible for the bean to manage resources at the beginning and end of its lifecycle. By contrast, the implementation class for a servlet-hosted bean does not have direct access to the lifecycle of that servlet, and therefore, it must use its constructor and finalize( ) method to allocate and release resources unless it also implements the optional ServiceLifecycle interface, which will be discussed in Chapter 6.

The container's call to the setSessionContext( ) method provides the bean with a SessionContext object, which allows it access to its execution context, such as the java.security.Principal object that identifies the caller of its service endpoint methods. A servlet-hosted service implementation must implement the ServiceLifecycle interface to obtain the same information. Of course, the caller identity is valid only if the container has authenticated the caller. For further discussion of this topic, refer to Chapter 6. Note that the bean may not attempt to use the SessionContext getEJBHome( ) and getEJBLocalHome( ) methods to access the home or local home interfaces if it does not define those interfaces. Similarly, the bean may not call the getEJBObject( ) and getEJBLocalObject( ) methods during the execution of the methods that implements its service endpoint interface. This remains true even if the bean provides a local or remote client view outside the scope of its web service role.

A web service implemented in a session bean can assume that it is single-threaded, since this restriction applies to all session beans. EJB-based web services, therefore, have a slightly simpler programming model than those hosted by servlets, where multithreading is the norm.

2.3.3 The Book Web Service Implemented as a Stateless Session Bean

It is extremely simple to convert the servant class for the book web service shown in Example 2-6 into a session bean implementation of the same service. Since web service beans don't need to have home interfaces or even remote interfaces, you need only to build the session bean class itself, although nothing prevents you from providing these interfaces if you also want to expose your web service to EJB clients. Example 2-18 shows part of the bean implementation class for the book web service.

Example 2-18. Implementing the book web service as a stateless session bean
package ora.jwsnut.chapter2.ejbbookservice;

import java.util.HashMap;
import javax.ejb.EJBException;
import javax.ejb.SessionBean;
import javax.ejb.SessionContext;
import ora.jwsnut.chapter2.bookservice.BookInfo;
import ora.jwsnut.chapter2.bookservice.BookServiceException;

/**
 * Implementation class for the books web service
 * hosted by a stateless session bean.
 */
public class BookServiceEJB implements SessionBean {

    private SessionContext sessionContext;
    
    /**
     * Provides the bean with access to state
     * from the container.
     */ 
    public void setSessionContext(SessionContext sessionContext) 
      throws EJBException {
        this.sessionContext = sessionContext;
    }

    // SessionBean methods
    public void ejbCreate(  ) throws EJBException {
        // Nothing to do in this example
    }
    
    public void ejbRemove(  ) throws EJBException {
        // Nothing to do in this example
    }

    /**
     * Activation/passivations methods are also no-ops
     */
    public void ejbActivate(  ) throws EJBException {
    }
    
    public void ejbPassivate(  ) throws EJBException {
    }
    
    /**
     * Gets the number of books known to the service
     * @return the number of books known to the service.
     */
    public int getBookCount(  ) {
        return BookServiceServantData.getBookInfo(  ).length;
    }
    
    // All other code unchanged
}

As you can see, the BookServiceEJB class implements the SessionBean interface, which requires it to provide the setSessionContext( ), ejbActivate( ), ejbPassivate( ), and ejbRemove( ) methods. In this case, there is nothing to do in any of these methods, although a more complex bean might use the ejbRemove( ) method, for example, to deallocate any resources that the bean uses during its lifecycle. In order to expose a session bean as a web service, it is also necessary to provide a no-argument ejbCreate( ) method, which could be used to perform initial resource allocation or to use JNDI to access configuration information specified in the ejb-jar.xml file and stored in the bean's environment, but which in this case also does nothing. The rest of the code consists of the implementation of the web service interface, which is not shown here because it is unchanged from Example 2-6.

The bean also has to provide a service endpoint interface class, which defines its web service view and provides the methods that its clients can use. In this case, the bean will simply reuse the BookQuery interface as defined in Example 2-3. Note, however, that the BookServiceEJB class does not state that it implements the BookQuery interface, because the Web Services for J2EE specification does not require it to do so. As you'll see later in this chapter, the connection between the bean class and the service endpoint interfaces that it implements is made in a deployment descriptor rather than in code. As a matter of fact, the bean implementation of this service can refer to exactly the same compiled class file for the service interface as the servlet-hosted variant. This means that a single application client could load the same service endpoint interface class file to access either the servlet- or the session bean-hosted variant of the service and not be able to tell them apart. More realistically, however, a client application will not have access to the original class file created by the service developer. Instead, it will use an interface class generated from the service's WSDL definition. Since both implementations offer the same service interface, they can share the same WSDL definition; therefore, no client that imports that definition will be aware of which implementation choice was made by the service developer.

2.3.4 EJB Web Service Deployment

Of course, writing the code is not the end of the story � you also need to deploy the service, which means creating the deployment descriptors and packaging them with the code. Writing deployment descriptors is a tedious task that is best delegated to a tool. Nevertheless, it is useful to know what they look like and how they depend on each other, so that you know where to look and what to expect should anything go wrong with your deployment.

You can compile and deploy the web service EJB by making chapter2\ejbbookservice your working directory and typing the command:

ant ejb-deploy

This command creates an EJB JAR file, wraps it in an Enterprise Archive, and deploys it to the J2EE 1.4 server. The EJB JAR file contains the files shown in Table 2-10.

Table 2-10. Files required to deploy the session bean-hosted book web service

File type

File name

Service endpoint interface

ora/jwsnut/chapter2/bookservice/BookInfo.class

 

ora/jwsnut/chapter2/bookservice/BookQuery.class

 

ora/jwsnut/chapter2/bookservice/BookServiceException.class

Bean implementation classes

ora/jwsnut/chapter2/ejbbookservice/BookServiceEJB.class

 

ora/jwsnut/chapter2/ejbbookservice/BookServiceServantData.class

 

ora/jwsnut/chapter2/ejbbookservice/booklist.txt

WSDL file

BookService.wsdl

Deployment information

META-INF/ejb-jar.xml

 

META-INF/mapping.xml

 

META-INF/model

 

META-INF/webservices.xml

Manifest file

META-INF/MANIFEST.MF

The mapping.xml and model files perform the same function for the EJB as they did for the servlet-hosted version of the service shown earlier in this chapter. The two files of most interest in the archive are ejb-jar.xml and webservices.xml. The first file contains standard deployment information for a stateless session bean, as shown in Example 2-19.

Example 2-19. ejb-jar.xml file for the book web service session bean
<?xml version="1.0" encoding="UTF-8"?>
<ejb-jar version="2.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://java.sun.com/xml/ns/j2ee/ejb-jar_2_1.xsd">
  <display-name>EJB Book Service</display-name>
  <enterprise-beans>
    <session>
      <ejb-name>BookQueryBean</ejb-name>
      <service-endpoint>ora.jwsnut.chapter2.bookservice.BookQuery
      </service-endpoint>
      <ejb-class>ora.jwsnut.chapter2.ejbbookservice.BookServiceEJB</ejb-class>
      <session-type>Stateless</session-type>
      <transaction-type>Container</transaction-type>
    </session>
  </enterprise-beans>
</ejb-jar>

There are a couple of points worth noting in this file. First, since this bean offers only a web service interface, it does not require either a remote or a home interface, and therefore the elements of the deployment descriptor that correspond to these classes have been omitted from ejb-jar.xml. Second, for a web service bean, you have to include an additional element that declares the service endpoint interface that the bean implements. The line containing the service-endpoint element appropriate for this bean is highlighted in Example 2-19.

The content of the webservices.xml file for this archive is shown in Example 2-20.

Example 2-20. The webservices.xml file for the book web service session bean
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE webservices 
   PUBLIC "-//IBM Corporation, Inc.//DTD J2EE Web services 1.0//EN" 
   "http://www.ibm.com/standards/xml/webservices/j2ee/j2ee_web_services_1_0.dtd">
         
<webservices>
  <webservice-description>
    <webservice-description-name>EJB-Based JAX-RPC Book Service
    </webservice-description-name>
    <wsdl-file>BookService.wsdl</wsdl-file>
    <jaxrpc-mapping-file>META-INF/model</jaxrpc-mapping-file>
    <port-component>
      <port-component-name>BookQueryPort</port-component-name>
      <wsdl-port>
        <namespaceURI>urn:jwsnut.chapter2.bookservice/wsdl/BookQuery
        </namespaceURI>
        <localpart>BookQueryPort</localpart>
      </wsdl-port>
      <service-endpoint-interface>ora.jwsnut.chapter2.bookservice.BookQuery
      </service-endpoint-interface>
      <service-impl-bean>
        <ejb-link>BookQueryBean</ejb-link>
      </service-impl-bean>
    </port-component>
  </webservice-description>
</webservices>

This file is almost the same as that deployed with the version of the service shown in Example 2-12, with the exception of the service-impl-bean element. Here, this element contains a nested ejb-link element instead of the servlet-link that is used when the service is hosted by a servlet. The value contained within the ejb-link element is the name of the session bean that provides the service and must match the value of the ejb-name element in the ejb-jar.xml file in Example 2-19.

2.3.5 The EJB Web Service URL

To be accessible to a client, a web service must have a URL. A web service port implemented in a servlet has a URL that is derived from that of the servlet itself, as described earlier in this chapter. Session beans, of course, do not have associated URLs. One of the steps required to deploy a session-bean hosted web service, therefore, is to define the URL to be used to access the bean's web service interface. The deployment tools and the container are responsible for ensuring that SOAP messages sent to the assigned URL are decoded and converted to calls on the session bean's service endpoint interface methods. Although the means by which this is achieved are container-dependent, a natural implementation involves generating a servlet and registering it to respond to the bean's URL.

In the J2EE 1.4 reference implementation, the URL for a session bean-hosted web service is declared in the file sun-j2ee-ri.xml, which is placed in the META-INF directory of the EAR file. The content of the file used in the deployment of the book web service session bean is shown in Example 2-21.

Example 2-21. The sun-j2ee-ri.xml file for the book web service session bean
<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE j2ee-ri-specific-information 
   PUBLIC '-//Sun Microsystems Inc.//DTD J2EE Reference Implementation 1.3//EN' 
   'http://localhost:8000/sun-j2ee-ri_1_3.dtd'>

<j2ee-ri-specific-information>  
    <enterprise-beans>
        <module-name>EJBBooks.jar</module-name>
        <unique-id>0</unique-id>
        <ejb>
            <ejb-name>BookQueryBean</ejb-name>
            <gen-classes/>
            <webservice-endpoint>
                <port-component-name>BookQueryPort</port-component-name>
                <endpoint-address-uri>webservice/EJBBookQuery
                </endpoint-address-uri>
            </webservice-endpoint>
        </ejb>
    </enterprise-beans>
</j2ee-ri-specific-information>

The content of enterprise-beans element applies to a bean called BookQueryBean that will be found in the file EJBBooks.jar within the EAR. This name matches the value of the ejb-name element in the ejb-jar.xml file in the JAR, as shown in Example 2-19. The nested webservice-endpoint element names the specific port of the web service for which the URL is to be defined and the URL itself, and the value of the port-component-name element must match that of a port-component-name element in the webservices.xml file in EJBBooks.jar (see Example 2-20). The URI given by the endpoint-address-uri element is appended to the URL of the server itself to become the full URI of the web service endpoint. To verify this, and to show that you can use the same web service client to access an EJB-hosted web service and one implemented in a servlet, you can run the book service client shown earlier in this chapter against the session bean by making chapter2\bookservice your working directory and typing the command:

ant -DCLIENT_ARGS=http://localhost:8000/webservice/EJBBookQuery run-client

For the steps required to deploy a session-bean hosted web service to a different J2EE application server and specify its URL, consult the server vendor's documentation.


  Previous section   Next section