jsr343-experts@jms-spec.java.net

[jsr343-experts] Re: [jms-spec users] Re: JMS Support for DI

From: Nigel Deakin <nigel.deakin_at_oracle.com>
Date: Thu, 10 Nov 2011 11:39:01 +0000

On 10/11/2011 00:45, Clebert Suconic wrote:
> Thanks Bruce.. I was a bit doubtful it was a good idea at first.. but later I talked to some other people and more
> people seem to like the idea.

It's good to continue this discussion.

>
> What I just thought also was adding the idea of a context.
>
> in 90% of the cases I have seen, users are always creating a consumer within a single Session. (because you have also
> the requirement of a session per thread, per the spec).

I'm not sure I follow. A Consumer is always tied to a single Session. Do you mean that users are always creating one
Consumer per Session?

>
> In case you need more objects as part of the same context (request/response case maybe), you could do this:
>
> Consumer sameContextProducer = producer.getContext().createConsumer();
>
> On this case both objects would be part of the same context. a commit call on either consumer.
>
> This would be the same idea as the Session. If people don't like the name..fine.. we can keep the name of Session here..
> but I guess you get the idea.

Are you thinking of the responder code, which receives the request message and sends the reply message? I agree that you
might want to perform both steps in the same transaction, which means the same session (you could achieve this with two
sessions in an XA transaction, but that may be less efficient).

By suggesting a "context" object you seem to be re-inventing the Session, so I don't see the benefit.

Perhaps we should start by reviewing which API classes we are trying to merge or eliminate.

1. Merge Connection and Session

This is straightforward for Java EE applications, since you can have only one session per connection anyway.

We could replace:

        Connection connection = connectionFactory.createConnection();
        Session session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE);

with:

        NewConnection newConnection = connectionFactory.createConnection(false,Session.AUTO_ACKNOWLEDGE);

But this would be a restriction for Java SE applications since we would be preventing the application making use of the
same Connection from multiple threads at the same time. We would have to think carefully whether we want to define an
API for Java EE use only.

2. Merge Session and Producer

This looks straightforward, at least in theory. I think we could replace

        // inject connectionFactory and queue in the usual way

        Connection connection = connectionFactory.createConnection();
        Session session = connection.createSession(false,Session.AUTO_ACKNOWLEDGE);
        MessageProducer messageProducer = session.createProducer(queue);
        TextMessage textMessage = session.createTextMessage(payload);
        messageProducer.send(queue,textMessage,DeliveryMode.PERSISTENT,0,0);
        
with:

        NewConnection newConnection = connectionFactory.createConnection(false,Session.AUTO_ACKNOWLEDGE);
        TextMessage textMessage = newConnection .createTextMessage(payload);
        newConnection.send(queue,textMessage,DeliveryMode.PERSISTENT,0,0);

(with a simpler variants that used the default delivery mode, priority and timeToLive)

There would be no problem using the same NewConnection to send messages to multiple destinations, so long as the
NewConnection was only used by one thread at a time.

3. Merge Session and Async Consumer.

The API to this looks relatively straightforward, since we could specify all the former MessageConsumer's state to the
setMessageListener() method:

        NewConnection newConnection = connectionFactory.createConnection(false,Session.AUTO_ACKNOWLEDGE);
        newConnection.setMessageListener(queue,messageListener,messageSelector,noLocal);

Note

(a) Existing Java EE restrictions on creating async consumers would mean this would not be permitted in Java EE
applications anyway

(b) that existing JMS threading restrictions means that once you did this you couldn't use the newConnection to send
messages.

4. Merge Session and Sync Consumer

The obvious way to combine these would be to specify all the former MessageConsumer's state on the receive() method:

        NewConnection newConnection = connectionFactory.createConnection(false,Session.AUTO_ACKNOWLEDGE);
        Message someMessage = newConnection.receive(queue,messageListener,messageSelector,noLocal,timeout);

