users@jms-spec.java.net

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

From: Robbie Gemmell <robbie.gemmell_at_gmail.com>
Date: Fri, 13 Dec 2013 12:47:24 +0000

Hi Nigel,

On 13 December 2013 10:19, Nigel Deakin <nigel.deakin_at_oracle.com> wrote:

> 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
>
>
Yep


> 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.
>

This is true. I have just always interpreted that it shouldn't because I
have never seen an implementation do it and the only areas that discuss the
properties read/write state are the sections about recieved messages, and
obviously that clearing the properties makes them writable. However, as you
say, there is technically nothing written to say clearProperties can't be
called called during send. I guess it just feels a little weird as I still
really think of it as being a received message.

In the end I think it is probably as simple as noone thinking of that
scenario at the time those JMSX properties were defined. Also many
providers don't support them, which basically removes the issue.


> 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).
>
> I also think it would be a breaking change at this point, so I wouldn't
propose revising it in this way now (though it would have been good at the
outset), the later options seem like a better approach now.


> 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.
>
>
Agreed.


> The safest change might be to add a new method called (e.g.)
> setMessagePropertiesToBeReadOnly().
>
>
I would possibly change it to be along the lines of
setPropetiesReadOnly(boolean readOnly) so that a single method can be used
to make them writable first and then make them read-only later, and remove
the need for the property juggling that is otherwise required.

Having said that, it now occurs to me that the foreign provider could
support an earlier JMS version and so may lack implementation of the new
method, basically requiring the property juggling to take place anyway, and
we will always have to use the JMS methods to gather the properties from
the foreign messages at some point (assuming there are any properties).

I'll have a think about which option(s) from above or otherwise that I
would like to propose for consideration.


>
>>
>>
>> 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
>

Great, thanks.


Robbie