Developing a Session Bean

Session beans act as agents to the client, controlling workflow (the business process) and filling the gaps between the representation of data by entity beans and the business logic that interacts with that data. Session beans are often used to manage interactions between entity beans and can perform complex manipulations of beans to accomplish certain tasks. Since we have defined only one entity bean so far, we will focus on a complex manipulation of the Cabin EJB rather than the interactions of the Cabin EJB with other entity beans. The interactions of entity beans within session beans will be explored in greater detail in Chapter 12.

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 will change over time—hence the importance of separating the data itself from the workflow. 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. Changing the definition of an entity bean should be done only within the context of a larger process—for example, a major redesign of the business system.

In Chapter 1 and Chapter 2, we talked hypothetically about a TravelAgent EJB that was responsible for the workflow 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 will develop the first implementation of this listing behavior in the TravelAgent EJB. The listing method we develop in this example is admittedly very crude and far from optimal. However, this example is useful for demonstrating how to develop a very simple stateless session bean and how these session beans can manage other beans. In Chapter 12, we will rewrite the listing method. The “list cabins” behavior developed here will be used by travel agents 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.

You will need to create a development directory for the TravelAgent EJB, as we did for the Cabin EJB. We will name this directory travelagent and nest it below the /dev/com/titan directory, which also contains the cabin directory (see Figure 4-5).

Directory structure for the TravelAgent EJB

Figure 4-5. Directory structure for the TravelAgent EJB

You will be placing all the Java files and the XML deployment descriptor for the TravelAgent EJB into this directory.

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; they are used with entity beans to locate persistent entities for use on a client. Unlike entity beans, session beans are not persistent and do not represent data in the database, so a find method would not be meaningful; there is no specific session to locate. 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. The following code contains the complete 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 = getInitialContext();
            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);}    
    }

    private javax.naming.Context getInitialContext() 
        throws javax.naming.NamingException {
        Properties p = new Properties();
        // ... Specify the JNDI properties specific to the vendor.
        return new javax.naming.InitialContext(p);
    }

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

Examining the listCabins() method in detail, we can address the implementation in pieces, starting with the use of JNDI to locate the CabinHomeRemote:

javax.naming.Context jndiContext = getInitialContext();

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 client applications interact with beans. In order for one bean to locate and use another bean, it must first locate and obtain a reference to the bean’s EJB home. This is accomplished using JNDI in exactly the same way we used JNDI to obtain a reference to the Cabin EJB in the Client_1 and Client_2 applications we developed earlier.

All beans have a 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 shortly when we take a look at the deployment descriptor for the TravelAgent EJB.

As you learned in Chapter 2, enterprise beans in EJB 2.0 may have remote and/or local component interfaces. However, to keep things simple with this first set of examples, we are working with only the remote component interfaces—Chapter 5 will explain how this example may have been implemented with local interfaces.

Once the remote EJB home of the Cabin EJB is obtained, we can use it to produce a list of cabins that match the parameters passed. The following code loops through all the Cabin EJBs and produces a list that includes only those cabins with the ship and bed count 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 simply 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 12—it is adequate for our current purposes. The purpose of this example is to illustrate that the workflow associated with this listing behavior is not included in the Cabin EJB, nor is it embedded in a client application. Workflow logic, whether it’s a process like booking a reservation or 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.xml file used to deploy the TravelAgent bean in EJB 2.0 and 1.1, respectively. In Chapter 12, you will learn how to deploy several beans in one deployment descriptor, but for now the TravelAgent and Cabin EJBs are deployed separately.

EJB 2.0: Deployment descriptor

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

<!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 1.1: Deployment descriptor

In EJB 1.1, the TravelAgent EJB’s deployment descriptor looks like this:

<!DOCTYPE ejb-jar PUBLIC "-//Sun Microsystems, Inc.//DTD Enterprise
JavaBeans 1.1//EN" "http://java.sun.com/j2ee/dtds/ejb-jar_1_1.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/CabinHome</ejb-ref-name>
                <ejb-ref-type>Entity</ejb-ref-type>
                <home>com.titan.cabin.CabinHomeRemote</home>
                <remote>com.titan.cabin.CabinRemote</remote>
            </ejb-ref>
        </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.0 and 1.1 deployment descriptors is the name of the DTD and the addition of a <security-identity> element in EJB 2.0, which simply propagates the caller’s identity.

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 will obtain 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 impacting the bean code or even the XML deployment descriptor.

However, as you will learn in Chapter 5, with EJB 2.0 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 both EJB 2.0 and EJB 1.1:

<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.

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 for this example, 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. You EJB server’s deployment tool will provide a mechanism for doing this. Deploy the TravelAgent EJB and proceed to the next section.

Use the same process to JAR the TravelAgent EJB as was 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. Next, use your EJB container’s proprietary tools to deploy the TravelAgent EJB into the container system.

Creating a Client Application

To show that our session bean works, we’ll create a simple client application that uses it. This client simply 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.cabin.CabinHomeRemote;
import com.titan.cabin.CabinRemote;

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.

Creating a Client ApplicationPlease refer to Workbook Exercise 4.2, A Simple Session Bean. This workbook is available free, in PDF format, at http://www.oreilly.com/catalog/entjbeans3/workbooks.

Get Enterprise JavaBeans, Third Edition now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.