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  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[]) { //this will be true if the value is null
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> &nbsp;...), <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 }