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).

1 2 Page 1