Serve It Up with J2EE 1.4 - Use JCA 1.5 and EJB 2.1 to Extend your App Server

J2EE application servers have become the standard for serving Web-based applications, and for good reason. Application servers provide a secure, reliable and manageable execution environment for Web applications using open, portable standards.

Introduction

J2EE application servers have become the standard for serving Web-based applications, and for good reason. Application servers provide a secure, reliable and manageable execution environment for Web applications using open, portable standards.

However, serving Web-based applications is pretty much it. Ok, there's JMS and RMI.IIOP, but J2EE 1.3 provides no mechanism for truly extending the protocols served by an application server. For example, if you wanted to implement a collaborative scheduling service using Calendar Access Protocol (CAP) you could not deploy it in a J2EE 1.3 server. You would not be able to take advantage of the J2EE environment.

Sun Microsystems' latest J2EE specification (1.4) has made significant changes in two complimentary features that will extend the capabilities of application servers. Java Connector Architecture (JCA) 1.5 and EJB 2.1 Message Driven Beans combine to allow new server components to be deployed within application servers in a portable and standardized way. Developers are now able to extend the services offered by the application server to any protocol or technology. The application server is now truly extensible.


Why is this important?

J2EE 1.4 is important for two reasons: it offers new innovation opportunities and it has significant implications on application server architecture.


New Innovative Possibilities

The application server can now serve anything. This opens the door to transform application servers into platforms for serving streaming media, monitoring SNMP, providing calendar servers or instant messaging servers. Application servers could serve highly sensitive applications using the strong authentication of Kerberos or something as basic as FTP.

These innovations are not limited to IP based services either. For example, application servers could serve SNA services.

Basically, you can serve most anything you can imagine. Serving from within the application server platform provides the security, reliability and manageability offered by application servers. These diverse servers can be administered as components within the application servers simplifying the management tasks of IS departments.


Application Server Architecture Implications

These advances can have important architectural implications on the way application servers themselves are implemented. Application servers can treat most resources as JCA connectors. This includes JDBC DataSources, JMS resources and potentially even the Servlet engine. These components can become JCA connectors and therefore simplify core application server designs. The core of an application server will manage transactions, deployed EJB components and JCA connectors. Everything else could be managed as a JCA connector and would not require special handling.

Theoretically, customers would be able to mix and match application server features. Users of one vendor's application server can deploy the JMS component from another vendor and the Database connection pooling from yet another vendor in a standardized way.


Requirements

There are some basic features that developers require in order to implement sound servers. Here are some of the features the application server must provide in order to implement services:

  • A threading model. Threading was explicitly forbidden in J2EE 1.3 and its predecessors but is required in order to perform any form of server processing.

  • An inbound component model. The component model must allow for application developers to develop and deploy components to receive and process requests. J2EE 1.3 introduced Message Driven Beans for this purpose. However, they were limited to JMS Message Listeners.

  • Deployment standards. A standardized method of deploying server components so that they are portable across application servers is required to achieve portability.

  • Transaction management consistent with other components and resources managed by the application server is also required. Transaction support is a very important requirement, but much too lengthy a topic for this article. Therefore, transactions will not be discussed accept to say that full support for distributed transactions is available. Furthermore, transactions may originate in a heterogeneous system outside the application server. That is, the application server can participate in transactions managed by another system.

J2EE 1.4 inbound connectors provide all of these features.


SMTP Example

Lets look in detail how server capabilities can be added to a J2EE 1.4 application server. We will explore developing a Simple Mail Transport Protocol (SMTP) server for deployment in a J2EE 1.4 application server. Don't worry, we won't spend too much time in SMTP; it's just a suitable example.


Message Driven Beans

Message Driven Beans were introduced in Enterprise J2EE 1.3 (EJB 2.0) to provide a transactional component model for inbound JMS Messages.

EJB 2.1 expands the definition of message driven beans to support arbitrary message types. This allows the deployment of components that may participate in transactions and listen for arbitrary events.

Message Driven Beans are implementations of inbound message listeners. The inbound messages are originated from resource adapters. The message listeners are described by a Java interface. For example the JMS message driven beans are described by the javax.jms.MessageListener interface. In our example, they are mail messages, but the interface can specify most anything. Here's the message listener interface for our example:


public interface MailMessageListener
{
    public void onMessage(javax.mail.Message message);
}

Message Driven Beans must implement the javax.ejb.MessageDrivenBean interface and the message listener interface. It must also follow the implementation requirements as specified by the EJB 2.1 specification. For example it must include an ejbCreate() method. Here's an example implementation:


