Read all about EJB 2.0

Dramatic EJB 2.0 changes enhance flexibility and portability in your application development

Enterprise JavaBeans 2.0, released June 2, is not just a point release but also a new version of the specification. At just over 500 pages, the EJB 2.0 specification is 200 pages (66 percent) longer than the previous EJB 1.1 specification. The most important changes in the specification are those made to container-managed persistence (CMP) and the introduction of a completely new bean type, the MessageDrivenBean.

The bulk of the changes in EJB 2.0 are found in the definition of a new CMP component model. It's radically different from the old CMP model because it introduces an entirely new participant, the persistence manager, and a completely new way of defining container-managed fields, as well as relationships with other beans and dependent objects.

The introduction of the MessageDrivenBean (the message bean) is also significant. The message bean represents the integration of JMS (Java Message Service) with EJB to create an entirely new type of bean designed to handle asynchronous JMS messages. That exciting new bean type provides a component model for JMS clients, allowing them to be deployed in the rich and robust environment of an EJB container system.

There are many other smaller changes made to the specification. Those other changes, while important, are mostly concerned with tightening the specification to eliminate ambiguities and make the components more portable. This article focuses on the new CMP and message bean component models introduced in EJB 2.0.

I provide several concrete examples, so it should be fairly easy to follow and comprehend. EJB novices might, however, find the material more difficult since it's assumed that readers have a basic understanding of EJB. For more information on EJBs, please see Resources.

Container-managed persistence

Container-managed persistence has undergone radical changes in EJB 2.0. In EJB 2.0, the persistence manager handles persistence of CMP entity beans automatically at runtime. The persistence manager is responsible for mapping the entity bean to the database based on a new bean-persistence manager contract called the abstract persistence schema. In addition, the persistence manager is responsible for implementing and executing find methods based on a new query language called EJB QL.

It's important to note that products that comply with the EJB 2.0 specification must support the EJB 1.1 CMP model as well as the new EJB 2.0 model. While those models are not compatible, support is required for the EJB 1.1 model to ensure backward compatibility.

The abstract persistence schema

To understand how the abstract persistence schema works and why it's important, I will quickly review for you how CMP is handled in EJB 1.1, and then discuss how it's defined in EJB 2.0.

The EJB 1.1 CMP model

In EJB 1.1, the bean developer is responsible for declaring the bean class' persistent fields as either Java primitive or serializable types. The following examples show an Employee enterprise bean class, as defined in EJB 1.1, with several CMP fields:

// the Employee bean class
public class EmployeeBean implements java.ejb.EntityBean {
    // instance fields
    EntityContext ejbContext;
 
    // container-managed fields
    public int identity;
    public String firstName;
    public String lastName;
    public double salary;
    public Address address;
    public Integer ejbCreate(int id, String fname, String lname){
        identity = id;
        firstName = fname;
        lastName = lname;
        return null;
    }
    ...
}
// The Address dependent class
public class Address implements Serializable{
           public String street;
           public String city;
           public String state;
           public String zip;
}

When a relational database is used for persistence, the primitive fields such as identity, firstName, lastName, and salary are fairly easy to persist since they map nicely to SQL types such as INTEGER, CHAR, and DOUBLE.

In EJB 1.1, the XML deployment descriptor of a CMP bean provides cmp-field elements for identifying the persistent fields (container-managed fields) in the bean class. As shown below, the cmp-field elements are used to differentiate between the fields that are written to the database and those that are not. For example, the ejbContext field is not included in the list of container-managed fields and is therefore not a persistent field.

<ejb-jar>
   <enterprise-beans>
      <entity>
             <ejb-name>EmployeeEJB</ejb-name>
             ...
             <persistence-type>Container</persistence-type>
             ...
             <cmp-field><field-name>identity</field-name></cmp-field>
             <cmp-field><field-name>firstName</field-name></cmp-field>
             <cmp-field><field-name>lastName</field-name></cmp-field>
             <cmp-field><field-name>salary</field-name></cmp-field>
             <cmp-field><field-name>address</field-name></cmp-field>
             ...

