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 }