There’s no better place to start than the Cabin EJB, which we have been examining throughout the previous chapters. The Cabin EJB is an entity bean that encapsulates the data and behavior associated with a cruise ship cabin in Titan’s business domain.
When developing an entity bean, we first want to define its remote interface. The remote interface defines the bean’s business purpose; the methods of this interface must capture the concept of the entity. We defined the remote interface for the Cabin EJB in Chapter 2; here, we add two new methods for setting and getting the ship ID and the bed count. The ship ID identifies the ship to which the cabin belongs, and the bed count tells how many people the cabin can accommodate:
package com.titan.cabin; import java.rmi.RemoteException; public interface CabinRemote extends javax.ejb.EJBObject { public String getName( ) throws RemoteException; public void setName(String str) throws RemoteException; public int getDeckLevel( ) throws RemoteException; public void setDeckLevel(int level) throws RemoteException; public int getShipId( ) throws RemoteException; public void setShipId(int sp) throws RemoteException; public int getBedCount( ) throws RemoteException; public void setBedCount(int bc) throws RemoteException; }
The CabinRemote
interface defines four properties:
name
, deckLevel
,
shipId
, and bedCount
.
Properties
are attributes of an enterprise bean that can be accessed by public
set and get methods.
Notice that we have made the CabinRemote
interface
a part of a new package named com.titan.cabin
.
Place all the classes and interfaces associated with each type of
bean in a package specific to the bean. Because our beans are for the
use of the Titan cruise line, we placed these packages in the
com.titan
package hierarchy. We also created
directory structures that match package structures. If you are using
an IDE that works directly with Java files, create a new directory
called dev (for development) and create the
directory structure shown in
Figure 4-1. Copy the CabinRemote
interface into your IDE and save its definition to the
cabin directory. Compile the
CabinRemote
interface to ensure that its
definition is correct. The CabinRemote.class
file, generated by the IDE’s compiler, should be
written to the cabin directory, the same
directory as the CabinRemote.java file. The rest
of the Cabin bean’s classes will be placed in this
same directory.
Once we have defined the remote
interface of the Cabin EJB, we have defined the remote view of this
simple entity bean. Next, we need to define the Cabin
EJB’s remote home interface, which specifies how the
enterprise bean can be created, located, and destroyed by remote
clients; in other words, the Cabin EJB’s life-cycle
behavior. Here is a complete definition of the
CabinHomeRemote
home interface:
package com.titan.cabin; import java.rmi.RemoteException; import javax.ejb.CreateException; import javax.ejb.FinderException; public interface CabinHomeRemote extends javax.ejb.EJBHome { public CabinRemote create(Integer id) throws CreateException, RemoteException; public CabinRemote findByPrimaryKey(Integer pk) throws FinderException, RemoteException; }
The CabinHomeRemote
interface extends
javax.ejb.EJBHome
and defines two life-cycle
methods: create( )
and findByPrimaryKey( )
. These methods create and locate remote
references to Cabin EJBs. Remove methods (for deleting enterprise
beans) are defined in the javax.ejb.EJBHome
interface, so the CabinHomeRemote
interface
inherits them.
We have now defined the remote
client-side API for creating, locating, using, and removing the Cabin
EJB. Now we need to define CabinBean
, the class
that provides the implementation on the server for the Cabin EJB. The
CabinBean
class is an entity bean that uses
container-managed persistence, so its definition will be fairly
simple.
In addition to the callback methods discussed in Chapter 2 and Chapter 3, we
must also define accessor
methods for the CabinRemote
interface and an
implementation of the create method defined in the
CabinHomeRemote
interface. Here is the complete
definition of the
CabinBean
class:
package com.titan.cabin; import javax.ejb.EntityContext; public abstract class CabinBean implements javax.ejb.EntityBean { public Integer ejbCreate(Integer id){ this.setId(id); return null; } public void ejbPostCreate(Integer id){ } public abstract void setId(Integer id); public abstract Integer getId( ); public abstract void setShipId(int ship); public abstract int getShipId( ); public abstract void setName(String name); public abstract String getName( ); public abstract void setBedCount(int count); public abstract int getBedCount( ); public abstract void setDeckLevel(int level); public abstract int getDeckLevel( ); public void setEntityContext(EntityContext ctx) { // Empty implementation. } public void unsetEntityContext( ) { // Empty implementation. } public void ejbActivate( ) { // Empty implementation. } public void ejbPassivate( ) { // Empty implementation. } public void ejbLoad( ) { // Empty implementation. } public void ejbStore( ) { // Empty implementation. } public void ejbRemove( ) { // Empty implementation. } }
The CabinBean
class can be divided into two
sections for discussion: declarations for the
container-managed fields and the
callback methods.
The CabinBean
defines several pairs of abstract
accessor methods. For example, setName( )
and
getName( )
are a pair of abstract accessor
methods. These methods are responsible for setting and getting the
entity bean’s name
field. When
the bean is deployed, the EJB container automatically implements all
the abstract accessor methods so that the bean state can be
synchronized with the database. These implementations map the
abstract accessor methods to fields in the database. Although all the
abstract accessor methods have corresponding methods in the remote
interface, CabinRemote
, it’s not
necessary that they do so. Some accessor methods are for the entity
bean’s use only and are never exposed to the client
through the remote or local interfaces. Note that, unlike the
matching methods in the remote interface, the abstract accessor
methods do not throw RemoteException
s.
It’s customary to consider the abstract accessor
methods as providing access to virtual fields and to refer to those
fields by their method names, less the get
or
set
prefix. For example, the getName( )
/setName( )
abstract accessor methods
define a virtual container-managed
persistence
(CMP) field called
name
(the first letter is always changed to
lowercase). The getDeckLevel( )
/setDeckLevel( )
abstract accessor
methods define a virtual CMP field called
deckLevel
, and so on.
The name
, deckLevel
,
shipId
, and bedCount
fields
represent the Cabin EJB’s persistent state. They
will be mapped to the database at deployment time. These fields are
also publicly available through the entity bean’s
remote interface. Invoking the getBedCount( )
method on a CabinRemote
EJB object causes the
container to delegate that call to the corresponding
getBedCount( )
method on the
CabinBean
instance.
There is no requirement that CMP fields must be exposed. The
id
field is another container-managed field, but
its abstract accessor methods are not exposed to the client through
the CabinRemote
interface. This field is the
primary key of the Cabin EJB; it’s the entity
bean’s index to its data in the database.
It’s bad practice to expose the primary key of an
entity bean—you don’t want client applications
to be able to change that key.
The CabinHomeRemote
interface defines one create( )
method,
so
there is only one corresponding ejbCreate( )
method and one ejbPostCreate( )
method defined by
the CabinBean
class. When a client invokes the
create( )
method on the remote home interface, it
is delegated to a matching ejbCreate( )
method on
the entity bean instance. The ejbCreate( )
method
initializes the fields; in the case of the
CabinBean
, it sets the id
field.
Tip
Although it’s not required by the EJB specification,
some J2EE application vendors insist that
ejbCreate( )
throw
a javax.ejb.CreateException
—this is true of
the J2EE 1.4 SDK. This has never been a requirement, but
it’s an issue that continues to crop up every time
there is a new edition of this book.
The ejbCreate( )
method always returns the primary
key type; with container-managed persistence, this method returns the
null
value. It’s the
container’s
responsibility to create the primary key. Why does it return
null
? This convention makes it easier for EJB
vendors that support container-managed persistence using bean-managed
persistence—it’s a technique that is more
common in EJB 1.1. Bean-managed persistence beans, which are covered
in Chapter 10, always return the primary key
type.
Once the ejbCreate( )
method has executed, the
ejbPostCreate( )
method is called to perform any
follow-up operations. The ejbCreate( )
and
ejbPostCreate( )
methods must have signatures that
match the parameters and (optionally) the exceptions of the home
interface’s create( )
method. The
ejbPostCreate( )
method is used to perform any
postprocessing on the bean after it is created, but before it can be
used by the client. Both methods will execute, one right after the
other, when the client invokes the create( )
method on the remote home interface.
The findByPrimaryKey( )
method is not defined in
container-managed bean classes. Instead, find methods are generated at deployment
and implemented by the container. With bean-managed entity beans,
find methods must be defined in the bean class. In Chapter 10, when you develop bean-managed entity
beans, you will define the find methods in the bean classes you
develop.
The CabinBean
class implements
javax.ejb.EntityBean
, which defines seven callback
methods: setEntityContext( )
,
unsetEntityContext( )
, ejbActivate( )
, ejbPassivate( )
, ejbLoad( )
, ejbStore( )
, and ejbRemove( )
. The container uses these callback methods to notify the
CabinBean
of certain events in its life cycle.
Although the callback methods are implemented, the implementations
are empty. The CabinBean
is simple enough that it
doesn’t need to do any special processing during its
life cycle. When we study entity beans in more detail in Chapter 6 through Chapter 11, we
will take advantage of these callback methods.
You are now ready to create a deployment descriptor for the Cabin EJB. The deployment descriptor performs a function similar to a properties file. It describes which classes make up an enterprise bean and how the enterprise bean should be managed at runtime. During deployment, the deployment descriptor is read and its properties are displayed for editing. The deployer can then modify and add settings as appropriate for the application’s operational environment. Once the deployer is satisfied with the deployment information, she uses it to generate the entire supporting infrastructure needed to deploy the enterprise bean in the EJB server. This may include resolving enterprise bean references, adding the enterprise bean to the naming system, and generating the enterprise bean’s EJB object and EJB home, persistence infrastructure, transactional support, and so forth.
Although most EJB server products provide a wizard for creating and editing deployment descriptors, we will create ours directly so that the enterprise bean is defined in a vendor-independent manner. This requires some manual labor, but it gives you a much better understanding of how deployment descriptors are created. Once the deployment descriptor is finished, the enterprise bean can be placed in a JAR file and deployed on any EJB-compliant server of the appropriate version. An XML deployment descriptor has been created for every example in this book; they are available from the download site.
Tip
Vendors often require that you include vendor-specific deployment files along with the standard ones. This is an unfortunate situation that impacts portability, but something you need to be aware of. Consult your vendor’s documentation to discover what additional configuration files they require.
Throughout this book, we show both the EJB 2.1 and EJB 2.0 code when they are different. In many cases, the component interfaces are the same; however, XML deployment descriptors will be different because EJB 2.1 uses XML Schema, while EJB 2.0 uses an XML DTD. This is the case with the Cabin EJB.
Here’s the deployment descriptor for the Cabin bean in EJB 2.1:
<?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> <entity> <ejb-name>CabinEJB</ejb-name> <home>com.titan.cabin.CabinHomeRemote</home> <remote>com.titan.cabin.CabinRemote</remote> <ejb-class>com.titan.cabin.CabinBean</ejb-class> <persistence-type>Container</persistence-type> <prim-key-class>java.lang.Integer</prim-key-class> <reentrant>False</reentrant> <abstract-schema-name>Cabin</abstract-schema-name> <cmp-field><field-name>id</field-name></cmp-field> <cmp-field><field-name>name</field-name></cmp-field> <cmp-field><field-name>deckLevel</field-name></cmp-field> <cmp-field><field-name>shipId</field-name></cmp-field> <cmp-field><field-name>bedCount</field-name></cmp-field> <primkey-field>id</primkey-field> <security-identity><use-caller-identity/></security-identity> </entity> </enterprise-beans> <assembly-descriptor> ... </assembly-descriptor> </ejb-jar>
The ejb-jar
element declares its namespace, the
XSI namespace, and the location of the XML Schema that is used to
validate it. The meaning of namespaces and XML schemas are described
in more detail in Chapter 16.
In EJB 2.0, the deployment descriptor is based on an XML DTD and 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> <entity> <ejb-name>CabinEJB</ejb-name> <home>com.titan.cabin.CabinHomeRemote</home> <remote>com.titan.cabin.CabinRemote</remote> <ejb-class>com.titan.cabin.CabinBean</ejb-class> <persistence-type>Container</persistence-type> <prim-key-class>java.lang.Integer</prim-key-class> <reentrant>False</reentrant> <abstract-schema-name>Cabin</abstract-schema-name> <cmp-field><field-name>id</field-name></cmp-field> <cmp-field><field-name>name</field-name></cmp-field> <cmp-field><field-name>deckLevel</field-name></cmp-field> <cmp-field><field-name>shipId</field-name></cmp-field> <cmp-field><field-name>bedCount</field-name></cmp-field> <primkey-field>id</primkey-field> <security-identity><use-caller-identity/></security-identity> </entity> </enterprise-beans> <assembly-descriptor> ... </assembly-descriptor> </ejb-jar>
The <!DOCTYPE>
element describes the purpose
of the XML file, its root element, and the location of its DTD. The
DTD is used to verify that the document is structured correctly. This
element is discussed in detail in Chapter 16.
EJB 2.0 specifies the ejb-jar_2_0.dtd
as its DTD.
One important difference between EJB 2.1 and EJB 2.0 is that they use different types of validation for deployment descriptors. EJB 2.0 uses XML DTDs, which have been employed for the past few years to validate the structure of the XML deployment descriptor. XML Schema is a new mechanism for validating deployment descriptors. XML Schema can validate not only the structure but also the values used in the deployment descriptor—something DTDs couldn’t do well. On the other hand, XML Schema is complex and takes time to master, so there is a price to be paid for the added precision it offers.
The rest of the XML
elements are nested one within another and delimited by beginning and
ending tags. The structure is not complicated. If you have done any
HTML coding, you already understand the format. An element always
starts with a
<
name_of_tag
>
tag and ends with a
</
name_of_tag
>
tag. Everything in between—even other elements—is part of
the enclosing element.
The first major element is the <ejb-jar>
element,
which is the root of the document. All the other elements must lie
within this element. Next is the
<enterprise-beans>
element. Every bean
declared in an XML file must be included in this section. This file
describes only the Cabin EJB, but we could define several beans in
one deployment descriptor.
The <entity>
element shows that the beans
defined within this tag are entity beans. Similarly, a
<session>
element describes session beans;
since the Cabin EJB is an entity bean, we don’t need
a <session>
element. In addition to a
description, the <entity>
element provides
the fully qualified class names of the remote interface, home
interface, bean class, and primary key. The
<cmp-field>
elements list all the
container-managed fields in the entity bean class. These are the
fields that will persist in the database and be managed by the
container at runtime. The <entity>
element
also includes a <reentrant>
element that can
be set as True
or False
depending on whether the bean allows reentrant loopbacks or not.
The deployment descriptor also specifies the
<security-identity>
as
<use-caller-identity/>
, which simply means
the bean propagates the calling client’s security
identity when it accesses resources or other beans. Security
identities are covered in Chapter 3.
The section of the XML file after the
<enterprise-beans>
element is enclosed by
the <assembly-descriptor>
element, which
describes the
security
roles and
transaction
attributes of the bean. In this example, this section of the XML file
is the same for both EJB 2.1 and EJB 2.0:
<ejb-jar ...> <enterprise-beans> ... </enterprise-beans> <assembly-descriptor> <security-role> <description> This role represents everyone who is allowed full access to the Cabin EJB. </description> <role-name>everyone</role-name> </security-role> <method-permission> <role-name>everyone</role-name> <method> <ejb-name>CabinEJB</ejb-name> <method-name>*</method-name> </method> </method-permission> <container-transaction> <method> <ejb-name>CabinEJB</ejb-name> <method-name>*</method-name> </method> <trans-attribute>Required</trans-attribute> </container-transaction> </assembly-descriptor> </ejb-jar>
It may seem odd to separate the
<assembly-descriptor>
information from the
<enterprise-beans>
information, since it
clearly applies to the Cabin EJB, but in the scheme of things,
it’s perfectly natural. A single XML deployment
descriptor can describe several beans, which might all rely on the
same security roles and transaction attributes. To make it easier to
deploy several beans together, this common information is grouped in
the <assembly-descriptor>
element.
There is another (perhaps more important) reason for separating
information about the bean itself from the security roles and
transaction attributes. Enterprise JavaBeans defines the
responsibilities of different participants in the development and
deployment of beans. We don’t address these
development roles in this book because they are not critical to
learning the fundamentals of EJB. For now, it’s
enough to know that the person who develops the beans and the person
who assembles the beans into an application have separate
responsibilities and therefore deal with separate parts of the XML
deployment descriptor. The bean developer is responsible for
everything within the <enterprise-beans>
element; the bean assembler is responsible for everything within the
<assembly-descriptor>
. Throughout this book
you will play both roles, developing the beans and assembling them.
Other roles you will fill are that of the deployer, who actually
loads the enterprise beans into the EJB container, and the
administrator, who is responsible for tuning the EJB server and
managing it at runtime. In real projects, these roles may be filled
by an individual, several different individuals, or even teams.
The <assembly-descriptor>
contains the
<security-role>
elements and their
corresponding <method-permission>
elements.
In this example, there is one security role,
everyone
, which is mapped to all the methods in
the Cabin EJB using the <method-permission>
element. (The *
in the
<method-name>
element means
“all methods.”)
The <container-transaction>
element declares
that all the methods of the Cabin EJB have a
Required
transaction attribute, which means that
all the methods must be executed within a transaction. Transaction
attributes are explained in more detail in Chapter 14. The deployment descriptor ends with the
closing tag of the <ejb-jar>
element.
Copy the Cabin EJB’s deployment descriptor into the META-INF directory and save it as ejb-jar.xml. You have now created all the files you need to package your Cabin EJB. Figure 4-2 shows all the files that should be in the dev directory.
The JAR file is a platform-independent file format for compressing, packaging, and delivering several files together. Based on the ZIP file format and the ZLIB compression standards, the JAR (Java archive) tool and packages were originally developed to make downloads of Java applets more efficient. As a packaging mechanism, however, the JAR file format is a very convenient way to “shrink-wrap” components and other software for delivery to third parties. In EJB development, a JAR file packages all the classes and interfaces associated with a bean, including the deployment descriptor, into one file.
Creating the JAR file for deployment is easy. Position yourself in the dev directory that is just above the com/titan/cabin directory tree, and execute the following command:
\dev % jar cf cabin.jar com/titan/cabin/*.class META-INF/ejb-jar.xml F:\..\dev>jar cf cabin.jar com\titan\cabin\*.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 c option tells the
jar utility to create a new JAR file that
contains the files indicated in subsequent parameters. It also tells
the jar utility to stream the resulting JAR file
to standard output. The f option tells
jar to redirect the standard output to a new
file named in the second parameter (cabin.jar).
It’s important to get the order of the option
letters and the command-line parameters to match. You can learn more
about the jar utility and the
java.util.zip
package in Java in a
Nutshell by David Flanagan, or Learning
Java by Pat Niemeyer and Jonathan Knudsen (both published
by O’Reilly).
The jar utility creates the file cabin.jar in the dev directory. If you’re interested in looking at the contents of the JAR file, you can use any standard ZIP application (WinZip, PKZIP, etc.), or you can use the command jar tvf cabin.jar.
One of the
primary jobs of a deployment tool is mapping entity beans to
databases. In the case of the Cabin EJB, we must map its
id
, name
,
deckLevel
, shipId
, and
bedCount
container-managed fields to some data
source. Before proceeding with deployment, you need to set up a
database and create a CABIN
table. You can use the
following standard SQL statement to create a CABIN
table that will be consistent with the examples provided in this
chapter:
create table CABIN ( ID int primary key NOT NULL, SHIP_ID int, BED_COUNT int, NAME char(30), DECK_LEVEL int )
This statement creates a CABIN
table that has five
columns corresponding to the container-managed fields in the
CabinBean
class. Once the table is created and
connectivity to the database is confirmed, you can proceed with the
deployment process.
Deployment is the process of reading the bean’s JAR file, changing or adding properties to the deployment descriptor, mapping the bean to the database, defining access control in the security domain, and generating any vendor-specific classes needed to support the bean in the EJB environment. Every EJB server product has its own deployment tools, which may provide a graphical user interface, a set of command-line programs, or both. Graphical deployment wizards are the easiest deployment tools to use.
A deployment tool reads the JAR file and looks for ejb-jar.xml. In a graphical deployment wizard, the deployment descriptor elements are presented using a set of property sheets similar to those used in environments such as VisualBasic.NET, PowerBuilder, and JBuilder. Figure 4-3 shows the deployment wizard for the J2EE 1.3 SDK (Reference Implementation) server.
The J2EE Reference Implementation’s deployment
wizard has fields and panels that match the XML deployment
descriptor. You can map security roles to user groups, set the JNDI
lookup name, map the container-managed fields to the database, etc.
EJB deployment tools provide varying degrees of support for mapping
container-managed fields to a data source. Some provide sophisticated
graphical user interfaces, while others are simpler and less
flexible. Fortunately, mapping the
CabinBean
’s container-managed
fields to the CABIN
table is a fairly
straightforward process. The documentation for your
vendor’s deployment tool will show you how to create
this mapping. Once you have finished the mapping, you can complete
the deployment of the Cabin EJB and prepare to access it from the EJB
server.
Now
that the Cabin EJB has been deployed, we want to access it from a
remote client. In this section, we create a remote client that
connects to the EJB server, locates the EJB remote home for the Cabin
EJB, and creates and interacts with several Cabin EJBs. The following
code shows a Java application that creates a new Cabin EJB, sets its
name
, deckLevel
,
shipId
, and bedCount
properties, and then locates it again using its primary key:
package com.titan.cabin; import com.titan.cabin.CabinHomeRemote; import com.titan.cabin.CabinRemote; import javax.naming.InitialContext; import javax.naming.Context; import javax.naming.NamingException; import java.rmi.RemoteException; import java.util.Properties; import javax.rmi.PortableRemoteObject; public class Client_1 { public static void main(String [] args) { try { Context jndiContext = getInitialContext( ); Object ref = jndiContext.lookup("CabinHomeRemote"); CabinHomeRemote home = (CabinHomeRemote) PortableRemoteObject.narrow(ref,CabinHomeRemote.class); CabinRemote cabin_1 = home.create(new Integer(1)); cabin_1.setName("Master Suite"); cabin_1.setDeckLevel(1); cabin_1.setShipId(1); cabin_1.setBedCount(3); Integer pk = new Integer(1); CabinRemote cabin_2 = home.findByPrimaryKey(pk); System.out.println(cabin_2.getName( )); System.out.println(cabin_2.getDeckLevel( )); System.out.println(cabin_2.getShipId( )); System.out.println(cabin_2.getBedCount( )); } catch (java.rmi.RemoteException re){re.printStackTrace( );} catch (javax.naming.NamingException ne){ne.printStackTrace( );} catch (javax.ejb.CreateException ce){ce.printStackTrace( );} catch (javax.ejb.FinderException fe){fe.printStackTrace( );} } public static Context getInitialContext( ) throws javax.naming.NamingException { Properties p = new Properties( ); // ... Specify the JNDI properties specific to the vendor. return new javax.naming.InitialContext(p); } }
To access an enterprise bean, a
client starts by using JNDI to obtain a directory connection to a
bean’s container. JNDI is an
implementation-independent API for directory and naming systems.
Every EJB vendor must provide a directory service that is
JNDI-compliant. This means that they must provide a JNDI service
provider, which is a piece of software analogous to a driver in JDBC.
Different service providers connect to different directory
services—not unlike JDBC, where different drivers connect to
different relational databases. The
getInitialContext( )
method uses JNDI to obtain a network connection to the EJB server.
The code used to obtain the JNDI Context
depends
on which EJB vendor you use. Consult your vendor’s
documentation to find out how to obtain a JNDI
Context
appropriate to your product. For example,
the code used to obtain a JNDI Context
in
WebSphere might look something like the following:
public static Context getInitialContext( ) throws javax.naming.NamingException { java.util.Properties properties = new java.util.Properties( ); properties.put(javax.naming.Context.PROVIDER_URL, "iiop:///"); properties.put(javax.naming.Context.INITIAL_CONTEXT_FACTORY, "com.ibm.ejs.ns.jndi.CNInitialContextFactory"); return new InitialContext(properties); }
The same method developed for BEA’s WebLogic Server would be different:
public static Context getInitialContext( ) throws javax.naming.NamingException { Properties p = new Properties( ); p.put(Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory"); p.put(Context.PROVIDER_URL, "t3://localhost:7001"); return new javax.naming.InitialContext(p); }
Once a JNDI connection is established and a context is obtained from
the getInitialContext( )
method, the context can
be used to look up the EJB home of the Cabin EJB.
Object ref = jndiContext.lookup("CabinHomeRemote");
Throughout this book, we’ll use lookup names like “CabinHomeRemote” for remote client applications. The actual name you use to do a lookup may be different, depending on the requirements of your vendor. You will need to bind a lookup name to the EJB server’s naming service, and some vendors may require a special directory path.
If you are using a standard J2EE component (Servlet, JSP, EJB,
or J2EE Application Client), you will not need to set the properties
explicitly when creating a JNDI InitialContext
, no
matter which EJB vendor you are using. That’s
because the
JNDI
properties can be configured at deployment time and are applied
automatically. A J2EE component would obtain its
InitialContext
as follows:
public static Context getInitialContext( ) throws javax.naming.NamingException { return new javax.naming.InitialContext( ); }
This is simpler and more portable than configuring JNDI properties
for simple Java clients. All J2EE components use the same JNDI naming
system that enterprise beans use to lookup any service. Specifically,
they require that EJB references be bound to the
java:comp/env/ejb/
namespace. For example, for a
J2EE component, here’s all we need to look up the
Cabin EJB:
Object ref = jndiContext.lookup("java:comp/env/ejb/CabinHomeRemote");
At deployment time you would use the vendor’s deployment tools to map that JNDI name to the Cabin EJB’s home. In this book, Java client applictions will need to use explicit parameters for JNDI lookups. As an alternative you could use a special J2EE component called a J2EE Application Client, but this type of component is outside the scope of this book. For more information about J2EE Application Client components consult the J2EE 1.3 (for EJB 2.0) or the J2EE 1.4 specifications.
The Client_1
application uses the
PortableRemoteObject.narrow( )
method to narrow
the Object ref
to a
CabinHomeRemote
reference:
Object ref = jndiContext.lookup("CabinHomeRemote"); CabinHomeRemote home = (CabinHomeRemote) PortableRemoteObject.narrow(ref,CabinHomeRemote.class);
The PortableRemoteObject.narrow( )
method was first introduced in EJB 1.1 and continues to be used on
remote clients in EJB 2.1 and 2.0. It is needed to support the
requirements of RMI over IIOP. Because CORBA supports many
different languages, casting is not native to CORBA (some languages
don’t have casting). Therefore, to get a remote
reference to CabinHomeRemote
, we must explicitly
narrow the object returned from lookup( )
. This
has the same effect as casting and is explained in more detail in
Chapter 5.
The name used to find the Cabin EJB’s EJB home is set by the deployer using a deployment wizard like the one pictured earlier. The JNDI name is entirely up to the person deploying the bean; it can be the same as the bean name set in the XML deployment descriptor, or something completely different.
Once we have a remote reference to the EJB home, we can use it to create a new Cabin entity:
CabinRemote cabin_1 = home.create(new Integer(1));
We create a new Cabin
entity using the
create(Integer id)
method defined in the remote
home interface of the Cabin EJB. When this method is invoked, the EJB
home works with the EJB server to create a Cabin EJB, adding its data to the
database. The EJB server creates an EJB object to wrap the Cabin EJB
instance and returns a remote reference to the EJB object. The
cabin_1
variable then contains a remote reference
to the Cabin EJB we just created. We don’t need to
use the PortableRemoteObject.narrow( )
method to
get the EJB object from the home reference, because it was declared
as returning the CabinRemote
type; no casting was
required. We don’t need to explicitly narrow remote
references returned by findByPrimaryKey( )
for the
same reason.
With the remote reference to the EJB object, we can update the
name
, deckLevel
,
shipId
, and bedCount
of the
Cabin EJB:
CabinRemote cabin_1 = home.create(new Integer(1)); cabin_1.setName("Master Suite"); cabin_1.setDeckLevel(1); cabin_1.setShipId(1); cabin_1.setBedCount(3);
Figure 4-4 shows how the relational database table we created should look after this code has been executed. It should contain one record.
A client locates entity beans using the
findByPrimaryKey( )
method in the home interface. To look up
the Cabin bean we just created, we create a primary key of the
correct type—in this case, Integer
. When we
invoke the finder method on the home interface using the primary key,
we get back a remote reference to the EJB object. We can now
interrogate the remote reference returned by
findByPrimaryKey( )
to get the Cabin
EJB’s name
,
deckLevel
, shipId
, and
bedCount
:
Integer pk = new Integer(1); CabinRemote cabin_2 = home.findByPrimaryKey(pk); System.out.println(cabin_2.getName( )); System.out.println(cabin_2.getDeckLevel( )); System.out.println(cabin_2.getShipId( )); System.out.println(cabin_2.getBedCount( ));
We are ready to create and run the Client_1
application. Compile the client application and deploy the Cabin EJB
into the container system (see the JBoss Workbook section of this
book, Exercise 4.1). Then run the Client_1
application. The output should look something like this:
Master Suite 1 1 3
Congratulations! You just created and used your first entity bean. Of
course, the client application doesn’t do much.
Before going on to create session beans, create another client that
adds some test data to the database. Here we’ll
create Client_2
, which is a modification of
Client_1
that populates the database with a large
number of cabins for three different ships:
package com.titan.cabin; 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_2 { public static void main(String [] args) { try { Context jndiContext = getInitialContext( ); Object ref = jndiContext.lookup("CabinHomeRemote"); CabinHomeRemote home = (CabinHomeRemote) PortableRemoteObject.narrow(ref,CabinHomeRemote.class); // Add 9 cabins to deck 1 of ship 1. makeCabins(home, 2, 10, 1, 1); // Add 10 cabins to deck 2 of ship 1. makeCabins(home, 11, 20, 2, 1); // Add 10 cabins to deck 3 of ship 1. makeCabins(home, 21, 30, 3, 1); // Add 10 cabins to deck 1 of ship 2. makeCabins(home, 31, 40, 1, 2); // Add 10 cabins to deck 2 of ship 2. makeCabins(home, 41, 50, 2, 2); // Add 10 cabins to deck 3 of ship 2. makeCabins(home, 51, 60, 3, 2); // Add 10 cabins to deck 1 of ship 3. makeCabins(home, 61, 70, 1, 3); // Add 10 cabins to deck 2 of ship 3. makeCabins(home, 71, 80, 2, 3); // Add 10 cabins to deck 3 of ship 3. makeCabins(home, 81, 90, 3, 3); // Add 10 cabins to deck 4 of ship 3. makeCabins(home, 91, 100, 4, 3); for (int i = 1; i <= 100; i++){ Integer pk = new Integer(i); CabinRemote cabin = home.findByPrimaryKey(pk); System.out.println("PK = "+i+", Ship = "+cabin.getShipId( ) + ", Deck = "+cabin.getDeckLevel( ) + ", BedCount = "+cabin.getBedCount( ) + ", Name = "+cabin.getName( )); } } catch (java.rmi.RemoteException re) {re.printStackTrace( );} catch (javax.naming.NamingException ne) {ne.printStackTrace( );} catch (javax.ejb.CreateException ce) {ce.printStackTrace( );} catch (javax.ejb.FinderException fe) {fe.printStackTrace( );} } public static 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 static void makeCabins(CabinHomeRemote home, int fromId, int toId, int deckLevel, int shipNumber) throws RemoteException, CreateException { int bc = 3; for (int i = fromId; i <= toId; i++) { CabinRemote cabin = home.create(new Integer(i)); int suiteNumber = deckLevel*100+(i-fromId); cabin.setName("Suite "+suiteNumber); cabin.setDeckLevel(deckLevel); bc = (bc==3)?2:3; cabin.setBedCount(bc); cabin.setShipId(shipNumber); } } }
Create and run the Client_2
application against
the Cabin EJB we deployed earlier. Client_2
lists
all the Cabin EJBs it added to the database:
PK = 1, Ship = 1, Deck = 1, BedCount = 3, Name = Master Suite PK = 2, Ship = 1, Deck = 1, BedCount = 2, Name = Suite 100 PK = 3, Ship = 1, Deck = 1, BedCount = 3, Name = Suite 101 PK = 4, Ship = 1, Deck = 1, BedCount = 2, Name = Suite 102 PK = 5, Ship = 1, Deck = 1, BedCount = 3, Name = Suite 103 PK = 6, Ship = 1, Deck = 1, BedCount = 2, Name = Suite 104 PK = 7, Ship = 1, Deck = 1, BedCount = 3, Name = Suite 105 ...
We now have 100 cabin records in our CABIN
table,
representing 100 cabin entities in our EJB system. This amount
provides a good set of test data for the session bean we will create
in the next section, and for subsequent examples throughout the book.
Get Enterprise JavaBeans, Fourth 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.