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        if (value == null) {
316            throw new NullPointerException();
317        }
318        if (value instanceof Character) {
319            return ((Character)value).charValue();
320        } else {
321            throw new MessageFormatException(" cannot read a short 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        if (value == null) {
441            return 0;
442        }
443        if (value instanceof Double) {
444            return ((Double)value).doubleValue();
445        }
446        if (value instanceof Float) {
447            return ((Float)value).floatValue();
448        }
449        if (value instanceof UTF8Buffer) {
450            return Float.valueOf(value.toString()).floatValue();
451        }
452        if (value instanceof String) {
453            return Float.valueOf(value.toString()).floatValue();
454        } else {
455            throw new MessageFormatException(" cannot read a double from " + value.getClass().getName());
456        }
457    }
458
459    /**
460     * Returns the <CODE>String</CODE> value with the specified name.
461     *
462     * @param name the name of the <CODE>String</CODE>
463     * @return the <CODE>String</CODE> value with the specified name; if there
464     *         is no item by this name, a null value is returned
465     * @throws JMSException if the JMS provider fails to read the message due to
466     *                 some internal error.
467     * @throws MessageFormatException if this type conversion is invalid.
468     */
469    @Override
470    public String getString(String name) throws JMSException {
471        initializeReading();
472        Object value = map.get(name);
473        if (value == null) {
474            return null;
475        }
476        if (value instanceof byte[]) {
477            throw new MessageFormatException("Use getBytes to read a byte array");
478        } else {
479            return value.toString();
480        }
481    }
482
483    /**
484     * Returns the byte array value with the specified name.
485     *
486     * @param name the name of the byte array
487     * @return a copy of the byte array value with the specified name; if there
488     *         is no item by this name, a null value is returned.
489     * @throws JMSException if the JMS provider fails to read the message due to
490     *                 some internal error.
491     * @throws MessageFormatException if this type conversion is invalid.
492     */
493    @Override
494    public byte[] getBytes(String name) throws JMSException {
495        initializeReading();
496        Object value = map.get(name);
497        if (value instanceof byte[]) {
498            return (byte[])value;
499        } else {
500            throw new MessageFormatException(" cannot read a byte[] from " + value.getClass().getName());
501        }
502    }
503
504    /**
505     * Returns the value of the object with the specified name.
506     * <P>
507     * This method can be used to return, in objectified format, an object in
508     * the Java programming language ("Java object") that had been stored in the
509     * Map with the equivalent <CODE>setObject</CODE> method call, or its
510     * equivalent primitive <CODE>set <I>type </I></CODE> method.
511     * <P>
512     * Note that byte values are returned as <CODE>byte[]</CODE>, not
513     * <CODE>Byte[]</CODE>.
514     *
515     * @param name the name of the Java object
516     * @return a copy of the Java object value with the specified name, in
517     *         objectified format (for example, if the object was set as an
518     *         <CODE>int</CODE>, an <CODE>Integer</CODE> is returned); if
519     *         there is no item by this name, a null value is returned
520     * @throws JMSException if the JMS provider fails to read the message due to
521     *                 some internal error.
522     */
523    @Override
524    public Object getObject(String name) throws JMSException {
525        initializeReading();
526        Object result = map.get(name);
527        if (result instanceof UTF8Buffer) {
528            result = result.toString();
529        }
530
531        return result;
532    }
533
534    /**
535     * Returns an <CODE>Enumeration</CODE> of all the names in the
536     * <CODE>MapMessage</CODE> object.
537     *
538     * @return an enumeration of all the names in this <CODE>MapMessage</CODE>
539     * @throws JMSException
540     */
541    @Override
542    public Enumeration<String> getMapNames() throws JMSException {
543        initializeReading();
544        return Collections.enumeration(map.keySet());
545    }
546
547    protected void put(String name, Object value) throws JMSException {
548        if (name == null) {
549            throw new IllegalArgumentException("The name of the property cannot be null.");
550        }
551        if (name.length() == 0) {
552            throw new IllegalArgumentException("The name of the property cannot be an emprty string.");
553        }
554        map.put(name, value);
555    }
556
557    /**
558     * Sets a <CODE>boolean</CODE> value with the specified name into the Map.
559     *
560     * @param name the name of the <CODE>boolean</CODE>
561     * @param value the <CODE>boolean</CODE> value to set in the Map
562     * @throws JMSException if the JMS provider fails to write the message due
563     *                 to some internal error.
564     * @throws IllegalArgumentException if the name is null or if the name is an
565     *                 empty string.
566     * @throws MessageNotWriteableException if the message is in read-only mode.
567     */
568    @Override
569    public void setBoolean(String name, boolean value) throws JMSException {
570        initializeWriting();
571        put(name, value ? Boolean.TRUE : Boolean.FALSE);
572    }
573
574    /**
575     * Sets a <CODE>byte</CODE> value with the specified name into the Map.
576     *
577     * @param name the name of the <CODE>byte</CODE>
578     * @param value the <CODE>byte</CODE> value to set in the Map
579     * @throws JMSException if the JMS provider fails to write the message due
580     *                 to some internal error.
581     * @throws IllegalArgumentException if the name is null or if the name is an
582     *                 empty string.
583     * @throws MessageNotWriteableException if the message is in read-only mode.
584     */
585    @Override
586    public void setByte(String name, byte value) throws JMSException {
587        initializeWriting();
588        put(name, Byte.valueOf(value));
589    }
590
591    /**
592     * Sets a <CODE>short</CODE> value with the specified name into the Map.
593     *
594     * @param name the name of the <CODE>short</CODE>
595     * @param value the <CODE>short</CODE> value to set in the Map
596     * @throws JMSException if the JMS provider fails to write the message due
597     *                 to some internal error.
598     * @throws IllegalArgumentException if the name is null or if the name is an
599     *                 empty string.
600     * @throws MessageNotWriteableException if the message is in read-only mode.
601     */
602    @Override
603    public void setShort(String name, short value) throws JMSException {
604        initializeWriting();
605        put(name, Short.valueOf(value));
606    }
607
608    /**
609     * Sets a Unicode character value with the specified name into the Map.
610     *
611     * @param name the name of the Unicode character
612     * @param value the Unicode character value to set in the Map
613     * @throws JMSException if the JMS provider fails to write the message due
614     *                 to some internal error.
615     * @throws IllegalArgumentException if the name is null or if the name is an
616     *                 empty string.
617     * @throws MessageNotWriteableException if the message is in read-only mode.
618     */
619    @Override
620    public void setChar(String name, char value) throws JMSException {
621        initializeWriting();
622        put(name, Character.valueOf(value));
623    }
624
625    /**
626     * Sets an <CODE>int</CODE> value with the specified name into the Map.
627     *
628     * @param name the name of the <CODE>int</CODE>
629     * @param value the <CODE>int</CODE> value to set in the Map
630     * @throws JMSException if the JMS provider fails to write the message due
631     *                 to some internal error.
632     * @throws IllegalArgumentException if the name is null or if the name is an
633     *                 empty string.
634     * @throws MessageNotWriteableException if the message is in read-only mode.
635     */
636    @Override
637    public void setInt(String name, int value) throws JMSException {
638        initializeWriting();
639        put(name, Integer.valueOf(value));
640    }
641
642    /**
643     * Sets a <CODE>long</CODE> value with the specified name into the Map.
644     *
645     * @param name the name of the <CODE>long</CODE>
646     * @param value the <CODE>long</CODE> value to set in the Map
647     * @throws JMSException if the JMS provider fails to write the message due
648     *                 to some internal error.
649     * @throws IllegalArgumentException if the name is null or if the name is an
650     *                 empty string.
651     * @throws MessageNotWriteableException if the message is in read-only mode.
652     */
653    @Override
654    public void setLong(String name, long value) throws JMSException {
655        initializeWriting();
656        put(name, Long.valueOf(value));
657    }
658
659    /**
660     * Sets a <CODE>float</CODE> value with the specified name into the Map.
661     *
662     * @param name the name of the <CODE>float</CODE>
663     * @param value the <CODE>float</CODE> value to set in the Map
664     * @throws JMSException if the JMS provider fails to write the message due
665     *                 to some internal error.
666     * @throws IllegalArgumentException if the name is null or if the name is an
667     *                 empty string.
668     * @throws MessageNotWriteableException if the message is in read-only mode.
669     */
670    @Override
671    public void setFloat(String name, float value) throws JMSException {
672        initializeWriting();
673        put(name, new Float(value));
674    }
675
676    /**
677     * Sets a <CODE>double</CODE> value with the specified name into the Map.
678     *
679     * @param name the name of the <CODE>double</CODE>
680     * @param value the <CODE>double</CODE> value to set in the Map
681     * @throws JMSException if the JMS provider fails to write the message due
682     *                 to some internal error.
683     * @throws IllegalArgumentException if the name is null or if the name is an
684     *                 empty string.
685     * @throws MessageNotWriteableException if the message is in read-only mode.
686     */
687    @Override
688    public void setDouble(String name, double value) throws JMSException {
689        initializeWriting();
690        put(name, new Double(value));
691    }
692
693    /**
694     * Sets a <CODE>String</CODE> value with the specified name into the Map.
695     *
696     * @param name the name of the <CODE>String</CODE>
697     * @param value the <CODE>String</CODE> value to set in the Map
698     * @throws JMSException if the JMS provider fails to write the message due
699     *                 to some internal error.
700     * @throws IllegalArgumentException if the name is null or if the name is an
701     *                 empty string.
702     * @throws MessageNotWriteableException if the message is in read-only mode.
703     */
704    @Override
705    public void setString(String name, String value) throws JMSException {
706        initializeWriting();
707        put(name, value);
708    }
709
710    /**
711     * Sets a byte array value with the specified name into the Map.
712     *
713     * @param name the name of the byte array
714     * @param value the byte array value to set in the Map; the array is copied
715     *                so that the value for <CODE>name </CODE> will not be
716     *                altered by future modifications
717     * @throws JMSException if the JMS provider fails to write the message due
718     *                 to some internal error.
719     * @throws NullPointerException if the name is null, or if the name is an
720     *                 empty string.
721     * @throws MessageNotWriteableException if the message is in read-only mode.
722     */
723    @Override
724    public void setBytes(String name, byte[] value) throws JMSException {
725        initializeWriting();
726        if (value != null) {
727            put(name, value);
728        } else {
729            map.remove(name);
730        }
731    }
732
733    /**
734     * Sets a portion of the byte array value with the specified name into the
735     * Map.
736     *
737     * @param name the name of the byte array
738     * @param value the byte array value to set in the Map
739     * @param offset the initial offset within the byte array
740     * @param length the number of bytes to use
741     * @throws JMSException if the JMS provider fails to write the message due
742     *                 to some internal error.
743     * @throws IllegalArgumentException if the name is null or if the name is an
744     *                 empty string.
745     * @throws MessageNotWriteableException if the message is in read-only mode.
746     */
747    @Override
748    public void setBytes(String name, byte[] value, int offset, int length) throws JMSException {
749        initializeWriting();
750        byte[] data = new byte[length];
751        System.arraycopy(value, offset, data, 0, length);
752        put(name, data);
753    }
754
755    /**
756     * Sets an object value with the specified name into the Map.
757     * <P>
758     * This method works only for the objectified primitive object types (<code>Integer</code>,<code>Double</code>,
759     * <code>Long</code> &nbsp;...), <code>String</code> objects, and byte
760     * arrays.
761     *
762     * @param name the name of the Java object
763     * @param value the Java object value to set in the Map
764     * @throws JMSException if the JMS provider fails to write the message due
765     *                 to some internal error.
766     * @throws IllegalArgumentException if the name is null or if the name is an
767     *                 empty string.
768     * @throws MessageFormatException if the object is invalid.
769     * @throws MessageNotWriteableException if the message is in read-only mode.
770     */
771    @Override
772    public void setObject(String name, Object value) throws JMSException {
773        initializeWriting();
774        if (value != null) {
775            // byte[] not allowed on properties
776            if (!(value instanceof byte[])) {
777                checkValidObject(value);
778            }
779            put(name, value);
780        } else {
781            put(name, null);
782        }
783    }
784
785    /**
786     * Indicates whether an item exists in this <CODE>MapMessage</CODE>
787     * object.
788     *
789     * @param name the name of the item to test
790     * @return true if the item exists
791     * @throws JMSException if the JMS provider fails to determine if the item
792     *                 exists due to some internal error.
793     */
794    @Override
795    public boolean itemExists(String name) throws JMSException {
796        initializeReading();
797        return map.containsKey(name);
798    }
799
800    private void initializeReading() throws JMSException {
801        loadContent();
802    }
803
804    private void initializeWriting() throws MessageNotWriteableException {
805        checkReadOnlyBody();
806        setContent(null);
807    }
808
809    @Override
810    public void compress() throws IOException {
811        storeContent();
812        super.compress();
813    }
814
815    @Override
816    public String toString() {
817        return super.toString() + " ActiveMQMapMessage{ " + "theTable = " + map + " }";
818    }
819
820    public Map<String, Object> getContentMap() throws JMSException {
821        initializeReading();
822        return map;
823    }
824}