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.IOException;
020    import java.io.UnsupportedEncodingException;
021    import java.util.Enumeration;
022    import java.util.HashMap;
023    import java.util.List;
024    import java.util.Map;
025    import java.util.Vector;
026    
027    import javax.jms.DeliveryMode;
028    import javax.jms.Destination;
029    import javax.jms.JMSException;
030    import javax.jms.MessageFormatException;
031    import javax.jms.MessageNotWriteableException;
032    
033    import org.apache.activemq.ActiveMQConnection;
034    import org.apache.activemq.ScheduledMessage;
035    import org.apache.activemq.broker.scheduler.CronParser;
036    import org.apache.activemq.filter.PropertyExpression;
037    import org.apache.activemq.state.CommandVisitor;
038    import org.apache.activemq.util.Callback;
039    import org.apache.activemq.util.JMSExceptionSupport;
040    import org.apache.activemq.util.TypeConversionSupport;
041    
042    /**
043     *
044     * @openwire:marshaller code="23"
045     */
046    public class ActiveMQMessage extends Message implements org.apache.activemq.Message, ScheduledMessage {
047        public static final byte DATA_STRUCTURE_TYPE = CommandTypes.ACTIVEMQ_MESSAGE;
048        public static final String DLQ_DELIVERY_FAILURE_CAUSE_PROPERTY = "dlqDeliveryFailureCause";
049        private static final Map<String, PropertySetter> JMS_PROPERTY_SETERS = new HashMap<String, PropertySetter>();
050    
051        protected transient Callback acknowledgeCallback;
052    
053        @Override
054        public byte getDataStructureType() {
055            return DATA_STRUCTURE_TYPE;
056        }
057    
058        @Override
059        public Message copy() {
060            ActiveMQMessage copy = new ActiveMQMessage();
061            copy(copy);
062            return copy;
063        }
064    
065        protected void copy(ActiveMQMessage copy) {
066            super.copy(copy);
067            copy.acknowledgeCallback = acknowledgeCallback;
068        }
069    
070        @Override
071        public int hashCode() {
072            MessageId id = getMessageId();
073            if (id != null) {
074                return id.hashCode();
075            } else {
076                return super.hashCode();
077            }
078        }
079    
080        @Override
081        public boolean equals(Object o) {
082            if (this == o) {
083                return true;
084            }
085            if (o == null || o.getClass() != getClass()) {
086                return false;
087            }
088    
089            ActiveMQMessage msg = (ActiveMQMessage) o;
090            MessageId oMsg = msg.getMessageId();
091            MessageId thisMsg = this.getMessageId();
092            return thisMsg != null && oMsg != null && oMsg.equals(thisMsg);
093        }
094    
095        @Override
096        public void acknowledge() throws JMSException {
097            if (acknowledgeCallback != null) {
098                try {
099                    acknowledgeCallback.execute();
100                } catch (JMSException e) {
101                    throw e;
102                } catch (Throwable e) {
103                    throw JMSExceptionSupport.create(e);
104                }
105            }
106        }
107    
108        @Override
109        public void clearBody() throws JMSException {
110            setContent(null);
111            readOnlyBody = false;
112        }
113    
114        @Override
115        public String getJMSMessageID() {
116            MessageId messageId = this.getMessageId();
117            if (messageId == null) {
118                return null;
119            }
120            return messageId.toString();
121        }
122    
123        /**
124         * Seems to be invalid because the parameter doesn't initialize MessageId
125         * instance variables ProducerId and ProducerSequenceId
126         *
127         * @param value
128         * @throws JMSException
129         */
130        @Override
131        public void setJMSMessageID(String value) throws JMSException {
132            if (value != null) {
133                try {
134                    MessageId id = new MessageId(value);
135                    this.setMessageId(id);
136                } catch (NumberFormatException e) {
137                    // we must be some foreign JMS provider or strange user-supplied
138                    // String
139                    // so lets set the IDs to be 1
140                    MessageId id = new MessageId();
141                    id.setTextView(value);
142                    this.setMessageId(messageId);
143                }
144            } else {
145                this.setMessageId(null);
146            }
147        }
148    
149        /**
150         * This will create an object of MessageId. For it to be valid, the instance
151         * variable ProducerId and producerSequenceId must be initialized.
152         *
153         * @param producerId
154         * @param producerSequenceId
155         * @throws JMSException
156         */
157        public void setJMSMessageID(ProducerId producerId, long producerSequenceId) throws JMSException {
158            MessageId id = null;
159            try {
160                id = new MessageId(producerId, producerSequenceId);
161                this.setMessageId(id);
162            } catch (Throwable e) {
163                throw JMSExceptionSupport.create("Invalid message id '" + id + "', reason: " + e.getMessage(), e);
164            }
165        }
166    
167        @Override
168        public long getJMSTimestamp() {
169            return this.getTimestamp();
170        }
171    
172        @Override
173        public void setJMSTimestamp(long timestamp) {
174            this.setTimestamp(timestamp);
175        }
176    
177        @Override
178        public String getJMSCorrelationID() {
179            return this.getCorrelationId();
180        }
181    
182        @Override
183        public void setJMSCorrelationID(String correlationId) {
184            this.setCorrelationId(correlationId);
185        }
186    
187        @Override
188        public byte[] getJMSCorrelationIDAsBytes() throws JMSException {
189            return encodeString(this.getCorrelationId());
190        }
191    
192        @Override
193        public void setJMSCorrelationIDAsBytes(byte[] correlationId) throws JMSException {
194            this.setCorrelationId(decodeString(correlationId));
195        }
196    
197        @Override
198        public String getJMSXMimeType() {
199            return "jms/message";
200        }
201    
202        protected static String decodeString(byte[] data) throws JMSException {
203            try {
204                if (data == null) {
205                    return null;
206                }
207                return new String(data, "UTF-8");
208            } catch (UnsupportedEncodingException e) {
209                throw new JMSException("Invalid UTF-8 encoding: " + e.getMessage());
210            }
211        }
212    
213        protected static byte[] encodeString(String data) throws JMSException {
214            try {
215                if (data == null) {
216                    return null;
217                }
218                return data.getBytes("UTF-8");
219            } catch (UnsupportedEncodingException e) {
220                throw new JMSException("Invalid UTF-8 encoding: " + e.getMessage());
221            }
222        }
223    
224        @Override
225        public Destination getJMSReplyTo() {
226            return this.getReplyTo();
227        }
228    
229        @Override
230        public void setJMSReplyTo(Destination destination) throws JMSException {
231            this.setReplyTo(ActiveMQDestination.transform(destination));
232        }
233    
234        @Override
235        public Destination getJMSDestination() {
236            return this.getDestination();
237        }
238    
239        @Override
240        public void setJMSDestination(Destination destination) throws JMSException {
241            this.setDestination(ActiveMQDestination.transform(destination));
242        }
243    
244        @Override
245        public int getJMSDeliveryMode() {
246            return this.isPersistent() ? DeliveryMode.PERSISTENT : DeliveryMode.NON_PERSISTENT;
247        }
248    
249        @Override
250        public void setJMSDeliveryMode(int mode) {
251            this.setPersistent(mode == DeliveryMode.PERSISTENT);
252        }
253    
254        @Override
255        public boolean getJMSRedelivered() {
256            return this.isRedelivered();
257        }
258    
259        @Override
260        public void setJMSRedelivered(boolean redelivered) {
261            this.setRedelivered(redelivered);
262        }
263    
264        @Override
265        public String getJMSType() {
266            return this.getType();
267        }
268    
269        @Override
270        public void setJMSType(String type) {
271            this.setType(type);
272        }
273    
274        @Override
275        public long getJMSExpiration() {
276            return this.getExpiration();
277        }
278    
279        @Override
280        public void setJMSExpiration(long expiration) {
281            this.setExpiration(expiration);
282        }
283    
284        @Override
285        public int getJMSPriority() {
286            return this.getPriority();
287        }
288    
289        @Override
290        public void setJMSPriority(int priority) {
291            this.setPriority((byte) priority);
292        }
293    
294        @Override
295        public void clearProperties() {
296            super.clearProperties();
297            readOnlyProperties = false;
298        }
299    
300        @Override
301        public boolean propertyExists(String name) throws JMSException {
302            try {
303                return (this.getProperties().containsKey(name) || getObjectProperty(name)!= null);
304            } catch (IOException e) {
305                throw JMSExceptionSupport.create(e);
306            }
307        }
308    
309        @Override
310        @SuppressWarnings("rawtypes")
311        public Enumeration getPropertyNames() throws JMSException {
312            try {
313                Vector<String> result = new Vector<String>(this.getProperties().keySet());
314                if( getRedeliveryCounter()!=0 ) {
315                    result.add("JMSXDeliveryCount");
316                }
317                if( getGroupID()!=null ) {
318                    result.add("JMSXGroupID");
319                }
320                if( getGroupID()!=null ) {
321                    result.add("JMSXGroupSeq");
322                }
323                if( getUserID()!=null ) {
324                    result.add("JMSXUserID");
325                }
326                return result.elements();
327            } catch (IOException e) {
328                throw JMSExceptionSupport.create(e);
329            }
330        }
331    
332        /**
333         * return all property names, including standard JMS properties and JMSX properties
334         * @return  Enumeration of all property names on this message
335         * @throws JMSException
336         */
337        @SuppressWarnings("rawtypes")
338        public Enumeration getAllPropertyNames() throws JMSException {
339            try {
340                Vector<String> result = new Vector<String>(this.getProperties().keySet());
341                result.addAll(JMS_PROPERTY_SETERS.keySet());
342                return result.elements();
343            } catch (IOException e) {
344                throw JMSExceptionSupport.create(e);
345            }
346        }
347    
348        interface PropertySetter {
349            void set(Message message, Object value) throws MessageFormatException;
350        }
351    
352        static {
353            JMS_PROPERTY_SETERS.put("JMSXDeliveryCount", new PropertySetter() {
354                @Override
355                public void set(Message message, Object value) throws MessageFormatException {
356                    Integer rc = (Integer) TypeConversionSupport.convert(value, Integer.class);
357                    if (rc == null) {
358                        throw new MessageFormatException("Property JMSXDeliveryCount cannot be set from a " + value.getClass().getName() + ".");
359                    }
360                    message.setRedeliveryCounter(rc.intValue() - 1);
361                }
362            });
363            JMS_PROPERTY_SETERS.put("JMSXGroupID", new PropertySetter() {
364                @Override
365                public void set(Message message, Object value) throws MessageFormatException {
366                    String rc = (String) TypeConversionSupport.convert(value, String.class);
367                    if (rc == null) {
368                        throw new MessageFormatException("Property JMSXGroupID cannot be set from a " + value.getClass().getName() + ".");
369                    }
370                    message.setGroupID(rc);
371                }
372            });
373            JMS_PROPERTY_SETERS.put("JMSXGroupSeq", new PropertySetter() {
374                @Override
375                public void set(Message message, Object value) throws MessageFormatException {
376                    Integer rc = (Integer) TypeConversionSupport.convert(value, Integer.class);
377                    if (rc == null) {
378                        throw new MessageFormatException("Property JMSXGroupSeq cannot be set from a " + value.getClass().getName() + ".");
379                    }
380                    message.setGroupSequence(rc.intValue());
381                }
382            });
383            JMS_PROPERTY_SETERS.put("JMSCorrelationID", new PropertySetter() {
384                @Override
385                public void set(Message message, Object value) throws MessageFormatException {
386                    String rc = (String) TypeConversionSupport.convert(value, String.class);
387                    if (rc == null) {
388                        throw new MessageFormatException("Property JMSCorrelationID cannot be set from a " + value.getClass().getName() + ".");
389                    }
390                    ((ActiveMQMessage) message).setJMSCorrelationID(rc);
391                }
392            });
393            JMS_PROPERTY_SETERS.put("JMSDeliveryMode", new PropertySetter() {
394                @Override
395                public void set(Message message, Object value) throws MessageFormatException {
396                    Integer rc = (Integer) TypeConversionSupport.convert(value, Integer.class);
397                    if (rc == null) {
398                        Boolean bool = (Boolean) TypeConversionSupport.convert(value, Boolean.class);
399                        if (bool == null) {
400                            throw new MessageFormatException("Property JMSDeliveryMode cannot be set from a " + value.getClass().getName() + ".");
401                        }
402                        else {
403                            rc = bool.booleanValue() ? DeliveryMode.PERSISTENT : DeliveryMode.NON_PERSISTENT;
404                        }
405                    }
406                    ((ActiveMQMessage) message).setJMSDeliveryMode(rc);
407                }
408            });
409            JMS_PROPERTY_SETERS.put("JMSExpiration", new PropertySetter() {
410                @Override
411                public void set(Message message, Object value) throws MessageFormatException {
412                    Long rc = (Long) TypeConversionSupport.convert(value, Long.class);
413                    if (rc == null) {
414                        throw new MessageFormatException("Property JMSExpiration cannot be set from a " + value.getClass().getName() + ".");
415                    }
416                    ((ActiveMQMessage) message).setJMSExpiration(rc.longValue());
417                }
418            });
419            JMS_PROPERTY_SETERS.put("JMSPriority", new PropertySetter() {
420                @Override
421                public void set(Message message, Object value) throws MessageFormatException {
422                    Integer rc = (Integer) TypeConversionSupport.convert(value, Integer.class);
423                    if (rc == null) {
424                        throw new MessageFormatException("Property JMSPriority cannot be set from a " + value.getClass().getName() + ".");
425                    }
426                    ((ActiveMQMessage) message).setJMSPriority(rc.intValue());
427                }
428            });
429            JMS_PROPERTY_SETERS.put("JMSRedelivered", new PropertySetter() {
430                @Override
431                public void set(Message message, Object value) throws MessageFormatException {
432                    Boolean rc = (Boolean) TypeConversionSupport.convert(value, Boolean.class);
433                    if (rc == null) {
434                        throw new MessageFormatException("Property JMSRedelivered cannot be set from a " + value.getClass().getName() + ".");
435                    }
436                    ((ActiveMQMessage) message).setJMSRedelivered(rc.booleanValue());
437                }
438            });
439            JMS_PROPERTY_SETERS.put("JMSReplyTo", new PropertySetter() {
440                @Override
441                public void set(Message message, Object value) throws MessageFormatException {
442                    ActiveMQDestination rc = (ActiveMQDestination) TypeConversionSupport.convert(value, ActiveMQDestination.class);
443                    if (rc == null) {
444                        throw new MessageFormatException("Property JMSReplyTo cannot be set from a " + value.getClass().getName() + ".");
445                    }
446                    ((ActiveMQMessage) message).setReplyTo(rc);
447                }
448            });
449            JMS_PROPERTY_SETERS.put("JMSTimestamp", new PropertySetter() {
450                @Override
451                public void set(Message message, Object value) throws MessageFormatException {
452                    Long rc = (Long) TypeConversionSupport.convert(value, Long.class);
453                    if (rc == null) {
454                        throw new MessageFormatException("Property JMSTimestamp cannot be set from a " + value.getClass().getName() + ".");
455                    }
456                    ((ActiveMQMessage) message).setJMSTimestamp(rc.longValue());
457                }
458            });
459            JMS_PROPERTY_SETERS.put("JMSType", new PropertySetter() {
460                @Override
461                public void set(Message message, Object value) throws MessageFormatException {
462                    String rc = (String) TypeConversionSupport.convert(value, String.class);
463                    if (rc == null) {
464                        throw new MessageFormatException("Property JMSType cannot be set from a " + value.getClass().getName() + ".");
465                    }
466                    ((ActiveMQMessage) message).setJMSType(rc);
467                }
468            });
469        }
470    
471        @Override
472        public void setObjectProperty(String name, Object value) throws JMSException {
473            setObjectProperty(name, value, true);
474        }
475    
476        public void setObjectProperty(String name, Object value, boolean checkReadOnly) throws JMSException {
477    
478            if (checkReadOnly) {
479                checkReadOnlyProperties();
480            }
481            if (name == null || name.equals("")) {
482                throw new IllegalArgumentException("Property name cannot be empty or null");
483            }
484    
485            checkValidObject(value);
486            value = convertScheduled(name, value);
487            PropertySetter setter = JMS_PROPERTY_SETERS.get(name);
488    
489            if (setter != null && value != null) {
490                setter.set(this, value);
491            } else {
492                try {
493                    this.setProperty(name, value);
494                } catch (IOException e) {
495                    throw JMSExceptionSupport.create(e);
496                }
497            }
498        }
499    
500        public void setProperties(Map<String, ?> properties) throws JMSException {
501            for (Map.Entry<String, ?> entry : properties.entrySet()) {
502                // Lets use the object property method as we may contain standard
503                // extension headers like JMSXGroupID
504                setObjectProperty(entry.getKey(), entry.getValue());
505            }
506        }
507    
508        protected void checkValidObject(Object value) throws MessageFormatException {
509    
510            boolean valid = value instanceof Boolean || value instanceof Byte || value instanceof Short || value instanceof Integer || value instanceof Long;
511            valid = valid || value instanceof Float || value instanceof Double || value instanceof Character || value instanceof String || value == null;
512    
513            if (!valid) {
514    
515                ActiveMQConnection conn = getConnection();
516                // conn is null if we are in the broker rather than a JMS client
517                if (conn == null || conn.isNestedMapAndListEnabled()) {
518                    if (!(value instanceof Map || value instanceof List)) {
519                        throw new MessageFormatException("Only objectified primitive objects, String, Map and List types are allowed but was: " + value + " type: " + value.getClass());
520                    }
521                } else {
522                    throw new MessageFormatException("Only objectified primitive objects and String types are allowed but was: " + value + " type: " + value.getClass());
523                }
524            }
525        }
526    
527        protected void checkValidScheduled(String name, Object value) throws MessageFormatException {
528            if (AMQ_SCHEDULED_DELAY.equals(name) || AMQ_SCHEDULED_PERIOD.equals(name) || AMQ_SCHEDULED_REPEAT.equals(name)) {
529                if (value instanceof Long == false && value instanceof Integer == false) {
530                    throw new MessageFormatException(name + " should be long or int value");
531                }
532            }
533            if (AMQ_SCHEDULED_CRON.equals(name)) {
534                CronParser.validate(value.toString());
535            }
536        }
537    
538        protected Object convertScheduled(String name, Object value) throws MessageFormatException {
539            Object result = value;
540            if (AMQ_SCHEDULED_DELAY.equals(name)){
541                result = TypeConversionSupport.convert(value, Long.class);
542            }
543            else if (AMQ_SCHEDULED_PERIOD.equals(name)){
544                result = TypeConversionSupport.convert(value, Long.class);
545            }
546            else if (AMQ_SCHEDULED_REPEAT.equals(name)){
547                result = TypeConversionSupport.convert(value, Integer.class);
548            }
549            return result;
550        }
551    
552        @Override
553        public Object getObjectProperty(String name) throws JMSException {
554            if (name == null) {
555                throw new NullPointerException("Property name cannot be null");
556            }
557    
558            // PropertyExpression handles converting message headers to properties.
559            PropertyExpression expression = new PropertyExpression(name);
560            return expression.evaluate(this);
561        }
562    
563        @Override
564        public boolean getBooleanProperty(String name) throws JMSException {
565            Object value = getObjectProperty(name);
566            if (value == null) {
567                return false;
568            }
569            Boolean rc = (Boolean) TypeConversionSupport.convert(value, Boolean.class);
570            if (rc == null) {
571                throw new MessageFormatException("Property " + name + " was a " + value.getClass().getName() + " and cannot be read as a boolean");
572            }
573            return rc.booleanValue();
574        }
575    
576        @Override
577        public byte getByteProperty(String name) throws JMSException {
578            Object value = getObjectProperty(name);
579            if (value == null) {
580                throw new NumberFormatException("property " + name + " was null");
581            }
582            Byte rc = (Byte) TypeConversionSupport.convert(value, Byte.class);
583            if (rc == null) {
584                throw new MessageFormatException("Property " + name + " was a " + value.getClass().getName() + " and cannot be read as a byte");
585            }
586            return rc.byteValue();
587        }
588    
589        @Override
590        public short getShortProperty(String name) throws JMSException {
591            Object value = getObjectProperty(name);
592            if (value == null) {
593                throw new NumberFormatException("property " + name + " was null");
594            }
595            Short rc = (Short) TypeConversionSupport.convert(value, Short.class);
596            if (rc == null) {
597                throw new MessageFormatException("Property " + name + " was a " + value.getClass().getName() + " and cannot be read as a short");
598            }
599            return rc.shortValue();
600        }
601    
602        @Override
603        public int getIntProperty(String name) throws JMSException {
604            Object value = getObjectProperty(name);
605            if (value == null) {
606                throw new NumberFormatException("property " + name + " was null");
607            }
608            Integer rc = (Integer) TypeConversionSupport.convert(value, Integer.class);
609            if (rc == null) {
610                throw new MessageFormatException("Property " + name + " was a " + value.getClass().getName() + " and cannot be read as an integer");
611            }
612            return rc.intValue();
613        }
614    
615        @Override
616        public long getLongProperty(String name) throws JMSException {
617            Object value = getObjectProperty(name);
618            if (value == null) {
619                throw new NumberFormatException("property " + name + " was null");
620            }
621            Long rc = (Long) TypeConversionSupport.convert(value, Long.class);
622            if (rc == null) {
623                throw new MessageFormatException("Property " + name + " was a " + value.getClass().getName() + " and cannot be read as a long");
624            }
625            return rc.longValue();
626        }
627    
628        @Override
629        public float getFloatProperty(String name) throws JMSException {
630            Object value = getObjectProperty(name);
631            if (value == null) {
632                throw new NullPointerException("property " + name + " was null");
633            }
634            Float rc = (Float) TypeConversionSupport.convert(value, Float.class);
635            if (rc == null) {
636                throw new MessageFormatException("Property " + name + " was a " + value.getClass().getName() + " and cannot be read as a float");
637            }
638            return rc.floatValue();
639        }
640    
641        @Override
642        public double getDoubleProperty(String name) throws JMSException {
643            Object value = getObjectProperty(name);
644            if (value == null) {
645                throw new NullPointerException("property " + name + " was null");
646            }
647            Double rc = (Double) TypeConversionSupport.convert(value, Double.class);
648            if (rc == null) {
649                throw new MessageFormatException("Property " + name + " was a " + value.getClass().getName() + " and cannot be read as a double");
650            }
651            return rc.doubleValue();
652        }
653    
654        @Override
655        public String getStringProperty(String name) throws JMSException {
656            Object value = null;
657            if (name.equals("JMSXUserID")) {
658                value = getUserID();
659                if (value == null) {
660                    value = getObjectProperty(name);
661                }
662            } else {
663                value = getObjectProperty(name);
664            }
665            if (value == null) {
666                return null;
667            }
668            String rc = (String) TypeConversionSupport.convert(value, String.class);
669            if (rc == null) {
670                throw new MessageFormatException("Property " + name + " was a " + value.getClass().getName() + " and cannot be read as a String");
671            }
672            return rc;
673        }
674    
675        @Override
676        public void setBooleanProperty(String name, boolean value) throws JMSException {
677            setBooleanProperty(name, value, true);
678        }
679    
680        public void setBooleanProperty(String name, boolean value, boolean checkReadOnly) throws JMSException {
681            setObjectProperty(name, Boolean.valueOf(value), checkReadOnly);
682        }
683    
684        @Override
685        public void setByteProperty(String name, byte value) throws JMSException {
686            setObjectProperty(name, Byte.valueOf(value));
687        }
688    
689        @Override
690        public void setShortProperty(String name, short value) throws JMSException {
691            setObjectProperty(name, Short.valueOf(value));
692        }
693    
694        @Override
695        public void setIntProperty(String name, int value) throws JMSException {
696            setObjectProperty(name, Integer.valueOf(value));
697        }
698    
699        @Override
700        public void setLongProperty(String name, long value) throws JMSException {
701            setObjectProperty(name, Long.valueOf(value));
702        }
703    
704        @Override
705        public void setFloatProperty(String name, float value) throws JMSException {
706            setObjectProperty(name, new Float(value));
707        }
708    
709        @Override
710        public void setDoubleProperty(String name, double value) throws JMSException {
711            setObjectProperty(name, new Double(value));
712        }
713    
714        @Override
715        public void setStringProperty(String name, String value) throws JMSException {
716            setObjectProperty(name, value);
717        }
718    
719        private void checkReadOnlyProperties() throws MessageNotWriteableException {
720            if (readOnlyProperties) {
721                throw new MessageNotWriteableException("Message properties are read-only");
722            }
723        }
724    
725        protected void checkReadOnlyBody() throws MessageNotWriteableException {
726            if (readOnlyBody) {
727                throw new MessageNotWriteableException("Message body is read-only");
728            }
729        }
730    
731        public Callback getAcknowledgeCallback() {
732            return acknowledgeCallback;
733        }
734    
735        public void setAcknowledgeCallback(Callback acknowledgeCallback) {
736            this.acknowledgeCallback = acknowledgeCallback;
737        }
738    
739        /**
740         * Send operation event listener. Used to get the message ready to be sent.
741         */
742        public void onSend() throws JMSException {
743            setReadOnlyBody(true);
744            setReadOnlyProperties(true);
745        }
746    
747        @Override
748        public Response visit(CommandVisitor visitor) throws Exception {
749            return visitor.processMessage(this);
750        }
751    
752        @Override
753        public void storeContent() {
754        }
755    }