users@jms-spec.java.net

[jms-spec users] Re: JMS_SPEC-134: Declarative Annotation Based JMS Listeners

From: Nigel Deakin <nigel.deakin_at_oracle.com>
Date: Wed, 2 Sep 2015 15:24:13 +0100

Evans,

Thanks. That's helpful.

I can see how that might work, and see how it offers a way to plug a callback method directly into the @MessageSelector
annotation. We'd not want to lose the ability to specify the message selector directly, so we'd need to define the
@MessageSelector annotation to allow both, perhaps using

@MessageSelector("ticker='ORCL'") <== using the "value" attribute

@MessageSelector(expression="method=#{selectorProvider.tickerSelector}") <== using the "expression" attribute

However I'm still unenthusiastic about the use of EL. It's not Java. It's a script inside a Java program, and a typo
would not be detected at compile time. And it's something new for the JMS developer to learn about. I'd like to keep
looking for something better.

I'm recording all these suggestions in a wiki page. I've captured the various comments on runtime customisation at
https://java.net/projects/jms-spec/pages/CDIListenerBeanComments#Runtime_customisation

Nigel

On 01/09/2015 14:32, Evans Armitage wrote:
>
> Hi Nigel,
>
> The EL evaluation can be done using javax.el.CompositeELResolver which can be created with beanManager.getELResolver.
>
> @Named is not limited to JSF and JSP pages hence the unification of EL. You can use @Named to qualify any beans and
> @Inject the one with the right @Named at the right injection point. It's not refactoring friendly but is all CDI and
> has nothing to do with Web pages.
>
> It is clearer to have the message selector definition as part of the activation configs on the listener method rather
> than being located elsewhere in the class definition. Whether it should be restricted to only be inside the class that
> has the listener method is also another issue. The EL approach eliminates the need to have the selector method in the
> same class (or module) as the listener method and removes the complexity that could arise with choosing the selector
> method when multiple listener methods are in the same class.
> A 'caveat' of the EL approach is that the latest EL spec allows expressions like "#{bean.methodName ('someValue')"
> where methodName takes a String but this would be transparent to the JMS implementor as they simply pass the EL to the
> ELResolver.
>
> A lambda expression could indeed be better but I think it would need to allow instance::instanceMethodName which means
> the instance needs to be created somewhere and I don't think that will work in an annotation. Just allowing the static
> ClassName::methodName option would be limiting IMO. My lambdas are not the best though.
>
> Regards
>
> Evans
>
> On 01 Sep 2015 1:36 PM, "Nigel Deakin" <nigel.deakin_at_oracle.com <mailto:nigel.deakin_at_oracle.com>> wrote:
>
> On 28/08/2015 17:03, Evans Armitage wrote:
>
> Hi Nigel, the EL use could look like below
>
> import javax.inject.Named;
> @Named
> public class SelectorProvider {
> public String tickerSelector() {
> return "ticker='ORCL'";
> }
> }
>
> Here CDI will make the SelectorProvider bean available using the name #{selectorProvider}
>
>
> Then the listener method would have
>
> @MessageSelector("method=#{selectorProvider.tickerSelector}")
>
>
>
> Currently when the listener bean is initialised (using code plugged in by the portable extension), it can use
> reflection to discover what message selector to use for the callback method as follows:
>
> MessageSelector messageSelectorAnnotation method.getAnnotation(MessageSelector.class);
> String messageSelector = messageSelectorAnnotation.getValue();
> ...
> JMSContext jmsContext = ...
> Destination destination = ...
> JMSConsumer consumer = jmsContext.createConsumer(destination,messageSelector);
> ...
>
> If the message selector is "method=#{selectorProvider.tickerSelector}" then how does this code need to change to
> evaluate the expression?
>
> I'm not sure EL is the best way to specify a callback method which should be called to obtain the message
> selector: EL is intended for use in non-Java code such as a JSF or JSP pages, whereas we're in Java code here and
> have the full power of Java available to us. Perhaps a lambda expression is more appropriate.
>
> Nigel
>
> Evans
>
> On Fri, Aug 28, 2015 at 5:30 PM, Nigel Deakin <nigel.deakin_at_oracle.com <mailto:nigel.deakin_at_oracle.com
> <mailto:nigel.deakin_at_oracle.com>>> wrote:
>
> Evans,
>
> On 28/08/2015 16:03, Evans Armitage wrote:
>
> Hi Nigel,
>
> For the problem of how to specify the message selector when there are multiple callback methods, an
> approach could
> be using the unified expression language_at_MessageSelector(method="#{someBean.method}") which allows
> selectors to be
> reused in other listeners. The evaluation of the EL expression would be delegated to the CDI container.
>
>
> I'm not familiar with that. Can you give a more detailed example of what you are suggesting?
>
> The listener bean connects to the JMS provider and creates the JMS consumer during its @PostConstruct
> phase, using
> code plugged in by the portable extension. What code would this use to discover what message selector to
> use and
> (say) save it in a local variable?
>
> Making the injecting application decide the selector might not be feasible because depending on the scope e.g
> @ApplicationScoped and @SessionScoped the listener instance might already have been created and
> receiving messages
> before it is injected at any injection point. This is assuming that @Eager like behavior gets
> implemented as default.
>
> This is a problem even if the instance has not already been eagerly created. As I mentioned in my example
> below:
>
> Inject Instance<MyDepScopeListenerBean> listenerProvider;
> MyDepScopeJMSListener jmsListener1 = listenerProvider.get();
>
> since the consumer is created during the bean's @PostConstruct stage then we need a way for the application to
> specify the message selector etc before CDI actually creates the bean. Once the bean has been created it's
> too late.
> CDI allows qualifiers to be specified before calling get(), but these annotations (@MessageSelector etc)
> are not
> qualifiers (i.e. they're not used to determined which bean class to use).
>
> Nigel
>
>
> Kind regards
> Evans Armitage
>
> On Fri, Aug 28, 2015 at 12:41 PM, Nigel Deakin <nigel.deakin_at_oracle.com
> <mailto:nigel.deakin_at_oracle.com <mailto:nigel.deakin_at_oracle.com>>> wrote:
>
> Evans,
>
> Agreed. The current proposals is that the listener bean class specifies the destination,
> connection factory,
> destination type, acknowledge mode, subscription durability, clientId, subscription name and
> message selector
> using annotations, which means these are set at compile time.
>
> It would be desirable to allow these to be specified at runtime, for each listener bean instance
> separately.
>
> I like your suggestion of allowing the listener bean itself to have callbacks which return these
> values.
>
> @SessionScoped
> public class MyCDIBean21 {
>
> @JMSListener(lookup="java:global/java:global/Trades",type=JMSListener.Type.TOPIC )
> @JMSConnectionFactory("java:global/MyCF")
> @MessageSelector("ticker='ORCL'")
> public void processNewsItem(String newsItem) {
> ...
> }
>
> @GetMessageSelector
> public void returnMessageSelector(){
> // some logic to work out message selector
> return ...
> }
>
> This would allow the bean initialisation code to find out what message selector to use. However
> what only
> works if there is a single callback method. The current proposals suggest that a bean can have
> multiple
> callbacks, each listening to different destinations or using different message selectors. To
> handle that we'd
> need something like:
>
> @GetMessageSelector
> public void returnMessageSelector(Method m){
> // some logic to work out message selector for the specified method
> return ...
>
> It would be simpler overall if we allowed these beans to define just a single callback method.
> (The same issue
> applies to the new-style MDBs).
>
> There's a second, and more important, issue with these callback methods. Although it allows the
> listen bean
> itself to decide (say) the message selector, it doesn't provide a way for the application which is
> injecting
> it to decide the message selector. I think that's probably a bigger requirement, but I'm not sure
> the best way
> to achieve that.
>
> CDI provides a way to programmatically obtain an instance of the listener bean. I describe this here:
> https://java.net/projects/jms-spec/pages/CDIBeansAsJMSListeners#JMS_listener_bean_with_dependent_scope_and_explicit_lifecycle_management
>
> Inject Instance<MyDepScopeListenerBean> listenerProvider;
> MyDepScopeJMSListener jmsListener1 = listenerProvider.get();
>
> However since the consumer is created during the bean's @postCreate stage then we need a way for the
> application to specify the message selector etc before we actually create the bean. CDI allows
> qualifiers to
> be specified before calling get(), but these annotations are not qualifiers. Ideas welcome.
>
> Nigel
>
>
>
> On 26/08/2015 12:36, Evans Armitage wrote:
>
>
> Hi Nigel,
> I was asking more around the definition of the destinations themselves. Currently those
> properties are static
> and thus do not allow the developer to decide at runtime which messages get delivered. In the
> @SessionScoped
> scenario it is likely that if a developer needs a listener to be @SessionScoped then they
> intend for the
> messages to be delivered to the current session user only. They would thus likely appreciate some
> customization on their activation config (especially for message selector) to allow them to
> specify the
> selector dynamically through a call back method to select messages for, say, the current user
> principal.
> It's just a thought triggered by your suggested addition. If the targets for delivery are now
> going to be
> dynamic would it also be feasible to make the message selector dynamic?
>
> Evans
>
> On 26 Aug 2015 12:34 PM, "Nigel Deakin" <nigel.deakin_at_oracle.com
> <mailto:nigel.deakin_at_oracle.com <mailto:nigel.deakin_at_oracle.com>>> wrote:
>
> Evans,
>
> On 26/08/2015 05:44, Evans Armitage wrote:
>
> I take it container discovery and validation still happens at startup even for these
> CDI listeners?
>
>
> My proposal was that "JMS listener beans" would be completely ordinary CDI managed beans,
> created in the
> same way. The new portable extension would simply extend their postCreate behaviour to
> create a JMS
> consumer, and extend their preDestroy behaviour to close that consumer. I'm not familiar
> with those
> specific events, but I'm not proposing anything to interfere with them working as normal.
>
> Is there anything that can be added to allow an @SessionScoped listener that only
> delivers messages
> for the current
> session? I think that will be a more common reason to use a @SessionScoped jms
> listener (i.e only
> deliver when user
> session exists AND only messages intended for the current user).
>
>
> A JMS listener bean could have any scope, including @SessionScoped. So once it was
> injected and created
> by the application it would listen for messages until the scope ends and the bean was
> destroyed.
>
> Over on the cdi-dev list people have suggested we provide some way of automatically
> creating a listener
> bean whenever a new scope starts, so that the application wouldn't have to inject it. If
> that were the
> case then an instance of the listener would be automatically created whenever a new
> SessionScope starts.
> (I'm still thinking about this).
>
> Would adding a message selector callback approach be too much work for container
> developers for too
> little gain?
>
>
> I don't understand your question. Can you explain it a bit more?
>
> Nigel
>
>
>
>
>