ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


O'Reilly Book Excerpts: Enterprise JavaBeans, 4th Edition

Developing Your First EJBs, Part 2

by Bill Burke, Richard Monson-Haefel, and Sacha Labourey

Editor's note: In part one of this two-part excerpt from Chapter 4 of Enterprise JavaBeans, 4th Edition, the authors walked through what you need to do to develop your first entity bean. This week concludes this series with a look at how to develop a session bean, building on the examples presented in part one.

Developing a Session Bean

Related Reading

Enterprise JavaBeans
By Richard Monson-Haefel, Bill Burke, Sacha Labourey

Session beans act as agents to the client, controlling taskflow (the business process) and filling the gaps between the representation of data by entity beans and the business logic. Session beans are often used to manage interactions between entity beans and can perform complex manipulations of beans. Since we have defined only one entity bean so far, we will start by manipulating this bean. The interactions of entity beans within session beans is explored in greater detail in Chapter 11.

Client applications and other beans use the Cabin EJB in a variety of ways. Some of these uses were predictable when the Cabin EJB was defined, but many were not. After all, an entity bean represents data - in this case, data describing a cabin. The uses to which we put that data change over time - hence the importance of separating the data itself from the taskflow. In Titan's business system, for example, we may need to list and report on cabins in ways that were not predictable when the Cabin EJB was defined. Rather than change the Cabin EJB every time we need to look at it differently, we will obtain the information we need using a session bean. The definition of an entity bean should only be changed within the context of a larger process - for example, a major redesign of the business system.

We'll start developing a TravelAgent EJB that is responsible for the taskflow of booking a passage on a cruise. This session bean will be used in client applications accessed by travel agents throughout the world. In addition to booking tickets, the TravelAgent EJB provides information about which cabins are available on the cruise. In this chapter, we develop the first implementation of this listing behavior. The "list cabins" behavior will be used to provide customers with a list of cabins that can accommodate their needs. The Cabin EJB does not directly support this kind of list, nor should it. The list we need is specific to the TravelAgent EJB, so it's the TravelAgent EJB's responsibility to query the Cabin EJB and produce the list.

Start by creating a development directory for the TravelAgent EJB, as we did for the Cabin EJB. Name this directory travelagent and nest it below the /dev/com/titan directory, which also contains the cabin directory (see Figure 4-5). Place all the Java files and the XML deployment descriptor for the TravelAgent EJB into the travelagent directory.

Figure 4-5
Figure 4-5. Directory structure for the TravelAgent EJB

TravelAgentRemote: The Remote Interface

As before, we start by defining the remote interface so that our focus is on the business purpose of the bean, rather than its implementation. Starting small, we know that the TravelAgent EJB will need to provide a method for listing all the cabins available with a specified bed count for a specific ship. We'll call that method listCabins( ). Since we need only a list of cabin names and deck levels, we'll define listCabins( ) to return an array of Strings. Here's the remote interface for TravelAgentRemote:

package com.titan.travelagent;

import java.rmi.RemoteException;
import javax.ejb.FinderException;

public interface TravelAgentRemote extends javax.ejb.EJBObject {

    // String elements follow the format "id, name, deck level"
    public String [] listCabins(int shipID, int bedCount)
        throws RemoteException;
}

TravelAgentHomeRemote: The Remote Home Interface

The second step in the development of the TravelAgent EJB bean is to create the remote home interface. The remote home interface for a session bean defines the create methods that initialize a new session bean for use by a client.

Find methods are not used in session beans; session beans do not represent data in the database, so a find method would not be meaningful. A session bean is dedicated to a client for the life of that client (or less). For the same reason, we don't need to worry about primary keys - since session beans don't represent persistent data, we don't need a key to access that data.

package com.titan.travelagent;

import java.rmi.RemoteException;
import javax.ejb.CreateException;

public interface TravelAgentHomeRemote extends javax.ejb.EJBHome {
    public TravelAgentRemote create( )
        throws RemoteException, CreateException;
}

In the case of the TravelAgent EJB, we need only a simple create( ) method to get a reference to the bean. Invoking this create( ) method returns the TravelAgent EJB's remote reference, which the client can use for the reservation process.

TravelAgentBean: The Bean Class

Using the remote interface as a guide, we can define the TravelAgentBean class that implements the listCabins( ) method. Here's the definition of TravelAgentBean for this example:

package com.titan.travelagent;

