jsr343-experts@jms-spec.java.net

[jsr343-experts] Re: (JMS_SPEC-33) Improving the JMS API with API simplifications, annotations and CDI

From: John D. Ament <john.d.ament_at_gmail.com>
Date: Mon, 25 Jul 2011 21:56:04 -0400

Nigel,

Quite a big email here, but all really great points. I've made a few
comments in line.

On Thu, Jul 21, 2011 at 1:00 PM, Nigel Deakin <nigel.deakin_at_oracle.com>wrote:

> I logged the following issue:
> http://java.net/jira/browse/**JMS_SPEC-33<http://java.net/jira/browse/JMS_SPEC-33>
>
> This is a placeholder for this goal I proposed some time ago for JMS 2.0,
> and which received support from EG members: "We should provide a modern,
> easier-to-use API, probably using annotations and CDI. There seemed to be a
> variety of ideas of how we might do this, and we will need to explore these
> in detail. Whilst I think we mostly see this as a feature for Java EE
> containers, there seems to be interest in offering features to those using a
> Java SE environment as well."
>
> There has been some discussion already about this, but I'd like to go back
> a few steps to discuss what we are trying to achieve, and how we might
> achieve it, and where technologies like annotations and CDI might fit in.
>
> One of the reasons I'm doing this is that although we have some Java
> language and CDI experts in this EG, I know from talking to individuals that
> the level of knowledge in this group is variable. I hope the "experts" take
> this as a hint that they should be prepared to help teach the others...
>
> Also, I want to avoid us saying "CDI is the solution, now what's the
> problem" and, in particular, make sure that we don't simply use CDI as a way
> to hide the complexities of the JMS API when we could be eliminating those
> complexities, not hiding them.
>

I agree with this completely, and I'm sure some people think I've done this
already... :-)


>
> I also think we need to discuss the extent to which any improvements could
> be offered to Java SE applications as well as those running Java EE.
> Although it is tempting to instinctively define our goal as offering the
> same improvements to both, I think there's much greater scope to offer
> improvements to Java EE applications, given that Java EE is by definition
> intended to make application development simpler and already offers a
> significantly simpler experience of using JMS than Java SE. At the every
> least, the different nature of the two environments will by definition mean
> the way that JMS is used will always be different in each.
>
> One thing we should bear in mind is that the JMS API wasn't really designed
> with Java EE in mind. Java EE already imposes significant restrictions on
> how the API can be used, and we might want to consider whether this offers
> opportunities to simplify the API for such users.
>

Well, except we already EJB can run in a bootstrapped SE environment.
Perhaps we leverage that, or the proposed CDI SE support to supplement the
APIs that were designed for both? My concern is that I don't want there to
be separate APIs for SE and EE environments, especially for backwards
compatibility.


>
> WHAT'S THE PROBLEM WITH THE JMS API?
> ------------------------------**------
>
> I'd like to start with discussion of the API and what is wrong with it,
> before we get on to possible solutions. What are the main problems with the
> JMS API at present which make it more difficult to use than it should?
>
> I think the starting point is that the JMS API is rather more cumbersome
> than it need to be, with a lot of boilerplate code needed to send or receive
> a single message. Here is how the existing JMS spec expects you to write a
> method which sends a String:
>
> private void send(String messageText) throws NamingException,
> JMSException {
> Properties props = new Properties();
> InitialContext ic = new InitialContext(props);
> ConnectionFactory connectionFactory = (ConnectionFactory)
> ic.lookup("**myConnectionFactory");
> Connection connection = connectionFactory.**
> createConnection();
> try {
> Session session = connection.createSession(**false,
> Session.AUTO_ACKNOWLEDGE);
> Destination destination = (Destination)
> ic.lookup("MyQueue");
> MessageProducer producer = session.createProducer(**
> destination);
> Message message = session.createTextMessage(**
> messageText);
> producer.send(message);
> } finally {
> if (connection!=null) connection.close();
> }
> }
>
> That is indeed a lot of boilerplate code (i.e. code that has to be included
> in many places with little or no alteration). It also throws two Exceptions
> which need to be handled somehow. In addition the administrator is expected
> to use provider-specific tools to bind suitable Connection and Destination
> objects in JNDI.
>
> In a Java EE container, resource injection allows things to be simplified a
> little by removing the need to create an InitialContext and replacing the
> JNDI lookups with @Resource annotations. This means that NamingException no
> longer needs to be explicitly handled.
>
> @Resource(name="myQueue") Destination destination;
>
> @Resource(name="**myConnectionFactory") ConnectionFactory
> connectionFactory;
>
> private void send(String messageText) throws JMSException {
> Connection connection = connectionFactory.**
> createConnection();
> try {
> Session session = connection.createSession(**false,
> Session.AUTO_ACKNOWLEDGE);
> MessageProducer producer = session.createProducer(**
> destination);
> Message message = session.createTextMessage(**
> messageText);
> producer.send(message);
> } finally {
> if (connection!=null) connection.close();
> }
> }
>
> This still requires a JMSException to be handled, and the administrator is
> still expected to use provider-specific tools to bind suitable Connection
> and Destination objects in JNDI.
>
> Almost identical issues arise if you write code to synchronously consume a
> message. Let's leave asynchronously consuming messages (via MessageListeners
> and MDBs until a little later).
>
> So, what are the problems here?
>
> PROBLEMS
> --------
>
> 1. The need to create several intermediate objects which we might use only
> once and not use again. In the above example, the Connection, Session,
> MessageProducer and TextMessage objects will never be used again after this
> method returns (though in other scenarious they might).
>
> 2. The need to tidy up resources after use. In practice, this means calling
> Connection.close() after use, ideally in a finally block. Whether this
> physically releases resources or simply returns the connection to a pool, if
> we don't call this method we will eventually run out of either resources or
> connections in the pool.
>
> 3. The rather redundant arguments to connection.createSession(). In a Java
> SE container we have two arguments when one would do, and in a Java EE EJB
> or web container they're ignored.
>
> 4. The dependence on JNDI as a means to isolate provider-specific
> connection factory and destination configuration from the portable
> application (which is currently a fundamendal fature of JMS, so this
> dependence is currently by design).
>

