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