import com.titan.cabin.CabinRemote;
import com.titan.cabin.CabinHomeRemote;
import java.rmi.RemoteException;
import javax.naming.InitialContext;
import javax.naming.Context;
import java.util.Properties;
import java.util.Vector;
import javax.rmi.PortableRemoteObject;
import javax.ejb.EJBException;


public class TravelAgentBean implements javax.ejb.SessionBean {

    public void ejbCreate( ) {
    // Do nothing.
    }
    public String [] listCabins(int shipID, int bedCount) {

        try {
            javax.naming.Context jndiContext = new InitialContext( );
            Object obj = jndiContext.lookup("java:comp/env/ejb/CabinHomeRemote");

            CabinHomeRemote home = (CabinHomeRemote)
                PortableRemoteObject.narrow(obj,CabinHomeRemote.class);
    
            Vector vect = new Vector( );
            for (int i = 1; ; i++) {
                Integer pk = new Integer(i);
                CabinRemote cabin;
                try {
                    cabin = home.findByPrimaryKey(pk);
                } catch(javax.ejb.FinderException fe) {
                    break;
                }
                // Check to see if the bed count and ship ID match.
                if (cabin.getShipId( ) == shipID && 
                    cabin.getBedCount( ) == bedCount) {
                    String details = i+","+cabin.getName( )+
                                     ","+cabin.getDeckLevel( );
                    vect.addElement(details);
                }
            }
        
            String [] list = new String[vect.size( )];
            vect.copyInto(list);
            return list;
       
        } catch(Exception e) {throw new EJBException(e);}    
    }

    public void ejbRemove( ){}
    public void ejbActivate( ){}
    public void ejbPassivate( ){}
    public void setSessionContext(javax.ejb.SessionContext cntx){}
}

In order to examine the listCabins( ) method in detail, let's address the implementation in pieces, starting with the use of JNDI to locate the CabinHomeRemote:

javax.naming.Context jndiContext = new InitialContext( );

Object obj = jndiContext.lookup("java:comp/env/ejb/CabinHomeRemote");

CabinHomeRemote home = (CabinHomeRemote)
    javax.rmi.PortableRemoteObject.narrow(obj, CabinHomeRemote.class);

Beans are clients to other beans, just like client applications. This means that they must interact with other beans in the same way that J2EE application clients interact with beans. For one bean to locate and use another bean, it must first locate and obtain areference to the bean's EJB home. This is accomplished using the JNDI default context, which is the JNDI context that the container provides automatically when you create a new instance of the InitialContext. You don't need to set any properties on the InitialContext when using a standard J2EE component (EJB, Servlet/JSP, or J2EE Application Client).

All beans have their own default JNDI context called the environment naming context, which was discussed briefly in Chapter 3. The default context exists in the name space (directory) called "java:comp/env" and its subdirectories. When the bean is deployed, any beans it uses are mapped into the subdirectory "java:comp/env/ejb", so that bean references can be obtained at runtime through a simple and consistent use of the JNDI default context. We'll come back to this when we look at the deployment descriptor for the TravelAgent EJB.

Once the remote EJB home of the Cabin EJB has been obtained, we can use it to produce a list of cabins that match the parameters passed into the method. The following code loops through all the Cabin EJBs and produces a list that includes only those cabins in which the ship and bed count are specified:

Vector vect = new Vector( );
for (int i = 1; ; i++) {
    Integer pk = new Integer(i);
    CabinRemote cabin;
    try {
        cabin = home.findByPrimaryKey(pk);
    } catch(javax.ejb.FinderException fe){
        break;
    }
    // Check to see if the bed count and ship ID match.
    if (cabin.getShipId( ) == shipID && cabin.getBedCount( ) == bedCount) {
        String details = i+","+cabin.getName( )+","+cabin.getDeckLevel( );
        vect.addElement(details);
    }
}

This method iterates through all the primary keys, obtaining a remote reference to each Cabin EJB in the system and checking whether its shipId and bedCount match the parameters passed. The for loop continues until a FinderException is thrown, which will probably occur when a primary key that isn't associated with a bean is used. (This isn't the most robust code possible, but it will do for now.) Following this block of code, we simply copy the Vector's contents into an array and return it to the client.

While this is a very crude approach to locating the right Cabin EJBs - we will define a better method in Chapter 11 - it is adequate for our current purposes. The purpose of this example is to illustrate that the taskflow associated with this listing behavior is not included in the Cabin EJB, nor is it embedded in a client application. Taskflow logic, whether it's a process like booking a reservation or like obtaining a list, is placed in a session bean.

The TravelAgent EJB's Deployment Descriptor