At a high level, one language level improvement I can think of is the usage
of closeable try blocks, but I agree sessions and connections are very
closely coupled and maybe can be collapse. It would be very ideal though if
the new connection/session could be injected directly rather than
constructed, with construction as a valid alternative in an SE environment.


>
> EXISTING DIFFERENCES BETWEEN JMS API in SE and EE
> ------------------------------**-------------------
>
> Although it is desirable to keep the JMS API the same when in a Java SE and
> a Java EE environment, I think we should recognise that the API within the
> Java EE EJB or web container is already significantly simpler than the full
> JMS API and would be easier to simplify further. Here are some of the things
> that we might be able to simplify in a Java EE environment.
>
> (I should point out that I'm just trying to provoke discussion here and not
> trying to make specific proposals)
>
> - Connections (as objects exposed to applications) are of limited use,
> other than to create a single Session. For example, each Connection is
> limited to one Session, so there is no point in having separate Connection
> and Session objects. The methods setClientID(), setExceptionListener() and
> stop() are forbidden. This leaves start() as just about the only useful
> method remaining on a Connection (and then only when consuming messages). So
> do we really need to expose Connections to the application at all? (Note
> that Java EE connection pooling means the application doesn't need to keep
> instances around purely to avoid the cost of creating them).
>
>
- Sessions are also of limited use, other than to create producers and
> consumers. Transactions are managed by the transaction manager, so we don't
> need commit() or rollback(). Client acknowledgement is forbidden, so we
> don't need acknowledge(). This mainly leaves createMessage() and the other
> factory methods for messages, which have no particular need to be on the
> Session object at all. So if we move these factory methods somewhere else,
> do we need to expose Sessions to the application at all? Sessions have one
> further important function in JMS: the resources of a session may only be
> used by a single thread at a time. Any change to hide the Session from the
> application should not break this restriction or prevent the application
> observing it.
>

> - MessageProducers are obviously useful for sending messages. But do we
> need to expose a MessageProducer object to the application, other than to
> allow us to call send() once? The main purpose of a MessageProducer, other
> than representing a tuple of <destination, deliveryMode, priority,
> timeToLive> is that it defines JMS message order. So we can't make this
> object disappear, though we can perhaps make it invisible to the application
> by defining some rules such as when the order of repeated calls to send() is
> honoured by the JMS provider.
>
> - MessageConsumers represent the tuple <destination, messageSelector>, and
> for durable topic subscriptions <subscriptionName, clientId> as well. For a
> queue there is no particular difference between one MessageConsumer instance
> and another (for the same <destination, messageSelector>). However for a
> topic the object identity of the MessageConsumer is significant in that each
> instance receives a copy of all the messages received on the topic, so we
> probably need to continue to expose this object to the application, at least
> for synchronous message consumption (let's talk about async message
> consumption separately).
>

> - Messages (and their subclasses) are essentially wrappers for the message
> payload which allow the application to define user-defined message
> properties which can be used by message selectors, and one or two header
> properties such as JMSReplyTo and JMSCorrelationID. Whilst these continue to
> be needed, perhaps we could provide an simpler API which allows a payload to
> to be passed directly for use by applications which don't need them.
>

