001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.activemq.transport.amqp.message; 018 019import static org.apache.activemq.transport.amqp.message.AmqpMessageSupport.AMQP_DATA; 020import static org.apache.activemq.transport.amqp.message.AmqpMessageSupport.AMQP_NULL; 021import static org.apache.activemq.transport.amqp.message.AmqpMessageSupport.AMQP_SEQUENCE; 022import static org.apache.activemq.transport.amqp.message.AmqpMessageSupport.AMQP_UNKNOWN; 023import static org.apache.activemq.transport.amqp.message.AmqpMessageSupport.AMQP_VALUE_BINARY; 024import static org.apache.activemq.transport.amqp.message.AmqpMessageSupport.AMQP_VALUE_LIST; 025import static org.apache.activemq.transport.amqp.message.AmqpMessageSupport.AMQP_VALUE_STRING; 026import static org.apache.activemq.transport.amqp.message.AmqpMessageSupport.CONTENT_ENCODING; 027import static org.apache.activemq.transport.amqp.message.AmqpMessageSupport.CONTENT_TYPE; 028import static org.apache.activemq.transport.amqp.message.AmqpMessageSupport.DELIVERY_ANNOTATION_PREFIX; 029import static org.apache.activemq.transport.amqp.message.AmqpMessageSupport.EMPTY_BINARY; 030import static org.apache.activemq.transport.amqp.message.AmqpMessageSupport.FIRST_ACQUIRER; 031import static org.apache.activemq.transport.amqp.message.AmqpMessageSupport.FOOTER_PREFIX; 032import static org.apache.activemq.transport.amqp.message.AmqpMessageSupport.HEADER; 033import static org.apache.activemq.transport.amqp.message.AmqpMessageSupport.JMS_AMQP_CONTENT_TYPE; 034import static org.apache.activemq.transport.amqp.message.AmqpMessageSupport.JMS_AMQP_DELIVERY_ANNOTATION_PREFIX; 035import static org.apache.activemq.transport.amqp.message.AmqpMessageSupport.JMS_AMQP_FOOTER_PREFIX; 036import static org.apache.activemq.transport.amqp.message.AmqpMessageSupport.JMS_AMQP_MESSAGE_ANNOTATION_PREFIX; 037import static org.apache.activemq.transport.amqp.message.AmqpMessageSupport.JMS_AMQP_ORIGINAL_ENCODING; 038import static org.apache.activemq.transport.amqp.message.AmqpMessageSupport.JMS_AMQP_PREFIX; 039import static org.apache.activemq.transport.amqp.message.AmqpMessageSupport.JMS_AMQP_PREFIX_LENGTH; 040import static org.apache.activemq.transport.amqp.message.AmqpMessageSupport.MESSAGE_ANNOTATION_PREFIX; 041import static org.apache.activemq.transport.amqp.message.AmqpMessageSupport.MESSAGE_FORMAT; 042import static org.apache.activemq.transport.amqp.message.AmqpMessageSupport.NATIVE; 043import static org.apache.activemq.transport.amqp.message.AmqpMessageSupport.ORIGINAL_ENCODING; 044import static org.apache.activemq.transport.amqp.message.AmqpMessageSupport.PROPERTIES; 045import static org.apache.activemq.transport.amqp.message.AmqpMessageSupport.REPLYTO_GROUP_ID; 046import static org.apache.activemq.transport.amqp.message.AmqpMessageSupport.SERIALIZED_JAVA_OBJECT_CONTENT_TYPE; 047import static org.apache.activemq.transport.amqp.message.AmqpMessageSupport.getBinaryFromMessageBody; 048import static org.apache.activemq.transport.amqp.message.AmqpMessageSupport.getMapFromMessageBody; 049 050import java.io.IOException; 051import java.nio.charset.StandardCharsets; 052import java.util.ArrayList; 053import java.util.Date; 054import java.util.HashMap; 055import java.util.LinkedHashMap; 056import java.util.Map; 057 058import javax.jms.JMSException; 059import javax.jms.Message; 060import javax.jms.MessageEOFException; 061import javax.jms.TextMessage; 062 063import org.apache.activemq.command.ActiveMQBytesMessage; 064import org.apache.activemq.command.ActiveMQDestination; 065import org.apache.activemq.command.ActiveMQMapMessage; 066import org.apache.activemq.command.ActiveMQMessage; 067import org.apache.activemq.command.ActiveMQObjectMessage; 068import org.apache.activemq.command.ActiveMQStreamMessage; 069import org.apache.activemq.command.ActiveMQTextMessage; 070import org.apache.activemq.command.CommandTypes; 071import org.apache.activemq.command.ConnectionId; 072import org.apache.activemq.command.ConnectionInfo; 073import org.apache.activemq.command.ConsumerId; 074import org.apache.activemq.command.MessageId; 075import org.apache.activemq.command.RemoveInfo; 076import org.apache.activemq.transport.amqp.AmqpProtocolException; 077import org.apache.activemq.util.JMSExceptionSupport; 078import org.apache.activemq.util.TypeConversionSupport; 079import org.apache.qpid.proton.amqp.Binary; 080import org.apache.qpid.proton.amqp.Symbol; 081import org.apache.qpid.proton.amqp.UnsignedByte; 082import org.apache.qpid.proton.amqp.UnsignedInteger; 083import org.apache.qpid.proton.amqp.messaging.AmqpSequence; 084import org.apache.qpid.proton.amqp.messaging.AmqpValue; 085import org.apache.qpid.proton.amqp.messaging.ApplicationProperties; 086import org.apache.qpid.proton.amqp.messaging.Data; 087import org.apache.qpid.proton.amqp.messaging.DeliveryAnnotations; 088import org.apache.qpid.proton.amqp.messaging.Footer; 089import org.apache.qpid.proton.amqp.messaging.Header; 090import org.apache.qpid.proton.amqp.messaging.MessageAnnotations; 091import org.apache.qpid.proton.amqp.messaging.Properties; 092import org.apache.qpid.proton.amqp.messaging.Section; 093import org.apache.qpid.proton.codec.AMQPDefinedTypes; 094import org.apache.qpid.proton.codec.DecoderImpl; 095import org.apache.qpid.proton.codec.EncoderImpl; 096 097public class JMSMappingOutboundTransformer implements OutboundTransformer { 098 099 public static final Symbol JMS_DEST_TYPE_MSG_ANNOTATION = Symbol.valueOf("x-opt-jms-dest"); 100 public static final Symbol JMS_REPLY_TO_TYPE_MSG_ANNOTATION = Symbol.valueOf("x-opt-jms-reply-to"); 101 102 private static final String AMQ_SCHEDULED_MESSAGE_PREFIX = "AMQ_SCHEDULED_"; 103 104 public static final byte QUEUE_TYPE = 0x00; 105 public static final byte TOPIC_TYPE = 0x01; 106 public static final byte TEMP_QUEUE_TYPE = 0x02; 107 public static final byte TEMP_TOPIC_TYPE = 0x03; 108 109 private final UTF8BufferType utf8BufferEncoding; 110 111 // For now Proton requires that we create a decoder to create an encoder 112 private final DecoderImpl decoder = new DecoderImpl(); 113 private final EncoderImpl encoder = new EncoderImpl(decoder); 114 { 115 AMQPDefinedTypes.registerAllTypes(decoder, encoder); 116 117 utf8BufferEncoding = new UTF8BufferType(encoder, decoder); 118 119 encoder.register(utf8BufferEncoding); 120 } 121 122 @Override 123 public EncodedMessage transform(ActiveMQMessage message) throws Exception { 124 if (message == null) { 125 return null; 126 } 127 128 long messageFormat = 0; 129 Header header = null; 130 Properties properties = null; 131 Map<Symbol, Object> daMap = null; 132 Map<Symbol, Object> maMap = null; 133 Map<String,Object> apMap = null; 134 Map<Object, Object> footerMap = null; 135 136 Section body = convertBody(message); 137 138 if (message.isPersistent()) { 139 if (header == null) { 140 header = new Header(); 141 } 142 header.setDurable(true); 143 } 144 byte priority = message.getPriority(); 145 if (priority != Message.DEFAULT_PRIORITY) { 146 if (header == null) { 147 header = new Header(); 148 } 149 header.setPriority(UnsignedByte.valueOf(priority)); 150 } 151 String type = message.getType(); 152 if (type != null) { 153 if (properties == null) { 154 properties = new Properties(); 155 } 156 properties.setSubject(type); 157 } 158 MessageId messageId = message.getMessageId(); 159 if (messageId != null) { 160 if (properties == null) { 161 properties = new Properties(); 162 } 163 properties.setMessageId(getOriginalMessageId(message)); 164 } 165 ActiveMQDestination destination = message.getDestination(); 166 if (destination != null) { 167 if (properties == null) { 168 properties = new Properties(); 169 } 170 properties.setTo(destination.getQualifiedName()); 171 if (maMap == null) { 172 maMap = new HashMap<>(); 173 } 174 maMap.put(JMS_DEST_TYPE_MSG_ANNOTATION, destinationType(destination)); 175 } 176 ActiveMQDestination replyTo = message.getReplyTo(); 177 if (replyTo != null) { 178 if (properties == null) { 179 properties = new Properties(); 180 } 181 properties.setReplyTo(replyTo.getQualifiedName()); 182 if (maMap == null) { 183 maMap = new HashMap<>(); 184 } 185 maMap.put(JMS_REPLY_TO_TYPE_MSG_ANNOTATION, destinationType(replyTo)); 186 } 187 String correlationId = message.getCorrelationId(); 188 if (correlationId != null) { 189 if (properties == null) { 190 properties = new Properties(); 191 } 192 try { 193 properties.setCorrelationId(AMQPMessageIdHelper.INSTANCE.toIdObject(correlationId)); 194 } catch (AmqpProtocolException e) { 195 properties.setCorrelationId(correlationId); 196 } 197 } 198 long expiration = message.getExpiration(); 199 if (expiration != 0) { 200 long ttl = expiration - System.currentTimeMillis(); 201 if (ttl < 0) { 202 ttl = 1; 203 } 204 205 if (header == null) { 206 header = new Header(); 207 } 208 header.setTtl(new UnsignedInteger((int) ttl)); 209 210 if (properties == null) { 211 properties = new Properties(); 212 } 213 properties.setAbsoluteExpiryTime(new Date(expiration)); 214 } 215 long timeStamp = message.getTimestamp(); 216 if (timeStamp != 0) { 217 if (properties == null) { 218 properties = new Properties(); 219 } 220 properties.setCreationTime(new Date(timeStamp)); 221 } 222 223 // JMSX Message Properties 224 int deliveryCount = message.getRedeliveryCounter(); 225 if (deliveryCount > 0) { 226 if (header == null) { 227 header = new Header(); 228 } 229 header.setDeliveryCount(UnsignedInteger.valueOf(deliveryCount)); 230 } 231 String userId = message.getUserID(); 232 if (userId != null) { 233 if (properties == null) { 234 properties = new Properties(); 235 } 236 properties.setUserId(new Binary(userId.getBytes(StandardCharsets.UTF_8))); 237 } 238 String groupId = message.getGroupID(); 239 if (groupId != null) { 240 if (properties == null) { 241 properties = new Properties(); 242 } 243 properties.setGroupId(groupId); 244 } 245 int groupSequence = message.getGroupSequence(); 246 if (groupSequence > 0) { 247 if (properties == null) { 248 properties = new Properties(); 249 } 250 properties.setGroupSequence(UnsignedInteger.valueOf(groupSequence)); 251 } 252 253 final Map<String, Object> entries; 254 try { 255 entries = message.getProperties(); 256 } catch (IOException e) { 257 throw JMSExceptionSupport.create(e); 258 } 259 260 for (Map.Entry<String, Object> entry : entries.entrySet()) { 261 String key = entry.getKey(); 262 Object value = entry.getValue(); 263 264 if (key.startsWith(JMS_AMQP_PREFIX)) { 265 if (key.startsWith(NATIVE, JMS_AMQP_PREFIX_LENGTH)) { 266 // skip transformer appended properties 267 continue; 268 } else if (key.startsWith(ORIGINAL_ENCODING, JMS_AMQP_PREFIX_LENGTH)) { 269 // skip transformer appended properties 270 continue; 271 } else if (key.startsWith(MESSAGE_FORMAT, JMS_AMQP_PREFIX_LENGTH)) { 272 messageFormat = (long) TypeConversionSupport.convert(entry.getValue(), Long.class); 273 continue; 274 } else if (key.startsWith(HEADER, JMS_AMQP_PREFIX_LENGTH)) { 275 if (header == null) { 276 header = new Header(); 277 } 278 continue; 279 } else if (key.startsWith(PROPERTIES, JMS_AMQP_PREFIX_LENGTH)) { 280 if (properties == null) { 281 properties = new Properties(); 282 } 283 continue; 284 } else if (key.startsWith(MESSAGE_ANNOTATION_PREFIX, JMS_AMQP_PREFIX_LENGTH)) { 285 if (maMap == null) { 286 maMap = new HashMap<>(); 287 } 288 String name = key.substring(JMS_AMQP_MESSAGE_ANNOTATION_PREFIX.length()); 289 maMap.put(Symbol.valueOf(name), value); 290 continue; 291 } else if (key.startsWith(FIRST_ACQUIRER, JMS_AMQP_PREFIX_LENGTH)) { 292 if (header == null) { 293 header = new Header(); 294 } 295 header.setFirstAcquirer((boolean) TypeConversionSupport.convert(value, Boolean.class)); 296 continue; 297 } else if (key.startsWith(CONTENT_TYPE, JMS_AMQP_PREFIX_LENGTH)) { 298 if (properties == null) { 299 properties = new Properties(); 300 } 301 properties.setContentType(Symbol.getSymbol((String) TypeConversionSupport.convert(value, String.class))); 302 continue; 303 } else if (key.startsWith(CONTENT_ENCODING, JMS_AMQP_PREFIX_LENGTH)) { 304 if (properties == null) { 305 properties = new Properties(); 306 } 307 properties.setContentEncoding(Symbol.getSymbol((String) TypeConversionSupport.convert(value, String.class))); 308 continue; 309 } else if (key.startsWith(REPLYTO_GROUP_ID, JMS_AMQP_PREFIX_LENGTH)) { 310 if (properties == null) { 311 properties = new Properties(); 312 } 313 properties.setReplyToGroupId((String) TypeConversionSupport.convert(value, String.class)); 314 continue; 315 } else if (key.startsWith(DELIVERY_ANNOTATION_PREFIX, JMS_AMQP_PREFIX_LENGTH)) { 316 if (daMap == null) { 317 daMap = new HashMap<>(); 318 } 319 String name = key.substring(JMS_AMQP_DELIVERY_ANNOTATION_PREFIX.length()); 320 daMap.put(Symbol.valueOf(name), value); 321 continue; 322 } else if (key.startsWith(FOOTER_PREFIX, JMS_AMQP_PREFIX_LENGTH)) { 323 if (footerMap == null) { 324 footerMap = new HashMap<>(); 325 } 326 String name = key.substring(JMS_AMQP_FOOTER_PREFIX.length()); 327 footerMap.put(Symbol.valueOf(name), value); 328 continue; 329 } 330 } else if (key.startsWith(AMQ_SCHEDULED_MESSAGE_PREFIX )) { 331 // strip off the scheduled message properties 332 continue; 333 } 334 335 // The property didn't map into any other slot so we store it in the 336 // Application Properties section of the message. 337 if (apMap == null) { 338 apMap = new HashMap<>(); 339 } 340 apMap.put(key, value); 341 342 int messageType = message.getDataStructureType(); 343 if (messageType == CommandTypes.ACTIVEMQ_MESSAGE) { 344 // Type of command to recognize advisory message 345 Object data = message.getDataStructure(); 346 if(data != null) { 347 apMap.put("ActiveMqDataStructureType", data.getClass().getSimpleName()); 348 } 349 } 350 } 351 352 final AmqpWritableBuffer buffer = new AmqpWritableBuffer(); 353 encoder.setByteBuffer(buffer); 354 355 if (header != null) { 356 encoder.writeObject(header); 357 } 358 if (daMap != null) { 359 encoder.writeObject(new DeliveryAnnotations(daMap)); 360 } 361 if (maMap != null) { 362 encoder.writeObject(new MessageAnnotations(maMap)); 363 } 364 if (properties != null) { 365 encoder.writeObject(properties); 366 } 367 if (apMap != null) { 368 encoder.writeObject(new ApplicationProperties(apMap)); 369 } 370 if (body != null) { 371 encoder.writeObject(body); 372 } 373 if (footerMap != null) { 374 encoder.writeObject(new Footer(footerMap)); 375 } 376 377 return new EncodedMessage(messageFormat, buffer.getArray(), 0, buffer.getArrayLength()); 378 } 379 380 private Section convertBody(ActiveMQMessage message) throws JMSException { 381 382 Section body = null; 383 short orignalEncoding = AMQP_UNKNOWN; 384 385 try { 386 orignalEncoding = message.getShortProperty(JMS_AMQP_ORIGINAL_ENCODING); 387 } catch (Exception ex) { 388 // Ignore and stick with UNKNOWN 389 } 390 391 int messageType = message.getDataStructureType(); 392 393 if (messageType == CommandTypes.ACTIVEMQ_MESSAGE) { 394 Object data = message.getDataStructure(); 395 if (data instanceof ConnectionInfo) { 396 ConnectionInfo connectionInfo = (ConnectionInfo)data; 397 final HashMap<String, Object> connectionMap = new LinkedHashMap<String, Object>(); 398 399 connectionMap.put("ConnectionId", connectionInfo.getConnectionId().getValue()); 400 connectionMap.put("ClientId", connectionInfo.getClientId()); 401 connectionMap.put("ClientIp", connectionInfo.getClientIp()); 402 connectionMap.put("UserName", connectionInfo.getUserName()); 403 connectionMap.put("BrokerMasterConnector", connectionInfo.isBrokerMasterConnector()); 404 connectionMap.put("Manageable", connectionInfo.isManageable()); 405 connectionMap.put("ClientMaster", connectionInfo.isClientMaster()); 406 connectionMap.put("FaultTolerant", connectionInfo.isFaultTolerant()); 407 connectionMap.put("FailoverReconnect", connectionInfo.isFailoverReconnect()); 408 409 body = new AmqpValue(connectionMap); 410 } else if (data instanceof RemoveInfo) { 411 RemoveInfo removeInfo = (RemoveInfo)message.getDataStructure(); 412 final HashMap<String, Object> removeMap = new LinkedHashMap<String, Object>(); 413 414 if (removeInfo.isConnectionRemove()) { 415 removeMap.put(ConnectionId.class.getSimpleName(), ((ConnectionId)removeInfo.getObjectId()).getValue()); 416 } else if (removeInfo.isConsumerRemove()) { 417 removeMap.put(ConsumerId.class.getSimpleName(), ((ConsumerId)removeInfo.getObjectId()).getValue()); 418 removeMap.put("SessionId", ((ConsumerId)removeInfo.getObjectId()).getSessionId()); 419 removeMap.put("ConnectionId", ((ConsumerId)removeInfo.getObjectId()).getConnectionId()); 420 removeMap.put("ParentId", ((ConsumerId)removeInfo.getObjectId()).getParentId()); 421 } 422 423 body = new AmqpValue(removeMap); 424 } 425 } else if (messageType == CommandTypes.ACTIVEMQ_BYTES_MESSAGE) { 426 Binary payload = getBinaryFromMessageBody((ActiveMQBytesMessage) message); 427 428 if (payload == null) { 429 payload = EMPTY_BINARY; 430 } 431 432 switch (orignalEncoding) { 433 case AMQP_NULL: 434 break; 435 case AMQP_VALUE_BINARY: 436 body = new AmqpValue(payload); 437 break; 438 case AMQP_DATA: 439 case AMQP_UNKNOWN: 440 default: 441 body = new Data(payload); 442 break; 443 } 444 } else if (messageType == CommandTypes.ACTIVEMQ_TEXT_MESSAGE) { 445 switch (orignalEncoding) { 446 case AMQP_NULL: 447 break; 448 case AMQP_DATA: 449 body = new Data(getBinaryFromMessageBody((ActiveMQTextMessage) message)); 450 break; 451 case AMQP_VALUE_STRING: 452 case AMQP_UNKNOWN: 453 default: 454 body = new AmqpValue(((TextMessage) message).getText()); 455 break; 456 } 457 } else if (messageType == CommandTypes.ACTIVEMQ_MAP_MESSAGE) { 458 body = new AmqpValue(getMapFromMessageBody((ActiveMQMapMessage) message)); 459 } else if (messageType == CommandTypes.ACTIVEMQ_STREAM_MESSAGE) { 460 ArrayList<Object> list = new ArrayList<>(); 461 final ActiveMQStreamMessage m = (ActiveMQStreamMessage) message; 462 try { 463 while (true) { 464 list.add(m.readObject()); 465 } 466 } catch (MessageEOFException e) { 467 } 468 469 switch (orignalEncoding) { 470 case AMQP_SEQUENCE: 471 body = new AmqpSequence(list); 472 break; 473 case AMQP_VALUE_LIST: 474 case AMQP_UNKNOWN: 475 default: 476 body = new AmqpValue(list); 477 break; 478 } 479 } else if (messageType == CommandTypes.ACTIVEMQ_OBJECT_MESSAGE) { 480 Binary payload = getBinaryFromMessageBody((ActiveMQObjectMessage) message); 481 482 if (payload == null) { 483 payload = EMPTY_BINARY; 484 } 485 486 switch (orignalEncoding) { 487 case AMQP_VALUE_BINARY: 488 body = new AmqpValue(payload); 489 break; 490 case AMQP_DATA: 491 case AMQP_UNKNOWN: 492 default: 493 body = new Data(payload); 494 break; 495 } 496 497 // For a non-AMQP message we tag the outbound content type as containing 498 // a serialized Java object so that an AMQP client has a hint as to what 499 // we are sending it. 500 if (!message.propertyExists(JMS_AMQP_CONTENT_TYPE)) { 501 message.setReadOnlyProperties(false); 502 message.setStringProperty(JMS_AMQP_CONTENT_TYPE, SERIALIZED_JAVA_OBJECT_CONTENT_TYPE); 503 message.setReadOnlyProperties(true); 504 } 505 } 506 507 return body; 508 } 509 510 private static byte destinationType(ActiveMQDestination destination) { 511 if (destination.isQueue()) { 512 if (destination.isTemporary()) { 513 return TEMP_QUEUE_TYPE; 514 } else { 515 return QUEUE_TYPE; 516 } 517 } else if (destination.isTopic()) { 518 if (destination.isTemporary()) { 519 return TEMP_TOPIC_TYPE; 520 } else { 521 return TOPIC_TYPE; 522 } 523 } 524 525 throw new IllegalArgumentException("Unknown Destination Type passed to JMS Transformer."); 526 } 527 528 private static Object getOriginalMessageId(ActiveMQMessage message) { 529 Object result; 530 MessageId messageId = message.getMessageId(); 531 if (messageId.getTextView() != null) { 532 try { 533 result = AMQPMessageIdHelper.INSTANCE.toIdObject(messageId.getTextView()); 534 } catch (AmqpProtocolException e) { 535 result = messageId.getTextView(); 536 } 537 } else { 538 result = messageId.toString(); 539 } 540 541 return result; 542 } 543}