However this would prevent implementations enhancing performance by creating state on the server to improve the
performance when delivering multiple messages to the same consumer, or caching messages on the MessageConsumer prior to
the call to receive(). I think this would be a big drawback.

One possible solution might be to require the application to "express its interest" in consuming messages before it
called receive(). Something like:

        NewConnection newConnection = connectionFactory.createConnection(false,Session.AUTO_ACKNOWLEDGE);
        newConnection.startReceiving(queue,messageListener,messageSelector,noLocal,timeout);
        Message someMessage1 = newConnection.receive(queue,messageListener,messageSelector,noLocal,timeout);
        Message someMessage2 = newConnection.receive(queue,messageListener,messageSelector,noLocal,timeout);
        Message someMessage3 = newConnection.receive(queue,messageListener,messageSelector,noLocal,timeout);
        newConnection.endReceiving(queue,messageListener,messageSelector,noLocal,timeout);

However I'm not sure that would represent an improvement: if you didn't know about JMS and looked at the above code you
would immediately want to replace it with:

        NewConnection newConnection = connectionFactory.createConnection(false,Session.AUTO_ACKNOWLEDGE);
        NewConsumer newConsumer = newConnection.startReceiving(queue,messageListener,messageSelector,noLocal);
        Message someMessage1 = newConsumer.receive(timeout);
        Message someMessage2 = newConsumer.receive(timeout);
        Message someMessage3 = newConsumer.receive(timeout);
        newConsumer.close()

...which is almost exactly what we have now.

5. Eliminating javax.jms.Message when sending messages

We've discussed this before. It would certainly be possible to create convenience methods for use when the user wanted
to send a TextMessage, ObjectMessage and perhaps the other types, and the user did not want to set any message properties:

Replace:

        TextMessage textMessage = newConnection.createTextMessage(payload);
         newConnection.send(queue,textMessage,DeliveryMode.PERSISTENT,0,0);

With:

         newConnection.send(payload,queue,textMessage,DeliveryMode.PERSISTENT,0,0);

(I'm not sure whether offering this for BytesMessage, MapMessage and StreamMessage would prevent vendor optimisations
for large messages.)

Since message properties are a very important part of JMS, users would still need to be able to create Message objects.
I suppose we could consider adding the message properties to the send() call, but I don't think this would represent a
simplification since the user would still need to create an intermediate Map or Properties object (or even a new
MessageProperties object).

         newConnection.send(payload,messageProperties,queue,textMessage,DeliveryMode.PERSISTENT,0,0);

6. Eliminating javax.jms.Message when receiving messages

I'm much less convinced that we can eliminate the Message object when consuming messages.

For the sync case, we could invent:

        newConsumer.receiveText(timeout);

but this would be one of 15 new methods needed to handle all the five message types and the three variants of receive().

This also raises the question of what should this method do if the next message is not a TextMessage. Should it throw a
ClassCastException? Should this method add an implicit message selector that only received TextMessages? I think this
may be introducing more complexities than it removes.

For the async case, we would need to invent

        onMessage(String payload);

and four other callbacks. Similarly, I don't think this is making things simpler for applications.

7. Conclusions

So here is what I think may be possible:

a. Merging Session and Connection (easy for Java EE, a major restriction for Java SE)
b. Removing the need to create a MessageProducer for anything
c. Removing the need to create a MessageConsumer when using a MessageListener (Java SE only)

This would leave us with the need to create a MessageConsumer when using receive().

In addition to all this, we could offer convenience methods which avoided the need to create a Message when sending
messages, but this would only be useful in limited cases, and applications would still need to handle Message objects
when receiving messages.

Note: The above is just a brainstorm, not a carefully worked out proposal. Depending on comments, I might attempt such a
proposal in the future.

Thoughts?

Nigel


>
>
> This would need to be done on a new package as we would need the current stuff backward compatible.

Indeed. We'd probably want to use new interface names as well. I used NewConnection above to illustrate this, but I'm
not suggesting this would be a suitable name. We could perhaps use a completely new word such as JMSContext or JMSHandle.

Nigel