by James Carman

Get down to business

news
Jul 18, 200318 mins

Achieve implementation transparency for your business objects with the Business Object Factory framework

Many architects strive to reduce coupling between the presentation tier and the business logic tier in multitier applications. The Business Delegate design pattern (in Sun Microsystems’ Core Java 2 Platform, Enterprise Edition (J2EE) Patterns Catalog) was created to solve this problem. But, many critics claim it is an unnecessary added layer of abstraction for achieving this simple goal. Most argue that the business delegate’s interface mimics the business logic interface so closely that any change to the business logic interface also mandates a change to the business delegate’s interface. In most cases, this is true. However, the Business Delegate pattern has other benefits such as remoteness hiding, failure recovery, thread synchronization, and caching. But many applications that use the Business Delegate pattern do not take advantage of these additional benefits. Thus, the pattern’s overhead seems to outweigh its merits. For these applications, all that is desired is an abstraction layer that hides the underlying implementation details, such as the familiar lookup and narrow operations required for Enterprise JavaBeans (EJB) implementations. This article takes you through the API, configuration, and implementation of one such framework, the Business Object Factory framework.

Note: You can download this article’s source code from Resources.

The API

The Business Object Factory framework’s simplest part is the API itself. There is only one major class, but a few other “behind-the-scenes” classes can extend and customize the framework.

The BusinessObjectFactory class

Apart from the business interfaces used in their applications, consumers of business objects’ services need only concern themselves with one primary class, BusinessObjectFactory. Let’s look at its public interface:

public final class BusinessObjectFactory
{
  public BusinessObjectFactory getInstace();
  public Remote create( Class businessInterface );
}

As you can see, the BusinessObjectFactory class implements the Singleton design pattern (a Gang of Four design pattern). The BusinessObjectFactory‘s constructor is declared as private, and the class only maintains one static instance. This provides one global point of access to the functionality BusinessObjectFactory provides. We could just as easily make all of the methods static, but that approach is more object-oriented.

The create() method tends to raise a few eyebrows. First, it returns an instance of java.rmi.Remote. Most business object implementation choices, namely Remote Method Invocation (RMI), EJB, and the Java API for XML-based Remote Procedure Calls (JAX-RPC), require a remote interface. For simple JavaBean-based business object implementations, this adds the inconvenience of requiring the client code to catch java.rmi.RemoteException when it will never be thrown. However, this inconvenience helps provide the implementation transparency, as the business objects can be migrated to a different implementation type without altering the client code. The only stipulation is that the remote interface does not change. Another create() method peculiarity is its parameter type, java.lang.Class, which corresponds to the remote interface type requested. This allows the BusinessObjectFactory class to be loosely coupled with the application using it. This does not prevent company XYZ from creating an adapter class to simplify client programming:

public class XyzBusinessObjectFactory
{
  // Reference to singleton used for brevity, not required!
  private BusinessObjectFactory bof = BusinessObjectFactory.getInstance();
  
  public AccountServices createAccountServices()
  {
    return ( AccountServices )bof.create( AccountServices.class );
  }
}

In the above code, the XyzBusinessObjectFactory class encapsulates the required type casting involved when using the BusinessObjectFactory class and makes client coding much cleaner and type-safe.

Also notice that none of the BusinessObjectFactory class’s methods declare that they throw any checked exceptions (those that directly extend the java.lang.Exception class). The implementation will throw runtime exceptions (those that extend from java.lang.RuntimeExceptions) when problems occur, as these problems are typically indicative of a configuration issue and not necessarily an error within the application logic itself. The use of runtime exceptions greatly simplifies the client code, as runtime exceptions do not require try/catch blocks, but still allows for customized error trapping if required. When used in a Web application, a servlet filter or a customized error page defined in the web.xml file can be employed to catch these exceptions. The Java Data Objects (JDO) API uses a similar approach.

Another interesting note about the BusinessObjectFactory class is that it is declared final. This rules out the option of using the Abstract Factory-Factory Method design pattern combination so typical in implementation-hiding frameworks, such as the Data Access Object design pattern. However, as you’ll see, the Business Object Factory framework’s extensibility isn’t provided by subclasses, but through delegation.