The container provider supplies a tool for mapping the bean's persistent fields to the columns in the database tables, usually one table per bean. Serializable types such as Address, however, are more difficult to persist. In EJB 1.1 there is no standard way of mapping serializable objects to a relational database. Although the Address class has its own set of fields, the XML deployment descriptor doesn't provide a mechanism for mapping those fields to the database. In most cases it was expected that serializable objects such as Address would be persisted as a binary type, which is sometimes called a blob type, to a database table.

That problem is exacerbated as the entity bean's data schema grows in complexity. An Employee bean, for example, might have many child objects similar to Address, such as Benefits and JobPosition. Those child objects, called dependent objects, can form complex object graphs spanning several tables in a relational database. In addition, CMP in EJB 1.1 is largely inadequate for persisting relationships with other beans. In EJB 1.1, if a bean was to maintain a relationship with another bean, the container would automatically use the primary key or handle as a link. That has proven to be a fairly crude mechanism for maintaining relationships with other beans whose natural relationship may be bidirectional or dependent on fields not easily represented by the primary key or handle.

The EJB 2.0 CMP model

In EJB 2.0, a new contract between the CMP entity bean and persistence manager lets you define more complex and portable bean-to-bean, bean-to-dependent, and even dependent-to-dependent object relationships within an entity bean.

The persistent manager is a new participant in the Enterprise JavaBeans deployment process. The container vendor or a vendor that specializes in persistence to a particular database may provide the persistence manager. The idea is to separate the mechanism used to manage bean relationships from the container, which is responsible for managing security, transactions, and resources. The separation of responsibilities allows different persistence managers to work with different containers. It also allows entity beans to become more portable across EJB vendors as well as persistence managers.

If you have worked with or studied CocoBase, a product from Thought Inc. that automatically generates BMP (Bean Managed Persistence) beans for EJB 1.1 containers, then you are already somewhat familiar with how a persistent manager tool might work. CocoBase generates all the database access logic for BMP beans based on object-to-relational mapping information provided by the bean deployer. In EJB 2.0, the persistence manager can generate a mapping of CMP entities to a relational database based on information provided by the deployment descriptor, the bean's abstract persistence schema, and work performed by the deployer. The persistence manager is not, however, limited to a relational database. Persistence managers may also be developed for object databases as well as legacy and ERP systems such as SAP.

In order for the persistence manager to be separated from the container, a contract between the bean and the persistence manager had to be defined. The contract is manifested in the new abstract persistence schema. That schema is defined through a new set of XML elements in the deployment descriptor and a set of code idioms in the CMP entity beans. In EJB 2.0, the CMP bean class is declared as abstract and its persistent and relationship fields are accessed using abstract accessor and mutator methods whose method signatures map to special elements in the XML deployment descriptor.

When the bean is deployed, you will use persistent manager tools to generate a concrete implementation of the abstract bean class and its dependent object classes based on the XML deployment descriptor and the bean class. The concrete implementations will include the data access code that will actually read and write the bean's state to the database at runtime. At runtime, the container uses the subclasses generated by the persistence manager tools instead of the abstract classes defined by the bean provider.

Inheritance hierarchy of a bean class

To give some meat to the discussion, a CMP entity example is provided that explains more concretely how the abstract persistence schema works.

An example CMP entity in EJB 2.0

In EJB 2.0 a container-managed entity bean is defined to be abstract and its persistent fields are not defined directly in the bean class. Instead, an abstract persistent schema has been developed that lets the bean provider declare the persistent fields and bean relationships indirectly. Below is an example of the Employee bean that uses the new abstract persistent schema. Notice that none of the persistent fields are declared in the bean class.

public abstract EmployeeBean implements javax.ejb.EntityBean {
  .  // instance fields
     EntityContext ejbContext;
     // container-managed persistent fields
           public abstract void setIdentity(int identity);
           public abstract int getIdentity();
           public abstract void setFirstName(String firstName);
           public abstract String getFirstName();
           public abstract void setLastName(String lastName);
           public abstract String getLastName();
           
     // container-managed relationship fields
           public abstract void setContactInfo(ContactInfo info);
           public abstract ContactInfo getContactInfo();
            
...
}

In the XML deployment descriptor of that bean, the abstract persistence schema declares the container-managed fields and relationships.

