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.

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.

List 1-A. Using the environment properties in EJB 1.0
public class AccountBean implements EntityBean {
  int id; 
  double balance; 
  EntityContext ejbContext; 
  public void setEntityContext(EntityContext ctx){ 
    ejbContext = ctx; 
  }
  public void withdraw(Double withdraw) 
      throws WithdrawLimitException {
    Properties props =   ejbContext.getEnvironment();
    String value =       props.getProperty("withdraw_limit");
    Double limit = new Double(value)
  
    if (withdraw.doubleValue() > limit.doubleValue())
      throw new WithdrawLimitException(limit); 
    else
      balance = balance - withdraw.doubleValue();
    }
    ... 
}
List 1-B. Using the JNDI ENC in EJB 1.1
public class AccountBean implements EntityBean {
  int id;
  double balance;
  EntityContext ejbContext;
 
  public void setEntityContext(EntityContext ctx){
    ejbContext = ctx;
  }
 
  public void withdraw(Double withdraw)
      throws WithdrawLimitException {
 
    InitialContext jndiContext = new InitialContext();
    Double limit = (Double)
      jndiContext.lookup("java:comp/env/withdraw_limit");
 
    if (withdraw.doubleValue() > limit.doubleValue())
      throw new WithdrawLimitException(limit);
    else
 
      balance = balance - withdraw.doubleValue();
    }
 
    ...
}

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.

List 2. PortableContext
import javax.ejb.EJBContext; 
public abstract class PortableContext {
    
    EJBContext ejbContext;
   
    public void setEJBContext(EJBContext ctx){
        ejbContext = ctx;
    }
    public abstract Object lookup(String name, Class type)throws PortableContextException; 
}

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.

List 3-A. PortableContext1_0 for EJB 1.0 containers
import javax.ejb.EJBContext; 
import java.util.Properties;
public class PortableContext1_0 extends PortableContext {
  public Object lookup(String name, Class type) 
  throws PortableContextException {
    try {
      Properties props = ejbContext.getEnvironment();
      String value = props.getProperty(name); 
      if (value == null) 
        return null; 
      else {
        if (type == String.class) 
          return value; 
        else
          return primitiveWrapper(value, type); 
      }
    } catch(Exception e) {
      throw new PortableContextException(e); 
    }
  }
  private Object primitiveWrapper(String value, Class type) 
  throws PortableContextException {
    if (type == Double.class) 
      return new Double(value); 
    if (type == Integer.class) 
      return new Integer(value); 
    if (type == Boolean.class) 
      return new Boolean(value); 
    if (type == Long.class) 
      return new Long(value); 
    if (type == Byte.class) 
      return new Byte(value); 
    if (type == Short.class) 
      return new Short(value); 
    if (type == Float.class) 
      return new Float(value); 
    else
      throw new PortableContextException();
  }
}
List 3-B. PortableContext1_1 for EJB 1.1 containers
import javax.ejb.EJBContext;
import javax.naming.InitialContext;
import javax.naming.NamingException;
 
public class PortableContext1_1 extends PortableContext {
  InitialContext jndiContext;
  
  public Object lookup(String name, Class type)
  throws PortableContextException {
    try{
      jndiContext = new InitialContext();
      Object value = jndiContext.lookup(name);
      return value;
    } catch (NamingException ne) {
      throw new PortableContextException(ne);
    }
  }
}

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.

List 4. Using the PortableContext to access an environment property in both EJB 1.0 and EJB 1.1
public class AccountBean implements javax.ejb.EntityContext {
  int id;
  double balance;
  EntityContext ejbContext;
  PortableContext portableContext;
  ...
  public void withdraw(Double withdraw) 
  throws WithdrawLimitException {
       Double limit = (Double)
     portableContext.lookup("java:comp/env/withdraw_limit", Double.class);
     
    if (withdraw.doubleValue() > limit.doubleValue())
      throw new WithdrawLimitException(limit);
    else
      balance = balance - withdraw.doubleValue();
  }
  ...
}

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

1 2 3 Page 1
Page 1 of 3