0
votes

The Setup

I have a JMS messaging application that has messages inbound from multiple JMS destinations. The message payloads are varying JSON representations with some common headers. I am relying on Spring's dynamic Jackson type conversion at the ServiceActivators to convert into the actual POJOs. At present the routing is trivial because the channels are essentially "datatype" channels split out by the JSON payload type (they are all JSON String payloads but the JSON represents very different object types).

The Problem

I would like to apply global validation logic to all inbound messages across several pattern matched channels e.g. "*input*" and divert invalid messages to a validation error channel for review. Regardless of whether the message is valid or invalid, the local JMS transaction should be committed; if the message is invalid, I don't want the invalid message to be resent later.

Potential Options Considered

Channel Interceptor

My initial thought was to implement a ChannelInterceptor that matches all the channels where this logic should be applied, but it does not appear that the capability to divert a message can be implemented in a ChannelInterceptor. It appears that my two options with the ChannelInterceptor are:

  1. Have the JMS transaction rolled back when the interceptor returns null on preSend, OR
  2. the invalid message is still sent along to the original destination.

Neither of these are the desired behavior. The JMS local transaction should always be committed (provided no other error) and the message either sent to the original destination, or diverted to the invalid message channel.

Router

A Router might be a good choice, but it doesn't appear that there is a way to apply a router to a pattern matched set of channels, so I believe I would have to apply it to each channel individually. That kind of duplication is something that I am hoping to avoid.

AspectJ Pointcut

Another option I've thought of is to break out AspectJ and implement @Around advice on the AbstractMessageSendingTemplate.convertAndSend(destination, payload, postProcessor) method. This seems intrusive, but appears as if it may work. If there is an option better supported directly by the framework I would be happy to hear it.

Common Input Channel w/ Payload Routing

If I can't find a way to globally apply this type of routing logic, then another option may be to route all inbound JMS messages through a single channel. A custom Router could be applied to that inbound channel that uses payload type headers to direct messages to their proper "datatype" channels and routes invalid messages to the validation error channel.

The Question(s)

  • Is there a way to apply this type of message diversion to a pattern matched set of channels?
  • Have I missed an important capability of the Spring Integration framework that will make one of my considerations work?
  • If not, are there better EIP options than what I've mentioned?

Many Thanks!

2

2 Answers

1
votes

My initial thought was to implement a ChannelInterceptor that matches all the channels where this logic should be applied, but it does not appear that the capability to divert a message can be implemented in a ChannelInterceptor

What leads you to believe that? preSend() can return null which effectively terminates the operation; simply send the failed validation to the common channel and return null.

/**
 * Invoked before the Message is actually sent to the channel.
 * This allows for modification of the Message if necessary.
 * If this method returns {@code null} then the actual
 * send invocation will not occur.
 */
@Nullable
default Message<?> preSend(Message<?> message, MessageChannel channel) {
    return message;
}

This will cause a MessageDeliveryException at the inbound adapter, but you can simply absorb that in an error channel flow.

1
votes

My position do not do that in the global ChannelInterceptor because it is easy to fall into the pattern with a channel which should not be affected with such a filtering logic.

You always can send all the messages to the same channel for a common logic. The routing behavior you can control via replyChannel header populated before sending to the validation channel. So, for me the logic looks like:

  1. Each flow performs a HeaderEnricher to populate a replyChannel header with the desired next step in the flow.

  2. After that enricher all the flows send messages to the Filter component with the validation logic.

  3. There, on failure, you send message to the discardChannel of the Filter as you explained in your question.

  4. On success you just don't send anywhere unless the replyChannel header. So, you valid messages are going come back to their original flows.

Does it make sense for you?