<ejb-jar>
  <enterprise-beans>
        <entity>
             <ejb-name>EmployeeEJB</ejb-name>
             ...
             <persistence-type>Container</persistence-type>
             ...
             <cmp-field><field-name>identity</field-name></cmp-field>
             <cmp-field><field-name>firstName</field-name></cmp-field>
             <cmp-field><field-name>lastName</field-name></cmp-field>
             ...
        </entity>
   </enterprise-beans>
   <dependents>
      <dependent>
            <dependent-class>ContactInfo</dependent-class>
            <dependent-name>ContactInfo</dependent-name>
            <cmp-field>street</cmp-field>
            <cmp-field>city</cmp-field>
            <cmp-field>state</cmp-field>
            <cmp-field>zip</cmp-field>
           <cmp-field>homePhone</cmp-field>
           <cmp-field>workPhone</cmp-field>
           <cmp-field>email</cmp-field>
           ...
      </dependent>
      <relationships>
          <ejb-relation>
              <ejb-relation-name>Employee-ContactInfo</ejb-relation-name>
              <ejb-relationship-role>
                    <ejb-relationship-role-name>
                                    employee-has-contactinfo
                    </ejb-relationship-role-name>
                    <multiplicity>one</multiplicity>
                    <role-source>
                          <ejb-name>EmployeeEJB</ejb-name>
                    </role-source>
                   <cmr-field>
                          <cmr-field-name>contactInfo</cmr-field-name>
                          <cmr-field-type>ContactInfo</cmr-field-type>
                  </cmr-field>
           </ejb-relationship-role>
          <ejb-relationship-role>
                   <ejb-relationship-role-name>
                          contactinfo_belongsto_employee
                   </ejb-relationship-role-name>
                   <multiplicity>one</multiplicity>
                   <role-source>
                             <dependent-name>ContactInfo<dependent-name>
                   </role-source>
            </ejb-relationship-role>
       </ejb-relation>
   </relationships>
<ejb-jar>

The XML elements used to describe container-managed relationships can become very complex, as they must deal with both the cardinality and direction (unidirectional vs. bidirectional) of the relationships. The above code snippet demonstrates the elements you need to describe a simple relationship between a bean and its dependent object class. Although even simple relationships translate into verbose XML, all those elements are necessary so that the persistence manager can map complex object graphs to the database.

While the XML elements used to define the abstract persistence schema of CMP beans are central to CMP in EJB 2.0, for the sake of brevity, this article will cease to provide XML examples. Instead, it will illustrate the basic concepts behind CMP in EJB 2.0 by relying solely on the abstract idioms that must be used in the bean class. Those code idioms are used in concert with and are defined by the relationship elements in the XML deployment descriptor, so you can't have one without the other, but they are easier to follow than the XML part of the schema.

In addition to the XML elements, the abstract persistence schema also defines a set of idioms that you must use when declaring the bean class and its dependent objects. The way fields are accessed and modified is strictly defined, requiring that set<METHOD> methods be used for modifying persistent fields and get<METHOD> methods be used for accessing them. The names of those methods and their return types are dictated by their corresponding XML relationship elements in the deployment descriptor.

Both the entity bean class and dependent class follow the same abstract persistence schema. Below is an example of how the ContactInfo object might be defined as a dependent object class.

public abstract class ContactInfo {
            // home address info
      public abstract void setStreet(String street);
      public abstract String getStreet();
      public abstract void setState(String state);
      public abstract String getState();
      public abstract void setZip(String zip);
      public abstract String getZip();
      public abstract void setHomePhone(String phone);
      public abstract String getHomePhone();
      // work address info
      public abstract void setWorkPhone(String phone);
      public abstract String getWorkPhone();
      public abstract void setEMail(String email);
      public abstract String getEMail();
      ...
}

Dependents live and die along with the entity bean, which is critical to understanding the relationship between dependents and entity beans. Dependents are contained by a specific entity, so removing the entity results in the removal of the dependents. In relational database terms that is sometimes called a cascading delete.

Dependent objects such as ContactInfo are used in relationship fields. Dependent objects that form relationships with the entity bean are technically called dependent object classes. An EJB client application can never access a dependent object class directly; the class cannot be used as an argument or return value in a bean's remote or home interfaces. Dependent object classes are only visible to the bean class.

