There seems to be 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 the enterprise bean’s remote interface. The remote interface defines the enterprise 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:
the name
, deckLevel
,
shipId
, and bedCount
.
Properties
are attributes of an enterprise bean that can be accessed by public
set and get methods. The methods that access these properties are not
explicitly defined in the CabinRemote
interface,
but the interface clearly specifies that these attributes are
readable and changeable by a client.
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.[19] 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
somewhere 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 the
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 complete client-side API for creating, locating,
removing, and using 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.
Throughout this book we will show both the EJB 2.0 and EJB 1.1 code when they are different. In many cases the component interfaces are the same, but the bean class code and XML deployment descriptors will be different. This is the case with the Cabin EJB, so two code listings are shown for the bean class: the first is for EJB 2.0, and the second is for EJB 1.1. EJB 2.0 readers should ignore the EJB 1.1 listing and EJB 1.1 readers should ignore the EJB 2.0 code listing.
Here is the complete definition of the CabinBean
class in EJB 2.0:
package com.titan.cabin; import javax.ejb.EntityContext; public abstract class CabinBean implements javax.ejb.EntityBean { public Integer ejbCreate(Integer id){ this.setId(id); returns null; } public void ejbPostCreate(Integer id){ // Not implemented. } 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) { // Not implemented. } public void unsetEntityContext() { // Not implemented. } public void ejbActivate() { // Not implemented. } public void ejbPassivate() { // Not implemented. } public void ejbLoad() { // Not implemented. } public void ejbStore() { // Not implemented. } public void ejbRemove() { // Not implemented. } }
The CabinBean
class can be divided into four
sections for discussion: declarations for the container-managed
fields, the
ejbCreate()
/ejbPostCreate()
methods, the callback methods, and the remote-interface
implementations.
The CabinBean
defines several abstract accessor
methods that appear in pairs. 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.
It’s customary in EJB 2.0 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
virtual
CMP 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 at runtime causes the
container to delegate that call to the corresponding
getBedCount()
method on the
CabinBean
instance. Unlike the matching methods in
the remote interface, the abstract accessor methods do not throw
RemoteException
s.
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 because you don’t want client
applications to be able to change that key.
Here is the complete definition of the CabinBean
class in EJB 1.1:
package com.titan.cabin; import javax.ejb.EntityContext; public class CabinBean implements javax.ejb.EntityBean { public Integer id; public String name; public int deckLevel; public int shipId; public int bedCount; public Integer ejbCreate(Integer id) { this.id = id; return null; } public void ejbPostCreate(Integer id) { // Do nothing. Required. } public String getName() { return name; } public void setName(String str) { name = str; } public int getShipId() { return shipId; } public void setShipId(int sp) { shipId = sp; } public int getBedCount() { return bedCount; } public void setBedCount(int bc) { bedCount = bc; } public int getDeckLevel() { return deckLevel; } public void setDeckLevel(int level ) { deckLevel = level; } public void setEntityContext(EntityContext ctx) { // Not implemented. } public void unsetEntityContext() { // Not implemented. } public void ejbActivate() { // Not implemented. } public void ejbPassivate() { // Not implemented. } public void ejbLoad() { // Not implemented. } public void ejbStore() { // Not implemented. } public void ejbRemove() { // Not implemented. } }
Declared fields in a bean class can be
persistent
fields or
property
fields. These categories are not
mutually exclusive. Persistent field declarations describe the fields
that will be mapped to the database. A persistent field is often a
property (in the JavaBeans sense): any attribute that is available
using public set and get methods. Of course, a bean can have any
fields that it needs; they need not all be persistent, or properties.
Fields that aren’t persistent won’t be saved in the
database. In CabinBean
, all the fields are
persistent.
The id
field is persistent, but it is not a
property. In other words, id
is mapped to the
database but cannot be accessed through the remote interface.
The name
, deckLevel
,
shipId
, and bedCount
fields are
also persistent fields. They will be mapped to the database at
deployment time. These fields are also properties, because they are
publicly available through the remote interface.
In the case of the Cabin EJB, there was
only one create()
method, so there is only one
corresponding ejbCreate()
method and one
ejbPostCreate()
method. 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 name
field.
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
? Simply put, this convention makes it easier
for a bean-managed enterprise bean (i.e., an enterprise bean that
explicitly manages its own persistence) to extend a container-managed
enterprise bean. This functionality is valuable for EJB vendors who
support container-managed persistence beans by extending them with
bean-managed persistence bean implementations—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 a 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.[20] 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 for every example in this book has already been created and is available from the download site.
The following sections offer a quick peek at the EJB 2.0 and 1.1 deployment descriptors for the Cabin EJB, so you can get a feel for how an XML deployment descriptor is structured and the type of information it contains.
In EJB 2.0, the deployment descriptor 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> <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>
In EJB 1.1, the 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> <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> <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> </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. One important distinction between EJB 2.0
and EJB 1.1 is that they use different DTDs for deployment
descriptors. EJB 2.0 specifies the
ejb-jar_2_0.dtd
, while EJB 1.1 specifies the
ejb-jar_1_1.dtd
.
The rest of the XML elements are nested one within another and are
delimited by beginning and ending tags. The structure is not
complicated. If you have done any HTML coding, you should 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.
EJB 2.0 specifies a name
, which is used in EJB QL
(Query Language) to identify the entity bean. This isn’t
important right now. The 2.0 deployment descriptor also specifies the
<security-identity>
as
<use-caller-identity/>
, which simply means
the bean will propagate the calling client’s security identity
when it accesses resources or other beans. This was covered in detail
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.
This section of the XML file is the same for both EJB 2.0 and EJB 1.1
in this example:
<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, all 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 is the person
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, all these roles may be
filled by one or two people, or by several different individuals or
even teams.
The <assembly-descriptor>
contains the
<security-role>
elements and
their
corresponding
<method-permission>
elements, which were described in Chapter 3 under Section 3.2.7. 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.”) As already mentioned, for
EJB 2.0 you’ll have to specify a security identity; in this
case, it’s the caller’s identity.
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 same
directory as the class files for the Cabin EJB files
(Cabin.class
,
CabinHome.class
,
CabinBean.class
, and
CabinPK.class
) 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
cabin
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. The original JavaBeans component architecture depends on JAR files for packaging, as does Enterprise JavaBeans. The goal in using the JAR file format in EJB is to package 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
thecom/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 the
ejb-jar.xml
file. In a graphical deployment
wizard, the deployment descriptor elements are presented in a set of
property sheets similar to those used to customize visual components
in environments such as Visual Basic, PowerBuilder, JBuilder, and
Symantec Café. Figure 4-3 shows the
deployment wizard used in 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.
Different EJB deployment tools provide varying degrees of support for
mapping container-managed fields to a data source. Some provide very
robust and 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
in the EJB server, we want to access it from a remote client. When we
say remote, we are usually talking about either a client application
that is located on a different computer or a different process on the
same computer. In this section, we will create a remote client that
will connect to the EJB server, locate the EJB remote home for the
Cabin EJB, and create and interact with several Cabin EJBs. The
following code shows a Java application that is designed to create a
new Cabin EJB, set its name
,
deckLevel
, shipId
, and
bedCount
properties, and then locate 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 the JNDI package 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 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
contains logic that uses JNDI to obtain a network connection to the
EJB server.
The code used to obtain the JNDI Context
differs
depending 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.
The Client_1
application uses the
PortableRemoteObject.narrow()
method:
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.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 then creates an EJB object to wrap
the Cabin EJB instance and returns a remote reference to the EJB
object to the client. 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 executing this code. It should contain one record.
After an entity bean has been created, a client can locate it using
the findByPrimaryKey()
method in the home
interface. First, 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());
You are now ready to create and run the Client_1
application against the Cabin EJB you deployed earlier. Compile the
client application and deploy the Cabin EJB into the container
system. Then run the Client_1
application.
Please refer to Workbook Exercise 4.1, A Simple Entity Bean. This workbook is available free, in PDF format, at http://www.oreilly.com/catalog/entjbeans3/workbooks.
When you run the Client_1
application, your output
should look something like the following:
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
, as 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 you deployed earlier. Client_2
produces a lot of output that lists all the new Cabin EJBs you just
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 ...
You now have 100 cabin records in your CABIN
table, representing 100 cabin entities in your EJB system. This
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.
[19] The examples, which can be downloaded from http://www.oreilly.com/catalog/entjbeans3/, provide a good guide for how to organize your code. The code is organized in a directory structure that’s typical for most products. The workbooks provide additional help for organizing your development projects and will point out any vendor-specific requirements.
[20] The workbooks show you how to use the vendors’ tools for creating deployment descriptors.
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.