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 clearUnMarshalledState() throws JMSException {
134        super.clearUnMarshalledState();
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    @Override
166    public boolean isContentMarshalled() {
167        return content != null || map == null || map.isEmpty();
168    }
169
170    /**
171     * Builds the message body from data
172     *
173     * @throws JMSException
174     * @throws IOException
175     */
176    private void loadContent() throws JMSException {
177        try {
178            if (getContent() != null && map.isEmpty()) {
179                ByteSequence content = getContent();
180                InputStream is = new ByteArrayInputStream(content);
181                if (isCompressed()) {
182                    is = new InflaterInputStream(is);
183                }
184                DataInputStream dataIn = new DataInputStream(is);
185                map = MarshallingSupport.unmarshalPrimitiveMap(dataIn);
186                dataIn.close();
187            }
188        } catch (IOException e) {
189            throw JMSExceptionSupport.create(e);
190        }
191    }
192
193    @Override
194    public byte getDataStructureType() {
195        return DATA_STRUCTURE_TYPE;
196    }
197
198    @Override
199    public String getJMSXMimeType() {
200        return "jms/map-message";
201    }
202
203    /**
204     * Clears out the message body. Clearing a message's body does not clear its
205     * header values or property entries.
206     * <P>
207     * If this message body was read-only, calling this method leaves the
208     * message body in the same state as an empty body in a newly created
209     * message.
210     */
211    @Override
212    public void clearBody() throws JMSException {
213        super.clearBody();
214        map.clear();
215    }
216
217    /**
218     * Returns the <CODE>boolean</CODE> value with the specified name.
219     *
220     * @param name the name of the <CODE>boolean</CODE>
221     * @return the <CODE>boolean</CODE> value with the specified name
222     * @throws JMSException if the JMS provider fails to read the message due to
223     *                 some internal error.
224     * @throws MessageFormatException if this type conversion is invalid.
225     */
226    @Override
227    public boolean getBoolean(String name) throws JMSException {
228        initializeReading();
229        Object value = map.get(name);
230        if (value == null) {
231            return false;
232        }
233        if (value instanceof Boolean) {
234            return ((Boolean)value).booleanValue();
235        }
236        if (value instanceof UTF8Buffer) {
237            return Boolean.valueOf(value.toString()).booleanValue();
238        }
239        if (value instanceof String) {
240            return Boolean.valueOf(value.toString()).booleanValue();
241        } else {
242            throw new MessageFormatException(" cannot read a boolean from " + value.getClass().getName());
243        }
244    }
245
246    /**
247     * Returns the <CODE>byte</CODE> value with the specified name.
248     *
249     * @param name the name of the <CODE>byte</CODE>
250     * @return the <CODE>byte</CODE> value with the specified name
251     * @throws JMSException if the JMS provider fails to read the message due to
252     *                 some internal error.
253     * @throws MessageFormatException if this type conversion is invalid.
254     */
255    @Override
256    public byte getByte(String name) throws JMSException {
257        initializeReading();
258        Object value = map.get(name);
259        if (value == null) {
260            return 0;
261        }
262        if (value instanceof Byte) {
263            return ((Byte)value).byteValue();
264        }
265        if (value instanceof UTF8Buffer) {
266            return Byte.valueOf(value.toString()).byteValue();
267        }
268        if (value instanceof String) {
269            return Byte.valueOf(value.toString()).byteValue();
270        } else {
271            throw new MessageFormatException(" cannot read a byte from " + value.getClass().getName());
272        }
273    }
274
275    /**
276     * Returns the <CODE>short</CODE> value with the specified name.
277     *
278     * @param name the name of the <CODE>short</CODE>
279     * @return the <CODE>short</CODE> value with the specified name
280     * @throws JMSException if the JMS provider fails to read the message due to
281     *                 some internal error.
282     * @throws MessageFormatException if this type conversion is invalid.
283     */
284    @Override
285    public short getShort(String name) throws JMSException {
286        initializeReading();
287        Object value = map.get(name);
288        if (value == null) {
289            return 0;
290        }
291        if (value instanceof Short) {
292            return ((Short)value).shortValue();
293        }
294        if (value instanceof Byte) {
295            return ((Byte)value).shortValue();
296        }
297        if (value instanceof UTF8Buffer) {
298            return Short.valueOf(value.toString()).shortValue();
299        }
300        if (value instanceof String) {
301            return Short.valueOf(value.toString()).shortValue();
302        } else {
303            throw new MessageFormatException(" cannot read a short from " + value.getClass().getName());
304        }
305    }
306
307    /**
308     * Returns the Unicode character value with the specified name.
309     *
310     * @param name the name of the Unicode character
311     * @return the Unicode character value with the specified name
312     * @throws JMSException if the JMS provider fails to read the message due to
313     *                 some internal error.
314     * @throws MessageFormatException if this type conversion is invalid.
315     */
316    @Override
317    public char getChar(String name) throws JMSException {
318        initializeReading();
319        Object value = map.get(name);
320
321        if (value == null) {
322            throw new NullPointerException();
323        } else if (value instanceof Character) {
324            return ((Character)value).charValue();
325        } else {
326            throw new MessageFormatException(" cannot read a char from " + value.getClass().getName());
327        }
328    }
329
330    /**
331     * Returns the <CODE>int</CODE> value with the specified name.
332     *
333     * @param name the name of the <CODE>int</CODE>
334     * @return the <CODE>int</CODE> value with the specified name
335     * @throws JMSException if the JMS provider fails to read the message due to
336     *                 some internal error.
337     * @throws MessageFormatException if this type conversion is invalid.
338     */
339    @Override
340    public int getInt(String name) throws JMSException {
341        initializeReading();
342        Object value = map.get(name);
343        if (value == null) {
344            return 0;
345        }
346        if (value instanceof Integer) {
347            return ((Integer)value).intValue();
348        }
349        if (value instanceof Short) {
350            return ((Short)value).intValue();
351        }
352        if (value instanceof Byte) {
353            return ((Byte)value).intValue();
354        }
355        if (value instanceof UTF8Buffer) {
356            return Integer.valueOf(value.toString()).intValue();
357        }
358        if (value instanceof String) {
359            return Integer.valueOf(value.toString()).intValue();
360        } else {
361            throw new MessageFormatException(" cannot read an int from " + value.getClass().getName());
362        }
363    }
364
365    /**
366     * Returns the <CODE>long</CODE> value with the specified name.
367     *
368     * @param name the name of the <CODE>long</CODE>
369     * @return the <CODE>long</CODE> value with the specified name
370     * @throws JMSException if the JMS provider fails to read the message due to
371     *                 some internal error.
372     * @throws MessageFormatException if this type conversion is invalid.
373     */
374    @Override
375    public long getLong(String name) throws JMSException {
376        initializeReading();
377        Object value = map.get(name);
378        if (value == null) {
379            return 0;
380        }
381        if (value instanceof Long) {
382            return ((Long)value).longValue();
383        }
384        if (value instanceof Integer) {
385            return ((Integer)value).longValue();
386        }
387        if (value instanceof Short) {
388            return ((Short)value).longValue();
389        }
390        if (value instanceof Byte) {
391            return ((Byte)value).longValue();
392        }
393        if (value instanceof UTF8Buffer) {
394            return Long.valueOf(value.toString()).longValue();
395        }
396        if (value instanceof String) {
397            return Long.valueOf(value.toString()).longValue();
398        } else {
399            throw new MessageFormatException(" cannot read a long from " + value.getClass().getName());
400        }
401    }
402
403    /**
404     * Returns the <CODE>float</CODE> value with the specified name.
405     *
406     * @param name the name of the <CODE>float</CODE>
407     * @return the <CODE>float</CODE> value with the specified name
408     * @throws JMSException if the JMS provider fails to read the message due to
409     *                 some internal error.
410     * @throws MessageFormatException if this type conversion is invalid.
411     */
412    @Override
413    public float getFloat(String name) throws JMSException {
414        initializeReading();
415        Object value = map.get(name);
416        if (value == null) {
417            return 0;
418        }
419        if (value instanceof Float) {
420            return ((Float)value).floatValue();
421        }
422        if (value instanceof UTF8Buffer) {
423            return Float.valueOf(value.toString()).floatValue();
424        }
425        if (value instanceof String) {
426            return Float.valueOf(value.toString()).floatValue();
427        } else {
428            throw new MessageFormatException(" cannot read a float from " + value.getClass().getName());
429        }
430    }
431
432    /**
433     * Returns the <CODE>double</CODE> value with the specified name.
434     *
435     * @param name the name of the <CODE>double</CODE>
436     * @return the <CODE>double</CODE> value with the specified name
437     * @throws JMSException if the JMS provider fails to read the message due to
438     *                 some internal error.
439     * @throws MessageFormatException if this type conversion is invalid.
440     */
441    @Override
442    public double getDouble(String name) throws JMSException {
443        initializeReading();
444        Object value = map.get(name);
445
446        if (value == null) {
447            return 0;
448        } else if (value instanceof Double) {
449            return ((Double)value).doubleValue();
450        } else if (value instanceof Float) {
451            return ((Float)value).floatValue();
452        } else if (value instanceof UTF8Buffer) {
453            return Double.valueOf(value.toString()).doubleValue();
454        } else if (value instanceof String) {
455            return Double.valueOf(value.toString()).doubleValue();
456        } else {
457            throw new MessageFormatException("Cannot read a double from " + value.getClass().getName());
458        }
459    }
460
461    /**
462     * Returns the <CODE>String</CODE> value with the specified name.
463     *
464     * @param name the name of the <CODE>String</CODE>
465     * @return the <CODE>String</CODE> value with the specified name; if there
466     *         is no item by this name, a null value is returned
467     * @throws JMSException if the JMS provider fails to read the message due to
468     *                 some internal error.
469     * @throws MessageFormatException if this type conversion is invalid.
470     */
471    @Override
472    public String getString(String name) throws JMSException {
473        initializeReading();
474        Object value = map.get(name);
475        if (value == null) {
476            return null;
477        }
478        if (value instanceof byte[]) {
479            throw new MessageFormatException("Use getBytes to read a byte array");
480        } else {
481            return value.toString();
482        }
483    }
484
485    /**
486     * Returns the byte array value with the specified name.
487     *
488     * @param name the name of the byte array
489     * @return a copy of the byte array value with the specified name; if there
490     *         is no item by this name, a null value is returned.
491     * @throws JMSException if the JMS provider fails to read the message due to
492     *                 some internal error.
493     * @throws MessageFormatException if this type conversion is invalid.
494     */
495    @Override
496    public byte[] getBytes(String name) throws JMSException {
497        initializeReading();
498        Object value = map.get(name);
499        if (value == null) {
500            return null;
501        }
502
503        if (value instanceof byte[]) {
504            return (byte[])value;
505        } else {
506            throw new MessageFormatException(" cannot read a byte[] from " + value.getClass().getName());
507        }
508    }
509
510    /**
511     * Returns the value of the object with the specified name.
512     * <P>
513     * This method can be used to return, in objectified format, an object in
514     * the Java programming language ("Java object") that had been stored in the
515     * Map with the equivalent <CODE>setObject</CODE> method call, or its
516     * equivalent primitive <CODE>set <I>type </I></CODE> method.
517     * <P>
518     * Note that byte values are returned as <CODE>byte[]</CODE>, not
519     * <CODE>Byte[]</CODE>.
520     *
521     * @param name the name of the Java object
522     * @return a copy of the Java object value with the specified name, in
523     *         objectified format (for example, if the object was set as an
524     *         <CODE>int</CODE>, an <CODE>Integer</CODE> is returned); if
525     *         there is no item by this name, a null value is returned
526     * @throws JMSException if the JMS provider fails to read the message due to
527     *                 some internal error.
528     */
529    @Override
530    public Object getObject(String name) throws JMSException {
531        initializeReading();
532        Object result = map.get(name);
533        if (result instanceof UTF8Buffer) {
534            result = result.toString();
535        }
536
537        return result;
538    }
539
540    /**
541     * Returns an <CODE>Enumeration</CODE> of all the names in the
542     * <CODE>MapMessage</CODE> object.
543     *
544     * @return an enumeration of all the names in this <CODE>MapMessage</CODE>
545     * @throws JMSException
546     */
547    @Override
548    public Enumeration<String> getMapNames() throws JMSException {
549        initializeReading();
550        return Collections.enumeration(map.keySet());
551    }
552
553    protected void put(String name, Object value) throws JMSException {
554        if (name == null) {
555            throw new IllegalArgumentException("The name of the property cannot be null.");
556        }
557        if (name.length() == 0) {
558            throw new IllegalArgumentException("The name of the property cannot be an emprty string.");
559        }
560        map.put(name, value);
561    }
562
563    /**
564     * Sets a <CODE>boolean</CODE> value with the specified name into the Map.
565     *
566     * @param name the name of the <CODE>boolean</CODE>
567     * @param value the <CODE>boolean</CODE> value to set in the Map
568     * @throws JMSException if the JMS provider fails to write the message due
569     *                 to some internal error.
570     * @throws IllegalArgumentException if the name is null or if the name is an
571     *                 empty string.
572     * @throws MessageNotWriteableException if the message is in read-only mode.
573     */
574    @Override
575    public void setBoolean(String name, boolean value) throws JMSException {
576        initializeWriting();
577        put(name, value ? Boolean.TRUE : Boolean.FALSE);
578    }
579
580    /**
581     * Sets a <CODE>byte</CODE> value with the specified name into the Map.
582     *
583     * @param name the name of the <CODE>byte</CODE>
584     * @param value the <CODE>byte</CODE> value to set in the Map
585     * @throws JMSException if the JMS provider fails to write the message due
586     *                 to some internal error.
587     * @throws IllegalArgumentException if the name is null or if the name is an
588     *                 empty string.
589     * @throws MessageNotWriteableException if the message is in read-only mode.
590     */
591    @Override
592    public void setByte(String name, byte value) throws JMSException {
593        initializeWriting();
594        put(name, Byte.valueOf(value));
595    }
596
597    /**
598     * Sets a <CODE>short</CODE> value with the specified name into the Map.
599     *
600     * @param name the name of the <CODE>short</CODE>
601     * @param value the <CODE>short</CODE> value to set in the Map
602     * @throws JMSException if the JMS provider fails to write the message due
603     *                 to some internal error.
604     * @throws IllegalArgumentException if the name is null or if the name is an
605     *                 empty string.
606     * @throws MessageNotWriteableException if the message is in read-only mode.
607     */
608    @Override
609    public void setShort(String name, short value) throws JMSException {
610        initializeWriting();
611        put(name, Short.valueOf(value));
612    }
613
614    /**
615     * Sets a Unicode character value with the specified name into the Map.
616     *
617     * @param name the name of the Unicode character
618     * @param value the Unicode character value to set in the Map
619     * @throws JMSException if the JMS provider fails to write the message due
620     *                 to some internal error.
621     * @throws IllegalArgumentException if the name is null or if the name is an
622     *                 empty string.
623     * @throws MessageNotWriteableException if the message is in read-only mode.
624     */
625    @Override
626    public void setChar(String name, char value) throws JMSException {
627        initializeWriting();
628        put(name, Character.valueOf(value));
629    }
630
631    /**
632     * Sets an <CODE>int</CODE> value with the specified name into the Map.
633     *
634     * @param name the name of the <CODE>int</CODE>
635     * @param value the <CODE>int</CODE> value to set in the Map
636     * @throws JMSException if the JMS provider fails to write the message due
637     *                 to some internal error.
638     * @throws IllegalArgumentException if the name is null or if the name is an
639     *                 empty string.
640     * @throws MessageNotWriteableException if the message is in read-only mode.
641     */
642    @Override
643    public void setInt(String name, int value) throws JMSException {
644        initializeWriting();
645        put(name, Integer.valueOf(value));
646    }
647
648    /**
649     * Sets a <CODE>long</CODE> value with the specified name into the Map.
650     *
651     * @param name the name of the <CODE>long</CODE>
652     * @param value the <CODE>long</CODE> value to set in the Map
653     * @throws JMSException if the JMS provider fails to write the message due
654     *                 to some internal error.
655     * @throws IllegalArgumentException if the name is null or if the name is an
656     *                 empty string.
657     * @throws MessageNotWriteableException if the message is in read-only mode.
658     */
659    @Override
660    public void setLong(String name, long value) throws JMSException {
661        initializeWriting();
662        put(name, Long.valueOf(value));
663    }
664
665    /**
666     * Sets a <CODE>float</CODE> value with the specified name into the Map.
667     *
668     * @param name the name of the <CODE>float</CODE>
669     * @param value the <CODE>float</CODE> value to set in the Map
670     * @throws JMSException if the JMS provider fails to write the message due
671     *                 to some internal error.
672     * @throws IllegalArgumentException if the name is null or if the name is an
673     *                 empty string.
674     * @throws MessageNotWriteableException if the message is in read-only mode.
675     */
676    @Override
677    public void setFloat(String name, float value) throws JMSException {
678        initializeWriting();
679        put(name, new Float(value));
680    }
681
682    /**
683     * Sets a <CODE>double</CODE> value with the specified name into the Map.
684     *
685     * @param name the name of the <CODE>double</CODE>
686     * @param value the <CODE>double</CODE> value to set in the Map
687     * @throws JMSException if the JMS provider fails to write the message due
688     *                 to some internal error.
689     * @throws IllegalArgumentException if the name is null or if the name is an
690     *                 empty string.
691     * @throws MessageNotWriteableException if the message is in read-only mode.
692     */
693    @Override
694    public void setDouble(String name, double value) throws JMSException {
695        initializeWriting();
696        put(name, new Double(value));
697    }
698
699    /**
700     * Sets a <CODE>String</CODE> value with the specified name into the Map.
701     *
702     * @param name the name of the <CODE>String</CODE>
703     * @param value the <CODE>String</CODE> value to set in the Map
704     * @throws JMSException if the JMS provider fails to write the message due
705     *                 to some internal error.
706     * @throws IllegalArgumentException if the name is null or if the name is an
707     *                 empty string.
708     * @throws MessageNotWriteableException if the message is in read-only mode.
709     */
710    @Override
711    public void setString(String name, String value) throws JMSException {
712        initializeWriting();
713        put(name, value);
714    }
715
716    /**
717     * Sets a byte array value with the specified name into the Map.
718     *
719     * @param name the name of the byte array
720     * @param value the byte array value to set in the Map; the array is copied
721     *                so that the value for <CODE>name </CODE> will not be
722     *                altered by future modifications
723     * @throws JMSException if the JMS provider fails to write the message due
724     *                 to some internal error.
725     * @throws NullPointerException if the name is null, or if the name is an
726     *                 empty string.
727     * @throws MessageNotWriteableException if the message is in read-only mode.
728     */
729    @Override
730    public void setBytes(String name, byte[] value) throws JMSException {
731        initializeWriting();
732        if (value != null) {
733            put(name, value);
734        } else {
735            map.remove(name);
736        }
737    }
738
739    /**
740     * Sets a portion of the byte array value with the specified name into the
741     * Map.
742     *
743     * @param name the name of the byte array
744     * @param value the byte array value to set in the Map
745     * @param offset the initial offset within the byte array
746     * @param length the number of bytes to use
747     * @throws JMSException if the JMS provider fails to write the message due
748     *                 to some internal error.
749     * @throws IllegalArgumentException if the name is null or if the name is an
750     *                 empty string.
751     * @throws MessageNotWriteableException if the message is in read-only mode.
752     */
753    @Override
754    public void setBytes(String name, byte[] value, int offset, int length) throws JMSException {
755        initializeWriting();
756        byte[] data = new byte[length];
757        System.arraycopy(value, offset, data, 0, length);
758        put(name, data);
759    }
760
761    /**
762     * Sets an object value with the specified name into the Map.
763     * <P>
764     * This method works only for the objectified primitive object types (<code>Integer</code>,<code>Double</code>,
765     * <code>Long</code> &nbsp;...), <code>String</code> objects, and byte
766     * arrays.
767     *
768     * @param name the name of the Java object
769     * @param value the Java object value to set in the Map
770     * @throws JMSException if the JMS provider fails to write the message due
771     *                 to some internal error.
772     * @throws IllegalArgumentException if the name is null or if the name is an
773     *                 empty string.
774     * @throws MessageFormatException if the object is invalid.
775     * @throws MessageNotWriteableException if the message is in read-only mode.
776     */
777    @Override
778    public void setObject(String name, Object value) throws JMSException {
779        initializeWriting();
780        if (value != null) {
781            // byte[] not allowed on properties
782            if (!(value instanceof byte[])) {
783                checkValidObject(value);
784            }
785            put(name, value);
786        } else {
787            put(name, null);
788        }
789    }
790
791    /**
792     * Indicates whether an item exists in this <CODE>MapMessage</CODE>
793     * object.
794     *
795     * @param name the name of the item to test
796     * @return true if the item exists
797     * @throws JMSException if the JMS provider fails to determine if the item
798     *                 exists due to some internal error.
799     */
800    @Override
801    public boolean itemExists(String name) throws JMSException {
802        initializeReading();
803        return map.containsKey(name);
804    }
805
806    private void initializeReading() throws JMSException {
807        loadContent();
808    }
809
810    private void initializeWriting() throws MessageNotWriteableException {
811        checkReadOnlyBody();
812        setContent(null);
813    }
814
815    @Override
816    public void compress() throws IOException {
817        storeContent();
818        super.compress();
819    }
820
821    @Override
822    public String toString() {
823        return super.toString() + " ActiveMQMapMessage{ " + "theTable = " + map + " }";
824    }
825
826    public Map<String, Object> getContentMap() throws JMSException {
827        initializeReading();
828        return map;
829    }
830}