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