public class MyMailMessageListener implements javax.ejb.MessageDrivenBean,
    com.acme.mail.MailMessageListener
{
    public MyMailMessageListener(){}

    public void ejbCreate() throws CreateException, EJBException
    {
    }

    public void ejbRemove() throws EJBException
    {
    }

    public void setMessageDrivenContext(MessageDrivenContext ctx)
        throws EJBException
    {
    }

    public void onMessage(javax.mail.Message message)
    {
        // store, relay or otherwise do something interesting
        // with the mail message
    }
}

The deployment descriptor tells the application server about the message driven bean. There are a couple of important things to point out in the descriptor. First, the descriptor defines the message listener class implemented by this message driven bean via the messaging-type element. The descriptor also specifies activation configuration properties. These properties are used during endpoint activation, which is covered later.


<?xml version="1.0" encoding="UTF-8"?>
<ejb-jar version="2.1" xmlns="http://java.sun.com/xml/ns/j2ee"
  xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
  D:Javaj2sdkee1.4libschemasejb-jar_2_1.xsd">
  <display-name>MyMailListener</display-name>
  <enterprise-beans>

    <!-- a message driven descriptor -->
    <message-driven>
      <display-name>MDB_MAIL_LISTENER</display-name>
      <ejb-name>MDB_MAIL_LISTENER</ejb-name>
      <ejb-class>MyMailMessageListener</ejb-class>

      <!-- The message listener interface -->
      <messaging-type>
        com.acme.mail.MailMessageListener
      </messaging-type>

      <transaction-type>Container</transaction-type>

      <!-- the values for the Activation Spec JavaBean -->
      <activation-config>
        <activation-config-property>
          <activation-config-property-name>
            Expression
          </activation-config-property-name>
          <activation-config-property-value>
            .*@hotmail.com
          </activation-config-property-value>
        </activation-config-property>
      </activation-config>

    </message-driven>
  </enterprise-beans>

  <assembly-descriptor>
    <container-transaction>
      <method>
        <ejb-name>MDB_MAIL_LISTENER</ejb-name>
        <method-name>*</method-name>
      </method>
      <trans-attribute>NotSupported</trans-attribute>
    </container-transaction>
  </assembly-descriptor>

</ejb-jar>

Messages are delivered to the message driven beans via JCA inbound connectors.


Java Connector Architecture

Java Connector Architecture (JCA) was introduced in J2EE 1.3 to provide for outbound connectors to enterprise information systems (EIS). JCA went a long way to integrate J2EE based applications into the enterprise. It provided a portable standard for managing and interacting with an EIS as connection oriented resources. Interactions with the EIS may also participate in distributed transactions. However, JCA 1.0 did not provide a mechanism for inbound connectors or a threading model for connectors.

JCA 1.5 adds these capabilities.


Resource Adapters

Deployable JCA components are called resource adapters. The JCA 1.5 specification defines resource adapters as: "a system-level software driver that is used by a Java application to connect to an EIS. The resource adapter plugs into an application server and provides connectivity between the EIS, the application server, and the enterprise application." Basically, resource adapters manage connections or other resources for interaction with some facility. The definition is intentionally vague because resource adapters can be used for almost anything.

Resource adapters can support inbound communication, outbound communication or both. In this article we will focus on inbound resource adapters.

Resource Adapters must implement the javax.resource.spi.ResourceAdapter interface and must be JavaBean compliant.

Here's the ResourceAdapter interface:


package javax.resource.spi;
...
public interface ResourceAdapter
{

    void start(BootstrapContext context)
        throws ResourceAdapterInternalException;

    void stop();

    void endpointActivation(MessageEndpointFactory, ActivationSpec)
        throws NotSupportedException;

    void endpointDeactivation(MessageEndpointFactory, ActivationSpec);

    XAResource[] getXAResources(ActivationSpec[] specs)
        throws ResourceException;
}

The resource adapter interface includes methods for the following that is of particular interest:

  • Methods to manage the lifecycle of the Resource Adapter.
  • Methods to manage the lifecycle of endpoints.

Let's build our Resource Adapter a little bit at a time.


Resource Adapter JavaBean