The BusinessObjectFactoryDelegate class

The BusinessObjectFactory class delegates business object creation to one of its BusinessObjectFactoryDelegate instances:

public abstract class BusinessObjectFactoryDelegate
{
  protected abstract Remote doCreate();
  public final void setBusinessInterface( Class businessInterface );
  public final Class getBusinessInterface();
}

BusinessObjectFactory instances create business object instances that implement a specific remote business interface, represented by the businessInterface property. You can imagine different BusinessObjectFactoryDelegate implementation flavors. Each flavor would represent another possible business object implementation choice, such as JavaBeans, RMI, EJB, or JAX-RPC. I implement some examples later. Notice that client code does not need to be exposed to any BusinessObjectFactoryDelegate instances. The BusinessObjectFactory class takes care of all of the delegation internally, as we shall soon see. This is not the framework’s only hidden feature.

The InvocationDecorator interface

Client code sometimes uses another aspect of the Business Object Factory framework without even knowing it. Each invocation of a business interface can be optionally “decorated” by instances of the InvocationDecorator interface:

public interface InvocationDecorator
{
  public Object decorate( Method method, Object[] args, InvocationDecoratorChain chain ) throws Throwable;
}

The InvocationDecorator instances are given a chance to intercept each method invocation and add decorations to it. Some potential decorations include logging, transaction management, and authorization. Each InvocationDecorator instance can perform some operation and then pass control to the instance of InvocationDecoratorChain passed in as a parameter:

public interface InvocationDecoratorChain
{
  public Object decorate( Method method, Object[] args ) throws Throwable;
}

InvocationDecoratorChain objects track an ordered list of the InvocationDecorator instances and eventually call the desired method on the target business object. For those of you with Java Servlet API experience, this decorator idea seems strikingly similar to servlet filter support introduced in the Servlet API 2.3 specification. In fact, the idea is specifically borrowed from that specification. One consideration came to mind as I designed this specific part of the framework. Why not use aspect-oriented programming (AOP) and AspectJ to decorate each invocation? However, AOP requires recompilation to add aspects to your code. But, InvocationDecorator instances can be added to (and removed from) the InvocationDecoratorChain by merely changing the configuration file.

Configuration

A very simple XML file (aptly named BusinessObjectFactory.xml) configures the Business Object Factory framework. You can configure all delegates and invocation decorators by adding specific elements to the configuration file:

<business-objects>
  <delegate className="bof.delegates.BeanDelegate" businessInterface="xyz.IBusinessObject">
    <property name="beanClass" value="xyz.BusinessObjectImpl" />
    <decorator className="bof.decorators.CommonsLoggingDecorator" >
      <property name="logName" value="xyz.BusinessObjectImpl" />
    </decorator>
  </delegate>
</business-objects>

This example’s delegate element declares that an instance of bof.delegates.BeanDelegate, which we’ll see later, constructs all business objects implementing the xyz.IBusinessObject interface. The decorator element indicates that an instance of bof.decorators.CommonsLoggingDecorator, which we’ll also see later, should be added to the invocation decorator chain for the aforementioned delegate. Also, the nested property elements set the object’s properties represented by the enclosing element. For example, the bof.delegates.BeanDelegate‘s beanClass would be set to the xyz.BusinessObjectImpl class. Likewise, the bof.decorators.CommonsLoggingDecorator instance’s logName property would be set to the java.lang.String value, xyz.BusinssObjectImpl.

Implementation

First, I show how the BusinessObjectFactory class actually performs the delegation to its constituent delegates. Remember that each delegate is responsible for creating objects implementing a specific business interface, represented by a java.lang.Class instance. Therefore, you must maintain a mapping between Class objects and BusinessObjectFactoryDelegate instances. For this, you use a java.util.Map object:

public class BusinessObjectFactory 
{
  private Map delegateMap = new HashMap();
  
  private BusinessObjectFactory()
  {
        // Prevents instantiation by other classes.  Enforces the Singleton design pattern.
  }
    
