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