Java Tip 110: Implement the Observer pattern with EJBs

Use this convenient framework to assign event listeners to EJBs

The Observer pattern is probably one of the best ways to reduce coupling between objects. For example, if you are writing a typical application, you may decide to provide a factory or manager that fires appropriate events, and provide separate business logic functionality as a set of listeners of those events. The system’s startup class would then assign such listeners to the factory or manager right after it was internally created.

In most J2EE systems such factories/managers are stateless session beans. EJB containers handle the demand for stateless session beans by creating as many instances of them as required or by reusing existing ones. The problem is that each new bean instance must be initialized with exactly the same set of listeners as exists for all other instances. A logical solution occurs when a stateless session bean instance is created, and the bean goes to some repository where it somehow retrieves relevant listeners and attaches them to itself. In this tip, I will show you how to implement this solution.

An example scenario

Consider this typical design scenario. An online auctioning system has a stateless session bean called AuctionFactory, which creates auction objects. For each newly created auction, the business logic requires the system to do some extra work, like sending email, updating user profiles, and so on. In many systems, the code for creating an auction and performing the aforementioned tasks looks like this:

public Auction createAuction(int numOfContainers) throws RemoteException{
    SomeAuctionClass auction = new SomeAuctionClass (numOfContainers);
    //and after creation there is notification code like this(instead of
    //firing an "auction created" event):
    sendEmailsAboutNewAuction(auction);
    updateUserProfiles(auction);
    doOtherNotificationStuffAboutNewAuction(auction);
    //etc..
    return auction;
}

The reason for such poorly written code lies in the difficulties of initializing each bean instance with some set of required listeners. If this bean were a publisher of events and each bean instance were initialized with a set of required listeners, the code would be much cleaner and robust, like this:

public Auction createAuction(int numOfContainers) throws RemoteException{
    SomeAuctionClass auction = new SomeAuctionClass (numOfContainers);
    fireAuctionCreated(auction);
    return auction;
}

Framework description

The framework principle I will describe is very simple. A ListenerRegistry class maps a publisher class and the listeners that need to be assigned to it. The system’s startup module initializes the ListenerRegistry with a required set of listeners for each publisher type. When each publisher is constructed or activated, it goes to the ListenerRegistry, passes its class to the ListenerRegistry, and in return gets a set of listeners. Then the publisher assigns all these listeners to itself. It’s that simple.

The following UML diagram depicts the ListenerRegistry interface and the relationship between participants in the framework:

UML diagram

After looking at the diagram, you may naturally ask, “What is a ListenerSupplier?” and “Why can’t I register and work directly with EventListeners?” Of course you could; actually, the first version of this framework used listeners directly. But if you use listeners in the ListenerRegistry, they must exist at the moment of registering. On the other hand, if you register a mediator called ListenerSupplier, you have the freedom to postpone the creation/retrieval of a listener until it’s absolutely needed. ListenerSuppliers are similar to factories, to use “Gang of Four” terminology, but different in that they don’t necessarily create new listeners. Their purpose is to return a listener. It is up to you, the developer, to decide whether the supplier creates a new listener or returns the same instance every time the getListener() method is called.

That said, by ListenerRegistry working with suppliers, you can establish a relationship between publishers and observers (or listeners) without either of them existing at that moment. In my opinion, that is a very important benefit; it’s lazy instantiation for publishers and observers.

Framework implementation

In this section, you will find the code of all the framework participants. I assume you understand the basics of EJBs, synchronization, and of course, the core Java libraries. You can find the complete source code for this framework in Resources.

Here is the code of the ListenerRegistry interface:

//ListenerRegistry.java
package com.jwasp.listener;
import java.util.EventListener;
import java.rmi.RemoteException;
import com.jwasp.listener.ListenerSupplier;
/**
 * Core of the framework. Maps publisher class to ListenerSuppliers.
 * @author Roman Stepanenko
 */
public interface ListenerRegistry {
  void addListenerSupplier(ListenerSupplier listenerSupplier, Class publisherClass);
  void removeListenerSupplier(ListenerSupplier listenerSupplier, Class publisherClass);
  EventListener[] getListeners(Class publisherClass) throws RemoteException, ListenerActivationException;
}

Here is the ListenerSupplier interface:

//ListenerSupplier.java
package com.jwasp.listener;
import java.util.EventListener;
/**
 * Convenient mediator responsible for creation/retrieval of the corresponding listener.
 * @author Roman Stepanenko
 */
public interface ListenerSupplier {
/**
 * Returns listener corresponding to the specified publisher class.
 */
  EventListener getListener(Class publisherClass) throws java.rmi.RemoteException, ListenerActivationException;
}