The dependent object classes are not suitable as remote argument types because they are tightly coupled with the bean's persistence logic at runtime. The persistence manager extends the abstract dependent object classes to provide implementations that can be used to manage the bean's persistence state at runtime. In addition, the abstract persistence schema models the data -- not the business concepts represented by the enterprise bean -- so it makes sense to hide the abstract persistence schema from the EJB clients as a design strategy.

The ContactInfo relationship field, for example, contains a lot of information beyond the simple address information needed by the bean's clients. While you can use the ContactInfo dependent object class in the abstract persistence schema, which is hidden from the bean's clients, you will use other objects to actually expose the data to the client. Below is an example of how the ContactInfo dependent is hidden from the EJB client. In that case, the address information is exposed using the Address object developed in the EJB 1.1 example.

// remote interface for the Employee bean
public interface Employee extends javax.ejb.EJBObject {
   public Address getHomeAddress();
   public void setHomeAddress(Address address);
   public int getIdentity() throws RemoteException;
   public void setFirstName(String firstName) throws RemoteException;
   public String getFirstName()throws RemoteException;
   public void setLastName(String lastName) throws RemoteException;
   public String getLastName() throws RemoteException;
}
// bean class for the Employee bean
public abstract EmployeeBean implements javax.ejb.EntityBean {
    ...
    public Address getHomeAddress(){
      ContactInfo info = getContactInfo();
      Address addr = new Address();
      addr.street = info.getStreet();
      addr.city = info.getCity();
      addr.state = info.getState();
      addr.zip = info.getZip();
      return addr;
     }
     public void setHomeAddress(Address addr){
      ContactInfo info = getContactInfo();
            info.setStreet(addr.street);
            info.setCity(addr.city); 
            info.setState(addr.state); 
            info.setZip(addr.zip);
     }
  ....
  // container-managed relationship fields
  public abstract void setContactInfo(ContactInfo info);
  public abstract ContactInfo getContactInfo();
...
}

While container-managed relationship fields are not exposed to the client, you can use container-managed persistence fields directly from the remote interface. Notice that the container-managed persistence fields used for accessing the firstName and lastName are used in the remote interface.

A bean may have many different kinds of relationships with dependent object classes (dependents), which are defined by the cardinality and direction of the relationship. Beans can have one-to-many and one-to-one relationships with dependents. For example, the Employee bean may have only one Benefit dependent but may have many ContactInfo dependents.

public abstract EmployeeBean implements javax.ejb.EntityBean {
     ...
      public abstract void setContactInfos(Collection addresses);
      public abstract Collection getContactInfos():
      
      public abstract void setBenefit(Benefit benefit);
      public abstract Benefit getBenefit();
     ...
}

A one-to-many relationship with a dependent is represented as either the java.util.Collection or java.util.Set types (note: the java.util.Map and java.util.List are being considered as additional return types in a subsequent version of the specification), while a one-to-one relationship with a dependent uses the dependent type object's type.

Entity beans may also define relationships with other entity beans. Those relationships may be one-to-one, one-to-many, or many-to-many. For example, the Employee bean might have many children but only one spouse. The code fragment below illustrates how those relationships are modeled, using the method idioms of the abstract persistence schema. Children and spouses are both manifested in that application as Person beans.

public abstract EmployeeBean implements javax.ejb.EntityBean {
      ...
      public abstract void setSpouse(Person manager);
      public abstract Person getSpouse();
      
      public abstract void setChildren(Collection family);
      public abstract Collection getChildren();
      ...
}

A one-to-many relationship with another bean is represented as a java.util.Collection or java.util.Set type, while a one-to-one relationship uses the bean's remote interface type.

Dependents themselves can have one-to-one, one-to-many, and many-to-many relationships with other dependents within the same bean. In addition, dependents may have one-to-one or one-to-many relationships with entity beans other than their parent bean. The below example shows how the Benefit dependent object class has a one-to-one relationship with the Salary dependent (a compensation calculator) as well as a one-to-many relationship with the Investment bean.

