From 62627bf2ee4789d8c019170fba0fcbf64a697a38 Mon Sep 17 00:00:00 2001 From: Timothy Bish Date: Mon, 3 Oct 2016 14:23:50 -0400 Subject: [PATCH] ARTEMIS-770 AMQP Message Transformer refactor Refactor the AMQP Message transformers both for better performance and also to fix a number of issues with the transformers creating inbound and outbound messages with incorrectly mapped values or extra data appended where it should not be. --- .../amqp/broker/AMQPSessionCallback.java | 12 +- .../amqp/converter/ActiveMQJMSVendor.java | 148 --- .../converter/ProtonMessageConverter.java | 83 +- .../amqp/converter/jms/ServerJMSMessage.java | 9 +- .../converter/jms/ServerJMSObjectMessage.java | 47 +- .../message/AMQPContentTypeSupport.java | 146 +++ .../message/AMQPMessageIdHelper.java | 59 +- .../converter/message/AMQPMessageSupport.java | 272 +++++ .../converter/message/AMQPMessageTypes.java | 4 + .../message/AMQPNativeInboundTransformer.java | 28 +- .../AMQPNativeOutboundTransformer.java | 82 +- .../message/AMQPRawInboundTransformer.java | 48 +- .../converter/message/EncodedMessage.java | 12 +- .../converter/message/InboundTransformer.java | 186 ++-- .../message/JMSMappingInboundTransformer.java | 198 ++-- .../JMSMappingOutboundTransformer.java | 676 +++++++++---- .../amqp/converter/message/JMSVendor.java | 53 - .../message/OutboundTransformer.java | 82 +- ...tiveMQAMQPInvalidContentTypeException.java | 27 + .../proton/ProtonServerSenderContext.java | 64 +- .../amqp/converter/TestConversions.java | 130 +-- .../message/AMQPContentTypeSupportTest.java | 230 +++++ .../message/AMQPMessageIdHelperTest.java | 391 +++++++ .../message/AMQPMessageSupportTest.java | 108 ++ .../JMSMappingInboundTransformerTest.java | 718 +++++++++++++ .../JMSMappingOutboundTransformerTest.java | 952 ++++++++++++++++++ .../JMSTransformationSpeedComparisonTest.java | 300 ++++++ .../message/MessageTransformationTest.java | 264 +++++ .../transport/amqp/client/AmqpMessage.java | 24 + .../tests/integration/amqp/ProtonTest.java | 71 +- 30 files changed, 4475 insertions(+), 949 deletions(-) delete mode 100644 artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/ActiveMQJMSVendor.java create mode 100644 artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/AMQPContentTypeSupport.java create mode 100644 artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/AMQPMessageSupport.java delete mode 100644 artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/JMSVendor.java create mode 100644 artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/exceptions/ActiveMQAMQPInvalidContentTypeException.java create mode 100644 artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/message/AMQPContentTypeSupportTest.java create mode 100644 artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/message/AMQPMessageIdHelperTest.java create mode 100644 artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/message/AMQPMessageSupportTest.java create mode 100644 artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/message/JMSMappingInboundTransformerTest.java create mode 100644 artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/message/JMSMappingOutboundTransformerTest.java create mode 100644 artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/message/JMSTransformationSpeedComparisonTest.java create mode 100644 artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/message/MessageTransformationTest.java diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPSessionCallback.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPSessionCallback.java index c7ca44611a..66c7b4bab8 100644 --- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPSessionCallback.java +++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/broker/AMQPSessionCallback.java @@ -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() { diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/ActiveMQJMSVendor.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/ActiveMQJMSVendor.java deleted file mode 100644 index 0b28660864..0000000000 --- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/ActiveMQJMSVendor.java +++ /dev/null @@ -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; - } - -} diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/ProtonMessageConverter.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/ProtonMessageConverter.java index 6eb78d02d8..6aa44a4f55 100644 --- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/ProtonMessageConverter.java +++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/ProtonMessageConverter.java @@ -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; + try { + transformedMessage = inboundTransformer.transform(encodedMessageSource); + } catch (Exception e) { + ActiveMQClientLogger.LOGGER.debug("Transform of message using [{}] transformer, failed" + inboundTransformer.getTransformerName()); + ActiveMQClientLogger.LOGGER.trace("Transformation error:", e); - while (transformer != null) { - try { - transformedMessage = (ServerJMSMessage) transformer.transform(encodedMessageSource); - break; - } 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); } } } diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/jms/ServerJMSMessage.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/jms/ServerJMSMessage.java index a6eac1dd34..c7900e46bd 100644 --- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/jms/ServerJMSMessage.java +++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/jms/ServerJMSMessage.java @@ -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; /** diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/jms/ServerJMSObjectMessage.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/jms/ServerJMSObjectMessage.java index 7f0906e387..d1eaac6078 100644 --- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/jms/ServerJMSObjectMessage.java +++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/jms/ServerJMSObjectMessage.java @@ -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); } } diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/AMQPContentTypeSupport.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/AMQPContentTypeSupport.java new file mode 100644 index 0000000000..01d72c8d02 --- /dev/null +++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/AMQPContentTypeSupport.java @@ -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; + } + } +} diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/AMQPMessageIdHelper.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/AMQPMessageIdHelper.java index dc7891cb27..e9a9969eba 100644 --- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/AMQPMessageIdHelper.java +++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/AMQPMessageIdHelper.java @@ -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. *

- *

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:
+ *

+ * 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:
*

* {@literal "AMQP_BINARY:"}
* {@literal "AMQP_UUID:"}
* {@literal "AMQP_ULONG:"}
* {@literal "AMQP_STRING:"}
*

- *

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. *

- *

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. + *

+ *

+ * 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 { *

* 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 { *

* 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) { @@ -198,11 +210,10 @@ public class AMQPMessageIdHelper { return builder.toString(); } - //----- Internal implementation ------------------------------------------// + // ----- 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) { diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/AMQPMessageSupport.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/AMQPMessageSupport.java new file mode 100644 index 0000000000..9eab737100 --- /dev/null +++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/AMQPMessageSupport.java @@ -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 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 content) throws JMSException { + ServerJMSMapMessage message = createMapMessage(idGenerator); + final Set> set = content.entrySet(); + for (Map.Entry 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; + } +} diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/AMQPMessageTypes.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/AMQPMessageTypes.java index 9b0635aa72..70c755a10a 100644 --- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/AMQPMessageTypes.java +++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/AMQPMessageTypes.java @@ -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"; diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/AMQPNativeInboundTransformer.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/AMQPNativeInboundTransformer.java index 8a5d17c460..7028547379 100644 --- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/AMQPNativeInboundTransformer.java +++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/AMQPNativeInboundTransformer.java @@ -1,13 +1,13 @@ -/** +/* * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with + * 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 - *

+ * 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. @@ -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); } } diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/AMQPNativeOutboundTransformer.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/AMQPNativeOutboundTransformer.java index ac18a94f7f..8e89bb30a8 100644 --- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/AMQPNativeOutboundTransformer.java +++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/AMQPNativeOutboundTransformer.java @@ -1,13 +1,13 @@ -/** +/* * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with + * 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 - *

+ * 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. @@ -16,45 +16,65 @@ */ 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"); - - // decode... - ProtonJMessage amqp = (ProtonJMessage) org.apache.qpid.proton.message.Message.Factory.create(); - int offset = 0; - int len = data.length; - while (len > 0) { - final int decoded = amqp.decode(data, offset, len); - assert decoded > 0 : "Make progress decoding the message"; - offset += decoded; - len -= decoded; + @Override + public long transform(ServerJMSMessage message, WritableBuffer buffer) throws JMSException, UnsupportedEncodingException { + if (message == null || !(message instanceof ServerJMSBytesMessage)) { + return 0; } - // Update the DeliveryCount header... + 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, - // whereas JMSXDeliveryCount includes the first/current delivery attempt. Subtract 1. - if (amqp.getHeader() == null) { - amqp.setHeader(new Header()); + int amqpDeliveryCount = message.getDeliveryCount() - 1; + if (amqpDeliveryCount >= 1) { + + // decode... + ProtonJMessage amqp = (ProtonJMessage) org.apache.qpid.proton.message.Message.Factory.create(); + int offset = 0; + int len = data.length; + while (len > 0) { + final int decoded = amqp.decode(data, offset, len); + assert decoded > 0 : "Make progress decoding the message"; + offset += decoded; + len -= decoded; + } + + // 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(amqpDeliveryCount)); + + amqp.encode(buffer); + } else { + buffer.put(data, 0, data.length); } - amqp.getHeader().setDeliveryCount(new UnsignedInteger(count - 1)); - - return amqp; + return 0; } } diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/AMQPRawInboundTransformer.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/AMQPRawInboundTransformer.java index e6bf171e74..445eaca6cf 100644 --- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/AMQPRawInboundTransformer.java +++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/AMQPRawInboundTransformer.java @@ -1,13 +1,13 @@ -/** +/* * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with + * 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 - *

