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 }