users@jms-spec.java.net

[jms-spec users] Re: Sending a previously-received Message and supporting JMSXUserID

From: Nigel Deakin <nigel.deakin_at_oracle.com>
Date: Fri, 13 Dec 2013 10:19:28 +0000

Robbie,

On 12/12/2013 20:59, Robbie Gemmell wrote:
> Hi Nigel,
>
>
> On 12 December 2013 19:20, Nigel Deakin <nigel.deakin_at_oracle.com <mailto:nigel.deakin_at_oracle.com>> wrote:
>
> Robbie,
>
>
> On 12/12/2013 17:01, robbie.gemmell_at_gmail.com <mailto:robbie.gemmell_at_gmail.com> wrote:
>
> Hi all,
>
> I wondered if anyone could shed some light on a question I have around
> handling of JMSXUserID (and JMSXAppID as well, I guess).
>
> The JMS-defined JMSXUserID property is optional, but if supported is of
> the 'set by Provider on Send' variety. The spec says that properties
> are read-only when a Message is received. What then is the appropriate
> action to take when supporting JMSXUserID and sending a
> previously-received Message?
>
>
> Hmm. Interesting question. Here are my initial thoughts on this:
>
> I would expect the JMS provider's send() implementation to set the JMSXUserID property on the message passed to
> send(), prior to actually sending the message. If the message passed to send() is its own implementation then it can
> use whatever method it likes to do this. This is just one part of the provider calling another part of the same
> provider and so there is no requirement to do this using the JMS API. So even if the message properties are
> read-only the JMS provider can easily by-pass this.
>
>
> I did think that at one point (In my case would be doing the same thing as I mention in option 2 regarding "just set the
> property directly on the underlying message", but without the copy first) and it does seem to be the only clean way to
> handle our own messages without property-clearing, I would just have preferred to treat all messages equally while
> handling the spec-required steps in relation to setting headers etc (but without doing what you suggested below to our
> own messages just for the sake of making it the same for all).

Indeed. I was just pointing out that providers are able to optimise this part of the implementation when sending their
own messages.

>
>
> Different issues arise if the message passed to send() is a "foreign" implementation which was received using a
> different JMS provider. In this case the JMS provider's send() implementation will need to call the JMS standard
> method setStringProperty("JMSXUserID"__,userID).
>
> As you say, if the message properties are read-only then this will throw a MessageNotWriteableException. (I think
> requiring this even for JMSX properties that are set by the provider is a flaw in the spec, but never mind...)
>
>
> I agree it seems like a flaw, as if it should really have been defined as a header or been given explicit exemption from
> the read-only restriction.
>
> So the JMS provider's send() implementation will need to work around this by taking a copy of any existing
> properties, calling clearProperties(), restoring the saved properties and then calling
> setStringProperty("JMSXUserID"__,userID).
>
> An alternative would be to be optimistic and simply call setStringProperty. If a MessageNotWriteableException was
> thrown would it be necessary to take a copy of any existing properties, call clearProperties(), restore the saved
> properties and then call setStringProperty() again.
>
> I agree this doesn't sound very elegant, but none of this inelegance is exposed to the application. It's all
> internal to the JMS provider.
>
>
> I thought that might be the suggestion though I somewhat hoped it wouldn't be :)
>
> I didn't really like it and so didn't suggest it due to the side effect of changing the message properties to become
> writable without any way to set them read-only again (it would be really nice to be able to control write-access without
> clearing the existing properties..), thus going against what the spec says will be true of the recieved message.
> However, it was also the only thing I could think of that would ensure ability to actually set the property on an
> original foreign message.

I think you are saying that (for a provider which is setting JMSXUserID)

Message m = consumer.receive();

// at this point m's message properties are in read-only mode

producer.send(m);

// at this point m's message properties are writeable

The spec doesn't say whether it is valid or not for the send() method to change the message properties from read-only to
writeable in this way.

It could be argued that since the spec allows the send method to set JMSXUserID, and that the spec explicitly says that
JMSXUserID can only be set if the message properties are writeable, then it is inevitable that calling the send() method
changes the message properties from read-only to writeable.

But it's certainly a little bit inelegant. If the spec allowed JMSXUserID (etc) to be set even if the properties were in
read-only mode then this could be avoided.

You're welcome to propose this (my preceding sentence) for a future revision of the spec (see
https://java.net/projects/jms-spec/pages/Home#How_to_create_an_issue). The main issue we'd need to consider is whether
this would be an "incompatible change" which could change the behaviour of existing applications. Strictly speaking it
would be (and I think we are supposed to be strict here).

A safer change might be to explicitly allow the send() method to change the message properties to writeable. This would
make it clear that our current best "workaround" is valid.

The safest change might be to add a new method called (e.g.) setMessagePropertiesToBeReadOnly().

>
>
>
> I have searched through the spec and looked at a few implementations to
> see what they do, but haven't as yet really been able to settle on an
> answer to my question. Is there a clarifying area of the spec that I
> have missed? Any other thoughts?
>
> I can see there are various somewhat ugly options, e.g:
>
> 1. Since the spec says properties are read-only in that situation,
> accept this also includes properties the spec defines a provider itself
> sets on send and let the send methods throw
> MessageNotWriteableException. Require that users clear and re-set all
> the properties of recieved Message objects before trying to send them
> if JMSXUserID support is enabled.
>
>
> I don't see why "users" need to worry about this case at all. The JMS provider itself can (and should) do all the work.
>
>
> The main reason I left the suggestion as above instead of 'we do this bit for the application during send' was the side
> effect mentioned above. Admittedly the spec says applications shouldn't be trying to set properties at that point
> anyway, and so it shouldnt really cause a problem, but alas not everyone reads specs/API's as closely as they could
> reasonably be expected to. From a 'does what that user expected' perspective when sending a received message and finding
> the correct headers and properties set on it, that does however seem the best option in hindsight.
>
>
> 2. Copy the Message within the send method to get something writable
> and set the property on that (or possibly just set the property
> directly on the underlying message being sent, e.g using a specific
> field of an underlying protocols native message format), allowing the
> value to be sent but not actually setting it on the Message object
> originally provided.
>
>
> That wouldn't work because the spec says that "JMSX properties ‘set by provider on send’ are available to both the
> producer and the consumers of the message."
>
> This means that the JMS provider needs to set the JMSXUserID property on the exact same message object that was
> passed to the send() method. After the send() method returns the application needs to be able to call
> getStringProperty() to read the value.
>
>
> Yep, I understood that, which is why this one is definitely super-ugly and a spec breaker. I probably wouldn't even have
> thought of it if I hadn't noticed that the RI seems to do it.
>
> Ultimately I consider everything mentioned so far to break the intention of the spec in some way or other (except
> magically setting of the property on our own messages without actually using the JMS methods, which is really just an
> implementation choice), its just a case of how much.. option 2) and 3) are clearly the worst offenders by far though.
>
>
> 3. Try to set the property, catch the MessageNotWriteableException,
> continue to send without the value (or potentially with the wrong value
> if it already existed).
>
>
> If a MessageNotWriteableException is thrown the JMS provider's send method can handle this correctly, as described
> earlier.
>
>
>
> The JMS 2.0 reference implementation appears to do a combination of 1)
> and 2), though I admit I didn't actually test it in practice. If
> support for JMSXUserID is enabled (looks to be disabled by default) it
> seems like it would throw a MessageNotWriteableException from the send
> method, so long as the message was one of its own. If the Message was
> from a foreign provider, it seems like it would perform a conversion
> and then succeed in setting the property on the converted message and
> not even attempt setting it on the original.
>
>
> I don't think the RI sets the JMSXUserID property anywhere, but I haven't looked very hard.
>
> Nigel
>
>
> It is rather buried away, occurring in a different class (com.sun.messaging.jmq.jmsclient.ProtocolHandler) well after
> most of the header manipulation and message conversion occurs in the producer, which is why it looks to react
> differently to which providers message is supplied:
> //set JMSXUserID if requested
> if (setJMSXUserID) {
> message.setStringProperty(ConnectionMetaDataImpl.JMSXUserID,
> jmsxUserID);
> }

Thanks for pointing that out. As you suggest, it appears that this will throw an exception if the message being sent was
previously received and the message properties have been left in read-only mode. I've logged this as
https://java.net/jira/browse/MQ-347

Nigel