Here is the default implementation of ListenerRegistry:

//DefaultListenerRegistry.java package com.jwasp.listener; import java.util.*; import java.rmi.RemoteException; import com.jwasp.listener.ListenerRegistry; import com.jwasp.listener.ListenerSupplier; /** * Basic implementation of ListenerRegistry. This class is a singleton. * When publisher asks for listeners this registry will return not only * listeners registered explicitly (by means of ListenerSuppliers) for the specified publisher class but also registered * listeners for all parent classes of the publishers. For example:

* If publisher B extends publisher A, and there are suppliers registered for A, then if you pass B.class as an argument for getListeners * method, you would get not only listeners registered explicitly for B but also listeners for all parents of B (A, in our example). * @author Roman Stepanenko */ public class DefaultListenerRegistry implements ListenerRegistry{ private DefaultListenerRegistry(){} public static DefaultListenerRegistry getInstance(){ return instance; } public synchronized void addListenerSupplier(ListenerSupplier listenerSupplier, Class publisherClass) { assertNotNull("Publisher class is null", publisherClass); assertNotNull("ListenerSupplierr is null", listenerSupplier); Collection listenerSuppliers = (Collection)myListenerSuppliersMap.get(publisherClass); if ( listenerSuppliers == null ) { listenerSuppliers = new ArrayList(); myListenerSuppliersMap.put(publisherClass, listenerSuppliers); } listenerSuppliers.add(listenerSupplier); } public synchronized void removeListenerSupplier(ListenerSupplier listenerSupplier, Class publisherClass) { assertNotNull("Publisher class is null", publisherClass); assertNotNull("ListenerSupplierr is null", listenerSupplier); Collection listenerSuppliers = (Collection)myListenerSuppliersMap.get(publisherClass); if ( listenerSuppliers == null ) { return; } listenerSuppliers.remove(listenerSupplier); if ( listenerSuppliers.isEmpty() ) { myListenerSuppliersMap.remove(publisherClass); } } /** * Returns an array of EventListeners registered for the specified publisher class. If this registry contains * listener suppliers registered for the publisher, it iterates all such suppliers and invokes ListenerSupplier.getListener(publisherClass) * method on each. * @param publisherClass class of the publisher. * @return array of EventListeners */ public EventListener[] getListeners(Class publisherClass) throws RemoteException,ListenerActivationException { Collection listenerSuppliers = getListenerSuppliersCopy(publisherClass, true);//set to false to disable inheritance check EventListener[] array = new EventListener[listenerSuppliers.size()]; Iterator i = listenerSuppliers.iterator(); int count = 0; while (i.hasNext()){ ListenerSupplier listenerSupplier = (ListenerSupplier)i.next(); array[count] = listenerSupplier.getListener(publisherClass); count++; } return array; } /** * Returns shallow copy of currently registered listenerSuppliers for the specified publisher class. Method is synchronized. This * allows to keep getListeners method non-synchronized. * @param publisherClass * @param checkInheritance If true, will return suppliers registered for specified publisher class and * all parents of the specified publisher class. * If false, will return only those suppliers that are registered only for the specified publisher class. * @return Collection of corresponding suppliers (never null). */ private synchronized Collection getListenerSuppliersCopy(Class publisherClass, boolean checkInheritance){ if ( checkInheritance ) { Collection publishers = myListenerSuppliersMap.keySet(); Iterator i = publishers.iterator(); Collection listenerSuppliers = new ArrayList(); while (i.hasNext()) { Class nextPublisherClass = (Class)i.next(); if ( nextPublisherClass.isAssignableFrom(publisherClass) ) { if ( myListenerSuppliersMap.get(nextPublisherClass) != null ) { listenerSuppliers.addAll((Collection)myListenerSuppliersMap.get(nextPublisherClass)); } } } return listenerSuppliers; } else { Collection listenerSuppliers = (Collection)myListenerSuppliersMap.get(publisherClass); if ( listenerSuppliers == null ) { return Collections.EMPTY_LIST; } else { return (ArrayList)((ArrayList)listenerSuppliers).clone();//if you decide not to use ArrayList use appropriate cast if clone is supported } } } /** * Simple non-null assertion method. */ protected void assertNotNull(String message, Object object){ if ( object == null ) { throw new IllegalArgumentException(message); } } protected HashMap myListenerSuppliersMap = new HashMap(); //keeps "publisher class -> Collection of ListenersSuppliers" pairs. private static DefaultListenerRegistry instance = new DefaultListenerRegistry(); }

Here is the exception used to indicate an error in obtaining a listener:

//ListenerActivationException.java
package com.jwasp.listener;
/**
 * Exception indicating an error while getting listener.
 * @author Roman Stepanenko
 */
public class ListenerActivationException extends Exception{
  public ListenerActivationException(String message){
    super(message);
  }
}

ListenerSupplier examples

Here are two convenient ListenerSupplier examples. The first example is intended to work with thread-safe listeners, and will return the same listener for all relevant publishers.

//InstanceSupplier.java
package com.jwasp.listener;
import java.util.EventListener;
import com.jwasp.listener.ListenerSupplier;
/**
 * Returns the same listener for each getListener() call. Listener is supposed to be thread-safe.
 * @author Roman Stepanenko
 */
public class InstanceSupplier implements ListenerSupplier{
  public InstanceSupplier(EventListener listener) {
    myListener = listener;
  }
  public EventListener getListener(Class publisherClass) {
    return myListener;
  }
  private EventListener myListener;
}

The second sample supplier lets you use listeners that are stateless session beans. It is constructed with an EJBHome of the session bean and uses the create() method to return an available stateless session bean. Please note that this supplier works with Weblogic 5.1, since that server serializes calls to stateless beans, and no RemoteException would be thrown (required by EJB 1.1 specification) due to the simultaneous usage of the same stateless bean (unless you synchronize on the listeners in the fireXXX() method).

//StatelessSupplier.java
package com.jwasp.listener;
import java.util.EventListener;
import javax.ejb.CreateException;
import javax.ejb.EJBHome;
import java.rmi.RemoteException;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;
/**
 * Uses specified EJBHome of the listener (stateless session bean) to call create() method to return a listener
 * in getListener() method. Be sure that your EJB container serializes calls to the listener
 * (or provide synchronization on listeners in your fireXXX method).
 * @author Roman Stepanenko
 */
public class StatelessSupplier implements ListenerSupplier {
  public StatelessSupplier(EJBHome ejbHome) throws RemoteException{
    if ( ejbHome == null ) {
      throw new IllegalArgumentException("EJBHome is null");
    } else if ( !ejbHome.getEJBMetaData().isStatelessSession() ) {
      throw new IllegalArgumentException("EJBHome should belong to stateless session bean");
    }
    myEjbHome = ejbHome;
  }
  public EventListener getListener(Class publisherClass) throws RemoteException, ListenerActivationException {
    try {
      Method createMethod = myEjbHome.getClass().getMethod("create", new Class[0]);
      Object object = createMethod.invoke(myEjbHome, new Object[0]);
      if ( object instanceof EventListener ) {
        return (EventListener)object;
      } else {
        throw new ListenerActivationException("Remote interface doesn't extend EventListener");
      }
    } catch(NoSuchMethodException nsme) {
      throw new ListenerActivationException("Home interface doesn't define create() method");
    } catch(SecurityException se) {
      throw new ListenerActivationException(se.getMessage());
    } catch(IllegalAccessException iae) {
      throw new ListenerActivationException(iae.getMessage());
    } catch(IllegalArgumentException iare) {
      throw new ListenerActivationException(iare.getMessage());
    } catch(InvocationTargetException ite) {
      throw new ListenerActivationException(ite.getTargetException().getMessage());
    }
  }
  private EJBHome myEjbHome;
}

Conclusion

The framework described here lets you work with distributed observers/listeners (yes, listeners can also be EJBs; the supplier will be responsible for creating/locating the EJB) but it’s not intended as a substitute for JMS or message-driven beans. This framework is intended to supplement those two powerful notification frameworks, and in conjunction with them, it allows correct implementation of the Observer pattern when working with distributed publishers and observers.

Note that publishers do not necessarily have to be EJBs. You can easily use my framework with publishers that are regular classes as well.

Finally, I’d like to point out that since this framework’s essence is to establish a relationship between publishers and observers without either of them existing at that moment, you can use it in all systems where you want publishers to configure themselves at the time of their creation. (If the publisher is an EJB, that configuration would be in ejbCreate()/ejbActivate() methods; if it is a regular class, the publisher would be in the constructor). That way, if a publisher is not a singleton, you don’t have to worry that all instances have the same set of listeners (and thus the same business logic functionality).

Roman Stepanenko is the leading developer at
GoCargo.com. Previously, he was a senior developer at TogetherSoft
(creator of TogetherJ). He has an MS in Applied Math from St.
Petersburg State University in Russia. With four years of Java
experience, Roman is a Sun-certified Java 2 Programmer, a Brainbech
Java 2 Master (transcript 80560), and a member of BrainBench MVP
(Most Valuable Professionals) for the Java 2 Platform. His hobbies
include skiing and playing 3-D action games.

Source: www.infoworld.com