public abstract class Benefit {
      public abstract void setSalary(Salary salary);
      public abstract Salary getSalary();
      public abstract void setInvestments(Collection investments);
      public abstract Collection getInvestments();
}

At deployment time, the deployer will use the persistence manager tools to generate concrete implementations of the bean class and its dependent classes. Those concrete implementations will maintain relationships and synchronize the bean instance's state with the database at runtime. The container will manage the persistent instance at runtime, providing a robust environment with automatic access control and transaction control.

A bean may also define dependent object values, which are serializable objects such as the Address object in the EJB 1.1 example. Those values are persisted using serialization and do not form relationships with the bean -- they are strictly container-managed persistence fields.

A contract has also been defined between the container and the persistence manager, so that the persistence manager may obtain handles to transactions and access database connection pools managed by the container. That contract is a little looser and will need to be tightened in the future, but it is a basis for allowing portability of persistence managers across EJB containers. The details of the contract between the container and persistence manager are beyond the scope of this article.

In addition to defining persistence through the abstract persistence schema, EJB 2.0 provides a new query language that declares how the persistence manager should implement find methods in CMP.

The EJB Query Language

The EJB Query Language (EJB QL) specifies how the persistence manager should implement find methods defined in the home interface. EJB QL, which is based on SQL-92, can be compiled automatically by the persistence manager, which makes entity beans more portable and easier to deploy.

EJB QL and find methods

EJB QL statements are declared in the entity bean's deployment descriptor. EJB QL is fairly simple to work with. As an example, the home interface of the Employee bean may be declared as follows:

public interface EmployeeHome extends javax.ejb.EJBHome {
      ...
      public Employee findByPrimaryKey(Integer id)
      throws RemoteException, CreateException;
      public Collection findByZipCode(String zipcode)
      throws RemoteException, CreateException;
      
      public Collection findByInvestment(String investmentName)
      throws RemoteException, CreateException;
}

Given the above home interface definition, you can use EJB QL to specify how the find methods should be executed by the persistence manager. All entity beans are required to have a findByPrimaryKey() method. The query required to execute that method is obvious -- use the field(s) of the primary key to lookup the bean in the database, so no EJB QL statement is needed.

The findByZipCode() method is used to obtain all the Employee beans with a certain zip code. That would be expressed by using the following EJB QL in the deployment descriptor.

FROM contactInfo WHERE contactInfo.zip = ?1

This statement basically says "select all the Employee beans that have a zip code equal to the zipcode argument." The SELECT clause, which indicates what to select, is not needed in the EJB QL statements for find methods. That is because the find methods will always select the remote references of its own bean type. In that case, the select statement is supposed to return a collection of remote Employee bean references.

Find methods can even span the abstract persistent schemas of other beans, providing they are deployed together in the same ejb-jar file and have an actual relationship that can be navigated. For example, the findByInvestment() method would require that the find query navigate from the Employee to the Investment bean's abstract persistence schema. The EJB QL statement declared to express that find operation is shown below.

FROM element IN benefit.investments WHERE element.name = ?1

The above statement says "select all the Employee beans whose benefit dependent contains at least one Investment bean reference with the name equal to the investmentName argument of the findByInvestment() method."

EJB QL and select methods

EJB QL is also used in a new query method, called the ejbSelect method, that is similar to the find methods, except it's only for the bean class to use. It's not declared in the home interface and is therefore not exposed to the client. In addition, the ejbSelect methods can return a wider range of values, not just the bean's own remote interface types.

There are two kinds of select methods, ejbSelect<METHOD> and ejbSelect<METHOD>InEntity. The ejbSelect<METHOD> method is executed globally, which means it's not specific to the bean instance on which it is executed. The ejbSelect<METHOD>InEntity is specific to the entity instance in which it is executed. Those select methods are declared as abstract methods in the bean class and are used in the class' business methods. Below are examples of ejbSelect<METHOD> and ejbSelect<METHOD>InEntity methods and a demonstration of how you might use them in business methods.

public abstract class EmployeeBean implements javax.ejb.EntityBean {
      ...
      // ejbSelect<METHOD>InEntity
      public abstract Collection ejbSelectInvestmentsInEntity (String risk);
      