> Note that since most of the simplfications suggested above are only
> possible in a Java EE EJB or web container, we would be introducing a
> different API in Java EE as for Java SE. However it could be argued that we
> *already* have a different API in a Java EE EJB or web container (with
> resource injection, 1 session per connection, forbidden methods, connection
> pooling, etc) but just don't explain this in the JMS spec and javadocs, much
> to the confusion of application developers.
>

The one thing I want to point out, since I hinted at it above, is that we
should also consider the case where CDI and JMS are running together, but
not in an EE environment, if there is consideration for CDI support in JMS'
core. One issue I've noted is the lack of consistency between messages and
sessions/connections; whether they are linked or not. Personally, I'd
prefer a single interface that described the ability to both send and
receive, potentially against multiple destinations simultaneously (maybe).


>
> ASYNC MESSAGE CONSUMPTION
> -------------------------
>
> Now let's talk about asynchronous message consumption. We need to consider
> this separately because this is the feature which *already* has a completely
> different API in a Java SE environment and in a Java EE EJB or web container
> (even though the JMS spec doesn't mention this at all).
>
> In a Java SE environment (or the Java EE application client container),
> message may be consumed asynchronously by creating a MessageConsumer as
> described above, and then calling MessageConsumer.**setMessageListener()
> to register a MessageListener with an onMessage() method.
>
> In a Java EE EJB or web container, the use of MessageConsumer.**setMessageListener()
> is explicitly forbidden (Java EE 6 Platform Spec section EE.6.7). Instead,
> the only means provided for asynchronous message consumption is the
> message-driven bean (MDB). Since this was defined in the EJB spec rather
> than the JMS spec this is defined declaratively, using either deployment
> descriptors or annotations. Here's a simple example using annotation:
>
> @MessageDriven(mappedName = "jms/inboundQueue")
> public class NewMessageBean implements MessageListener {
> public void onMessage(Message message) {
> System.out.println("Received a message");
> }
> }
>
> Where "jms/inboundQueue" is the JNDI name where the queue object can be
> obtained. Additional information, such as the message selector, can be
> specified using the activationConfig element of the MessageDriven
> annotation:
>
> @MessageDriven(mappedName = "jms/inboundQueue", activationConfig = {
> @ActivationConfigProperty(
> propertyName = "messageSelector",
> propertyValue = "JMSType = 'car' AND color = 'blue'"),
> @ActivationConfigProperty(**propertyName = "destinationType",
> propertyValue = "javax.jms.Queue")
> })
>
> The above examples are for GlassFish, and there does appear to be a problem
> in that the EJB 3.1 spec is distinctly vague about how a MDB is defined. The
> relevant sections are 5.4.15, 5.4.16 and 5.4.17.1. The only activation
> config properties defined are acknowledgeMode (only used when transactions
> are bean-managed, and which must be either Auto-acknowledge or
> Dups-ok-acknowledge), messageSelector, destinationType (which must be must
> be either javax.jms.Queue or javax.jms.Topic) and subscriptionDurability
> (which must be either Durable or NonDurable). But it doesn't specify how the
> destination is defined or, when subscriptionDurability is Durable, how the
> subscription name and clientId are defined.
>
> The JCA 1.6 spec has some additional guidance, at least for MDBs that use a
> resource adapter. Section B2 states thatr providers are "strongly
> encouraged" to provide the properties mentioned above and also destination,
> subscriptionName and clientId, with destination and destinationType as
> "required" properties.
>
> Whatever else we do, there seems to be a clear need to make these mandatory
> for JMS MDBs, whether or not they use JCA, so I've logged this separately as
> http://java.net/jira/browse/**JMS_SPEC-30<http://java.net/jira/browse/JMS_SPEC-30>and we've alreay started discussing the details separately in the thread for
> that issue.
>
> But it seems to me that the existing MDB feature already addresses a lot of
> the API problems I listed in the section "PROBLEMS" above.
>
> 1. There's no creation of intermediate objects which have no real purpose.
> Indeed no objects are exposed to the application at all apart from the
> incoming message itself.
>
> 2. There's no need for the application to close or tidy up resources after
> use.
>
> 3. There's no need for the user to use the problematical createSession()
> method
>
> 4. The only potential complexity this leaves, of the four I listed above,
> is the need to define the queue or topic as an administered object in JNDI -
> though strictly speaking since the configuration properties are not fully
> standardised it might be possible to avoid even this, if we think it
> beneficial.
>
> What other "problems" with MDBs does this leave? I can think of a few
> possible ones:
>
> * Reliance on MessageListener interface. You can't simply annotate any
> arbitrary method as the callback.
>
> * You can only have one callback per MDB class
>
> * Other types of bean such as stateless session beans and singleton beans
> can't consume messages asynchronously
>
> * MDBs can't be used in a Java SE environment (or the Java EE application
> client). In those environments you're back to using the raw JMS API.
>
> * Any others?
>
> We can certainly think of how we might make MDBs easier to use or more
> flexible. We can also think of how the changes currently being considered in
> the EJB 3.2 EG offer opportunities for generaising MDBs.
>
> However it seems to me that MDBs already go a long way to offering a simple
> API to applications, and that our real priority is to make it easier and
> simpler to *send* messages and to consume them *synchronously*.
>

