Saturday, June 15, 2013

What's new in JMS 2 - Part 2 - An Event based Message Sender

One of the pipe dreams we had in Seam3 was to be able to create a variable JMS event router that could send and receive messages as if they were simple CDI events. Why did we want this? For one, it was rather difficult to build a JMS sender. lots of objects and exception handling, not much to do with these errors. It was beneficial to support this as this gave an easy way to handle asynchronous event processing with CDI, leveraging JMS. It should be simple, but there were some stumbling blocks we ran across.

  1. CDI events used a global firing process, not a first match or best match algorithm. If you had an observer method that was as generic as:

    public void handleObj(@Observes Object obj) { ... }

    Then every event fired would make it to this method.

  2. Creating specific bindings breaks dynamic event processing.

    In CDI, there are two ways to fire an event. The first is to use the Event interface, the other is to use a BeanManager.

    We got close. With a fairly convoluted way we could support static binding of events to JMS messages. However, we had no way to support the additional qualifiers that may accompany a message. Let's say we had the following qualifiers on our event:

    @SendTo("someQueue") @From(customer=1) @ReplyTo("someOtherQueue")

    We could use the SendTo to map the message to a Destination, however the extra annotations were not able to come along for the ride. This is because CDI didn't give access to these qualifiers, so any observer method was blind to its input.

  3. In bound message processing breaks Java EE specification requirements. Technically, you cannot start a message listener in Java EE; you instead need to setup an MDB. You could create a message listener implementation and allow developers to extend it for custom processing, or have it mapped in ejb-jar.xml but it doesn't allow this solution to be turn key.




To help fix the problems introduced in 1 and 2, along came the EventMetadata object, which helps out our observer methods. This class acts like an InjectionPoint but is specific to event observers.

Issue 3 remains an issue. Thankfully, one of the late additions in Java EE 7 was support for a Resource Adapter that was more dynamic in nature. We'll have to see if something can be

Now, with CDI 1.1 and JMS 2 we can easily build this outbound message sender with just about 25 lines of code. Behold

 public class SendToObserver {  
   @Inject  
   private JMSContext jmsContext;  
   @Resource  
   private Context context;  
   public void sendViaJms(@Observes @SendTo("") Serializable obj, EventMetadata metadata) {  
     SendTo st = getSendTo(metadata);  
     try {  
       Destination d = (Destination) context.lookup(st.value());  
       jmsContext.createProducer().setProperty("qualifiers", metadata.getQualifiers()).send(d, obj);  
     } catch (NamingException e) {  
       // TODO log something here please  
     }  
   }  
   public SendTo getSendTo(EventMetadata metadata) {  
     Set<Annotation> qualifiers = metadata.getQualifiers();  
     for (Annotation a : qualifiers) {  
       if (a instanceof SendTo) {  
         return (SendTo) a;  
       }  
     }  
     return null;  
   }  
 }  


Assuming that we have a SendTo qualifier defined as

 @Qualifier  
 @Target({ TYPE, METHOD, PARAMETER, FIELD })  
 @Retention(RUNTIME)  
 @Documented  
 public @interface SendTo {  
      @Nonbinding String value();  
 }  


So what happens in this observer? Simple. First, we're observing any Serializable object. So an injection point for this observer could be

 @Inject  
 @SendTo("someQueue")  
 @Important  
 private Event<Serializable> serializableEvent;  


Now, in CDI 1.1 the qualifiers (@SendTo("someQueue") and @Important) will be forwarded and found in the annotation set, returned in getQualifiers(). We can iterate through them to find the SendTo to identify where to send the message to. We can also take all of these qualifiers and send them as an attribute of the message; making them available for use on the receiver side. getSendTo processes the values to read in this destination.

Next, we use JMS2's JMSContext to simplify sending. Behind the scenes, this is using a JMSProducer via a builder pattern to set properties on the message and then send it to the destination. We don't need to create a message, it happens within the sender for us. The injection here can be Request scoped or Transaction scoped, whichever is appropriate here will be activated.

What was easily 1000 lines + in Seam JMS is now just about 25 lines (30 lines if you count the qualifier). This is a huge amount of code improvement. And now, sending JMS messages is really as easy as firing a CDI event.

2 comments:

  1. Nice post! Makes me wish I was still working with JMS.

    You seem to be missing something to finish your sentence: "We'll have to see if something can be "...

    ReplyDelete
  2. Meh, it was going to be a trailing sentence anyways. More on that in a later post.

    ReplyDelete