      // ejbSelect<METHOD>
      public abstract Collection ejbSelectInvestments(String risk);
      ...
}

In the above declarations, the two select methods operate under different scopes. The ejbSelectInvestmentsInEntity() is executed only on the current Employee bean instance, so it would return only the employee's risky investments.

SELECT invest FROM invest IN benefit.investments WHERE invest.type = ?1

The ejbSelect<METHOD> methods, on the other hand, are global in scope, so the same query would return all the risky investments of all the employees in the entire enterprise.

ejbSelect<METHOD>InEntity select methods can return the remote types of beans (as in the above query), dependent objects, or any other Java type. Global select methods, on the other hand, are not allowed to return the dependent object types of beans.

The EJB QL statements for select methods require the use of the SELECT clause, since they can return a wider range of values.

The new ejbHome methods

In EJB 2.0, entity beans can declare ejbHome methods that perform operations related to the EJB component but that are not specific to a bean instance. The ejbHome methods defined in the bean class must have a matching home method in the home interface. The code below illustrates a home method as defined for the Employee bean's home interface. The applyCola() method is used to update the salaries of all employees with the most recent COLA (cost of living adjustment) increase.

public interface EmployeeHome extends javax.ejb.EJBHome {
      // a home method
      public void applyCola(double increate) throws RemoteException;
      ...
}

The applyCola() method must have a matching ejbHome method in the bean class, which is declared ejbHomeApplyCola(). The ejbHomeApplyCola() method is not specific to one bean instance, its scope is global, so it will make a COLA to the salaries of all the employees.

public abstract class EmployeeBean implements javax.ejb.EntityBean {
      ...
            // ejbHome method 
      public void ejbHomeApplyCola (double increase ){
            Collection col = ejbSelectAllEmployees ();
            Iterator employees = col.iterator();
            while(employees.next()){
                  Employee emp = (Employee)employees.next();
                  double salary = emp.getAnnualSalary();
                  salary = salary + (salary*increase);
                  emp.setAnnualSalary(salary);
            }
      }
}

The bean developer is required to implement the ejbHome methods for both BMP and CMP entity beans. CMP implementations will probably rely heavily on global select statements (as illustrated above) and finder methods, while BMP implementations of ejbHome will use direct database access and the finder methods of beans to query data and apply changes.

MessageDrivenBean

A fundamental change to the specification in EJB 2.0 is the addition of a completely new enterprise bean type, the MessageDrivenBean. The MessageDrivenBean is designed specifically to handle incoming JMS messages. JMS is a new paradigm for many developers, so this article will spend a little time developing an understanding of JMS and how it's used in EJB 2.0.

What is JMS?

JMS is a vendor agnostic API for accessing messaging systems. It's analogous to JDBC (Java Database Connectivity): where JDBC is an API that you can use to access many different relational databases, JMS provides the same vendor-independent access to messaging services. Many vendors currently support JMS including IBM's MQSeries, BEA's Weblogic JMS service, and Progress' SonicMQ to name a few.

JMS lets you send messages from one JMS client to another through a messaging service, sometimes called a message broker or router. A message is a type object in JMS, which has two parts: a header and a message body. The header is composed of routing information and metadata about the message. The message body carries the application data or payload. There are several message types depending on the type of payload, including message types that carry simple text (TextMessage), serializable objects (ObjectMessage), a collection of properties (MapMessage), byte streams (BytesMessage), primitive values streams (StreamMessage), or no payload (Message).

Messaging systems are asynchronous, which means that a JMS client can send a message without having to wait for a reply. That is completely different when compared to RPC-based (Remote Procedure-based) systems such as EJB 1.1, CORBA, and the Java RMI reference implementation. In RPC, the client invokes a method on a distributed object on a server. Until the method invocation returns, the client is blocked; it must wait for the method invocation to end before it can execute the next instruction. In JMS, a client delivers a message to a virtual channel (topic or queue) to which other JMS clients subscribe or listen. When a JMS client sends a message, it doesn't wait for a reply. It executes the send operation and moves on to the next instruction. The message might eventually be forwarded to one client or many, none of which need reply.

JMS in EJB 2.0

