This closes #820

This commit is contained in:
Clebert Suconic 2016-10-07 10:45:23 -04:00
commit 67f804054d
30 changed files with 4475 additions and 949 deletions

View File

@ -21,7 +21,6 @@ import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import io.netty.buffer.ByteBuf;
import org.apache.activemq.artemis.api.core.ActiveMQException;
import org.apache.activemq.artemis.api.core.ActiveMQQueueExistsException;
import org.apache.activemq.artemis.api.core.SimpleString;
@ -37,6 +36,7 @@ import org.apache.activemq.artemis.core.server.ServerSession;
import org.apache.activemq.artemis.core.server.impl.ServerConsumerImpl;
import org.apache.activemq.artemis.core.transaction.Transaction;
import org.apache.activemq.artemis.jms.client.ActiveMQConnection;
import org.apache.activemq.artemis.protocol.amqp.converter.ProtonMessageConverter;
import org.apache.activemq.artemis.protocol.amqp.converter.message.EncodedMessage;
import org.apache.activemq.artemis.protocol.amqp.exceptions.ActiveMQAMQPException;
import org.apache.activemq.artemis.protocol.amqp.exceptions.ActiveMQAMQPInternalErrorException;
@ -59,9 +59,10 @@ import org.apache.qpid.proton.amqp.messaging.Accepted;
import org.apache.qpid.proton.amqp.messaging.Rejected;
import org.apache.qpid.proton.amqp.transport.AmqpError;
import org.apache.qpid.proton.amqp.transport.ErrorCondition;
import org.apache.qpid.proton.codec.WritableBuffer;
import org.apache.qpid.proton.engine.Delivery;
import org.apache.qpid.proton.engine.Receiver;
import org.apache.qpid.proton.message.ProtonJMessage;
import io.netty.buffer.ByteBuf;
import org.jboss.logging.Logger;
public class AMQPSessionCallback implements SessionCallback {
@ -259,8 +260,11 @@ public class AMQPSessionCallback implements SessionCallback {
}
}
public ProtonJMessage encodeMessage(Object message, int deliveryCount) throws Exception {
return (ProtonJMessage) manager.getConverter().outbound((ServerMessage) message, deliveryCount);
public long encodeMessage(Object message, int deliveryCount, WritableBuffer buffer) throws Exception {
ProtonMessageConverter converter = (ProtonMessageConverter) manager.getConverter();
// The Proton variant accepts a WritableBuffer to allow for a faster more direct encode.
return (long) converter.outbound((ServerMessage) message, deliveryCount, buffer);
}
public String tempQueueName() {

View File

@ -1,148 +0,0 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.artemis.protocol.amqp.converter;
import javax.jms.BytesMessage;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.ObjectMessage;
import javax.jms.StreamMessage;
import javax.jms.TextMessage;
import org.apache.activemq.artemis.core.buffers.impl.ResetLimitWrappedActiveMQBuffer;
import org.apache.activemq.artemis.core.server.ServerMessage;
import org.apache.activemq.artemis.core.server.impl.ServerMessageImpl;
import org.apache.activemq.artemis.jms.client.ActiveMQDestination;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerDestination;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSBytesMessage;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSMapMessage;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSMessage;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSObjectMessage;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSStreamMessage;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSTextMessage;
import org.apache.activemq.artemis.protocol.amqp.converter.message.JMSVendor;
import org.apache.activemq.artemis.utils.IDGenerator;
public class ActiveMQJMSVendor implements JMSVendor {
private final IDGenerator serverGenerator;
ActiveMQJMSVendor(IDGenerator idGenerator) {
this.serverGenerator = idGenerator;
}
@Override
public BytesMessage createBytesMessage() {
return new ServerJMSBytesMessage(newMessage(org.apache.activemq.artemis.api.core.Message.BYTES_TYPE), 0);
}
@Override
public StreamMessage createStreamMessage() {
return new ServerJMSStreamMessage(newMessage(org.apache.activemq.artemis.api.core.Message.STREAM_TYPE), 0);
}
@Override
public Message createMessage() {
return new ServerJMSMessage(newMessage(org.apache.activemq.artemis.api.core.Message.DEFAULT_TYPE), 0);
}
@Override
public TextMessage createTextMessage() {
return new ServerJMSTextMessage(newMessage(org.apache.activemq.artemis.api.core.Message.TEXT_TYPE), 0);
}
@Override
public ObjectMessage createObjectMessage() {
return new ServerJMSObjectMessage(newMessage(org.apache.activemq.artemis.api.core.Message.OBJECT_TYPE), 0);
}
@Override
public MapMessage createMapMessage() {
return new ServerJMSMapMessage(newMessage(org.apache.activemq.artemis.api.core.Message.MAP_TYPE), 0);
}
@Override
public void setJMSXUserID(Message message, String s) {
}
@Override
public Destination createDestination(String name) {
return new ServerDestination(name);
}
@Override
public void setJMSXGroupID(Message message, String s) {
try {
message.setStringProperty("_AMQ_GROUP_ID", s);
} catch (JMSException e) {
throw new RuntimeException(e);
}
}
@Override
public void setJMSXGroupSequence(Message message, int i) {
try {
message.setIntProperty("JMSXGroupSeq", i);
} catch (JMSException e) {
throw new RuntimeException(e);
}
}
@Override
public void setJMSXDeliveryCount(Message message, long l) {
try {
message.setLongProperty("JMSXDeliveryCount", l);
} catch (JMSException e) {
throw new RuntimeException(e);
}
}
public ServerJMSMessage wrapMessage(int messageType, ServerMessage wrapped, int deliveryCount) {
switch (messageType) {
case org.apache.activemq.artemis.api.core.Message.STREAM_TYPE:
return new ServerJMSStreamMessage(wrapped, deliveryCount);
case org.apache.activemq.artemis.api.core.Message.BYTES_TYPE:
return new ServerJMSBytesMessage(wrapped, deliveryCount);
case org.apache.activemq.artemis.api.core.Message.MAP_TYPE:
return new ServerJMSMapMessage(wrapped, deliveryCount);
case org.apache.activemq.artemis.api.core.Message.TEXT_TYPE:
return new ServerJMSTextMessage(wrapped, deliveryCount);
case org.apache.activemq.artemis.api.core.Message.OBJECT_TYPE:
return new ServerJMSObjectMessage(wrapped, deliveryCount);
default:
return new ServerJMSMessage(wrapped, deliveryCount);
}
}
@Override
public String toAddress(Destination destination) {
if (destination instanceof ActiveMQDestination) {
return ((ActiveMQDestination) destination).getAddress();
}
return null;
}
private ServerMessageImpl newMessage(byte messageType) {
ServerMessageImpl message = new ServerMessageImpl(serverGenerator.generateID(), 512);
message.setType(messageType);
((ResetLimitWrappedActiveMQBuffer) message.getBodyBuffer()).setMessage(null);
return message;
}
}

View File

@ -16,91 +16,86 @@
*/
package org.apache.activemq.artemis.protocol.amqp.converter;
import javax.jms.BytesMessage;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.JMS_AMQP_NATIVE;
import java.io.IOException;
import javax.jms.BytesMessage;
import org.apache.activemq.artemis.core.client.ActiveMQClientLogger;
import org.apache.activemq.artemis.core.server.ServerMessage;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSBytesMessage;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSMessage;
import org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport;
import org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPNativeOutboundTransformer;
import org.apache.activemq.artemis.protocol.amqp.converter.message.EncodedMessage;
import org.apache.activemq.artemis.protocol.amqp.converter.message.InboundTransformer;
import org.apache.activemq.artemis.protocol.amqp.converter.message.JMSMappingInboundTransformer;
import org.apache.activemq.artemis.protocol.amqp.converter.message.JMSMappingOutboundTransformer;
import org.apache.activemq.artemis.protocol.amqp.converter.message.OutboundTransformer;
import org.apache.activemq.artemis.protocol.amqp.util.NettyWritable;
import org.apache.activemq.artemis.spi.core.protocol.MessageConverter;
import org.apache.activemq.artemis.utils.IDGenerator;
import org.apache.qpid.proton.codec.WritableBuffer;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
public class ProtonMessageConverter implements MessageConverter {
ActiveMQJMSVendor activeMQJMSVendor;
private final String prefixVendor;
public ProtonMessageConverter(IDGenerator idGenerator) {
activeMQJMSVendor = new ActiveMQJMSVendor(idGenerator);
inboundTransformer = new JMSMappingInboundTransformer(activeMQJMSVendor);
outboundTransformer = new JMSMappingOutboundTransformer(activeMQJMSVendor);
prefixVendor = outboundTransformer.getPrefixVendor();
inboundTransformer = new JMSMappingInboundTransformer(idGenerator);
outboundTransformer = new JMSMappingOutboundTransformer(idGenerator);
}
private final InboundTransformer inboundTransformer;
private final JMSMappingOutboundTransformer outboundTransformer;
private final OutboundTransformer outboundTransformer;
@Override
public ServerMessage inbound(Object messageSource) throws Exception {
ServerJMSMessage jmsMessage = inboundJMSType((EncodedMessage) messageSource);
return (ServerMessage) jmsMessage.getInnerMessage();
}
/**
* Just create the JMS Part of the inbound (for testing)
*
* @param messageSource
* @return
* @throws Exception https://issues.jboss.org/browse/ENTMQ-1560
*/
public ServerJMSMessage inboundJMSType(EncodedMessage messageSource) throws Exception {
EncodedMessage encodedMessageSource = messageSource;
EncodedMessage encodedMessageSource = (EncodedMessage) messageSource;
ServerJMSMessage transformedMessage = null;
InboundTransformer transformer = inboundTransformer;
while (transformer != null) {
try {
transformedMessage = (ServerJMSMessage) transformer.transform(encodedMessageSource);
break;
transformedMessage = inboundTransformer.transform(encodedMessageSource);
} catch (Exception e) {
ActiveMQClientLogger.LOGGER.debug("Transform of message using [{}] transformer, failed" + inboundTransformer.getTransformerName());
ActiveMQClientLogger.LOGGER.trace("Transformation error:", e);
transformer = transformer.getFallbackTransformer();
}
}
if (transformedMessage == null) {
throw new IOException("Failed to transform incoming delivery, skipping.");
}
transformedMessage.encode();
return transformedMessage;
return (ServerMessage) transformedMessage.getInnerMessage();
}
@Override
public Object outbound(ServerMessage messageOutbound, int deliveryCount) throws Exception {
ServerJMSMessage jmsMessage = activeMQJMSVendor.wrapMessage(messageOutbound.getType(), messageOutbound, deliveryCount);
// Useful for testing but not recommended for real life use.
ByteBuf nettyBuffer = Unpooled.buffer(1024);
NettyWritable buffer = new NettyWritable(nettyBuffer);
long messageFormat = (long) outbound(messageOutbound, deliveryCount, buffer);
EncodedMessage encoded = new EncodedMessage(messageFormat, nettyBuffer.array(), nettyBuffer.arrayOffset() + nettyBuffer.readerIndex(),
nettyBuffer.readableBytes());
return encoded;
}
public Object outbound(ServerMessage messageOutbound, int deliveryCount, WritableBuffer buffer) throws Exception {
ServerJMSMessage jmsMessage = AMQPMessageSupport.wrapMessage(messageOutbound.getType(), messageOutbound, deliveryCount);
jmsMessage.decode();
if (jmsMessage.getBooleanProperty(prefixVendor + "NATIVE")) {
if (jmsMessage.getBooleanProperty(JMS_AMQP_NATIVE)) {
if (jmsMessage instanceof BytesMessage) {
return AMQPNativeOutboundTransformer.transform(outboundTransformer, (BytesMessage) jmsMessage);
return AMQPNativeOutboundTransformer.transform(outboundTransformer, (ServerJMSBytesMessage) jmsMessage, buffer);
} else {
return null;
return 0;
}
} else {
return outboundTransformer.convert(jmsMessage);
return outboundTransformer.transform(jmsMessage, buffer);
}
}
}

View File

@ -16,12 +16,13 @@
*/
package org.apache.activemq.artemis.protocol.amqp.converter.jms;
import java.util.Collections;
import java.util.Enumeration;
import javax.jms.DeliveryMode;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import java.util.Collections;
import java.util.Enumeration;
import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
import org.apache.activemq.artemis.api.core.ActiveMQException;
@ -47,6 +48,10 @@ public class ServerJMSMessage implements Message {
this.deliveryCount = deliveryCount;
}
public int getDeliveryCount() {
return deliveryCount;
}
private ActiveMQBuffer readBodyBuffer;
/**

View File

@ -16,31 +16,20 @@
*/
package org.apache.activemq.artemis.protocol.amqp.converter.jms;
import java.io.Serializable;
import javax.jms.JMSException;
import javax.jms.ObjectMessage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import org.apache.activemq.artemis.api.core.Message;
import org.apache.activemq.artemis.core.message.impl.MessageInternal;
import org.apache.activemq.artemis.utils.ObjectInputStreamWithClassLoader;
import org.apache.qpid.proton.amqp.Binary;
public class ServerJMSObjectMessage extends ServerJMSMessage implements ObjectMessage {
private static final String DEFAULT_WHITELIST;
private static final String DEFAULT_BLACKLIST;
public static final byte TYPE = Message.OBJECT_TYPE;
static {
DEFAULT_WHITELIST = System.getProperty(ObjectInputStreamWithClassLoader.WHITELIST_PROPERTY, "java.lang,java.math,javax.security,java.util,org.apache.activemq,org.apache.qpid.proton.amqp");
DEFAULT_BLACKLIST = System.getProperty(ObjectInputStreamWithClassLoader.BLACKLIST_PROPERTY, null);
}
public static final byte TYPE = Message.STREAM_TYPE;
private Serializable object;
private Binary payload;
public ServerJMSObjectMessage(MessageInternal message, int deliveryCount) {
super(message, deliveryCount);
@ -48,23 +37,27 @@ public class ServerJMSObjectMessage extends ServerJMSMessage implements ObjectMe
@Override
public void setObject(Serializable object) throws JMSException {
this.object = object;
throw new UnsupportedOperationException("Cannot set Object on this internal message");
}
@Override
public Serializable getObject() throws JMSException {
return object;
throw new UnsupportedOperationException("Cannot set Object on this internal message");
}
public void setSerializedForm(Binary payload) {
this.payload = payload;
}
public Binary getSerializedForm() {
return payload;
}
@Override
public void encode() throws Exception {
super.encode();
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream ous = new ObjectOutputStream(out);
ous.writeObject(object);
byte[] src = out.toByteArray();
getInnerMessage().getBodyBuffer().writeInt(src.length);
getInnerMessage().getBodyBuffer().writeBytes(src);
getInnerMessage().getBodyBuffer().writeInt(payload.getLength());
getInnerMessage().getBodyBuffer().writeBytes(payload.getArray(), payload.getArrayOffset(), payload.getLength());
}
@Override
@ -73,10 +66,6 @@ public class ServerJMSObjectMessage extends ServerJMSMessage implements ObjectMe
int size = getInnerMessage().getBodyBuffer().readInt();
byte[] bytes = new byte[size];
getInnerMessage().getBodyBuffer().readBytes(bytes);
try (ObjectInputStreamWithClassLoader ois = new ObjectInputStreamWithClassLoader(new ByteArrayInputStream(bytes))) {
ois.setWhiteList(DEFAULT_WHITELIST);
ois.setBlackList(DEFAULT_BLACKLIST);
object = (Serializable) ois.readObject();
}
payload = new Binary(bytes);
}
}

View File

@ -0,0 +1,146 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.artemis.protocol.amqp.converter.message;
import java.nio.charset.Charset;
import java.nio.charset.IllegalCharsetNameException;
import java.nio.charset.StandardCharsets;
import java.nio.charset.UnsupportedCharsetException;
import java.util.StringTokenizer;
import org.apache.activemq.artemis.protocol.amqp.exceptions.ActiveMQAMQPInvalidContentTypeException;
public final class AMQPContentTypeSupport {
private static final String UTF_8 = "UTF-8";
private static final String CHARSET = "charset";
private static final String TEXT = "text";
private static final String APPLICATION = "application";
private static final String JAVASCRIPT = "javascript";
private static final String XML = "xml";
private static final String XML_VARIANT = "+xml";
private static final String JSON = "json";
private static final String JSON_VARIANT = "+json";
private static final String XML_DTD = "xml-dtd";
private static final String ECMASCRIPT = "ecmascript";
/**
* @param contentType
* the contentType of the received message
* @return the character set to use, or null if not to treat the message as text
* @throws ActiveMQAMQPInvalidContentTypeException
* if the content-type is invalid in some way.
*/
public static Charset parseContentTypeForTextualCharset(final String contentType) throws ActiveMQAMQPInvalidContentTypeException {
if (contentType == null || contentType.trim().isEmpty()) {
throw new ActiveMQAMQPInvalidContentTypeException("Content type can't be null or empty");
}
int subTypeSeparator = contentType.indexOf("/");
if (subTypeSeparator == -1) {
throw new ActiveMQAMQPInvalidContentTypeException("Content type has no '/' separator: " + contentType);
}
final String type = contentType.substring(0, subTypeSeparator).toLowerCase().trim();
String subTypePart = contentType.substring(subTypeSeparator + 1).toLowerCase().trim();
String parameterPart = null;
int parameterSeparator = subTypePart.indexOf(";");
if (parameterSeparator != -1) {
if (parameterSeparator < subTypePart.length() - 1) {
parameterPart = contentType.substring(subTypeSeparator + 1).toLowerCase().trim();
}
subTypePart = subTypePart.substring(0, parameterSeparator).trim();
}
if (subTypePart.isEmpty()) {
throw new ActiveMQAMQPInvalidContentTypeException("Content type has no subtype after '/'" + contentType);
}
final String subType = subTypePart;
if (isTextual(type, subType)) {
String charset = findCharset(parameterPart);
if (charset == null) {
charset = UTF_8;
}
if (UTF_8.equals(charset)) {
return StandardCharsets.UTF_8;
} else {
try {
return Charset.forName(charset);
} catch (IllegalCharsetNameException icne) {
throw new ActiveMQAMQPInvalidContentTypeException("Illegal charset: " + charset);
} catch (UnsupportedCharsetException uce) {
throw new ActiveMQAMQPInvalidContentTypeException("Unsupported charset: " + charset);
}
}
}
return null;
}
// ----- Internal Content Type utilities ----------------------------------//
private static boolean isTextual(String type, String subType) {
if (TEXT.equals(type)) {
return true;
}
if (APPLICATION.equals(type)) {
if (XML.equals(subType) || JSON.equals(subType) || JAVASCRIPT.equals(subType) || subType.endsWith(XML_VARIANT) || subType.endsWith(JSON_VARIANT)
|| XML_DTD.equals(subType) || ECMASCRIPT.equals(subType)) {
return true;
}
}
return false;
}
private static String findCharset(String paramaterPart) {
String charset = null;
if (paramaterPart != null) {
StringTokenizer tokenizer = new StringTokenizer(paramaterPart, ";");
while (tokenizer.hasMoreTokens()) {
String parameter = tokenizer.nextToken().trim();
int eqIndex = parameter.indexOf('=');
if (eqIndex != -1) {
String name = parameter.substring(0, eqIndex);
if (CHARSET.equalsIgnoreCase(name.trim())) {
String value = unquote(parameter.substring(eqIndex + 1));
charset = value.toUpperCase();
break;
}
}
}
}
return charset;
}
private static String unquote(String s) {
if (s.length() > 1 && (s.startsWith("\"") && s.endsWith("\""))) {
return s.substring(1, s.length() - 1);
} else {
return s;
}
}
}

View File

@ -28,24 +28,29 @@ import org.apache.qpid.proton.amqp.Binary;
import org.apache.qpid.proton.amqp.UnsignedLong;
/**
* Helper class for identifying and converting message-id and correlation-id values between
* the AMQP types and the Strings values used by JMS.
* Helper class for identifying and converting message-id and correlation-id values between the
* AMQP types and the Strings values used by JMS.
* <p>
* <p>AMQP messages allow for 4 types of message-id/correlation-id: message-id-string, message-id-binary,
* message-id-uuid, or message-id-ulong. In order to accept or return a string representation of these
* for interoperability with other AMQP clients, the following encoding can be used after removing or
* before adding the "ID:" prefix used for a JMSMessageID value:<br>
* <p>
* AMQP messages allow for 4 types of message-id/correlation-id: message-id-string,
* message-id-binary, message-id-uuid, or message-id-ulong. In order to accept or return a
* string representation of these for interoperability with other AMQP clients, the following
* encoding can be used after removing or before adding the "ID:" prefix used for a JMSMessageID
* value:<br>
* <p>
* {@literal "AMQP_BINARY:<hex representation of binary content>"}<br>
* {@literal "AMQP_UUID:<string representation of uuid>"}<br>
* {@literal "AMQP_ULONG:<string representation of ulong>"}<br>
* {@literal "AMQP_STRING:<string>"}<br>
* <p>
* <p>The AMQP_STRING encoding exists only for escaping message-id-string values that happen to begin
* with one of the encoding prefixes (including AMQP_STRING itself). It MUST NOT be used otherwise.
* <p>
* <p>When provided a string for conversion which attempts to identify itself as an encoded binary, uuid, or
* ulong but can't be converted into the indicated format, an exception will be thrown.
* The AMQP_STRING encoding exists only for escaping message-id-string values that happen to
* begin with one of the encoding prefixes (including AMQP_STRING itself). It MUST NOT be used
* otherwise.
* <p>
* <p>
* When provided a string for conversion which attempts to identify itself as an encoded binary,
* uuid, or ulong but can't be converted into the indicated format, an exception will be thrown.
*/
public class AMQPMessageIdHelper {
@ -63,11 +68,12 @@ public class AMQPMessageIdHelper {
private static final char[] HEX_CHARS = "0123456789ABCDEF".toCharArray();
/**
* Takes the provided AMQP messageId style object, and convert it to a base string.
* Encodes type information as a prefix where necessary to convey or escape the type
* of the provided object.
* Takes the provided AMQP messageId style object, and convert it to a base string. Encodes
* type information as a prefix where necessary to convey or escape the type of the provided
* object.
*
* @param messageId the raw messageId object to process
* @param messageId
* the raw messageId object to process
* @return the base string to be used in creating the actual id.
*/
public String toBaseMessageIdString(Object messageId) {
@ -106,9 +112,12 @@ public class AMQPMessageIdHelper {
* Takes the provided base id string and return the appropriate amqp messageId style object.
* Converts the type based on any relevant encoding information found as a prefix.
*
* @param baseId the object to be converted to an AMQP MessageId value.
* @param baseId
* the object to be converted to an AMQP MessageId value.
* @return the AMQP messageId style object
* @throws ActiveMQAMQPIllegalStateException if the provided baseId String indicates an encoded type but can't be converted to that type.
* @throws ActiveMQAMQPIllegalStateException
* if the provided baseId String indicates an encoded type but can't be converted to
* that type.
*/
public Object toIdObject(String baseId) throws ActiveMQAMQPIllegalStateException {
if (baseId == null) {
@ -143,15 +152,17 @@ public class AMQPMessageIdHelper {
* <p>
* The hex characters may be upper or lower case.
*
* @param hexString string to convert to a binary value.
* @param hexString
* string to convert to a binary value.
* @return a byte array containing the binary representation
* @throws IllegalArgumentException if the provided String is a non-even length or contains
* non-hex characters
* @throws IllegalArgumentException
* if the provided String is a non-even length or contains non-hex characters
*/
public byte[] convertHexStringToBinary(String hexString) throws IllegalArgumentException {
int length = hexString.length();
// As each byte needs two characters in the hex encoding, the string must be an even length.
// As each byte needs two characters in the hex encoding, the string must be an even
// length.
if (length % 2 != 0) {
throw new IllegalArgumentException("The provided hex String must be an even length, but was of length " + length + ": " + hexString);
}
@ -177,7 +188,8 @@ public class AMQPMessageIdHelper {
* <p>
* The returned hex characters are upper-case.
*
* @param bytes the binary value to convert to a hex String instance.
* @param bytes
* the binary value to convert to a hex String instance.
* @return a String containing a hex representation of the bytes
*/
public String convertBinaryToHexString(byte[] bytes) {
@ -201,8 +213,7 @@ public class AMQPMessageIdHelper {
// ----- Internal implementation ------------------------------------------//
private boolean hasTypeEncodingPrefix(String stringId) {
return hasAmqpBinaryPrefix(stringId) || hasAmqpUuidPrefix(stringId) ||
hasAmqpUlongPrefix(stringId) || hasAmqpStringPrefix(stringId);
return hasAmqpBinaryPrefix(stringId) || hasAmqpUuidPrefix(stringId) || hasAmqpUlongPrefix(stringId) || hasAmqpStringPrefix(stringId);
}
private boolean hasAmqpStringPrefix(String stringId) {

View File

@ -0,0 +1,272 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.artemis.protocol.amqp.converter.message;
import static org.apache.activemq.artemis.api.core.Message.BYTES_TYPE;
import static org.apache.activemq.artemis.api.core.Message.DEFAULT_TYPE;
import static org.apache.activemq.artemis.api.core.Message.MAP_TYPE;
import static org.apache.activemq.artemis.api.core.Message.OBJECT_TYPE;
import static org.apache.activemq.artemis.api.core.Message.STREAM_TYPE;
import static org.apache.activemq.artemis.api.core.Message.TEXT_TYPE;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Map;
import java.util.Set;
import javax.jms.Destination;
import javax.jms.JMSException;
import org.apache.activemq.artemis.core.buffers.impl.ResetLimitWrappedActiveMQBuffer;
import org.apache.activemq.artemis.core.server.ServerMessage;
import org.apache.activemq.artemis.core.server.impl.ServerMessageImpl;
import org.apache.activemq.artemis.jms.client.ActiveMQDestination;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSBytesMessage;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSMapMessage;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSMessage;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSObjectMessage;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSStreamMessage;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSTextMessage;
import org.apache.activemq.artemis.protocol.amqp.exceptions.ActiveMQAMQPInvalidContentTypeException;
import org.apache.activemq.artemis.utils.IDGenerator;
import org.apache.qpid.proton.amqp.Binary;
import org.apache.qpid.proton.amqp.Symbol;
import org.apache.qpid.proton.amqp.messaging.Data;
import org.apache.qpid.proton.message.Message;
/**
* Support class containing constant values and static methods that are used to map to / from
* AMQP Message types being sent or received.
*/
public final class AMQPMessageSupport {
// Message Properties used to map AMQP to JMS and back
public static final String JMS_AMQP_PREFIX = "JMS_AMQP_";
public static final int JMS_AMQP_PREFIX_LENGTH = JMS_AMQP_PREFIX.length();
public static final String MESSAGE_FORMAT = "MESSAGE_FORMAT";
public static final String ORIGINAL_ENCODING = "ORIGINAL_ENCODING";
public static final String NATIVE = "NATIVE";
public static final String HEADER = "HEADER";
public static final String PROPERTIES = "PROPERTIES";
public static final String FIRST_ACQUIRER = "FirstAcquirer";
public static final String CONTENT_TYPE = "ContentType";
public static final String CONTENT_ENCODING = "ContentEncoding";
public static final String REPLYTO_GROUP_ID = "ReplyToGroupID";
public static final String DELIVERY_ANNOTATION_PREFIX = "DA_";
public static final String MESSAGE_ANNOTATION_PREFIX = "MA_";
public static final String FOOTER_PREFIX = "FT_";
public static final String JMS_AMQP_HEADER = JMS_AMQP_PREFIX + HEADER;
public static final String JMS_AMQP_PROPERTIES = JMS_AMQP_PREFIX + PROPERTIES;
public static final String JMS_AMQP_ORIGINAL_ENCODING = JMS_AMQP_PREFIX + ORIGINAL_ENCODING;
public static final String JMS_AMQP_MESSAGE_FORMAT = JMS_AMQP_PREFIX + MESSAGE_FORMAT;
public static final String JMS_AMQP_NATIVE = JMS_AMQP_PREFIX + NATIVE;
public static final String JMS_AMQP_FIRST_ACQUIRER = JMS_AMQP_PREFIX + FIRST_ACQUIRER;
public static final String JMS_AMQP_CONTENT_TYPE = JMS_AMQP_PREFIX + CONTENT_TYPE;
public static final String JMS_AMQP_CONTENT_ENCODING = JMS_AMQP_PREFIX + CONTENT_ENCODING;
public static final String JMS_AMQP_REPLYTO_GROUP_ID = JMS_AMQP_PREFIX + REPLYTO_GROUP_ID;
public static final String JMS_AMQP_DELIVERY_ANNOTATION_PREFIX = JMS_AMQP_PREFIX + DELIVERY_ANNOTATION_PREFIX;
public static final String JMS_AMQP_MESSAGE_ANNOTATION_PREFIX = JMS_AMQP_PREFIX + MESSAGE_ANNOTATION_PREFIX;
public static final String JMS_AMQP_FOOTER_PREFIX = JMS_AMQP_PREFIX + FOOTER_PREFIX;
// Message body type definitions
public static final Binary EMPTY_BINARY = new Binary(new byte[0]);
public static final Data EMPTY_BODY = new Data(EMPTY_BINARY);
public static final short AMQP_UNKNOWN = 0;
public static final short AMQP_NULL = 1;
public static final short AMQP_DATA = 2;
public static final short AMQP_SEQUENCE = 3;
public static final short AMQP_VALUE_NULL = 4;
public static final short AMQP_VALUE_STRING = 5;
public static final short AMQP_VALUE_BINARY = 6;
public static final short AMQP_VALUE_MAP = 7;
public static final short AMQP_VALUE_LIST = 8;
/**
* Content type used to mark Data sections as containing a serialized java object.
*/
public static final String SERIALIZED_JAVA_OBJECT_CONTENT_TYPE = "application/x-java-serialized-object";
/**
* Content type used to mark Data sections as containing arbitrary bytes.
*/
public static final String OCTET_STREAM_CONTENT_TYPE = "application/octet-stream";
/**
* Lookup and return the correct Proton Symbol instance based on the given key.
*
* @param key
* the String value name of the Symbol to locate.
*
* @return the Symbol value that matches the given key.
*/
public static Symbol getSymbol(String key) {
return Symbol.valueOf(key);
}
/**
* Safe way to access message annotations which will check internal structure and either
* return the annotation if it exists or null if the annotation or any annotations are
* present.
*
* @param key
* the String key to use to lookup an annotation.
* @param message
* the AMQP message object that is being examined.
*
* @return the given annotation value or null if not present in the message.
*/
public static Object getMessageAnnotation(String key, Message message) {
if (message != null && message.getMessageAnnotations() != null) {
Map<Symbol, Object> annotations = message.getMessageAnnotations().getValue();
return annotations.get(AMQPMessageSupport.getSymbol(key));
}
return null;
}
/**
* Check whether the content-type field of the properties section (if present) in the given
* message matches the provided string (where null matches if there is no content type
* present.
*
* @param contentType
* content type string to compare against, or null if none
* @param message
* the AMQP message object that is being examined.
*
* @return true if content type matches
*/
public static boolean isContentType(String contentType, Message message) {
if (contentType == null) {
return message.getContentType() == null;
} else {
return contentType.equals(message.getContentType());
}
}
/**
* @param contentType
* the contentType of the received message
* @return the character set to use, or null if not to treat the message as text
*/
public static Charset getCharsetForTextualContent(String contentType) {
try {
return AMQPContentTypeSupport.parseContentTypeForTextualCharset(contentType);
} catch (ActiveMQAMQPInvalidContentTypeException e) {
return null;
}
}
public static ServerJMSMessage wrapMessage(int messageType, ServerMessage wrapped, int deliveryCount) {
switch (messageType) {
case STREAM_TYPE:
return new ServerJMSStreamMessage(wrapped, deliveryCount);
case BYTES_TYPE:
return new ServerJMSBytesMessage(wrapped, deliveryCount);
case MAP_TYPE:
return new ServerJMSMapMessage(wrapped, deliveryCount);
case TEXT_TYPE:
return new ServerJMSTextMessage(wrapped, deliveryCount);
case OBJECT_TYPE:
return new ServerJMSObjectMessage(wrapped, deliveryCount);
default:
return new ServerJMSMessage(wrapped, deliveryCount);
}
}
public static String toAddress(Destination destination) {
if (destination instanceof ActiveMQDestination) {
return ((ActiveMQDestination) destination).getAddress();
}
return null;
}
public static ServerJMSBytesMessage createBytesMessage(IDGenerator idGenerator) {
return new ServerJMSBytesMessage(newMessage(idGenerator, BYTES_TYPE), 0);
}
public static ServerJMSMessage createBytesMessage(IDGenerator idGenerator, byte[] array, int arrayOffset, int length) throws JMSException {
ServerJMSBytesMessage message = createBytesMessage(idGenerator);
message.writeBytes(array, arrayOffset, length);
return message;
}
public static ServerJMSStreamMessage createStreamMessage(IDGenerator idGenerator) {
return new ServerJMSStreamMessage(newMessage(idGenerator, STREAM_TYPE), 0);
}
public static ServerJMSMessage createMessage(IDGenerator idGenerator) {
return new ServerJMSMessage(newMessage(idGenerator, DEFAULT_TYPE), 0);
}
public static ServerJMSTextMessage createTextMessage(IDGenerator idGenerator) {
return new ServerJMSTextMessage(newMessage(idGenerator, TEXT_TYPE), 0);
}
public static ServerJMSTextMessage createTextMessage(IDGenerator idGenerator, String text) throws JMSException {
ServerJMSTextMessage message = createTextMessage(idGenerator);
message.setText(text);
return message;
}
public static ServerJMSObjectMessage createObjectMessage(IDGenerator idGenerator) {
return new ServerJMSObjectMessage(newMessage(idGenerator, OBJECT_TYPE), 0);
}
public static ServerJMSMessage createObjectMessage(IDGenerator idGenerator, Binary serializedForm) throws JMSException {
ServerJMSObjectMessage message = createObjectMessage(idGenerator);
message.setSerializedForm(serializedForm);
return message;
}
public static ServerJMSMessage createObjectMessage(IDGenerator idGenerator, byte[] array, int offset, int length) throws JMSException {
ServerJMSObjectMessage message = createObjectMessage(idGenerator);
message.setSerializedForm(new Binary(array, offset, length));
return message;
}
public static ServerJMSMapMessage createMapMessage(IDGenerator idGenerator) {
return new ServerJMSMapMessage(newMessage(idGenerator, MAP_TYPE), 0);
}
public static ServerJMSMapMessage createMapMessage(IDGenerator idGenerator, Map<String, Object> content) throws JMSException {
ServerJMSMapMessage message = createMapMessage(idGenerator);
final Set<Map.Entry<String, Object>> set = content.entrySet();
for (Map.Entry<String, Object> entry : set) {
Object value = entry.getValue();
if (value instanceof Binary) {
Binary binary = (Binary) value;
value = Arrays.copyOfRange(binary.getArray(), binary.getArrayOffset(), binary.getLength());
}
message.setObject(entry.getKey(), value);
}
return message;
}
private static ServerMessageImpl newMessage(IDGenerator idGenerator, byte messageType) {
ServerMessageImpl message = new ServerMessageImpl(idGenerator.generateID(), 512);
message.setType(messageType);
((ResetLimitWrappedActiveMQBuffer) message.getBodyBuffer()).setMessage(null);
return message;
}
}

View File

@ -16,8 +16,12 @@
*/
package org.apache.activemq.artemis.protocol.amqp.converter.message;
@Deprecated
public class AMQPMessageTypes {
// TODO - Remove in future release as these are no longer used by the
// inbound JMS Transformer.
public static final String AMQP_TYPE_KEY = "amqp:type";
public static final String AMQP_SEQUENCE = "amqp:sequence";

View File

@ -1,13 +1,13 @@
/**
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* <p>
*
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -16,12 +16,13 @@
*/
package org.apache.activemq.artemis.protocol.amqp.converter.message;
import javax.jms.Message;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSMessage;
import org.apache.activemq.artemis.utils.IDGenerator;
public class AMQPNativeInboundTransformer extends AMQPRawInboundTransformer {
public AMQPNativeInboundTransformer(JMSVendor vendor) {
super(vendor);
public AMQPNativeInboundTransformer(IDGenerator idGenerator) {
super(idGenerator);
}
@Override
@ -31,16 +32,13 @@ public class AMQPNativeInboundTransformer extends AMQPRawInboundTransformer {
@Override
public InboundTransformer getFallbackTransformer() {
return new AMQPRawInboundTransformer(getVendor());
return new AMQPRawInboundTransformer(idGenerator);
}
@Override
public Message transform(EncodedMessage amqpMessage) throws Exception {
public ServerJMSMessage transform(EncodedMessage amqpMessage) throws Exception {
org.apache.qpid.proton.message.Message amqp = amqpMessage.decode();
Message rc = super.transform(amqpMessage);
populateMessage(rc, amqp);
return rc;
return populateMessage(super.transform(amqpMessage), amqp);
}
}

View File

@ -1,13 +1,13 @@
/**
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* <p>
*
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -16,24 +16,41 @@
*/
package org.apache.activemq.artemis.protocol.amqp.converter.message;
import javax.jms.BytesMessage;
import java.io.UnsupportedEncodingException;
import javax.jms.JMSException;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSBytesMessage;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSMessage;
import org.apache.activemq.artemis.utils.IDGenerator;
import org.apache.qpid.proton.amqp.UnsignedInteger;
import org.apache.qpid.proton.amqp.messaging.Header;
import org.apache.qpid.proton.codec.WritableBuffer;
import org.apache.qpid.proton.message.ProtonJMessage;
public class AMQPNativeOutboundTransformer extends OutboundTransformer {
public AMQPNativeOutboundTransformer(JMSVendor vendor) {
super(vendor);
public AMQPNativeOutboundTransformer(IDGenerator idGenerator) {
super(idGenerator);
}
public static ProtonJMessage transform(OutboundTransformer options, BytesMessage msg) throws JMSException {
byte[] data = new byte[(int) msg.getBodyLength()];
msg.readBytes(data);
msg.reset();
int count = msg.getIntProperty("JMSXDeliveryCount");
@Override
public long transform(ServerJMSMessage message, WritableBuffer buffer) throws JMSException, UnsupportedEncodingException {
if (message == null || !(message instanceof ServerJMSBytesMessage)) {
return 0;
}
return transform(this, (ServerJMSBytesMessage) message, buffer);
}
public static long transform(OutboundTransformer options, ServerJMSBytesMessage message, WritableBuffer buffer) throws JMSException {
byte[] data = new byte[(int) message.getBodyLength()];
message.readBytes(data);
message.reset();
// The AMQP delivery-count field only includes prior failed delivery attempts,
int amqpDeliveryCount = message.getDeliveryCount() - 1;
if (amqpDeliveryCount >= 1) {
// decode...
ProtonJMessage amqp = (ProtonJMessage) org.apache.qpid.proton.message.Message.Factory.create();
@ -46,15 +63,18 @@ public class AMQPNativeOutboundTransformer extends OutboundTransformer {
len -= decoded;
}
// Update the DeliveryCount header...
// The AMQP delivery-count field only includes prior failed delivery attempts,
// whereas JMSXDeliveryCount includes the first/current delivery attempt. Subtract 1.
if (amqp.getHeader() == null) {
// Update the DeliveryCount header which might require adding a Header
if (amqp.getHeader() == null && amqpDeliveryCount > 0) {
amqp.setHeader(new Header());
}
amqp.getHeader().setDeliveryCount(new UnsignedInteger(count - 1));
amqp.getHeader().setDeliveryCount(new UnsignedInteger(amqpDeliveryCount));
return amqp;
amqp.encode(buffer);
} else {
buffer.put(data, 0, data.length);
}
return 0;
}
}

View File

@ -1,13 +1,13 @@
/**
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* <p>
*
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -16,14 +16,21 @@
*/
package org.apache.activemq.artemis.protocol.amqp.converter.message;
import javax.jms.BytesMessage;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.JMS_AMQP_MESSAGE_FORMAT;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.JMS_AMQP_NATIVE;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.createBytesMessage;
import javax.jms.DeliveryMode;
import javax.jms.Message;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSBytesMessage;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSMessage;
import org.apache.activemq.artemis.utils.IDGenerator;
public class AMQPRawInboundTransformer extends InboundTransformer {
public AMQPRawInboundTransformer(JMSVendor vendor) {
super(vendor);
public AMQPRawInboundTransformer(IDGenerator idGenerator) {
super(idGenerator);
}
@Override
@ -37,24 +44,19 @@ public class AMQPRawInboundTransformer extends InboundTransformer {
}
@Override
public Message transform(EncodedMessage amqpMessage) throws Exception {
BytesMessage rc = vendor.createBytesMessage();
rc.writeBytes(amqpMessage.getArray(), amqpMessage.getArrayOffset(), amqpMessage.getLength());
public ServerJMSMessage transform(EncodedMessage amqpMessage) throws Exception {
ServerJMSBytesMessage message = createBytesMessage(idGenerator);
message.writeBytes(amqpMessage.getArray(), amqpMessage.getArrayOffset(), amqpMessage.getLength());
// We cannot decode the message headers to check so err on the side of caution
// and mark all messages as persistent.
rc.setJMSDeliveryMode(DeliveryMode.PERSISTENT);
rc.setJMSPriority(defaultPriority);
message.setJMSDeliveryMode(DeliveryMode.PERSISTENT);
message.setJMSPriority(Message.DEFAULT_PRIORITY);
message.setJMSTimestamp(System.currentTimeMillis());
final long now = System.currentTimeMillis();
rc.setJMSTimestamp(now);
if (defaultTtl > 0) {
rc.setJMSExpiration(now + defaultTtl);
}
message.setLongProperty(JMS_AMQP_MESSAGE_FORMAT, amqpMessage.getMessageFormat());
message.setBooleanProperty(JMS_AMQP_NATIVE, true);
rc.setLongProperty(prefixVendor + "MESSAGE_FORMAT", amqpMessage.getMessageFormat());
rc.setBooleanProperty(prefixVendor + "NATIVE", true);
return rc;
return message;
}
}

View File

@ -1,13 +1,13 @@
/**
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* <p>
*
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

View File

@ -16,13 +16,26 @@
*/
package org.apache.activemq.artemis.protocol.amqp.converter.message;
import javax.jms.DeliveryMode;
import javax.jms.JMSException;
import javax.jms.Message;
import static org.apache.activemq.artemis.api.core.Message.HDR_SCHEDULED_DELIVERY_TIME;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.JMS_AMQP_CONTENT_ENCODING;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.JMS_AMQP_CONTENT_TYPE;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.JMS_AMQP_FIRST_ACQUIRER;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.JMS_AMQP_FOOTER_PREFIX;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.JMS_AMQP_HEADER;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.JMS_AMQP_MESSAGE_ANNOTATION_PREFIX;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.JMS_AMQP_REPLYTO_GROUP_ID;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Set;
import javax.jms.DeliveryMode;
import javax.jms.JMSException;
import javax.jms.Message;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerDestination;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSMessage;
import org.apache.activemq.artemis.utils.IDGenerator;
import org.apache.qpid.proton.amqp.Binary;
import org.apache.qpid.proton.amqp.Decimal128;
import org.apache.qpid.proton.amqp.Decimal32;
@ -38,109 +51,61 @@ import org.apache.qpid.proton.amqp.messaging.Header;
import org.apache.qpid.proton.amqp.messaging.MessageAnnotations;
import org.apache.qpid.proton.amqp.messaging.Properties;
import static org.apache.activemq.artemis.api.core.Message.HDR_SCHEDULED_DELIVERY_TIME;
public abstract class InboundTransformer {
JMSVendor vendor;
protected IDGenerator idGenerator;
public static final String TRANSFORMER_NATIVE = "native";
public static final String TRANSFORMER_RAW = "raw";
public static final String TRANSFORMER_JMS = "jms";
String prefixVendor = "JMS_AMQP_";
String prefixDeliveryAnnotations = "DA_";
String prefixMessageAnnotations = "MA_";
String prefixFooter = "FT_";
int defaultDeliveryMode = DeliveryMode.NON_PERSISTENT;
int defaultPriority = Message.DEFAULT_PRIORITY;
long defaultTtl = Message.DEFAULT_TIME_TO_LIVE;
public InboundTransformer(JMSVendor vendor) {
this.vendor = vendor;
public InboundTransformer(IDGenerator idGenerator) {
this.idGenerator = idGenerator;
}
public abstract Message transform(EncodedMessage amqpMessage) throws Exception;
public abstract ServerJMSMessage transform(EncodedMessage amqpMessage) throws Exception;
public abstract String getTransformerName();
public abstract InboundTransformer getFallbackTransformer();
public int getDefaultDeliveryMode() {
return defaultDeliveryMode;
}
public void setDefaultDeliveryMode(int defaultDeliveryMode) {
this.defaultDeliveryMode = defaultDeliveryMode;
}
public int getDefaultPriority() {
return defaultPriority;
}
public void setDefaultPriority(int defaultPriority) {
this.defaultPriority = defaultPriority;
}
public long getDefaultTtl() {
return defaultTtl;
}
public void setDefaultTtl(long defaultTtl) {
this.defaultTtl = defaultTtl;
}
public String getPrefixVendor() {
return prefixVendor;
}
public void setPrefixVendor(String prefixVendor) {
this.prefixVendor = prefixVendor;
}
public JMSVendor getVendor() {
return vendor;
}
public void setVendor(JMSVendor vendor) {
this.vendor = vendor;
}
protected void populateMessage(Message jms, org.apache.qpid.proton.message.Message amqp) throws Exception {
@SuppressWarnings("unchecked")
protected ServerJMSMessage populateMessage(ServerJMSMessage jms, org.apache.qpid.proton.message.Message amqp) throws Exception {
Header header = amqp.getHeader();
if (header == null) {
header = new Header();
}
if (header != null) {
jms.setBooleanProperty(JMS_AMQP_HEADER, true);
if (header.getDurable() != null) {
jms.setJMSDeliveryMode(header.getDurable().booleanValue() ? DeliveryMode.PERSISTENT : DeliveryMode.NON_PERSISTENT);
} else {
jms.setJMSDeliveryMode(defaultDeliveryMode);
jms.setJMSDeliveryMode(Message.DEFAULT_DELIVERY_MODE);
}
if (header.getPriority() != null) {
jms.setJMSPriority(header.getPriority().intValue());
} else {
jms.setJMSPriority(defaultPriority);
jms.setJMSPriority(Message.DEFAULT_PRIORITY);
}
if (header.getFirstAcquirer() != null) {
jms.setBooleanProperty(prefixVendor + "FirstAcquirer", header.getFirstAcquirer());
jms.setBooleanProperty(JMS_AMQP_FIRST_ACQUIRER, header.getFirstAcquirer());
}
if (header.getDeliveryCount() != null) {
vendor.setJMSXDeliveryCount(jms, header.getDeliveryCount().longValue());
// AMQP Delivery Count counts only failed delivers where JMS
// Delivery Count should include the original delivery in the count.
jms.setLongProperty("JMSXDeliveryCount", header.getDeliveryCount().longValue() + 1);
}
} else {
jms.setJMSPriority((byte) Message.DEFAULT_PRIORITY);
jms.setJMSDeliveryMode(DeliveryMode.NON_PERSISTENT);
}
final MessageAnnotations ma = amqp.getMessageAnnotations();
if (ma != null) {
for (Map.Entry<?, ?> entry : ma.getValue().entrySet()) {
String key = entry.getKey().toString();
if ("x-opt-jms-type".equals(key) && entry.getValue() != null) {
// Legacy annotation, JMSType value will be replaced by Subject further down if also present.
jms.setJMSType(entry.getValue().toString());
} else if ("x-opt-delivery-time".equals(key) && entry.getValue() != null) {
if ("x-opt-delivery-time".equals(key) && entry.getValue() != null) {
long deliveryTime = ((Number) entry.getValue()).longValue();
jms.setLongProperty(HDR_SCHEDULED_DELIVERY_TIME.toString(), deliveryTime);
} else if ("x-opt-delivery-delay".equals(key) && entry.getValue() != null) {
@ -149,41 +114,15 @@ public abstract class InboundTransformer {
jms.setLongProperty(HDR_SCHEDULED_DELIVERY_TIME.toString(), System.currentTimeMillis() + delay);
}
}
//todo
/*else if ("x-opt-delivery-repeat".equals(key) && entry.getValue() != null) {
int repeat = ((Number) entry.getValue()).intValue();
if (repeat > 0) {
jms.setIntProperty(ScheduledMessage.AMQ_SCHEDULED_REPEAT, repeat);
}
} else if ("x-opt-delivery-period".equals(key) && entry.getValue() != null) {
long period = ((Number) entry.getValue()).longValue();
if (period > 0) {
jms.setLongProperty(ScheduledMessage.AMQ_SCHEDULED_PERIOD, period);
}
} else if ("x-opt-delivery-cron".equals(key) && entry.getValue() != null) {
String cronEntry = (String) entry.getValue();
if (cronEntry != null) {
jms.setStringProperty(ScheduledMessage.AMQ_SCHEDULED_CRON, cronEntry);
}
}*/
setProperty(jms, prefixVendor + prefixMessageAnnotations + key, entry.getValue());
setProperty(jms, JMS_AMQP_MESSAGE_ANNOTATION_PREFIX + key, entry.getValue());
}
}
final ApplicationProperties ap = amqp.getApplicationProperties();
if (ap != null) {
for (Map.Entry<Object, Object> entry : (Set<Map.Entry<Object, Object>>) ap.getValue().entrySet()) {
String key = entry.getKey().toString();
if ("JMSXGroupID".equals(key)) {
vendor.setJMSXGroupID(jms, entry.getValue().toString());
} else if ("JMSXGroupSequence".equals(key)) {
vendor.setJMSXGroupSequence(jms, ((Number) entry.getValue()).intValue());
} else if ("JMSXUserID".equals(key)) {
vendor.setJMSXUserID(jms, entry.getValue().toString());
} else {
setProperty(jms, key, entry.getValue());
}
setProperty(jms, entry.getKey().toString(), entry.getValue());
}
}
@ -194,37 +133,38 @@ public abstract class InboundTransformer {
}
Binary userId = properties.getUserId();
if (userId != null) {
vendor.setJMSXUserID(jms, new String(userId.getArray(), userId.getArrayOffset(), userId.getLength(), StandardCharsets.UTF_8));
// TODO - Better Way to set this?
jms.setStringProperty("JMSXUserID", new String(userId.getArray(), userId.getArrayOffset(), userId.getLength(), StandardCharsets.UTF_8));
}
if (properties.getTo() != null) {
jms.setJMSDestination(vendor.createDestination(properties.getTo()));
jms.setJMSDestination(new ServerDestination(properties.getTo()));
}
if (properties.getSubject() != null) {
jms.setJMSType(properties.getSubject());
}
if (properties.getReplyTo() != null) {
jms.setJMSReplyTo(vendor.createDestination(properties.getReplyTo()));
jms.setJMSReplyTo(new ServerDestination(properties.getReplyTo()));
}
if (properties.getCorrelationId() != null) {
jms.setJMSCorrelationID(AMQPMessageIdHelper.INSTANCE.toBaseMessageIdString(properties.getCorrelationId()));
}
if (properties.getContentType() != null) {
jms.setStringProperty(prefixVendor + "ContentType", properties.getContentType().toString());
jms.setStringProperty(JMS_AMQP_CONTENT_TYPE, properties.getContentType().toString());
}
if (properties.getContentEncoding() != null) {
jms.setStringProperty(prefixVendor + "ContentEncoding", properties.getContentEncoding().toString());
jms.setStringProperty(JMS_AMQP_CONTENT_ENCODING, properties.getContentEncoding().toString());
}
if (properties.getCreationTime() != null) {
jms.setJMSTimestamp(properties.getCreationTime().getTime());
}
if (properties.getGroupId() != null) {
vendor.setJMSXGroupID(jms, properties.getGroupId());
jms.setStringProperty("_AMQ_GROUP_ID", properties.getGroupId());
}
if (properties.getGroupSequence() != null) {
vendor.setJMSXGroupSequence(jms, properties.getGroupSequence().intValue());
jms.setIntProperty("JMSXGroupSeq", properties.getGroupSequence().intValue());
}
if (properties.getReplyToGroupId() != null) {
jms.setStringProperty(prefixVendor + "ReplyToGroupID", properties.getReplyToGroupId());
jms.setStringProperty(JMS_AMQP_REPLYTO_GROUP_ID, properties.getReplyToGroupId());
}
if (properties.getAbsoluteExpiryTime() != null) {
jms.setJMSExpiration(properties.getAbsoluteExpiryTime().getTime());
@ -232,9 +172,9 @@ public abstract class InboundTransformer {
}
// If the jms expiration has not yet been set...
if (jms.getJMSExpiration() == 0) {
if (header != null && jms.getJMSExpiration() == 0) {
// Then lets try to set it based on the message ttl.
long ttl = defaultTtl;
long ttl = Message.DEFAULT_TIME_TO_LIVE;
if (header.getTtl() != null) {
ttl = header.getTtl().longValue();
}
@ -250,9 +190,11 @@ public abstract class InboundTransformer {
if (fp != null) {
for (Map.Entry<Object, Object> entry : (Set<Map.Entry<Object, Object>>) fp.getValue().entrySet()) {
String key = entry.getKey().toString();
setProperty(jms, prefixVendor + prefixFooter + key, entry.getValue());
setProperty(jms, JMS_AMQP_FOOTER_PREFIX + key, entry.getValue());
}
}
return jms;
}
private void setProperty(Message msg, String key, Object value) throws JMSException {

View File

@ -1,13 +1,13 @@
/**
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* <p>
*
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -16,27 +16,50 @@
*/
package org.apache.activemq.artemis.protocol.amqp.converter.message;
import javax.jms.BytesMessage;
import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.ObjectMessage;
import javax.jms.StreamMessage;
import javax.jms.TextMessage;
import java.io.Serializable;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.AMQP_DATA;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.AMQP_NULL;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.AMQP_SEQUENCE;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.AMQP_VALUE_BINARY;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.AMQP_VALUE_LIST;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.AMQP_VALUE_MAP;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.AMQP_VALUE_NULL;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.AMQP_VALUE_STRING;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.JMS_AMQP_MESSAGE_FORMAT;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.JMS_AMQP_ORIGINAL_ENCODING;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.OCTET_STREAM_CONTENT_TYPE;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.SERIALIZED_JAVA_OBJECT_CONTENT_TYPE;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.createBytesMessage;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.createMapMessage;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.createMessage;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.createObjectMessage;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.createStreamMessage;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.createTextMessage;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.getCharsetForTextualContent;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.isContentType;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSMessage;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSStreamMessage;
import org.apache.activemq.artemis.protocol.amqp.exceptions.ActiveMQAMQPInternalErrorException;
import org.apache.activemq.artemis.utils.IDGenerator;
import org.apache.qpid.proton.amqp.Binary;
import org.apache.qpid.proton.amqp.messaging.AmqpSequence;
import org.apache.qpid.proton.amqp.messaging.AmqpValue;
import org.apache.qpid.proton.amqp.messaging.Data;
import org.apache.qpid.proton.amqp.messaging.Section;
import org.apache.qpid.proton.message.Message;
public class JMSMappingInboundTransformer extends InboundTransformer {
public JMSMappingInboundTransformer(JMSVendor vendor) {
super(vendor);
public JMSMappingInboundTransformer(IDGenerator idGenerator) {
super(idGenerator);
}
@Override
@ -46,75 +69,128 @@ public class JMSMappingInboundTransformer extends InboundTransformer {
@Override
public InboundTransformer getFallbackTransformer() {
return new AMQPNativeInboundTransformer(getVendor());
return new AMQPNativeInboundTransformer(idGenerator);
}
@SuppressWarnings({"unchecked"})
@Override
public Message transform(EncodedMessage amqpMessage) throws Exception {
org.apache.qpid.proton.message.Message amqp = amqpMessage.decode();
public ServerJMSMessage transform(EncodedMessage encodedMessage) throws Exception {
ServerJMSMessage transformedMessage = null;
try {
Message amqpMessage = encodedMessage.decode();
transformedMessage = createServerMessage(amqpMessage);
populateMessage(transformedMessage, amqpMessage);
} catch (Exception ex) {
InboundTransformer transformer = this.getFallbackTransformer();
while (transformer != null) {
try {
transformedMessage = transformer.transform(encodedMessage);
break;
} catch (Exception e) {
transformer = transformer.getFallbackTransformer();
}
}
}
// Regardless of the transformer that finally decoded the message we need to ensure that
// the AMQP Message Format value is preserved for application on retransmit.
if (transformedMessage != null && encodedMessage.getMessageFormat() != 0) {
transformedMessage.setLongProperty(JMS_AMQP_MESSAGE_FORMAT, encodedMessage.getMessageFormat());
}
return transformedMessage;
}
@SuppressWarnings("unchecked")
private ServerJMSMessage createServerMessage(Message message) throws Exception {
Section body = message.getBody();
ServerJMSMessage result;
Message rc;
final Section body = amqp.getBody();
if (body == null) {
rc = vendor.createMessage();
if (isContentType(SERIALIZED_JAVA_OBJECT_CONTENT_TYPE, message)) {
result = createObjectMessage(idGenerator);
} else if (isContentType(OCTET_STREAM_CONTENT_TYPE, message) || isContentType(null, message)) {
result = createBytesMessage(idGenerator);
} else {
Charset charset = getCharsetForTextualContent(message.getContentType());
if (charset != null) {
result = createTextMessage(idGenerator);
} else {
result = createMessage(idGenerator);
}
}
result.setShortProperty(JMS_AMQP_ORIGINAL_ENCODING, AMQP_NULL);
} else if (body instanceof Data) {
Binary d = ((Data) body).getValue();
BytesMessage m = vendor.createBytesMessage();
m.writeBytes(d.getArray(), d.getArrayOffset(), d.getLength());
rc = m;
Binary payload = ((Data) body).getValue();
if (isContentType(SERIALIZED_JAVA_OBJECT_CONTENT_TYPE, message)) {
result = createObjectMessage(idGenerator, payload.getArray(), payload.getArrayOffset(), payload.getLength());
} else if (isContentType(OCTET_STREAM_CONTENT_TYPE, message)) {
result = createBytesMessage(idGenerator, payload.getArray(), payload.getArrayOffset(), payload.getLength());
} else {
Charset charset = getCharsetForTextualContent(message.getContentType());
if (StandardCharsets.UTF_8.equals(charset)) {
ByteBuffer buf = ByteBuffer.wrap(payload.getArray(), payload.getArrayOffset(), payload.getLength());
try {
CharBuffer chars = charset.newDecoder().decode(buf);
result = createTextMessage(idGenerator, String.valueOf(chars));
} catch (CharacterCodingException e) {
result = createBytesMessage(idGenerator, payload.getArray(), payload.getArrayOffset(), payload.getLength());
}
} else {
result = createBytesMessage(idGenerator, payload.getArray(), payload.getArrayOffset(), payload.getLength());
}
}
result.setShortProperty(JMS_AMQP_ORIGINAL_ENCODING, AMQP_DATA);
} else if (body instanceof AmqpSequence) {
AmqpSequence sequence = (AmqpSequence) body;
StreamMessage m = vendor.createStreamMessage();
ServerJMSStreamMessage m = createStreamMessage(idGenerator);
for (Object item : sequence.getValue()) {
m.writeObject(item);
}
rc = m;
m.setStringProperty(AMQPMessageTypes.AMQP_TYPE_KEY, AMQPMessageTypes.AMQP_SEQUENCE);
result = m;
result.setShortProperty(JMS_AMQP_ORIGINAL_ENCODING, AMQP_SEQUENCE);
} else if (body instanceof AmqpValue) {
Object value = ((AmqpValue) body).getValue();
if (value == null) {
rc = vendor.createObjectMessage();
}
if (value instanceof String) {
TextMessage m = vendor.createTextMessage();
m.setText((String) value);
rc = m;
if (value == null || value instanceof String) {
result = createTextMessage(idGenerator, (String) value);
result.setShortProperty(JMS_AMQP_ORIGINAL_ENCODING, value == null ? AMQP_VALUE_NULL : AMQP_VALUE_STRING);
} else if (value instanceof Binary) {
Binary d = (Binary) value;
BytesMessage m = vendor.createBytesMessage();
m.writeBytes(d.getArray(), d.getArrayOffset(), d.getLength());
rc = m;
Binary payload = (Binary) value;
if (isContentType(SERIALIZED_JAVA_OBJECT_CONTENT_TYPE, message)) {
result = createObjectMessage(idGenerator, payload);
} else {
result = createBytesMessage(idGenerator, payload.getArray(), payload.getArrayOffset(), payload.getLength());
}
result.setShortProperty(JMS_AMQP_ORIGINAL_ENCODING, AMQP_VALUE_BINARY);
} else if (value instanceof List) {
StreamMessage m = vendor.createStreamMessage();
ServerJMSStreamMessage m = createStreamMessage(idGenerator);
for (Object item : (List<Object>) value) {
m.writeObject(item);
}
rc = m;
m.setStringProperty(AMQPMessageTypes.AMQP_TYPE_KEY, AMQPMessageTypes.AMQP_LIST);
result = m;
result.setShortProperty(JMS_AMQP_ORIGINAL_ENCODING, AMQP_VALUE_LIST);
} else if (value instanceof Map) {
MapMessage m = vendor.createMapMessage();
final Set<Map.Entry<String, Object>> set = ((Map<String, Object>) value).entrySet();
for (Map.Entry<String, Object> entry : set) {
m.setObject(entry.getKey(), entry.getValue());
}
rc = m;
result = createMapMessage(idGenerator, (Map<String, Object>) value);
result.setShortProperty(JMS_AMQP_ORIGINAL_ENCODING, AMQP_VALUE_MAP);
} else {
ObjectMessage m = vendor.createObjectMessage();
m.setObject((Serializable) value);
rc = m;
// Trigger fall-back to native encoder which generates BytesMessage with the
// original message stored in the message body.
throw new ActiveMQAMQPInternalErrorException("Unable to encode to ActiveMQ JMS Message");
}
} else {
throw new RuntimeException("Unexpected body type: " + body.getClass());
}
rc.setJMSDeliveryMode(defaultDeliveryMode);
rc.setJMSPriority(defaultPriority);
rc.setJMSExpiration(defaultTtl);
populateMessage(rc, amqp);
rc.setLongProperty(prefixVendor + "MESSAGE_FORMAT", amqpMessage.getMessageFormat());
rc.setBooleanProperty(prefixVendor + "NATIVE", false);
return rc;
return result;
}
}

View File

@ -1,13 +1,13 @@
/**
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* <p>
*
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -16,30 +16,61 @@
*/
package org.apache.activemq.artemis.protocol.amqp.converter.message;
import javax.jms.BytesMessage;
import javax.jms.DeliveryMode;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.MessageEOFException;
import javax.jms.ObjectMessage;
import javax.jms.Queue;
import javax.jms.StreamMessage;
import javax.jms.TemporaryQueue;
import javax.jms.TemporaryTopic;
import javax.jms.TextMessage;
import javax.jms.Topic;
import static org.apache.activemq.artemis.api.core.Message.HDR_SCHEDULED_DELIVERY_TIME;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.AMQP_DATA;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.AMQP_NULL;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.AMQP_SEQUENCE;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.AMQP_UNKNOWN;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.AMQP_VALUE_BINARY;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.AMQP_VALUE_LIST;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.AMQP_VALUE_STRING;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.EMPTY_BINARY;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.JMS_AMQP_CONTENT_ENCODING;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.JMS_AMQP_CONTENT_TYPE;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.JMS_AMQP_DELIVERY_ANNOTATION_PREFIX;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.JMS_AMQP_FIRST_ACQUIRER;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.JMS_AMQP_FOOTER_PREFIX;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.JMS_AMQP_HEADER;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.JMS_AMQP_MESSAGE_ANNOTATION_PREFIX;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.JMS_AMQP_MESSAGE_FORMAT;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.JMS_AMQP_NATIVE;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.JMS_AMQP_ORIGINAL_ENCODING;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.JMS_AMQP_PREFIX;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.JMS_AMQP_PROPERTIES;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.JMS_AMQP_REPLYTO_GROUP_ID;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.SERIALIZED_JAVA_OBJECT_CONTENT_TYPE;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.toAddress;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageEOFException;
import javax.jms.Queue;
import javax.jms.TemporaryQueue;
import javax.jms.TemporaryTopic;
import javax.jms.TextMessage;
import javax.jms.Topic;
import org.apache.activemq.artemis.core.message.impl.MessageInternal;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSBytesMessage;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSMapMessage;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSMessage;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSObjectMessage;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSStreamMessage;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSTextMessage;
import org.apache.activemq.artemis.protocol.amqp.exceptions.ActiveMQAMQPIllegalStateException;
import org.apache.activemq.artemis.reader.MessageUtil;
import org.apache.activemq.artemis.utils.IDGenerator;
import org.apache.qpid.proton.amqp.Binary;
import org.apache.qpid.proton.amqp.Symbol;
import org.apache.qpid.proton.amqp.UnsignedByte;
@ -54,12 +85,16 @@ import org.apache.qpid.proton.amqp.messaging.Header;
import org.apache.qpid.proton.amqp.messaging.MessageAnnotations;
import org.apache.qpid.proton.amqp.messaging.Properties;
import org.apache.qpid.proton.amqp.messaging.Section;
import org.apache.qpid.proton.message.ProtonJMessage;
import org.apache.qpid.proton.codec.AMQPDefinedTypes;
import org.apache.qpid.proton.codec.DecoderImpl;
import org.apache.qpid.proton.codec.EncoderImpl;
import org.apache.qpid.proton.codec.WritableBuffer;
import org.jboss.logging.Logger;
public class JMSMappingOutboundTransformer extends OutboundTransformer {
private static final Logger logger = Logger.getLogger(JMSMappingOutboundTransformer.class);
public static final Symbol JMS_DEST_TYPE_MSG_ANNOTATION = Symbol.valueOf("x-opt-jms-dest");
public static final Symbol JMS_REPLY_TO_TYPE_MSG_ANNOTATION = Symbol.valueOf("x-opt-jms-reply-to");
@ -68,51 +103,340 @@ public class JMSMappingOutboundTransformer extends OutboundTransformer {
public static final byte TEMP_QUEUE_TYPE = 0x02;
public static final byte TEMP_TOPIC_TYPE = 0x03;
public JMSMappingOutboundTransformer(JMSVendor vendor) {
super(vendor);
// For now Proton requires that we create a decoder to create an encoder
private static class EncoderDecoderPair {
DecoderImpl decoder = new DecoderImpl();
EncoderImpl encoder = new EncoderImpl(decoder);
{
AMQPDefinedTypes.registerAllTypes(decoder, encoder);
}
}
/**
* Perform the conversion between JMS Message and Proton Message without
* re-encoding it to array. This is needed because some frameworks may elect
* to do this on their own way (Netty for instance using Nettybuffers)
*
* @param msg
* @return
* @throws Exception
*/
public ProtonJMessage convert(Message msg) throws JMSException, UnsupportedEncodingException {
Header header = new Header();
Properties props = new Properties();
HashMap<Symbol, Object> daMap = null;
HashMap<Symbol, Object> maMap = null;
HashMap apMap = null;
private static final ThreadLocal<EncoderDecoderPair> tlsCodec = new ThreadLocal<EncoderDecoderPair>() {
@Override
protected EncoderDecoderPair initialValue() {
return new EncoderDecoderPair();
}
};
public JMSMappingOutboundTransformer(IDGenerator idGenerator) {
super(idGenerator);
}
@Override
public long transform(ServerJMSMessage message, WritableBuffer buffer) throws JMSException, UnsupportedEncodingException {
if (message == null) {
return 0;
}
long messageFormat = 0;
Header header = null;
Properties properties = null;
Map<Symbol, Object> daMap = null;
Map<Symbol, Object> maMap = null;
Map<String, Object> apMap = null;
Map<Object, Object> footerMap = null;
Section body = convertBody(message);
if (message.getInnerMessage().isDurable()) {
if (header == null) {
header = new Header();
}
header.setDurable(true);
}
byte priority = (byte) message.getJMSPriority();
if (priority != Message.DEFAULT_PRIORITY) {
if (header == null) {
header = new Header();
}
header.setPriority(UnsignedByte.valueOf(priority));
}
String type = message.getJMSType();
if (type != null) {
if (properties == null) {
properties = new Properties();
}
properties.setSubject(type);
}
String messageId = message.getJMSMessageID();
if (messageId != null) {
if (properties == null) {
properties = new Properties();
}
try {
properties.setMessageId(AMQPMessageIdHelper.INSTANCE.toIdObject(messageId));
} catch (ActiveMQAMQPIllegalStateException e) {
properties.setMessageId(messageId);
}
}
Destination destination = message.getJMSDestination();
if (destination != null) {
if (properties == null) {
properties = new Properties();
}
properties.setTo(toAddress(destination));
if (maMap == null) {
maMap = new HashMap<Symbol, Object>();
}
maMap.put(JMS_DEST_TYPE_MSG_ANNOTATION, destinationType(destination));
}
Destination replyTo = message.getJMSReplyTo();
if (replyTo != null) {
if (properties == null) {
properties = new Properties();
}
properties.setReplyTo(toAddress(replyTo));
if (maMap == null) {
maMap = new HashMap<Symbol, Object>();
}
maMap.put(JMS_REPLY_TO_TYPE_MSG_ANNOTATION, destinationType(replyTo));
}
String correlationId = message.getJMSCorrelationID();
if (correlationId != null) {
if (properties == null) {
properties = new Properties();
}
try {
properties.setCorrelationId(AMQPMessageIdHelper.INSTANCE.toIdObject(correlationId));
} catch (ActiveMQAMQPIllegalStateException e) {
properties.setCorrelationId(correlationId);
}
}
long expiration = message.getJMSExpiration();
if (expiration != 0) {
long ttl = expiration - System.currentTimeMillis();
if (ttl < 0) {
ttl = 1;
}
if (header == null) {
header = new Header();
}
header.setTtl(new UnsignedInteger((int) ttl));
if (properties == null) {
properties = new Properties();
}
properties.setAbsoluteExpiryTime(new Date(expiration));
}
long timeStamp = message.getJMSTimestamp();
if (timeStamp != 0) {
if (properties == null) {
properties = new Properties();
}
properties.setCreationTime(new Date(timeStamp));
}
final Set<String> keySet = MessageUtil.getPropertyNames(message.getInnerMessage());
for (String key : keySet) {
if (key.startsWith("JMSX")) {
if (key.equals("JMSXDeliveryCount")) {
// The AMQP delivery-count field only includes prior failed delivery attempts,
// whereas JMSXDeliveryCount includes the first/current delivery attempt.
int amqpDeliveryCount = message.getDeliveryCount() - 1;
if (amqpDeliveryCount > 0) {
if (header == null) {
header = new Header();
}
header.setDeliveryCount(new UnsignedInteger(amqpDeliveryCount));
}
continue;
} else if (key.equals("JMSXUserID")) {
String value = message.getStringProperty(key);
if (properties == null) {
properties = new Properties();
}
properties.setUserId(new Binary(value.getBytes(StandardCharsets.UTF_8)));
continue;
} else if (key.equals("JMSXGroupID")) {
String value = message.getStringProperty(key);
if (properties == null) {
properties = new Properties();
}
properties.setGroupId(value);
continue;
} else if (key.equals("JMSXGroupSeq")) {
UnsignedInteger value = new UnsignedInteger(message.getIntProperty(key));
if (properties == null) {
properties = new Properties();
}
properties.setGroupSequence(value);
continue;
}
} else if (key.startsWith(JMS_AMQP_PREFIX)) {
// AMQP Message Information stored from a conversion to the Core Message
if (key.equals(JMS_AMQP_MESSAGE_FORMAT)) {
messageFormat = message.getLongProperty(JMS_AMQP_MESSAGE_FORMAT);
continue;
} else if (key.equals(JMS_AMQP_NATIVE)) {
// skip..internal use only
continue;
} else if (key.equals(JMS_AMQP_ORIGINAL_ENCODING)) {
// skip..internal use only
continue;
} else if (key.equals(JMS_AMQP_FIRST_ACQUIRER)) {
if (header == null) {
header = new Header();
}
header.setFirstAcquirer(message.getBooleanProperty(key));
continue;
} else if (key.equals(JMS_AMQP_HEADER)) {
if (header == null) {
header = new Header();
}
continue;
} else if (key.startsWith(JMS_AMQP_PROPERTIES)) {
if (properties == null) {
properties = new Properties();
}
continue;
} else if (key.startsWith(JMS_AMQP_DELIVERY_ANNOTATION_PREFIX)) {
if (daMap == null) {
daMap = new HashMap<Symbol, Object>();
}
String name = key.substring(JMS_AMQP_DELIVERY_ANNOTATION_PREFIX.length());
daMap.put(Symbol.valueOf(name), message.getObjectProperty(key));
continue;
} else if (key.startsWith(JMS_AMQP_MESSAGE_ANNOTATION_PREFIX)) {
if (maMap == null) {
maMap = new HashMap<Symbol, Object>();
}
String name = key.substring(JMS_AMQP_MESSAGE_ANNOTATION_PREFIX.length());
maMap.put(Symbol.valueOf(name), message.getObjectProperty(key));
continue;
} else if (key.equals(JMS_AMQP_CONTENT_TYPE)) {
if (properties == null) {
properties = new Properties();
}
properties.setContentType(Symbol.getSymbol(message.getStringProperty(key)));
continue;
} else if (key.equals(JMS_AMQP_CONTENT_ENCODING)) {
if (properties == null) {
properties = new Properties();
}
properties.setContentEncoding(Symbol.getSymbol(message.getStringProperty(key)));
continue;
} else if (key.equals(JMS_AMQP_REPLYTO_GROUP_ID)) {
if (properties == null) {
properties = new Properties();
}
properties.setReplyToGroupId(message.getStringProperty(key));
continue;
} else if (key.startsWith(JMS_AMQP_FOOTER_PREFIX)) {
if (footerMap == null) {
footerMap = new HashMap<Object, Object>();
}
String name = key.substring(JMS_AMQP_FOOTER_PREFIX.length());
footerMap.put(name, message.getObjectProperty(key));
continue;
}
} else if (key.equals("_AMQ_GROUP_ID")) {
String value = message.getStringProperty(key);
if (properties == null) {
properties = new Properties();
}
properties.setGroupId(value);
continue;
} else if (key.equals(ServerJMSMessage.NATIVE_MESSAGE_ID)) {
// skip..internal use only
continue;
} else if (key.endsWith(HDR_SCHEDULED_DELIVERY_TIME.toString())) {
// skip..remove annotation from previous inbound transformation
continue;
} else if (key.equals(AMQPMessageTypes.AMQP_TYPE_KEY)) {
// skip..internal use only - TODO - Remove this deprecated value in future release.
continue;
}
if (apMap == null) {
apMap = new HashMap<String, Object>();
}
Object objectProperty = message.getObjectProperty(key);
if (objectProperty instanceof byte[]) {
objectProperty = new Binary((byte[]) objectProperty);
}
apMap.put(key, objectProperty);
}
EncoderImpl encoder = tlsCodec.get().encoder;
encoder.setByteBuffer(buffer);
if (header != null) {
encoder.writeObject(header);
}
if (daMap != null) {
encoder.writeObject(new DeliveryAnnotations(daMap));
}
if (maMap != null) {
encoder.writeObject(new MessageAnnotations(maMap));
}
if (properties != null) {
encoder.writeObject(properties);
}
if (apMap != null) {
encoder.writeObject(new ApplicationProperties(apMap));
}
if (body != null) {
encoder.writeObject(body);
}
if (footerMap != null) {
encoder.writeObject(new Footer(footerMap));
}
return messageFormat;
}
private Section convertBody(ServerJMSMessage message) throws JMSException {
Section body = null;
HashMap footerMap = null;
if (msg instanceof BytesMessage) {
BytesMessage m = (BytesMessage) msg;
byte[] data = new byte[(int) m.getBodyLength()];
m.readBytes(data);
m.reset(); // Need to reset after readBytes or future readBytes
// calls (ex: redeliveries) will fail and return -1
body = new Data(new Binary(data));
short orignalEncoding = AMQP_UNKNOWN;
try {
orignalEncoding = message.getShortProperty(JMS_AMQP_ORIGINAL_ENCODING);
} catch (Exception ex) {
// Ignore and stick with UNKNOWN
}
if (msg instanceof TextMessage) {
body = new AmqpValue(((TextMessage) msg).getText());
if (message instanceof ServerJMSBytesMessage) {
Binary payload = getBinaryFromMessageBody((ServerJMSBytesMessage) message);
if (payload == null) {
payload = EMPTY_BINARY;
}
if (msg instanceof MapMessage) {
final HashMap<String, Object> map = new HashMap<>();
final MapMessage m = (MapMessage) msg;
final Enumeration<String> names = m.getMapNames();
while (names.hasMoreElements()) {
String key = names.nextElement();
map.put(key, m.getObject(key));
switch (orignalEncoding) {
case AMQP_NULL:
break;
case AMQP_VALUE_BINARY:
body = new AmqpValue(payload);
break;
case AMQP_DATA:
case AMQP_UNKNOWN:
default:
body = new Data(payload);
break;
}
body = new AmqpValue(map);
} else if (message instanceof ServerJMSTextMessage) {
switch (orignalEncoding) {
case AMQP_NULL:
break;
case AMQP_DATA:
body = new Data(getBinaryFromMessageBody((ServerJMSTextMessage) message));
break;
case AMQP_VALUE_STRING:
case AMQP_UNKNOWN:
default:
body = new AmqpValue(((TextMessage) message).getText());
break;
}
if (msg instanceof StreamMessage) {
} else if (message instanceof ServerJMSMapMessage) {
body = new AmqpValue(getMapFromMessageBody((ServerJMSMapMessage) message));
} else if (message instanceof ServerJMSStreamMessage) {
ArrayList<Object> list = new ArrayList<>();
final StreamMessage m = (StreamMessage) msg;
final ServerJMSStreamMessage m = (ServerJMSStreamMessage) message;
try {
while (true) {
list.add(m.readObject());
@ -120,21 +444,58 @@ public class JMSMappingOutboundTransformer extends OutboundTransformer {
} catch (MessageEOFException e) {
}
String amqpType = msg.getStringProperty(AMQPMessageTypes.AMQP_TYPE_KEY);
// Deprecated encoding markers - TODO - Remove on future release
if (orignalEncoding == AMQP_UNKNOWN) {
String amqpType = message.getStringProperty(AMQPMessageTypes.AMQP_TYPE_KEY);
if (amqpType != null) {
if (amqpType.equals(AMQPMessageTypes.AMQP_LIST)) {
body = new AmqpValue(list);
orignalEncoding = AMQP_VALUE_LIST;
} else {
orignalEncoding = AMQP_SEQUENCE;
}
}
}
switch (orignalEncoding) {
case AMQP_SEQUENCE:
body = new AmqpSequence(list);
break;
case AMQP_VALUE_LIST:
case AMQP_UNKNOWN:
default:
body = new AmqpValue(list);
break;
}
}
if (msg instanceof ObjectMessage) {
body = new AmqpValue(((ObjectMessage) msg).getObject());
} else if (message instanceof ServerJMSObjectMessage) {
Binary payload = getBinaryFromMessageBody((ServerJMSObjectMessage) message);
if (payload == null) {
payload = EMPTY_BINARY;
}
if (body == null && msg instanceof ServerJMSMessage) {
switch (orignalEncoding) {
case AMQP_VALUE_BINARY:
body = new AmqpValue(payload);
break;
case AMQP_DATA:
case AMQP_UNKNOWN:
default:
body = new Data(payload);
break;
}
MessageInternal internalMessage = ((ServerJMSMessage) msg).getInnerMessage();
if (!internalMessage.containsProperty("AMQP_MESSAGE_FORMAT")) {
// For a non-AMQP message we tag the outbound content type as containing
// a serialized Java object so that an AMQP client has a hint as to what
// we are sending it.
if (!message.propertyExists(JMS_AMQP_CONTENT_TYPE)) {
message.setStringProperty(JMS_AMQP_CONTENT_TYPE, SERIALIZED_JAVA_OBJECT_CONTENT_TYPE);
}
} else if (message instanceof ServerJMSMessage) {
// If this is not an AMQP message that was converted then the original encoding
// will be unknown so we check for special cases of messages with special data
// encoded into the server message body.
if (orignalEncoding == AMQP_UNKNOWN) {
MessageInternal internalMessage = message.getInnerMessage();
int readerIndex = internalMessage.getBodyBuffer().readerIndex();
try {
Object s = internalMessage.getBodyBuffer().readNullableSimpleString();
@ -149,146 +510,51 @@ public class JMSMappingOutboundTransformer extends OutboundTransformer {
}
}
header.setDurable(msg.getJMSDeliveryMode() == DeliveryMode.PERSISTENT ? true : false);
header.setPriority(new UnsignedByte((byte) msg.getJMSPriority()));
if (msg.getJMSType() != null) {
props.setSubject(msg.getJMSType());
}
if (msg.getJMSMessageID() != null) {
String msgId = msg.getJMSMessageID();
try {
props.setMessageId(AMQPMessageIdHelper.INSTANCE.toIdObject(msgId));
} catch (ActiveMQAMQPIllegalStateException e) {
props.setMessageId(msgId);
}
}
if (msg.getJMSDestination() != null) {
props.setTo(vendor.toAddress(msg.getJMSDestination()));
if (maMap == null) {
maMap = new HashMap<>();
}
maMap.put(JMS_DEST_TYPE_MSG_ANNOTATION, destinationType(msg.getJMSDestination()));
}
if (msg.getJMSReplyTo() != null) {
props.setReplyTo(vendor.toAddress(msg.getJMSReplyTo()));
if (maMap == null) {
maMap = new HashMap<>();
}
maMap.put(JMS_REPLY_TO_TYPE_MSG_ANNOTATION, destinationType(msg.getJMSReplyTo()));
}
if (msg.getJMSCorrelationID() != null) {
String correlationId = msg.getJMSCorrelationID();
try {
props.setCorrelationId(AMQPMessageIdHelper.INSTANCE.toIdObject(correlationId));
} catch (ActiveMQAMQPIllegalStateException e) {
props.setCorrelationId(correlationId);
}
}
if (msg.getJMSExpiration() != 0) {
long ttl = msg.getJMSExpiration() - System.currentTimeMillis();
if (ttl < 0) {
ttl = 1;
}
header.setTtl(new UnsignedInteger((int) ttl));
props.setAbsoluteExpiryTime(new Date(msg.getJMSExpiration()));
}
if (msg.getJMSTimestamp() != 0) {
props.setCreationTime(new Date(msg.getJMSTimestamp()));
return body;
}
final Enumeration<String> keys = msg.getPropertyNames();
while (keys.hasMoreElements()) {
String key = keys.nextElement();
if (key.equals(messageFormatKey) || key.equals(nativeKey) || key.equals(ServerJMSMessage.NATIVE_MESSAGE_ID)) {
// skip..
} else if (key.equals(firstAcquirerKey)) {
header.setFirstAcquirer(msg.getBooleanProperty(key));
} else if (key.startsWith("JMSXDeliveryCount")) {
// The AMQP delivery-count field only includes prior failed delivery attempts,
// whereas JMSXDeliveryCount includes the first/current delivery attempt.
int amqpDeliveryCount = msg.getIntProperty(key) - 1;
if (amqpDeliveryCount > 0) {
header.setDeliveryCount(new UnsignedInteger(amqpDeliveryCount));
}
} else if (key.startsWith("JMSXUserID")) {
String value = msg.getStringProperty(key);
props.setUserId(new Binary(value.getBytes(StandardCharsets.UTF_8)));
} else if (key.startsWith("JMSXGroupID") || key.startsWith("_AMQ_GROUP_ID")) {
String value = msg.getStringProperty(key);
props.setGroupId(value);
if (apMap == null) {
apMap = new HashMap();
}
apMap.put(key, value);
} else if (key.startsWith("JMSXGroupSeq")) {
UnsignedInteger value = new UnsignedInteger(msg.getIntProperty(key));
props.setGroupSequence(value);
if (apMap == null) {
apMap = new HashMap();
}
apMap.put(key, value);
} else if (key.startsWith(prefixDeliveryAnnotationsKey)) {
if (daMap == null) {
daMap = new HashMap<>();
}
String name = key.substring(prefixDeliveryAnnotationsKey.length());
daMap.put(Symbol.valueOf(name), msg.getObjectProperty(key));
} else if (key.startsWith(prefixMessageAnnotationsKey)) {
if (maMap == null) {
maMap = new HashMap<>();
}
String name = key.substring(prefixMessageAnnotationsKey.length());
maMap.put(Symbol.valueOf(name), msg.getObjectProperty(key));
} else if (key.equals(contentTypeKey)) {
props.setContentType(Symbol.getSymbol(msg.getStringProperty(key)));
} else if (key.equals(contentEncodingKey)) {
props.setContentEncoding(Symbol.getSymbol(msg.getStringProperty(key)));
} else if (key.equals(replyToGroupIDKey)) {
props.setReplyToGroupId(msg.getStringProperty(key));
} else if (key.startsWith(prefixFooterKey)) {
if (footerMap == null) {
footerMap = new HashMap();
}
String name = key.substring(prefixFooterKey.length());
footerMap.put(name, msg.getObjectProperty(key));
} else if (key.equals(AMQPMessageTypes.AMQP_TYPE_KEY)) {
// skip
} else {
if (apMap == null) {
apMap = new HashMap();
}
Object objectProperty = msg.getObjectProperty(key);
if (objectProperty instanceof byte[]) {
Binary binary = new Binary((byte[]) objectProperty);
apMap.put(key, binary);
} else {
apMap.put(key, objectProperty);
}
}
private Binary getBinaryFromMessageBody(ServerJMSBytesMessage message) throws JMSException {
byte[] data = new byte[(int) message.getBodyLength()];
message.readBytes(data);
message.reset(); // Need to reset after readBytes or future readBytes
return new Binary(data);
}
MessageAnnotations ma = null;
if (maMap != null) {
ma = new MessageAnnotations(maMap);
}
DeliveryAnnotations da = null;
if (daMap != null) {
da = new DeliveryAnnotations(daMap);
}
ApplicationProperties ap = null;
if (apMap != null) {
ap = new ApplicationProperties(apMap);
}
Footer footer = null;
if (footerMap != null) {
footer = new Footer(footerMap);
private Binary getBinaryFromMessageBody(ServerJMSTextMessage message) throws JMSException {
Binary result = null;
String text = message.getText();
if (text != null) {
result = new Binary(text.getBytes(StandardCharsets.UTF_8));
}
return (ProtonJMessage) org.apache.qpid.proton.message.Message.Factory.create(header, da, ma, props, ap, body, footer);
return result;
}
private Binary getBinaryFromMessageBody(ServerJMSObjectMessage message) throws JMSException {
message.getInnerMessage().getBodyBuffer().resetReaderIndex();
int size = message.getInnerMessage().getBodyBuffer().readInt();
byte[] bytes = new byte[size];
message.getInnerMessage().getBodyBuffer().readBytes(bytes);
return new Binary(bytes);
}
private Map<String, Object> getMapFromMessageBody(ServerJMSMapMessage message) throws JMSException {
final HashMap<String, Object> map = new LinkedHashMap<>();
@SuppressWarnings("unchecked")
final Enumeration<String> names = message.getMapNames();
while (names.hasMoreElements()) {
String key = names.nextElement();
Object value = message.getObject(key);
if (value instanceof byte[]) {
value = new Binary((byte[]) value);
}
map.put(key, value);
}
return map;
}
private static byte destinationType(Destination destination) {

View File

@ -1,53 +0,0 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.artemis.protocol.amqp.converter.message;
import javax.jms.BytesMessage;
import javax.jms.Destination;
import javax.jms.MapMessage;
import javax.jms.Message;
import javax.jms.ObjectMessage;
import javax.jms.StreamMessage;
import javax.jms.TextMessage;
public interface JMSVendor {
BytesMessage createBytesMessage();
StreamMessage createStreamMessage();
Message createMessage();
TextMessage createTextMessage();
ObjectMessage createObjectMessage();
MapMessage createMapMessage();
void setJMSXUserID(Message message, String value);
Destination createDestination(String name);
void setJMSXGroupID(Message message, String groupId);
void setJMSXGroupSequence(Message message, int value);
void setJMSXDeliveryCount(Message message, long value);
String toAddress(Destination destination);
}

View File

@ -1,13 +1,13 @@
/**
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* <p>
*
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@ -16,54 +16,36 @@
*/
package org.apache.activemq.artemis.protocol.amqp.converter.message;
import java.io.UnsupportedEncodingException;
import javax.jms.JMSException;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSMessage;
import org.apache.activemq.artemis.utils.IDGenerator;
import org.apache.qpid.proton.codec.WritableBuffer;
public abstract class OutboundTransformer {
JMSVendor vendor;
String prefixVendor;
protected IDGenerator idGenerator;
String prefixDeliveryAnnotations = "DA_";
String prefixMessageAnnotations = "MA_";
String prefixFooter = "FT_";
String messageFormatKey;
String nativeKey;
String firstAcquirerKey;
String prefixDeliveryAnnotationsKey;
String prefixMessageAnnotationsKey;
String contentTypeKey;
String contentEncodingKey;
String replyToGroupIDKey;
String prefixFooterKey;
public OutboundTransformer(JMSVendor vendor) {
this.vendor = vendor;
this.setPrefixVendor("JMS_AMQP_");
public OutboundTransformer(IDGenerator idGenerator) {
this.idGenerator = idGenerator;
}
public String getPrefixVendor() {
return prefixVendor;
}
public void setPrefixVendor(String prefixVendor) {
this.prefixVendor = prefixVendor;
messageFormatKey = prefixVendor + "MESSAGE_FORMAT";
nativeKey = prefixVendor + "NATIVE";
firstAcquirerKey = prefixVendor + "FirstAcquirer";
prefixDeliveryAnnotationsKey = prefixVendor + prefixDeliveryAnnotations;
prefixMessageAnnotationsKey = prefixVendor + prefixMessageAnnotations;
contentTypeKey = prefixVendor + "ContentType";
contentEncodingKey = prefixVendor + "ContentEncoding";
replyToGroupIDKey = prefixVendor + "ReplyToGroupID";
prefixFooterKey = prefixVendor + prefixFooter;
/**
* Given an JMS Message perform a conversion to an AMQP Message and encode into a form that
* is ready for transmission.
*
* @param message
* the message to transform
* @param buffer
* the buffer where encoding should write to
*
* @return the message format key of the encoded message.
*
* @throws Exception
* if an error occurs during message transformation
*/
public abstract long transform(ServerJMSMessage message, WritableBuffer buffer) throws JMSException, UnsupportedEncodingException;
}
public JMSVendor getVendor() {
return vendor;
}
public void setVendor(JMSVendor vendor) {
this.vendor = vendor;
}
}

View File

@ -0,0 +1,27 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.artemis.protocol.amqp.exceptions;
import org.apache.activemq.artemis.api.core.ActiveMQExceptionType;
import org.apache.qpid.proton.amqp.transport.AmqpError;
public class ActiveMQAMQPInvalidContentTypeException extends ActiveMQAMQPException {
public ActiveMQAMQPInvalidContentTypeException(String message) {
super(AmqpError.INTERNAL_ERROR, message, ActiveMQExceptionType.INTERNAL_ERROR);
}
}

View File

@ -19,8 +19,6 @@ package org.apache.activemq.artemis.protocol.amqp.proton;
import java.util.Map;
import java.util.Objects;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.PooledByteBufAllocator;
import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.core.server.QueueQueryResult;
import org.apache.activemq.artemis.core.transaction.Transaction;
@ -52,9 +50,11 @@ import org.apache.qpid.proton.amqp.transport.ErrorCondition;
import org.apache.qpid.proton.amqp.transport.SenderSettleMode;
import org.apache.qpid.proton.engine.Delivery;
import org.apache.qpid.proton.engine.Sender;
import org.apache.qpid.proton.message.ProtonJMessage;
import org.jboss.logging.Logger;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.PooledByteBufAllocator;
public class ProtonServerSenderContext extends ProtonInitializable implements ProtonDeliveryHandler {
private static final Logger log = Logger.getLogger(ProtonServerSenderContext.class);
@ -407,33 +407,6 @@ public class ProtonServerSenderContext extends ProtonInitializable implements Pr
return 0;
}
//encode the message
ProtonJMessage serverMessage;
try {
// This can be done a lot better here
serverMessage = sessionSPI.encodeMessage(message, deliveryCount);
} catch (Throwable e) {
log.warn(e.getMessage(), e);
throw new ActiveMQAMQPInternalErrorException(e.getMessage(), e);
}
return performSend(serverMessage, message);
}
private static boolean hasCapabilities(Symbol symbol, Source source) {
if (source != null) {
if (source.getCapabilities() != null) {
for (Symbol cap : source.getCapabilities()) {
if (symbol.equals(cap)) {
return true;
}
}
}
}
return false;
}
protected int performSend(ProtonJMessage serverMessage, Object context) {
if (!creditsSemaphore.tryAcquire()) {
try {
creditsSemaphore.acquire();
@ -444,22 +417,32 @@ public class ProtonServerSenderContext extends ProtonInitializable implements Pr
}
}
//presettle means we can ack the message on the dealer side before we send it, i.e. for browsers
// presettle means we can settle the message on the dealer side before we send it, i.e. for browsers
boolean preSettle = sender.getRemoteSenderSettleMode() == SenderSettleMode.SETTLED;
//we only need a tag if we are going to ack later
// we only need a tag if we are going to settle later
byte[] tag = preSettle ? new byte[0] : protonSession.getTag();
ByteBuf nettyBuffer = PooledByteBufAllocator.DEFAULT.heapBuffer(1024);
try {
serverMessage.encode(new NettyWritable(nettyBuffer));
long messageFormat = 0;
// Encode the Server Message into the given Netty Buffer as an AMQP
// Message transformed from the internal message model.
try {
messageFormat = sessionSPI.encodeMessage(message, deliveryCount, new NettyWritable(nettyBuffer));
} catch (Throwable e) {
log.warn(e.getMessage(), e);
throw new ActiveMQAMQPInternalErrorException(e.getMessage(), e);
}
int size = nettyBuffer.writerIndex();
synchronized (connection.getLock()) {
final Delivery delivery;
delivery = sender.delivery(tag, 0, tag.length);
delivery.setContext(context);
delivery.setMessageFormat((int) messageFormat);
delivery.setContext(message);
// this will avoid a copy.. patch provided by Norman using buffer.array()
sender.send(nettyBuffer.array(), nettyBuffer.arrayOffset() + nettyBuffer.readerIndex(), nettyBuffer.readableBytes());
@ -479,6 +462,19 @@ public class ProtonServerSenderContext extends ProtonInitializable implements Pr
}
}
private static boolean hasCapabilities(Symbol symbol, Source source) {
if (source != null) {
if (source.getCapabilities() != null) {
for (Symbol cap : source.getCapabilities()) {
if (symbol.equals(cap)) {
return true;
}
}
}
}
return false;
}
private static String createQueueName(String clientId, String pubId) {
return clientId + "." + pubId;
}

View File

@ -16,32 +16,31 @@
*/
package org.apache.activemq.artemis.protocol.amqp.converter;
import java.io.ByteArrayOutputStream;
import static org.apache.activemq.artemis.api.core.Message.BYTES_TYPE;
import static org.apache.activemq.artemis.api.core.Message.MAP_TYPE;
import static org.apache.activemq.artemis.api.core.Message.STREAM_TYPE;
import static org.apache.activemq.artemis.api.core.Message.TEXT_TYPE;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.wrapMessage;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.PooledByteBufAllocator;
import org.apache.activemq.artemis.api.core.ActiveMQBuffer;
import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.core.journal.EncodingSupport;
import org.apache.activemq.artemis.core.server.ServerMessage;
import org.apache.activemq.artemis.core.server.impl.ServerMessageImpl;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSBytesMessage;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSMapMessage;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSMessage;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSObjectMessage;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSStreamMessage;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSTextMessage;
import org.apache.activemq.artemis.protocol.amqp.converter.message.EncodedMessage;
import org.apache.activemq.artemis.protocol.amqp.util.NettyWritable;
import org.apache.activemq.artemis.utils.SimpleIDGenerator;
import org.apache.blacklist.ABadClass;
import org.apache.qpid.proton.amqp.Binary;
import org.apache.qpid.proton.amqp.messaging.AmqpSequence;
import org.apache.qpid.proton.amqp.messaging.AmqpValue;
@ -53,10 +52,13 @@ import org.apache.qpid.proton.message.impl.MessageImpl;
import org.junit.Assert;
import org.junit.Test;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.PooledByteBufAllocator;
public class TestConversions extends Assert {
@Test
public void testObjectMessageWhiteList() throws Exception {
public void testAmqpValueOfBooleanIsPassedThrough() throws Exception {
Map<String, Object> mapprop = createPropertiesMap();
ApplicationProperties properties = new ApplicationProperties(mapprop);
MessageImpl message = (MessageImpl) Message.Factory.create();
@ -73,39 +75,15 @@ public class TestConversions extends Assert {
EncodedMessage encodedMessage = encodeMessage(message);
ProtonMessageConverter converter = new ProtonMessageConverter(new SimpleIDGenerator(0));
ServerJMSObjectMessage serverMessage = (ServerJMSObjectMessage) converter.inboundJMSType(encodedMessage);
ServerMessage serverMessage = converter.inbound(encodedMessage);
verifyProperties(serverMessage);
verifyProperties(new ServerJMSMessage(serverMessage, 0));
assertEquals(true, serverMessage.getObject());
EncodedMessage encoded = (EncodedMessage) converter.outbound(serverMessage, 0);
Message amqpMessage = encoded.decode();
Object obj = converter.outbound((ServerMessage) serverMessage.getInnerMessage(), 0);
AmqpValue value = (AmqpValue) ((Message) obj).getBody();
AmqpValue value = (AmqpValue) amqpMessage.getBody();
assertEquals(value.getValue(), true);
}
@Test
public void testObjectMessageNotOnWhiteList() throws Exception {
ProtonMessageConverter converter = new ProtonMessageConverter(new SimpleIDGenerator(0));
ServerMessageImpl message = new ServerMessageImpl(1, 1024);
message.setType((byte) 2);
ServerJMSObjectMessage serverMessage = new ServerJMSObjectMessage(message, 1024);
ByteArrayOutputStream out = new ByteArrayOutputStream();
ObjectOutputStream ois = new ObjectOutputStream(out);
ois.writeObject(new ABadClass());
byte[] src = out.toByteArray();
serverMessage.getInnerMessage().getBodyBuffer().writeInt(src.length);
serverMessage.getInnerMessage().getBodyBuffer().writeBytes(src);
try {
converter.outbound((ServerMessage) serverMessage.getInnerMessage(), 0);
fail("should throw ClassNotFoundException");
} catch (ClassNotFoundException e) {
//ignore
}
}
@Test
@ -126,22 +104,23 @@ public class TestConversions extends Assert {
EncodedMessage encodedMessage = encodeMessage(message);
ProtonMessageConverter converter = new ProtonMessageConverter(new SimpleIDGenerator(0));
ServerJMSBytesMessage serverMessage = (ServerJMSBytesMessage) converter.inboundJMSType(encodedMessage);
ServerMessage serverMessage = converter.inbound(encodedMessage);
verifyProperties(serverMessage);
ServerJMSBytesMessage bytesMessage = (ServerJMSBytesMessage) wrapMessage(BYTES_TYPE, serverMessage, 0);
assertEquals(bodyBytes.length, serverMessage.getBodyLength());
verifyProperties(bytesMessage);
assertEquals(bodyBytes.length, bytesMessage.getBodyLength());
byte[] newBodyBytes = new byte[4];
serverMessage.readBytes(newBodyBytes);
bytesMessage.readBytes(newBodyBytes);
Assert.assertArrayEquals(bodyBytes, newBodyBytes);
Object obj = converter.outbound((ServerMessage) serverMessage.getInnerMessage(), 0);
Object obj = converter.outbound(serverMessage, 0);
System.out.println("output = " + obj);
}
private void verifyProperties(javax.jms.Message message) throws Exception {
@ -175,25 +154,25 @@ public class TestConversions extends Assert {
EncodedMessage encodedMessage = encodeMessage(message);
ProtonMessageConverter converter = new ProtonMessageConverter(new SimpleIDGenerator(0));
ServerJMSMapMessage serverMessage = (ServerJMSMapMessage) converter.inboundJMSType(encodedMessage);
ServerMessage serverMessage = converter.inbound(encodedMessage);
verifyProperties(serverMessage);
ServerJMSMapMessage mapMessage = (ServerJMSMapMessage) wrapMessage(MAP_TYPE, serverMessage, 0);
mapMessage.decode();
Assert.assertEquals(1, serverMessage.getInt("someint"));
Assert.assertEquals("value", serverMessage.getString("somestr"));
verifyProperties(mapMessage);
Object obj = converter.outbound((ServerMessage) serverMessage.getInnerMessage(), 0);
Assert.assertEquals(1, mapMessage.getInt("someint"));
Assert.assertEquals("value", mapMessage.getString("somestr"));
reEncodeMsg(obj);
EncodedMessage encoded = (EncodedMessage) converter.outbound(serverMessage, 0);
Message amqpMessage = encoded.decode();
MessageImpl outMessage = (MessageImpl) obj;
AmqpValue value = (AmqpValue) outMessage.getBody();
Map mapoutput = (Map) value.getValue();
AmqpValue value = (AmqpValue) amqpMessage.getBody();
Map<?, ?> mapoutput = (Map<?, ?>) value.getValue();
assertEquals(Integer.valueOf(1), mapoutput.get("someint"));
System.out.println("output = " + obj);
System.out.println("output = " + amqpMessage);
}
@Test
@ -212,26 +191,25 @@ public class TestConversions extends Assert {
EncodedMessage encodedMessage = encodeMessage(message);
ProtonMessageConverter converter = new ProtonMessageConverter(new SimpleIDGenerator(0));
ServerJMSStreamMessage serverMessage = (ServerJMSStreamMessage) converter.inboundJMSType(encodedMessage);
ServerMessage serverMessage = converter.inbound(encodedMessage);
simulatePersistence(serverMessage);
verifyProperties(serverMessage);
ServerJMSStreamMessage streamMessage = (ServerJMSStreamMessage) wrapMessage(STREAM_TYPE, serverMessage, 0);
serverMessage.reset();
verifyProperties(streamMessage);
assertEquals(10, serverMessage.readInt());
assertEquals("10", serverMessage.readString());
streamMessage.reset();
Object obj = converter.outbound((ServerMessage) serverMessage.getInnerMessage(), 0);
assertEquals(10, streamMessage.readInt());
assertEquals("10", streamMessage.readString());
reEncodeMsg(obj);
EncodedMessage encoded = (EncodedMessage) converter.outbound(serverMessage, 0);
Message amqpMessage = encoded.decode();
MessageImpl outMessage = (MessageImpl) obj;
List list = ((AmqpSequence) outMessage.getBody()).getValue();
List<?> list = ((AmqpSequence) amqpMessage.getBody()).getValue();
Assert.assertEquals(Integer.valueOf(10), list.get(0));
Assert.assertEquals("10", list.get(1));
}
@Test
@ -247,33 +225,33 @@ public class TestConversions extends Assert {
EncodedMessage encodedMessage = encodeMessage(message);
ProtonMessageConverter converter = new ProtonMessageConverter(new SimpleIDGenerator(0));
ServerJMSTextMessage serverMessage = (ServerJMSTextMessage) converter.inboundJMSType(encodedMessage);
ServerMessage serverMessage = converter.inbound(encodedMessage);
simulatePersistence(serverMessage);
verifyProperties(serverMessage);
ServerJMSTextMessage textMessage = (ServerJMSTextMessage) wrapMessage(TEXT_TYPE, serverMessage, 0);
textMessage.decode();
Assert.assertEquals(text, serverMessage.getText());
verifyProperties(textMessage);
Object obj = converter.outbound((ServerMessage) serverMessage.getInnerMessage(), 0);
Assert.assertEquals(text, textMessage.getText());
reEncodeMsg(obj);
EncodedMessage encoded = (EncodedMessage) converter.outbound(serverMessage, 0);
Message amqpMessage = encoded.decode();
MessageImpl outMessage = (MessageImpl) obj;
AmqpValue value = (AmqpValue) outMessage.getBody();
AmqpValue value = (AmqpValue) amqpMessage.getBody();
String textValue = (String) value.getValue();
Assert.assertEquals(text, textValue);
System.out.println("output = " + obj);
System.out.println("output = " + amqpMessage);
}
private void simulatePersistence(ServerJMSMessage serverMessage) {
serverMessage.getInnerMessage().setAddress(new SimpleString("jms.queue.SomeAddress"));
private void simulatePersistence(ServerMessage serverMessage) {
serverMessage.setAddress(new SimpleString("jms.queue.SomeAddress"));
// This is just to simulate what would happen during the persistence of the message
// We need to still be able to recover the message when we read it back
((EncodingSupport) serverMessage.getInnerMessage()).encode(new EmptyBuffer());
((EncodingSupport) serverMessage).encode(new EmptyBuffer());
}
private ProtonJMessage reEncodeMsg(Object obj) {

View File

@ -0,0 +1,230 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.artemis.protocol.amqp.converter.message;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import org.apache.activemq.artemis.protocol.amqp.exceptions.ActiveMQAMQPInvalidContentTypeException;
import org.junit.Test;
public class AMQPContentTypeSupportTest {
@Test(expected = ActiveMQAMQPInvalidContentTypeException.class)
public void testParseContentTypeWithOnlyType() throws Exception {
doParseContentTypeTestImpl("type", null);
}
@Test(expected = ActiveMQAMQPInvalidContentTypeException.class)
public void testParseContentTypeEndsWithSlash() throws Exception {
doParseContentTypeTestImpl("type/", null);
}
@Test(expected = ActiveMQAMQPInvalidContentTypeException.class)
public void testParseContentTypeMissingSubtype() throws Exception {
doParseContentTypeTestImpl("type/;", null);
}
@Test(expected = ActiveMQAMQPInvalidContentTypeException.class)
public void testParseContentTypeEmptyString() throws Exception {
doParseContentTypeTestImpl("", null);
}
@Test(expected = ActiveMQAMQPInvalidContentTypeException.class)
public void testParseContentTypeNullString() throws Exception {
doParseContentTypeTestImpl(null, null);
}
@Test
public void testParseContentTypeNoParamsAfterSeparatorNonTextual() throws Exception {
// Expect null as this is not a textual type
doParseContentTypeTestImpl("type/subtype;", null);
}
@Test
public void testParseContentTypeNoParamsAfterSeparatorTextualType() throws Exception {
doParseContentTypeTestImpl("text/plain;", StandardCharsets.UTF_8);
}
@Test
public void testParseContentTypeEmptyParamsAfterSeparator() throws Exception {
doParseContentTypeTestImpl("text/plain;;", StandardCharsets.UTF_8);
}
@Test
public void testParseContentTypeNoParams() throws Exception {
doParseContentTypeTestImpl("text/plain", StandardCharsets.UTF_8);
}
@Test
public void testParseContentTypeWithCharsetUtf8() throws Exception {
doParseContentTypeTestImpl("text/plain;charset=utf-8", StandardCharsets.UTF_8);
}
@Test
public void testParseContentTypeWithCharsetAscii() throws Exception {
doParseContentTypeTestImpl("text/plain;charset=us-ascii", StandardCharsets.US_ASCII);
}
@Test
public void testParseContentTypeWithMultipleParams() throws Exception {
doParseContentTypeTestImpl("text/plain; param=value; charset=us-ascii", StandardCharsets.US_ASCII);
}
@Test
public void testParseContentTypeWithCharsetQuoted() throws Exception {
doParseContentTypeTestImpl("text/plain;charset=\"us-ascii\"", StandardCharsets.US_ASCII);
}
@Test(expected = ActiveMQAMQPInvalidContentTypeException.class)
public void testParseContentTypeWithCharsetQuotedEmpty() throws Exception {
doParseContentTypeTestImpl("text/plain;charset=\"\"", null);
}
@Test(expected = ActiveMQAMQPInvalidContentTypeException.class)
public void testParseContentTypeWithCharsetQuoteNotClosed() throws Exception {
doParseContentTypeTestImpl("text/plain;charset=\"unclosed", null);
}
@Test(expected = ActiveMQAMQPInvalidContentTypeException.class)
public void testParseContentTypeWithCharsetQuoteNotClosedEmpty() throws Exception {
doParseContentTypeTestImpl("text/plain;charset=\"", null);
}
@Test(expected = ActiveMQAMQPInvalidContentTypeException.class)
public void testParseContentTypeWithNoCharsetValue() throws Exception {
doParseContentTypeTestImpl("text/plain;charset=", null);
}
@Test
public void testParseContentTypeWithTextPlain() throws Exception {
doParseContentTypeTestImpl("text/plain;charset=iso-8859-1", StandardCharsets.ISO_8859_1);
doParseContentTypeTestImpl("text/plain;charset=us-ascii", StandardCharsets.US_ASCII);
doParseContentTypeTestImpl("text/plain;charset=utf-8", StandardCharsets.UTF_8);
doParseContentTypeTestImpl("text/plain", StandardCharsets.UTF_8);
}
@Test
public void testParseContentTypeWithTextJson() throws Exception {
doParseContentTypeTestImpl("text/json;charset=iso-8859-1", StandardCharsets.ISO_8859_1);
doParseContentTypeTestImpl("text/json;charset=us-ascii", StandardCharsets.US_ASCII);
doParseContentTypeTestImpl("text/json;charset=utf-8", StandardCharsets.UTF_8);
doParseContentTypeTestImpl("text/json", StandardCharsets.UTF_8);
}
@Test
public void testParseContentTypeWithTextHtml() throws Exception {
doParseContentTypeTestImpl("text/html;charset=iso-8859-1", StandardCharsets.ISO_8859_1);
doParseContentTypeTestImpl("text/html;charset=us-ascii", StandardCharsets.US_ASCII);
doParseContentTypeTestImpl("text/html;charset=utf-8", StandardCharsets.UTF_8);
doParseContentTypeTestImpl("text/html", StandardCharsets.UTF_8);
}
@Test
public void testParseContentTypeWithTextFoo() throws Exception {
doParseContentTypeTestImpl("text/foo;charset=iso-8859-1", StandardCharsets.ISO_8859_1);
doParseContentTypeTestImpl("text/foo;charset=us-ascii", StandardCharsets.US_ASCII);
doParseContentTypeTestImpl("text/foo;charset=utf-8", StandardCharsets.UTF_8);
doParseContentTypeTestImpl("text/foo", StandardCharsets.UTF_8);
}
@Test
public void testParseContentTypeWithApplicationJson() throws Exception {
doParseContentTypeTestImpl("application/json;charset=iso-8859-1", StandardCharsets.ISO_8859_1);
doParseContentTypeTestImpl("application/json;charset=us-ascii", StandardCharsets.US_ASCII);
doParseContentTypeTestImpl("application/json;charset=utf-8", StandardCharsets.UTF_8);
doParseContentTypeTestImpl("application/json", StandardCharsets.UTF_8);
}
@Test
public void testParseContentTypeWithApplicationJsonVariant() throws Exception {
doParseContentTypeTestImpl("application/something+json;charset=iso-8859-1", StandardCharsets.ISO_8859_1);
doParseContentTypeTestImpl("application/something+json;charset=us-ascii", StandardCharsets.US_ASCII);
doParseContentTypeTestImpl("application/something+json;charset=utf-8", StandardCharsets.UTF_8);
doParseContentTypeTestImpl("application/something+json", StandardCharsets.UTF_8);
}
@Test
public void testParseContentTypeWithApplicationJavascript() throws Exception {
doParseContentTypeTestImpl("application/javascript;charset=iso-8859-1", StandardCharsets.ISO_8859_1);
doParseContentTypeTestImpl("application/javascript;charset=us-ascii", StandardCharsets.US_ASCII);
doParseContentTypeTestImpl("application/javascript;charset=utf-8", StandardCharsets.UTF_8);
doParseContentTypeTestImpl("application/javascript", StandardCharsets.UTF_8);
}
@Test
public void testParseContentTypeWithApplicationEcmascript() throws Exception {
doParseContentTypeTestImpl("application/ecmascript;charset=iso-8859-1", StandardCharsets.ISO_8859_1);
doParseContentTypeTestImpl("application/ecmascript;charset=us-ascii", StandardCharsets.US_ASCII);
doParseContentTypeTestImpl("application/ecmascript;charset=utf-8", StandardCharsets.UTF_8);
doParseContentTypeTestImpl("application/ecmascript", StandardCharsets.UTF_8);
}
@Test
public void testParseContentTypeWithApplicationXml() throws Exception {
doParseContentTypeTestImpl("application/xml;charset=iso-8859-1", StandardCharsets.ISO_8859_1);
doParseContentTypeTestImpl("application/xml;charset=us-ascii", StandardCharsets.US_ASCII);
doParseContentTypeTestImpl("application/xml;charset=utf-8", StandardCharsets.UTF_8);
doParseContentTypeTestImpl("application/xml", StandardCharsets.UTF_8);
}
@Test
public void testParseContentTypeWithApplicationXmlVariant() throws Exception {
doParseContentTypeTestImpl("application/something+xml;charset=iso-8859-1", StandardCharsets.ISO_8859_1);
doParseContentTypeTestImpl("application/something+xml;charset=us-ascii", StandardCharsets.US_ASCII);
doParseContentTypeTestImpl("application/something+xml;charset=utf-8", StandardCharsets.UTF_8);
doParseContentTypeTestImpl("application/something+xml", StandardCharsets.UTF_8);
}
@Test
public void testParseContentTypeWithApplicationXmlDtd() throws Exception {
doParseContentTypeTestImpl("application/xml-dtd;charset=iso-8859-1", StandardCharsets.ISO_8859_1);
doParseContentTypeTestImpl("application/xml-dtd;charset=us-ascii", StandardCharsets.US_ASCII);
doParseContentTypeTestImpl("application/xml-dtd;charset=utf-8", StandardCharsets.UTF_8);
doParseContentTypeTestImpl("application/xml-dtd", StandardCharsets.UTF_8);
}
@Test
public void testParseContentTypeWithApplicationOtherNotTextual() throws Exception {
// Expect null as this is not a textual type
doParseContentTypeTestImpl("application/other", null);
}
@Test
public void testParseContentTypeWithApplicationOctetStream() throws Exception {
// Expect null as this is not a textual type
doParseContentTypeTestImpl(AMQPMessageSupport.OCTET_STREAM_CONTENT_TYPE, null);
}
@Test
public void testParseContentTypeWithApplicationJavaSerialized() throws Exception {
// Expect null as this is not a textual type
doParseContentTypeTestImpl(AMQPMessageSupport.SERIALIZED_JAVA_OBJECT_CONTENT_TYPE, null);
}
private void doParseContentTypeTestImpl(String contentType, Charset expected) throws ActiveMQAMQPInvalidContentTypeException {
Charset charset = AMQPContentTypeSupport.parseContentTypeForTextualCharset(contentType);
if (expected == null) {
assertNull("Expected no charset, but got:" + charset, charset);
} else {
assertEquals("Charset not as expected", expected, charset);
}
}
}

View File

@ -0,0 +1,391 @@
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package org.apache.activemq.artemis.protocol.amqp.converter.message;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import java.util.UUID;
import org.apache.activemq.artemis.protocol.amqp.exceptions.ActiveMQAMQPException;
import org.apache.qpid.proton.amqp.Binary;
import org.apache.qpid.proton.amqp.UnsignedLong;
import org.junit.Before;
import org.junit.Test;
public class AMQPMessageIdHelperTest {
private AMQPMessageIdHelper messageIdHelper;
@Before
public void setUp() throws Exception {
messageIdHelper = new AMQPMessageIdHelper();
}
/**
* Test that {@link AMQPMessageIdHelper#toBaseMessageIdString(Object)} returns null if given
* null
*/
@Test
public void testToBaseMessageIdStringWithNull() {
String nullString = null;
assertNull("null string should have been returned", messageIdHelper.toBaseMessageIdString(nullString));
}
/**
* Test that {@link AMQPMessageIdHelper#toBaseMessageIdString(Object)} throws an IAE if given
* an unexpected object type.
*/
@Test
public void testToBaseMessageIdStringThrowsIAEWithUnexpectedType() {
try {
messageIdHelper.toBaseMessageIdString(new Object());
fail("expected exception not thrown");
} catch (IllegalArgumentException iae) {
// expected
}
}
/**
* Test that {@link AMQPMessageIdHelper#toBaseMessageIdString(Object)} returns the given
* basic string unchanged
*/
@Test
public void testToBaseMessageIdStringWithString() {
String stringMessageId = "myIdString";
String baseMessageIdString = messageIdHelper.toBaseMessageIdString(stringMessageId);
assertNotNull("null string should not have been returned", baseMessageIdString);
assertEquals("expected base id string was not returned", stringMessageId, baseMessageIdString);
}
/**
* Test that {@link AMQPMessageIdHelper#toBaseMessageIdString(Object)} returns a string
* indicating an AMQP encoded string, when the given string happens to already begin with the
* {@link AMQPMessageIdHelper#AMQP_UUID_PREFIX}.
*/
@Test
public void testToBaseMessageIdStringWithStringBeginningWithEncodingPrefixForUUID() {
String uuidStringMessageId = AMQPMessageIdHelper.AMQP_UUID_PREFIX + UUID.randomUUID();
String expected = AMQPMessageIdHelper.AMQP_STRING_PREFIX + uuidStringMessageId;
String baseMessageIdString = messageIdHelper.toBaseMessageIdString(uuidStringMessageId);
assertNotNull("null string should not have been returned", baseMessageIdString);
assertEquals("expected base id string was not returned", expected, baseMessageIdString);
}
/**
* Test that {@link AMQPMessageIdHelper#toBaseMessageIdString(Object)} returns a string
* indicating an AMQP encoded string, when the given string happens to already begin with the
* {@link AMQPMessageIdHelper#AMQP_ULONG_PREFIX}.
*/
@Test
public void testToBaseMessageIdStringWithStringBeginningWithEncodingPrefixForLong() {
String longStringMessageId = AMQPMessageIdHelper.AMQP_ULONG_PREFIX + Long.valueOf(123456789L);
String expected = AMQPMessageIdHelper.AMQP_STRING_PREFIX + longStringMessageId;
String baseMessageIdString = messageIdHelper.toBaseMessageIdString(longStringMessageId);
assertNotNull("null string should not have been returned", baseMessageIdString);
assertEquals("expected base id string was not returned", expected, baseMessageIdString);
}
/**
* Test that {@link AMQPMessageIdHelper#toBaseMessageIdString(Object)} returns a string
* indicating an AMQP encoded string, when the given string happens to already begin with the
* {@link AMQPMessageIdHelper#AMQP_BINARY_PREFIX}.
*/
@Test
public void testToBaseMessageIdStringWithStringBeginningWithEncodingPrefixForBinary() {
String binaryStringMessageId = AMQPMessageIdHelper.AMQP_BINARY_PREFIX + "0123456789ABCDEF";
String expected = AMQPMessageIdHelper.AMQP_STRING_PREFIX + binaryStringMessageId;
String baseMessageIdString = messageIdHelper.toBaseMessageIdString(binaryStringMessageId);
assertNotNull("null string should not have been returned", baseMessageIdString);
assertEquals("expected base id string was not returned", expected, baseMessageIdString);
}
/**
* Test that {@link AMQPMessageIdHelper#toBaseMessageIdString(Object)} returns a string
* indicating an AMQP encoded string (effectively twice), when the given string happens to
* already begin with the {@link AMQPMessageIdHelper#AMQP_STRING_PREFIX}.
*/
@Test
public void testToBaseMessageIdStringWithStringBeginningWithEncodingPrefixForString() {
String stringMessageId = AMQPMessageIdHelper.AMQP_STRING_PREFIX + "myStringId";
String expected = AMQPMessageIdHelper.AMQP_STRING_PREFIX + stringMessageId;
String baseMessageIdString = messageIdHelper.toBaseMessageIdString(stringMessageId);
assertNotNull("null string should not have been returned", baseMessageIdString);
assertEquals("expected base id string was not returned", expected, baseMessageIdString);
}
/**
* Test that {@link AMQPMessageIdHelper#toBaseMessageIdString(Object)} returns a string
* indicating an AMQP encoded UUID when given a UUID object.
*/
@Test
public void testToBaseMessageIdStringWithUUID() {
UUID uuidMessageId = UUID.randomUUID();
String expected = AMQPMessageIdHelper.AMQP_UUID_PREFIX + uuidMessageId.toString();
String baseMessageIdString = messageIdHelper.toBaseMessageIdString(uuidMessageId);
assertNotNull("null string should not have been returned", baseMessageIdString);
assertEquals("expected base id string was not returned", expected, baseMessageIdString);
}
/**
* Test that {@link AMQPMessageIdHelper#toBaseMessageIdString(Object)} returns a string
* indicating an AMQP encoded ulong when given a UnsignedLong object.
*/
@Test
public void testToBaseMessageIdStringWithUnsignedLong() {
UnsignedLong uLongMessageId = UnsignedLong.valueOf(123456789L);
String expected = AMQPMessageIdHelper.AMQP_ULONG_PREFIX + uLongMessageId.toString();
String baseMessageIdString = messageIdHelper.toBaseMessageIdString(uLongMessageId);
assertNotNull("null string should not have been returned", baseMessageIdString);
assertEquals("expected base id string was not returned", expected, baseMessageIdString);
}
/**
* Test that {@link AMQPMessageIdHelper#toBaseMessageIdString(Object)} returns a string
* indicating an AMQP encoded binary when given a Binary object.
*/
@Test
public void testToBaseMessageIdStringWithBinary() {
byte[] bytes = new byte[] {(byte) 0x00, (byte) 0xAB, (byte) 0x09, (byte) 0xFF};
Binary binary = new Binary(bytes);
String expected = AMQPMessageIdHelper.AMQP_BINARY_PREFIX + "00AB09FF";
String baseMessageIdString = messageIdHelper.toBaseMessageIdString(binary);
assertNotNull("null string should not have been returned", baseMessageIdString);
assertEquals("expected base id string was not returned", expected, baseMessageIdString);
}
/**
* Test that {@link AMQPMessageIdHelper#toIdObject(String)} returns an UnsignedLong when
* given a string indicating an encoded AMQP ulong id.
*
* @throws Exception
* if an error occurs during the test.
*/
@Test
public void testToIdObjectWithEncodedUlong() throws Exception {
UnsignedLong longId = UnsignedLong.valueOf(123456789L);
String provided = AMQPMessageIdHelper.AMQP_ULONG_PREFIX + "123456789";
Object idObject = messageIdHelper.toIdObject(provided);
assertNotNull("null object should not have been returned", idObject);
assertEquals("expected id object was not returned", longId, idObject);
}
/**
* Test that {@link AMQPMessageIdHelper#toIdObject(String)} returns a Binary when given a
* string indicating an encoded AMQP binary id, using upper case hex characters
*
* @throws Exception
* if an error occurs during the test.
*/
@Test
public void testToIdObjectWithEncodedBinaryUppercaseHexString() throws Exception {
byte[] bytes = new byte[] {(byte) 0x00, (byte) 0xAB, (byte) 0x09, (byte) 0xFF};
Binary binaryId = new Binary(bytes);
String provided = AMQPMessageIdHelper.AMQP_BINARY_PREFIX + "00AB09FF";
Object idObject = messageIdHelper.toIdObject(provided);
assertNotNull("null object should not have been returned", idObject);
assertEquals("expected id object was not returned", binaryId, idObject);
}
/**
* Test that {@link AMQPMessageIdHelper#toIdObject(String)} returns null when given null.
*
* @throws Exception
* if an error occurs during the test.
*/
@Test
public void testToIdObjectWithNull() throws Exception {
assertNull("null object should have been returned", messageIdHelper.toIdObject(null));
}
/**
* Test that {@link AMQPMessageIdHelper#toIdObject(String)} returns a Binary when given a
* string indicating an encoded AMQP binary id, using lower case hex characters.
*
* @throws Exception
* if an error occurs during the test.
*/
@Test
public void testToIdObjectWithEncodedBinaryLowercaseHexString() throws Exception {
byte[] bytes = new byte[] {(byte) 0x00, (byte) 0xAB, (byte) 0x09, (byte) 0xFF};
Binary binaryId = new Binary(bytes);
String provided = AMQPMessageIdHelper.AMQP_BINARY_PREFIX + "00ab09ff";
Object idObject = messageIdHelper.toIdObject(provided);
assertNotNull("null object should not have been returned", idObject);
assertEquals("expected id object was not returned", binaryId, idObject);
}
/**
* Test that {@link AMQPMessageIdHelper#toIdObject(String)} returns a UUID when given a
* string indicating an encoded AMQP uuid id.
*
* @throws Exception
* if an error occurs during the test.
*/
@Test
public void testToIdObjectWithEncodedUuid() throws Exception {
UUID uuid = UUID.randomUUID();
String provided = AMQPMessageIdHelper.AMQP_UUID_PREFIX + uuid.toString();
Object idObject = messageIdHelper.toIdObject(provided);
assertNotNull("null object should not have been returned", idObject);
assertEquals("expected id object was not returned", uuid, idObject);
}
/**
* Test that {@link AMQPMessageIdHelper#toIdObject(String)} returns a string when given a
* string without any type encoding prefix.
*
* @throws Exception
* if an error occurs during the test.
*/
@Test
public void testToIdObjectWithStringContainingNoEncodingPrefix() throws Exception {
String stringId = "myStringId";
Object idObject = messageIdHelper.toIdObject(stringId);
assertNotNull("null object should not have been returned", idObject);
assertEquals("expected id object was not returned", stringId, idObject);
}
/**
* Test that {@link AMQPMessageIdHelper#toIdObject(String)} returns the remainder of the
* provided string after removing the {@link AMQPMessageIdHelper#AMQP_STRING_PREFIX} prefix.
*
* @throws Exception
* if an error occurs during the test.
*/
@Test
public void testToIdObjectWithStringContainingStringEncodingPrefix() throws Exception {
String suffix = "myStringSuffix";
String stringId = AMQPMessageIdHelper.AMQP_STRING_PREFIX + suffix;
Object idObject = messageIdHelper.toIdObject(stringId);
assertNotNull("null object should not have been returned", idObject);
assertEquals("expected id object was not returned", suffix, idObject);
}
/**
* Test that when given a string with with the {@link AMQPMessageIdHelper#AMQP_STRING_PREFIX}
* prefix and then additionally the {@link AMQPMessageIdHelper#AMQP_UUID_PREFIX}, the
* {@link AMQPMessageIdHelper#toIdObject(String)} method returns the remainder of the
* provided string after removing the {@link AMQPMessageIdHelper#AMQP_STRING_PREFIX} prefix.
*
* @throws Exception
* if an error occurs during the test.
*/
@Test
public void testToIdObjectWithStringContainingStringEncodingPrefixAndThenUuidPrefix() throws Exception {
String encodedUuidString = AMQPMessageIdHelper.AMQP_UUID_PREFIX + UUID.randomUUID().toString();
String stringId = AMQPMessageIdHelper.AMQP_STRING_PREFIX + encodedUuidString;
Object idObject = messageIdHelper.toIdObject(stringId);
assertNotNull("null object should not have been returned", idObject);
assertEquals("expected id object was not returned", encodedUuidString, idObject);
}
/**
* Test that {@link AMQPMessageIdHelper#toIdObject(String)} throws an
* {@link IdConversionException} when presented with an encoded binary hex string of uneven
* length (after the prefix) that thus can't be converted due to each byte using 2 characters
*/
@Test
public void testToIdObjectWithStringContainingBinaryHexThrowsWithUnevenLengthString() {
String unevenHead = AMQPMessageIdHelper.AMQP_BINARY_PREFIX + "123";
try {
messageIdHelper.toIdObject(unevenHead);
fail("expected exception was not thrown");
} catch (ActiveMQAMQPException ex) {
// expected
}
}
/**
* Test that {@link AMQPMessageIdHelper#toIdObject(String)} throws an
* {@link IdConversionException} when presented with an encoded binary hex string (after the
* prefix) that contains characters other than 0-9 and A-F and a-f, and thus can't be
* converted
*/
@Test
public void testToIdObjectWithStringContainingBinaryHexThrowsWithNonHexCharacters() {
// char before '0'
char nonHexChar = '/';
String nonHexString = AMQPMessageIdHelper.AMQP_BINARY_PREFIX + nonHexChar + nonHexChar;
try {
messageIdHelper.toIdObject(nonHexString);
fail("expected exception was not thrown");
} catch (ActiveMQAMQPException ex) {
// expected
}
// char after '9', before 'A'
nonHexChar = ':';
nonHexString = AMQPMessageIdHelper.AMQP_BINARY_PREFIX + nonHexChar + nonHexChar;
try {
messageIdHelper.toIdObject(nonHexString);
fail("expected exception was not thrown");
} catch (ActiveMQAMQPException ex) {
// expected
}
// char after 'F', before 'a'
nonHexChar = 'G';
nonHexString = AMQPMessageIdHelper.AMQP_BINARY_PREFIX + nonHexChar + nonHexChar;
try {
messageIdHelper.toIdObject(nonHexString);
fail("expected exception was not thrown");
} catch (ActiveMQAMQPException ex) {
// expected
}
// char after 'f'
nonHexChar = 'g';
nonHexString = AMQPMessageIdHelper.AMQP_BINARY_PREFIX + nonHexChar + nonHexChar;
try {
messageIdHelper.toIdObject(nonHexString);
fail("expected exception was not thrown");
} catch (ActiveMQAMQPException ex) {
// expected
}
}
}

View File

@ -0,0 +1,108 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.artemis.protocol.amqp.converter.message;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.util.HashMap;
import java.util.Map;
import org.apache.qpid.proton.Proton;
import org.apache.qpid.proton.amqp.Symbol;
import org.apache.qpid.proton.amqp.messaging.MessageAnnotations;
import org.apache.qpid.proton.message.Message;
import org.junit.Test;
public class AMQPMessageSupportTest {
// ---------- getSymbol ---------------------------------------------------//
@Test
public void testGetSymbol() {
assertNotNull(AMQPMessageSupport.getSymbol("x-opt-something-or-other"));
}
// ---------- getMessageAnnotation ----------------------------------------//
@Test
public void testGetMessageAnnotationWhenMessageHasAnnotationsMap() {
Map<Symbol, Object> messageAnnotationsMap = new HashMap<>();
messageAnnotationsMap.put(Symbol.valueOf("x-opt-test"), Boolean.TRUE);
Message message = Proton.message();
message.setMessageAnnotations(new MessageAnnotations(messageAnnotationsMap));
assertNotNull(AMQPMessageSupport.getMessageAnnotation("x-opt-test", message));
}
@Test
public void testGetMessageAnnotationWhenMessageHasEmptyAnnotationsMap() {
Map<Symbol, Object> messageAnnotationsMap = new HashMap<>();
Message message = Proton.message();
message.setMessageAnnotations(new MessageAnnotations(messageAnnotationsMap));
assertNull(AMQPMessageSupport.getMessageAnnotation("x-opt-test", message));
}
@Test
public void testGetMessageAnnotationWhenMessageHasNoAnnotationsMap() {
Message message = Proton.message();
assertNull(AMQPMessageSupport.getMessageAnnotation("x-opt-test", message));
}
@Test
public void testGetMessageAnnotationWhenMessageIsNull() {
assertNull(AMQPMessageSupport.getMessageAnnotation("x-opt-test", null));
}
// ---------- isContentType -----------------------------------------------//
@Test
public void testIsContentTypeWithNullStringValueAndNullMessageContentType() {
Message message = Proton.message();
assertTrue(AMQPMessageSupport.isContentType(null, message));
}
@Test
public void testIsContentTypeWithNonNullStringValueAndNullMessageContentType() {
Message message = Proton.message();
assertFalse(AMQPMessageSupport.isContentType("test", message));
}
@Test
public void testIsContentTypeWithNonNullStringValueAndNonNullMessageContentTypeNotEqual() {
Message message = Proton.message();
message.setContentType("fails");
assertFalse(AMQPMessageSupport.isContentType("test", message));
}
@Test
public void testIsContentTypeWithNonNullStringValueAndNonNullMessageContentTypeEqual() {
Message message = Proton.message();
message.setContentType("test");
assertTrue(AMQPMessageSupport.isContentType("test", message));
}
@Test
public void testIsContentTypeWithNullStringValueAndNonNullMessageContentType() {
Message message = Proton.message();
message.setContentType("test");
assertFalse(AMQPMessageSupport.isContentType(null, message));
}
}

View File

@ -0,0 +1,718 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.artemis.protocol.amqp.converter.message;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.jms.Destination;
import javax.jms.MapMessage;
import javax.jms.Queue;
import javax.jms.TemporaryQueue;
import javax.jms.TemporaryTopic;
import javax.jms.TextMessage;
import javax.jms.Topic;
import org.apache.activemq.artemis.jms.client.ActiveMQMessage;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSBytesMessage;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSMapMessage;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSObjectMessage;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSStreamMessage;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSTextMessage;
import org.apache.activemq.artemis.utils.IDGenerator;
import org.apache.activemq.artemis.utils.SimpleIDGenerator;
import org.apache.qpid.proton.Proton;
import org.apache.qpid.proton.amqp.Binary;
import org.apache.qpid.proton.amqp.Symbol;
import org.apache.qpid.proton.amqp.messaging.AmqpSequence;
import org.apache.qpid.proton.amqp.messaging.AmqpValue;
import org.apache.qpid.proton.amqp.messaging.Data;
import org.apache.qpid.proton.amqp.messaging.MessageAnnotations;
import org.apache.qpid.proton.message.Message;
import org.junit.Before;
import org.junit.Test;
public class JMSMappingInboundTransformerTest {
private IDGenerator idGenerator;
@Before
public void setUp() {
this.idGenerator = new SimpleIDGenerator(0);
}
// ----- Null Body Section ------------------------------------------------//
/**
* Test that a message with no body section, but with the content type set to
* {@value AMQPMessageSupport#OCTET_STREAM_CONTENT_TYPE} results in a BytesMessage
*
* @throws Exception
* if an error occurs during the test.
*/
@Test
public void testCreateBytesMessageFromNoBodySectionAndContentType() throws Exception {
JMSMappingInboundTransformer transformer = new JMSMappingInboundTransformer(idGenerator);
Message message = Message.Factory.create();
message.setContentType(AMQPMessageSupport.OCTET_STREAM_CONTENT_TYPE);
EncodedMessage em = encodeMessage(message);
javax.jms.Message jmsMessage = transformer.transform(em);
assertNotNull("Message should not be null", jmsMessage);
assertEquals("Unexpected message class type", ServerJMSBytesMessage.class, jmsMessage.getClass());
}
/**
* Test that a message with no body section, and no content-type results in a BytesMessage
* when not otherwise annotated to indicate the type of JMS message it is.
*
* @throws Exception
* if an error occurs during the test.
*/
@Test
public void testCreateBytesMessageFromNoBodySectionAndNoContentType() throws Exception {
JMSMappingInboundTransformer transformer = new JMSMappingInboundTransformer(idGenerator);
Message message = Message.Factory.create();
EncodedMessage em = encodeMessage(message);
javax.jms.Message jmsMessage = transformer.transform(em);
assertNotNull("Message should not be null", jmsMessage);
assertEquals("Unexpected message class type", ServerJMSBytesMessage.class, jmsMessage.getClass());
}
/**
* Test that a message with no body section, but with the content type set to
* {@value AMQPMessageSupport#SERIALIZED_JAVA_OBJECT_CONTENT_TYPE} results in an
* ObjectMessage when not otherwise annotated to indicate the type of JMS message it is.
*
* @throws Exception
* if an error occurs during the test.
*/
@Test
public void testCreateObjectMessageFromNoBodySectionAndContentType() throws Exception {
JMSMappingInboundTransformer transformer = new JMSMappingInboundTransformer(idGenerator);
Message message = Message.Factory.create();
message.setContentType(AMQPMessageSupport.SERIALIZED_JAVA_OBJECT_CONTENT_TYPE);
EncodedMessage em = encodeMessage(message);
javax.jms.Message jmsMessage = transformer.transform(em);
assertNotNull("Message should not be null", jmsMessage);
assertEquals("Unexpected message class type", ServerJMSObjectMessage.class, jmsMessage.getClass());
}
@Test
public void testCreateTextMessageFromNoBodySectionAndContentType() throws Exception {
JMSMappingInboundTransformer transformer = new JMSMappingInboundTransformer(idGenerator);
Message message = Message.Factory.create();
message.setContentType("text/plain");
EncodedMessage em = encodeMessage(message);
javax.jms.Message jmsMessage = transformer.transform(em);
assertNotNull("Message should not be null", jmsMessage);
assertEquals("Unexpected message class type", ServerJMSTextMessage.class, jmsMessage.getClass());
}
/**
* Test that a message with no body section, and with the content type set to an unknown
* value results in a plain Message when not otherwise annotated to indicate the type of JMS
* message it is.
*
* @throws Exception
* if an error occurs during the test.
*/
public void testCreateGenericMessageFromNoBodySectionAndUnknownContentType() throws Exception {
JMSMappingInboundTransformer transformer = new JMSMappingInboundTransformer(idGenerator);
Message message = Message.Factory.create();
message.setContentType("unknown-content-type");
EncodedMessage em = encodeMessage(message);
javax.jms.Message jmsMessage = transformer.transform(em);
assertNotNull("Message should not be null", jmsMessage);
assertEquals("Unexpected message class type", ActiveMQMessage.class, jmsMessage.getClass());
}
// ----- Data Body Section ------------------------------------------------//
/**
* Test that a data body containing nothing, but with the content type set to
* {@value AMQPMessageSupport#OCTET_STREAM_CONTENT_TYPE} results in a BytesMessage when not
* otherwise annotated to indicate the type of JMS message it is.
*
* @throws Exception
* if an error occurs during the test.
*/
@Test
public void testCreateBytesMessageFromDataWithEmptyBinaryAndContentType() throws Exception {
Message message = Proton.message();
Binary binary = new Binary(new byte[0]);
message.setBody(new Data(binary));
message.setContentType(AMQPMessageSupport.OCTET_STREAM_CONTENT_TYPE);
EncodedMessage em = encodeMessage(message);
JMSMappingInboundTransformer transformer = new JMSMappingInboundTransformer(idGenerator);
javax.jms.Message jmsMessage = transformer.transform(em);
assertNotNull("Message should not be null", jmsMessage);
assertEquals("Unexpected message class type", ServerJMSBytesMessage.class, jmsMessage.getClass());
}
/**
* Test that a message with an empty data body section, and with the content type set to an
* unknown value results in a BytesMessage when not otherwise annotated to indicate the type
* of JMS message it is.
*
* @throws Exception
* if an error occurs during the test.
*/
public void testCreateBytesMessageFromDataWithUnknownContentType() throws Exception {
Message message = Proton.message();
Binary binary = new Binary(new byte[0]);
message.setBody(new Data(binary));
message.setContentType("unknown-content-type");
EncodedMessage em = encodeMessage(message);
JMSMappingInboundTransformer transformer = new JMSMappingInboundTransformer(idGenerator);
javax.jms.Message jmsMessage = transformer.transform(em);
assertNotNull("Message should not be null", jmsMessage);
assertEquals("Unexpected message class type", ServerJMSBytesMessage.class, jmsMessage.getClass());
}
/**
* Test that a receiving a data body containing nothing and no content type being set results
* in a BytesMessage when not otherwise annotated to indicate the type of JMS message it is.
*
* @throws Exception
* if an error occurs during the test.
*/
@Test
public void testCreateBytesMessageFromDataWithEmptyBinaryAndNoContentType() throws Exception {
Message message = Proton.message();
Binary binary = new Binary(new byte[0]);
message.setBody(new Data(binary));
assertNull(message.getContentType());
EncodedMessage em = encodeMessage(message);
JMSMappingInboundTransformer transformer = new JMSMappingInboundTransformer(idGenerator);
javax.jms.Message jmsMessage = transformer.transform(em);
assertNotNull("Message should not be null", jmsMessage);
assertEquals("Unexpected message class type", ServerJMSBytesMessage.class, jmsMessage.getClass());
}
/**
* Test that receiving a data body containing nothing, but with the content type set to
* {@value AMQPMessageSupport#SERIALIZED_JAVA_OBJECT_CONTENT_TYPE} results in an
* ObjectMessage when not otherwise annotated to indicate the type of JMS message it is.
*
* @throws Exception
* if an error occurs during the test.
*/
@Test
public void testCreateObjectMessageFromDataWithContentTypeAndEmptyBinary() throws Exception {
Message message = Proton.message();
Binary binary = new Binary(new byte[0]);
message.setBody(new Data(binary));
message.setContentType(AMQPMessageSupport.SERIALIZED_JAVA_OBJECT_CONTENT_TYPE);
EncodedMessage em = encodeMessage(message);
JMSMappingInboundTransformer transformer = new JMSMappingInboundTransformer(idGenerator);
javax.jms.Message jmsMessage = transformer.transform(em);
assertNotNull("Message should not be null", jmsMessage);
assertEquals("Unexpected message class type", ServerJMSObjectMessage.class, jmsMessage.getClass());
}
@Test
public void testCreateTextMessageFromDataWithContentTypeTextPlain() throws Exception {
doCreateTextMessageFromDataWithContentTypeTestImpl("text/plain;charset=iso-8859-1", StandardCharsets.ISO_8859_1);
doCreateTextMessageFromDataWithContentTypeTestImpl("text/plain;charset=us-ascii", StandardCharsets.US_ASCII);
doCreateTextMessageFromDataWithContentTypeTestImpl("text/plain;charset=utf-8", StandardCharsets.UTF_8);
doCreateTextMessageFromDataWithContentTypeTestImpl("text/plain", StandardCharsets.UTF_8);
}
@Test
public void testCreateTextMessageFromDataWithContentTypeTextJson() throws Exception {
doCreateTextMessageFromDataWithContentTypeTestImpl("text/json;charset=iso-8859-1", StandardCharsets.ISO_8859_1);
doCreateTextMessageFromDataWithContentTypeTestImpl("text/json;charset=us-ascii", StandardCharsets.US_ASCII);
doCreateTextMessageFromDataWithContentTypeTestImpl("text/json;charset=utf-8", StandardCharsets.UTF_8);
doCreateTextMessageFromDataWithContentTypeTestImpl("text/json", StandardCharsets.UTF_8);
}
@Test
public void testCreateTextMessageFromDataWithContentTypeTextHtml() throws Exception {
doCreateTextMessageFromDataWithContentTypeTestImpl("text/html;charset=iso-8859-1", StandardCharsets.ISO_8859_1);
doCreateTextMessageFromDataWithContentTypeTestImpl("text/html;charset=us-ascii", StandardCharsets.US_ASCII);
doCreateTextMessageFromDataWithContentTypeTestImpl("text/html;charset=utf-8", StandardCharsets.UTF_8);
doCreateTextMessageFromDataWithContentTypeTestImpl("text/html", StandardCharsets.UTF_8);
}
@Test
public void testCreateTextMessageFromDataWithContentTypeTextFoo() throws Exception {
doCreateTextMessageFromDataWithContentTypeTestImpl("text/foo;charset=iso-8859-1", StandardCharsets.ISO_8859_1);
doCreateTextMessageFromDataWithContentTypeTestImpl("text/foo;charset=us-ascii", StandardCharsets.US_ASCII);
doCreateTextMessageFromDataWithContentTypeTestImpl("text/foo;charset=utf-8", StandardCharsets.UTF_8);
doCreateTextMessageFromDataWithContentTypeTestImpl("text/foo", StandardCharsets.UTF_8);
}
@Test
public void testCreateTextMessageFromDataWithContentTypeApplicationJson() throws Exception {
doCreateTextMessageFromDataWithContentTypeTestImpl("application/json;charset=iso-8859-1", StandardCharsets.ISO_8859_1);
doCreateTextMessageFromDataWithContentTypeTestImpl("application/json;charset=us-ascii", StandardCharsets.US_ASCII);
doCreateTextMessageFromDataWithContentTypeTestImpl("application/json;charset=utf-8", StandardCharsets.UTF_8);
doCreateTextMessageFromDataWithContentTypeTestImpl("application/json", StandardCharsets.UTF_8);
}
@Test
public void testCreateTextMessageFromDataWithContentTypeApplicationJsonVariant() throws Exception {
doCreateTextMessageFromDataWithContentTypeTestImpl("application/something+json;charset=iso-8859-1", StandardCharsets.ISO_8859_1);
doCreateTextMessageFromDataWithContentTypeTestImpl("application/something+json;charset=us-ascii", StandardCharsets.US_ASCII);
doCreateTextMessageFromDataWithContentTypeTestImpl("application/something+json;charset=utf-8", StandardCharsets.UTF_8);
doCreateTextMessageFromDataWithContentTypeTestImpl("application/something+json", StandardCharsets.UTF_8);
}
@Test
public void testCreateTextMessageFromDataWithContentTypeApplicationJavascript() throws Exception {
doCreateTextMessageFromDataWithContentTypeTestImpl("application/javascript;charset=iso-8859-1", StandardCharsets.ISO_8859_1);
doCreateTextMessageFromDataWithContentTypeTestImpl("application/javascript;charset=us-ascii", StandardCharsets.US_ASCII);
doCreateTextMessageFromDataWithContentTypeTestImpl("application/javascript;charset=utf-8", StandardCharsets.UTF_8);
doCreateTextMessageFromDataWithContentTypeTestImpl("application/javascript", StandardCharsets.UTF_8);
}
@Test
public void testCreateTextMessageFromDataWithContentTypeApplicationEcmascript() throws Exception {
doCreateTextMessageFromDataWithContentTypeTestImpl("application/ecmascript;charset=iso-8859-1", StandardCharsets.ISO_8859_1);
doCreateTextMessageFromDataWithContentTypeTestImpl("application/ecmascript;charset=us-ascii", StandardCharsets.US_ASCII);
doCreateTextMessageFromDataWithContentTypeTestImpl("application/ecmascript;charset=utf-8", StandardCharsets.UTF_8);
doCreateTextMessageFromDataWithContentTypeTestImpl("application/ecmascript", StandardCharsets.UTF_8);
}
@Test
public void testCreateTextMessageFromDataWithContentTypeApplicationXml() throws Exception {
doCreateTextMessageFromDataWithContentTypeTestImpl("application/xml;charset=iso-8859-1", StandardCharsets.ISO_8859_1);
doCreateTextMessageFromDataWithContentTypeTestImpl("application/xml;charset=us-ascii", StandardCharsets.US_ASCII);
doCreateTextMessageFromDataWithContentTypeTestImpl("application/xml;charset=utf-8", StandardCharsets.UTF_8);
doCreateTextMessageFromDataWithContentTypeTestImpl("application/xml", StandardCharsets.UTF_8);
}
@Test
public void testCreateTextMessageFromDataWithContentTypeApplicationXmlVariant() throws Exception {
doCreateTextMessageFromDataWithContentTypeTestImpl("application/something+xml;charset=iso-8859-1", StandardCharsets.ISO_8859_1);
doCreateTextMessageFromDataWithContentTypeTestImpl("application/something+xml;charset=us-ascii", StandardCharsets.US_ASCII);
doCreateTextMessageFromDataWithContentTypeTestImpl("application/something+xml;charset=utf-8", StandardCharsets.UTF_8);
doCreateTextMessageFromDataWithContentTypeTestImpl("application/something+xml", StandardCharsets.UTF_8);
}
@Test
public void testCreateTextMessageFromDataWithContentTypeApplicationXmlDtd() throws Exception {
doCreateTextMessageFromDataWithContentTypeTestImpl("application/xml-dtd;charset=iso-8859-1", StandardCharsets.ISO_8859_1);
doCreateTextMessageFromDataWithContentTypeTestImpl("application/xml-dtd;charset=us-ascii", StandardCharsets.US_ASCII);
doCreateTextMessageFromDataWithContentTypeTestImpl("application/xml-dtd;charset=utf-8", StandardCharsets.UTF_8);
doCreateTextMessageFromDataWithContentTypeTestImpl("application/xml-dtd", StandardCharsets.UTF_8);
}
private void doCreateTextMessageFromDataWithContentTypeTestImpl(String contentType, Charset expectedCharset) throws Exception {
Message message = Proton.message();
Binary binary = new Binary(new byte[0]);
message.setBody(new Data(binary));
message.setContentType(contentType);
EncodedMessage em = encodeMessage(message);
JMSMappingInboundTransformer transformer = new JMSMappingInboundTransformer(idGenerator);
javax.jms.Message jmsMessage = transformer.transform(em);
assertNotNull("Message should not be null", jmsMessage);
if (StandardCharsets.UTF_8.equals(expectedCharset)) {
assertEquals("Unexpected message class type", ServerJMSTextMessage.class, jmsMessage.getClass());
} else {
assertEquals("Unexpected message class type", ServerJMSBytesMessage.class, jmsMessage.getClass());
}
}
// ----- AmqpValue transformations ----------------------------------------//
/**
* Test that an amqp-value body containing a string results in a TextMessage when not
* otherwise annotated to indicate the type of JMS message it is.
*
* @throws Exception
* if an error occurs during the test.
*/
@Test
public void testCreateTextMessageFromAmqpValueWithString() throws Exception {
Message message = Proton.message();
message.setBody(new AmqpValue("content"));
EncodedMessage em = encodeMessage(message);
JMSMappingInboundTransformer transformer = new JMSMappingInboundTransformer(idGenerator);
javax.jms.Message jmsMessage = transformer.transform(em);
assertNotNull("Message should not be null", jmsMessage);
assertEquals("Unexpected message class type", ServerJMSTextMessage.class, jmsMessage.getClass());
}
/**
* Test that an amqp-value body containing a null results in an TextMessage when not
* otherwise annotated to indicate the type of JMS message it is.
*
* @throws Exception
* if an error occurs during the test.
*/
@Test
public void testCreateTextMessageFromAmqpValueWithNull() throws Exception {
Message message = Proton.message();
message.setBody(new AmqpValue(null));
EncodedMessage em = encodeMessage(message);
JMSMappingInboundTransformer transformer = new JMSMappingInboundTransformer(idGenerator);
javax.jms.Message jmsMessage = transformer.transform(em);
assertNotNull("Message should not be null", jmsMessage);
assertEquals("Unexpected message class type", ServerJMSTextMessage.class, jmsMessage.getClass());
}
/**
* Test that a message with an AmqpValue section containing a Binary, but with the content
* type set to {@value AMQPMessageSupport#SERIALIZED_JAVA_OBJECT_CONTENT_TYPE} results in an
* ObjectMessage when not otherwise annotated to indicate the type of JMS message it is.
*
* @throws Exception
* if an error occurs during the test.
*/
@Test
public void testCreateObjectMessageFromAmqpValueWithBinaryAndContentType() throws Exception {
JMSMappingInboundTransformer transformer = new JMSMappingInboundTransformer(idGenerator);
Message message = Message.Factory.create();
message.setBody(new AmqpValue(new Binary(new byte[0])));
message.setContentType(AMQPMessageSupport.SERIALIZED_JAVA_OBJECT_CONTENT_TYPE);
EncodedMessage em = encodeMessage(message);
javax.jms.Message jmsMessage = transformer.transform(em);
assertNotNull("Message should not be null", jmsMessage);
assertEquals("Unexpected message class type", ServerJMSObjectMessage.class, jmsMessage.getClass());
}
/**
* Test that an amqp-value body containing a map results in an MapMessage when not otherwise
* annotated to indicate the type of JMS message it is.
*
* @throws Exception
* if an error occurs during the test.
*/
@Test
public void testCreateAmqpMapMessageFromAmqpValueWithMap() throws Exception {
Message message = Proton.message();
Map<String, String> map = new HashMap<>();
message.setBody(new AmqpValue(map));
EncodedMessage em = encodeMessage(message);
JMSMappingInboundTransformer transformer = new JMSMappingInboundTransformer(idGenerator);
javax.jms.Message jmsMessage = transformer.transform(em);
assertNotNull("Message should not be null", jmsMessage);
assertEquals("Unexpected message class type", ServerJMSMapMessage.class, jmsMessage.getClass());
}
/**
* Test that an amqp-value body containing a map that has an AMQP Binary as one of the
* entries encoded into the Map results in an MapMessage where a byte array can be read from
* the entry.
*
* @throws Exception
* if an error occurs during the test.
*/
@Test
public void testCreateAmqpMapMessageFromAmqpValueWithMapContainingBinaryEntry() throws Exception {
final String ENTRY_NAME = "bytesEntry";
Message message = Proton.message();
Map<String, Object> map = new HashMap<>();
byte[] inputBytes = new byte[] {1, 2, 3, 4, 5};
map.put(ENTRY_NAME, new Binary(inputBytes));
message.setBody(new AmqpValue(map));
EncodedMessage em = encodeMessage(message);
JMSMappingInboundTransformer transformer = new JMSMappingInboundTransformer(idGenerator);
javax.jms.Message jmsMessage = transformer.transform(em);
assertNotNull("Message should not be null", jmsMessage);
assertEquals("Unexpected message class type", ServerJMSMapMessage.class, jmsMessage.getClass());
MapMessage mapMessage = (MapMessage) jmsMessage;
byte[] outputBytes = mapMessage.getBytes(ENTRY_NAME);
assertNotNull(outputBytes);
assertTrue(Arrays.equals(inputBytes, outputBytes));
}
/**
* Test that an amqp-value body containing a list results in an StreamMessage when not
* otherwise annotated to indicate the type of JMS message it is.
*
* @throws Exception
* if an error occurs during the test.
*/
@Test
public void testCreateAmqpStreamMessageFromAmqpValueWithList() throws Exception {
Message message = Proton.message();
List<String> list = new ArrayList<>();
message.setBody(new AmqpValue(list));
EncodedMessage em = encodeMessage(message);
JMSMappingInboundTransformer transformer = new JMSMappingInboundTransformer(idGenerator);
javax.jms.Message jmsMessage = transformer.transform(em);
assertNotNull("Message should not be null", jmsMessage);
assertEquals("Unexpected message class type", ServerJMSStreamMessage.class, jmsMessage.getClass());
}
/**
* Test that an amqp-sequence body containing a list results in an StreamMessage when not
* otherwise annotated to indicate the type of JMS message it is.
*
* @throws Exception
* if an error occurs during the test.
*/
@Test
public void testCreateAmqpStreamMessageFromAmqpSequence() throws Exception {
Message message = Proton.message();
List<String> list = new ArrayList<>();
message.setBody(new AmqpSequence(list));
EncodedMessage em = encodeMessage(message);
JMSMappingInboundTransformer transformer = new JMSMappingInboundTransformer(idGenerator);
javax.jms.Message jmsMessage = transformer.transform(em);
assertNotNull("Message should not be null", jmsMessage);
assertEquals("Unexpected message class type", ServerJMSStreamMessage.class, jmsMessage.getClass());
}
/**
* Test that an amqp-value body containing a binary value results in BytesMessage when not
* otherwise annotated to indicate the type of JMS message it is.
*
* @throws Exception
* if an error occurs during the test.
*/
@Test
public void testCreateAmqpBytesMessageFromAmqpValueWithBinary() throws Exception {
Message message = Proton.message();
Binary binary = new Binary(new byte[0]);
message.setBody(new AmqpValue(binary));
EncodedMessage em = encodeMessage(message);
JMSMappingInboundTransformer transformer = new JMSMappingInboundTransformer(idGenerator);
javax.jms.Message jmsMessage = transformer.transform(em);
assertNotNull("Message should not be null", jmsMessage);
assertEquals("Unexpected message class type", ServerJMSBytesMessage.class, jmsMessage.getClass());
}
/**
* Test that an amqp-value body containing a value which can't be categorized results in an
* exception from the transformer and then try the transformer's own fallback transformer to
* result in an BytesMessage.
*
* @throws Exception
* if an error occurs during the test.
*/
@Test
public void testCreateBytesMessageFromAmqpValueWithUncategorisedContent() throws Exception {
Message message = Proton.message();
message.setBody(new AmqpValue(UUID.randomUUID()));
EncodedMessage em = encodeMessage(message);
JMSMappingInboundTransformer transformer = new JMSMappingInboundTransformer(idGenerator);
javax.jms.Message jmsMessage = transformer.transform(em);
assertNotNull("Message should not be null", jmsMessage);
assertEquals("Unexpected message class type", ServerJMSBytesMessage.class, jmsMessage.getClass());
}
@Test
public void testTransformMessageWithAmqpValueStringCreatesTextMessage() throws Exception {
String contentString = "myTextMessageContent";
Message message = Message.Factory.create();
message.setBody(new AmqpValue(contentString));
EncodedMessage em = encodeMessage(message);
JMSMappingInboundTransformer transformer = new JMSMappingInboundTransformer(idGenerator);
javax.jms.Message jmsMessage = transformer.transform(em);
assertTrue("Expected TextMessage", jmsMessage instanceof TextMessage);
assertEquals("Unexpected message class type", ServerJMSTextMessage.class, jmsMessage.getClass());
TextMessage textMessage = (TextMessage) jmsMessage;
assertNotNull(textMessage.getText());
assertEquals(contentString, textMessage.getText());
}
// ----- Destination Conversions ------------------------------------------//
@Test
public void testTransformWithNoToTypeDestinationTypeAnnotation() throws Exception {
doTransformWithToTypeDestinationTypeAnnotationTestImpl(null, Destination.class);
}
@Test
public void testTransformWithQueueStringToTypeDestinationTypeAnnotation() throws Exception {
doTransformWithToTypeDestinationTypeAnnotationTestImpl("queue", Queue.class);
}
@Test
public void testTransformWithTemporaryQueueStringToTypeDestinationTypeAnnotation() throws Exception {
doTransformWithToTypeDestinationTypeAnnotationTestImpl("queue,temporary", TemporaryQueue.class);
}
@Test
public void testTransformWithTopicStringToTypeDestinationTypeAnnotation() throws Exception {
doTransformWithToTypeDestinationTypeAnnotationTestImpl("topic", Topic.class);
}
@Test
public void testTransformWithTemporaryTopicStringToTypeDestinationTypeAnnotation() throws Exception {
doTransformWithToTypeDestinationTypeAnnotationTestImpl("topic,temporary", TemporaryTopic.class);
}
private void doTransformWithToTypeDestinationTypeAnnotationTestImpl(Object toTypeAnnotationValue, Class<? extends Destination> expectedClass)
throws Exception {
JMSMappingInboundTransformer transformer = new JMSMappingInboundTransformer(idGenerator);
String toAddress = "toAddress";
Message amqp = Message.Factory.create();
amqp.setBody(new AmqpValue("myTextMessageContent"));
amqp.setAddress(toAddress);
if (toTypeAnnotationValue != null) {
Map<Symbol, Object> map = new HashMap<>();
map.put(Symbol.valueOf("x-opt-to-type"), toTypeAnnotationValue);
MessageAnnotations ma = new MessageAnnotations(map);
amqp.setMessageAnnotations(ma);
}
EncodedMessage em = encodeMessage(amqp);
javax.jms.Message jmsMessage = transformer.transform(em);
assertTrue("Expected TextMessage", jmsMessage instanceof TextMessage);
}
// ----- ReplyTo Conversions ----------------------------------------------//
@Test
public void testTransformWithNoReplyToTypeDestinationTypeAnnotation() throws Exception {
doTransformWithReplyToTypeDestinationTypeAnnotationTestImpl(null, Destination.class);
}
@Test
public void testTransformWithQueueStringReplyToTypeDestinationTypeAnnotation() throws Exception {
doTransformWithReplyToTypeDestinationTypeAnnotationTestImpl("queue", Queue.class);
}
@Test
public void testTransformWithTemporaryQueueStringReplyToTypeDestinationTypeAnnotation() throws Exception {
doTransformWithReplyToTypeDestinationTypeAnnotationTestImpl("queue,temporary", TemporaryQueue.class);
}
@Test
public void testTransformWithTopicStringReplyToTypeDestinationTypeAnnotation() throws Exception {
doTransformWithReplyToTypeDestinationTypeAnnotationTestImpl("topic", Topic.class);
}
@Test
public void testTransformWithTemporaryTopicStringReplyToTypeDestinationTypeAnnotation() throws Exception {
doTransformWithReplyToTypeDestinationTypeAnnotationTestImpl("topic,temporary", TemporaryTopic.class);
}
private void doTransformWithReplyToTypeDestinationTypeAnnotationTestImpl(Object replyToTypeAnnotationValue, Class<? extends Destination> expectedClass)
throws Exception {
JMSMappingInboundTransformer transformer = new JMSMappingInboundTransformer(idGenerator);
String replyToAddress = "replyToAddress";
Message amqp = Message.Factory.create();
amqp.setBody(new AmqpValue("myTextMessageContent"));
amqp.setReplyTo(replyToAddress);
if (replyToTypeAnnotationValue != null) {
Map<Symbol, Object> map = new HashMap<>();
map.put(Symbol.valueOf("x-opt-reply-type"), replyToTypeAnnotationValue);
MessageAnnotations ma = new MessageAnnotations(map);
amqp.setMessageAnnotations(ma);
}
EncodedMessage em = encodeMessage(amqp);
javax.jms.Message jmsMessage = transformer.transform(em);
assertTrue("Expected TextMessage", jmsMessage instanceof TextMessage);
}
// ----- Utility Methods --------------------------------------------------//
private EncodedMessage encodeMessage(Message message) {
byte[] encodeBuffer = new byte[1024 * 8];
int encodedSize;
while (true) {
try {
encodedSize = message.encode(encodeBuffer, 0, encodeBuffer.length);
break;
} catch (java.nio.BufferOverflowException e) {
encodeBuffer = new byte[encodeBuffer.length * 2];
}
}
long messageFormat = 0;
return new EncodedMessage(messageFormat, encodeBuffer, 0, encodedSize);
}
}

View File

@ -0,0 +1,952 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.artemis.protocol.amqp.converter.message;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.AMQP_DATA;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.AMQP_NULL;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.AMQP_SEQUENCE;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.AMQP_UNKNOWN;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.AMQP_VALUE_BINARY;
import static org.apache.activemq.artemis.protocol.amqp.converter.message.AMQPMessageSupport.JMS_AMQP_ORIGINAL_ENCODING;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.jms.JMSException;
import org.apache.activemq.artemis.core.buffers.impl.ResetLimitWrappedActiveMQBuffer;
import org.apache.activemq.artemis.core.server.impl.ServerMessageImpl;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerDestination;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSBytesMessage;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSMapMessage;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSMessage;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSObjectMessage;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSStreamMessage;
import org.apache.activemq.artemis.protocol.amqp.converter.jms.ServerJMSTextMessage;
import org.apache.activemq.artemis.protocol.amqp.util.NettyWritable;
import org.apache.activemq.artemis.utils.IDGenerator;
import org.apache.activemq.artemis.utils.SimpleIDGenerator;
import org.apache.qpid.proton.amqp.Binary;
import org.apache.qpid.proton.amqp.Symbol;
import org.apache.qpid.proton.amqp.messaging.AmqpSequence;
import org.apache.qpid.proton.amqp.messaging.AmqpValue;
import org.apache.qpid.proton.amqp.messaging.Data;
import org.apache.qpid.proton.amqp.messaging.MessageAnnotations;
import org.apache.qpid.proton.message.Message;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
public class JMSMappingOutboundTransformerTest {
private final UUID TEST_OBJECT_VALUE = UUID.fromString("fee14b62-09e0-4ac6-a4c3-4206c630d844");
private final String TEST_ADDRESS = "queue://testAddress";
private IDGenerator idGenerator;
private JMSMappingOutboundTransformer transformer;
public static final byte QUEUE_TYPE = 0x00;
public static final byte TOPIC_TYPE = 0x01;
public static final byte TEMP_QUEUE_TYPE = 0x02;
public static final byte TEMP_TOPIC_TYPE = 0x03;
@Before
public void setUp() {
idGenerator = new SimpleIDGenerator(0);
transformer = new JMSMappingOutboundTransformer(idGenerator);
}
// ----- no-body Message type tests ---------------------------------------//
@Test
public void testConvertMessageToAmqpMessageWithNoBody() throws Exception {
ServerJMSMessage outbound = createMessage();
outbound.encode();
EncodedMessage encoded = transform(outbound);
assertNotNull(encoded);
Message amqp = encoded.decode();
assertNull(amqp.getBody());
}
@Test
public void testConvertTextMessageToAmqpMessageWithNoBodyOriginalEncodingWasNull() throws Exception {
ServerJMSTextMessage outbound = createTextMessage();
outbound.setShortProperty(JMS_AMQP_ORIGINAL_ENCODING, AMQP_NULL);
outbound.encode();
EncodedMessage encoded = transform(outbound);
assertNotNull(encoded);
Message amqp = encoded.decode();
assertNull(amqp.getBody());
}
// ----- BytesMessage type tests ---------------------------------------//
@Test
public void testConvertEmptyBytesMessageToAmqpMessageWithDataBody() throws Exception {
ServerJMSBytesMessage outbound = createBytesMessage();
outbound.encode();
EncodedMessage encoded = transform(outbound);
assertNotNull(encoded);
Message amqp = encoded.decode();
assertNotNull(amqp.getBody());
assertTrue(amqp.getBody() instanceof Data);
assertTrue(((Data) amqp.getBody()).getValue() instanceof Binary);
assertEquals(0, ((Data) amqp.getBody()).getValue().getLength());
}
@Test
public void testConvertUncompressedBytesMessageToAmqpMessageWithDataBody() throws Exception {
byte[] expectedPayload = new byte[] {8, 16, 24, 32};
ServerJMSBytesMessage outbound = createBytesMessage();
outbound.writeBytes(expectedPayload);
outbound.encode();
EncodedMessage encoded = transform(outbound);
assertNotNull(encoded);
Message amqp = encoded.decode();
assertNotNull(amqp.getBody());
assertTrue(amqp.getBody() instanceof Data);
assertTrue(((Data) amqp.getBody()).getValue() instanceof Binary);
assertEquals(4, ((Data) amqp.getBody()).getValue().getLength());
Binary amqpData = ((Data) amqp.getBody()).getValue();
Binary inputData = new Binary(expectedPayload);
assertTrue(inputData.equals(amqpData));
}
@Ignore("Compressed message body support not yet implemented.")
@Test
public void testConvertCompressedBytesMessageToAmqpMessageWithDataBody() throws Exception {
byte[] expectedPayload = new byte[] {8, 16, 24, 32};
ServerJMSBytesMessage outbound = createBytesMessage(true);
outbound.writeBytes(expectedPayload);
outbound.encode();
EncodedMessage encoded = transform(outbound);
assertNotNull(encoded);
Message amqp = encoded.decode();
assertNotNull(amqp.getBody());
assertTrue(amqp.getBody() instanceof Data);
assertTrue(((Data) amqp.getBody()).getValue() instanceof Binary);
assertEquals(4, ((Data) amqp.getBody()).getValue().getLength());
Binary amqpData = ((Data) amqp.getBody()).getValue();
Binary inputData = new Binary(expectedPayload);
assertTrue(inputData.equals(amqpData));
}
@Test
public void testConvertEmptyBytesMessageToAmqpMessageWithAmqpValueBody() throws Exception {
ServerJMSBytesMessage outbound = createBytesMessage();
outbound.setShortProperty(JMS_AMQP_ORIGINAL_ENCODING, AMQP_VALUE_BINARY);
outbound.encode();
EncodedMessage encoded = transform(outbound);
assertNotNull(encoded);
Message amqp = encoded.decode();
assertNotNull(amqp.getBody());
assertTrue(amqp.getBody() instanceof AmqpValue);
assertTrue(((AmqpValue) amqp.getBody()).getValue() instanceof Binary);
assertEquals(0, ((Binary) ((AmqpValue) amqp.getBody()).getValue()).getLength());
}
@Test
public void testConvertUncompressedBytesMessageToAmqpMessageWithAmqpValueBody() throws Exception {
byte[] expectedPayload = new byte[] {8, 16, 24, 32};
ServerJMSBytesMessage outbound = createBytesMessage();
outbound.setShortProperty(JMS_AMQP_ORIGINAL_ENCODING, AMQP_VALUE_BINARY);
outbound.writeBytes(expectedPayload);
outbound.encode();
EncodedMessage encoded = transform(outbound);
assertNotNull(encoded);
Message amqp = encoded.decode();
assertNotNull(amqp.getBody());
assertTrue(amqp.getBody() instanceof AmqpValue);
assertTrue(((AmqpValue) amqp.getBody()).getValue() instanceof Binary);
assertEquals(4, ((Binary) ((AmqpValue) amqp.getBody()).getValue()).getLength());
Binary amqpData = (Binary) ((AmqpValue) amqp.getBody()).getValue();
Binary inputData = new Binary(expectedPayload);
assertTrue(inputData.equals(amqpData));
}
@Ignore("Compressed message body support not yet implemented.")
@Test
public void testConvertCompressedBytesMessageToAmqpMessageWithAmqpValueBody() throws Exception {
byte[] expectedPayload = new byte[] {8, 16, 24, 32};
ServerJMSBytesMessage outbound = createBytesMessage(true);
outbound.setShortProperty(JMS_AMQP_ORIGINAL_ENCODING, AMQP_VALUE_BINARY);
outbound.writeBytes(expectedPayload);
outbound.encode();
EncodedMessage encoded = transform(outbound);
assertNotNull(encoded);
Message amqp = encoded.decode();
assertNotNull(amqp.getBody());
assertTrue(amqp.getBody() instanceof AmqpValue);
assertTrue(((AmqpValue) amqp.getBody()).getValue() instanceof Binary);
assertEquals(4, ((Binary) ((AmqpValue) amqp.getBody()).getValue()).getLength());
Binary amqpData = (Binary) ((AmqpValue) amqp.getBody()).getValue();
Binary inputData = new Binary(expectedPayload);
assertTrue(inputData.equals(amqpData));
}
// ----- MapMessage type tests --------------------------------------------//
@Test
public void testConvertMapMessageToAmqpMessageWithNoBody() throws Exception {
ServerJMSMapMessage outbound = createMapMessage();
outbound.encode();
EncodedMessage encoded = transform(outbound);
assertNotNull(encoded);
Message amqp = encoded.decode();
assertNotNull(amqp.getBody());
assertTrue(amqp.getBody() instanceof AmqpValue);
assertTrue(((AmqpValue) amqp.getBody()).getValue() instanceof Map);
}
@Test
public void testConvertMapMessageToAmqpMessageWithByteArrayValueInBody() throws Exception {
final byte[] byteArray = new byte[] {1, 2, 3, 4, 5};
ServerJMSMapMessage outbound = createMapMessage();
outbound.setBytes("bytes", byteArray);
outbound.encode();
EncodedMessage encoded = transform(outbound);
assertNotNull(encoded);
Message amqp = encoded.decode();
assertNotNull(amqp.getBody());
assertTrue(amqp.getBody() instanceof AmqpValue);
assertTrue(((AmqpValue) amqp.getBody()).getValue() instanceof Map);
@SuppressWarnings("unchecked")
Map<Object, Object> amqpMap = (Map<Object, Object>) ((AmqpValue) amqp.getBody()).getValue();
assertEquals(1, amqpMap.size());
Binary readByteArray = (Binary) amqpMap.get("bytes");
assertNotNull(readByteArray);
}
@Test
public void testConvertMapMessageToAmqpMessage() throws Exception {
ServerJMSMapMessage outbound = createMapMessage();
outbound.setString("property-1", "string");
outbound.setInt("property-2", 1);
outbound.setBoolean("property-3", true);
outbound.encode();
EncodedMessage encoded = transform(outbound);
assertNotNull(encoded);
Message amqp = encoded.decode();
assertNotNull(amqp.getBody());
assertTrue(amqp.getBody() instanceof AmqpValue);
assertTrue(((AmqpValue) amqp.getBody()).getValue() instanceof Map);
@SuppressWarnings("unchecked")
Map<Object, Object> amqpMap = (Map<Object, Object>) ((AmqpValue) amqp.getBody()).getValue();
assertEquals(3, amqpMap.size());
assertTrue("string".equals(amqpMap.get("property-1")));
}
@Test
public void testConvertCompressedMapMessageToAmqpMessage() throws Exception {
ServerJMSMapMessage outbound = createMapMessage(true);
outbound.setString("property-1", "string");
outbound.setInt("property-2", 1);
outbound.setBoolean("property-3", true);
outbound.encode();
EncodedMessage encoded = transform(outbound);
assertNotNull(encoded);
Message amqp = encoded.decode();
assertNotNull(amqp.getBody());
assertTrue(amqp.getBody() instanceof AmqpValue);
assertTrue(((AmqpValue) amqp.getBody()).getValue() instanceof Map);
@SuppressWarnings("unchecked")
Map<Object, Object> amqpMap = (Map<Object, Object>) ((AmqpValue) amqp.getBody()).getValue();
assertEquals(3, amqpMap.size());
assertTrue("string".equals(amqpMap.get("property-1")));
}
// ----- StreamMessage type tests -----------------------------------------//
@Test
public void testConvertStreamMessageToAmqpMessageWithAmqpValueBody() throws Exception {
ServerJMSStreamMessage outbound = createStreamMessage();
outbound.encode();
EncodedMessage encoded = transform(outbound);
assertNotNull(encoded);
Message amqp = encoded.decode();
assertNotNull(amqp.getBody());
assertTrue(amqp.getBody() instanceof AmqpValue);
assertTrue(((AmqpValue) amqp.getBody()).getValue() instanceof List);
}
@Test
public void testConvertStreamMessageToAmqpMessageWithAmqpSequencey() throws Exception {
ServerJMSStreamMessage outbound = createStreamMessage();
outbound.setShortProperty(JMS_AMQP_ORIGINAL_ENCODING, AMQP_SEQUENCE);
outbound.encode();
EncodedMessage encoded = transform(outbound);
assertNotNull(encoded);
Message amqp = encoded.decode();
assertNotNull(amqp.getBody());
assertTrue(amqp.getBody() instanceof AmqpSequence);
assertTrue(((AmqpSequence) amqp.getBody()).getValue() instanceof List);
}
@Test
public void testConvertCompressedStreamMessageToAmqpMessageWithAmqpValueBody() throws Exception {
ServerJMSStreamMessage outbound = createStreamMessage(true);
outbound.writeBoolean(false);
outbound.writeString("test");
outbound.encode();
EncodedMessage encoded = transform(outbound);
assertNotNull(encoded);
Message amqp = encoded.decode();
assertNotNull(amqp.getBody());
assertTrue(amqp.getBody() instanceof AmqpValue);
assertTrue(((AmqpValue) amqp.getBody()).getValue() instanceof List);
@SuppressWarnings("unchecked")
List<Object> amqpList = (List<Object>) ((AmqpValue) amqp.getBody()).getValue();
assertEquals(2, amqpList.size());
}
@Test
public void testConvertCompressedStreamMessageToAmqpMessageWithAmqpSequencey() throws Exception {
ServerJMSStreamMessage outbound = createStreamMessage(true);
outbound.setShortProperty(JMS_AMQP_ORIGINAL_ENCODING, AMQP_SEQUENCE);
outbound.writeBoolean(false);
outbound.writeString("test");
outbound.encode();
EncodedMessage encoded = transform(outbound);
assertNotNull(encoded);
Message amqp = encoded.decode();
assertNotNull(amqp.getBody());
assertTrue(amqp.getBody() instanceof AmqpSequence);
assertTrue(((AmqpSequence) amqp.getBody()).getValue() instanceof List);
@SuppressWarnings("unchecked")
List<Object> amqpList = ((AmqpSequence) amqp.getBody()).getValue();
assertEquals(2, amqpList.size());
}
// ----- ObjectMessage type tests -----------------------------------------//
@Test
public void testConvertEmptyObjectMessageToAmqpMessageWithDataBody() throws Exception {
ServerJMSObjectMessage outbound = createObjectMessage();
outbound.encode();
EncodedMessage encoded = transform(outbound);
assertNotNull(encoded);
Message amqp = encoded.decode();
assertNotNull(amqp.getBody());
assertTrue(amqp.getBody() instanceof Data);
assertEquals(5, ((Data) amqp.getBody()).getValue().getLength());
}
@Test
public void testConvertEmptyObjectMessageToAmqpMessageUnknownEncodingGetsDataSection() throws Exception {
ServerJMSObjectMessage outbound = createObjectMessage();
outbound.setShortProperty(JMS_AMQP_ORIGINAL_ENCODING, AMQP_UNKNOWN);
outbound.encode();
EncodedMessage encoded = transform(outbound);
assertNotNull(encoded);
Message amqp = encoded.decode();
assertNotNull(amqp.getBody());
assertTrue(amqp.getBody() instanceof Data);
assertEquals(5, ((Data) amqp.getBody()).getValue().getLength());
}
@Test
public void testConvertEmptyObjectMessageToAmqpMessageWithAmqpValueBody() throws Exception {
ServerJMSObjectMessage outbound = createObjectMessage();
outbound.setShortProperty(JMS_AMQP_ORIGINAL_ENCODING, AMQP_VALUE_BINARY);
outbound.encode();
EncodedMessage encoded = transform(outbound);
assertNotNull(encoded);
Message amqp = encoded.decode();
assertNotNull(amqp.getBody());
assertTrue(amqp.getBody() instanceof AmqpValue);
assertTrue(((AmqpValue) amqp.getBody()).getValue() instanceof Binary);
assertEquals(5, ((Binary) ((AmqpValue) amqp.getBody()).getValue()).getLength());
}
@Test
public void testConvertObjectMessageToAmqpMessageWithDataBody() throws Exception {
ServerJMSObjectMessage outbound = createObjectMessage(TEST_OBJECT_VALUE);
outbound.encode();
EncodedMessage encoded = transform(outbound);
assertNotNull(encoded);
Message amqp = encoded.decode();
assertNotNull(amqp.getBody());
assertTrue(amqp.getBody() instanceof Data);
assertFalse(0 == ((Data) amqp.getBody()).getValue().getLength());
Object value = deserialize(((Data) amqp.getBody()).getValue().getArray());
assertNotNull(value);
assertTrue(value instanceof UUID);
}
@Test
public void testConvertObjectMessageToAmqpMessageUnknownEncodingGetsDataSection() throws Exception {
ServerJMSObjectMessage outbound = createObjectMessage(TEST_OBJECT_VALUE);
outbound.setShortProperty(JMS_AMQP_ORIGINAL_ENCODING, AMQP_UNKNOWN);
outbound.encode();
EncodedMessage encoded = transform(outbound);
assertNotNull(encoded);
Message amqp = encoded.decode();
assertNotNull(amqp.getBody());
assertTrue(amqp.getBody() instanceof Data);
assertFalse(0 == ((Data) amqp.getBody()).getValue().getLength());
Object value = deserialize(((Data) amqp.getBody()).getValue().getArray());
assertNotNull(value);
assertTrue(value instanceof UUID);
}
@Test
public void testConvertObjectMessageToAmqpMessageWithAmqpValueBody() throws Exception {
ServerJMSObjectMessage outbound = createObjectMessage(TEST_OBJECT_VALUE);
outbound.setShortProperty(JMS_AMQP_ORIGINAL_ENCODING, AMQP_VALUE_BINARY);
outbound.encode();
EncodedMessage encoded = transform(outbound);
assertNotNull(encoded);
Message amqp = encoded.decode();
assertNotNull(amqp.getBody());
assertTrue(amqp.getBody() instanceof AmqpValue);
assertTrue(((AmqpValue) amqp.getBody()).getValue() instanceof Binary);
assertFalse(0 == ((Binary) ((AmqpValue) amqp.getBody()).getValue()).getLength());
Object value = deserialize(((Binary) ((AmqpValue) amqp.getBody()).getValue()).getArray());
assertNotNull(value);
assertTrue(value instanceof UUID);
}
@Test
public void testConvertCompressedObjectMessageToAmqpMessageWithDataBody() throws Exception {
ServerJMSObjectMessage outbound = createObjectMessage(TEST_OBJECT_VALUE, true);
outbound.encode();
EncodedMessage encoded = transform(outbound);
assertNotNull(encoded);
Message amqp = encoded.decode();
assertNotNull(amqp.getBody());
assertTrue(amqp.getBody() instanceof Data);
assertFalse(0 == ((Data) amqp.getBody()).getValue().getLength());
Object value = deserialize(((Data) amqp.getBody()).getValue().getArray());
assertNotNull(value);
assertTrue(value instanceof UUID);
}
@Test
public void testConvertCompressedObjectMessageToAmqpMessageUnknownEncodingGetsDataSection() throws Exception {
ServerJMSObjectMessage outbound = createObjectMessage(TEST_OBJECT_VALUE, true);
outbound.setShortProperty(JMS_AMQP_ORIGINAL_ENCODING, AMQP_UNKNOWN);
outbound.encode();
EncodedMessage encoded = transform(outbound);
assertNotNull(encoded);
Message amqp = encoded.decode();
assertNotNull(amqp.getBody());
assertTrue(amqp.getBody() instanceof Data);
assertFalse(0 == ((Data) amqp.getBody()).getValue().getLength());
Object value = deserialize(((Data) amqp.getBody()).getValue().getArray());
assertNotNull(value);
assertTrue(value instanceof UUID);
}
@Test
public void testConvertCompressedObjectMessageToAmqpMessageWithAmqpValueBody() throws Exception {
ServerJMSObjectMessage outbound = createObjectMessage(TEST_OBJECT_VALUE, true);
outbound.setShortProperty(JMS_AMQP_ORIGINAL_ENCODING, AMQP_VALUE_BINARY);
outbound.encode();
EncodedMessage encoded = transform(outbound);
assertNotNull(encoded);
Message amqp = encoded.decode();
assertNotNull(amqp.getBody());
assertTrue(amqp.getBody() instanceof AmqpValue);
assertTrue(((AmqpValue) amqp.getBody()).getValue() instanceof Binary);
assertFalse(0 == ((Binary) ((AmqpValue) amqp.getBody()).getValue()).getLength());
Object value = deserialize(((Binary) ((AmqpValue) amqp.getBody()).getValue()).getArray());
assertNotNull(value);
assertTrue(value instanceof UUID);
}
// ----- TextMessage type tests -------------------------------------------//
@Test
public void testConvertTextMessageToAmqpMessageWithNoBody() throws Exception {
ServerJMSTextMessage outbound = createTextMessage();
outbound.encode();
EncodedMessage encoded = transform(outbound);
assertNotNull(encoded);
Message amqp = encoded.decode();
assertNotNull(amqp.getBody());
assertTrue(amqp.getBody() instanceof AmqpValue);
assertNull(((AmqpValue) amqp.getBody()).getValue());
}
@Test
public void testConvertTextMessageCreatesBodyUsingOriginalEncodingWithDataSection() throws Exception {
String contentString = "myTextMessageContent";
ServerJMSTextMessage outbound = createTextMessage(contentString);
outbound.setShortProperty(JMS_AMQP_ORIGINAL_ENCODING, AMQP_DATA);
outbound.encode();
EncodedMessage encoded = transform(outbound);
assertNotNull(encoded);
Message amqp = encoded.decode();
assertNotNull(amqp.getBody());
assertTrue(amqp.getBody() instanceof Data);
assertTrue(((Data) amqp.getBody()).getValue() instanceof Binary);
Binary data = ((Data) amqp.getBody()).getValue();
String contents = new String(data.getArray(), data.getArrayOffset(), data.getLength(), StandardCharsets.UTF_8);
assertEquals(contentString, contents);
}
@Test
public void testConvertTextMessageContentNotStoredCreatesBodyUsingOriginalEncodingWithDataSection() throws Exception {
String contentString = "myTextMessageContent";
ServerJMSTextMessage outbound = createTextMessage(contentString);
outbound.setShortProperty(JMS_AMQP_ORIGINAL_ENCODING, AMQP_DATA);
outbound.encode();
EncodedMessage encoded = transform(outbound);
assertNotNull(encoded);
Message amqp = encoded.decode();
assertNotNull(amqp.getBody());
assertTrue(amqp.getBody() instanceof Data);
assertTrue(((Data) amqp.getBody()).getValue() instanceof Binary);
Binary data = ((Data) amqp.getBody()).getValue();
String contents = new String(data.getArray(), data.getArrayOffset(), data.getLength(), StandardCharsets.UTF_8);
assertEquals(contentString, contents);
}
@Test
public void testConvertTextMessageCreatesAmqpValueStringBody() throws Exception {
String contentString = "myTextMessageContent";
ServerJMSTextMessage outbound = createTextMessage(contentString);
outbound.encode();
EncodedMessage encoded = transform(outbound);
assertNotNull(encoded);
Message amqp = encoded.decode();
assertNotNull(amqp.getBody());
assertTrue(amqp.getBody() instanceof AmqpValue);
assertEquals(contentString, ((AmqpValue) amqp.getBody()).getValue());
}
@Test
public void testConvertTextMessageContentNotStoredCreatesAmqpValueStringBody() throws Exception {
String contentString = "myTextMessageContent";
ServerJMSTextMessage outbound = createTextMessage(contentString);
outbound.encode();
EncodedMessage encoded = transform(outbound);
assertNotNull(encoded);
Message amqp = encoded.decode();
assertNotNull(amqp.getBody());
assertTrue(amqp.getBody() instanceof AmqpValue);
assertEquals(contentString, ((AmqpValue) amqp.getBody()).getValue());
}
@Test
public void testConvertCompressedTextMessageCreatesDataSectionBody() throws Exception {
String contentString = "myTextMessageContent";
ServerJMSTextMessage outbound = createTextMessage(contentString, true);
outbound.setShortProperty(JMS_AMQP_ORIGINAL_ENCODING, AMQP_DATA);
outbound.encode();
EncodedMessage encoded = transform(outbound);
assertNotNull(encoded);
Message amqp = encoded.decode();
assertNotNull(amqp.getBody());
assertTrue(amqp.getBody() instanceof Data);
assertTrue(((Data) amqp.getBody()).getValue() instanceof Binary);
Binary data = ((Data) amqp.getBody()).getValue();
String contents = new String(data.getArray(), data.getArrayOffset(), data.getLength(), StandardCharsets.UTF_8);
assertEquals(contentString, contents);
}
// ----- Test JMSDestination Handling -------------------------------------//
@Test
public void testConvertMessageWithJMSDestinationNull() throws Exception {
doTestConvertMessageWithJMSDestination(null, null);
}
@Test
public void testConvertMessageWithJMSDestinationQueue() throws Exception {
doTestConvertMessageWithJMSDestination(createDestination(QUEUE_TYPE), QUEUE_TYPE);
}
@Ignore("Artemis code doesn't provide a means of supplying a typed destination to AMQP")
@Test
public void testConvertMessageWithJMSDestinationTemporaryQueue() throws Exception {
doTestConvertMessageWithJMSDestination(createDestination(TEMP_QUEUE_TYPE), TEMP_QUEUE_TYPE);
}
@Ignore("Artemis code doesn't provide a means of supplying a typed destination to AMQP")
@Test
public void testConvertMessageWithJMSDestinationTopic() throws Exception {
doTestConvertMessageWithJMSDestination(createDestination(TOPIC_TYPE), TOPIC_TYPE);
}
@Ignore("Artemis code doesn't provide a means of supplying a typed destination to AMQP")
@Test
public void testConvertMessageWithJMSDestinationTemporaryTopic() throws Exception {
doTestConvertMessageWithJMSDestination(createDestination(TEMP_TOPIC_TYPE), TEMP_TOPIC_TYPE);
}
private void doTestConvertMessageWithJMSDestination(ServerDestination jmsDestination, Object expectedAnnotationValue) throws Exception {
ServerJMSTextMessage textMessage = createTextMessage();
textMessage.setText("myTextMessageContent");
textMessage.setJMSDestination(jmsDestination);
EncodedMessage encoded = transform(textMessage);
assertNotNull(encoded);
Message amqp = encoded.decode();
MessageAnnotations ma = amqp.getMessageAnnotations();
Map<Symbol, Object> maMap = ma == null ? null : ma.getValue();
if (maMap != null) {
Object actualValue = maMap.get(JMSMappingOutboundTransformer.JMS_DEST_TYPE_MSG_ANNOTATION);
assertEquals("Unexpected annotation value", expectedAnnotationValue, actualValue);
} else if (expectedAnnotationValue != null) {
fail("Expected annotation value, but there were no annotations");
}
if (jmsDestination != null) {
assertEquals("Unexpected 'to' address", jmsDestination.getAddress(), amqp.getAddress());
}
}
// ----- Test JMSReplyTo Handling -----------------------------------------//
@Test
public void testConvertMessageWithJMSReplyToNull() throws Exception {
doTestConvertMessageWithJMSReplyTo(null, null);
}
@Test
public void testConvertMessageWithJMSReplyToQueue() throws Exception {
doTestConvertMessageWithJMSReplyTo(createDestination(QUEUE_TYPE), QUEUE_TYPE);
}
@Ignore("Artemis code doesn't provide a means of supplying a typed destination to AMQP")
@Test
public void testConvertMessageWithJMSReplyToTemporaryQueue() throws Exception {
doTestConvertMessageWithJMSReplyTo(createDestination(TEMP_QUEUE_TYPE), TEMP_QUEUE_TYPE);
}
@Ignore("Artemis code doesn't provide a means of supplying a typed destination to AMQP")
@Test
public void testConvertMessageWithJMSReplyToTopic() throws Exception {
doTestConvertMessageWithJMSReplyTo(createDestination(TOPIC_TYPE), TOPIC_TYPE);
}
@Ignore("Artemis code doesn't provide a means of supplying a typed destination to AMQP")
@Test
public void testConvertMessageWithJMSReplyToTemporaryTopic() throws Exception {
doTestConvertMessageWithJMSReplyTo(createDestination(TEMP_TOPIC_TYPE), TEMP_TOPIC_TYPE);
}
private void doTestConvertMessageWithJMSReplyTo(ServerDestination jmsReplyTo, Object expectedAnnotationValue) throws Exception {
ServerJMSTextMessage textMessage = createTextMessage();
textMessage.setText("myTextMessageContent");
textMessage.setJMSReplyTo(jmsReplyTo);
EncodedMessage encoded = transform(textMessage);
assertNotNull(encoded);
Message amqp = encoded.decode();
MessageAnnotations ma = amqp.getMessageAnnotations();
Map<Symbol, Object> maMap = ma == null ? null : ma.getValue();
if (maMap != null) {
Object actualValue = maMap.get(JMSMappingOutboundTransformer.JMS_REPLY_TO_TYPE_MSG_ANNOTATION);
assertEquals("Unexpected annotation value", expectedAnnotationValue, actualValue);
} else if (expectedAnnotationValue != null) {
fail("Expected annotation value, but there were no annotations");
}
if (jmsReplyTo != null) {
assertEquals("Unexpected 'reply-to' address", jmsReplyTo.getAddress(), amqp.getReplyTo());
}
}
// ----- Utility Methods used for this Test -------------------------------//
public EncodedMessage transform(ServerJMSMessage message) throws Exception {
// Useful for testing but not recommended for real life use.
ByteBuf nettyBuffer = Unpooled.buffer(1024);
NettyWritable buffer = new NettyWritable(nettyBuffer);
long messageFormat = transformer.transform(message, buffer);
EncodedMessage encoded = new EncodedMessage(messageFormat, nettyBuffer.array(), nettyBuffer.arrayOffset() + nettyBuffer.readerIndex(), nettyBuffer.readableBytes());
return encoded;
}
private ServerDestination createDestination(byte destType) {
ServerDestination destination = null;
switch (destType) {
case QUEUE_TYPE:
destination = new ServerDestination(TEST_ADDRESS);
break;
case TOPIC_TYPE:
destination = new ServerDestination(TEST_ADDRESS);
break;
case TEMP_QUEUE_TYPE:
destination = new ServerDestination(TEST_ADDRESS);
break;
case TEMP_TOPIC_TYPE:
destination = new ServerDestination(TEST_ADDRESS);
break;
default:
throw new IllegalArgumentException("Invliad Destination Type given/");
}
return destination;
}
private ServerJMSMessage createMessage() {
return new ServerJMSMessage(newMessage(org.apache.activemq.artemis.api.core.Message.DEFAULT_TYPE), 0);
}
private ServerJMSBytesMessage createBytesMessage() {
return createBytesMessage(false);
}
private ServerJMSBytesMessage createBytesMessage(boolean compression) {
ServerJMSBytesMessage message = new ServerJMSBytesMessage(newMessage(org.apache.activemq.artemis.api.core.Message.BYTES_TYPE), 0);
if (compression) {
// TODO
}
return message;
}
private ServerJMSMapMessage createMapMessage() {
return createMapMessage(false);
}
private ServerJMSMapMessage createMapMessage(boolean compression) {
ServerJMSMapMessage message = new ServerJMSMapMessage(newMessage(org.apache.activemq.artemis.api.core.Message.MAP_TYPE), 0);
if (compression) {
// TODO
}
return message;
}
private ServerJMSStreamMessage createStreamMessage() {
return createStreamMessage(false);
}
private ServerJMSStreamMessage createStreamMessage(boolean compression) {
ServerJMSStreamMessage message = new ServerJMSStreamMessage(newMessage(org.apache.activemq.artemis.api.core.Message.STREAM_TYPE), 0);
if (compression) {
// TODO
}
return message;
}
private ServerJMSObjectMessage createObjectMessage() {
return createObjectMessage(null);
}
private ServerJMSObjectMessage createObjectMessage(Serializable payload) {
return createObjectMessage(payload, false);
}
private ServerJMSObjectMessage createObjectMessage(Serializable payload, boolean compression) {
ServerJMSObjectMessage result = AMQPMessageSupport.createObjectMessage(idGenerator);
if (compression) {
// TODO
}
try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos);) {
oos.writeObject(payload);
byte[] data = baos.toByteArray();
result.setSerializedForm(new Binary(data));
} catch (Exception ex) {
throw new AssertionError("Should not fail to setObject in this test");
}
return result;
}
private ServerJMSTextMessage createTextMessage() {
return createTextMessage(null);
}
private ServerJMSTextMessage createTextMessage(String text) {
return createTextMessage(text, false);
}
private ServerJMSTextMessage createTextMessage(String text, boolean compression) {
ServerJMSTextMessage result = AMQPMessageSupport.createTextMessage(idGenerator);
if (compression) {
// TODO
}
try {
result.setText(text);
} catch (JMSException e) {
}
return result;
}
private Object deserialize(byte[] payload) throws Exception {
try (ByteArrayInputStream bis = new ByteArrayInputStream(payload); ObjectInputStream ois = new ObjectInputStream(bis);) {
return ois.readObject();
}
}
private ServerMessageImpl newMessage(byte messageType) {
ServerMessageImpl message = new ServerMessageImpl(idGenerator.generateID(), 512);
message.setType(messageType);
((ResetLimitWrappedActiveMQBuffer) message.getBodyBuffer()).setMessage(null);
return message;
}
}

View File

@ -0,0 +1,300 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.artemis.protocol.amqp.converter.message;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.apache.activemq.artemis.core.server.ServerMessage;
import org.apache.activemq.artemis.protocol.amqp.converter.ProtonMessageConverter;
import org.apache.activemq.artemis.protocol.amqp.util.NettyWritable;
import org.apache.activemq.artemis.utils.IDGenerator;
import org.apache.activemq.artemis.utils.SimpleIDGenerator;
import org.apache.qpid.proton.Proton;
import org.apache.qpid.proton.amqp.Symbol;
import org.apache.qpid.proton.amqp.messaging.AmqpValue;
import org.apache.qpid.proton.amqp.messaging.ApplicationProperties;
import org.apache.qpid.proton.amqp.messaging.MessageAnnotations;
import org.apache.qpid.proton.message.Message;
import org.apache.qpid.proton.message.ProtonJMessage;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
/**
* Some simple performance tests for the Message Transformers.
*/
@Ignore("Useful for profiling but slow and not meant as a unit test")
public class JMSTransformationSpeedComparisonTest {
@Rule
public TestName test = new TestName();
private IDGenerator idGenerator;
private ProtonMessageConverter converter;
private final int WARM_CYCLES = 1000;
private final int PROFILE_CYCLES = 1000000;
@Before
public void setUp() {
idGenerator = new SimpleIDGenerator(0);
converter = new ProtonMessageConverter(idGenerator);
}
@Test
public void testBodyOnlyMessage() throws Exception {
Message message = Proton.message();
message.setBody(new AmqpValue("String payload for AMQP message conversion performance testing."));
EncodedMessage encoded = encode(message);
// Warm up
for (int i = 0; i < WARM_CYCLES; ++i) {
ServerMessage intermediate = converter.inbound(encoded);
encode(converter.outbound(intermediate, 1));
}
long totalDuration = 0;
long startTime = System.nanoTime();
for (int i = 0; i < PROFILE_CYCLES; ++i) {
ServerMessage intermediate = converter.inbound(encoded);
encode(converter.outbound(intermediate, 1));
}
totalDuration += System.nanoTime() - startTime;
LOG_RESULTS(totalDuration);
}
@Test
public void testMessageWithNoPropertiesOrAnnotations() throws Exception {
Message message = Proton.message();
message.setAddress("queue://test-queue");
message.setDeliveryCount(1);
message.setCreationTime(System.currentTimeMillis());
message.setContentType("text/plain");
message.setBody(new AmqpValue("String payload for AMQP message conversion performance testing."));
EncodedMessage encoded = encode(message);
// Warm up
for (int i = 0; i < WARM_CYCLES; ++i) {
ServerMessage intermediate = converter.inbound(encoded);
encode(converter.outbound(intermediate, 1));
}
long totalDuration = 0;
long startTime = System.nanoTime();
for (int i = 0; i < PROFILE_CYCLES; ++i) {
ServerMessage intermediate = converter.inbound(encoded);
encode(converter.outbound(intermediate, 1));
}
totalDuration += System.nanoTime() - startTime;
LOG_RESULTS(totalDuration);
}
@Test
public void testTypicalQpidJMSMessage() throws Exception {
EncodedMessage encoded = encode(createTypicalQpidJMSMessage());
// Warm up
for (int i = 0; i < WARM_CYCLES; ++i) {
ServerMessage intermediate = converter.inbound(encoded);
encode(converter.outbound(intermediate, 1));
}
long totalDuration = 0;
long startTime = System.nanoTime();
for (int i = 0; i < PROFILE_CYCLES; ++i) {
ServerMessage intermediate = converter.inbound(encoded);
encode(converter.outbound(intermediate, 1));
}
totalDuration += System.nanoTime() - startTime;
LOG_RESULTS(totalDuration);
}
@Test
public void testComplexQpidJMSMessage() throws Exception {
EncodedMessage encoded = encode(createComplexQpidJMSMessage());
// Warm up
for (int i = 0; i < WARM_CYCLES; ++i) {
ServerMessage intermediate = converter.inbound(encoded);
encode(converter.outbound(intermediate, 1));
}
long totalDuration = 0;
long startTime = System.nanoTime();
for (int i = 0; i < PROFILE_CYCLES; ++i) {
ServerMessage intermediate = converter.inbound(encoded);
encode(converter.outbound(intermediate, 1));
}
totalDuration += System.nanoTime() - startTime;
LOG_RESULTS(totalDuration);
}
@Test
public void testTypicalQpidJMSMessageInBoundOnly() throws Exception {
EncodedMessage encoded = encode(createTypicalQpidJMSMessage());
// Warm up
for (int i = 0; i < WARM_CYCLES; ++i) {
converter.inbound(encoded);
}
long totalDuration = 0;
long startTime = System.nanoTime();
for (int i = 0; i < PROFILE_CYCLES; ++i) {
converter.inbound(encoded);
}
totalDuration += System.nanoTime() - startTime;
LOG_RESULTS(totalDuration);
}
@Test
public void testTypicalQpidJMSMessageOutBoundOnly() throws Exception {
EncodedMessage encoded = encode(createTypicalQpidJMSMessage());
ServerMessage intermediate = converter.inbound(encoded);
// Warm up
for (int i = 0; i < WARM_CYCLES; ++i) {
encode(converter.outbound(intermediate, 1));
}
long totalDuration = 0;
long startTime = System.nanoTime();
for (int i = 0; i < PROFILE_CYCLES; ++i) {
encode(converter.outbound(intermediate, 1));
}
totalDuration += System.nanoTime() - startTime;
LOG_RESULTS(totalDuration);
}
private Message createTypicalQpidJMSMessage() {
Map<String, Object> applicationProperties = new HashMap<>();
Map<Symbol, Object> messageAnnotations = new HashMap<>();
applicationProperties.put("property-1", "string");
applicationProperties.put("property-2", 512);
applicationProperties.put("property-3", true);
messageAnnotations.put(Symbol.valueOf("x-opt-jms-msg-type"), 0);
messageAnnotations.put(Symbol.valueOf("x-opt-jms-dest"), 0);
Message message = Proton.message();
message.setAddress("queue://test-queue");
message.setDeliveryCount(1);
message.setApplicationProperties(new ApplicationProperties(applicationProperties));
message.setMessageAnnotations(new MessageAnnotations(messageAnnotations));
message.setCreationTime(System.currentTimeMillis());
message.setContentType("text/plain");
message.setBody(new AmqpValue("String payload for AMQP message conversion performance testing."));
return message;
}
private Message createComplexQpidJMSMessage() {
Map<String, Object> applicationProperties = new HashMap<>();
Map<Symbol, Object> messageAnnotations = new HashMap<>();
applicationProperties.put("property-1", "string-1");
applicationProperties.put("property-2", 512);
applicationProperties.put("property-3", true);
applicationProperties.put("property-4", "string-2");
applicationProperties.put("property-5", 512);
applicationProperties.put("property-6", true);
applicationProperties.put("property-7", "string-3");
applicationProperties.put("property-8", 512);
applicationProperties.put("property-9", true);
messageAnnotations.put(Symbol.valueOf("x-opt-jms-msg-type"), 0);
messageAnnotations.put(Symbol.valueOf("x-opt-jms-dest"), 0);
Message message = Proton.message();
// Header Values
message.setPriority((short) 9);
message.setDurable(true);
message.setDeliveryCount(2);
message.setTtl(5000);
// Properties
message.setMessageId("ID:SomeQualifier:0:0:1");
message.setGroupId("Group-ID-1");
message.setGroupSequence(15);
message.setAddress("queue://test-queue");
message.setReplyTo("queue://reply-queue");
message.setCreationTime(System.currentTimeMillis());
message.setContentType("text/plain");
message.setCorrelationId("ID:SomeQualifier:0:7:9");
message.setUserId("username".getBytes(StandardCharsets.UTF_8));
// Application Properties / Message Annotations / Body
message.setApplicationProperties(new ApplicationProperties(applicationProperties));
message.setMessageAnnotations(new MessageAnnotations(messageAnnotations));
message.setBody(new AmqpValue("String payload for AMQP message conversion performance testing."));
return message;
}
private EncodedMessage encode(Object target) {
if (target instanceof ProtonJMessage) {
ProtonJMessage amqp = (ProtonJMessage) target;
ByteBuf nettyBuffer = Unpooled.buffer(1024);
amqp.encode(new NettyWritable(nettyBuffer));
return new EncodedMessage(0, nettyBuffer.array(), nettyBuffer.arrayOffset() + nettyBuffer.readerIndex(), nettyBuffer.readableBytes());
} else {
return null;
}
}
private void LOG_RESULTS(long duration) {
String result = "[JMS] Total time for " + PROFILE_CYCLES + " cycles of transforms = " + TimeUnit.NANOSECONDS.toMillis(duration) + " ms -> "
+ test.getMethodName();
System.out.println(result);
}
}

View File

@ -0,0 +1,264 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.activemq.artemis.protocol.amqp.converter.message;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import org.apache.activemq.artemis.core.server.ServerMessage;
import org.apache.activemq.artemis.protocol.amqp.converter.ProtonMessageConverter;
import org.apache.activemq.artemis.utils.IDGenerator;
import org.apache.activemq.artemis.utils.SimpleIDGenerator;
import org.apache.qpid.proton.Proton;
import org.apache.qpid.proton.amqp.Symbol;
import org.apache.qpid.proton.amqp.messaging.AmqpValue;
import org.apache.qpid.proton.amqp.messaging.ApplicationProperties;
import org.apache.qpid.proton.amqp.messaging.MessageAnnotations;
import org.apache.qpid.proton.amqp.messaging.Section;
import org.apache.qpid.proton.codec.CompositeWritableBuffer;
import org.apache.qpid.proton.codec.DroppingWritableBuffer;
import org.apache.qpid.proton.codec.WritableBuffer;
import org.apache.qpid.proton.message.Message;
import org.apache.qpid.proton.message.ProtonJMessage;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
/**
* Tests some basic encode / decode functionality on the transformers.
*/
public class MessageTransformationTest {
@Rule
public TestName test = new TestName();
private IDGenerator idGenerator;
private ProtonMessageConverter converter;
@Before
public void setUp() {
idGenerator = new SimpleIDGenerator(0);
converter = new ProtonMessageConverter(idGenerator);
}
@Test
public void testEncodeDecodeFidelity() throws Exception {
Map<String, Object> applicationProperties = new HashMap<>();
Map<Symbol, Object> messageAnnotations = new HashMap<>();
applicationProperties.put("property-1", "string");
applicationProperties.put("property-2", 512);
applicationProperties.put("property-3", true);
messageAnnotations.put(Symbol.valueOf("x-opt-jms-msg-type"), 0);
messageAnnotations.put(Symbol.valueOf("x-opt-jms-dest"), 0);
Message incomingMessage = Proton.message();
incomingMessage.setAddress("queue://test-queue");
incomingMessage.setDeliveryCount(1);
incomingMessage.setApplicationProperties(new ApplicationProperties(applicationProperties));
incomingMessage.setMessageAnnotations(new MessageAnnotations(messageAnnotations));
incomingMessage.setCreationTime(System.currentTimeMillis());
incomingMessage.setContentType("text/plain");
incomingMessage.setBody(new AmqpValue("String payload for AMQP message conversion performance testing."));
EncodedMessage encoded = encode(incomingMessage);
ServerMessage outbound = converter.inbound(encoded);
Message outboudMessage = ((EncodedMessage) converter.outbound(outbound, outbound.getLongProperty("JMSXDeliveryCount").intValue())).decode();
// Test that message details are equal
assertEquals(incomingMessage.getAddress(), outboudMessage.getAddress());
assertEquals(incomingMessage.getDeliveryCount(), outboudMessage.getDeliveryCount());
assertEquals(incomingMessage.getCreationTime(), outboudMessage.getCreationTime());
assertEquals(incomingMessage.getContentType(), outboudMessage.getContentType());
// Test Message annotations
ApplicationProperties incomingApplicationProperties = incomingMessage.getApplicationProperties();
ApplicationProperties outgoingApplicationProperties = outboudMessage.getApplicationProperties();
assertEquals(incomingApplicationProperties.getValue(), outgoingApplicationProperties.getValue());
// Test Message properties
MessageAnnotations incomingMessageAnnotations = incomingMessage.getMessageAnnotations();
MessageAnnotations outgoingMessageAnnotations = outboudMessage.getMessageAnnotations();
assertEquals(incomingMessageAnnotations.getValue(), outgoingMessageAnnotations.getValue());
// Test that bodies are equal
assertTrue(incomingMessage.getBody() instanceof AmqpValue);
assertTrue(outboudMessage.getBody() instanceof AmqpValue);
AmqpValue incomingBody = (AmqpValue) incomingMessage.getBody();
AmqpValue outgoingBody = (AmqpValue) outboudMessage.getBody();
assertTrue(incomingBody.getValue() instanceof String);
assertTrue(outgoingBody.getValue() instanceof String);
assertEquals(incomingBody.getValue(), outgoingBody.getValue());
}
@Test
public void testBodyOnlyEncodeDecode() throws Exception {
Message incomingMessage = Proton.message();
incomingMessage.setBody(new AmqpValue("String payload for AMQP message conversion performance testing."));
EncodedMessage encoded = encode(incomingMessage);
ServerMessage outbound = converter.inbound(encoded);
Message outboudMessage = ((EncodedMessage) converter.outbound(outbound, 1)).decode();
assertNull(outboudMessage.getHeader());
assertNull(outboudMessage.getProperties());
}
@Test
public void testPropertiesButNoHeadersEncodeDecode() throws Exception {
Message incomingMessage = Proton.message();
incomingMessage.setBody(new AmqpValue("String payload for AMQP message conversion performance testing."));
incomingMessage.setMessageId("ID:SomeQualifier:0:0:1");
EncodedMessage encoded = encode(incomingMessage);
ServerMessage outbound = converter.inbound(encoded);
Message outboudMessage = ((EncodedMessage) converter.outbound(outbound, 1)).decode();
assertNull(outboudMessage.getHeader());
assertNotNull(outboudMessage.getProperties());
}
@Test
public void testHeaderButNoPropertiesEncodeDecode() throws Exception {
Message incomingMessage = Proton.message();
incomingMessage.setBody(new AmqpValue("String payload for AMQP message conversion performance testing."));
incomingMessage.setDurable(true);
EncodedMessage encoded = encode(incomingMessage);
ServerMessage outbound = converter.inbound(encoded);
Message outboudMessage = ((EncodedMessage) converter.outbound(outbound, 1)).decode();
assertNotNull(outboudMessage.getHeader());
assertNull(outboudMessage.getProperties());
}
@Test
public void testMessageWithAmqpValueThatFailsJMSConversion() throws Exception {
Message incomingMessage = Proton.message();
incomingMessage.setBody(new AmqpValue(new Boolean(true)));
EncodedMessage encoded = encode(incomingMessage);
ServerMessage outbound = converter.inbound(encoded);
Message outboudMessage = ((EncodedMessage) converter.outbound(outbound, 1)).decode();
Section section = outboudMessage.getBody();
assertNotNull(section);
assertTrue(section instanceof AmqpValue);
AmqpValue amqpValue = (AmqpValue) section;
assertNotNull(amqpValue.getValue());
assertTrue(amqpValue.getValue() instanceof Boolean);
assertEquals(true, amqpValue.getValue());
}
@Test
public void testComplexQpidJMSMessageEncodeDecode() throws Exception {
Map<String, Object> applicationProperties = new HashMap<>();
Map<Symbol, Object> messageAnnotations = new HashMap<>();
applicationProperties.put("property-1", "string-1");
applicationProperties.put("property-2", 512);
applicationProperties.put("property-3", true);
applicationProperties.put("property-4", "string-2");
applicationProperties.put("property-5", 512);
applicationProperties.put("property-6", true);
applicationProperties.put("property-7", "string-3");
applicationProperties.put("property-8", 512);
applicationProperties.put("property-9", true);
messageAnnotations.put(Symbol.valueOf("x-opt-jms-msg-type"), 0);
messageAnnotations.put(Symbol.valueOf("x-opt-jms-dest"), 0);
messageAnnotations.put(Symbol.valueOf("x-opt-jms-reply-to"), 0);
messageAnnotations.put(Symbol.valueOf("x-opt-delivery-delay"), 2000);
Message message = Proton.message();
// Header Values
message.setPriority((short) 9);
message.setDurable(true);
message.setDeliveryCount(2);
message.setTtl(5000);
// Properties
message.setMessageId("ID:SomeQualifier:0:0:1");
message.setGroupId("Group-ID-1");
message.setGroupSequence(15);
message.setAddress("queue://test-queue");
message.setReplyTo("queue://reply-queue");
message.setCreationTime(System.currentTimeMillis());
message.setContentType("text/plain");
message.setCorrelationId("ID:SomeQualifier:0:7:9");
message.setUserId("username".getBytes(StandardCharsets.UTF_8));
// Application Properties / Message Annotations / Body
message.setApplicationProperties(new ApplicationProperties(applicationProperties));
message.setMessageAnnotations(new MessageAnnotations(messageAnnotations));
message.setBody(new AmqpValue("String payload for AMQP message conversion performance testing."));
EncodedMessage encoded = encode(message);
ServerMessage outbound = converter.inbound(encoded);
Message outboudMessage = ((EncodedMessage) converter.outbound(outbound, 1)).decode();
assertNotNull(outboudMessage.getHeader());
assertNotNull(outboudMessage.getProperties());
assertNotNull(outboudMessage.getMessageAnnotations());
assertNotNull(outboudMessage.getApplicationProperties());
assertNull(outboudMessage.getDeliveryAnnotations());
assertNull(outboudMessage.getFooter());
assertEquals(9, outboudMessage.getApplicationProperties().getValue().size());
assertEquals(4, outboudMessage.getMessageAnnotations().getValue().size());
}
private EncodedMessage encode(Message message) {
ProtonJMessage amqp = (ProtonJMessage) message;
ByteBuffer buffer = ByteBuffer.wrap(new byte[1024 * 4]);
final DroppingWritableBuffer overflow = new DroppingWritableBuffer();
int c = amqp.encode(new CompositeWritableBuffer(new WritableBuffer.ByteBufferWrapper(buffer), overflow));
if (overflow.position() > 0) {
buffer = ByteBuffer.wrap(new byte[1024 * 4 + overflow.position()]);
c = amqp.encode(new WritableBuffer.ByteBufferWrapper(buffer));
}
return new EncodedMessage(1, buffer.array(), 0, c);
}
}

View File

@ -203,6 +203,30 @@ public class AmqpMessage {
return message.getProperties().getTo();
}
/**
* Sets the replyTo address which is applied to the AMQP message reply-to field in the message properties
*
* @param address The replyTo address that should be applied in the Message To field.
*/
public void setReplyToAddress(String address) {
checkReadOnly();
lazyCreateProperties();
getWrappedMessage().setReplyTo(address);
}
/**
* Return the set replyTo address that was set in the Message To field.
*
* @return the set replyTo address String form or null if not set.
*/
public String getReplyToAddress() {
if (message.getProperties() == null) {
return null;
}
return message.getProperties().getReplyTo();
}
/**
* Sets the MessageId property on an outbound message using the provided String
*

View File

@ -16,6 +16,25 @@
*/
package org.apache.activemq.artemis.tests.integration.amqp;
import static org.apache.activemq.artemis.protocol.amqp.proton.AmqpSupport.DELAYED_DELIVERY;
import static org.apache.activemq.artemis.protocol.amqp.proton.AmqpSupport.PRODUCT;
import static org.apache.activemq.artemis.protocol.amqp.proton.AmqpSupport.VERSION;
import static org.apache.activemq.artemis.protocol.amqp.proton.AmqpSupport.contains;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import javax.jms.BytesMessage;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
@ -36,21 +55,9 @@ import javax.jms.StreamMessage;
import javax.jms.TemporaryQueue;
import javax.jms.TextMessage;
import javax.jms.Topic;
import javax.jms.TopicPublisher;
import javax.jms.TopicSession;
import javax.jms.TopicSubscriber;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.core.postoffice.Bindings;
@ -83,11 +90,6 @@ import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import static org.apache.activemq.artemis.protocol.amqp.proton.AmqpSupport.DELAYED_DELIVERY;
import static org.apache.activemq.artemis.protocol.amqp.proton.AmqpSupport.PRODUCT;
import static org.apache.activemq.artemis.protocol.amqp.proton.AmqpSupport.VERSION;
import static org.apache.activemq.artemis.protocol.amqp.proton.AmqpSupport.contains;
@RunWith(Parameterized.class)
public class ProtonTest extends ProtonTestBase {
@ -179,6 +181,31 @@ public class ProtonTest extends ProtonTestBase {
}
}
@Test
public void testSendAndReceiveOnTopic() throws Exception {
Connection connection = createConnection("myClientId");
try {
TopicSession session = (TopicSession) connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
Topic topic = session.createTopic("amqp_testtopic");
TopicSubscriber consumer = session.createSubscriber(topic);
TopicPublisher producer = session.createPublisher(topic);
TextMessage message = session.createTextMessage("test-message");
producer.send(message);
producer.close();
connection.start();
message = (TextMessage) consumer.receive(1000);
assertNotNull(message);
assertNotNull(message.getText());
} finally {
if (connection != null) {
connection.close();
}
}
}
@Test
public void testDurableSubscriptionUnsubscribe() throws Exception {
Connection connection = createConnection("myClientId");
@ -495,7 +522,7 @@ public class ProtonTest extends ProtonTestBase {
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
javax.jms.Queue queue = createQueue(address);
MessageProducer p = session.createProducer(queue);
ArrayList list = new ArrayList();
ArrayList<String> list = new ArrayList<>();
list.add("aString");
ObjectMessage objectMessage = session.createObjectMessage(list);
p.send(objectMessage);
@ -507,7 +534,7 @@ public class ProtonTest extends ProtonTestBase {
objectMessage = (ObjectMessage) cons.receive(5000);
assertNotNull(objectMessage);
list = (ArrayList) objectMessage.getObject();
list = (ArrayList<String>) objectMessage.getObject();
assertEquals(list.get(0), "aString");
connection.close();
}
@ -586,7 +613,7 @@ public class ProtonTest extends ProtonTestBase {
fillAddress(destinationAddress);
AmqpClient client = new AmqpClient(new URI(tcpAmqpConnectionUri), userName, password);
AmqpConnection amqpConnection = amqpConnection = client.connect();
AmqpConnection amqpConnection = client.connect();
try {
AmqpSession session = amqpConnection.createSession();
AmqpSender sender = session.createSender(destinationAddress);
@ -860,7 +887,7 @@ public class ProtonTest extends ProtonTestBase {
AmqpMessage request = new AmqpMessage();
request.setApplicationProperty("_AMQ_ResourceName", "core.server");
request.setApplicationProperty("_AMQ_OperationName", "getQueueNames");
request.setApplicationProperty("JMSReplyTo", destinationAddress);
request.setReplyToAddress(destinationAddress);
request.setText("[]");
sender.send(request);