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