View Javadoc

1   /*** 
2    * 
3    * Copyright 2004 Protique Ltd
4    * 
5    * Licensed under the Apache License, Version 2.0 (the "License"); 
6    * you may not use this file except in compliance with the License. 
7    * You may obtain a copy of the License at 
8    * 
9    * http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS, 
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
14   * See the License for the specific language governing permissions and 
15   * limitations under the License. 
16   * 
17   **/
18  
19  package org.codehaus.activemq.message;
20  
21  import org.codehaus.activemq.service.MessageIdentity;
22  import org.codehaus.activemq.util.IdGenerator;
23  
24  import javax.jms.DeliveryMode;
25  import javax.jms.Destination;
26  import javax.jms.JMSException;
27  import javax.jms.Message;
28  import javax.jms.MessageFormatException;
29  import javax.jms.MessageNotWriteableException;
30  import java.io.ByteArrayInputStream;
31  import java.io.ByteArrayOutputStream;
32  import java.io.DataInput;
33  import java.io.DataInputStream;
34  import java.io.DataOutput;
35  import java.io.DataOutputStream;
36  import java.io.IOException;
37  import java.util.Enumeration;
38  import java.util.Hashtable;
39  
40  /***
41   * The <CODE>Message</CODE> interface is the root interface of all JMS
42   * messages. It defines the message header and the <CODE>acknowledge</CODE>
43   * method used for all messages.
44   * <p/>
45   * <P>Most message-oriented middleware (MOM) products treat messages as
46   * lightweight entities that consist
47   * of a header and a payload. The header contains fields used for message
48   * routing and identification; the payload contains the application data
49   * being sent.
50   * <p/>
51   * <P>Within this general form, the definition of a message varies
52   * significantly across products. It would be quite difficult for the JMS API
53   * to support all of these message models.
54   * <p/>
55   * <P>With this in mind, the JMS message model has the following goals:
56   * <UL>
57   * <LI>Provide a single, unified message API
58   * <LI>Provide an API suitable for creating messages that match the
59   * format used by provider-native messaging applications
60   * <LI>Support the development of heterogeneous applications that span
61   * operating systems, machine architectures, and computer languages
62   * <LI>Support messages containing objects in the Java programming language
63   * ("Java objects")
64   * <LI>Support messages containing Extensible Markup Language (XML) pages
65   * </UL>
66   * <p/>
67   * <P>JMS messages are composed of the following parts:
68   * <UL>
69   * <LI>Header - All messages support the same set of header fields.
70   * Header fields contain values used by both clients and providers to
71   * identify and route messages.
72   * <LI>Properties - Each message contains a built-in facility for supporting
73   * application-defined property values. Properties provide an efficient
74   * mechanism for supporting application-defined message filtering.
75   * <LI>Body - The JMS API defines several types of message body, which cover
76   * the majority of messaging styles currently in use.
77   * </UL>
78   * <p/>
79   * <H4>Message Bodies</H4>
80   * <p/>
81   * <P>The JMS API defines five types of message body:
82   * <UL>
83   * <LI>Stream - A <CODE>StreamMessage</CODE> object's message body contains
84   * a stream of primitive values in the Java programming
85   * language ("Java primitives"). It is filled and read sequentially.
86   * <LI>Map - A <CODE>MapMessage</CODE> object's message body contains a set
87   * of name-value pairs, where names are <CODE>String</CODE>
88   * objects, and values are Java primitives. The entries can be accessed
89   * sequentially or randomly by name. The order of the entries is
90   * undefined.
91   * <LI>Text - A <CODE>TextMessage</CODE> object's message body contains a
92   * <CODE>java.lang.String</CODE> object. This message type can be used
93   * to transport plain-text messages, and XML messages.
94   * <LI>Object - An <CODE>ObjectMessage</CODE> object's message body contains
95   * a <CODE>Serializable</CODE> Java object.
96   * <LI>Bytes - A <CODE>BytesMessage</CODE> object's message body contains a
97   * stream of uninterpreted bytes. This message type is for
98   * literally encoding a body to match an existing message format. In
99   * many cases, it is possible to use one of the other body types,
100  * which are easier to use. Although the JMS API allows the use of
101  * message properties with byte messages, they are typically not used,
102  * since the inclusion of properties may affect the format.
103  * </UL>
104  * <p/>
105  * <H4>Message Headers</H4>
106  * <p/>
107  * <P>The <CODE>JMSCorrelationID</CODE> header field is used for linking one
108  * message with
109  * another. It typically links a reply message with its requesting message.
110  * <p/>
111  * <P><CODE>JMSCorrelationID</CODE> can hold a provider-specific message ID,
112  * an application-specific <CODE>String</CODE> object, or a provider-native
113  * <CODE>byte[]</CODE> value.
114  * <p/>
115  * <H4>Message Properties</H4>
116  * <p/>
117  * <P>A <CODE>Message</CODE> object contains a built-in facility for supporting
118  * application-defined property values. In effect, this provides a mechanism
119  * for adding application-specific header fields to a message.
120  * <p/>
121  * <P>Properties allow an application, via message selectors, to have a JMS
122  * provider select, or filter, messages on its behalf using
123  * application-specific criteria.
124  * <p/>
125  * <P>Property names must obey the rules for a message selector identifier.
126  * Property names must not be null, and must not be empty strings. If a property
127  * name is set and it is either null or an empty string, an
128  * <CODE>IllegalArgumentException</CODE> must be thrown.
129  * <p/>
130  * <P>Property values can be <CODE>boolean</CODE>, <CODE>byte</CODE>,
131  * <CODE>short</CODE>, <CODE>int</CODE>, <CODE>long</CODE>, <CODE>float</CODE>,
132  * <CODE>double</CODE>, and <CODE>String</CODE>.
133  * <p/>
134  * <P>Property values are set prior to sending a message. When a client
135  * receives a message, its properties are in read-only mode. If a
136  * client attempts to set properties at this point, a
137  * <CODE>MessageNotWriteableException</CODE> is thrown. If
138  * <CODE>clearProperties</CODE> is called, the properties can now be both
139  * read from and written to. Note that header fields are distinct from
140  * properties. Header fields are never in read-only mode.
141  * <p/>
142  * <P>A property value may duplicate a value in a message's body, or it may
143  * not. Although JMS does not define a policy for what should or should not
144  * be made a property, application developers should note that JMS providers
145  * will likely handle data in a message's body more efficiently than data in
146  * a message's properties. For best performance, applications should use
147  * message properties only when they need to customize a message's header.
148  * The primary reason for doing this is to support customized message
149  * selection.
150  * <p/>
151  * <P>Message properties support the following conversion table. The marked
152  * cases must be supported. The unmarked cases must throw a
153  * <CODE>JMSException</CODE>. The <CODE>String</CODE>-to-primitive conversions
154  * may throw a runtime exception if the
155  * primitive's <CODE>valueOf</CODE> method does not accept the
156  * <CODE>String</CODE> as a valid representation of the primitive.
157  * <p/>
158  * <P>A value written as the row type can be read as the column type.
159  * <p/>
160  * <PRE>
161  * |        | boolean byte short int long float double String
162  * |----------------------------------------------------------
163  * |boolean |    X                                       X
164  * |byte    |          X     X    X   X                  X
165  * |short   |                X    X   X                  X
166  * |int     |                     X   X                  X
167  * |long    |                         X                  X
168  * |float   |                               X     X      X
169  * |double  |                                     X      X
170  * |String  |    X     X     X    X   X     X     X      X
171  * |----------------------------------------------------------
172  * </PRE>
173  * <p/>
174  * <P>In addition to the type-specific set/get methods for properties, JMS
175  * provides the <CODE>setObjectProperty</CODE> and
176  * <CODE>getObjectProperty</CODE> methods. These support the same set of
177  * property types using the objectified primitive values. Their purpose is
178  * to allow the decision of property type to made at execution time rather
179  * than at compile time. They support the same property value conversions.
180  * <p/>
181  * <P>The <CODE>setObjectProperty</CODE> method accepts values of class
182  * <CODE>Boolean</CODE>, <CODE>Byte</CODE>, <CODE>Short</CODE>,
183  * <CODE>Integer</CODE>, <CODE>Long</CODE>, <CODE>Float</CODE>,
184  * <CODE>Double</CODE>, and <CODE>String</CODE>. An attempt
185  * to use any other class must throw a <CODE>JMSException</CODE>.
186  * <p/>
187  * <P>The <CODE>getObjectProperty</CODE> method only returns values of class
188  * <CODE>Boolean</CODE>, <CODE>Byte</CODE>, <CODE>Short</CODE>,
189  * <CODE>Integer</CODE>, <CODE>Long</CODE>, <CODE>Float</CODE>,
190  * <CODE>Double</CODE>, and <CODE>String</CODE>.
191  * <p/>
192  * <P>The order of property values is not defined. To iterate through a
193  * message's property values, use <CODE>getPropertyNames</CODE> to retrieve
194  * a property name enumeration and then use the various property get methods
195  * to retrieve their values.
196  * <p/>
197  * <P>A message's properties are deleted by the <CODE>clearProperties</CODE>
198  * method. This leaves the message with an empty set of properties.
199  * <p/>
200  * <P>Getting a property value for a name which has not been set returns a
201  * null value. Only the <CODE>getStringProperty</CODE> and
202  * <CODE>getObjectProperty</CODE> methods can return a null value.
203  * Attempting to read a null value as a primitive type must be treated as
204  * calling the primitive's corresponding <CODE>valueOf(String)</CODE>
205  * conversion method with a null value.
206  * <p/>
207  * <P>The JMS API reserves the <CODE>JMSX</CODE> property name prefix for JMS
208  * defined properties.
209  * The full set of these properties is defined in the Java Message Service
210  * specification. New JMS defined properties may be added in later versions
211  * of the JMS API.  Support for these properties is optional. The
212  * <CODE>String[] ConnectionMetaData.getJMSXPropertyNames</CODE> method
213  * returns the names of the JMSX properties supported by a connection.
214  * <p/>
215  * <P>JMSX properties may be referenced in message selectors whether or not
216  * they are supported by a connection. If they are not present in a
217  * message, they are treated like any other absent property.
218  * <p/>
219  * <P>JMSX properties defined in the specification as "set by provider on
220  * send" are available to both the producer and the consumers of the message.
221  * JMSX properties defined in the specification as "set by provider on
222  * receive" are available only to the consumers.
223  * <p/>
224  * <P><CODE>JMSXGroupID</CODE> and <CODE>JMSXGroupSeq</CODE> are standard
225  * properties that clients
226  * should use if they want to group messages. All providers must support them.
227  * Unless specifically noted, the values and semantics of the JMSX properties
228  * are undefined.
229  * <p/>
230  * <P>The JMS API reserves the <CODE>JMS_<I>vendor_name</I></CODE> property
231  * name prefix for provider-specific properties. Each provider defines its own
232  * value for <CODE><I>vendor_name</I></CODE>. This is the mechanism a JMS
233  * provider uses to make its special per-message services available to a JMS
234  * client.
235  * <p/>
236  * <P>The purpose of provider-specific properties is to provide special
237  * features needed to integrate JMS clients with provider-native clients in a
238  * single JMS application. They should not be used for messaging between JMS
239  * clients.
240  * <p/>
241  * <H4>Provider Implementations of JMS Message Interfaces</H4>
242  * <p/>
243  * <P>The JMS API provides a set of message interfaces that define the JMS
244  * message
245  * model. It does not provide implementations of these interfaces.
246  * <p/>
247  * <P>Each JMS provider supplies a set of message factories with its
248  * <CODE>Session</CODE> object for creating instances of messages. This allows
249  * a provider to use message implementations tailored to its specific needs.
250  * <p/>
251  * <P>A provider must be prepared to accept message implementations that are
252  * not its own. They may not be handled as efficiently as its own
253  * implementation; however, they must be handled.
254  * <p/>
255  * <P>Note the following exception case when a provider is handling a foreign
256  * message implementation. If the foreign message implementation contains a
257  * <CODE>JMSReplyTo</CODE> header field that is set to a foreign destination
258  * implementation, the provider is not required to handle or preserve the
259  * value of this header field.
260  * <p/>
261  * <H4>Message Selectors</H4>
262  * <p/>
263  * <P>A JMS message selector allows a client to specify, by
264  * header field references and property references, the
265  * messages it is interested in. Only messages whose header
266  * and property values
267  * match the
268  * selector are delivered. What it means for a message not to be delivered
269  * depends on the <CODE>MessageConsumer</CODE> being used (see
270  * {@link javax.jms.QueueReceiver QueueReceiver} and
271  * {@link javax.jms.TopicSubscriber TopicSubscriber}).
272  * <p/>
273  * <P>Message selectors cannot reference message body values.
274  * <p/>
275  * <P>A message selector matches a message if the selector evaluates to
276  * true when the message's header field values and property values are
277  * substituted for their corresponding identifiers in the selector.
278  * <p/>
279  * <P>A message selector is a <CODE>String</CODE> whose syntax is based on a
280  * subset of
281  * the SQL92 conditional expression syntax. If the value of a message selector
282  * is an empty string, the value is treated as a null and indicates that there
283  * is no message selector for the message consumer.
284  * <p/>
285  * <P>The order of evaluation of a message selector is from left to right
286  * within precedence level. Parentheses can be used to change this order.
287  * <p/>
288  * <P>Predefined selector literals and operator names are shown here in
289  * uppercase; however, they are case insensitive.
290  * <p/>
291  * <P>A selector can contain:
292  * <p/>
293  * <UL>
294  * <LI>Literals:
295  * <UL>
296  * <LI>A string literal is enclosed in single quotes, with a single quote
297  * represented by doubled single quote; for example,
298  * <CODE>'literal'</CODE> and <CODE>'literal''s'</CODE>. Like
299  * string literals in the Java programming language, these use the
300  * Unicode character encoding.
301  * <LI>An exact numeric literal is a numeric value without a decimal
302  * point, such as <CODE>57</CODE>, <CODE>-957</CODE>, and
303  * <CODE>+62</CODE>; numbers in the range of <CODE>long</CODE> are
304  * supported. Exact numeric literals use the integer literal
305  * syntax of the Java programming language.
306  * <LI>An approximate numeric literal is a numeric value in scientific
307  * notation, such as <CODE>7E3</CODE> and <CODE>-57.9E2</CODE>, or a
308  * numeric value with a decimal, such as <CODE>7.</CODE>,
309  * <CODE>-95.7</CODE>, and <CODE>+6.2</CODE>; numbers in the range of
310  * <CODE>double</CODE> are supported. Approximate literals use the
311  * floating-point literal syntax of the Java programming language.
312  * <LI>The boolean literals <CODE>TRUE</CODE> and <CODE>FALSE</CODE>.
313  * </UL>
314  * <LI>Identifiers:
315  * <UL>
316  * <LI>An identifier is an unlimited-length sequence of letters
317  * and digits, the first of which must be a letter. A letter is any
318  * character for which the method <CODE>Character.isJavaLetter</CODE>
319  * returns true. This includes <CODE>'_'</CODE> and <CODE>'$'</CODE>.
320  * A letter or digit is any character for which the method
321  * <CODE>Character.isJavaLetterOrDigit</CODE> returns true.
322  * <LI>Identifiers cannot be the names <CODE>NULL</CODE>,
323  * <CODE>TRUE</CODE>, and <CODE>FALSE</CODE>.
324  * <LI>Identifiers cannot be <CODE>NOT</CODE>, <CODE>AND</CODE>,
325  * <CODE>OR</CODE>, <CODE>BETWEEN</CODE>, <CODE>LIKE</CODE>,
326  * <CODE>IN</CODE>, <CODE>IS</CODE>, or <CODE>ESCAPE</CODE>.
327  * <LI>Identifiers are either header field references or property
328  * references.  The type of a property value in a message selector
329  * corresponds to the type used to set the property. If a property
330  * that does not exist in a message is referenced, its value is
331  * <CODE>NULL</CODE>.
332  * <LI>The conversions that apply to the get methods for properties do not
333  * apply when a property is used in a message selector expression.
334  * For example, suppose you set a property as a string value, as in the
335  * following:
336  * <PRE>myMessage.setStringProperty("NumberOfOrders", "2");</PRE>
337  * The following expression in a message selector would evaluate to
338  * false, because a string cannot be used in an arithmetic expression:
339  * <PRE>"NumberOfOrders > 1"</PRE>
340  * <LI>Identifiers are case-sensitive.
341  * <LI>Message header field references are restricted to
342  * <CODE>JMSDeliveryMode</CODE>, <CODE>JMSPriority</CODE>,
343  * <CODE>JMSMessageID</CODE>, <CODE>JMSTimestamp</CODE>,
344  * <CODE>JMSCorrelationID</CODE>, and <CODE>JMSType</CODE>.
345  * <CODE>JMSMessageID</CODE>, <CODE>JMSCorrelationID</CODE>, and
346  * <CODE>JMSType</CODE> values may be null and if so are treated as a
347  * <CODE>NULL</CODE> value.
348  * <LI>Any name beginning with <CODE>'JMSX'</CODE> is a JMS defined
349  * property name.
350  * <LI>Any name beginning with <CODE>'JMS_'</CODE> is a provider-specific
351  * property name.
352  * <LI>Any name that does not begin with <CODE>'JMS'</CODE> is an
353  * application-specific property name.
354  * </UL>
355  * <LI>White space is the same as that defined for the Java programming
356  * language: space, horizontal tab, form feed, and line terminator.
357  * <LI>Expressions:
358  * <UL>
359  * <LI>A selector is a conditional expression; a selector that evaluates
360  * to <CODE>true</CODE> matches; a selector that evaluates to
361  * <CODE>false</CODE> or unknown does not match.
362  * <LI>Arithmetic expressions are composed of themselves, arithmetic
363  * operations, identifiers (whose value is treated as a numeric
364  * literal), and numeric literals.
365  * <LI>Conditional expressions are composed of themselves, comparison
366  * operations, and logical operations.
367  * </UL>
368  * <LI>Standard bracketing <CODE>()</CODE> for ordering expression evaluation
369  * is supported.
370  * <LI>Logical operators in precedence order: <CODE>NOT</CODE>,
371  * <CODE>AND</CODE>, <CODE>OR</CODE>
372  * <LI>Comparison operators: <CODE>=</CODE>, <CODE>></CODE>, <CODE>>=</CODE>,
373  * <CODE><</CODE>, <CODE><=</CODE>, <CODE><></CODE> (not equal)
374  * <UL>
375  * <LI>Only like type values can be compared. One exception is that it
376  * is valid to compare exact numeric values and approximate numeric
377  * values; the type conversion required is defined by the rules of
378  * numeric promotion in the Java programming language. If the
379  * comparison of non-like type values is attempted, the value of the
380  * operation is false. If either of the type values evaluates to
381  * <CODE>NULL</CODE>, the value of the expression is unknown.
382  * <LI>String and boolean comparison is restricted to <CODE>=</CODE> and
383  * <CODE><></CODE>. Two strings are equal
384  * if and only if they contain the same sequence of characters.
385  * </UL>
386  * <LI>Arithmetic operators in precedence order:
387  * <UL>
388  * <LI><CODE>+</CODE>, <CODE>-</CODE> (unary)
389  * <LI><CODE>*</CODE>, <CODE>/</CODE> (multiplication and division)
390  * <LI><CODE>+</CODE>, <CODE>-</CODE> (addition and subtraction)
391  * <LI>Arithmetic operations must use numeric promotion in the Java
392  * programming language.
393  * </UL>
394  * <LI><CODE><I>arithmetic-expr1</I> [NOT] BETWEEN <I>arithmetic-expr2</I>
395  * AND <I>arithmetic-expr3</I></CODE> (comparison operator)
396  * <UL>
397  * <LI><CODE>"age&nbsp;BETWEEN&nbsp;15&nbsp;AND&nbsp;19"</CODE> is
398  * equivalent to
399  * <CODE>"age&nbsp;>=&nbsp;15&nbsp;AND&nbsp;age&nbsp;<=&nbsp;19"</CODE>
400  * <LI><CODE>"age&nbsp;NOT&nbsp;BETWEEN&nbsp;15&nbsp;AND&nbsp;19"</CODE>
401  * is equivalent to
402  * <CODE>"age&nbsp;<&nbsp;15&nbsp;OR&nbsp;age&nbsp;>&nbsp;19"</CODE>
403  * </UL>
404  * <LI><CODE><I>identifier</I> [NOT] IN (<I>string-literal1</I>,
405  * <I>string-literal2</I>,...)</CODE> (comparison operator where
406  * <CODE><I>identifier</I></CODE> has a <CODE>String</CODE> or
407  * <CODE>NULL</CODE> value)
408  * <UL>
409  * <LI><CODE>"Country&nbsp;IN&nbsp;('&nbsp;UK',&nbsp;'US',&nbsp;'France')"</CODE>
410  * is true for
411  * <CODE>'UK'</CODE> and false for <CODE>'Peru'</CODE>; it is
412  * equivalent to the expression
413  * <CODE>"(Country&nbsp;=&nbsp;'&nbsp;UK')&nbsp;OR&nbsp;(Country&nbsp;=&nbsp;'&nbsp;US')&nbsp;OR&nbsp;(Country&nbsp;=&nbsp;'&nbsp;France')"</CODE>
414  * <LI><CODE>"Country&nbsp;NOT&nbsp;IN&nbsp;('&nbsp;UK',&nbsp;'US',&nbsp;'France')"</CODE>
415  * is false for <CODE>'UK'</CODE> and true for <CODE>'Peru'</CODE>; it
416  * is equivalent to the expression
417  * <CODE>"NOT&nbsp;((Country&nbsp;=&nbsp;'&nbsp;UK')&nbsp;OR&nbsp;(Country&nbsp;=&nbsp;'&nbsp;US')&nbsp;OR&nbsp;(Country&nbsp;=&nbsp;'&nbsp;France'))"</CODE>
418  * <LI>If identifier of an <CODE>IN</CODE> or <CODE>NOT IN</CODE>
419  * operation is <CODE>NULL</CODE>, the value of the operation is
420  * unknown.
421  * </UL>
422  * <LI><CODE><I>identifier</I> [NOT] LIKE <I>pattern-value</I> [ESCAPE
423  * <I>escape-character</I>]</CODE> (comparison operator, where
424  * <CODE><I>identifier</I></CODE> has a <CODE>String</CODE> value;
425  * <CODE><I>pattern-value</I></CODE> is a string literal where
426  * <CODE>'_'</CODE> stands for any single character; <CODE>'%'</CODE>
427  * stands for any sequence of characters, including the empty sequence;
428  * and all other characters stand for themselves. The optional
429  * <CODE><I>escape-character</I></CODE> is a single-character string
430  * literal whose character is used to escape the special meaning of the
431  * <CODE>'_'</CODE> and <CODE>'%'</CODE> in
432  * <CODE><I>pattern-value</I></CODE>.)
433  * <UL>
434  * <LI><CODE>"phone&nbsp;LIKE&nbsp;'12%3'"</CODE> is true for
435  * <CODE>'123'</CODE> or <CODE>'12993'</CODE> and false for
436  * <CODE>'1234'</CODE>
437  * <LI><CODE>"word&nbsp;LIKE&nbsp;'l_se'"</CODE> is true for
438  * <CODE>'lose'</CODE> and false for <CODE>'loose'</CODE>
439  * <LI><CODE>"underscored&nbsp;LIKE&nbsp;'\_%'&nbsp;ESCAPE&nbsp;'\'"</CODE>
440  * is true for <CODE>'_foo'</CODE> and false for <CODE>'bar'</CODE>
441  * <LI><CODE>"phone&nbsp;NOT&nbsp;LIKE&nbsp;'12%3'"</CODE> is false for
442  * <CODE>'123'</CODE> or <CODE>'12993'</CODE> and true for
443  * <CODE>'1234'</CODE>
444  * <LI>If <CODE><I>identifier</I></CODE> of a <CODE>LIKE</CODE> or
445  * <CODE>NOT LIKE</CODE> operation is <CODE>NULL</CODE>, the value
446  * of the operation is unknown.
447  * </UL>
448  * <LI><CODE><I>identifier</I> IS NULL</CODE> (comparison operator that tests
449  * for a null header field value or a missing property value)
450  * <UL>
451  * <LI><CODE>"prop_name&nbsp;IS&nbsp;NULL"</CODE>
452  * </UL>
453  * <LI><CODE><I>identifier</I> IS NOT NULL</CODE> (comparison operator that
454  * tests for the existence of a non-null header field value or a property
455  * value)
456  * <UL>
457  * <LI><CODE>"prop_name&nbsp;IS&nbsp;NOT&nbsp;NULL"</CODE>
458  * </UL>
459  * <p/>
460  * <P>JMS providers are required to verify the syntactic correctness of a
461  * message selector at the time it is presented. A method that provides a
462  * syntactically incorrect selector must result in a <CODE>JMSException</CODE>.
463  * JMS providers may also optionally provide some semantic checking at the time
464  * the selector is presented. Not all semantic checking can be performed at
465  * the time a message selector is presented, because property types are not known.
466  * <p/>
467  * <P>The following message selector selects messages with a message type
468  * of car and color of blue and weight greater than 2500 pounds:
469  * <p/>
470  * <PRE>"JMSType&nbsp;=&nbsp;'car'&nbsp;AND&nbsp;color&nbsp;=&nbsp;'blue'&nbsp;AND&nbsp;weight&nbsp;>&nbsp;2500"</PRE>
471  * <p/>
472  * <H4>Null Values</H4>
473  * <p/>
474  * <P>As noted above, property values may be <CODE>NULL</CODE>. The evaluation
475  * of selector expressions containing <CODE>NULL</CODE> values is defined by
476  * SQL92 <CODE>NULL</CODE> semantics. A brief description of these semantics
477  * is provided here.
478  * <p/>
479  * <P>SQL treats a <CODE>NULL</CODE> value as unknown. Comparison or arithmetic
480  * with an unknown value always yields an unknown value.
481  * <p/>
482  * <P>The <CODE>IS NULL</CODE> and <CODE>IS NOT NULL</CODE> operators convert
483  * an unknown value into the respective <CODE>TRUE</CODE> and
484  * <CODE>FALSE</CODE> values.
485  * <p/>
486  * <P>The boolean operators use three-valued logic as defined by the
487  * following tables:
488  * <p/>
489  * <P><B>The definition of the <CODE>AND</CODE> operator</B>
490  * <p/>
491  * <PRE>
492  * | AND  |   T   |   F   |   U
493  * +------+-------+-------+-------
494  * |  T   |   T   |   F   |   U
495  * |  F   |   F   |   F   |   F
496  * |  U   |   U   |   F   |   U
497  * +------+-------+-------+-------
498  * </PRE>
499  * <p/>
500  * <P><B>The definition of the <CODE>OR</CODE> operator</B>
501  * <p/>
502  * <PRE>
503  * | OR   |   T   |   F   |   U
504  * +------+-------+-------+--------
505  * |  T   |   T   |   T   |   T
506  * |  F   |   T   |   F   |   U
507  * |  U   |   T   |   U   |   U
508  * +------+-------+-------+-------
509  * </PRE>
510  * <p/>
511  * <P><B>The definition of the <CODE>NOT</CODE> operator</B>
512  * <p/>
513  * <PRE>
514  * | NOT
515  * +------+------
516  * |  T   |   F
517  * |  F   |   T
518  * |  U   |   U
519  * +------+-------
520  * </PRE>
521  * <p/>
522  * <H4>Special Notes</H4>
523  * <p/>
524  * <P>When used in a message selector, the <CODE>JMSDeliveryMode</CODE> header
525  * field is treated as having the values <CODE>'PERSISTENT'</CODE> and
526  * <CODE>'NON_PERSISTENT'</CODE>.
527  * <p/>
528  * <P>Date and time values should use the standard <CODE>long</CODE>
529  * millisecond value. When a date or time literal is included in a message
530  * selector, it should be an integer literal for a millisecond value. The
531  * standard way to produce millisecond values is to use
532  * <CODE>java.util.Calendar</CODE>.
533  * <p/>
534  * <P>Although SQL supports fixed decimal comparison and arithmetic, JMS
535  * message selectors do not. This is the reason for restricting exact
536  * numeric literals to those without a decimal (and the addition of
537  * numerics with a decimal as an alternate representation for
538  * approximate numeric values).
539  * <p/>
540  * <P>SQL comments are not supported.
541  *
542  * @version $Revision: 1.21 $
543  * @see javax.jms.MessageConsumer#receive()
544  * @see javax.jms.MessageConsumer#receive(long)
545  * @see javax.jms.MessageConsumer#receiveNoWait()
546  * @see javax.jms.MessageListener#onMessage(Message)
547  * @see javax.jms.BytesMessage
548  * @see javax.jms.MapMessage
549  * @see javax.jms.ObjectMessage
550  * @see javax.jms.StreamMessage
551  * @see javax.jms.TextMessage
552  */
553 
554 public class ActiveMQMessage extends AbstractPacket implements Message, Comparable {
555 
556     /***
557      * The message producer's default delivery mode is <CODE>PERSISTENT</CODE>.
558      *
559      * @see DeliveryMode#PERSISTENT
560      */
561     static final int DEFAULT_DELIVERY_MODE = DeliveryMode.PERSISTENT;
562 
563     /***
564      * The message producer's default priority is 4.
565      */
566     static final int DEFAULT_PRIORITY = 4;
567 
568     /***
569      * The message producer's default time to live is unlimited; the message
570      * never expires.
571      */
572     static final long DEFAULT_TIME_TO_LIVE = 0;
573 
574     /***
575      * message property types
576      */
577     final static byte EOF = 2;
578     final static byte BYTES = 3;
579     final static byte STRING = 4;
580     final static byte BOOLEAN = 5;
581     final static byte CHAR = 6;
582     final static byte BYTE = 7;
583     final static byte SHORT = 8;
584     final static byte INT = 9;
585     final static byte LONG = 10;
586     final static byte FLOAT = 11;
587     final static byte DOUBLE = 12;
588     final static byte NULL = 13;
589 
590     /***
591      * Message flag indexes (used for writing/reading to/from a Stream
592      */
593     static final int CORRELATION_INDEX = 0;
594     static final int TYPE_INDEX = 1;
595     static final int BROKER_NAME_INDEX = 2;
596     static final int CLUSTER_NAME_INDEX = 3;
597     static final int TRANSACTION_ID_INDEX = 4;
598     static final int REPLY_TO_INDEX = 5;
599     static final int TIMESTAMP_INDEX = 6;
600     static final int EXPIRATION_INDEX = 7;
601     static final int REDELIVERED_INDEX = 8;
602     static final int XA_TRANS_INDEX = 9;
603     static final int CID_INDEX = 10;
604     static final int PROPERTIES_INDEX = 11;
605     static final int PAYLOAD_INDEX = 12;
606 
607 
608     /***
609      * <code>readOnlyMessage</code> denotes if the message is read only
610      */
611     protected boolean readOnlyMessage;
612 
613     private String jmsMessageID;
614     private String jmsClientID;
615     private String jmsCorrelationID;
616     private ActiveMQDestination jmsDestination;
617     private ActiveMQDestination jmsReplyTo;
618     private int jmsDeliveryMode = DEFAULT_DELIVERY_MODE;
619     private boolean jmsRedelivered;
620     private String jmsType;
621     private long jmsExpiration;
622     private int jmsPriority = DEFAULT_PRIORITY;
623     private long jmsTimestamp;
624     private Hashtable properties;
625     private boolean readOnlyProperties;
626     private String entryBrokerName;
627     private String entryClusterName;
628     private int[] consumerNos; //these are set by the broker, and only relevant to consuming connections
629     private String producerID;//set by the Producer on sending the message
630     private String transactionId;
631     private boolean xaTransacted;
632     private String consumerId; //this is only used on the Client for acknowledging receipt of a message
633     private boolean messageConsumed;//only used on the client - to denote if its been delivered and read
634     private MessageAcknowledge messageAcknowledge;
635     private byte[] bodyAsBytes;
636     private MessageIdentity messageIdentity;
637 
638 
639     /***
640      * Retrieve if a JMS Message type or not
641      *
642      * @return true if it is a JMS Message
643      */
644     public boolean isJMSMessage() {
645         return true;
646     }
647 
648 
649     /***
650      * @return pretty print of this Message
651      */
652     public String toString() {
653         return getPacketTypeAsString(getPacketType()) + ": dest = " + jmsDestination + ", id = " + jmsMessageID;
654     }
655 
656 
657     /***
658      * @return Returns the messageAcknowledge.
659      */
660     public MessageAcknowledge getMessageAcknowledge() {
661         return messageAcknowledge;
662     }
663 
664     /***
665      * @param messageAcknowledge The messageAcknowledge to set.
666      */
667     public void setMessageAcknowledge(MessageAcknowledge messageAcknowledge) {
668         this.messageAcknowledge = messageAcknowledge;
669     }
670 
671     /***
672      * Return the type of Packet
673      *
674      * @return integer representation of the type of Packet
675      */
676 
677     public int getPacketType() {
678         return ACTIVEMQ_MESSAGE;
679     }
680 
681     /***
682      * @return the unique id for this Packet
683      */
684 
685     public String getId() {
686         return this.jmsMessageID;
687     }
688 
689     /***
690      * Set the unique id for this Packet
691      *
692      * @param newId
693      */
694 
695     public void setId(String newId) {
696         this.jmsMessageID = newId;
697     }
698 
699 
700     /***
701      * set the message readOnly
702      *
703      * @param value
704      */
705     public void setReadOnly(boolean value) {
706         this.readOnlyProperties = value;
707         this.readOnlyMessage = value;
708     }
709 
710     /***
711      * test to see if a particular Consumer at a Connection
712      * is meant to receive this Message
713      *
714      * @param consumerNumber
715      * @return true if a target
716      */
717 
718     public boolean isConsumerTarget(int consumerNumber) {
719         if (consumerNos != null) {
720             for (int i = 0; i < consumerNos.length; i++) {
721                 if (consumerNos[i] == consumerNumber) {
722                     return true;
723                 }
724             }
725         }
726         return false;
727 
728     }
729 
730     /***
731      * @return Returns hash code for this instance
732      */
733 
734     public int hashCode() {
735         return this.jmsMessageID != null ? this.jmsMessageID.hashCode() : super.hashCode();
736     }
737 
738     /***
739      * Returns true if this instance is equivalent to obj
740      *
741      * @param obj the other instance to test
742      * @return true/false
743      */
744 
745     public boolean equals(Object obj) {
746         boolean result = obj == this;
747         if (!result && obj != null && obj instanceof ActiveMQMessage) {
748             ActiveMQMessage other = (ActiveMQMessage) obj;
749             result = this.jmsMessageID == other.jmsMessageID ||
750                     (this.jmsMessageID != null && other.jmsMessageID != null &&
751                     this.jmsMessageID.equals(other.jmsMessageID));
752         }
753         return result;
754     }
755 
756     /***
757      * @param o object to compare
758      * @return 1 if this > o else 0 if they are equal or -1 if this < o
759      */
760     public int compareTo(Object o) {
761         if (o instanceof ActiveMQMessage) {
762             return compareTo((ActiveMQMessage) o);
763         }
764         return -1;
765     }
766 
767     /***
768      * Sorted by destination and then messageId
769      *
770      * @param that another message to compare against
771      * @return 1 if this > that else 0 if they are equal or -1 if this < that
772      */
773     public int compareTo(ActiveMQMessage that) {
774         int answer = 1;
775 
776         if (that != null && this.jmsDestination != null && that.jmsDestination != null) {
777             answer = this.jmsDestination.compareTo(that.jmsDestination);
778             if (answer == 0) {
779                 if (this.jmsMessageID != null && that.jmsMessageID != null) {
780                     answer = IdGenerator.compare(this.jmsMessageID, that.jmsMessageID);
781                 }
782                 else {
783                     answer = 1;
784                 }
785             }
786         }
787         return answer;
788     }
789 
790 
791     /***
792      * @return Returns a shallow copy of the message instance
793      * @throws JMSException
794      */
795 
796     public ActiveMQMessage shallowCopy() throws JMSException {
797         ActiveMQMessage other = new ActiveMQMessage();
798         this.initializeOther(other);
799         return other;
800     }
801 
802     /***
803      * @return Returns a deep copy of the message - note the header fields are only shallow copied
804      * @throws JMSException
805      */
806 
807     public ActiveMQMessage deepCopy() throws JMSException {
808         return shallowCopy();
809     }
810 
811 
812     /***
813      * Indicates if the Message has expired
814      *
815      * @param currentTime -
816      *                    the current time in milliseconds
817      * @return true if the message can be expired
818      * @throws JMSException on internal error
819      */
820     public boolean isExpired(long currentTime) throws JMSException {
821         boolean result = false;
822         long expiration = getJMSExpiration();
823         if (expiration > 0 && expiration < currentTime) {
824             result = true;
825         }
826         return result;
827     }
828 
829     public boolean isExpired() throws JMSException {
830         return isExpired(System.currentTimeMillis());
831     }
832 
833     /***
834      * Initializes another message with current values from this instance
835      *
836      * @param other the other ActiveMQMessage to initialize
837      */
838     protected void initializeOther(ActiveMQMessage other) {
839         other.jmsMessageID = this.jmsMessageID;
840         other.jmsClientID = this.jmsClientID;
841         other.jmsCorrelationID = this.jmsCorrelationID;
842         other.jmsDestination = this.jmsDestination;
843         other.jmsReplyTo = this.jmsReplyTo;
844         other.jmsDeliveryMode = this.jmsDeliveryMode;
845         other.jmsRedelivered = this.jmsRedelivered;
846         other.jmsType = this.jmsType;
847         other.jmsExpiration = this.jmsExpiration;
848         other.jmsPriority = this.jmsPriority;
849         other.jmsTimestamp = this.jmsTimestamp;
850         other.properties = this.properties;
851         other.readOnlyProperties = this.readOnlyProperties;
852         other.readOnlyMessage = this.readOnlyMessage;
853         other.entryBrokerName = this.entryBrokerName;
854         other.entryClusterName = this.entryClusterName;
855         other.consumerNos = this.consumerNos;
856         other.producerID = this.producerID;
857         other.transactionId = this.transactionId;
858         other.xaTransacted = this.xaTransacted;
859         other.bodyAsBytes = this.bodyAsBytes;
860         other.messageAcknowledge = this.messageAcknowledge;
861         other.messageIdentity = this.messageIdentity;
862     }
863 
864     /***
865      * Gets the message ID.
866      * <p/>
867      * <P>The <CODE>JMSMessageID</CODE> header field contains a value that
868      * uniquely identifies each message sent by a provider.
869      * <p/>
870      * <P>When a message is sent, <CODE>JMSMessageID</CODE> can be ignored.
871      * When the <CODE>send</CODE> or <CODE>publish</CODE> method returns, it
872      * contains a provider-assigned value.
873      * <p/>
874      * <P>A <CODE>JMSMessageID</CODE> is a <CODE>String</CODE> value that
875      * should function as a
876      * unique key for identifying messages in a historical repository.
877      * The exact scope of uniqueness is provider-defined. It should at
878      * least cover all messages for a specific installation of a
879      * provider, where an installation is some connected set of message
880      * routers.
881      * <p/>
882      * <P>All <CODE>JMSMessageID</CODE> values must start with the prefix
883      * <CODE>'ID:'</CODE>.
884      * Uniqueness of message ID values across different providers is
885      * not required.
886      * <p/>
887      * <P>Since message IDs take some effort to create and increase a
888      * message's size, some JMS providers may be able to optimize message
889      * overhead if they are given a hint that the message ID is not used by
890      * an application. By calling the
891      * <CODE>MessageProducer.setDisableMessageID</CODE> method, a JMS client
892      * enables this potential optimization for all messages sent by that
893      * message producer. If the JMS provider accepts this
894      * hint, these messages must have the message ID set to null; if the
895      * provider ignores the hint, the message ID must be set to its normal
896      * unique value.
897      *
898      * @return the message ID
899      * @see javax.jms.Message#setJMSMessageID(String)
900      * @see javax.jms.MessageProducer#setDisableMessageID(boolean)
901      */
902 
903     public String getJMSMessageID() {
904         return this.jmsMessageID;
905     }
906 
907 
908     /***
909      * Sets the message ID.
910      * <p/>
911      * <P>JMS providers set this field when a message is sent. This method
912      * can be used to change the value for a message that has been received.
913      *
914      * @param id the ID of the message
915      * @see javax.jms.Message#getJMSMessageID()
916      */
917 
918     public void setJMSMessageID(String id) {
919         this.jmsMessageID = id;
920     }
921 
922 
923     /***
924      * Gets the message timestamp.
925      * <p/>
926      * <P>The <CODE>JMSTimestamp</CODE> header field contains the time a
927      * message was
928      * handed off to a provider to be sent. It is not the time the
929      * message was actually transmitted, because the actual send may occur
930      * later due to transactions or other client-side queueing of messages.
931      * <p/>
932      * <P>When a message is sent, <CODE>JMSTimestamp</CODE> is ignored. When
933      * the <CODE>send</CODE> or <CODE>publish</CODE>
934      * method returns, it contains a time value somewhere in the interval
935      * between the call and the return. The value is in the format of a normal
936      * millis time value in the Java programming language.
937      * <p/>
938      * <P>Since timestamps take some effort to create and increase a
939      * message's size, some JMS providers may be able to optimize message
940      * overhead if they are given a hint that the timestamp is not used by an
941      * application. By calling the
942      * <CODE>MessageProducer.setDisableMessageTimestamp</CODE> method, a JMS
943      * client enables this potential optimization for all messages sent by
944      * that message producer. If the JMS provider accepts this
945      * hint, these messages must have the timestamp set to zero; if the
946      * provider ignores the hint, the timestamp must be set to its normal
947      * value.
948      *
949      * @return the message timestamp
950      * @see javax.jms.Message#setJMSTimestamp(long)
951      * @see javax.jms.MessageProducer#setDisableMessageTimestamp(boolean)
952      */
953 
954     public long getJMSTimestamp() {
955         return jmsTimestamp;
956     }
957 
958 
959     /***
960      * Sets the message timestamp.
961      * <p/>
962      * <P>JMS providers set this field when a message is sent. This method
963      * can be used to change the value for a message that has been received.
964      *
965      * @param timestamp the timestamp for this message
966      * @see javax.jms.Message#getJMSTimestamp()
967      */
968 
969     public void setJMSTimestamp(long timestamp) {
970         this.jmsTimestamp = timestamp;
971     }
972 
973 
974     /***
975      * Gets the correlation ID as an array of bytes for the message.
976      * <p/>
977      * <P>The use of a <CODE>byte[]</CODE> value for
978      * <CODE>JMSCorrelationID</CODE> is non-portable.
979      *
980      * @return the correlation ID of a message as an array of bytes
981      * @see javax.jms.Message#setJMSCorrelationID(String)
982      * @see javax.jms.Message#getJMSCorrelationID()
983      * @see javax.jms.Message#setJMSCorrelationIDAsBytes(byte[])
984      */
985 
986     public byte[] getJMSCorrelationIDAsBytes() {
987         return this.jmsCorrelationID != null ? this.jmsCorrelationID.getBytes() : null;
988     }
989 
990 
991     /***
992      * Sets the correlation ID as an array of bytes for the message.
993      * <p/>
994      * <P>The array is copied before the method returns, so
995      * future modifications to the array will not alter this message header.
996      * <p/>
997      * <P>If a provider supports the native concept of correlation ID, a
998      * JMS client may need to assign specific <CODE>JMSCorrelationID</CODE>
999      * values to match those expected by native messaging clients.
1000      * JMS providers without native correlation ID values are not required to
1001      * support this method and its corresponding get method; their
1002      * implementation may throw a
1003      * <CODE>java.lang.UnsupportedOperationException</CODE>.
1004      * <p/>
1005      * <P>The use of a <CODE>byte[]</CODE> value for
1006      * <CODE>JMSCorrelationID</CODE> is non-portable.
1007      *
1008      * @param correlationID the correlation ID value as an array of bytes
1009      * @see javax.jms.Message#setJMSCorrelationID(String)
1010      * @see javax.jms.Message#getJMSCorrelationID()
1011      * @see javax.jms.Message#getJMSCorrelationIDAsBytes()
1012      */
1013 
1014     public void setJMSCorrelationIDAsBytes(byte[] correlationID) {
1015         if (correlationID == null) {
1016             this.jmsCorrelationID = null;
1017         }
1018         else {
1019             this.jmsCorrelationID = new String(correlationID);
1020         }
1021     }
1022 
1023 
1024     /***
1025      * Sets the correlation ID for the message.
1026      * <p/>
1027      * <P>A client can use the <CODE>JMSCorrelationID</CODE> header field to
1028      * link one message with another. A typical use is to link a response
1029      * message with its request message.
1030      * <p/>
1031      * <P><CODE>JMSCorrelationID</CODE> can hold one of the following:
1032      * <UL>
1033      * <LI>A provider-specific message ID
1034      * <LI>An application-specific <CODE>String</CODE>
1035      * <LI>A provider-native <CODE>byte[]</CODE> value
1036      * </UL>
1037      * <p/>
1038      * <P>Since each message sent by a JMS provider is assigned a message ID
1039      * value, it is convenient to link messages via message ID. All message ID
1040      * values must start with the <CODE>'ID:'</CODE> prefix.
1041      * <p/>
1042      * <P>In some cases, an application (made up of several clients) needs to
1043      * use an application-specific value for linking messages. For instance,
1044      * an application may use <CODE>JMSCorrelationID</CODE> to hold a value
1045      * referencing some external information. Application-specified values
1046      * must not start with the <CODE>'ID:'</CODE> prefix; this is reserved for
1047      * provider-generated message ID values.
1048      * <p/>
1049      * <P>If a provider supports the native concept of correlation ID, a JMS
1050      * client may need to assign specific <CODE>JMSCorrelationID</CODE> values
1051      * to match those expected by clients that do not use the JMS API. A
1052      * <CODE>byte[]</CODE> value is used for this
1053      * purpose. JMS providers without native correlation ID values are not
1054      * required to support <CODE>byte[]</CODE> values. The use of a
1055      * <CODE>byte[]</CODE> value for <CODE>JMSCorrelationID</CODE> is
1056      * non-portable.
1057      *
1058      * @param correlationID the message ID of a message being referred to
1059      * @see javax.jms.Message#getJMSCorrelationID()
1060      * @see javax.jms.Message#getJMSCorrelationIDAsBytes()
1061      * @see javax.jms.Message#setJMSCorrelationIDAsBytes(byte[])
1062      */
1063 
1064     public void setJMSCorrelationID(String correlationID) {
1065         this.jmsCorrelationID = correlationID;
1066     }
1067 
1068 
1069     /***
1070      * Gets the correlation ID for the message.
1071      * <p/>
1072      * <P>This method is used to return correlation ID values that are
1073      * either provider-specific message IDs or application-specific
1074      * <CODE>String</CODE> values.
1075      *
1076      * @return the correlation ID of a message as a <CODE>String</CODE>
1077      * @see javax.jms.Message#setJMSCorrelationID(String)
1078      * @see javax.jms.Message#getJMSCorrelationIDAsBytes()
1079      * @see javax.jms.Message#setJMSCorrelationIDAsBytes(byte[])
1080      */
1081 
1082     public String getJMSCorrelationID() {
1083         return this.jmsCorrelationID;
1084     }
1085 
1086 
1087     /***
1088      * Gets the <CODE>Destination</CODE> object to which a reply to this
1089      * message should be sent.
1090      *
1091      * @return <CODE>Destination</CODE> to which to send a response to this
1092      *         message
1093      * @see javax.jms.Message#setJMSReplyTo(Destination)
1094      */
1095 
1096     public Destination getJMSReplyTo() {
1097         return this.jmsReplyTo;
1098 
1099     }
1100 
1101 
1102     /***
1103      * Sets the <CODE>Destination</CODE> object to which a reply to this
1104      * message should be sent.
1105      * <p/>
1106      * <P>The <CODE>JMSReplyTo</CODE> header field contains the destination
1107      * where a reply
1108      * to the current message should be sent. If it is null, no reply is
1109      * expected. The destination may be either a <CODE>Queue</CODE> object or
1110      * a <CODE>Topic</CODE> object.
1111      * <p/>
1112      * <P>Messages sent with a null <CODE>JMSReplyTo</CODE> value may be a
1113      * notification of some event, or they may just be some data the sender
1114      * thinks is of interest.
1115      * <p/>
1116      * <P>Messages with a <CODE>JMSReplyTo</CODE> value typically expect a
1117      * response. A response is optional; it is up to the client to decide.
1118      * These messages are called requests. A message sent in response to a
1119      * request is called a reply.
1120      * <p/>
1121      * <P>In some cases a client may wish to match a request it sent earlier
1122      * with a reply it has just received. The client can use the
1123      * <CODE>JMSCorrelationID</CODE> header field for this purpose.
1124      *
1125      * @param replyTo <CODE>Destination</CODE> to which to send a response to
1126      *                this message
1127      * @see javax.jms.Message#getJMSReplyTo()
1128      */
1129 
1130     public void setJMSReplyTo(Destination replyTo) {
1131         this.jmsReplyTo = (ActiveMQDestination) replyTo;
1132     }
1133 
1134 
1135     /***
1136      * Gets the <CODE>Destination</CODE> object for this message.
1137      * <p/>
1138      * <P>The <CODE>JMSDestination</CODE> header field contains the
1139      * destination to which the message is being sent.
1140      * <p/>
1141      * <P>When a message is sent, this field is ignored. After completion
1142      * of the <CODE>send</CODE> or <CODE>publish</CODE> method, the field
1143      * holds the destination specified by the method.
1144      * <p/>
1145      * <P>When a message is received, its <CODE>JMSDestination</CODE> value
1146      * must be equivalent to the value assigned when it was sent.
1147      *
1148      * @return the destination of this message
1149      * @see javax.jms.Message#setJMSDestination(Destination)
1150      */
1151 
1152     public Destination getJMSDestination() {
1153         return this.jmsDestination;
1154     }
1155 
1156 
1157     /***
1158      * Sets the <CODE>Destination</CODE> object for this message.
1159      * <p/>
1160      * <P>JMS providers set this field when a message is sent. This method
1161      * can be used to change the value for a message that has been received.
1162      *
1163      * @param destination the destination for this message
1164      * @see javax.jms.Message#getJMSDestination()
1165      */
1166 
1167     public void setJMSDestination(Destination destination) {
1168         this.jmsDestination = (ActiveMQDestination) destination;
1169     }
1170 
1171 
1172     /***
1173      * Gets the <CODE>DeliveryMode</CODE> value specified for this message.
1174      *
1175      * @return the delivery mode for this message
1176      * @see javax.jms.Message#setJMSDeliveryMode(int)
1177      * @see javax.jms.DeliveryMode
1178      */
1179 
1180     public int getJMSDeliveryMode() {
1181         return this.jmsDeliveryMode;
1182     }
1183 
1184 
1185     /***
1186      * Sets the <CODE>DeliveryMode</CODE> value for this message.
1187      * <p/>
1188      * <P>JMS providers set this field when a message is sent. This method
1189      * can be used to change the value for a message that has been received.
1190      *
1191      * @param deliveryMode the delivery mode for this message
1192      * @see javax.jms.Message#getJMSDeliveryMode()
1193      * @see javax.jms.DeliveryMode
1194      */
1195 
1196     public void setJMSDeliveryMode(int deliveryMode) {
1197         this.jmsDeliveryMode = deliveryMode;
1198     }
1199 
1200 
1201     /***
1202      * Gets an indication of whether this message is being redelivered.
1203      * <p/>
1204      * <P>If a client receives a message with the <CODE>JMSRedelivered</CODE>
1205      * field set,
1206      * it is likely, but not guaranteed, that this message was delivered
1207      * earlier but that its receipt was not acknowledged
1208      * at that time.
1209      *
1210      * @return true if this message is being redelivered
1211      * @see javax.jms.Message#setJMSRedelivered(boolean)
1212      */
1213 
1214     public boolean getJMSRedelivered() {
1215         return this.jmsRedelivered;
1216     }
1217 
1218 
1219     /***
1220      * Specifies whether this message is being redelivered.
1221      * <p/>
1222      * <P>This field is set at the time the message is delivered. This
1223      * method can be used to change the value for a message that has
1224      * been received.
1225      *
1226      * @param redelivered an indication of whether this message is being
1227      *                    redelivered
1228      * @see javax.jms.Message#getJMSRedelivered()
1229      */
1230 
1231     public void setJMSRedelivered(boolean redelivered) {
1232         this.jmsRedelivered = redelivered;
1233     }
1234 
1235 
1236     /***
1237      * Gets the message type identifier supplied by the client when the
1238      * message was sent.
1239      *
1240      * @return the message type
1241      * @see javax.jms.Message#setJMSType(String)
1242      */
1243 
1244     public String getJMSType() {
1245         return this.jmsType;
1246     }
1247 
1248     /***
1249      * Sets the message type.
1250      * <p/>
1251      * <P>Some JMS providers use a message repository that contains the
1252      * definitions of messages sent by applications. The <CODE>JMSType</CODE>
1253      * header field may reference a message's definition in the provider's
1254      * repository.
1255      * <p/>
1256      * <P>The JMS API does not define a standard message definition repository,
1257      * nor does it define a naming policy for the definitions it contains.
1258      * <p/>
1259      * <P>Some messaging systems require that a message type definition for
1260      * each application message be created and that each message specify its
1261      * type. In order to work with such JMS providers, JMS clients should
1262      * assign a value to <CODE>JMSType</CODE>, whether the application makes
1263      * use of it or not. This ensures that the field is properly set for those
1264      * providers that require it.
1265      * <p/>
1266      * <P>To ensure portability, JMS clients should use symbolic values for
1267      * <CODE>JMSType</CODE> that can be configured at installation time to the
1268      * values defined in the current provider's message repository. If string
1269      * literals are used, they may not be valid type names for some JMS
1270      * providers.
1271      *
1272      * @param type the message type
1273      * @see javax.jms.Message#getJMSType()
1274      */
1275 
1276     public void setJMSType(String type) {
1277         this.jmsType = type;
1278     }
1279 
1280 
1281     /***
1282      * Gets the message's expiration value.
1283      * <p/>
1284      * <P>When a message is sent, the <CODE>JMSExpiration</CODE> header field
1285      * is left unassigned. After completion of the <CODE>send</CODE> or
1286      * <CODE>publish</CODE> method, it holds the expiration time of the
1287      * message. This is the sum of the time-to-live value specified by the
1288      * client and the GMT at the time of the <CODE>send</CODE> or
1289      * <CODE>publish</CODE>.
1290      * <p/>
1291      * <P>If the time-to-live is specified as zero, <CODE>JMSExpiration</CODE>
1292      * is set to zero to indicate that the message does not expire.
1293      * <p/>
1294      * <P>When a message's expiration time is reached, a provider should
1295      * discard it. The JMS API does not define any form of notification of
1296      * message expiration.
1297      * <p/>
1298      * <P>Clients should not receive messages that have expired; however,
1299      * the JMS API does not guarantee that this will not happen.
1300      *
1301      * @return the time the message expires, which is the sum of the
1302      *         time-to-live value specified by the client and the GMT at the
1303      *         time of the send
1304      * @see javax.jms.Message#setJMSExpiration(long)
1305      */
1306 
1307     public long getJMSExpiration() {
1308         return this.jmsExpiration;
1309     }
1310 
1311 
1312     /***
1313      * Sets the message's expiration value.
1314      * <p/>
1315      * <P>JMS providers set this field when a message is sent. This method
1316      * can be used to change the value for a message that has been received.
1317      *
1318      * @param expiration the message's expiration time
1319      * @see javax.jms.Message#getJMSExpiration()
1320      */
1321 
1322     public void setJMSExpiration(long expiration) {
1323         this.jmsExpiration = expiration;
1324     }
1325 
1326     /***
1327      * Gets the message priority level.
1328      * <p/>
1329      * <P>The JMS API defines ten levels of priority value, with 0 as the
1330      * lowest
1331      * priority and 9 as the highest. In addition, clients should consider
1332      * priorities 0-4 as gradations of normal priority and priorities 5-9
1333      * as gradations of expedited priority.
1334      * <p/>
1335      * <P>The JMS API does not require that a provider strictly implement
1336      * priority
1337      * ordering of messages; however, it should do its best to deliver
1338      * expedited messages ahead of normal messages.
1339      *
1340      * @return the default message priority
1341      * @see javax.jms.Message#setJMSPriority(int)
1342      */
1343 
1344     public int getJMSPriority() {
1345         return this.jmsPriority;
1346     }
1347 
1348 
1349     /***
1350      * Sets the priority level for this message.
1351      * <p/>
1352      * <P>JMS providers set this field when a message is sent. This method
1353      * can be used to change the value for a message that has been received.
1354      *
1355      * @param priority the priority of this message
1356      * @see javax.jms.Message#getJMSPriority()
1357      */
1358 
1359     public void setJMSPriority(int priority) {
1360         this.jmsPriority = priority;
1361     }
1362 
1363     /***
1364      * Clears a message's properties.
1365      * <p/>
1366      * <P>The message's header fields and body are not cleared.
1367      */
1368 
1369     public synchronized void clearProperties() {
1370         if (this.properties != null) {
1371             this.properties.clear();
1372         }
1373         this.readOnlyProperties = false;
1374     }
1375 
1376 
1377     /***
1378      * Indicates whether a property value exists.
1379      *
1380      * @param name the name of the property to test
1381      * @return true if the property exists
1382      */
1383 
1384     public boolean propertyExists(String name) {
1385         return this.properties != null ? this.properties.containsKey(name) : false;
1386     }
1387 
1388 
1389     /***
1390      * Returns the value of the <CODE>boolean</CODE> property with the
1391      * specified name.
1392      *
1393      * @param name the name of the <CODE>boolean</CODE> property
1394      * @return the <CODE>boolean</CODE> property value for the specified name
1395      * @throws JMSException           if the JMS provider fails to get the property
1396      *                                value due to some internal error.
1397      * @throws MessageFormatException if this type conversion is invalid.
1398      */
1399 
1400     public boolean getBooleanProperty(String name) throws JMSException {
1401         return vanillaToBoolean(this.properties, name);
1402     }
1403 
1404 
1405     /***
1406      * Returns the value of the <CODE>byte</CODE> property with the specified
1407      * name.
1408      *
1409      * @param name the name of the <CODE>byte</CODE> property
1410      * @return the <CODE>byte</CODE> property value for the specified name
1411      * @throws JMSException           if the JMS provider fails to get the property
1412      *                                value due to some internal error.
1413      * @throws MessageFormatException if this type conversion is invalid.
1414      */
1415 
1416     public byte getByteProperty(String name) throws JMSException {
1417         return vanillaToByte(this.properties, name);
1418     }
1419 
1420 
1421     /***
1422      * Returns the value of the <CODE>short</CODE> property with the specified
1423      * name.
1424      *
1425      * @param name the name of the <CODE>short</CODE> property
1426      * @return the <CODE>short</CODE> property value for the specified name
1427      * @throws JMSException           if the JMS provider fails to get the property
1428      *                                value due to some internal error.
1429      * @throws MessageFormatException if this type conversion is invalid.
1430      */
1431 
1432     public short getShortProperty(String name) throws JMSException {
1433         return vanillaToShort(this.properties, name);
1434     }
1435 
1436 
1437     /***
1438      * Returns the value of the <CODE>int</CODE> property with the specified
1439      * name.
1440      *
1441      * @param name the name of the <CODE>int</CODE> property
1442      * @return the <CODE>int</CODE> property value for the specified name
1443      * @throws JMSException           if the JMS provider fails to get the property
1444      *                                value due to some internal error.
1445      * @throws MessageFormatException if this type conversion is invalid.
1446      */
1447 
1448     public int getIntProperty(String name) throws JMSException {
1449         return vanillaToInt(this.properties, name);
1450     }
1451 
1452 
1453     /***
1454      * Returns the value of the <CODE>long</CODE> property with the specified
1455      * name.
1456      *
1457      * @param name the name of the <CODE>long</CODE> property
1458      * @return the <CODE>long</CODE> property value for the specified name
1459      * @throws JMSException           if the JMS provider fails to get the property
1460      *                                value due to some internal error.
1461      * @throws MessageFormatException if this type conversion is invalid.
1462      */
1463 
1464     public long getLongProperty(String name) throws JMSException {
1465         return vanillaToLong(this.properties, name);
1466     }
1467 
1468 
1469     /***
1470      * Returns the value of the <CODE>float</CODE> property with the specified
1471      * name.
1472      *
1473      * @param name the name of the <CODE>float</CODE> property
1474      * @return the <CODE>float</CODE> property value for the specified name
1475      * @throws JMSException           if the JMS provider fails to get the property
1476      *                                value due to some internal error.
1477      * @throws MessageFormatException if this type conversion is invalid.
1478      */
1479 
1480     public float getFloatProperty(String name) throws JMSException {
1481         return vanillaToFloat(this.properties, name);
1482     }
1483 
1484 
1485     /***
1486      * Returns the value of the <CODE>double</CODE> property with the specified
1487      * name.
1488      *
1489      * @param name the name of the <CODE>double</CODE> property
1490      * @return the <CODE>double</CODE> property value for the specified name
1491      * @throws JMSException           if the JMS provider fails to get the property
1492      *                                value due to some internal error.
1493      * @throws MessageFormatException if this type conversion is invalid.
1494      */
1495 
1496     public double getDoubleProperty(String name) throws JMSException {
1497         return vanillaToDouble(this.properties, name);
1498     }
1499 
1500 
1501     /***
1502      * Returns the value of the <CODE>String</CODE> property with the specified
1503      * name.
1504      *
1505      * @param name the name of the <CODE>String</CODE> property
1506      * @return the <CODE>String</CODE> property value for the specified name;
1507      *         if there is no property by this name, a null value is returned
1508      * @throws JMSException           if the JMS provider fails to get the property
1509      *                                value due to some internal error.
1510      * @throws MessageFormatException if this type conversion is invalid.
1511      */
1512 
1513     public String getStringProperty(String name) throws JMSException {
1514         return vanillaToString(this.properties, name);
1515     }
1516 
1517 
1518     /***
1519      * Returns the value of the Java object property with the specified name.
1520      * <p/>
1521      * <P>This method can be used to return, in objectified format,
1522      * an object that has been stored as a property in the message with the
1523      * equivalent <CODE>setObjectProperty</CODE> method call, or its equivalent
1524      * primitive <CODE>set<I>type</I>Property</CODE> method.
1525      *
1526      * @param name the name of the Java object property
1527      * @return the Java object property value with the specified name, in
1528      *         objectified format (for example, if the property was set as an
1529      *         <CODE>int</CODE>, an <CODE>Integer</CODE> is
1530      *         returned); if there is no property by this name, a null value
1531      *         is returned
1532      */
1533 
1534     public Object getObjectProperty(String name) {
1535         return this.properties != null ? this.properties.get(name) : null;
1536     }
1537 
1538 
1539     /***
1540      * Returns an <CODE>Enumeration</CODE> of all the property names.
1541      * <p/>
1542      * <P>Note that JMS standard header fields are not considered
1543      * properties and are not returned in this enumeration.
1544      *
1545      * @return an enumeration of all the names of property values
1546      */
1547 
1548     public Enumeration getPropertyNames() {
1549         if (this.properties == null) {
1550             this.properties = new Hashtable();
1551         }
1552         return this.properties.keys();
1553     }
1554 
1555     /***
1556      * Retrieve the message properties as a Hashtable
1557      *
1558      * @return the Hashtable representing the properties or null if not set or used
1559      */
1560 
1561     public Hashtable getProperties() {
1562         return this.properties;
1563     }
1564 
1565     /***
1566      * Set the Message's properties from an external source
1567      * No checking on correct types is done by this method
1568      *
1569      * @param newProperties
1570      */
1571 
1572     void setProperties(Hashtable newProperties) {
1573         this.properties = newProperties;
1574     }
1575 
1576 
1577     /***
1578      * Sets a <CODE>boolean</CODE> property value with the specified name into
1579      * the message.
1580      *
1581      * @param name  the name of the <CODE>boolean</CODE> property
1582      * @param value the <CODE>boolean</CODE> property value to set
1583      * @throws JMSException                 if the JMS provider fails to set the property
1584      *                                      due to some internal error.
1585      * @throws IllegalArgumentException     if the name is null or if the name is
1586      *                                      an empty string.
1587      * @throws MessageNotWriteableException if properties are read-only
1588      */
1589 
1590     public void setBooleanProperty(String name, boolean value) throws JMSException {
1591         prepareProperty(name);
1592         this.properties.put(name, (value) ? Boolean.TRUE : Boolean.FALSE);
1593     }
1594 
1595 
1596     /***
1597      * Sets a <CODE>byte</CODE> property value with the specified name into
1598      * the message.
1599      *
1600      * @param name  the name of the <CODE>byte</CODE> property
1601      * @param value the <CODE>byte</CODE> property value to set
1602      * @throws JMSException                 if the JMS provider fails to set the property
1603      *                                      due to some internal error.
1604      * @throws IllegalArgumentException     if the name is null or if the name is
1605      *                                      an empty string.
1606      * @throws MessageNotWriteableException if properties are read-only
1607      */
1608 
1609     public void setByteProperty(String name, byte value) throws JMSException {
1610         prepareProperty(name);
1611         this.properties.put(name, new Byte(value));
1612     }
1613 
1614 
1615     /***
1616      * Sets a <CODE>short</CODE> property value with the specified name into
1617      * the message.
1618      *
1619      * @param name  the name of the <CODE>short</CODE> property
1620      * @param value the <CODE>short</CODE> property value to set
1621      * @throws JMSException                 if the JMS provider fails to set the property
1622      *                                      due to some internal error.
1623      * @throws IllegalArgumentException     if the name is null or if the name is
1624      *                                      an empty string.
1625      * @throws MessageNotWriteableException if properties are read-only
1626      */
1627 
1628     public void setShortProperty(String name, short value) throws JMSException {
1629         prepareProperty(name);
1630         this.properties.put(name, new Short(value));
1631     }
1632 
1633 
1634     /***
1635      * Sets an <CODE>int</CODE> property value with the specified name into
1636      * the message.
1637      *
1638      * @param name  the name of the <CODE>int</CODE> property
1639      * @param value the <CODE>int</CODE> property value to set
1640      * @throws JMSException                 if the JMS provider fails to set the property
1641      *                                      due to some internal error.
1642      * @throws IllegalArgumentException     if the name is null or if the name is
1643      *                                      an empty string.
1644      * @throws MessageNotWriteableException if properties are read-only
1645      */
1646 
1647     public void setIntProperty(String name, int value) throws JMSException {
1648         prepareProperty(name);
1649         this.properties.put(name, new Integer(value));
1650     }
1651 
1652 
1653     /***
1654      * Sets a <CODE>long</CODE> property value with the specified name into
1655      * the message.
1656      *
1657      * @param name  the name of the <CODE>long</CODE> property
1658      * @param value the <CODE>long</CODE> property value to set
1659      * @throws JMSException                 if the JMS provider fails to set the property
1660      *                                      due to some internal error.
1661      * @throws IllegalArgumentException     if the name is null or if the name is
1662      *                                      an empty string.
1663      * @throws MessageNotWriteableException if properties are read-only
1664      */
1665 
1666     public void setLongProperty(String name, long value) throws JMSException {
1667         prepareProperty(name);
1668         this.properties.put(name, new Long(value));
1669     }
1670 
1671 
1672     /***
1673      * Sets a <CODE>float</CODE> property value with the specified name into
1674      * the message.
1675      *
1676      * @param name  the name of the <CODE>float</CODE> property
1677      * @param value the <CODE>float</CODE> property value to set
1678      * @throws JMSException                 if the JMS provider fails to set the property
1679      *                                      due to some internal error.
1680      * @throws IllegalArgumentException     if the name is null or if the name is
1681      *                                      an empty string.
1682      * @throws MessageNotWriteableException if properties are read-only
1683      */
1684 
1685     public void setFloatProperty(String name, float value) throws JMSException {
1686         prepareProperty(name);
1687         this.properties.put(name, new Float(value));
1688 
1689     }
1690 
1691 
1692     /***
1693      * Sets a <CODE>double</CODE> property value with the specified name into
1694      * the message.
1695      *
1696      * @param name  the name of the <CODE>double</CODE> property
1697      * @param value the <CODE>double</CODE> property value to set
1698      * @throws JMSException                 if the JMS provider fails to set the property
1699      *                                      due to some internal error.
1700      * @throws IllegalArgumentException     if the name is null or if the name is
1701      *                                      an empty string.
1702      * @throws MessageNotWriteableException if properties are read-only
1703      */
1704 
1705     public void setDoubleProperty(String name, double value) throws JMSException {
1706         prepareProperty(name);
1707         this.properties.put(name, new Double(value));
1708     }
1709 
1710 
1711     /***
1712      * Sets a <CODE>String</CODE> property value with the specified name into
1713      * the message.
1714      *
1715      * @param name  the name of the <CODE>String</CODE> property
1716      * @param value the <CODE>String</CODE> property value to set
1717      * @throws JMSException                 if the JMS provider fails to set the property
1718      *                                      due to some internal error.
1719      * @throws IllegalArgumentException     if the name is null or if the name is
1720      *                                      an empty string.
1721      * @throws MessageNotWriteableException if properties are read-only
1722      */
1723 
1724     public void setStringProperty(String name, String value) throws JMSException {
1725         prepareProperty(name);
1726         this.properties.put(name, value);
1727     }
1728 
1729 
1730     /***
1731      * Sets a Java object property value with the specified name into the
1732      * message.
1733      * <p/>
1734      * <P>Note that this method works only for the objectified primitive
1735      * object types (<CODE>Integer</CODE>, <CODE>Double</CODE>,
1736      * <CODE>Long</CODE> ...) and <CODE>String</CODE> objects.
1737      *
1738      * @param name  the name of the Java object property
1739      * @param value the Java object property value to set
1740      * @throws JMSException                 if the JMS provider fails to set the property
1741      *                                      due to some internal error.
1742      * @throws IllegalArgumentException     if the name is null or if the name is
1743      *                                      an empty string.
1744      * @throws MessageFormatException       if the object is invalid
1745      * @throws MessageNotWriteableException if properties are read-only
1746      */
1747 
1748     public void setObjectProperty(String name, Object value) throws JMSException {
1749         prepareProperty(name);
1750         if (value instanceof Number ||
1751                 value instanceof Character ||
1752                 value instanceof Boolean ||
1753                 value instanceof String) {
1754             this.properties.put(name, value);
1755         }
1756         else {
1757             throw new MessageFormatException("Cannot set property to type: " + value.getClass().getName());
1758         } // if-else
1759 
1760     }
1761 
1762 
1763     /***
1764      * Acknowledges all consumed messages of the session of this consumed
1765      * message.
1766      * <p/>
1767      * <P>All consumed JMS messages support the <CODE>acknowledge</CODE>
1768      * method for use when a client has specified that its JMS session's
1769      * consumed messages are to be explicitly acknowledged.  By invoking
1770      * <CODE>acknowledge</CODE> on a consumed message, a client acknowledges
1771      * all messages consumed by the session that the message was delivered to.
1772      * <p/>
1773      * <P>Calls to <CODE>acknowledge</CODE> are ignored for both transacted
1774      * sessions and sessions specified to use implicit acknowledgement modes.
1775      * <p/>
1776      * <P>A client may individually acknowledge each message as it is consumed,
1777      * or it may choose to acknowledge messages as an application-defined group
1778      * (which is done by calling acknowledge on the last received message of the group,
1779      * thereby acknowledging all messages consumed by the session.)
1780      * <p/>
1781      * <P>Messages that have been received but not acknowledged may be
1782      * redelivered.
1783      *
1784      * @throws JMSException if the JMS provider fails to acknowledge the
1785      *                      messages due to some internal error.
1786      * @throws javax.jms.IllegalStateException
1787      *                      if this method is called on a closed
1788      *                      session.
1789      * @see javax.jms.Session#CLIENT_ACKNOWLEDGE
1790      */
1791 
1792     public void acknowledge() throws JMSException {
1793         if (this.messageAcknowledge != null) {
1794             this.messageAcknowledge.acknowledge();
1795         }
1796     }
1797 
1798 
1799     /***
1800      * Clears out the message body. Clearing a message's body does not clear
1801      * its header values or property entries.
1802      * <p/>
1803      * <P>If this message body was read-only, calling this method leaves
1804      * the message body in the same state as an empty body in a newly
1805      * created message.
1806      *
1807      * @throws JMSException if the JMS provider fails to clear the message
1808      *                      body due to some internal error.
1809      */
1810 
1811     public void clearBody() throws JMSException {
1812         this.readOnlyMessage = false;
1813         this.bodyAsBytes = null;
1814     }
1815 
1816     boolean vanillaToBoolean(Hashtable table, String name) throws JMSException {
1817         boolean result = false;
1818         if (table != null) {
1819             Object value = table.get(name);
1820             if (value != null) {
1821                 if (value instanceof Boolean) {
1822                     result = ((Boolean) value).booleanValue();
1823                 }
1824                 else if (value instanceof String) {
1825                     // will throw a runtime exception if cannot convert
1826                     result = Boolean.valueOf((String) value).booleanValue();
1827                 }
1828                 else {
1829                     throw new MessageFormatException(name + " not a Boolean type");
1830                 }
1831             }
1832         }
1833         return result;
1834     }
1835 
1836     byte vanillaToByte(Hashtable table, String name) throws JMSException {
1837         byte result = 0;
1838         if (table != null) {
1839             Object value = table.get(name);
1840             if (value != null) {
1841                 if (value instanceof Byte) {
1842                     result = ((Byte) value).byteValue();
1843                 }
1844                 else if (value instanceof String) {
1845                     result = Byte.valueOf((String) value).byteValue();
1846                 }
1847                 else {
1848                     throw new MessageFormatException(name + " not a Byte type");
1849                 }
1850             }
1851             else {
1852 //object doesn't exist - so treat as a null ..
1853                 throw new NumberFormatException("Cannot interpret null as a Byte");
1854             }
1855         }
1856         return result;
1857     }
1858 
1859     short vanillaToShort(Hashtable table, String name) throws JMSException {
1860         short result = 0;
1861         if (table != null) {
1862             Object value = table.get(name);
1863             if (value != null) {
1864                 if (value instanceof Short) {
1865                     result = ((Short) value).shortValue();
1866                 }
1867                 else if (value instanceof String) {
1868                     return Short.valueOf((String) value).shortValue();
1869                 }
1870                 else if (value instanceof Byte) {
1871                     result = ((Byte) value).byteValue();
1872                 }
1873                 else {
1874                     throw new MessageFormatException(name + " not a Short type");
1875                 }
1876             }
1877             else {
1878                 throw new NumberFormatException(name + " is null");
1879             }
1880         }
1881         return result;
1882     }
1883 
1884     int vanillaToInt(Hashtable table, String name) throws JMSException {
1885         int result = 0;
1886         if (table != null) {
1887             Object value = table.get(name);
1888             if (value != null) {
1889                 if (value instanceof Integer) {
1890                     result = ((Integer) value).intValue();
1891                 }
1892                 else if (value instanceof String) {
1893                     result = Integer.valueOf((String) value).intValue();
1894                 }
1895                 else if (value instanceof Byte) {
1896                     result = ((Byte) value).intValue();
1897                 }
1898                 else if (value instanceof Short) {
1899                     result = ((Short) value).intValue();
1900                 }
1901                 else {
1902                     throw new MessageFormatException(name + " not an Integer type");
1903                 }
1904             }
1905             else {
1906                 throw new NumberFormatException(name + " is null");
1907             }
1908         }
1909         return result;
1910     }
1911 
1912     long vanillaToLong(Hashtable table, String name) throws JMSException {
1913         long result = 0;
1914         if (table != null) {
1915             Object value = table.get(name);
1916             if (value != null) {
1917                 if (value instanceof Long) {
1918                     result = ((Long) value).longValue();
1919                 }
1920                 else if (value instanceof String) {
1921                     // will throw a runtime exception if cannot convert
1922                     result = Long.valueOf((String) value).longValue();
1923                 }
1924                 else if (value instanceof Byte) {
1925                     result = ((Byte) value).byteValue();
1926                 }
1927                 else if (value instanceof Short) {
1928                     result = ((Short) value).shortValue();
1929                 }
1930                 else if (value instanceof Integer) {
1931                     result = ((Integer) value).intValue();
1932                 }
1933                 else {
1934                     throw new MessageFormatException(name + " not a Long type");
1935                 }
1936             }
1937             else {
1938                 throw new NumberFormatException(name + " is null");
1939             }
1940         }
1941         return result;
1942     }
1943 
1944     float vanillaToFloat(Hashtable table, String name) throws JMSException {
1945         float result = 0.0f;
1946         if (table != null) {
1947             Object value = table.get(name);
1948             if (value != null) {
1949                 if (value instanceof Float) {
1950                     result = ((Float) value).floatValue();
1951                 }
1952                 else if (value instanceof String) {
1953                     result = Float.valueOf((String) value).floatValue();
1954                 }
1955                 else {
1956                     throw new MessageFormatException(name + " not a Float type: " + value.getClass());
1957                 }
1958             }
1959             else {
1960                 throw new NullPointerException(name + " is null");
1961             }
1962         }
1963         return result;
1964     }
1965 
1966     double vanillaToDouble(Hashtable table, String name) throws JMSException {
1967         double result = 0.0d;
1968         if (table != null) {
1969             Object value = table.get(name);
1970             if (value != null) {
1971                 if (value instanceof Double) {
1972                     result = ((Double) value).doubleValue();
1973                 }
1974                 else if (value instanceof String) {
1975                     result = Double.valueOf((String) value).doubleValue();
1976                 }
1977                 else if (value instanceof Float) {
1978                     result = ((Float) value).floatValue();
1979                 }
1980                 else {
1981                     throw new MessageFormatException(name + " not a Double type");
1982                 }
1983             }
1984             else {
1985                 throw new NullPointerException(name + " is null");
1986             }
1987         }
1988         return result;
1989     }
1990 
1991 
1992     String vanillaToString(Hashtable table, String name) throws JMSException {
1993         String result = null;
1994         if (table != null) {
1995             Object value = table.get(name);
1996             if (value != null) {
1997                 if (value instanceof String || value instanceof Number || value instanceof Boolean) {
1998                     result = value.toString();
1999                 }
2000                 else {
2001                     throw new MessageFormatException(name + " not a String type");
2002                 }
2003             }
2004         }
2005         return result;
2006     }
2007 
2008     private void prepareProperty(String name) throws JMSException {
2009         if (name == null || name.length() == 0) {
2010             throw new IllegalArgumentException("Invalid name: " + name);
2011         }
2012         if (this.readOnlyProperties) {
2013             throw new MessageNotWriteableException("Properties are read-only");
2014         }
2015         if (this.properties == null) {
2016             this.properties = new Hashtable();
2017         }
2018     }
2019 
2020     /***
2021      * @return Returns the entryBrokerName.
2022      */
2023     public String getEntryBrokerName() {
2024         return this.entryBrokerName;
2025     }
2026 
2027     /***
2028      * @param newEntryBrokerName The entryBrokerName to set.
2029      */
2030     public void setEntryBrokerName(String newEntryBrokerName) {
2031         this.entryBrokerName = newEntryBrokerName;
2032     }
2033 
2034     /***
2035      * @return Returns the entryClusterName.
2036      */
2037     public String getEntryClusterName() {
2038         return this.entryClusterName;
2039     }
2040 
2041     /***
2042      * @param newEntryClusterName The entryClusterName to set.
2043      */
2044     public void setEntryClusterName(String newEntryClusterName) {
2045         this.entryClusterName = newEntryClusterName;
2046     }
2047 
2048     /***
2049      * @return Returns the consumerNos.
2050      */
2051     public int[] getConsumerNos() {
2052         return this.consumerNos;
2053     }
2054 
2055     /***
2056      * @param newConsumerNos The consumerIDs to set.
2057      */
2058     public void setConsumerNos(int[] newConsumerNos) {
2059         this.consumerNos = newConsumerNos;
2060     }
2061 
2062     /***
2063      * @return Returns the jmsClientID.
2064      */
2065     public String getJMSClientID() {
2066         return this.jmsClientID;
2067     }
2068 
2069     /***
2070      * @param newJmsClientID The jmsClientID to set.
2071      */
2072     public void setJMSClientID(String newJmsClientID) {
2073         this.jmsClientID = newJmsClientID;
2074     }
2075 
2076     /***
2077      * @return Returns the producerID.
2078      */
2079     public String getProducerID() {
2080         return this.producerID;
2081     }
2082 
2083     /***
2084      * @param newProducerID The producerID to set.
2085      */
2086     public void setProducerID(String newProducerID) {
2087         this.producerID = newProducerID;
2088     }
2089 
2090 
2091     /***
2092      * @return Returns true if this message is part of a transaction
2093      */
2094 
2095     public boolean isPartOfTransaction() {
2096         return this.transactionId != null && this.transactionId.length() > 0;
2097     }
2098 
2099     /***
2100      * @return Returns the transactionId.
2101      */
2102     public String getTransactionId() {
2103         return this.transactionId;
2104     }
2105 
2106     /***
2107      * @param newTransactionId The transactionId to set.
2108      */
2109     public void setTransactionId(String newTransactionId) {
2110         this.transactionId = newTransactionId;
2111     }
2112 
2113     /***
2114      * @return Returns the consumerId.
2115      */
2116     public String getConsumerId() {
2117         return consumerId;
2118     }
2119 
2120     /***
2121      * @param consId The consumerId to set.
2122      */
2123     public void setConsumerId(String consId) {
2124         this.consumerId = consId;
2125     }
2126 
2127     /***
2128      * @return Returns the messageConsumed.
2129      */
2130     public boolean isMessageConsumed() {
2131         return messageConsumed;
2132     }
2133 
2134     /***
2135      * @param messageConsumed The messageConsumed to set.
2136      */
2137     public void setMessageConsumed(boolean messageConsumed) {
2138         this.messageConsumed = messageConsumed;
2139     }
2140 
2141 
2142     /***
2143      * Convert the message body to data
2144      *
2145      * @throws IOException
2146      */
2147     public final void convertBodyToBytes() throws IOException {
2148         if (bodyAsBytes == null) {
2149             ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
2150             DataOutputStream dataOut = new DataOutputStream(bytesOut);
2151             writeBody(dataOut);
2152             dataOut.flush();
2153             bodyAsBytes = bytesOut.toByteArray();
2154             dataOut.close();
2155         }
2156     }
2157 
2158     /***
2159      * Builds the message body from data
2160      *
2161      * @throws IOException
2162      */
2163     public final void buildBodyFromBytes() throws IOException {
2164         if (bodyAsBytes != null) {
2165             ByteArrayInputStream bytesIn = new ByteArrayInputStream(bodyAsBytes);
2166             DataInputStream dataIn = new DataInputStream(bytesIn);
2167             readBody(dataIn);
2168             dataIn.close();
2169         }
2170     }
2171 
2172     /***
2173      * Used serialize the message body to an output stream
2174      *
2175      * @param dataOut
2176      * @throws IOException
2177      */
2178 
2179     protected void writeBody(DataOutput dataOut) throws IOException {
2180 
2181     }
2182 
2183     /***
2184      * Used to help build the body from an input stream
2185      *
2186      * @param dataIn
2187      * @throws IOException
2188      */
2189 
2190     protected void readBody(DataInput dataIn) throws IOException {
2191 
2192     }
2193 
2194     /***
2195      * @return Returns the bodyAsBytes.
2196      * @throws IOException
2197      */
2198     public byte[] getBodyAsBytes() throws IOException {
2199         if (bodyAsBytes == null) {
2200             convertBodyToBytes();
2201         }
2202         return bodyAsBytes;
2203     }
2204 
2205     /***
2206      * @param bodyAsBytes The bodyAsBytes to set.
2207      */
2208     public void setBodyAsBytes(byte[] bodyAsBytes) {
2209         this.bodyAsBytes = bodyAsBytes;
2210     }
2211 
2212     /***
2213      * write map properties to an output stream
2214      *
2215      * @param table
2216      * @param dataOut
2217      * @throws IOException
2218      */
2219 
2220     protected void writeMapProperties(Hashtable table, DataOutput dataOut) throws IOException {
2221         if (table != null) {
2222             dataOut.writeShort(table.size());
2223             for (Enumeration iter = table.keys(); iter.hasMoreElements();) {
2224                 String key = iter.nextElement().toString();
2225                 dataOut.writeUTF(key);
2226                 Object value = table.get(key);
2227 
2228                 if (value instanceof byte[]) {
2229                     byte[] data = (byte[]) value;
2230                     dataOut.write(ActiveMQMessage.BYTES);
2231                     if (data != null) {
2232                         dataOut.writeInt(data.length);
2233                         dataOut.write(data);
2234                     }
2235                     else {
2236                         dataOut.writeInt(-1);
2237                     }
2238                 }
2239                 else if (value instanceof Byte) {
2240                     dataOut.write(ActiveMQMessage.BYTE);
2241                     Byte v = (Byte) value;
2242                     dataOut.writeByte(v.byteValue());
2243                 }
2244                 else if (value instanceof Boolean) {
2245                     dataOut.write(ActiveMQMessage.BOOLEAN);
2246                     Boolean v = (Boolean) value;
2247                     dataOut.writeBoolean(v.booleanValue());
2248                 }
2249                 else if (value instanceof String) {
2250                     dataOut.write(ActiveMQMessage.STRING);
2251                     dataOut.writeUTF(value.toString());
2252                 }
2253                 else if (value instanceof Character) {
2254                     dataOut.write(ActiveMQMessage.CHAR);
2255                     Character v = (Character) value;
2256                     dataOut.writeChar(v.charValue());
2257                 }
2258                 else if (value instanceof Number) {
2259                     Number v = (Number) value;
2260 
2261                     if (value instanceof Long) {
2262                         dataOut.write(ActiveMQMessage.LONG);
2263                         dataOut.writeLong(v.longValue());
2264                     }
2265                     else if (value instanceof Integer) {
2266                         dataOut.write(ActiveMQMessage.INT);
2267                         dataOut.writeInt(v.intValue());
2268                     }
2269                     else if (value instanceof Short) {
2270                         dataOut.write(ActiveMQMessage.SHORT);
2271                         dataOut.writeShort(v.shortValue());
2272                     }
2273                     else if (value instanceof Float) {
2274                         dataOut.write(ActiveMQMessage.FLOAT);
2275                         dataOut.writeFloat(v.floatValue());
2276                     }
2277                     else if (value instanceof Double) {
2278                         dataOut.write(ActiveMQMessage.DOUBLE);
2279                         dataOut.writeDouble(v.doubleValue());
2280                     }
2281                 }
2282                 else {
2283                     throw new RuntimeException("Do not know how to parse value of type: " + value.getClass());
2284                 }
2285 
2286             }
2287         }
2288         else {
2289             dataOut.writeShort(-1);
2290         }
2291     }
2292 
2293     /***
2294      * @param dataIn
2295      * @return
2296      * @throws IOException
2297      */
2298     protected Hashtable readMapProperties(DataInput dataIn) throws IOException {
2299         Hashtable result = null;
2300         int size = dataIn.readShort();
2301         if (size > -1) {
2302             result = new Hashtable();
2303             for (int i = 0; i < size; i++) {
2304                 String key = dataIn.readUTF();
2305                 Object value = null;
2306                 int type = dataIn.readByte();
2307                 if (type == ActiveMQMessage.BYTES) {
2308                     byte[] data = null;
2309                     int dataSize = dataIn.readInt();
2310                     if (dataSize > -1) {
2311                         data = new byte[dataSize];
2312                         dataIn.readFully(data);
2313                     }
2314                     value = data;
2315                 }
2316                 else if (type == ActiveMQMessage.BYTE) {
2317                     value = new Byte(dataIn.readByte());
2318                 }
2319                 else if (type == ActiveMQMessage.BOOLEAN) {
2320                     value = (dataIn.readBoolean()) ? Boolean.TRUE : Boolean.FALSE;
2321                 }
2322                 else if (type == ActiveMQMessage.STRING) {
2323                     value = dataIn.readUTF();
2324                 }
2325                 else if (type == ActiveMQMessage.CHAR) {
2326                     value = new Character(dataIn.readChar());
2327                 }
2328                 else if (type == ActiveMQMessage.LONG) {
2329                     value = new Long(dataIn.readLong());
2330                 }
2331                 else if (type == ActiveMQMessage.INT) {
2332                     value = new Integer(dataIn.readInt());
2333                 }
2334                 else if (type == ActiveMQMessage.SHORT) {
2335                     value = new Short(dataIn.readShort());
2336                 }
2337                 else if (type == ActiveMQMessage.FLOAT) {
2338                     value = new Float(dataIn.readFloat());
2339                 }
2340                 else if (type == ActiveMQMessage.DOUBLE) {
2341                     value = new Double(dataIn.readDouble());
2342                 }
2343                 else {
2344                     throw new RuntimeException("Do not know how to parse type: " + type);
2345                 }
2346                 result.put(key, value);
2347             }
2348         }
2349         return result;
2350     }
2351 
2352     /***
2353      * @return Returns the xaTransacted.
2354      */
2355     public boolean isXaTransacted() {
2356         return xaTransacted;
2357     }
2358 
2359     /***
2360      * @param xaTransacted The xaTransacted to set.
2361      */
2362     public void setXaTransacted(boolean xaTransacted) {
2363         this.xaTransacted = xaTransacted;
2364     }
2365 
2366     public ActiveMQDestination getJMSActiveMQDestination() {
2367         return jmsDestination;
2368     }
2369 
2370     /***
2371      * @return the message identity, which contains the String messageID
2372      *         and the lazily populated sequence number
2373      */
2374     public MessageIdentity getJMSMessageIdentity() {
2375         if (messageIdentity == null) {
2376             messageIdentity = new MessageIdentity(jmsMessageID);
2377         }
2378         return messageIdentity;
2379     }
2380 }