  private void lock()
  {
    delegateMap = Collections.unmodifiableMap( delegateMap );
  }
  
  public Remote create( Class businessInterface )
  {
      if( businessInterface == null )
      {
          throw new IllegalArgumentException( "Cannot pass null java.lang.Class reference to BusinessObjectFactory.create() method." );
      }
      if( !businessInterface.isInterface() )
      {
          throw new IllegalArgumentException( "Parameter to BusinessObjectFactory.create() method must be an interface." );
      }
      if( !Remote.class.isAssignableFrom( businessInterface ) )
      {
          throw new IllegalArgumentException( "Interface parameter to BusinessObjectFactory.create() method must extend java.rmi.Remote." );
      }
      return getDelegate( businessInterface ).create();
  }
    private BusinessObjectFactoryDelegate getDelegate( Class businessInterface )
    {
      final BusinessObjectFactoryDelegate delegate = ( BusinessObjectFactoryDelegate ) delegateMap.get( businessInterface );
      if( delegate == null )
      {
          throw new ApplicationResourceException( "No delegate defined for interface " + businessInterface.getName() );
      }
      return delegate;
    }
    private void addDelegate( BusinessObjectFactoryDelegate delegate )
    {
      delegateMap.put( delegate.getBusinessInterface(), delegate );
    }
}

In the above code, I included only the BusinessObjectFactory class’s instance (nonstatic) methods. I discuss the static implementation methods in the “Load the Configuration File” section below. The ApplicationResourceException class is a runtime exception class that indicates some dependent resource is missing (no configuration file) or corrupt (insufficient configuration information). As you can see, the delegateMap object looks up the appropriate BusinessObjectFactoryDelegate instance. The create() method merely delegates creation to the create() method of the BusinessObjectFactoryDelegate instance returned from the getDelegate() method. The create() method’s name is not a misprint. You’ll soon see that the create() method is a package-private method that in turn calls the abstract doCreate() method listed earlier. The lock() method prevents any subsequent calls to the addDelegate() method to fail, since the delegateMap object becomes unmodifiable. However, how do the BusinessObjectFactoryDelegate objects get into the delegateMap object in the first place?

Load the configuration file

The Apache Software Foundation provides an open source library, Jakarta Commons Digester, perfectly suited for loading XML configuration files. Documentation about the Digester library is outside this article’s scope, but a thorough explanation of the Commons Digester can be found on its homepage (see Resources). The discussion that follows assumes a basic understanding (rules, the object stack, etc.) of the Digester library. Here, I just show how you use the Digester library to load the configuration information:

public class BusinessObjectFactory
{
  private static final String CONFIG_FILE = "BusinessObjectFactory.xml";
  private static final Object monitor = new Object();
  private static BusinessObjectFactory instance;
  public static BusinessObjectFactory getInstance()
  {
    synchronized( monitor )
    {
      if( instance == null )
      {
        instance = new BusinessObjectFactory();
        final Digester digester = new Digester();
        digester.addSetProperty( "*/property", "name", "value" );
        digester.addObjectCreate( "business-objects/delegate", "className", BeanDelegate.class );
        digester.addSetProperties( "business-objects/delegate" );
        digester.addSetNext( "business-objects/delegate", "addDelegate" );
        digester.addObjectCreate( "business-objects/delegate/decorator", "className", CommonsLoggingDecorator.class );
        digester.addSetProperties( "business-objects/delegate/decorator" );
        digester.addSetNext( "business-objects/delegate/decorator", "addDecorator" );
        digester.push( instance );
        try
        {
            digester.parse( Thread.currentThread().getContextClassLoader().getResourceAsStream( CONFIG_FILE ) );
            instance.lock();
        }
        catch( IOException e )
        {
            throw new ApplicationResourceException( "Unable to parse configuration file.", e );
        }
        catch( SAXException e )
        {
            throw new ApplicationResourceException( "Corrupt configuration file.", e );
        }
      }
      return instance;
    }
  }
  