+ * 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. @@ -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; } } diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/EncodedMessage.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/EncodedMessage.java index 4a80ea681f..22042da872 100644 --- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/EncodedMessage.java +++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/EncodedMessage.java @@ -1,13 +1,13 @@ -/** +/* * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with + * 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 - *

+ * 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. diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/InboundTransformer.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/InboundTransformer.java index ff0c03521b..5094af5abe 100644 --- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/InboundTransformer.java +++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/InboundTransformer.java @@ -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); + if (header.getDurable() != null) { + jms.setJMSDeliveryMode(header.getDurable().booleanValue() ? DeliveryMode.PERSISTENT : DeliveryMode.NON_PERSISTENT); + } else { + jms.setJMSDeliveryMode(Message.DEFAULT_DELIVERY_MODE); + } + + if (header.getPriority() != null) { + jms.setJMSPriority(header.getPriority().intValue()); + } else { + jms.setJMSPriority(Message.DEFAULT_PRIORITY); + } + + if (header.getFirstAcquirer() != null) { + jms.setBooleanProperty(JMS_AMQP_FIRST_ACQUIRER, header.getFirstAcquirer()); + } + + if (header.getDeliveryCount() != null) { + // 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.setJMSDeliveryMode(defaultDeliveryMode); - } - - if (header.getPriority() != null) { - jms.setJMSPriority(header.getPriority().intValue()); - } else { - jms.setJMSPriority(defaultPriority); - } - - if (header.getFirstAcquirer() != null) { - jms.setBooleanProperty(prefixVendor + "FirstAcquirer", header.getFirstAcquirer()); - } - - if (header.getDeliveryCount() != null) { - vendor.setJMSXDeliveryCount(jms, header.getDeliveryCount().longValue()); + 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 entry : (Set>) 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 entry : (Set>) 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 { diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/JMSMappingInboundTransformer.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/JMSMappingInboundTransformer.java index 9dd29ab20b..629c499fa5 100644 --- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/JMSMappingInboundTransformer.java +++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/JMSMappingInboundTransformer.java @@ -1,13 +1,13 @@ -/** +/* * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with + * 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 - *

+ * 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. @@ -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) 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> set = ((Map) value).entrySet(); - for (Map.Entry entry : set) { - m.setObject(entry.getKey(), entry.getValue()); - } - rc = m; + result = createMapMessage(idGenerator, (Map) 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; } } diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/JMSMappingOutboundTransformer.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/JMSMappingOutboundTransformer.java index 9f28a6be49..9ee0344899 100644 --- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/JMSMappingOutboundTransformer.java +++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/JMSMappingOutboundTransformer.java @@ -1,13 +1,13 @@ -/** +/* * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with + * 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 - *

+ * 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. @@ -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 daMap = null; - HashMap maMap = null; - HashMap apMap = null; - 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)); + private static final ThreadLocal tlsCodec = new ThreadLocal() { + @Override + protected EncoderDecoderPair initialValue() { + return new EncoderDecoderPair(); } - if (msg instanceof TextMessage) { - body = new AmqpValue(((TextMessage) msg).getText()); + }; + + public JMSMappingOutboundTransformer(IDGenerator idGenerator) { + super(idGenerator); + } + + @Override + public long transform(ServerJMSMessage message, WritableBuffer buffer) throws JMSException, UnsupportedEncodingException { + if (message == null) { + return 0; } - if (msg instanceof MapMessage) { - final HashMap map = new HashMap<>(); - final MapMessage m = (MapMessage) msg; - final Enumeration names = m.getMapNames(); - while (names.hasMoreElements()) { - String key = names.nextElement(); - map.put(key, m.getObject(key)); + + long messageFormat = 0; + Header header = null; + Properties properties = null; + Map daMap = null; + Map maMap = null; + Map apMap = null; + Map footerMap = null; + + Section body = convertBody(message); + + if (message.getInnerMessage().isDurable()) { + if (header == null) { + header = new Header(); } - body = new AmqpValue(map); + header.setDurable(true); } - if (msg instanceof StreamMessage) { + 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(); + } + 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(); + } + 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 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(); + } + 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(); + } + 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(); + } + 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(); + } + + 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; + short orignalEncoding = AMQP_UNKNOWN; + + try { + orignalEncoding = message.getShortProperty(JMS_AMQP_ORIGINAL_ENCODING); + } catch (Exception ex) { + // Ignore and stick with UNKNOWN + } + + if (message instanceof ServerJMSBytesMessage) { + Binary payload = getBinaryFromMessageBody((ServerJMSBytesMessage) message); + + if (payload == null) { + payload = EMPTY_BINARY; + } + + 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; + } + } 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; + } + } else if (message instanceof ServerJMSMapMessage) { + body = new AmqpValue(getMapFromMessageBody((ServerJMSMapMessage) message)); + } else if (message instanceof ServerJMSStreamMessage) { ArrayList 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); - if (amqpType.equals(AMQPMessageTypes.AMQP_LIST)) { - body = new AmqpValue(list); - } else { - body = new AmqpSequence(list); + // 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)) { + orignalEncoding = AMQP_VALUE_LIST; + } else { + orignalEncoding = AMQP_SEQUENCE; + } + } } - } - if (msg instanceof ObjectMessage) { - body = new AmqpValue(((ObjectMessage) msg).getObject()); - } - if (body == null && msg instanceof ServerJMSMessage) { + switch (orignalEncoding) { + case AMQP_SEQUENCE: + body = new AmqpSequence(list); + break; + case AMQP_VALUE_LIST: + case AMQP_UNKNOWN: + default: + body = new AmqpValue(list); + break; + } + } else if (message instanceof ServerJMSObjectMessage) { + Binary payload = getBinaryFromMessageBody((ServerJMSObjectMessage) message); - MessageInternal internalMessage = ((ServerJMSMessage) msg).getInnerMessage(); - if (!internalMessage.containsProperty("AMQP_MESSAGE_FORMAT")) { + if (payload == null) { + payload = EMPTY_BINARY; + } + + switch (orignalEncoding) { + case AMQP_VALUE_BINARY: + body = new AmqpValue(payload); + break; + case AMQP_DATA: + case AMQP_UNKNOWN: + default: + body = new Data(payload); + break; + } + + // 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()); + return body; + } + + 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); + } + + 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)); } - if (msg.getJMSMessageID() != null) { - String msgId = msg.getJMSMessageID(); + return result; + } - try { - props.setMessageId(AMQPMessageIdHelper.INSTANCE.toIdObject(msgId)); - } catch (ActiveMQAMQPIllegalStateException e) { - props.setMessageId(msgId); + 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 getMapFromMessageBody(ServerJMSMapMessage message) throws JMSException { + final HashMap map = new LinkedHashMap<>(); + + @SuppressWarnings("unchecked") + final Enumeration names = message.getMapNames(); + while (names.hasMoreElements()) { + String key = names.nextElement(); + Object value = message.getObject(key); + if (value instanceof byte[]) { + value = new Binary((byte[]) value); } - } - 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())); + map.put(key, value); } - final Enumeration 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); - } - } - } - - 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); - } - - return (ProtonJMessage) org.apache.qpid.proton.message.Message.Factory.create(header, da, ma, props, ap, body, footer); + return map; } private static byte destinationType(Destination destination) { diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/JMSVendor.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/JMSVendor.java deleted file mode 100644 index 9a0ed63bfd..0000000000 --- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/JMSVendor.java +++ /dev/null @@ -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 - *

- * 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 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); - -} diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/OutboundTransformer.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/OutboundTransformer.java index 310d4babe4..f15490f047 100644 --- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/OutboundTransformer.java +++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/converter/message/OutboundTransformer.java @@ -1,13 +1,13 @@ -/** +/* * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with + * 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 - *

+ * 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. @@ -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; - } + /** + * 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 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; - - } - - public JMSVendor getVendor() { - return vendor; - } - - public void setVendor(JMSVendor vendor) { - this.vendor = vendor; - } } diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/exceptions/ActiveMQAMQPInvalidContentTypeException.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/exceptions/ActiveMQAMQPInvalidContentTypeException.java new file mode 100644 index 0000000000..4def92cd15 --- /dev/null +++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/exceptions/ActiveMQAMQPInvalidContentTypeException.java @@ -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); + } +} diff --git a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/ProtonServerSenderContext.java b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/ProtonServerSenderContext.java index 7d401fa06e..94e6a47741 100644 --- a/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/ProtonServerSenderContext.java +++ b/artemis-protocols/artemis-amqp-protocol/src/main/java/org/apache/activemq/artemis/protocol/amqp/proton/ProtonServerSenderContext.java @@ -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; } diff --git a/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/TestConversions.java b/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/TestConversions.java index 148482ecc7..96ce90e07d 100644 --- a/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/TestConversions.java +++ b/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/TestConversions.java @@ -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 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) { diff --git a/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/message/AMQPContentTypeSupportTest.java b/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/message/AMQPContentTypeSupportTest.java new file mode 100644 index 0000000000..4caead7ed5 --- /dev/null +++ b/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/message/AMQPContentTypeSupportTest.java @@ -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); + } + } +} diff --git a/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/message/AMQPMessageIdHelperTest.java b/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/message/AMQPMessageIdHelperTest.java new file mode 100644 index 0000000000..c53cda56c9 --- /dev/null +++ b/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/message/AMQPMessageIdHelperTest.java @@ -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 + } + } +} diff --git a/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/message/AMQPMessageSupportTest.java b/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/message/AMQPMessageSupportTest.java new file mode 100644 index 0000000000..d4e078f629 --- /dev/null +++ b/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/message/AMQPMessageSupportTest.java @@ -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 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 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)); + } +} diff --git a/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/message/JMSMappingInboundTransformerTest.java b/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/message/JMSMappingInboundTransformerTest.java new file mode 100644 index 0000000000..d7a948a050 --- /dev/null +++ b/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/message/JMSMappingInboundTransformerTest.java @@ -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 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 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 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 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 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 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 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 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); + } +} diff --git a/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/message/JMSMappingOutboundTransformerTest.java b/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/message/JMSMappingOutboundTransformerTest.java new file mode 100644 index 0000000000..2ece01d08a --- /dev/null +++ b/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/message/JMSMappingOutboundTransformerTest.java @@ -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 amqpMap = (Map) ((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 amqpMap = (Map) ((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 amqpMap = (Map) ((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 amqpList = (List) ((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 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 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 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; + } +} diff --git a/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/message/JMSTransformationSpeedComparisonTest.java b/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/message/JMSTransformationSpeedComparisonTest.java new file mode 100644 index 0000000000..99aab33858 --- /dev/null +++ b/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/message/JMSTransformationSpeedComparisonTest.java @@ -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 applicationProperties = new HashMap<>(); + Map 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 applicationProperties = new HashMap<>(); + Map 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); + } +} diff --git a/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/message/MessageTransformationTest.java b/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/message/MessageTransformationTest.java new file mode 100644 index 0000000000..a5a2168cb7 --- /dev/null +++ b/artemis-protocols/artemis-amqp-protocol/src/test/java/org/apache/activemq/artemis/protocol/amqp/converter/message/MessageTransformationTest.java @@ -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 applicationProperties = new HashMap<>(); + Map 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 applicationProperties = new HashMap<>(); + Map 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); + } +} diff --git a/tests/artemis-test-support/src/main/java/org/apache/activemq/transport/amqp/client/AmqpMessage.java b/tests/artemis-test-support/src/main/java/org/apache/activemq/transport/amqp/client/AmqpMessage.java index f39a9c5ba6..e3e9681531 100644 --- a/tests/artemis-test-support/src/main/java/org/apache/activemq/transport/amqp/client/AmqpMessage.java +++ b/tests/artemis-test-support/src/main/java/org/apache/activemq/transport/amqp/client/AmqpMessage.java @@ -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 * diff --git a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/amqp/ProtonTest.java b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/amqp/ProtonTest.java index 2a1e8c9c59..15271f65e2 100644 --- a/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/amqp/ProtonTest.java +++ b/tests/integration-tests/src/test/java/org/apache/activemq/artemis/tests/integration/amqp/ProtonTest.java @@ -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 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) 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);