EJB 2.0 supports the integration of JMS in two ways: as a resource available to beans and as a MessageDrivenBean. When JMS is used as a resource, the bean using the JMS API is a message producer or sender. In that case, the bean is sending messages to a virtual channel called a topic or queue. A MessageDrivenBean, on the other hand, is a message consumer or receiver. It listens to a specific virtual channel (topic or queue), and handles messages delivered to that channel. To better understand the roles of both message producers and consumers, a SessionBean bean is used to send a message using JMS and then a new MessageDrivenBean is used to consume that same message.

JMS as an EJB 2.0 resource

Session and entity beans are RPC-based components, which is an excellent architecture for assembling transactional components. In some cases, however, the synchronous nature of RPC becomes a handicap, which is why access to the JMS API was included as a resource in EJB 1.1. Using the JNDI environment-naming context, a bean can obtain a JMS factory and deliver an asynchronous message to a topic or queue (also obtained from JNDI) without having to wait for a reply. Below is an example of a ShoppingCart bean that uses JMS to send the details of an Order to a messaging topic.

public class ShoppingCartBean implements SessionBean {
   // order detail is a serializable object that contains all the order information. 
   public OrderDetail orderDetail;
    public void processOrder(){
          // The logic to process the order goes here ....
          
          // ... after processing the order, send a message to other systems                  
          //     about the order
          InitialContext jndiEnc = new InitialContext();
          // Using JNDI ENC to obtain the JMS Factory and Topic identifier
          TopicConnectionFactory factory = 
          jndiEnc.lookup("java:comp/env/jms/topicfactory");
          Topic orderTopic = jndiEnc.lookup("java:comp/env/jms/ordertopic");
          // Obtain a publisher that is used to send messages
          TopicConnection con = factory.createTopicConnection();
          TopicSession session = 
                   con.createTopicSession(false, Session.AUTO_ACKNOWLEDGE );
          TopicPublisher publisher = session.createPublisher(orderTopic);
          // Send an ObjectMessage to the topic (virtual channel)
          ObjectMessage message = session.createObjectMessage();
          message.setObject(orderDetail);
          publisher.publish(message);
          con.close();
    }
    ...
}

In that case, JMS is used to notify other applications that an order has been processed. Those other applications are not critical to processing the order, but they benefit from being notified that an order has been processed. Examples include an inventory system that automatically adjusts inventory and a marketing application that adds customers to the mailing lists for product catalogs.

Using JMS lets the bean publish (send) the message without blocking. The bean doesn't know who will receive the message because it delivers the message to a topic (virtual channel) and not directly to another application. Applications can choose to subscribe to that topic and receive notification of new orders. That makes it possible to dynamically add and remove applications from virtual channels, which results in a more flexible system.

Applications that subscribe to the order topic will receive notification of a new order, which the applications can process in any way they see fit. Applications that subscribe to topics or receive messages from queues can be Java applications, EAI systems (for integrating legacy and ERP systems), or MessageDrivenBean components, all of which are considered, in JMS terminology, to be JMS clients.

JMS and the MessageDrivenBean

While most JMS vendors provide the message brokering facilities for routing messages from senders to receivers, it is the application developer's responsibility to build JMS clients that consume (receive) messages. In many cases the application receiving the message needs to be robust, secure, fast, and scalable; basically it needs the same infrastructure as an EJB application.

In recognition of that need, EJB 2.0 now includes the MessageDrivenBean type, which can consume JMS messages and process them in the same robust component-based infrastructure that session and entity beans enjoy. The MessageDrivenBean type (message bean) is an enterprise bean component that is designed to consume asynchronous JMS messages.

In addition to providing the container infrastructure, EJB also provides another important advantage: concurrent processing. In EJB, a deployed message bean represents a single message consumer, but the bean itself is serviced by many bean instances. Each bean instance can discretely consume messages received by the message bean. That means that a message bean doesn't have to consume messages serially as is the case with a normal JMS client. A message bean can consume the messages it receives concurrently, which enables much higher throughput and better scalability than is possible with a traditional JMS application.

To illustrate the role of a message bean, the MarketingBean class is developed and deployed to consume messages from the order topic. The MarketingBean will extract the OrderDetail object from the message and use it to add the customer to the appropriate catalog mailing lists. A junk mailing system at its finest.