  private static class AddDelegateRule extends Rule
  {
    private final BusinessObjectFactory bof;
    public AddDelegateRule( BusinessObjectFactory bof )
    {
        this.bof = bof;
    }
    public void begin( String s, String s1, Attributes attributes ) throws Exception
    {
        bof.addDelegate( ( BusinessObjectFactoryDelegate ) digester.peek() );
    }
  }
  
  private static class AddDecoratorRule extends Rule
  {
    public void begin( String s, String s1, Attributes attributes ) throws Exception
    {
        ( ( BusinessObjectFactoryDelegate ) digester.peek( 1 ) ).addDecorator( ( InvocationDecorator ) digester.peek() );
    }
  }
}

First, notice that I used a special object for synchronization called monitor. This prevents outside code from interfering with the synchronization, potentially causing a deadlock. Next, I assigned the Digester instance a series of rules telling it what to do with each XML element it encounters, summarized as follows:

  1. Each time you see a <property> element, set the property identified by the name attribute to the value specified by the value attribute.
  2. Each time you encounter a <delegate> element, create an instance of the class identified by the className attribute and push it onto the top of the stack. Default class is BeanDelegate.
  3. Each time you encounter a <delegate> element, set property values for each attribute name that has a matching property name to the attribute’s value.
  4. Each time you encounter a <delegate> element, call the addDelegate() method of the object below the object on top of the stack (a BusinessObjectFactory instance), passing in the object on top of the stack (a BusinessObjectFactoryDelegate instance) as a parameter.
  5. Each time you encounter a <decorator> element, create an instance of the class identified by the className attribute and push it onto the top of the stack. Default class is CommonsLoggingDecorator.
  6. Each time you encounter a <decorator> element, set property values for each attribute name that has a matching property name to the attribute’s value.

  7. Each time you encounter a <decorator> element, call the addDecorator() method of the object below the object on top of the stack (a BusinessObjectDelegate instance), passing in the object on top of the stack (an InvocationDecorator instance) as a parameter.

A few interesting implementation details are notable here. First, notice that I pushed the BusinessObjectFactory singleton instance onto the Digester’s stack prior to parsing the configuration file. Rule Number 4 requires that the object underneath the object on top of the stack must be an instance of BusinessObjectFactory. Next, notice the call to the lock() method after parsing the configuration file. Other objects should not be allowed to add BusinessObjectFactoryDelegate objects to the BusinessObjectFactory singleton instance once it has been completely configured via the configuration file.

Implement InvocationDecorator support

Java 2 Platform, Standard Edition (J2SE) 1.3 introduced a powerful new concept to the Java language, dynamic proxies. Since my framework is based upon interfaces, it offers you the opportunity to use dynamic proxies to implement support for InvocationDecorators. As previously mentioned, the BusinessObjectFactoryDelegate‘s create() method calls the abstract doCreate() method. This allows you to interject a dynamic proxy, which iterates through the invocation decorator chain:

public class BusinessObjectFactoryDelegate
{
  private final List decorators = new LinkedList();
  Remote create()
  {
    final Remote target = doCreate();
    return ( Remote ) Proxy.newProxyInstance( Thread.currentThread().getContextClassLoader(),
                                              new Class[]{getBusinessInterface()},
                                              new InvocationDecoratorInvocationHandler( target ) );
  }
  
  public final void addDecorator( InvocationDecorator decorator )
  {
    decorators.add( decorator );
  }
    
  private class InvocationDecoratorInvocationHandler implements InvocationHandler, InvocationDecoratorChain
  {
    private final Remote target;
    private final Iterator decoratorIterator;
    public InvocationDecoratorInvocationHandler( Remote target )
    {
        this.target = target;
        decoratorIterator = Collections.unmodifiableCollection( decorators ).iterator();
    }
    public Object invoke( Object proxy, Method method, Object[] args ) throws Throwable
    {
        return decorate( method, args );
    }
    public Object decorate( Method method, Object[] args ) throws Throwable
    {
        if( decoratorIterator.hasNext() )
        {
            final InvocationDecorator nextDecorator = ( InvocationDecorator ) decoratorIterator.next();
            return nextDecorator.decorate( method, args, this );
        }
        else
        {
            return method.invoke( target, args );
        }
    }
  }
}

