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.

jGuru
Server-Side Java: Read the whole series!
  • 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.

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

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.

List 7-A. JDBC in PortableContext1_0 (EJB 1.0)
import javax.ejb.EJBContext;
import java.util.Properties;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
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;
      if (name.startsWith("java:comp/env/jdbc"))
        return DriverManager.getConnection(value);
      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 7-B. JDBC in PortableContext1_1 (EJB 1.1)
import javax.ejb.EJBContext;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import java.sql.SQLException;
 
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);
      if (name.startsWith("java:comp/env/jdbc")) {
        DataSource ds = (DataSource)value;
        return ds.getConnection();
      } else
        return value;
    } catch(NamingException ne) {
      throw new PortableContextException(ne);
    } catch(SQLException se) {
      throw new PortableContextException(se);
    }
  }
}

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.

List 8. Using the PortableContext to access a JDBC connection 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 ejbLoad() {
    try {       Connection con = (Connection)
           portableContext.lookup("java:comp/env/jdbc/AccountDB", Connection.class);
           
        PreparedStatement ps = con.prepareStatement("select .... ");
    ...
  }
  ...
}

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.

List 9-A. EJB References in PortableContext1_0 (EJB 1.0)
import javax.ejb.EJBContext;
import java.util.Properties;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import javax.naming.InitialContext;
import javax.naming.Context;
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;
      if (name.startsWith("java:comp/env/jdbc"))
             return DriverManager.getConnection(value);     else if (name.startsWith("java:comp/env/ejb")) {
             InitialContext jndiContext = getInitialContext();
             return jndiContext.lookup(value);
      } 
      else {
            if (type == String.class)
                 return value;
            else
                 return primitiveWrapper(value, type);
      } 
    } catch(Exception e) {
      throw new PortableContextException(e);
    }
  }
  private InitialContext getInitialContext() throws Exception {
    Properties p = new Properties();
    p.put(Context.INITIAL_CONTEXT_FACTORY,
      "weblogic.jndi.T3InitialContextFactory");
    p.put(Context.PROVIDER_URL, "t3://localhost:7001");
    return new InitialContext(p);
  }
  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 9-B. EJB References in PortableContext1_1 (EJB 1.1)
import javax.ejb.EJBContext;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import java.sql.SQLException;
import javax.rmi.PortableRemoteObject;
 
 
 
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);
      if (name.startsWith("java:comp/env/jdbc")){
        DataSource ds = (DataSource)value;
        return ds.getConnection();
      } else if (name.startsWith("java:comp/env/ejb")) {
        return PortableRemoteObject.narrow(value, type);
      } else
        return value;
    } catch(NamingException ne) {
      throw new PortableContextException(ne);
    } catch(SQLException se) {
      throw new PortableContextException(se);
    }
  }
}

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.

List 10. Using the PortableContext to access a bean reference in both EJB 1.0 and EJB 1.1
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 transfer(int accountSource, int accountTarget, amount) 
      throws TransferException {
    try {
      AccountHome accountHome  = (AccountHome)
        portableContext.lookup("java:comp/env/ejb/AccountHome", AccountHome.class);
      Account as = accountHome.findByPrimaryKey(new AccountPK(accountSource));
      Account at = accountHome.findByPrimaryKey(new AccountPK(accountTarget));
      as.withdraw(amount);
      at.deposit(amount);
      ...
    }
}

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

Resources

).

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

Richard Monson-Haefel is an EJB expert for
jGuru.com and the author of Enterprise JavaBeans published
by O’Reilly & Associates (1999). Richard would like to thank
Chris Raber of Gemstone, who was a technical reviewer of this
article and a contributor in the development of the
PortableContext. Learn more about Richard at jGuru.com.
JavaWorld and jGuru have formed a partnership to help the
community better understand server-side Java technology. Together,
JavaWorld and jGuru are jointly producing articles, free
educational Web events, and working together on the
JavaWorld bookstore and Web-based training.

Source: www.infoworld.com