One feature of resource adapters is that they must conform to the JavaBean specification. As such, resource adapters may have properties to customize behavior. For our SMTP example, it would be nice to specify the port (in case we don't want to use port 25 for some reason).


public class SMTPResourceAdapter implements
    javax.resource.spi.ResourceAdapter, javax.resource.spi.work.Work
{
    private int port = 25;
    ...
    public SMTPResourceAdapter(){}

    public int getPort(){return port;}
    public void setPort(int port){this.port = port;}
    ...
}

Not all that exciting but when we discuss resource adapter deployment these properties will come up again.


Starting a Resource Adapter

An application server starts and stops a resource adapter using the start() and stop() methods respectively. Starting a resource adapter tells it to prepare resources, begin processing etc. The thread used to invoke the start method is an application server thread and may not be retained indefinitely. If the resource adapter is unable to successfully start (e.g., allocate resources, etc.) it must throw a ResourceAdapterInternalException.

For our SMTP Server example, this means creating a ServerSocket to listen on the configured port. Note this example uses J2SE 1.4 new I/O Channels. In practice, these provide more efficient thread pooling. Traditional sockets and streams can be used as well. This example cheats by not truly using non-blocking I/O, but hopefully you get the idea. So start() might include the following which creates a Socket Channel and binds it to the required port on the local host:


...
private ServerSocketChannel ssc = null;
private Selector selector = null;
...
public void start(BootstrapContext ctx)
    throws ResourceAdapterInternalException
{
    // This example uses non-blocking io API (even though it does not
    // fully make use of the non-blocking capabilities) to demonstrate
    // how one might use it within an inbound connector.
    // Create a new server socket and set to non blocking mode
    // Bind the server socket to the local host and port
    ssc = ServerSocketChannel.open();
    ssc.configureBlocking(false);//non-blocking I/O
    InetAddress lh = InetAddress.getLocalHost();
    InetSocketAddress isa = new InetSocketAddress(lh, getPort());
    ssc.socket().bind(isa);

    // The selector allows one thread to wait for multiple Channels
    // (sockets) making for more efficient use of Threads
    // Selectors can also monitor multiple channels for monitoring
    // multiple readers or multi-homed servers
    selector = SelectorProvider.provider().openSelector();

    // if we wanted to make full use of non-blocking I/O we could
    // also select for read operations and threads would not block
    // waiting for data.
    ssc.register(selector, SelectionKey.OP_ACCEPT);

    // now, we defer processing to another, long-running thread.
    // The JCA 1.5 threading model manages the execution of Work
    // while maintaining its own Thread pool.  This allows threading
    // without violating the Application Server's thread management
    // policies.
    ctx.getWorkManager().startWork(this);
}

Notice the last line of the start method. At this point in the resource adapter, we'd like to go into a loop accepting connections from clients. However, the thread in which start() was invoked is not ours to use indefinitely. We need to do work in another thread.


Threading

J2EE 1.4, as did its predecessors, forbids components from explicit thread management. The application server should manage threads exclusively. But, resource adapters can perform work in multiple concurrent threads that are managed by the application server using the Work Manager.

When starting a Resource Adapter the application server is required to provide a BootstrapContext instance. BootstrapContext provides access to the application server's WorkManager.


WorkManager

WorkManager is essentially the thread pool in which application Work may be performed in concurrent threads. Here's WorkManager:


public interface WorkManager
{
    void doWork(Work work) // startTimeout = INDEFINITE
        throws WorkException;

    void doWork(Work work, long startTimeout, ExecutionContext ctx,
        WorkListener lsnr) throws WorkException;

    long startWork(Work work) // startTimeout = INDEFINITE
        throws WorkException;

    long startWork(Work work, long startTimeout, ExecutionContext ctx,
        WorkListener lsnr) throws WorkException;

    void scheduleWork(Work work) // startTimeout = INDEFINITE
        throws WorkException;

    void scheduleWork(Work work, long startTimeout, ExecutionContext ctx,
        WorkListener lsnr) throws WorkException;
}

WorkManager has methods for "doing", "starting" or "scheduling" Work. Each method has slightly different blocking behavior as described in the following table:


Method Behavior
doWork Performs work synchronously.  That is control is returned from this method when the dispatched Work object's run method exits.  The work may be performed in the current thread context or using a pooled thread at the discretion of the Work Manager.
startWork Blocks until the Work execution has started in a pooled thread.  This method does not wait for the work to complete process but waits for it to start.
scheduleWork Returns immediately.  This method does not wait for the work to be started.  It drops it in a work "queue" and returns.  The Work Manager will dispatch it when able.

Work

Work is processing that is to be performed in a worker thread. Work may retain threads for long periods of time. However, one should be frugal about using threads for long durations. Reducing the number of long running threads can improve the scalability of the application server by limiting the number of threads required to service clients. If done correctly, few threads can service a large number of clients. The non-blocking I/O feature of J2SE 1.4 is one way to accomplish this.


public interface Work extends Runnable
{
    void release();
}

Implementations of Work may be run in WorkManager threads. A worker thread invokes run() when the work is dispatched to it by the Work Manager. Our example implements the Work interface and therefore may be used to execute work. The resource adapter or application server may invoke release() to hint to the Work object to release the thread as soon as is possible.

For our example, we want to loop accepting sockets until the resource adapter is stopped. So we add the following to our resource adapter:


public class SMTPResourceAdapter implements
    javax.resource.spi.ResourceAdapter, javax.resource.spi.work.Work
...
private boolean isRunning = false;

final synchronized boolean isRunning(){return isRunning;}
final synchronized void setRunning(Boolean running){isRunning = running;}

public void run()
{
    setRunning(true);
    try
    {
        // Here's where everything happens. The select will block
        // until a channel is ready to accept. We defer the servicing
        // of the client to another thread using the WorkManager
        while(isRunning())
        {
            if (selector.select() > 0)
            {
                SelectionKey sk = (SelectionKey)i.next();
                i.remove();

                // The key contains the channel ready for accept
                ServerSocketChannel ready =
                    (ServerSocketChannel) sk.channel();

                // accept the incoming socket
                SocketChannel channel = ready.accept();

                // serve the client in a separate thread
                workManager.scheduleWork(new SMTPProcessor(this, channel));
            }
        }
    }
    catch(...
}

public void release()
{
    setRunning(false);
    selector.wakeup();
}
...

Notice that, for our example, we defer reading and processing client requests to yet another thread. In our example, SMTPProcessor implements Work and services the client connection requests. This time it uses the scheduleWork() method which will not wait for the work to start and returns immediately.

The release() method causes the main worker thread to terminate by setting a flag and waking it up from waiting for connections.


Stopping a Resource Adapter

Stopping notifies that all processing should be terminated and all resources released by the Resource Adapter. In our example, stop may simply terminate using the release method.


public void stop()
{
    release();
}


Activation Specification

An activation specification is a JavaBean used during endpoint activation. Endpoint activation is the process of notifying resource adapters of eligible message listeners. The activation specification is a class implementing javax.resource.spi.endpoint.ActivationSpec. Here is the activation specification interface:


public interface ActivationSpec
{
    public ResourceAdapter getResourceAdapter();

    public void setResourceAdapter(ResourceAdapter)
        throws ResourceException;

    public void validate() throws InvalidPropertyException;
}

The activation specification specifies properties that are used to identify endpoint configuration information. For example, JMS resource adapters might include Message Selector criteria in the deployment descriptor. Resource adapters must define an activation specification for each message listener that it supports.

For our example, we will define one activation specification that provides a regular expression for selecting messages based on the recipient's email address.


public class SMTPActivationSpec implements ActivationSpec, Serializable
{
    private String expression = null;
    private Pattern pattern = null;

    private SMTPResourceAdapter ra = null;

    public SMTPActivationSpec()
    {
    }

    public String getExpression()
    {
        return expression;
    }

    public void setExpression(String expression)
    {
        this.expression = expression;
    }

    public ResourceAdapter getResourceAdapter()
    {
        return ra;
    }

    public void setResourceAdapter(ResourceAdapter resourceAdapter)
        throws ResourceException
    {
        ra = (SMTPResourceAdapter) resourceAdapter;
    }

    public void validate() throws InvalidPropertyException
    {
        try
        {
            pattern = Pattern.compile(expression);
        }
        catch(PatternSyntaxException e)
        {
            throw new InvalidPropertyException("invalid syntax");
        }
    }

    boolean accepts(String recipientAddress) throws InvalidPropertyException
    {
        if (pattern == null)
        {
            validate();
        }
        return pattern.matcher(recipientAddress).matches();
   }
}


Endpoint Activation

An endpoint is an application server proxy to a Message Driven Bean. An endpoint implements javax.resource.spi.endpoint.MessageEndpoint and the message listener interface. The application server provides endpoints to the Resource Adapter via javax.resource.spi.endpoint.MessageEndPointFactory during endpoint activation.

Endpoint activation is the process by which endpoint factories become associated with Resource Adapters. When a message driven bean is deployed, the application server invokes the endpointActivation() method on the resource adapters supporting the message listener type. During endpoint activation, the resource adapter saves the message endpoint factory for processing an incoming message. Here's how we implement it for our resource adapter.


public void endpointActivation(MessageEndpointFactory factory,
    ActivationSpec spec) throws NotSupportedException
{
    if ( ! (spec instanceof SMTPActivationSpec))
    {
        throw new NotSupportedException("invalid spec");
    }
    factories.put(spec, factory);
}

public void endpointDeactivation(MessageEndpointFactory factory,
    ActivationSpec spec)
{
    factories.remove(spec);
}

Resource Adapter Deployment

Resource adapters are described using a JCA deployment descriptor. The resource adapter deployment descriptor is contained in the file: META-INF/ra.xml. Here's the deployment descriptor for our example resource adapter.


<?xml version="1.0" encoding="UTF-8"?>
<connector xmlns="http://java.sun.com/xml/ns/j2ee"
  xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
  http://java.sun.com/xml/ns/j2ee/connector_1_5.xsd" version="1.5">

  <display-name>Acme Mail Adapter</display-name>
  <vendor-name>Acme Solutions, Inc.</vendor-name>
  <eis-type>SMTP Server</eis-type>
  <resourceadapter-version>1.0</resourceadapter-version>

  <resourceadapter>
    <resourceadapter-class>
      com.acme.mail.SMTPResourceAdapter
    </resourceadapter-class>

    <!-- ResourceAdapter default configuration properties -->
    <config-property>
      <config-property-name>Port</config-property-name>
      <config-property-type>java.lang.Integer</config-property-type>
      <config-property-value>25</config-property-value>
    </config-property>

    <!-The inbound resource adapter description -->
    <inbound-resourceadapter>
      <messageadapter>
        <messagelistener>
          <messagelistener-type>
            com.acme.mail.MailMessageListener
          </messagelistener-type>
          <activationspec>
            <activationspec-class>
              com.acme.mail.SMTPActivationSpec
            </activationspec-class>
            <required-config-property>
              <config-property-name>
                Expression
              </config-property-name>
            </required-config-property>
          </activationspec>
        </messagelistener>
      </messageadapter>
    </inbound-resourceadapter>
  </resourceadapter>
</connector>

The deployment descriptor contains some important information describing the resource adapter to the application server. It declares the Class of the resource adapter implementation. It also provides default values for the resource adapter's JavaBean properties. In our example, the descriptor defines the default port number.

The deployment descriptor also describes the inbound nature of the resource adapter. This is described using the inbound-resourceadapter element. This element declares is a list of supported message listeners. For each message listener, the deployment descriptor specifies an activation specification.


Message Delivery

Finally we can show how messages get delivered to the message driven bean. Here is a sequence diagram that illustrates the highlights of the message delivery processing for our SMTP resource adapter:



As we have already seen, the resource adapter defers processing a client connection to another worker thread. The SMTPProcessor receives and responds to the requests of the client as dictated by the protocol. Once a message is read it needs to pass the message to appropriate message listeners. First we need to identify target listeners based on the activation specification values given a recipient address. Then we need to get access to the MessageEndpoint (which is the application server's proxy of our message listener) and invoke the onMessage() method to pass it to the message driven bean.

Here's an excerpt of the code:


...
InternetAddress addr = ...//read from client
Message msg = ...//read from client
MessageEndpointFactory factory = null;

//iterate through the registered activation specs
Iterator i = ra.getFactories().keySet().iterator();
while (i.hasNext())
{
    SMTPActivationSpec spec = (SMTPActivationSpec) i.next();
    if (spec.accepts(addr.getAddress()))
    {
        // this spec should receive this message
        // get the endpoint factory paired with the activation spec
        factory = (MessageEndpointFactory)ra.getFactories().get(spec);

        // get an endpoint
        MailMessageListener endpoint =
            (MailMessageListener) factory.createMessageEndpoint(null);

        // invoke the method
        endpoint.onMessage(msg);
    }
}
...

The endpoint returned by the message endpoint factory will proxy the message to the deployed message driven bean. There you have it. The resource adapter manages network communication in a multithreaded manner and delivers messages to deployed message driven beans.


Summary

The examples shown demonstrate how to implement a server using the J2EE 1.4 features: JCA 1.5 and EJB 2.1. This article shows how the two features combine to provide multithreaded servers that can be written and deployed in an open and portable way. While we demonstrated these capabilities in the context of a SMTP server, the types of services we could provide using J2EE 1.4 are endless.

J2EE 1.4 makes application servers more versatile than ever before and truly extensible. J2EE 1.4 opens the door to new innovative applications that are deployed in the secure and reliable environment of application servers.


About the author

Wade Poziombka is a Software Architect at Cysive Inc. He has more than 10 years of software development experience, and is a Software Architect at Cysive, Inc. where he works in Product Development on the Cymbio Interaction Server. A Sun Certified Java Developer, he has a BS in Computer Science from Northern Illinois University. Wade may be reached at [email protected].

Dig Deeper on Software development best practices and processes