Alexander Sakharov
 
     
 

Automation of Component Communication in Java

OOPSLA 2001 Slides (pdf)

Problem Statement

When software components are assembled in applications, they communicate to each other without having direct static references between them. Component communication amounts to property change propagation and inter-component calls including event method calls. Many of inter-component (communication) calls have to be dynamically dispatched. Writing this dispatching code could be quite intricate even when employing the command pattern. Communication-related code is often multi-threaded. Hard-coded component communication implementations are error-prone and difficult to maintain for applications having big quantities of components.

In traditional one-tier applications, components are usually assembled into hierarchies. Automation of the assembly of visual components in composites is supported by many tools, e.g. by Visual Basic. There are also general-purpose component assembly tools for Java: BeanBox; ContextBox. Both BeanBox and ContextBox let users bind event sources with event listeners. They generate an adapter for synchronous event delivery between source-listener pairs. These tools also allow users to bind properties of different components given that property changes are announced.

One significant problem with these tools is that they only help automate synchronous event delivery with listeners bound at design time. Component interaction is not always so in real life. These tools do not support dynamic dispatching of calls at all. Another problem with them is that they help bind properties of only those components that announce property changes. Most components are not designed to generate property change events. These two tools also require matching types for events and bound properties.

In enterprise applications, one client, e.g. a servlet, communicating with multiple components, e. g. EJBs or JSPs, is the most typical communication model. Automation of component communication for this model has not been much addressed.

Solution

There is a generic solution to the problem of automating implementation of component communication in Java at the level of one JVM. It applies to J2EE components tied with one client as well. J2EE components may be remote objects (EJBs) or non-Java objects (JSPs). J2EE components having a common client are usually represented by their proxies (or stubs) on the same JVM as the client. First, let us introduce class JSPProxy representing JSPs on the side of a client servlet. Second, let us use a generator for automated construction of wrapper classes for EJBs.

JSPs may be collocated with their client servlet or may reside remotely. JSPProxy instances are components that represent JSPs on the client JVM. A JSP is the JSPProxy constructor's only parameter.


   public JSPProxy(String page) {
      this.jsp = page;
   }

JSPProxy connects to the JSP via using RequestDispatcher.


   public void forward(HttpServletRequest req, HttpServletResponse resp) throws ServletException {
      RequestDispatcher dispatcher= req.getRequestDispatcher(this.jsp);
      dispatcher.forward(req, resp);
   }

Ironically, EJBs are not designed for dynamic binding; these wrapper classes fill the gap. A single wrapper class represents each EJB on the client side as opposed to a combination of EJBObject and EJBHome. Wrapper constructors create a home object. A home interface's JNDI name is their only parameter. Once, wrapper is constructed, a 'create' method coming from the home interface should be applied to it. After that, business methods of the remote interface can be used.

Sample home interface methods:


   public Foo create(int id) throws CreateException, RemoteException;
   public Foo findByPrimaryKey(FooPK pk) throws FinderException, RemoteException;

Sample remote interface methods:


   public String getName() throws RemoteException;
   public void setName(String str) throws RemoteException;
   ...
   public void process() throws RemoteException;

Sample wrapper:


   public class FooWrapper implements FooHome, Foo {
      private FooHome home = null;
      private Foo remote = null;
      public FooWrapper(String name) throws RemoteException, NamingException;
      // home interface methods
      // remote interface methods
   }

Client application using the wrapper:


   FooWrapper wrapper = new FooWrapper("ejb/FooHome");
   wrapper.create(...);
   wrapper. setName("...");

This approach allows users to specify interactions for a component collection, and then, a communication adapter is automatically generated from the specifications. The communication specifications are comprised of both declarative specifications and procedural ones. Dynamic dispatching of incoming calls is a primary function of generated adapters. Depending on the specifications, adapters facilitate synchronous or asynchronous communications. These adapters support binding of all properties including ones not announcing their changes.

This solution takes advantage of component hierarchies. The hierarchies regulate component participation in communication as well as call dispatching. A component is active when it listens to events or service requests and possibly generates events or requests. In communication hierarchies, a sub-component may only be active when its parent is. Events and other calls are always dispatched to the innermost relevant component. If several components belonging to different branches of a communication tree implement the same action, then the call is dispatched to all of them.

Features and Benefits

When to Use This Approach

Communication Specifications

Declarative:

Procedural:

Adapter Generation

Class AdapterGenerator is a code generator that builds communication adapter classes as extensions of their specification classes. The adapter's constructor takes a hash table of component objects keyed on their identifiers as the only argument. The adapter has a method to reset these objects. The generated adapters implement communication interfaces including listener interfaces. The adapter should be registered as a sole communication listener via methods like add…EventListener, BeanContextServiceSupport, etc. If there are listener method name conflicts, inner classes are generated. The respective identifiers become their names, and instances of these inner classes should be registered instead of the adapter. Adapter's methods getListener return the instances to register.

The adapters queue parameters of calls of asynchronous communication interfaces. The adapters are Runnable in presence of asynchronous interfaces. The adapters construct and fire multiple threads when one call goes to several components simultaneously. In addition to dispatching communication calls, the adapters serve as property containers; they perform and monitor property updates induced by the specifications. Property changes take place right after fully processing each communication call. The adapters convert property types and communication call parameters/results when transformation methods are specified.

----------

See also my article 'Automation of Component Communication in Java' in OOPSLA 2001 Companion.
 

Copyright © 2001 Alexander Sakharov

Need to relax? Try brain teasers. I would recommend those marked 'cool'.