Now we see what wasn’t shown before from the BusinessObjectFactoryDelegate class (I omitted the getter/setter method implementation for the businessInterface property for succinctness). The create() method returns a dynamic proxy instance that implements the desired business interface, using an instance of the inner class InvocationDecoratorInvocationHandler as its invocation handler. The InvocationDecoratorInvocationHandler class merely calls the invoke() method of each InvocationDecorator object in the decorator collection, passing in itself as the InvocationDecoratorChain parameter. Let’s look at some example BusinessObjectFactoryDelegate implementations.

Example delegates

First, we examine the simplest case, a JavaBean-based implementation. The BeanDelegate class contains one property, beanClass of type java.lang.Class, which tells it what type of JavaBean to create:

public class BeanDelegate extends BusinessObjectFactoryDelegate
{
  protected Remote doCreate()
  {
    try
    {
        return ( Remote )beanClass.newInstance();
    }
    catch( InstantiationException e )
    {
        throw new ApplicationResourceException( "Unable to instantiate bean of type " + beanClass.getName() + ".", e );
    }
    catch( IllegalAccessException e )
    {
        throw new ApplicationResourceException( "Default constructor of bean class " + beanClass.getName() + " not accessible.", e );
    }
    catch( ClassCastException e )
    {
        throw new ApplicationResourceException( "Bean class " + beanClass.getName() + " does not implement the " + Remote.class.getName() + " interface.", e );
    }
  }
}

The BeanDelegate class uses reflection to create a new instance of the desired class for every doCreate() method invocation. Provided the implementation is thread-safe, another approach that might be more efficient uses a single instance of the bean class. Or, if the implementation is not thread-safe, a pool of objects could be used to avoid the overhead of excessive object creation, which as we know, is very expensive in Java. Since there is no way to return the object to the pool, however, a timeout value for the business objects would be required. In a J2EE application server, EJB use would eliminate such design decisions because the EJB container addresses those problems:

public class EjbDelegate extends BusinessObjectFactoryDelegate
{
  private static final Class[] CREATE_PARAM_TYPES = new Class[]{Void.TYPE};
  private static final Object[] CREATE_PARAMS = new Object[0];
  private static final String CREATE_NAME = "create";
  private final Object monitor = new Object();
  private String jndiName;
  private Class homeInterface;
  private EJBHome ejbHome;
  private Method createMethod;
  protected Remote doCreate()
  {
      try
      {
          return ( EJBObject ) getCreateMethod().invoke( getEjbHome(), CREATE_PARAMS );
      }
      catch( IllegalAccessException e )
      {
          throw new ApplicationResourceException( "Unable to access create method.", e );
      }
      catch( InvocationTargetException e )
      {
          throw new ApplicationResourceException( "Create method threw an exception.", e );
      }
  }
  public Class getHomeInterface()
  {
      return homeInterface;
  }
  public void setHomeInterface( Class homeInterface )
  {
      this.homeInterface = homeInterface;
  }
  public String getJndiName()
  {
      return jndiName;
  }
  public void setJndiName( String jndiName )
  {
      this.jndiName = jndiName;
  }
  private Method getCreateMethod()
  {
      synchronized( monitor )
      {
          if( createMethod == null )
          {
              try
              {
                  createMethod = getEjbHome().getClass().getMethod( CREATE_NAME, CREATE_PARAM_TYPES );
              }
              catch( NoSuchMethodException e )
              {
                  throw new ApplicationResourceException( "Home interface does not define a no-argument create method." );
              }
          }
      }
      return createMethod;
  }
  private EJBHome getEjbHome()
  {
      synchronized( monitor )
      {
          if( ejbHome == null )
          {
              final EnvironmentNamingContext enc = new EnvironmentNamingContext();
              ejbHome = enc.getEjbHome( jndiName, getHomeInterface() );
          }
      }
      return ejbHome;
  }
}