The following is the definition of the MarketingBean class, which consumes messages published to the order topic.

public class MarketingBean implements javax.ejb.MessageDrivenBean {
      public void onMessage(Message message) {
          ObjectMessage orderMessage = (ObjectMessage)message;
            OrderDetail orderDetail = (OrderDetail)orderMessage.getObject();
       
          Integer customerID = orderDetail.getCustomerID();      
          InitialContext jndiEnc = new InitialContext();
          CatalogHome catalogHome = 
            (CatalogHome)jndiEnc.lookup("java:comp/env/ejb/catalog");
          Iterator productIDs = orderDetail.getProductsIDs();
          while(productIDs.hasNext()){
            Integer productID = (Integer)productIDs.next();
            Catalog cat = CatalogHome.findByProductID(productID);
            cat.addCustomerToMailingList(customerID);
          }
     }
}

The MessageDrivenBean is a complete enterprise bean just like the session and entity beans, but there are some important differences. A message bean does not have a remote or home interface. That is because the message bean is not an RPC component. It does not have business methods that are invoked by EJB clients. A message bean listens to a virtual message channel (topic or queue) and consumes messages delivered by other JMS clients to that channel.

Messaging beans are composed of a bean class that implements the MessageDrivenBean interface and an XML deployment descriptor. Below is the definition of the MessageDrivenBean interface that all message beans must implement.

package javax.ejb;
import javax.jms.Message;
import javax.jms.MessageListener;
public interface MessageDrivenBean extends MessageListener{
  public void onMessage(Message message);
  public void ejbCreate();
  public void ejbRemove();
  public void setMessageDrivenContext(MessageDrivenContext mdc);
}

When a message-driven bean is deployed, it's assigned to handle messages from a specific topic or queue. Any messages sent by a JMS client (Java application, bean, or native client) will be forwarded by the message router to the message bean assigned to receive messages from that virtual channel. When a message is delivered to a message bean, one instance of that bean is selected by the EJB container from a pool to handle the message. The bean instance will receive the message when its onMessage() method is invoked and can process the message any way it sees fit. Once the message is consumed, it won't be delivered to any other instance of that message bean, providing that the transaction is not aborted.

Message beans are similar to stateless session beans in that both beans maintain no state between requests. Message-driven beans are therefore stateless but, like stateless session beans, they may have instance variables, which are maintained throughout the bean instances' life.

As a final note on the message bean, it's important to understand that messages do not have to be produced by other beans in order for the message bean to consume them. Message beans can consume messages from any topic or queue provided by a JMS-compliant vendor. Messages consumed by message beans may have come from other beans (session, entity, or message beans), non-EJB Java applications, or even non-Java applications that use a JMS-compliant vendor. A legacy application might, for example, use IBM's MQSeries to deliver messages to a queue, which is consumed by other legacy applications as well as message beans.

Conclusion

Enterprise JavaBean 2.0 embodies some fantastic changes from the previous specification. The new CMP model is far more flexible than the previous model, allowing entities to model complex object graphs while providing for more portability across containers. The definition of a common query language for find and select operations is greatly anticipated and will also help with portability.

The new MessageDrivenBean type will help bring the powerful messaging paradigm into the limelight enjoyed by EJB. Messaging is an extremely significant ingredient in the distributed computing mix, and its inclusion in EJB is a testament to its importance.

At the time of this writing, EJB 2.0 has just been released as a public draft, which means that it will probably change before it becomes a final specification. An attempt will be made to annotate this article if changes have a serious impact on the material presented here, but the specification is stabilizing, so truly momentous changes are unlikely.

Richard Monson-Haefel is the author of the recently released second edition of Enterprise JavaBeans. He is the lead architect of OpenEJB, an open source Enterprise JavaBeans 2.0 container and has consulted as an architect on Enterprise JavaBeans, CORBA, Java RMI, and other Java projects. Monson-Haefel also maintains a Website for the discussion of Enterprise JavaBeans and related distributed computing technologies.

Learn more about this topic

  • More articles by Richard Monson-Haefel

Join the discussion
Be the first to comment on this article. Our Commenting Policies