001 /**
002 *
003 * Copyright 2004 Protique Ltd
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 * http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 *
017 **/
018 package org.activemq.message;
019
020 import java.io.DataInput;
021 import java.io.DataOutput;
022 import java.io.IOException;
023 import java.util.Collections;
024 import java.util.Enumeration;
025 import java.util.HashMap;
026 import java.util.Map;
027
028 import javax.jms.JMSException;
029 import javax.jms.MapMessage;
030 import javax.jms.MessageFormatException;
031 import javax.jms.MessageNotWriteableException;
032
033 /**
034 * A <CODE>MapMessage</CODE> object is used to send a set of name-value pairs. The names are <CODE>String</CODE>
035 * objects, and the values are primitive data types in the Java programming language. The names must have a value that
036 * is not null, and not an empty string. The entries can be accessed sequentially or randomly by name. The order of the
037 * entries is undefined. <CODE>MapMessage</CODE> inherits from the <CODE>Message</CODE> interface and adds a
038 * message body that contains a Map.
039 * <P>
040 * The primitive types can be read or written explicitly using methods for each type. They may also be read or written
041 * generically as objects. For instance, a call to <CODE>MapMessage.setInt("foo", 6)</CODE> is equivalent to <CODE>
042 * MapMessage.setObject("foo", new Integer(6))</CODE>. Both forms are provided, because the explicit form is
043 * convenient for static programming, and the object form is needed when types are not known at compile time.
044 * <P>
045 * When a client receives a <CODE>MapMessage</CODE>, it is in read-only mode. If a client attempts to write to the
046 * message at this point, a <CODE>MessageNotWriteableException</CODE> is thrown. If <CODE>clearBody</CODE> is
047 * called, the message can now be both read from and written to.
048 * <P>
049 * <CODE>MapMessage</CODE> objects support the following conversion table. The marked cases must be supported. The
050 * unmarked cases must throw a <CODE>JMSException</CODE>. The <CODE>String</CODE> -to-primitive conversions may
051 * throw a runtime exception if the primitive's <CODE>valueOf()</CODE> method does not accept it as a valid <CODE>
052 * String</CODE> representation of the primitive.
053 * <P>
054 * A value written as the row type can be read as the column type.
055 * <p/>
056 * <PRE>| | boolean byte short char int long float double String byte[]
057 * |---------------------------------------------------------------------- |boolean | X X |byte | X X X X X |short | X
058 * 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
059 * |----------------------------------------------------------------------
060 * <p/>
061 * </PRE>
062 * <p/>
063 * <P>
064 * Attempting to read a null value as a primitive type must be treated as calling the primitive's corresponding <code>valueOf(String)</code>
065 * conversion method with a null value. Since <code>char</code> does not support a <code>String</code> conversion,
066 * attempting to read a null value as a <code>char</code> must throw a <code>NullPointerException</code>.
067 *
068 * @see javax.jms.Session#createMapMessage()
069 * @see javax.jms.BytesMessage
070 * @see javax.jms.Message
071 * @see javax.jms.ObjectMessage
072 * @see javax.jms.StreamMessage
073 * @see javax.jms.TextMessage
074 */
075 public class ActiveMQMapMessage extends ActiveMQMessage implements MapMessage {
076 private Map theTable;
077
078 /**
079 * Return the type of Packet
080 *
081 * @return integer representation of the type of Packet
082 */
083 public int getPacketType() {
084 return ACTIVEMQ_MAP_MESSAGE;
085 }
086
087 /**
088 * @return Returns a shallow copy of the message instance
089 * @throws JMSException
090 */
091 public ActiveMQMessage shallowCopy() throws JMSException {
092 ActiveMQMapMessage other = new ActiveMQMapMessage();
093 this.initializeOther(other);
094 other.theTable = this.theTable;
095 return other;
096 }
097
098 /**
099 * @return Returns a deep copy of the message - note the header fields are only shallow copied
100 * @throws JMSException
101 */
102 public ActiveMQMessage deepCopy() throws JMSException {
103 ActiveMQMapMessage other = new ActiveMQMapMessage();
104 this.initializeOther(other);
105 if (this.theTable != null) {
106 other.theTable = new HashMap(theTable);
107 }
108 return other;
109 }
110
111 /**
112 * set the table body
113 *
114 * @param newTable the new data to set
115 */
116 public void setTable(Map newTable) {
117 theTable = newTable;
118 }
119
120 /**
121 * @Transient
122 * @return Returns the table body
123 * @throws JMSException
124 */
125 public Map getTable() throws JMSException {
126 if (theTable == null) {
127 try {
128 super.buildBodyFromBytes();
129 }
130 catch (IOException ioe) {
131 JMSException jmsEx = new JMSException("building table from data failed");
132 jmsEx.setLinkedException(ioe);
133 throw jmsEx;
134 }
135 }
136 if (theTable == null) {
137 theTable = new HashMap();
138 }
139 return theTable;
140 }
141
142 /**
143 * Clears out the message body. Clearing a message's body does not clear its header values or property entries.
144 * <P>
145 * If this message body was read-only, calling this method leaves the message body in the same state as an empty
146 * body in a newly created message.
147 *
148 * @throws JMSException if the JMS provider fails to clear the message body due to some internal error.
149 */
150 public void clearBody() throws JMSException {
151 super.clearBody();
152 getTable().clear();
153 }
154
155 /**
156 * Returns the <CODE>boolean</CODE> value with the specified name.
157 *
158 * @param name the name of the <CODE>boolean</CODE>
159 * @return the <CODE>boolean</CODE> value with the specified name
160 * @throws JMSException if the JMS provider fails to read the message due to some internal error.
161 * @throws MessageFormatException if this type conversion is invalid.
162 */
163 public boolean getBoolean(String name) throws JMSException {
164 Object value = getTable().get(name);
165 if (value == null) {
166 return false;
167 }
168 if (value instanceof Boolean) {
169 return ((Boolean) value).booleanValue();
170 }
171 if (value instanceof String) {
172 return Boolean.valueOf(value.toString()).booleanValue();
173 }
174 else {
175 throw new MessageFormatException(" cannot read a boolean from " + value.getClass().getName());
176 }
177 }
178
179 /**
180 * Returns the <CODE>byte</CODE> value with the specified name.
181 *
182 * @param name the name of the <CODE>byte</CODE>
183 * @return the <CODE>byte</CODE> value with the specified name
184 * @throws JMSException if the JMS provider fails to read the message due to some internal error.
185 * @throws MessageFormatException if this type conversion is invalid.
186 */
187 public byte getByte(String name) throws JMSException {
188 Object value = getTable().get(name);
189 if (value == null) {
190 return 0;
191 }
192 if (value instanceof Byte) {
193 return ((Byte) value).byteValue();
194 }
195 if (value instanceof String) {
196 return Byte.valueOf(value.toString()).byteValue();
197 }
198 else {
199 throw new MessageFormatException(" cannot read a byte from " + value.getClass().getName());
200 }
201 }
202
203 /**
204 * Returns the <CODE>short</CODE> value with the specified name.
205 *
206 * @param name the name of the <CODE>short</CODE>
207 * @return the <CODE>short</CODE> value with the specified name
208 * @throws JMSException if the JMS provider fails to read the message due to some internal error.
209 * @throws MessageFormatException if this type conversion is invalid.
210 */
211 public short getShort(String name) throws JMSException {
212 Object value = getTable().get(name);
213 if (value == null) {
214 return 0;
215 }
216 if (value instanceof Short) {
217 return ((Short) value).shortValue();
218 }
219 if (value instanceof Byte) {
220 return ((Byte) value).shortValue();
221 }
222 if (value instanceof String) {
223 return Short.valueOf(value.toString()).shortValue();
224 }
225 else {
226 throw new MessageFormatException(" cannot read a short from " + value.getClass().getName());
227 }
228 }
229
230 /**
231 * Returns the Unicode character value with the specified name.
232 *
233 * @param name the name of the Unicode character
234 * @return the Unicode character value with the specified name
235 * @throws JMSException if the JMS provider fails to read the message due to some internal error.
236 * @throws MessageFormatException if this type conversion is invalid.
237 */
238 public char getChar(String name) throws JMSException {
239 Object value = getTable().get(name);
240 if (value == null) {
241 throw new NullPointerException();
242 }
243 if (value instanceof Character) {
244 return ((Character) value).charValue();
245 }
246 else {
247 throw new MessageFormatException(" cannot read a short from " + value.getClass().getName());
248 }
249 }
250
251 /**
252 * Returns the <CODE>int</CODE> value with the specified name.
253 *
254 * @param name the name of the <CODE>int</CODE>
255 * @return the <CODE>int</CODE> value with the specified name
256 * @throws JMSException if the JMS provider fails to read the message due to some internal error.
257 * @throws MessageFormatException if this type conversion is invalid.
258 */
259 public int getInt(String name) throws JMSException {
260 Object value = getTable().get(name);
261 if (value == null) {
262 return 0;
263 }
264 if (value instanceof Integer) {
265 return ((Integer) value).intValue();
266 }
267 if (value instanceof Short) {
268 return ((Short) value).intValue();
269 }
270 if (value instanceof Byte) {
271 return ((Byte) value).intValue();
272 }
273 if (value instanceof String) {
274 return Integer.valueOf(value.toString()).intValue();
275 }
276 else {
277 throw new MessageFormatException(" cannot read an int from " + value.getClass().getName());
278 }
279 }
280
281 /**
282 * Returns the <CODE>long</CODE> value with the specified name.
283 *
284 * @param name the name of the <CODE>long</CODE>
285 * @return the <CODE>long</CODE> value with the specified name
286 * @throws JMSException if the JMS provider fails to read the message due to some internal error.
287 * @throws MessageFormatException if this type conversion is invalid.
288 */
289 public long getLong(String name) throws JMSException {
290 Object value = getTable().get(name);
291 if (value == null) {
292 return 0;
293 }
294 if (value instanceof Long) {
295 return ((Long) value).longValue();
296 }
297 if (value instanceof Integer) {
298 return ((Integer) value).longValue();
299 }
300 if (value instanceof Short) {
301 return ((Short) value).longValue();
302 }
303 if (value instanceof Byte) {
304 return ((Byte) value).longValue();
305 }
306 if (value instanceof String) {
307 return Long.valueOf(value.toString()).longValue();
308 }
309 else {
310 throw new MessageFormatException(" cannot read a long from " + value.getClass().getName());
311 }
312 }
313
314 /**
315 * Returns the <CODE>float</CODE> value with the specified name.
316 *
317 * @param name the name of the <CODE>float</CODE>
318 * @return the <CODE>float</CODE> value with the specified name
319 * @throws JMSException if the JMS provider fails to read the message due to some internal error.
320 * @throws MessageFormatException if this type conversion is invalid.
321 */
322 public float getFloat(String name) throws JMSException {
323 Object value = getTable().get(name);
324 if (value == null) {
325 return 0;
326 }
327 if (value instanceof Float) {
328 return ((Float) value).floatValue();
329 }
330 if (value instanceof String) {
331 return Float.valueOf(value.toString()).floatValue();
332 }
333 else {
334 throw new MessageFormatException(" cannot read a float from " + value.getClass().getName());
335 }
336 }
337
338 /**
339 * Returns the <CODE>double</CODE> value with the specified name.
340 *
341 * @param name the name of the <CODE>double</CODE>
342 * @return the <CODE>double</CODE> value with the specified name
343 * @throws JMSException if the JMS provider fails to read the message due to some internal error.
344 * @throws MessageFormatException if this type conversion is invalid.
345 */
346 public double getDouble(String name) throws JMSException {
347 Object value = getTable().get(name);
348 if (value == null) {
349 return 0;
350 }
351 if (value instanceof Double) {
352 return ((Double) value).doubleValue();
353 }
354 if (value instanceof Float) {
355 return ((Float) value).floatValue();
356 }
357 if (value instanceof String) {
358 return Float.valueOf(value.toString()).floatValue();
359 }
360 else {
361 throw new MessageFormatException(" cannot read a double from " + value.getClass().getName());
362 }
363 }
364
365 /**
366 * Returns the <CODE>String</CODE> value with the specified name.
367 *
368 * @param name the name of the <CODE>String</CODE>
369 * @return the <CODE>String</CODE> value with the specified name; if there is no item by this name, a null value
370 * is returned
371 * @throws JMSException if the JMS provider fails to read the message due to some internal error.
372 * @throws MessageFormatException if this type conversion is invalid.
373 */
374 public String getString(String name) throws JMSException {
375 Object value = getTable().get(name);
376 if (value == null) {
377 return null;
378 }
379 if (value instanceof byte[]) {
380 throw new MessageFormatException("Use getBytes to read a byte array");
381 }
382 else {
383 return value.toString();
384 }
385 }
386
387 /**
388 * Returns the byte array value with the specified name.
389 *
390 * @param name the name of the byte array
391 * @return a copy of the byte array value with the specified name; if there is no item by this name, a null value
392 * is returned.
393 * @throws JMSException if the JMS provider fails to read the message due to some internal error.
394 * @throws MessageFormatException if this type conversion is invalid.
395 */
396 public byte[] getBytes(String name) throws JMSException {
397 Object value = getTable().get(name);
398 if (value instanceof byte[]) { //this will be true if the value is null
399 return (byte[]) value;
400 }
401 else {
402 throw new MessageFormatException(" cannot read a byte[] from " + value.getClass().getName());
403 }
404 }
405
406 /**
407 * Returns the value of the object with the specified name.
408 * <P>
409 * This method can be used to return, in objectified format, an object in the Java programming language ("Java
410 * object") that had been stored in the Map with the equivalent <CODE>setObject</CODE> method call, or its
411 * equivalent primitive <CODE>set <I>type </I></CODE> method.
412 * <P>
413 * Note that byte values are returned as <CODE>byte[]</CODE>, not <CODE>Byte[]</CODE>.
414 *
415 * @param name the name of the Java object
416 * @return a copy of the Java object value with the specified name, in objectified format (for example, if the
417 * object was set as an <CODE>int</CODE>, an <CODE>Integer</CODE> is returned); if there is no item by this
418 * name, a null value is returned
419 * @throws JMSException if the JMS provider fails to read the message due to some internal error.
420 */
421 public Object getObject(String name) throws JMSException {
422 return getTable().get(name);
423 }
424
425 /**
426 * Returns an <CODE>Enumeration</CODE> of all the names in the <CODE>MapMessage</CODE> object.
427 *
428 * @return an enumeration of all the names in this <CODE>MapMessage</CODE>
429 * @throws JMSException
430 */
431 public Enumeration getMapNames() throws JMSException {
432 return Collections.enumeration(getTable().keySet());
433 }
434
435 protected void put(String name, Object value) throws JMSException {
436 if( name==null )
437 throw new IllegalArgumentException("The name of the property cannot be null.");
438 if( name.length()==0 )
439 throw new IllegalArgumentException("The name of the property cannot be an emprty string.");
440 getTable().put(name, value);
441 }
442
443 /**
444 * Sets a <CODE>boolean</CODE> value with the specified name into the Map.
445 *
446 * @param name the name of the <CODE>boolean</CODE>
447 * @param value the <CODE>boolean</CODE> value to set in the Map
448 * @throws JMSException if the JMS provider fails to write the message due to some internal error.
449 * @throws IllegalArgumentException if the name is null or if the name is an empty string.
450 * @throws MessageNotWriteableException if the message is in read-only mode.
451 */
452 public void setBoolean(String name, boolean value) throws JMSException {
453 initializeWriting();
454 put(name, (value) ? Boolean.TRUE : Boolean.FALSE);
455 }
456
457 /**
458 * Sets a <CODE>byte</CODE> value with the specified name into the Map.
459 *
460 * @param name the name of the <CODE>byte</CODE>
461 * @param value the <CODE>byte</CODE> value to set in the Map
462 * @throws JMSException if the JMS provider fails to write the message due to some internal error.
463 * @throws IllegalArgumentException if the name is null or if the name is an empty string.
464 * @throws MessageNotWriteableException if the message is in read-only mode.
465 */
466 public void setByte(String name, byte value) throws JMSException {
467 initializeWriting();
468 put(name, new Byte(value));
469 }
470
471 /**
472 * Sets a <CODE>short</CODE> value with the specified name into the Map.
473 *
474 * @param name the name of the <CODE>short</CODE>
475 * @param value the <CODE>short</CODE> value to set in the Map
476 * @throws JMSException if the JMS provider fails to write the message due to some internal error.
477 * @throws IllegalArgumentException if the name is null or if the name is an empty string.
478 * @throws MessageNotWriteableException if the message is in read-only mode.
479 */
480 public void setShort(String name, short value) throws JMSException {
481 initializeWriting();
482 put(name, new Short(value));
483 }
484
485 /**
486 * Sets a Unicode character value with the specified name into the Map.
487 *
488 * @param name the name of the Unicode character
489 * @param value the Unicode character value to set in the Map
490 * @throws JMSException if the JMS provider fails to write the message due to some internal error.
491 * @throws IllegalArgumentException if the name is null or if the name is an empty string.
492 * @throws MessageNotWriteableException if the message is in read-only mode.
493 */
494 public void setChar(String name, char value) throws JMSException {
495 initializeWriting();
496 put(name, new Character(value));
497 }
498
499 /**
500 * Sets an <CODE>int</CODE> value with the specified name into the Map.
501 *
502 * @param name the name of the <CODE>int</CODE>
503 * @param value the <CODE>int</CODE> value to set in the Map
504 * @throws JMSException if the JMS provider fails to write the message due to some internal error.
505 * @throws IllegalArgumentException if the name is null or if the name is an empty string.
506 * @throws MessageNotWriteableException if the message is in read-only mode.
507 */
508 public void setInt(String name, int value) throws JMSException {
509 initializeWriting();
510 put(name, new Integer(value));
511 }
512
513 /**
514 * Sets a <CODE>long</CODE> value with the specified name into the Map.
515 *
516 * @param name the name of the <CODE>long</CODE>
517 * @param value the <CODE>long</CODE> value to set in the Map
518 * @throws JMSException if the JMS provider fails to write the message due to some internal error.
519 * @throws IllegalArgumentException if the name is null or if the name is an empty string.
520 * @throws MessageNotWriteableException if the message is in read-only mode.
521 */
522 public void setLong(String name, long value) throws JMSException {
523 initializeWriting();
524 put(name, new Long(value));
525 }
526
527 /**
528 * Sets a <CODE>float</CODE> value with the specified name into the Map.
529 *
530 * @param name the name of the <CODE>float</CODE>
531 * @param value the <CODE>float</CODE> value to set in the Map
532 * @throws JMSException if the JMS provider fails to write the message due to some internal error.
533 * @throws IllegalArgumentException if the name is null or if the name is an empty string.
534 * @throws MessageNotWriteableException if the message is in read-only mode.
535 */
536 public void setFloat(String name, float value) throws JMSException {
537 initializeWriting();
538 put(name, new Float(value));
539 }
540
541 /**
542 * Sets a <CODE>double</CODE> value with the specified name into the Map.
543 *
544 * @param name the name of the <CODE>double</CODE>
545 * @param value the <CODE>double</CODE> value to set in the Map
546 * @throws JMSException if the JMS provider fails to write the message due to some internal error.
547 * @throws IllegalArgumentException if the name is null or if the name is an empty string.
548 * @throws MessageNotWriteableException if the message is in read-only mode.
549 */
550 public void setDouble(String name, double value) throws JMSException {
551 initializeWriting();
552 put(name, new Double(value));
553 }
554
555 /**
556 * Sets a <CODE>String</CODE> value with the specified name into the Map.
557 *
558 * @param name the name of the <CODE>String</CODE>
559 * @param value the <CODE>String</CODE> value to set in the Map
560 * @throws JMSException if the JMS provider fails to write the message due to some internal error.
561 * @throws IllegalArgumentException if the name is null or if the name is an empty string.
562 * @throws MessageNotWriteableException if the message is in read-only mode.
563 */
564 public void setString(String name, String value) throws JMSException {
565 initializeWriting();
566 put(name, value);
567 }
568
569 /**
570 * Sets a byte array value with the specified name into the Map.
571 *
572 * @param name the name of the byte array
573 * @param value the byte array value to set in the Map; the array is copied so that the value for <CODE>name
574 * </CODE> will not be altered by future modifications
575 * @throws JMSException if the JMS provider fails to write the message due to some internal error.
576 * @throws NullPointerException if the name is null, or if the name is an empty string.
577 * @throws MessageNotWriteableException if the message is in read-only mode.
578 */
579 public void setBytes(String name, byte[] value) throws JMSException {
580 initializeWriting();
581 if( value != null ) {
582 put(name, value);
583 } else {
584 getTable().remove(name);
585 }
586 }
587
588 /**
589 * Sets a portion of the byte array value with the specified name into the Map.
590 *
591 * @param name the name of the byte array
592 * @param value the byte array value to set in the Map
593 * @param offset the initial offset within the byte array
594 * @param length the number of bytes to use
595 * @throws JMSException if the JMS provider fails to write the message due to some internal error.
596 * @throws IllegalArgumentException if the name is null or if the name is an empty string.
597 * @throws MessageNotWriteableException if the message is in read-only mode.
598 */
599 public void setBytes(String name, byte[] value, int offset, int length) throws JMSException {
600 initializeWriting();
601 byte[] data = new byte[length];
602 System.arraycopy(value, offset, data, 0, length);
603 put(name, data);
604 }
605
606 /**
607 * Sets an object value with the specified name into the Map.
608 * <P>
609 * This method works only for the objectified primitive object types (<code>Integer</code>,<code>Double</code>,
610 * <code>Long</code> ...), <code>String</code> objects, and byte arrays.
611 *
612 * @param name the name of the Java object
613 * @param value the Java object value to set in the Map
614 * @throws JMSException if the JMS provider fails to write the message due to some internal error.
615 * @throws IllegalArgumentException if the name is null or if the name is an empty string.
616 * @throws MessageFormatException if the object is invalid.
617 * @throws MessageNotWriteableException if the message is in read-only mode.
618 */
619 public void setObject(String name, Object value) throws JMSException {
620 initializeWriting();
621 if( value != null ) {
622 if (value instanceof Number || value instanceof String || value instanceof Boolean || value instanceof Byte
623 || value instanceof Character || value instanceof byte[]) {
624 put(name, value);
625 }
626 else {
627 throw new MessageFormatException(value.getClass() + " is not a primitive type");
628 }
629 } else {
630 put(name, null);
631 }
632 }
633
634 public void writeBody(DataOutput dataOut) throws IOException {
635 super.writeMapProperties(theTable, dataOut);
636 theTable = null;
637 }
638
639 public void readBody(DataInput dataIn) throws IOException {
640 theTable = super.readMapProperties(dataIn);
641 }
642
643 /**
644 * Indicates whether an item exists in this <CODE>MapMessage</CODE> object.
645 *
646 * @param name the name of the item to test
647 * @return true if the item exists
648 * @throws JMSException if the JMS provider fails to determine if the item exists due to some internal error.
649 */
650 public boolean itemExists(String name) throws JMSException {
651 return getTable().containsKey(name);
652 }
653
654 private void initializeWriting() throws MessageNotWriteableException {
655 if (super.readOnlyMessage) {
656 throw new MessageNotWriteableException("This message is in read-only mode");
657 }
658 }
659
660 public String toString() {
661 return super.toString() + " ActiveMQMapMessage{ " +
662 "theTable = " + theTable +
663 " }";
664 }
665 }