Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs

Create forward-compatible beans in EJB, Part 1

How to write EJB 1.0 beans to port to EJB 1.1 servers

  • Print
  • Feedback

Page 4 of 6

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 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.

List 5. PortableContext with the getInstance() static method
import javax.ejb.EJBContext;
public abstract class PortableContext {
    
    final static String SYSTEM_PROPERTY_NAME = "java.ejb.portable_context";
    EJBContext ejbContext;
      public static PortableContext getInstance(EJBContext context) 
    throws PortableContextException{
             
        String className = 
            System.getProperty(SYSTEM_PROPERTY_NAME);
        if(className == null)
            throw new PortableContextException("No system property for impl");
        try{
            Class clazz = Class.forName(className);
            PortableContext portableCtx = 
                (PortableContext)clazz.newInstance();
            portableCtx.setEJBContext(context);
            return portableCtx;
        }catch(Exception e){
            throw new PortableContextException(e);
        }
    }
   
    public void setEJBContext(EJBContext ctx){
        ejbContext = ctx;
    }
    public abstract Object lookup(String name, Class type)
    throws PortableContextException;
}


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.

List 6-A. Using the PortableContext factory in a session bean
import javax.ejb.SessionContext;
public class TellerBean implements javax.ejb.SessionBean {
  SessionContext ejbContext;
  PortableContext portableContext = null;
  public void setSessionContext(SessionContext ctx) {
    ejbContext = ctx;
    setPortableContext();
  }
  public void ejbActivate() {
    setPortableContext();
  }
  public void setPortableContext(){
    if (portableContext == null)
      portableContext = 
        PortableContext.getInstance(ejbContext);
    else 
      portableContext.setEJBContext(ejbContext);
  }
  ...
}


List 6-B. Using the PortableContext factory in an entity bean
import javax.ejb.EntityContext;
 
public class AccountBean implements javax.ejb.EntityBean {
  EntityContext ejbContext;
  PortableContext portableContext = null;
 
  public void setEntityContext(EntityContext ctx){
    ejbContext = ctx;
    setPortableContext();
  }
 
  public void ejbActivate() {
    setPortableContext();
  }
 
  public void setPortableContext() {
    if (portableContext == null)
      portableContext =
        PortableContext.getInstance(ejbContext);
    else
      portableContext.setEJBContext(ejbContext);
  }
  ...
}


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.

  • Print
  • Feedback

Resources