JMS Patterns – Generic Message Handler

Refactoring some JMS code got me thinking about message validation, type casting, and payload extraction. Having recently read Adam Bien’s excellent book “Real World Java EE Patterns – Rethinking Best Practices” I decided to implement the “Payload Extractor” pattern. However, I couldn’t help feeling there’s something not quite right about it. In particular I didn’t like having to implement an empty onMessage method, and some of the reflection overhead seemed unnecessary. Don’t get me wrong; EJB 3 interceptors are great for extracting cross-cutting concerns, but in this case the implementation of the bean is entirely dependent upon the presence of the interceptor.

The alternative I came up with uses a generic abstract superclass and the Template Method pattern. In my opinion, this approach has several advantages:

  • The solution handles both message type checking and payload extraction.
  • Less reflective code. Determining the expected type happens once during object construction. Message type checking and payload extraction use simple instanceof and isAssignableFrom checks.
  • There’s no empty onMessage method in the concrete subclass.
  • Concrete subclasses can also access the dead letter handler if the need arises.
  • More robust compile time checks. The generic abstract method forces you to implement a correctly typed consume method. In the Payload Extractor pattern this check doesn’t happen until runtime, at which point any coding errors will simply masquerade as invalid messages.

You could argue that using an interceptor frees you from having to use inheritance. However, in this case, I don’t see this as a problem. It’s generally accepted best practice to not place business logic in your Message-Driven Beans, so scenarios in which your listener is required to inherit from another class should be rare.

GenericMessageHandler.java

public abstract class GenericMessageHandler<T> implements MessageListener {

    private final Class expectedType;
    @EJB
    private DeadLetterHandler deadLetterHandler;

    public GenericMessageHandler() {
        final ParameterizedType parameterizedType =
            (ParameterizedType) getClass().getGenericSuperclass();
        expectedType = (Class) parameterizedType.getActualTypeArguments()[0];
    }

    public void onMessage(final Message message) {
        if (matchesExpectedType(message)) {
            invokeConsume(message);
        } else {
            final Object payload = extractPayload(message);
            if (matchesExpectedType(payload)) {
                invokeConsume(payload);
            } else {
                deadLetterHandler.invalidMessageType(message);
            }
        }
    }

    protected DeadLetterHandler getDeadLetterHandler() {
        return deadLetterHandler;
    }

    protected abstract void consume(T obj);

    private boolean matchesExpectedType(final Object obj) {
        return obj != null && expectedType.isAssignableFrom(obj.getClass());
    }

    private Object extractPayload(final Message message) {
        try {
            if (message instanceof ObjectMessage) {
                return ((ObjectMessage) message).getObject();
            } else if (message instanceof TextMessage) {
                return ((TextMessage) message).getText();
            }
            return null;
        } catch (JMSException e) {
            throw new IllegalStateException("Error extracting payload", e);
        }
    }

    private void invokeConsume(final Object obj) {
        consume((T) obj);
    }
}

Payload Extraction Example – MailQueueHandlerBean.java

public class MailQueueHandlerBean extends GenericMessageHandler<Email> {

    @EJB
    private Mailer mailer;

    @Override
    public void consume(final Email email) {
        mailer.sendMessage(email);
    }
}

In the above example MailQueueHandlerBean expects messages of type ObjectMessage with a payload of type Email. As you can see, the implementation is super-lean and there’s no ambiguity about who’s performing what.

Message Type Check Example – RawDataQueueHandlerBean.java

public class RawDataQueueHandlerBean extends GenericMessageHandler<BytesMessage> {

    @EJB
    private DataProcessor dataProcessor;

    @Override
    public void consume(final BytesMessage bytesMessage) {
        try {
            if (bytesMessage.getBodyLength() == 0) {
                getDeadLetterHandler().invalidMessageContent(bytesMessage);
            } else {
                final byte[] data = // extract byte array
                dataProcessor.process(data);
            }
        } catch (JMSException e) {
            throw new IllegalStateException("Error extracting bytes", e);
        }
    }
}
Advertisements

Installing Rational Software Modeller 7.5 on Fedora 11

  1. Because the installer uses text relocations you’ll need to relax your SELinux settings. Go to System -> Administration -> SELinux Management -> Status and set the Current Enforcing Mode to Permissive.
  2. Launch the installer and follow the wizard to completion (the remaining steps assume you accepted the default installation and workspace locations).
  3. Rational Software Modeller also uses text relocations at runtime so you need to run the following command before setting your Current Enforcing Mode back to Enforcing:
    sudo chcon -R -t textrel_shlib_t /opt/IBM
  4. If you try and launch the application at this point eclipse will crash and display an error dialog telling you to check <HOME>/IBM/rationalsdp/workspace/.metadata/.log. This bug is the result of a change in the xulrunner SDK which is required to display the eclipse welcome screen.
  5. To disable the welcome screen run the following command:
    echo "org.eclipse.ui/showIntro=false" > /tmp/noWelcomeScreen.ini

    Now append the following option to the launch command:

    -pluginCustomization /tmp/noWelcomeScreen.ini

    For example:

    opt/IBM/SDP/eclipse -product com.ibm.rational.rsm.product.v75.ide -pluginCustomization /tmp/noWelcomeScreen.ini
  6. Go to Application -> IBM Software Delivery Platform -> IBM Software Modeller
  7. Happy modelling!