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> &nbsp;...), <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    }