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).
You will be placing all the Java files and the XML deployment descriptor for the TravelAgent EJB into this directory.
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
String
s. 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; }
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.
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 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.
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>
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>
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>
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.
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.
Please 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.