The beans contained in a JAR file are
described within the deployment
descriptor’s enterprise-beans
element. So
far, we’ve only talked about deployment descriptors for a
single bean, but there’s no reason that you can’t package
several beans in a JAR file and describe them all
within a single deployment descriptor. We could, for example, have
deployed the TravelAgent, ProcessPayment, Cruise, Customer, and
Reservation beans in the same JAR file. The deployment descriptor
would look something like this:
<?xml version="1.0"?> <!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> <description> This Deployment includes all the beans needed to make a reservation: TravelAgent, ProcessPayment, Reservation, Customer, Cruise, and Cabin. </description> <enterprise-beans> <session> <ejb-name>TravelAgentBean
</ejb-name> <remote>com.titan.travelagent.TravelAgent</remote> ... </session> <entity> <ejb-name>CustomerBean
</ejb-name> <remote>com.titan.customer.Customer</remote> ... </entity> <session> <ejb-name>ProcessPaymentBean
</ejb-name> <remote>com.titan.processpayment.ProcessPayment</remote> ... </session> ... </enterprise-beans> <assembly-descriptor> ... </assembly-descriptor> ... </ejb-jar>
In this descriptor, the enterprise-beans
element
contains two session
elements and one
entity
element describing the three beans.
Other elements within the entity
and
session
elements provide detailed information
about the beans; as you can see, the
ejb-name
element defines the bean’s
name. We’ll discuss all of the things that can go into a
bean’s description later.
Multiple bean deployments have the advantage that they can share
assembly information, which is defined in the
assembly-descriptor
element that follows the
enterprise-beans
element. In other words, beans
can share
security and transactional
declarations, making it simpler to deploy them consistently. For
example, deployment is easier if the same logical security roles
control access to all the beans, and it’s easiest to guarantee
that the roles are defined consistently if they are defined in one
place. It’s also easier to ensure that the transactional
attributes are applied consistently to all beans because you can
declare them all at the same time.
The
session
and entity
elements,
which are used to describe session and entity beans, usually contain
many elements nested within them, but the lists of allowable
subelements are similar. Therefore, we’ll discuss the
session
and entity
elements
together.
Like the ebj-jar
element itself, a
session
or an entity
element
can optionally have
description
, display-name
,
small-icon
, and large-icon
elements. These are fairly self-explanatory and, in any case, mean
the same as they did for the ejb-jar
element. The
description
lets you provide a comment that
describes the bean; the display-name
is used by
deployment tools to represent the bean; and the two icons are used to
represent the bean in visual environments. The icons must point to
JPEG or GIF images within the JAR file.
The other elements are more interesting:
<ejb-name>
(one required)This is the name of the bean component. It is used in the
methodx
element to scope method declarations to
the correct bean. Throughout this book, we use
ejb-name
s of the form
"Name Bean” as the
ejb-name
for bean. Other common conventions use
the ejb-name
s of the form "Name
EJB” or “The Name.”
<home>
(one required)This is the fully qualified class name of the bean’s home interface.
<remote>
(one required)This is the fully qualified class name of the bean’s remote interface.
<ejb-class>
(one required)This is the fully qualified class name of the bean class.
<primkey-field>
(optional; entity beans only)This element is used to specify the primary key field for entity beans that use container-managed persistence. Its value is the name of the field that is used as the primary key. It is not used if the bean has a custom primary key or if the entity bean manages its own persistence. This element is discussed in more detail in Section 10.5.2, later in this chapter.
<prim-key-class>
(one required; entity beans only)This element specifies the class of the primary key for entity beans.
Its value is the fully qualified name of the primary key class; it
makes no difference whether you’re using a custom compound
primary key like the CabinPK
, or a simple
primkey-field
like an Integer
,
String
, Date
, etc. If you defer
definition of the primary key class to the deployer, specify the type
as java.lang.Object
in this element.
<persistence-type>
(one required; entity beans only)The
persistence-type
element declares that the entity bean
uses either container-managed persistence or bean-managed
persistence. This element can have one of two values:
Container
or Bean
.
<reentrant>
(one required; entity beans only)The reentrant
element declares that the bean either
allows loopbacks (reentrant invocations)
or not. This element can have one of two values:
True
or False
.
True
means that the bean allows loopbacks;
False
means that the bean throws an exception if a
loopback occurs.
<cmp-field>
(zero or more; entity beans only)This element is used in entity beans with container-managed
persistence. A cmp-field
element must exist for
each container-managed field in the bean class. Each
cmp-field
element may include a
description
element and must include a
field-name
element. The description is an optional
comment describing the field. The field-name
is
required and must be the name of one of the bean’s fields. The
container will manage persistence for the given field. The following
portion of a descriptor shows several cmp-field
declarations for the Cabin bean:
<cmp-field> <description>This is the primary key</description> <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>ship</field-name> </cmp-field> <cmp-field> <field-name>bedCount</field-name> </cmp-field>
<env-entry>
(zero or more)This element declares an environment entry that is available through the JNDI ENC. The use of environment entries in a bean and a deployment descriptor is discussed further in Section 10.5.3.
<ejb-ref>
(zero or more)This element declares a bean reference that is available through the JNDI ENC. The mechanism for making bean references available through the ENC is described in more detail later, in Section 10.5.4.
<resource-ref>
(zero or more)This element declares a reference to a connection factory that is
available through the JNDI ENC. An example of a resource factory is
the javax.sql.DataSource
, which is used to obtain
a connection to a database. This element is discussed in detail in Section 10.5.4 later in
this chapter.
<security-role-ref>
(zero or more)The
security-role-ref
element is used to declare security
roles in the deployment descriptor, and map them into the security
roles in effect for the bean’s runtime environment. This
element is described in more detail in Section 10.5.6.
<session-type>
(one required; session beans only)The session-type
element declares that a session bean
is either stateful or stateless. This element can have one of two
values: Stateful
or Stateless
.
<transaction-type>
(one required; session beans only)The transaction-type
element declares that a
session bean either manages its own transactions, or that its
transactions are managed by the container. This element can have one
of two values: Bean
or
Container
. A bean that manages its own
transactions will not have container-transaction
declarations in the assembly-descriptor
section of
the
deployment
descriptor.
An entity bean does not always have to use a custom key class as a
primary key. If there’s a
single field in the bean that can serve naturally as a unique
identifier, you can use that field as the primary key without having
to create a custom key. In the Cabin bean, for example, the primary
key type was the CabinPK
, which mapped to the bean
class field id
as shown here (the CabinBean is
using bean-managed persistence to better illustrate):
public class CabinBean implements javax.ejb.EntityBean {
public int id;
public String name;
public int deckLevel;
public int ship;
public int bedCount;
public CabinPK
ejbCreate(int id) {
this.id = id;
return new CabinPk(id);
}
...
}
Instead of using the custom CabinPK
class, we
could have used the appropriate primitive wrapper,
java.lang.Integer
, and defined the CabinBean as:
public class CabinBean implements javax.ejb.EntityBean {
public int id;
public String name;
public int deckLevel;
public int ship;
public int bedCount;
public Integer
ejbCreate(int id){
this.id = id;
return new Integer(id);
}
...
}
This simplifies things a lot. Instead of taking the time to define a
custom primary key like CabinPK
, we simply use the
appropriate wrapper. To do this, we need to add a
primkey-field
element to the Cabin bean’s
deployment descriptor, so that it knows which field to use as the
primary key. We also need to change the
prim-key-class
element to state that the
Integer
class is being used to represent the
primary key. The following code shows how the Cabin bean’s
deployment descriptor would need to change to use
Integer
as the primary key field:
<entity> <description> This Cabin enterprise bean entity represents a cabin on a cruise ship. </description> <ejb-name>CabinBean</ejb-name> <home>com.titan.cabin.CabinHome</home> <remote>com.titan.cabin.Cabin</remote> <ejb-class>com.titan.cabin.CabinBean</ejb-class> <persistence-type>Bean</persistence-type> <prim-key-class>java.lang.Integer</prim-key-class> <primkey-field>id</primkey-field> <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>ship</field-name></cmp-field> <cmp-field><field-name>bedCount</field-name></cmp-field> </entity>
Simple
primary key fields are not limited to the primitive wrapper classes
(Byte
, Boolean
,
Integer
, etc.); any container-managed field can be
used as a primary key as long as it’s serializable.
String
types are probably the most common, but
other types, such as java.lang.StringBuffer
,
java.util.Date
, or even
java.util.Hashtable
are also valid. Custom types
can also be primkey-field
s providing that they are
serializable. Of course, common sense should be used when choosing a
primary key: because it is used as an index to the data in the
database, it should be lightweight. Here’s code for a bean that
uses a Date
as its primary key:
// bean class that uses Date as a primary key
public class HypotheticalBean implements javax.ejb.EntityBean {
public Date creationDate;
...
public Date
ejbCreate() {
creationDate = new Date();
return creationDate;
}
...
}
And here’s the corresponding section of the deployment descriptor:
// primkey-field declaration for the Hypothetical bean ... <entity> <ejb-name>HypotheticalBean</ejb-name> ... <prim-key-class>java.util.Date</prim-key-class> <primkey-field>creationDate</primkey-field> <reentrant>False</reentrant> <cmp-field><field-name>creationDate</field-name></cmp-field> ... </entity>
Throughout the book we use
custom compound primary keys, like
ShipPK
and CabinPK
, instead of
using simple primary keys. This may seem strange because these custom
primary keys only wrap a single field, usually an integer, which
could have been represented by an Integer
and used
as the primkey-field
.
The reason we use custom primary keys is simple: encapsulation. If
the primary key fields of the beans change over time, using a custom
key hides the changes from client applications that use the key. If,
for example, the CabinBean
changed to use both a
String
and a long
primitive as
the primary key fields instead of a single integer field
(id
), the Cabin bean’s custom primary key
class (CabinPK
) would hide this change from the
client application. If, however, we had used a
primkey-field
of
java.lang.Integer
, any client applications that
use the findByPrimaryKey()
method (and other
similar operations involving the key) would have to be modified.
With container-managed persistence, it’s also possible for the bean developer to defer defining the primary key, leaving key definition to the bean deployer. This feature might be needed if, for example, the primary key is generated by the database and is not a container-managed field in the bean class. Containers that have a tight integration with database or legacy systems that automatically generate primary keys might use this approach. It’s also an attractive approach for vendors that sell shrink-wrapped beans because it makes the bean more portable. The following code shows how an entity bean using container-managed persistence defers the definition of the primary key to the deployer:
// bean class for bean that uses a deferred primary key public class HypotheticalBean implements javax.ejb.EntityBean { ... publicjava.lang.Object
ejbCreate(){ ... return null; } ... } // home interface for bean with deferred primary key public interface HypotheticalHome extends javax.ejb.EJBHome { public Hypothetical create() throws ...; public Hypothetical findByPrimaryKey(java.lang.Object key
) throws ...; }
Here’s the relevant portion of the deployment descriptor:
// primkey-field declaration for the Hypothetical bean ... <entity> <ejb-name>HypotheticalBean</ejb-name> ... <persistence-type>Container</persistence-type> <prim-key-class>java.lang.Object</prim-key-class> <reentrant>False</reentrant> <cmp-field><field-name>creationDate</field-name></cmp-field> ... </entity>
Because the primary key is of type
java.lang.Object
, the client application’s
interaction with the bean’s key is limited to the
Object
type and its methods.
A deployment descriptor can define environment entries, which are values similar to properties that the bean can read when it is running. The bean can use environment entries to customize its behavior, find out about how it is deployed, etc.
The env-entry
element is used to define environment
entries. This element contains a description
element (optional), env-entry-name
(required),
env-entry-type
(required), and
env-entry-value
(optional). Here is a typical
env-entry
declaration:
<env-entry> <env-entry-name>minCheckNumber</env-entry-name> <env-entry-type>java.lang.Integer</env-entry-type> <env-entry-value>2000</env-entry-value> </env-entry>
The
env-entry-name
is relative to the
"java:comp/env"
context. For example, the
minCheckNumber
entry can be accessed using the path
"java:comp/env/minCheckNumber"
in a JNDI ENC
lookup:
InitialContext jndiContext = new InitialContext(); Integer miniumValue = (Integer) jndiContext.lookup("java:comp/env/minCheckNumber");
The
env-entry-type
can be of type
String
, or one of several primitive wrapper types
including Integer
, Long
,
Double
, Float
,
Byte
, Boolean
, and
Short
.
The env-entry-value
is optional. The value can be
specified by the bean developer or deferred to the application
assembler or deployer.
The subcontext "java:comp/env/ejb10-properties"
can be used to make an entry available via the
EJBContext.getEnvironment()
method. This feature has been
deprecated, but it may help you deploy EJB 1.0 beans within a EJB 1.1
server. The ejb-entry-type
must always be
java.lang.String
for entries in this subcontext.
Here’s an example:
<env-entry> <description>This property is available through EJBContext.getEnvironment()</description> <env-entry-name>ejb10-properties/minCheckNumber</env-entry-name> <env-entry-type>java.lang.String</env-entry-name> <env-entry-value>20000</env-entry-value> </env-entry>
The env-ref
element is used to define
references to other beans within the
JNDI ENC. This makes it much easier for beans to reference other
beans; they can use JNDI to look up a reference to the home interface
for any beans that they are interested in.
The env-ref
element contains
description
(optional),
ejb-ref-name
(required),
ejb-ref-type
(required), remote
(required), home
(required), and
ejb-link
(optional) elements. Here is a typical
env-ref
declaration:
<ejb-ref> <ejb-ref-name>ejb/CabinHome</ejb-ref-name> <ejb-ref-type>Entity</ejb-ref-type> <home>com.titan.cabin.CabinHome</home> <remote>com.titan.cabin.Cabin</remote> </ejb-ref>
The ejb-ref-name
is relative to the
"java:comp/env"
context. It is recommended, but
not required, that the name be placed under a subcontext of
ejb/
. Following this convention, the path used to
access the Cabin bean’s home would be
"java:comp/env/ejb/CabinHome"
. The following code
shows how a client bean would use this context to look up a
reference to the Cabin bean:
InitialContext jndiContext = new InititalContext(); Object ref = jndiContext.lookup("java:comp/env/ejb/CabinHome"); CabinHome home = (CabinHome) PortableRemoteObject.narrow(ref, CabinHome.class);
The ejb-ref-type
can have one of two values:
Entity
or Session
, according to
whether the bean is an entity or a session bean.
The home
element specifies the fully qualified
class name of the bean’s home interface; the
remote
element specifies the fully qualified class
name of the bean’s remote interface.
If the bean referenced by the ejb-ref
element is
deployed in the same deployment descriptor (it is defined under the
same ejb-jar
element), the
ejb-ref
element can be linked to the bean’s
declaration using the ejb-link
element. If, for
example, the TravelAgent bean uses references to the ProcessPayment
and Customer beans and they are all declared in the same deployment
descriptor, then the ejb-ref
elements for the
TravelAgent bean can use an ejb-link
element to
map its ejb-ref
elements to the ProcessPayment and
Customer beans. The ejb-link
value must match one
of the ejb-name
values declared in the same
deployment descriptor. Here’s a portion of a deployment
descriptor that uses the
ejb-link
element:
<ejb-jar> <enterprise-beans> <session> <ejb-name>TravelAgentBean
</ejb-name> <remote>com.titan.travelagent.TravelAgent</remote> ... <ejb-ref> <ejb-ref-name>ejb/ProcessPaymentHome</ejb-ref-name> <ejb-ref-type>Session</ejb-ref-type> <home>com.titan.processpayment.ProcessPaymentHome</home> <remote>com.titan.processpayment.ProcessPayment</remote> <ejb-link>ProcessPaymentBean</ejb-link> </ejb-ref> <ejb-ref> <ejb-ref-name>ejb/CustomerHome</ejb-ref-name> <ejb-ref-type>Entity</ejb-ref-type> <home>com.titan.customer.CustomerHome</home> <remote>com.titan.customer.Customer</remote> <ejb-link>CustomerBean</ejb-link> </ejb-ref> </session> <entity> <ejb-name>CustomerBean
</ejb-name> <remote>com.titan.customer.Customer</remote> ... </entity> <session> <ejb-name>ProcessPaymentBean
</ejb-name> <remote>com.titan.processpayment.ProcessPayment</remote> ... </session> ... </enterprise-beans> ... </ejb-jar>
Beans
also use the
JNDI ENC to
look up external resources, like database connections, that they need
to access. The mechanism for doing this is similar to the mechanism
used for referencing other beans and environment entries: the
external resources are mapped into a name within the JNDI ENC name
space. For external resources, the mapping is performed by the
resource-ref
element.
The resource-ref
element contains
description
(optional),
res-ref-name
(required),
res-type
(required), and
res-auth
(required) elements. Here is a
resource-ref
declaration used for a
DataSource
connection factory:
<resource-ref>
<description>DataSource for the Titan database</description>
<res-ref-name>jdbc/titanDB
</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
The res-ref-name
is relative to the
"java:comp/env"
context. Although it is not a
requirement, it’s a good idea to place connection factories
under a subcontext that describes the resource type. For example:
jdbc/
for a JDBC DataSource
factory
jms/
for a JMS
QueueConnectionFactory
or a
TopicConnectionFactory
factory
mail/
for a JavaMail Session
factory
url/
for a javax.net.URL
factory
Here is how a bean would use JNDI to look up a resource—in this
case, a DataSource
:
InitialContext jndiContext = new InitialContext(); DataSource source = (DataSource) jndiContext.lookup("java:comp/env/jdbc/titanDB");
The res-type
is used to declare the fully qualified
class name of the connection factory. In this example, the
res-type
is
javax.sql.DataSource
.
The res-auth
tells the server who is responsible for
authentication. It can
have one of two values: Container
or
Application
. If Container
is
specified, authentication (sign-on or login) to use the resource will
be performed automatically by the container as specified at
deployment time. If Application
is specified, the
bean itself must perform the necessary authentication before using
the resource. The following code shows how a bean might sign on to a
connection factory when
Application
is specified for
res-auth
:
InitialContext jndiContext = new InitialContext();
DataSource source = (DataSource)
jndiContext.lookup("java:comp/env/jdbc/titanDB");
String loginName = ejbContext.getCallerPrincipal().getName();
String password = ...; // get password from somewhere
// use login name and password to obtain a database connection
java.sql.Connection con = source.getConnection(loginName, password
);
The
security-role-ref
element is used to define the security
roles that are used by a bean and to map them into the security roles
that are in effect for the runtime environment. It can contain three
subelements: an optional
description
, a
role-name
(required), and an optional
role-link
.
Here’s how security roles are defined. When a role name is used
in the EJBContext.isCallerInRole(String roleName)
method, the role name must be statically defined (it cannot be
derived at runtime) and it must be declared in the deployment
descriptor using the security-role-ref
element:
<-- security-role-ref declaration for Account bean --> <entity> <ejb-name>AccountBean</ejb-name> ... <security-role-ref> <description> The caller must be a member of this role in order to withdraw over $10,000 </description> <role-name>Manager</role-name> <role-link>Administrator</role-link> </security-role-ref> .. </entity>
The role-name
defined in the deployment descriptor
must match the role name used in the
EJBContext.isCallerInRole()
method. Here is how the role name
is used in the bean’s code:
// Account bean uses the isCallerInRole() method
public class AccountBean implements EntityBean {
int id;
double balance;
EntityContext context;
public void withdraw(Double withdraw)
throws AccessDeniedException {
if (withdraw.doubleValue() > 10000) {
boolean isManager = context.isCallerInRole("Manager");
if (!isManager) {
// only Managers can withdraw more than 10k
throw new AccessDeniedException();
}
}
balance = balance - withdraw.doubleValue();
}
...
}
The role-link
element is optional; it can be used
to map the role name used in the bean to a logical role defined in a
security-role
element in the
assembly-descriptor
section of the deployment
descriptor. If no role-link
is specified, the
deployer must map the security-role-ref
to an
existing security role in the target
environment.