Create forward-compatible beans in EJB, Part 1
How to write EJB 1.0 beans to port to EJB 1.1 servers
Enterprise JavaBeans (EJB), which celebrated its one-year anniversary in June 1999, has been cutting its teeth on real-world applications. During EJB’s first year, several areas of improvement were identified, many of which have been incorporated into EJB 1.1. These improvements include mandating support for entity beans, updating the deployment descriptors to an XML-based format, improving the bean-container contract, and tightening the overall specification to reduce ambiguities and loopholes.
- Welcome to the server-side Java series
- Part 2. More key strategies for developing portable EJB 1.0 beans for EJB 1.1 servers
- Create forward-compatible beans in EJB, Part 1
- Understanding JavaServer Pages Model 2 Architecture
Although EJB 1.1 provides a more stable and concrete specification, it falters with some conventions in the EJB 1.0 programming model, introducing forward compatibility problems. Beans developed for the EJB 1.0-compliant server today will not automatically port to EJB 1.1 servers tomorrow. This article provides solutions for these problems. More information on additional changes from EJB 1.0 to EJB 1.1 can be found in Appendix D of my book Enterprise JavaBeans (O’Reilly, 1999), which is posted in HMTL and PDF formats at the O’Reilly Web site (see Resources).
This is the first of two installments about forward compatibility in Enterprise JavaBeans. This installment covers the environment-naming context and the implementation of an abstraction that hides the differences between EJB 1.0 and EJB 1.1 when accessing bean properties, Java Database Connectivity (JDBC), and other beans. The second installment addresses security, changes specific to entity beans, and changes to the deployment descriptor.
This is an advanced-topic article and is not intended for individuals new to Enterprise JavaBeans. Readers should be familiar with Java, Java Naming and Directory Interface (JNDI), JDBC, and the EJB 1.0 specification. In addition, I have simplified the exception handling so that the example code is clear and easy to follow. If you are not yet familiar with these technologies, be sure to see the Resources section for a review.
The environment-naming context
Enterprise JavaBeans 1.0 provides one interface to the bean’s environment, the
EJBContext
. The
EJBContext
provides the bean class with an interface to the container, allowing the bean to discover and interact with aspects of its environment. The interface provides methods concerned with caller identity, transactions, and accessing environment properties.
Enterprise JavaBeans 1.1 introduces a new bean-container interface called the environment-naming context (ENC). The ENC is a JNDI name space that is specific to a bean type and its context at runtime. To simplify the bean-container interface, the ENC is made available, by default, when a JNDI context is created. The JNDI ENC enhances the bean-container contract by adding new functionality, but it doesn’t completely replace the EJBContext
. In EJB 1.1, the JNDI ENC and the EJBContext
together represent the complete bean-container interface.
List 1-A is an example of an EJB 1.0 bean using the EJBContext
to read an environment property used to validate a request (the comparison is applicable to both entity and session beans). List 1-B is an example of how an EJB 1.1 bean would use the new JNDI ENC to obtain an environment property to also validate a request.
|
|
In both EJB 1.0 and EJB 1.1, the value associated with the property name withdraw_limit
is used as a business validation boundary. You can use properties for many things, including validation boundaries and other static values. The advantage of using environment properties is that you can modify the bean’s behavior without having to change its code.
In EJB 1.0, environment properties are limited to String
types and are available through the EJBContext
. In EJB 1.1, environment properties can be a type of String
or any one of the primitive numerical wrappers (Integer
, Long
, Double
, Boolean
, Byte
, and Float
); they are available through a default JNDI context. Why the change? EJB 1.1 wanted to extend the bean-container contract to address many of the issues that would have mandated complicated changes to the EJBContext
interface. To avoid the limitations of EJBContext
— its definition is fixed and therefore limited — the JNDI ENC was introduced, which provides a more dynamic and extensible bean-container interface. The EJBContext
still exists, with some changes, but most of the new EJB 1.1 features are realized through the JNDI ENC.
The JNDI ENC standardizes how resources are obtained and used, so enterprise beans are more flexible and forward compatible. The JNDI ENC is a default JNDI context whose root is distinguished by the java:comp/env
directory, which is always available when an InitialContext
is instantiated within a bean. The JNDI ENC is a very simple, common, and extensible mechanism for making any resource available to the bean at runtime. EJB 1.1 deprecates the EJBContext.getEnvironment()
method (it’s an EJB 1.1 optional feature) and moves the environment properties to the ENC. EJB 1.1 also makes JDBC, JavaMail, URL, and the JMS resource factories available through the ENC. In addition, EJB 1.1 uses JNDI ENC for accessing the EJBHome
remote references of other beans. Access to resources and beans are covered in more detail later in this article. By using JNDI as part of the bean-container interface, future enhancements of the specification will not affect bean portability; instead, they will be added as namable entries to the JNDI ENC name space.
While ENC is a welcome change, it throws a wrench into the forward compatibility of EJB 1.0 beans that access environment properties, resources, and other beans. In other words, beans developed in EJB 1.0 that access those things may not be portable to an EJB 1.1 container. Now, I will present a solution to this problem that will work in any EJB 1.0- and EJB 1.1-compliant container. This solution is portable and automatic: it requires no additional work once a bean is designed to utilize it.
The PortableContext
As any good object-oriented developer will tell you, always encapsulate that which varies. In other words, abstract the code that will change across permutations (versions, implementations, technologies, and so forth). In the case of EJB, you need to encapsulate changes made in the bean-container interface between EJB 1.0 and EJB 1.1. Those changes include access to environment properties, resources, and other beans. The abstraction developed to solve these forward compatibility problems is called the
PortableContext
.
The environment properties
The simplest change that you can encapsulate is the mechanism by which environment properties are obtained. In EJB 1.0, they are obtained from the
EJBContext
(as shown in List 1-A). In EJB 1.1, they can be obtained using the same mechanism, which has been deprecated, or through the JNDI environment context (as shown in List 1-B).
Support for obtaining environment properties via the EJBContext.getEnvironment()
method is optional for EJB 1.1 servers, so some vendors will maintain this mechanism while others will not — the latter will instead deprecate the EJBContext.getEnvironment()
method. To avoid dependence on the EJBContext.getEnvironment()
method, we will provide the enterprise beans with the PortableContext
, which abstracts the mechanism for obtaining environment variables. List 2 shows the abstract class that defines our PortableContext
.
|
This is the abstraction that enterprise beans will use in both EJB 1.0 and EJB 1.1 containers. The concrete implementation of the abstraction is different in EJB 1.0 than in EJB 1.1. These differences are illustrated in Lists 3-A and 3-B.
|
|
Each concrete implementation of the abstract PortableContext
obtains the environment properties differently, but the behavior is the same from the bean developer’s perspective, because the details of the implementation are hidden. The bean developer is concerned only with the behavior described by the PortableContext
type.
One of the interesting things about the PortableContext.lookup()
method is that it is designed to return the EJB 1.1 range of types instead of being limited to EJB 1.0 types as you might expect. The PortableContext1_0
implementation is enhanced to allow primitive wrappers as well as simple String
types. Of course, the enhancement requires a little more discipline since you must pass in the correct Class
type parameter in the lookup()
method call; however, it allows the EJB 1.0 beans to take advantage of EJB 1.1’s more advanced feature: support for primitive wrappers as properties. It’s not just forward compatible, it’s forward featured.
In List 4, the withdraw()
method of the AccountBean
from List 1 is rewritten to use the new PortableContext
class.
|
Here we use the EJB 1.1 naming conventions in our forward-compatible bean, which will work in EJB 1.0 if we simply use these conventions to name the properties. This ensures that neither the EJB 1.0 nor EJB 1.1 concrete implementations requires name translations to look up a property.
The Class
parameter passed with every method invocation may seem a little clumsy at first, but it actually proves useful as we increase the functionality of the PortableContext
later in this article.
PortableContext as a factory
A big hurdle in this strategy is figuring out how to get the EJB 1.0 beans to use the
PortableContext1_0
implementation while the EJB 1.1 beans use the
PortableContext1_1
implementation. We could explicitly instantiate the correct implementation within the bean code, but this would require code changes when the bean is upgraded from an EJB 1.0 to an EJB 1.1 container. To avoid having to change the bean code when porting the bean, we give the
PortableContext
a factory method, which automatically chooses the correct concrete implementation at runtime.
List 5 shows a new static method, getInstance()
, in PortableContext
. The getInstance()
method automatically chooses the correct implementation based on the system property java.ejb.portable_context
, which is set to the fully qualified class name of the appropriate concrete implementation. With an EJB 1.0 server, the java.ejb.portable_context
property is set to PortableContext1_0
; if it’s an EJB 1.1 server, the system property is set to PortableContext1_1
. Once the class is loaded using Class.forName()
, the proper concrete implementation can be instantiated and initiated with the EJBContext
. Below, in List 5, is the static method that is added to the PortableContext
class.
|
While the foregoing algorithm for dynamically loading the concrete implementation works in most servers, access to system properties and dynamic class loading may not be universally supported. In these cases, hard coding the instantiation of the proper concrete implementation into the getInstance()
method is suitable.
You can use the PortableContext
factory in the bean code so that it hides the concrete implementation. Unfortunately, you must reset the EJBContext
used by the PortableContext
in a couple of different areas, namely in the setEntityContext()
or setSessionContext()
method and in the ejbActivate()
method. That is because of the specified life cycles of the different bean types and the latitude given to EJB vendors on EJBContext
preservation through activation in EJB 1.0. By resetting the EJBContext
, you ensure that it is always current. List 6 shows how this would work in an enterprise bean. The strategy would be the same in session and entity beans.
|
|
JDBC and other resource connections
Beans, including session and entity beans, frequently use JDBC to access relational databases. In EJB 1.0, the mechanism for obtaining a JDBC connection was not specified, so vendors had flexibility in how connections were accessed. Most vendors supported the standard use of
DriverManager.getConnection()
to obtain either a pooled JDBC driver or an exclusive connection. EJB 1.1 changes how JDBC connections are accessed by providing a standard mechanism for obtaining a JDBC connection factory (
javax.sql.DataSource
) through the JNDI ENC.
To make an EJB 1.0 bean forward compatible, it is necessary to encapsulate these differences, which you can conveniently do in PortableContext
. In Lists 7-A and 7-B, the lookup()
method in the two concrete implementations have been modified to provide access to JDBC connections.
|
|
In the PortableContext1_0
implementation, the name
variable is used to obtain the JDBC URL from the environment properties. The JDBC URL is used in the DriverManager.getConnection()
to obtain the JDBC connection. In the PortableContext1_1
context, the name
is used to look up the JDBC factory (javax.sql.DataSource
), which is in turn used to get the connection.
Below is an example of how the PortableContext
would be used to get a database connection in a forward-compatible enterprise bean.
|
EJB 1.1 recommends, but does not require, that the JDBC DataSource
be mapped to the java:comp/env/jdbc
directory name. This naming scheme is adopted on the PortableContext
for consistency and forward compatibility.
The EJB 1.1 specification also supports the use of the JNDI ENC to access factories for other resources like the Java Messaging Service (JMS), Java Mail, and URL with appropriate JNDI names (for example, java:comp/env/jms
for the JMS). You can enhance PortableContext
to support the use of these resources as well — an exercise I will leave up to you.
Bean references
Frequently beans use references to other beans to accomplish tasks. EJB 1.0 and EJB 1.1 differ in how references to the bean homes are obtained. EJB 1.0 relies on the conventional use of JNDI, just like a client application, to obtain a remote reference to a bean home. EJB 1.1 allows bean homes to be included in the JNDI ENC, which gives the deployer more control over the network location of beans and how they are accessed. Those differences can also be encapsulated (hidden) in the
PortableContext
, as illustrated below.
|
|
With the EJB 1.0 implementation (PortableContext1_0
), the bean home is obtained using conventional JNDI access with security authentication and loading of the JNDI service provider. This example uses BEA WebLogic 3.x conventions. If you use a different EJB server, substitute the appropriate properties for that platform.
In EJB 1.1, you can obtain the bean home from the JNDI environment context without having to authenticate or load the service provider. EJB 1.1 requires that the javax.rmi.PortableRemoteObject.narrow()
method be used to explicitly cast the home’s remote reference to its appropriate type. The use of PortableRemoteObject
is required to support Java RMI-IIOP (Remote Method Invocation over Internet Inter-ORB Protocol), which is based on CORBA reference types that do not support native casting. You can take advantage of the Class
type variable used in the lookup()
method to specify the target interface in the PortableRemoteObject.narrow()
method.
List 10 shows how the remote reference to a bean’s EJB home is obtained in a forward-portable bean.
|
Wrapping Up
This installment on forward compatibility has presented a strategy for mitigating forward compatibility problems related to runtime access to environment properties, resources, and other beans. Source code for the
PortableContext
and its implementations are available at my Web site (see EJBNow in
).
The next and final installment in this series will continue to enhance the PortableContext
to encapsulate differences in security. The next installment will also explore the application of the PortableContext
to make beans portable between brands of EJB servers. In addition, it will cover issues specific to entity bean forward compatibility and deployment descriptors (*.ser
to xml
).