In the above example, the EjbDelegate class uses reflection to call the EJBHome object’s create() method for the EJB component. Notice that the reference to the EJBHome object and the create() method are both cached. Also notice that in the getEjbHome() method, I use the EnvironmentNamingContext class (see sidebar, “The EnvironmentNamingContext Class“) to look up the EJBHome object. This approach requires you to extend the remote business interface to create your EJB remote interface:

public interface MyBusinessInterface extends Remote
{
  public void someBusinessMethod() throws RemoteException;
}
public interface MyEjb extend EJBObject, MyBusinessInterface
{
}

This approach does not allow you to call the EJBObject interface’s remove() method, since the client would be dealing only with the remote business interface. However, when you use stateless session beans, this is inconsequential, as the remove() method does nothing when invoked upon the EJBObject or EJBHome for stateless session beans. With stateful session beans, problems do arise because an invocation to the remove() method on EJBObject or EJBHome actually decommissions the EJBObject, allowing the EJB container to release its resources. However, most application servers allow you to specify a timeout for stateless session beans, so they can be automatically removed if a client neglects to clean up after itself. I will not discuss using entity beans with this framework, as it is not recommended. Entity beans should be hidden or encapsulated using the Session Façade pattern (again in Sun’s Core J2EE Patterns Catalog). Delegates are the framework’s major extension mechanism, but invocation decorators can also be very powerful.

Example invocation decorator

Suppose you want to log each invocation to a business object’s methods. You can implement an invocation decorator using the Jakarta Commons Logging library:

public class CommonsLoggingDecorator implements InvocationDecorator
{
    private String logName = getClass().getName();
    private Log log;
    public Object decorate( Method method, Object[] args, InvocationDecoratorChain chain ) throws Throwable
    {
        try
        {
            getLog().debug( "Entering " + method );
            return chain.decorate( method, args );
        }
        finally
        {
            getLog().debug( "Leaving " + method );
        }
    }
    public String getLogName()
    {
        return logName;
    }
    public void setLogName( String logName )
    {
        this.logName = logName;
    }
    private Log getLog()
    {
        if( log == null )
        {
            log = LogFactory.getLog( logName );
        }
        return log;
    }
}

Here, the log name defaults to the CommonsLoggingDecorator class name. However, this can be overridden using the configuration file and a <property> element.

Future enhancements

The Business Object Factory framework is very useful in its current form, but there is always room for improvement in any framework. One particular improvement would eliminate the need for a timeout value in the BeanDelegate when using an object pool approach and allow you to invoke the remove() method on stateful session beans. The API change would involve introducing a close() method to the BusinessObjectFactory and BusinessObjectFactoryDelegate public interfaces:

public final class BusinessObjectFactory
{
  public BusinessObjectFactory getInstace();
  public Remote create( Class businessInterface );
  public void close( Remote businessObject );
}
public abstract class BusinessObjectFactoryDelegate
{
  protected abstract Remote doCreate();
  public final void setBusinessInterface( Class businessInterface );
  public final Class getBusinessInterface();
  public void close( Remote businessObject );
}
public 

The implementation would need to route calls to the appropriate delegate that created the business object in the first place, making the code somewhat tricky.

Build your business object implementation

The Business Object Factory framework, with or without the business delegates, provides implementation transparency for the business objects. If used in conjunction with business delegates, it alleviates some of the business delegates’ responsibility, allowing them to concentrate on the value-add services (caching, remoteness hiding, etc.) they provide. If used alone, the client code uses the BusinessObjectFactory class directly, allowing the client to focus solely on the business interface rather than the implementation-specific details. As a product matures, mandating more stringent performance and scalability requirements, the business object implementation can be upgraded without requiring changes to the user interface or presentation code.

James Carman, an independent consultant, has been working with Java since 1997. He is a Sun Certified Developer for the Java 2 Platform, Sun Certified Web Component Developer for J2EE, and a Sun Certified Architect for Java Technology. He serves as codirector of the Cincinnati Java Users Group, where he frequently gives talks about various Java topics (J2EE architecture, JavaSpaces, Java Message Service, Java security, and Java Web services, to name a few).