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 package org.codehaus.activemq.message;
19
20 import javax.jms.JMSException;
21 import javax.jms.MapMessage;
22 import javax.jms.MessageFormatException;
23 import javax.jms.MessageNotWriteableException;
24 import java.io.DataInput;
25 import java.io.DataOutput;
26 import java.io.IOException;
27 import java.util.Enumeration;
28 import java.util.Hashtable;
29
30 /***
31 * A <CODE>MapMessage</CODE> object is used to send a set of name-value pairs. The names are <CODE>String</CODE>
32 * objects, and the values are primitive data types in the Java programming language. The names must have a value that
33 * is not null, and not an empty string. The entries can be accessed sequentially or randomly by name. The order of the
34 * entries is undefined. <CODE>MapMessage</CODE> inherits from the <CODE>Message</CODE> interface and adds a
35 * message body that contains a Map.
36 * <P>
37 * The primitive types can be read or written explicitly using methods for each type. They may also be read or written
38 * generically as objects. For instance, a call to <CODE>MapMessage.setInt("foo", 6)</CODE> is equivalent to <CODE>
39 * MapMessage.setObject("foo", new Integer(6))</CODE>. Both forms are provided, because the explicit form is
40 * convenient for static programming, and the object form is needed when types are not known at compile time.
41 * <P>
42 * When a client receives a <CODE>MapMessage</CODE>, it is in read-only mode. If a client attempts to write to the
43 * message at this point, a <CODE>MessageNotWriteableException</CODE> is thrown. If <CODE>clearBody</CODE> is
44 * called, the message can now be both read from and written to.
45 * <P>
46 * <CODE>MapMessage</CODE> objects support the following conversion table. The marked cases must be supported. The
47 * unmarked cases must throw a <CODE>JMSException</CODE>. The <CODE>String</CODE> -to-primitive conversions may
48 * throw a runtime exception if the primitive's <CODE>valueOf()</CODE> method does not accept it as a valid <CODE>
49 * String</CODE> representation of the primitive.
50 * <P>
51 * A value written as the row type can be read as the column type.
52 * <p/>
53 * <PRE>| | boolean byte short char int long float double String byte[]
54 * |---------------------------------------------------------------------- |boolean | X X |byte | X X X X X |short | X
55 * X X X |char | X X |int | X X X |long | X X |float | X X X |double | X X |String | X X X X X X X X |byte[] | X
56 * |----------------------------------------------------------------------
57 * <p/>
58 * </PRE>
59 * <p/>
60 * <P>
61 * Attempting to read a null value as a primitive type must be treated as calling the primitive's corresponding <code>valueOf(String)</code>
62 * conversion method with a null value. Since <code>char</code> does not support a <code>String</code> conversion,
63 * attempting to read a null value as a <code>char</code> must throw a <code>NullPointerException</code>.
64 *
65 * @see javax.jms.Session#createMapMessage()
66 * @see javax.jms.BytesMessage
67 * @see javax.jms.Message
68 * @see javax.jms.ObjectMessage
69 * @see javax.jms.StreamMessage
70 * @see javax.jms.TextMessage
71 */
72 public class ActiveMQMapMessage extends ActiveMQMessage implements MapMessage {
73 private Hashtable theTable;
74
75 /***
76 * Return the type of Packet
77 *
78 * @return integer representation of the type of Packet
79 */
80 public int getPacketType() {
81 return ACTIVEMQ_MAP_MESSAGE;
82 }
83
84 /***
85 * @return Returns a shallow copy of the message instance
86 * @throws JMSException
87 */
88 public ActiveMQMessage shallowCopy() throws JMSException {
89 ActiveMQMapMessage other = new ActiveMQMapMessage();
90 this.initializeOther(other);
91 other.theTable = this.theTable;
92 return other;
93 }
94
95 /***
96 * @return Returns a deep copy of the message - note the header fields are only shallow copied
97 * @throws JMSException
98 */
99 public ActiveMQMessage deepCopy() throws JMSException {
100 ActiveMQMapMessage other = new ActiveMQMapMessage();
101 this.initializeOther(other);
102 if (this.theTable != null) {
103 other.theTable = (Hashtable) this.theTable.clone();
104 }
105 return other;
106 }
107
108 /***
109 * set the table body
110 *
111 * @param newTable the new data to set
112 */
113 public void setTable(Hashtable newTable) {
114 theTable = newTable;
115 }
116
117 /***
118 * @return Returns the table body
119 * @throws JMSException
120 */
121 public Hashtable getTable() throws JMSException {
122 if (theTable == null) {
123 try {
124 super.buildBodyFromBytes();
125 }
126 catch (IOException ioe) {
127 JMSException jmsEx = new JMSException("building table from data failed");
128 jmsEx.setLinkedException(ioe);
129 throw jmsEx;
130 }
131 }
132 if (theTable == null) {
133 theTable = new Hashtable();
134 }
135 return theTable;
136 }
137
138 /***
139 * Clears out the message body. Clearing a message's body does not clear its header values or property entries.
140 * <P>
141 * If this message body was read-only, calling this method leaves the message body in the same state as an empty
142 * body in a newly created message.
143 *
144 * @throws JMSException if the JMS provider fails to clear the message body due to some internal error.
145 */
146 public void clearBody() throws JMSException {
147 super.clearBody();
148 getTable().clear();
149 }
150
151 /***
152 * Returns the <CODE>boolean</CODE> value with the specified name.
153 *
154 * @param name the name of the <CODE>boolean</CODE>
155 * @return the <CODE>boolean</CODE> value with the specified name
156 * @throws JMSException if the JMS provider fails to read the message due to some internal error.
157 * @throws MessageFormatException if this type conversion is invalid.
158 */
159 public boolean getBoolean(String name) throws JMSException {
160 Object value = getTable().get(name);
161 if (value == null) {
162 return false;
163 }
164 if (value instanceof Boolean) {
165 return ((Boolean) value).booleanValue();
166 }
167 if (value instanceof String) {
168 return Boolean.valueOf(value.toString()).booleanValue();
169 }
170 else {
171 throw new MessageFormatException(" cannot read a boolean from " + value.getClass().getName());
172 }
173 }
174
175 /***
176 * Returns the <CODE>byte</CODE> value with the specified name.
177 *
178 * @param name the name of the <CODE>byte</CODE>
179 * @return the <CODE>byte</CODE> value with the specified name
180 * @throws JMSException if the JMS provider fails to read the message due to some internal error.
181 * @throws MessageFormatException if this type conversion is invalid.
182 */
183 public byte getByte(String name) throws JMSException {
184 Object value = getTable().get(name);
185 if (value == null) {
186 return 0;
187 }
188 if (value instanceof Byte) {
189 return ((Byte) value).byteValue();
190 }
191 if (value instanceof String) {
192 return Byte.valueOf(value.toString()).byteValue();
193 }
194 else {
195 throw new MessageFormatException(" cannot read a byte from " + value.getClass().getName());
196 }
197 }
198
199 /***
200 * Returns the <CODE>short</CODE> value with the specified name.
201 *
202 * @param name the name of the <CODE>short</CODE>
203 * @return the <CODE>short</CODE> value with the specified name
204 * @throws JMSException if the JMS provider fails to read the message due to some internal error.
205 * @throws MessageFormatException if this type conversion is invalid.
206 */
207 public short getShort(String name) throws JMSException {
208 Object value = getTable().get(name);
209 if (value == null) {
210 return 0;
211 }
212 if (value instanceof Short) {
213 return ((Short) value).shortValue();
214 }
215 if (value instanceof Byte) {
216 return ((Byte) value).shortValue();
217 }
218 if (value instanceof String) {
219 return Short.valueOf(value.toString()).shortValue();
220 }
221 else {
222 throw new MessageFormatException(" cannot read a short from " + value.getClass().getName());
223 }
224 }
225
226 /***
227 * Returns the Unicode character value with the specified name.
228 *
229 * @param name the name of the Unicode character
230 * @return the Unicode character value with the specified name
231 * @throws JMSException if the JMS provider fails to read the message due to some internal error.
232 * @throws MessageFormatException if this type conversion is invalid.
233 */
234 public char getChar(String name) throws JMSException {
235 Object value = getTable().get(name);
236 if (value == null) {
237 throw new NullPointerException();
238 }
239 if (value instanceof Character) {
240 return ((Character) value).charValue();
241 }
242 else {
243 throw new MessageFormatException(" cannot read a short from " + value.getClass().getName());
244 }
245 }
246
247 /***
248 * Returns the <CODE>int</CODE> value with the specified name.
249 *
250 * @param name the name of the <CODE>int</CODE>
251 * @return the <CODE>int</CODE> value with the specified name
252 * @throws JMSException if the JMS provider fails to read the message due to some internal error.
253 * @throws MessageFormatException if this type conversion is invalid.
254 */
255 public int getInt(String name) throws JMSException {
256 Object value = getTable().get(name);
257 if (value == null) {
258 return 0;
259 }
260 if (value instanceof Integer) {
261 return ((Integer) value).intValue();
262 }
263 if (value instanceof Short) {
264 return ((Short) value).intValue();
265 }
266 if (value instanceof Byte) {
267 return ((Byte) value).intValue();
268 }
269 if (value instanceof String) {
270 return Integer.valueOf(value.toString()).intValue();
271 }
272 else {
273 throw new MessageFormatException(" cannot read an int from " + value.getClass().getName());
274 }
275 }
276
277 /***
278 * Returns the <CODE>long</CODE> value with the specified name.
279 *
280 * @param name the name of the <CODE>long</CODE>
281 * @return the <CODE>long</CODE> value with the specified name
282 * @throws JMSException if the JMS provider fails to read the message due to some internal error.
283 * @throws MessageFormatException if this type conversion is invalid.
284 */
285 public long getLong(String name) throws JMSException {
286 Object value = getTable().get(name);
287 if (value == null) {
288 return 0;
289 }
290 if (value instanceof Long) {
291 return ((Long) value).longValue();
292 }
293 if (value instanceof Integer) {
294 return ((Integer) value).longValue();
295 }
296 if (value instanceof Short) {
297 return ((Short) value).longValue();
298 }
299 if (value instanceof Byte) {
300 return ((Byte) value).longValue();
301 }
302 if (value instanceof String) {
303 return Long.valueOf(value.toString()).longValue();
304 }
305 else {
306 throw new MessageFormatException(" cannot read a long from " + value.getClass().getName());
307 }
308 }
309
310 /***
311 * Returns the <CODE>float</CODE> value with the specified name.
312 *
313 * @param name the name of the <CODE>float</CODE>
314 * @return the <CODE>float</CODE> value with the specified name
315 * @throws JMSException if the JMS provider fails to read the message due to some internal error.
316 * @throws MessageFormatException if this type conversion is invalid.
317 */
318 public float getFloat(String name) throws JMSException {
319 Object value = getTable().get(name);
320 if (value == null) {
321 return 0;
322 }
323 if (value instanceof Float) {
324 return ((Float) value).floatValue();
325 }
326 if (value instanceof String) {
327 return Float.valueOf(value.toString()).floatValue();
328 }
329 else {
330 throw new MessageFormatException(" cannot read a float from " + value.getClass().getName());
331 }
332 }
333
334 /***
335 * Returns the <CODE>double</CODE> value with the specified name.
336 *
337 * @param name the name of the <CODE>double</CODE>
338 * @return the <CODE>double</CODE> value with the specified name
339 * @throws JMSException if the JMS provider fails to read the message due to some internal error.
340 * @throws MessageFormatException if this type conversion is invalid.
341 */
342 public double getDouble(String name) throws JMSException {
343 Object value = getTable().get(name);
344 if (value == null) {
345 return 0;
346 }
347 if (value instanceof Double) {
348 return ((Double) value).doubleValue();
349 }
350 if (value instanceof Float) {
351 return ((Float) value).floatValue();
352 }
353 if (value instanceof String) {
354 return Float.valueOf(value.toString()).floatValue();
355 }
356 else {
357 throw new MessageFormatException(" cannot read a double from " + value.getClass().getName());
358 }
359 }
360
361 /***
362 * Returns the <CODE>String</CODE> value with the specified name.
363 *
364 * @param name the name of the <CODE>String</CODE>
365 * @return the <CODE>String</CODE> value with the specified name; if there is no item by this name, a null value
366 * is returned
367 * @throws JMSException if the JMS provider fails to read the message due to some internal error.
368 * @throws MessageFormatException if this type conversion is invalid.
369 */
370 public String getString(String name) throws JMSException {
371 Object value = getTable().get(name);
372 if (value == null) {
373 return null;
374 }
375 if (value instanceof byte[]) {
376 throw new MessageFormatException("Use getBytes to read a byte array");
377 }
378 else {
379 return value.toString();
380 }
381 }
382
383 /***
384 * Returns the byte array value with the specified name.
385 *
386 * @param name the name of the byte array
387 * @return a copy of the byte array value with the specified name; if there is no item by this name, a null value
388 * is returned.
389 * @throws JMSException if the JMS provider fails to read the message due to some internal error.
390 * @throws MessageFormatException if this type conversion is invalid.
391 */
392 public byte[] getBytes(String name) throws JMSException {
393 Object value = getTable().get(name);
394 if (value instanceof byte[]) {
395 return (byte[]) value;
396 }
397 else {
398 throw new MessageFormatException(" cannot read a byte[] from " + value.getClass().getName());
399 }
400 }
401
402 /***
403 * Returns the value of the object with the specified name.
404 * <P>
405 * This method can be used to return, in objectified format, an object in the Java programming language ("Java
406 * object") that had been stored in the Map with the equivalent <CODE>setObject</CODE> method call, or its
407 * equivalent primitive <CODE>set <I>type </I></CODE> method.
408 * <P>
409 * Note that byte values are returned as <CODE>byte[]</CODE>, not <CODE>Byte[]</CODE>.
410 *
411 * @param name the name of the Java object
412 * @return a copy of the Java object value with the specified name, in objectified format (for example, if the
413 * object was set as an <CODE>int</CODE>, an <CODE>Integer</CODE> is returned); if there is no item by this
414 * name, a null value is returned
415 * @throws JMSException if the JMS provider fails to read the message due to some internal error.
416 */
417 public Object getObject(String name) throws JMSException {
418 return getTable().get(name);
419 }
420
421 /***
422 * Returns an <CODE>Enumeration</CODE> of all the names in the <CODE>MapMessage</CODE> object.
423 *
424 * @return an enumeration of all the names in this <CODE>MapMessage</CODE>
425 * @throws JMSException
426 */
427 public Enumeration getMapNames() throws JMSException {
428 return getTable().keys();
429 }
430
431 /***
432 * Sets a <CODE>boolean</CODE> value with the specified name into the Map.
433 *
434 * @param name the name of the <CODE>boolean</CODE>
435 * @param value the <CODE>boolean</CODE> value to set in the Map
436 * @throws JMSException if the JMS provider fails to write the message due to some internal error.
437 * @throws IllegalArgumentException if the name is null or if the name is an empty string.
438 * @throws MessageNotWriteableException if the message is in read-only mode.
439 */
440 public void setBoolean(String name, boolean value) throws JMSException {
441 initializeWriting();
442 getTable().put(name, (value) ? Boolean.TRUE : Boolean.FALSE);
443 }
444
445 /***
446 * Sets a <CODE>byte</CODE> value with the specified name into the Map.
447 *
448 * @param name the name of the <CODE>byte</CODE>
449 * @param value the <CODE>byte</CODE> value to set in the Map
450 * @throws JMSException if the JMS provider fails to write the message due to some internal error.
451 * @throws IllegalArgumentException if the name is null or if the name is an empty string.
452 * @throws MessageNotWriteableException if the message is in read-only mode.
453 */
454 public void setByte(String name, byte value) throws JMSException {
455 initializeWriting();
456 getTable().put(name, new Byte(value));
457 }
458
459 /***
460 * Sets a <CODE>short</CODE> value with the specified name into the Map.
461 *
462 * @param name the name of the <CODE>short</CODE>
463 * @param value the <CODE>short</CODE> value to set in the Map
464 * @throws JMSException if the JMS provider fails to write the message due to some internal error.
465 * @throws IllegalArgumentException if the name is null or if the name is an empty string.
466 * @throws MessageNotWriteableException if the message is in read-only mode.
467 */
468 public void setShort(String name, short value) throws JMSException {
469 initializeWriting();
470 getTable().put(name, new Short(value));
471 }
472
473 /***
474 * Sets a Unicode character value with the specified name into the Map.
475 *
476 * @param name the name of the Unicode character
477 * @param value the Unicode character value to set in the Map
478 * @throws JMSException if the JMS provider fails to write the message due to some internal error.
479 * @throws IllegalArgumentException if the name is null or if the name is an empty string.
480 * @throws MessageNotWriteableException if the message is in read-only mode.
481 */
482 public void setChar(String name, char value) throws JMSException {
483 initializeWriting();
484 getTable().put(name, new Character(value));
485 }
486
487 /***
488 * Sets an <CODE>int</CODE> value with the specified name into the Map.
489 *
490 * @param name the name of the <CODE>int</CODE>
491 * @param value the <CODE>int</CODE> value to set in the Map
492 * @throws JMSException if the JMS provider fails to write the message due to some internal error.
493 * @throws IllegalArgumentException if the name is null or if the name is an empty string.
494 * @throws MessageNotWriteableException if the message is in read-only mode.
495 */
496 public void setInt(String name, int value) throws JMSException {
497 initializeWriting();
498 getTable().put(name, new Integer(value));
499 }
500
501 /***
502 * Sets a <CODE>long</CODE> value with the specified name into the Map.
503 *
504 * @param name the name of the <CODE>long</CODE>
505 * @param value the <CODE>long</CODE> value to set in the Map
506 * @throws JMSException if the JMS provider fails to write the message due to some internal error.
507 * @throws IllegalArgumentException if the name is null or if the name is an empty string.
508 * @throws MessageNotWriteableException if the message is in read-only mode.
509 */
510 public void setLong(String name, long value) throws JMSException {
511 initializeWriting();
512 getTable().put(name, new Long(value));
513 }
514
515 /***
516 * Sets a <CODE>float</CODE> value with the specified name into the Map.
517 *
518 * @param name the name of the <CODE>float</CODE>
519 * @param value the <CODE>float</CODE> value to set in the Map
520 * @throws JMSException if the JMS provider fails to write the message due to some internal error.
521 * @throws IllegalArgumentException if the name is null or if the name is an empty string.
522 * @throws MessageNotWriteableException if the message is in read-only mode.
523 */
524 public void setFloat(String name, float value) throws JMSException {
525 initializeWriting();
526 getTable().put(name, new Float(value));
527 }
528
529 /***
530 * Sets a <CODE>double</CODE> value with the specified name into the Map.
531 *
532 * @param name the name of the <CODE>double</CODE>
533 * @param value the <CODE>double</CODE> value to set in the Map
534 * @throws JMSException if the JMS provider fails to write the message due to some internal error.
535 * @throws IllegalArgumentException if the name is null or if the name is an empty string.
536 * @throws MessageNotWriteableException if the message is in read-only mode.
537 */
538 public void setDouble(String name, double value) throws JMSException {
539 initializeWriting();
540 getTable().put(name, new Double(value));
541 }
542
543 /***
544 * Sets a <CODE>String</CODE> value with the specified name into the Map.
545 *
546 * @param name the name of the <CODE>String</CODE>
547 * @param value the <CODE>String</CODE> value to set in the Map
548 * @throws JMSException if the JMS provider fails to write the message due to some internal error.
549 * @throws IllegalArgumentException if the name is null or if the name is an empty string.
550 * @throws MessageNotWriteableException if the message is in read-only mode.
551 */
552 public void setString(String name, String value) throws JMSException {
553 initializeWriting();
554 getTable().put(name, value);
555 }
556
557 /***
558 * Sets a byte array value with the specified name into the Map.
559 *
560 * @param name the name of the byte array
561 * @param value the byte array value to set in the Map; the array is copied so that the value for <CODE>name
562 * </CODE> will not be altered by future modifications
563 * @throws JMSException if the JMS provider fails to write the message due to some internal error.
564 * @throws NullPointerException if the name is null, or if the name is an empty string.
565 * @throws MessageNotWriteableException if the message is in read-only mode.
566 */
567 public void setBytes(String name, byte[] value) throws JMSException {
568 initializeWriting();
569 getTable().put(name, value);
570 }
571
572 /***
573 * Sets a portion of the byte array value with the specified name into the Map.
574 *
575 * @param name the name of the byte array
576 * @param value the byte array value to set in the Map
577 * @param offset the initial offset within the byte array
578 * @param length the number of bytes to use
579 * @throws JMSException if the JMS provider fails to write the message due to some internal error.
580 * @throws IllegalArgumentException if the name is null or if the name is an empty string.
581 * @throws MessageNotWriteableException if the message is in read-only mode.
582 */
583 public void setBytes(String name, byte[] value, int offset, int length) throws JMSException {
584 initializeWriting();
585 byte[] data = new byte[length];
586 System.arraycopy(value, offset, data, 0, length);
587 getTable().put(name, data);
588 }
589
590 /***
591 * Sets an object value with the specified name into the Map.
592 * <P>
593 * This method works only for the objectified primitive object types (<code>Integer</code>,<code>Double</code>,
594 * <code>Long</code> ...), <code>String</code> objects, and byte arrays.
595 *
596 * @param name the name of the Java object
597 * @param value the Java object value to set in the Map
598 * @throws JMSException if the JMS provider fails to write the message due to some internal error.
599 * @throws IllegalArgumentException if the name is null or if the name is an empty string.
600 * @throws MessageFormatException if the object is invalid.
601 * @throws MessageNotWriteableException if the message is in read-only mode.
602 */
603 public void setObject(String name, Object value) throws JMSException {
604 initializeWriting();
605 if (value instanceof Number || value instanceof String || value instanceof Boolean || value instanceof Byte
606 || value instanceof Character || value instanceof byte[]) {
607 getTable().put(name, value);
608 }
609 else {
610 throw new MessageFormatException(value.getClass() + " is not a primitive type");
611 }
612 }
613
614 protected void writeBody(DataOutput dataOut) throws IOException {
615 super.writeMapProperties(theTable, dataOut);
616 theTable = null;
617 }
618
619 protected void readBody(DataInput dataIn) throws IOException {
620 theTable = super.readMapProperties(dataIn);
621 }
622
623 /***
624 * Indicates whether an item exists in this <CODE>MapMessage</CODE> object.
625 *
626 * @param name the name of the item to test
627 * @return true if the item exists
628 * @throws JMSException if the JMS provider fails to determine if the item exists due to some internal error.
629 */
630 public boolean itemExists(String name) throws JMSException {
631 return getTable().containsKey(name);
632 }
633
634 private void initializeWriting() throws MessageNotWriteableException {
635 if (super.readOnlyMessage) {
636 throw new MessageNotWriteableException("This message is in read-only mode");
637 }
638 }
639 }