PDA

View Full Version : To what extent does JMS unification work?


boerdijk
Sep 7th, 2004, 12:00 PM
Hello,

In org.springframework.jms.support.converter.SimpleMe ssageConvertor.java, I see that the fromMessage() method uses the BytesMessage.getBodyLength() method. This method is not available in JMS 1.0.2 so I was wondering if this code actually works with JMS 1.0.2, and if yes, how?

Best regards,

Robin Boerdijk

Mark Pollack
Sep 13th, 2004, 06:53 PM
Hi,

Good catch. Some of the 1.1 isms have snuck in there. It won't work in 1.0.2...I'll look around for a workaround, such as using readBytes(byte[] value), but it might be best just to issue a warning if using 1.0.2 or remove it completely.

Cheers,
Mark

boerdijk
Sep 14th, 2004, 03:26 AM
Hi Mark,

Some time ago, I solved this problem myself by writing a JMS 1.1 adapter framework for JMS 1.0.2 using dynamic proxies. This solution allows programmers to use the full JMS 1.1 API, even if the underlying JMS message provider only supports JMS 1.0.2. I can donate this solution to the Spring framework if you're interested.

Cheers,
Robin

Mark Pollack
Sep 15th, 2004, 03:59 PM
Hi,

We did think of writing a 1.1 wrapper for 1.0.2 but felt it was too complex an approach given the relatively limited 'send' functionality in JmsTemplate. I'd be interested to see it nevertheless. How did you address the specific issue of getBodyLength()?

- Mark

boerdijk
Sep 16th, 2004, 05:03 AM
The getBodyLength() implementation of my BytesMessageAdapter class looks like this:


private long getBodyLength() throws JMSException
{
// It is the responsibility of the caller to make sure that the
// BytesMessage is in read-only mode when this method is called
// as dictated by the JMS 1.1 API.

// The implementation of this method is very tricky because we
// need to make sure that the position of the stream is not
// affected by the invocation of this method. Since there is
// no way to find out what the current position of the stream
// is, we actually need to read through the whole message twice.

byte[] buffer = new byte[256 * 1024];

// First we read from the current position to the end of the
// message. This gives us lenMinusPosition and leaves the position
// at the end of the stream.

long lenMinusPosition = 0;
while (true) {
int read = message.readBytes(buffer);
if (read == -1) {
break;
}
lenMinusPosition = lenMinusPosition + read;
}

// Then we reset the message and read through the entire message.
// This gives us len.

message.reset();

long len = 0;
while (true) {
int read = message.readBytes(buffer);
if (read == -1) {
break;
}
len = len + read;
}

// From len and lenMinusPosition we can determine the original
// postion. We then reset the message again and read until the
// position.

long position = len - lenMinusPosition;

message.reset();

while (position > buffer.length) {
message.readBytes(buffer);
position = position - buffer.length;
}
if (position > 0) {
message.readBytes(buffer, (int) position);
}

// The message is now in the same state as before the method
// was invoked.

return len;
}


The whole adapter framework consists of 26 more Adapter and reverse Adapter classes so you were right in your observation that it is quite complex.

A simpler solution would be to wrap the current getBodyLength() call in a try/catch block, but this is really a clutch.


long len;
try {
len = bytesMessage.getBodyLength();
} catch (NoSuchMethodError eNoSuchMethod) {
len = getJMS102BodyLength(bytesMessage);
}

where the getJMS102BodyLength() method would be implemented as above.

Cheers,
Robin.

Juergen Hoeller
Sep 17th, 2004, 06:24 PM
Actually, we don't need to determine the body length of a BytesMessage per se; in particular, we don't need to care about resetting the message to its original position. All we need to achieve in SimpleMessageConverter is to convert the contents to a byte array.

So on JMS 1.0.2, we could simply call readBytes with an array buffer as long as there is content available (checking the return value of readBytes), copying everything into a single byte array as result (possibly via ByteArrayOutputStream).

Of course, the above is not too efficient, so we should keep using getBodyLength() on JMS 1.1. Thus, I guess it's appropriate to add a SimpleMessageConverter102 subclass with buffer-based copying of BytesMessage contents, used by JmsTemplate102 as default.

I'll give such a SimpleMessageConverter102 subclass a try this weekend.

Juergen

Juergen Hoeller
Sep 18th, 2004, 08:26 AM
I've just added a corresponding SimpleMessageConverter102, used as default for JmsTemplate102.

Juergen

boerdijk
Sep 18th, 2004, 08:34 AM
Hi Juergen,

Is this solution transparent for developers using the Spring JMS framework? In other words, can they keep on using the SimpleMessageConvertor or do they need to explicitly instantiate a SimpleMessageConvertor102 somewhere? In the latter case, the code they write becomes dependent on a specific JMS version.

And how about any future classes that want to use the JMS 1.1 BytesMessage.getBodyLength() method? Wouldn't you need to provide 102 versions for each of them as well?

The reason why I wrote a JMS unification framework (of which the BytesMessageAdapter.getBodyLength() method shown in my previous post is just a part) is that I wanted to iron out the differences between JMS 1.0.2 and JMS 1.1 at the lowest possible level (i.e. at the JMS API level itself). Using this JMS unification framework, you never need to write any JMS version specific code, except for the code that creates the initial ConnectionFactory.

Wouldn't this be a valuable addition to the Spring framework as well? Such a framework could sit underneath the current Spring JMS framework which can then be written entirely in terms of the JMS 1.1 API, removing the need for any JMS 1.0.2 specific code.

Cheers,
Robin.