jsr343-experts@jms-spec.java.net

[jsr343-experts] Re: (JMS_SPEC-64) Define simplified JMS API (CDI questions)

From: Nigel Deakin <nigel.deakin_at_oracle.com>
Date: Thu, 19 Jan 2012 18:34:01 +0000

This is a question for all those CDI experts out there....

I'm looking into how CDI can be used to make the Simplified JMS API even simpler to use, and would welcome some
suggestions on the best way to proceed.

Our previous CDI discussions reached a dead end because the JMS API requires multiple objects to be injected
(connection, session, producer, consumer) , and we couldn't find a way of representing the relationship between these
injected objects (e.g. guaranteeing that an injected producer uses the same connection as an injected consumer) when
using CDI.

The simplified API removes this complication, as it uses just a single object, the MessagingContext, which can be
created from a connection factory in a similar way to a Connection.

I therefore hope it will be possible to define some built-in injection behaviour which makes it easy for applications to
inject MessagingContext objects into their code.

So, how will this work? Since a MessagingContext is created from a Connectionfactory, what I'd like to do is to be able
to inject a MessagingContext, specifying the JNDI name of the connection factory (and the sessionMode) at the injection
point.

To give an example, I've taken the example from the simplified API proposal of sending a message in a Java EE
environment, and converted it to use injection:

@Stateless
@LocalBean
public class JavaEESenderNew {

     @Inject
     @JMSConnectionFactory(lookup="jms/connectionFactory")
     @SessionMode("AUTO_ACKNOWLEDGE")
     private MessagingContext messagingContext;

     @Resource(mappedName = "jms/inboundQueue")
     private Queue inboundQueue;

     public void sendMessageNew(String payload) {
         messagingContext.send(inboundQueue, payload);
     }
}

I actually have this example working (in prototype form), using the following producer method:

     @Produces
     @JMSConnectionFactory(lookup="")
     @SessionMode("AUTO_ACKNOWLEDGE")
     @Dependent
     public MessagingContext createMessagingContextAutoAcknowledge(InjectionPoint ip) {
         String lookup = ip.getAnnotated().getAnnotation(JMSConnectionFactory.class).lookup();
         ConnectionFactory thisConnectionFactory = null;
         MessagingContext result = null;
         try {
             InitialContext initialContext = new InitialContext();
             thisConnectionFactory = (ConnectionFactory) initialContext.lookup(lookup);
         } catch (NamingException ex) {
             Logger.getLogger(OrderResources.class.getName()).log(Level.SEVERE, null, ex);
         }

         return thisConnectionFactory.createMessagingContext(Session.AUTO_ACKNOWLEDGE);
     }

Unfortunately are several problems with this, but the most important one is this:

  * The producer method needs to obtain the JNDI name specified at the injection point. However the above code, which
    uses an InjectionPoint object, only works when the injected object has a scope of @Dependent. I think I understand
    the reason for this, and I remember John A mentioning this in the past.

    However this leaves me with a problem, since a scope of @Dependent is not appropriate here: I need the
    MessagingContext (which holds a JMS connection) to be closed when the request is completed (and ideally as soon as
    the method returns), but I also want the application to be able to specify an arbitrary JNDI name in the @Inject
    declaration.

  Is this possible, and if so, how?

(I know that applications could define their own set of Qualifiers, with a different Qualifier for each JNDI name, but
my goal here is to define some built-in facilities which would avoid the need to do this).

The other issues I encountered were:

  * how to avoid looking up the connection factory whenever a messaging context is created?
  * how to make the @SessionMode qualifier optional (without getting an "ambiguous injection" error)

but I think the most important issue is how to be able to specify the connection factory (either as a JNDI name or as an
actual object), but still have a suitable scope.

Any ideas?

Nigel