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