The TravelAgent EJB uses an XML deployment descriptor similar to the one used for the Cabin entity bean. The following sections contain the ejb-jar.xmlfile used to deploy the TravelAgent bean in EJB. Chapter 11 describes how to deploy several beans in one deployment descriptor, but for now the TravelAgent and Cabin EJBs are deployed separately.

EJB 2.1: Deployment descriptor

In EJB 2.1, the deployment descriptor for the TravelAgent EJB looks like this:

<?xml version="1.0" encoding="UTF-8" ?>
<ejb-jar 
     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"
     version="2.1">
    <enterprise-beans>
        <session>
            <ejb-name>TravelAgentEJB</ejb-name>
            <home>com.titan.travelagent.TravelAgentHomeRemote</home>
            <remote>com.titan.travelagent.TravelAgentRemote</remote>
            <ejb-class>com.titan.travelagent.TravelAgentBean</ejb-class>
            <session-type>Stateless</session-type>
            <transaction-type>Container</transaction-type>
            <ejb-ref>
                <ejb-ref-name>ejb/CabinHomeRemote</ejb-ref-name>
                <ejb-ref-type>Entity</ejb-ref-type>
                <home>com.titan.cabin.CabinHomeRemote</home>
                <remote>com.titan.cabin.CabinRemote</remote>
            </ejb-ref>
            <security-identity><use-caller-identity/></security-identity>
        </session>
    </enterprise-beans>
    <assembly-descriptor>
    ...
    </assembly-descriptor>
</ejb-jar>

EJB 2.0: Deployment descriptor

In EJB 2.0, the deployment descriptor for the TravelAgent EJB looks like this:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise
JavaBeans 2.0//EN" "http://java.sun.com/dtd/ejb-jar_2_0.dtd">
<ejb-jar>
    <enterprise-beans>
        <session>
            <ejb-name>TravelAgentEJB</ejb-name>
            <home>com.titan.travelagent.TravelAgentHomeRemote</home>
            <remote>com.titan.travelagent.TravelAgentRemote</remote>
            <ejb-class>com.titan.travelagent.TravelAgentBean</ejb-class>
            <session-type>Stateless</session-type>
            <transaction-type>Container</transaction-type>
            <ejb-ref>
                <ejb-ref-name>ejb/CabinHomeRemote</ejb-ref-name>
                <ejb-ref-type>Entity</ejb-ref-type>
                <home>com.titan.cabin.CabinHomeRemote</home>
                <remote>com.titan.cabin.CabinRemote</remote>
            </ejb-ref>
            <security-identity><use-caller-identity/></security-identity>
        </session>
    </enterprise-beans>
    <assembly-descriptor>
    ...
    </assembly-descriptor>
</ejb-jar>

EJB 2.0 and 1.1: Defining the XML elements

The only significant difference between the 2.1 and 2.0 deployment descriptors is that EJB 2.1 declares the use of an XML Schema for validation while EJB 2.0 uses a DTD.

Other than the <session-type> and <ejb-ref> elements, the TravelAgent EJB's XML deployment descriptor should be familiar: it uses many of the same elements as the Cabin EJB's. The <session-type> element can be Stateful or Stateless, to indicate which type of session bean is used. In this case, we are defining a stateless session bean.

The <ejb-ref> element is used at deployment time to map the bean references used within the TravelAgent EJB. In this case, the <ejb-ref> element describes the Cabin EJB, which we already deployed. The <ejb-ref-name> element specifies the name that must be used by the TravelAgent EJB to obtain a reference to the Cabin EJB's home. The <ejb-ref-type> tells the container what kind of bean it is, Entity or Session. The <home> and <remote> elements specify the fully qualified interface names of the Cabin's home and remote bean interfaces.

When the bean is deployed, the <ejb-ref> will be mapped to the Cabin EJB in the EJB server. This is a vendor-specific process, but the outcome should always be the same. When the TravelAgent EJB does a JNDI lookup using the context name "java:comp/env/ejb/CabinHomeRemote", it obtains a remote reference to the Cabin EJB's home. The purpose of the <ejb-ref> element is to eliminate network-specific and implementation-specific use of JNDI to obtain remote bean references. This makes a bean more portable, because the network location and JNDI service provider can change without affecting the bean code or even the XML deployment descriptor.

While we haven't yet created a local interface for our beans, it's always preferable to use local references instead of remote references when beans access each other within the same server. Local references are specified using the <ejb-local-ref> element, which looks just like the <ejb-ref> element.

The <assembly-descriptor> section of the deployment descriptor is the same for EJB 2.1 and EJB 2.0:

<assembly-descriptor>
    <security-role>
        <description>
            This role represents everyone who is allowed full access 
            to the TravelAgent EJB.
        </description>
        <role-name>everyone</role-name>
    </security-role>

    <method-permission>
        <role-name>everyone</role-name>
        <method>
            <ejb-name>TravelAgentEJB</ejb-name>
            <method-name>*</method-name>
        </method>
    </method-permission>

    <container-transaction>
        <method>
            <ejb-name>TravelAgentEJB</ejb-name>
            <method-name>*</method-name>
        </method>
        <trans-attribute>Required</trans-attribute>
    </container-transaction>
</assembly-descriptor>

Deploying the TravelAgent EJB

Once you've defined the XML deployment descriptor, you are ready to place the TravelAgent EJB in its own JAR file and deploy it into the EJB server. Use the same process to JAR the TravelAgent EJB as you used for the Cabin EJB. Shrink-wrap the TravelAgent EJB class and its deployment descriptor into a JAR file and save the file to the com/titan/travelagent directory:

\dev % jar cf travelagent.jar com/titan/travelagent/*.class META-INF/ejb-jar.xml

F:\..\dev>jar cf travelagent.jar com\titan\travelagent\*.class META-INF\ejb-jar.xml

You might have to create the META-INF directory first, and copy ejb-jar.xml into that directory. The TravelAgent EJB is now complete and ready to be deployed.

To make your TravelAgent EJB available to a client application, you need to use the deployment utility or wizard of your EJB server. The deployment utility reads the JAR file to add the TravelAgent EJB to the EJB server environment. Unless your EJB server has special requirements, it is unlikely that you will need to change or add any new attributes to the bean. You will not need to create a database table, since the TravelAgent EJB is using the Cabin EJB and is not itself persistent. However, you will need to map the <ejb-ref> element in the TravelAgent EJB's deployment descriptor to the Cabin EJB. Your EJB server's deployment facilities provides a mechanism for accomplishing this task (see Exercise 4.2 in the Workbook).

Creating a Client Application

To show that our session bean works, we'll create a simple client application that uses it. This client produces a list of cabins assigned to ship 1 with a bed count of 3. Its logic is similar to the client we created earlier to test the Cabin EJB: it creates a context for looking up TravelAgentHomeRemote, creates a TravelAgent EJB, and invokes listCabins( ) to generate a list of the cabins available. Here's the code:

import com.titan.travelagent.TravelAgentRemote;
import com.titan.travelagent.TravelAgentHomeRemote;

import javax.naming.InitialContext;
import javax.naming.Context;
import javax.naming.NamingException;
import javax.ejb.CreateException;
import java.rmi.RemoteException;
import java.util.Properties;
import javax.rmi.PortableRemoteObject;

public class Client_3 {
    public static int SHIP_ID = 1;
    public static int BED_COUNT = 3;

    public static void main(String [] args) {
        try {
            Context jndiContext = getInitialContext( );
           
            Object ref = jndiContext.lookup("TravelAgentHomeRemote");
            TravelAgentHomeRemote home = (TravelAgentHomeRemote)
                PortableRemoteObject.narrow(ref,TravelAgentHomeRemote.class);
        
            TravelAgentRemote travelAgent = home.create( );
        
            // Get a list of all cabins on ship 1 with a bed count of 3.
            String list [] = travelAgent.listCabins(SHIP_ID,BED_COUNT);
        
            for(int i = 0; i < list.length; i++){
                System.out.println(list[i]);
            }
        
        } catch(java.rmi.RemoteException re){re.printStackTrace( );}
            catch(Throwable t){t.printStackTrace( );}
    }
    static public Context getInitialContext( ) throws Exception {
        Properties p = new Properties( );
        // ... Specify the JNDI properties specific to the vendor.
        return new InitialContext(p);
    }
}

When you have successfully run Client_3, the output should look like this:

1,Master Suite                  ,1
3,Suite 101                     ,1
5,Suite 103                     ,1
7,Suite 105                     ,1
9,Suite 107                     ,1
12,Suite 201                     ,2
14,Suite 203                     ,2
16,Suite 205                     ,2
18,Suite 207                     ,2
20,Suite 209                     ,2
22,Suite 301                     ,3
24,Suite 303                     ,3
26,Suite 305                     ,3
28,Suite 307                     ,3
30,Suite 309                     ,3

You have now successfully created the first piece of the TravelAgent session bean - a method that obtains a list of cabins by manipulating the Cabin EJB entity.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.