MDBs conceptually do it well; but only support receiving messages. I agree,
making it dependent on the MessageListener interface makes it hard to use or
reuse a class. It would be ideal if all of your listeners could be in a
single class, supporting multiple destinations at once. Maybe something
like:

@JMSMessageDriven(destination="jms/MyDestination",acknowledgeMode=AUTO_ACKNOWLEDGE)
public void receiveFromMyDestination(@Handles Message message) {
...
}

And maybe even allow it to typecast in. My big catch here remains that this
needs to be supportable within a standalone JMS client, not just in an app
server.


>
> CDI
> ---
>
> So far I've not mentioned CDI (JSR 299). This is part of Java EE 6 and is
> already built-in to a Java EE container, so long as you define a beans.xml
> file in your application.
>
>
Shouldn't this be JSR-346 (CDI 1.1)?


> What does this give us? As I write at the start I'm hoping that other EG
> members can help share their knowledge on this, but as far as I can see it:
>
> 1. Provides a way to offer a simplified API which is simply a wrapper on
> top of the JMS API, effectively replacing boilerplate factory code with
> annotations which inject objects.
>
> 2. Provides a way to define the "scope" of a object, so that close() can be
> called automatically when it falls out of scope. Scopes might also allow us
> to define the circumstances when repeated calls to some new send method use
> the same MessageProducer, and when they use different MessageProducers
> (important for message order).
>
> 3. Offers an internal async event mechanism which has some similarity with
> async messages.
>
> CDI is not built into Java SE, and the existing CDI spec doesn't require
> implementations to support Java SE environments, though this may change.
> However any CDI-based API in Java SE may need to be significantly different
> from a CDI-based API in Java EE because Java SE does not support resource
> injection. Also, any API for use in Java SE will need to reflect the
> additional features available in SE (local transactions, client ack,
> multiple sessions per connection etc).
>
> Out of the gate, I'm not sure that CDI by itself gives you better API. It
just gives you more consistent behavior for a dependency injection
framework; someone still needs to implement the producer methods that are
needed to process injections. Same thing with disposers (the code that
destroys objects when falling out of scope). If we are targetting JSR-346 I
would really prefer to target SE and EE for the CDI capabilities.

CDI today only supports a synchronous event mechanism, not async. You can
very simply forward JMS messages as events and have them process before your
method completes (in fact, you're guaranteed this).


> ANNOTATIONS
> -----------
>
> We shouldn't forget that annotations don't have to use CDI. Just like with
> MDBs, if we want to pass information to the container using annotations we
> can simply define the annotations we want.
>

In my opinion, JMS should support basic annotation functionality outside of
CDI; but in any environment possible that has CDI we should be leveraging
CDI's injection capabilities. Setting up a message listener shouldn't
require CDI.


>
> WHAT NEXT?
> ---------
>
> I'm not trying to make any proposals at this stage. The purpose of this
> email was to raise the issues that I think we need to consider as part of
> doing that. In particular:
>
> 1. What do *you* think needs to be improved in the JMS API?
>
> 2. To what extent should we be trying to simplify the plain Java API,
> especially in a Java EE environment when a significant proportion of it is
> redundant anyway?
>
> 3. How might the use of annotations (not necessarily CDI) make the JMS API
> simpler?
>
> 4. How might CDI make the JMS API simpler?
>
> 5. If we do try to hide complexities behind CDI, will users have to be
> aware of what is happening behind the scenes and occasionally dip into using
> some of the hidden objects? If so, are we really making this simpler?
>
> 6. Is it possible to provide similar benefits to plain Java SE applications
> as to Java EE applications, or are the environments just too different,
> bearing in mind the existing differences between JMS in Java EE and Java SE?
>
> I look forward to the discussion.
>
> Nigel
>
>
>
>
Hope you got what you were looking for out of my comments.

John

>
>
>
>
>