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