Author: robbie
Date: Wed May 14 16:00:06 2014
New Revision: 1594627
URL: http://svn.apache.org/r1594627
Log:
QPIDJMS-21: implement support for byte[] entries, use LinkedHashMap for
predictable ordering behaviour, add additional tests.
Added:
qpid/jms/trunk/src/test/java/org/apache/qpid/jms/MapMessageIntegrationTest.java
Modified:
qpid/jms/trunk/src/main/java/org/apache/qpid/jms/engine/AmqpMapMessage.java
qpid/jms/trunk/src/main/java/org/apache/qpid/jms/impl/MapMessageImpl.java
qpid/jms/trunk/src/main/java/org/apache/qpid/jms/impl/SessionImpl.java
qpid/jms/trunk/src/test/java/org/apache/qpid/jms/impl/MapMessageImplTest.java
Modified:
qpid/jms/trunk/src/main/java/org/apache/qpid/jms/engine/AmqpMapMessage.java
URL:
http://svn.apache.org/viewvc/qpid/jms/trunk/src/main/java/org/apache/qpid/jms/engine/AmqpMapMessage.java?rev=1594627&r1=1594626&r2=1594627&view=diff
==============================================================================
--- qpid/jms/trunk/src/main/java/org/apache/qpid/jms/engine/AmqpMapMessage.java
(original)
+++ qpid/jms/trunk/src/main/java/org/apache/qpid/jms/engine/AmqpMapMessage.java
Wed May 14 16:00:06 2014
@@ -18,10 +18,12 @@
*/
package org.apache.qpid.jms.engine;
-import java.util.HashMap;
+import java.util.Arrays;
+import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
+import org.apache.qpid.proton.amqp.Binary;
import org.apache.qpid.proton.amqp.messaging.AmqpValue;
import org.apache.qpid.proton.amqp.messaging.Section;
import org.apache.qpid.proton.engine.Delivery;
@@ -67,7 +69,9 @@ public class AmqpMapMessage extends Amqp
private void initialiseMessageBodyMap()
{
- _messageBodyMap= new HashMap<String,Object>();
+ //Using LinkedHashMap because AMQP map equality considers order,
+ //so we should behave in as predictable a manner as possible
+ _messageBodyMap= new LinkedHashMap<String,Object>();
getMessage().setBody(new AmqpValue(_messageBodyMap));
}
@@ -84,25 +88,47 @@ public class AmqpMapMessage extends Amqp
/**
* Associates the specified value with the specified key in this map
message.
*
- * If a previous mapping for the key exists, the old value is replaced by
the specified value and the old value is returned.
+ * If a previous mapping for the key exists, the old value is replaced by
the specified value.
+ *
+ * To be clear, if the value provided is a byte[] then it is NOT copied
and MUST NOT be subsequently altered.
+ *
* @param key the key for the mapping
* @param value the value for the mapping
- * @return the old value if one exists for this key, or null if there was
none.
*/
- public Object setMapEntry(String key, Object value)
+ public void setMapEntry(String key, Object value)
{
- return _messageBodyMap.put(key, value);
+ Object entry = value;
+ if(value instanceof byte[])
+ {
+ entry = new Binary((byte[]) value);
+ }
+
+ _messageBodyMap.put(key, entry);
}
/**
* Returns the value to which the specified key is mapped, or null if this
map contains no mapping for the key.
*
+ * If the value being returned is a byte[], the array returned IS a copy.
+ *
* @param key the key for the mapping
* @return the value if one exists for this key, or null if there was none.
*/
public Object getMapEntry(String key)
{
- return _messageBodyMap.get(key);
+ Object object = _messageBodyMap.get(key);
+
+ if(object instanceof Binary)
+ {
+ //We will return a byte[]. It is possibly only part of the
underlying array, copy that bit.
+ Binary bin = ((Binary) object);
+
+ return Arrays.copyOfRange(bin.getArray(), bin.getArrayOffset(),
bin.getLength());
+ }
+ else
+ {
+ return object;
+ }
}
/**
Modified:
qpid/jms/trunk/src/main/java/org/apache/qpid/jms/impl/MapMessageImpl.java
URL:
http://svn.apache.org/viewvc/qpid/jms/trunk/src/main/java/org/apache/qpid/jms/impl/MapMessageImpl.java?rev=1594627&r1=1594626&r2=1594627&view=diff
==============================================================================
--- qpid/jms/trunk/src/main/java/org/apache/qpid/jms/impl/MapMessageImpl.java
(original)
+++ qpid/jms/trunk/src/main/java/org/apache/qpid/jms/impl/MapMessageImpl.java
Wed May 14 16:00:06 2014
@@ -30,13 +30,6 @@ import javax.jms.MessageNotWriteableExce
import org.apache.qpid.jms.engine.AmqpMapMessage;
-/**
- * TODO
- *
- * NOTES:
- * Implement handling for byte[] in the map (e.g convert to/from Binary);
- *
- */
public class MapMessageImpl extends MessageImpl<AmqpMapMessage> implements
MapMessage
{
//message to be sent
@@ -54,8 +47,8 @@ public class MapMessageImpl extends Mess
@Override
protected AmqpMapMessage
prepareUnderlyingAmqpMessageForSending(AmqpMapMessage amqpMessage)
{
- //TODO
- throw new UnsupportedOperationException("Not Implemented");
+ //Currently nothing to do, we always operate directly on the
underlying AmqpMapMessage.
+ return amqpMessage;
}
private void setMapEntry(String name, Object value) throws
IllegalArgumentException, MessageNotWriteableException
@@ -167,7 +160,7 @@ public class MapMessageImpl extends Mess
{
Object value = getMapEntry(name);
- if ((value instanceof Character)) //TODO: verify we dont get a Binary,
push down impl to ensure we dont
+ if ((value instanceof Character))
{
return (char) value;
}
@@ -290,7 +283,7 @@ public class MapMessageImpl extends Mess
{
return (String) value;
}
- else if (value instanceof byte[])//TODO: verify we dont get a Binary,
push down impl to ensure we dont
+ else if (value instanceof byte[])
{
throw new MessageFormatException("Map entry " + name + " of type
byte[] " + "cannot be converted to String.");
}
@@ -305,7 +298,7 @@ public class MapMessageImpl extends Mess
{
Object value = getMapEntry(name);
- if ((value instanceof byte[]) || (value == null)) //TODO: verify we
dont get a Binary, push down impl to ensure we dont
+ if ((value instanceof byte[]) || (value == null))
{
return (byte[]) value;
}
@@ -318,8 +311,6 @@ public class MapMessageImpl extends Mess
@Override
public Object getObject(String name) throws JMSException
{
- //TODO: verify what happens with byte[] for received messages
- //(i.e does it return a Binary? if so, push impl down to ensure we get
a byte[] instead)
return getMapEntry(name);
}
@@ -388,16 +379,32 @@ public class MapMessageImpl extends Mess
@Override
public void setBytes(String name, byte[] value) throws JMSException
{
- //TODO
- throw new UnsupportedOperationException("Not Implemented");
+ int length = 0;
+ if(value != null)
+ {
+ length = value.length;
+ }
+
+ setBytes(name, value, 0, length);
}
@Override
public void setBytes(String name, byte[] value, int offset, int length)
throws JMSException
{
- //TODO
- throw new UnsupportedOperationException("Not Implemented");
+ byte[] dest;
+
+ if(value == null)
+ {
+ dest = null;
+ }
+ else
+ {
+ dest = new byte[length];
+ System.arraycopy(value, offset, dest, 0, length);
+ }
+
+ setMapEntry(name, dest);
}
@Override
Modified: qpid/jms/trunk/src/main/java/org/apache/qpid/jms/impl/SessionImpl.java
URL:
http://svn.apache.org/viewvc/qpid/jms/trunk/src/main/java/org/apache/qpid/jms/impl/SessionImpl.java?rev=1594627&r1=1594626&r2=1594627&view=diff
==============================================================================
--- qpid/jms/trunk/src/main/java/org/apache/qpid/jms/impl/SessionImpl.java
(original)
+++ qpid/jms/trunk/src/main/java/org/apache/qpid/jms/impl/SessionImpl.java Wed
May 14 16:00:06 2014
@@ -228,8 +228,7 @@ public class SessionImpl implements Sess
@Override
public MapMessage createMapMessage() throws JMSException
{
- // TODO Auto-generated method stub
- throw new UnsupportedOperationException("Not Implemented");
+ return new MapMessageImpl(this, getConnectionImpl());
}
@Override
Added:
qpid/jms/trunk/src/test/java/org/apache/qpid/jms/MapMessageIntegrationTest.java
URL:
http://svn.apache.org/viewvc/qpid/jms/trunk/src/test/java/org/apache/qpid/jms/MapMessageIntegrationTest.java?rev=1594627&view=auto
==============================================================================
---
qpid/jms/trunk/src/test/java/org/apache/qpid/jms/MapMessageIntegrationTest.java
(added)
+++
qpid/jms/trunk/src/test/java/org/apache/qpid/jms/MapMessageIntegrationTest.java
Wed May 14 16:00:06 2014
@@ -0,0 +1,257 @@
+/*
+ * 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.qpid.jms;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Arrays;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import javax.jms.Connection;
+import javax.jms.MapMessage;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageProducer;
+import javax.jms.Queue;
+import javax.jms.Session;
+
+import org.apache.qpid.jms.test.testpeer.TestAmqpPeer;
+import
org.apache.qpid.jms.test.testpeer.describedtypes.sections.AmqpValueDescribedType;
+import
org.apache.qpid.jms.test.testpeer.matchers.sections.MessageAnnotationsSectionMatcher;
+import
org.apache.qpid.jms.test.testpeer.matchers.sections.MessageHeaderSectionMatcher;
+import
org.apache.qpid.jms.test.testpeer.matchers.sections.MessagePropertiesSectionMatcher;
+import
org.apache.qpid.jms.test.testpeer.matchers.sections.TransferPayloadCompositeMatcher;
+import
org.apache.qpid.jms.test.testpeer.matchers.types.EncodedAmqpValueMatcher;
+import org.apache.qpid.proton.amqp.Binary;
+import org.apache.qpid.proton.amqp.DescribedType;
+import org.junit.Test;
+
+public class MapMessageIntegrationTest extends QpidJmsTestCase
+{
+ private final IntegrationTestFixture _testFixture = new
IntegrationTestFixture();
+
+ /**
+ * Test that a message received from the test peer with an AmqpValue
section containing
+ * a map which holds entries of the various supported entry types is
returned as a
+ * {@link MapMessage}, and verify the values can all be retrieved as
expected.
+ */
+ @Test
+ public void testReceiveBasicMapMessage() throws Exception
+ {
+ try(TestAmqpPeer testPeer = new
TestAmqpPeer(IntegrationTestFixture.PORT);)
+ {
+ Connection connection = _testFixture.establishConnecton(testPeer);
+ connection.start();
+
+ testPeer.expectBegin();
+
+ Session session = connection.createSession(false,
Session.AUTO_ACKNOWLEDGE);
+ Queue queue = session.createQueue("myQueue");
+
+ //Prepare an AMQP message for the test peer to send, containing an
+ //AmqpValue section holding a map with entries for each supported
type.
+ //with each supported type of entry
+ String myBoolKey = "myBool";
+ boolean myBool = true;
+ String myByteKey = "myByte";
+ byte myByte = 4;
+ String myBytesKey = "myBytes";
+ byte[] myBytes = myBytesKey.getBytes();
+ String myCharKey = "myChar";
+ char myChar = 'd';
+ String myDoubleKey = "myDouble";
+ double myDouble = 1234567890123456789.1234;
+ String myFloatKey = "myFloat";
+ float myFloat = 1.1F;
+ String myIntKey = "myInt";
+ int myInt = Integer.MAX_VALUE;
+ String myLongKey = "myLong";
+ long myLong = Long.MAX_VALUE;
+ String myShortKey = "myShort";
+ short myShort = 25;
+ String myStringKey = "myString";
+ String myString = myStringKey;
+
+ Map<String,Object> map = new LinkedHashMap<String,Object>();
+ map.put(myBoolKey, myBool);
+ map.put(myByteKey, myByte);
+ map.put(myBytesKey, new Binary(myBytes));//the underlying AMQP
message uses Binary rather than byte[] directly.
+ map.put(myCharKey, myChar);
+ map.put(myDoubleKey, myDouble);
+ map.put(myFloatKey, myFloat);
+ map.put(myIntKey, myInt);
+ map.put(myLongKey, myLong);
+ map.put(myShortKey, myShort);
+ map.put(myStringKey, myString);
+
+ DescribedType amqpValueSectionContent = new
AmqpValueDescribedType(map);
+
+ //receive the message from the test peer
+ testPeer.expectReceiverAttach();
+ testPeer.expectLinkFlowRespondWithTransfer(null, null, null, null,
amqpValueSectionContent);
+ testPeer.expectDispositionThatIsAcceptedAndSettled();
+
+ MessageConsumer messageConsumer = session.createConsumer(queue);
+ Message receivedMessage = messageConsumer.receive(1000);
+ testPeer.waitForAllHandlersToComplete(3000);
+
+ //verify the content is as expected
+ assertNotNull("Message was not received", receivedMessage);
+ assertTrue("Message was not a BytesMessage", receivedMessage
instanceof MapMessage);
+ MapMessage receivedMapMessage = (MapMessage) receivedMessage;
+
+ assertEquals("Unexpected boolean value", myBool,
receivedMapMessage.getBoolean(myBoolKey));
+ assertEquals("Unexpected byte value", myByte,
receivedMapMessage.getByte(myByteKey));
+ byte[] readBytes = receivedMapMessage.getBytes(myBytesKey);
+ assertTrue("Read bytes were not as expected: " +
Arrays.toString(readBytes), Arrays.equals(myBytes, readBytes));
+ assertEquals("Unexpected char value", myChar,
receivedMapMessage.getChar(myCharKey));
+ assertEquals("Unexpected double value", myDouble,
receivedMapMessage.getDouble(myDoubleKey), 0.0);
+ assertEquals("Unexpected float value", myFloat,
receivedMapMessage.getFloat(myFloatKey), 0.0);
+ assertEquals("Unexpected int value", myInt,
receivedMapMessage.getInt(myIntKey));
+ assertEquals("Unexpected long value", myLong,
receivedMapMessage.getLong(myLongKey));
+ assertEquals("Unexpected short value", myShort,
receivedMapMessage.getShort(myShortKey));
+ assertEquals("Unexpected UTF value", myString,
receivedMapMessage.getString(myStringKey));
+ }
+ }
+
+/*
+ * TODO: decide what to do about this
+ *
+ * The test below fails if a char is added, because the DataImpl-based decoder
used by the test peer
+ * decodes the char to an Integer object and thus the EncodedAmqpValueMatcher
fails the comparison
+ * of its contained map due to the differing types. This doesn't happen in the
above test as the
+ * reversed roles mean it is DecoderImpl doing the decoding and it casts the
output to a char.
+ *
+ * The below change to alter DataImpl's output makes the test pass, as would
making the test 'expect an int'.
+----START PATCH-----
+diff --git
proton-j/src/main/java/org/apache/qpid/proton/codec/impl/CharElement.java
proton-j/src/main/java/org/apache/qpid/proton/codec/impl/CharElement.java
+index 808d43e..6cdf84c 100644
+--- proton-j/src/main/java/org/apache/qpid/proton/codec/impl/CharElement.java
++++ proton-j/src/main/java/org/apache/qpid/proton/codec/impl/CharElement.java
+@@ -25,9 +25,8 @@ import java.nio.ByteBuffer;
+
+ import org.apache.qpid.proton.codec.Data;
+
+-class CharElement extends AtomicElement<Integer>
++class CharElement extends AtomicElement<Character>
+ {
+-
+ private final int _value;
+
+ CharElement(Element parent, Element prev, int i)
+@@ -43,9 +42,9 @@ class CharElement extends AtomicElement<Integer>
+ }
+
+ @Override
+- public Integer getValue()
++ public Character getValue()
+ {
+- return _value;
++ return (char)_value;
+ }
+
+ @Override
+----END PATCH-----
+*/
+ /**
+ * Test that sending a map message to the test peer results in receipt of
a message with
+ * an AmqpValue section containing a map which holds entries of the
various supported entry
+ * types with the expected values.
+ */
+ @Test
+ public void testSendBasicMapMessage() throws Exception
+ {
+ try(TestAmqpPeer testPeer = new
TestAmqpPeer(IntegrationTestFixture.PORT);)
+ {
+ Connection connection = _testFixture.establishConnecton(testPeer);
+ testPeer.expectBegin();
+ testPeer.expectSenderAttach();
+
+ Session session = connection.createSession(false,
Session.AUTO_ACKNOWLEDGE);
+ Queue queue = session.createQueue("myQueue");
+ MessageProducer producer = session.createProducer(queue);
+
+
+ String myBoolKey = "myBool";
+ boolean myBool = true;
+ String myByteKey = "myByte";
+ byte myByte = 4;
+ String myBytesKey = "myBytes";
+ byte[] myBytes = myBytesKey.getBytes();
+ String myCharKey = "myChar";
+ char myChar = 'd';
+ String myDoubleKey = "myDouble";
+ double myDouble = 1234567890123456789.1234;
+ String myFloatKey = "myFloat";
+ float myFloat = 1.1F;
+ String myIntKey = "myInt";
+ int myInt = Integer.MAX_VALUE;
+ String myLongKey = "myLong";
+ long myLong = Long.MAX_VALUE;
+ String myShortKey = "myShort";
+ short myShort = 25;
+ String myStringKey = "myString";
+ String myString = myStringKey;
+
+ //Prepare a MapMessage to send to the test peer to send
+ MapMessage mapMessage = session.createMapMessage();
+
+ mapMessage.setBoolean(myBoolKey, myBool);
+ mapMessage.setByte(myByteKey, myByte);
+ mapMessage.setBytes(myBytesKey, myBytes);
+ //TODO: see note above: mapMessage.setChar(myCharKey, myChar);
+ mapMessage.setDouble(myDoubleKey, myDouble);
+ mapMessage.setFloat(myFloatKey, myFloat);
+ mapMessage.setInt(myIntKey, myInt);
+ mapMessage.setLong(myLongKey, myLong);
+ mapMessage.setShort(myShortKey, myShort);
+ mapMessage.setString(myStringKey, myString);
+
+ //prepare a matcher for the test peer to use to receive and verify
the message
+ Map<String,Object> map = new LinkedHashMap<String,Object>();
+ map.put(myBoolKey, myBool);
+ map.put(myByteKey, myByte);
+ map.put(myBytesKey, new Binary(myBytes));//the underlying AMQP
message uses Binary rather than byte[] directly.
+ //TODO: see note above: map.put(myCharKey, myChar);
+ map.put(myDoubleKey, myDouble);
+ map.put(myFloatKey, myFloat);
+ map.put(myIntKey, myInt);
+ map.put(myLongKey, myLong);
+ map.put(myShortKey, myShort);
+ map.put(myStringKey, myString);
+
+ MessageHeaderSectionMatcher headersMatcher = new
MessageHeaderSectionMatcher(true).withDurable(equalTo(true));
+ MessageAnnotationsSectionMatcher msgAnnotationsMatcher = new
MessageAnnotationsSectionMatcher(true);
+ MessagePropertiesSectionMatcher propertiesMatcher = new
MessagePropertiesSectionMatcher(true);
+ TransferPayloadCompositeMatcher messageMatcher = new
TransferPayloadCompositeMatcher();
+ messageMatcher.setHeadersMatcher(headersMatcher);
+ messageMatcher.setMessageAnnotationsMatcher(msgAnnotationsMatcher);
+ messageMatcher.setPropertiesMatcher(propertiesMatcher);
+ messageMatcher.setMessageContentMatcher(new
EncodedAmqpValueMatcher(map));
+
+ //send the message
+ testPeer.expectTransfer(messageMatcher);
+ producer.send(mapMessage);
+ }
+ }
+}
Modified:
qpid/jms/trunk/src/test/java/org/apache/qpid/jms/impl/MapMessageImplTest.java
URL:
http://svn.apache.org/viewvc/qpid/jms/trunk/src/test/java/org/apache/qpid/jms/impl/MapMessageImplTest.java?rev=1594627&r1=1594626&r2=1594627&view=diff
==============================================================================
---
qpid/jms/trunk/src/test/java/org/apache/qpid/jms/impl/MapMessageImplTest.java
(original)
+++
qpid/jms/trunk/src/test/java/org/apache/qpid/jms/impl/MapMessageImplTest.java
Wed May 14 16:00:06 2014
@@ -22,10 +22,12 @@ package org.apache.qpid.jms.impl;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
@@ -38,8 +40,11 @@ import javax.jms.MessageNotWriteableExce
import org.apache.qpid.jms.QpidJmsTestCase;
import org.apache.qpid.jms.engine.AmqpConnection;
import org.apache.qpid.jms.engine.AmqpMapMessage;
+import
org.apache.qpid.jms.test.testpeer.describedtypes.sections.AmqpValueDescribedType;
import org.apache.qpid.proton.Proton;
+import org.apache.qpid.proton.amqp.Binary;
import org.apache.qpid.proton.amqp.messaging.AmqpValue;
+import org.apache.qpid.proton.codec.impl.DataImpl;
import org.apache.qpid.proton.engine.Delivery;
import org.apache.qpid.proton.message.Message;
import org.junit.Before;
@@ -64,6 +69,8 @@ public class MapMessageImplTest extends
Mockito.when(_mockSessionImpl.getDestinationHelper()).thenReturn(new
DestinationHelper());
}
+ // ======= general =========
+
@Test
public void testGetMapNamesWithNewMessageToSendReturnsEmptyEnumeration()
throws Exception
{
@@ -204,6 +211,40 @@ public class MapMessageImplTest extends
}
/**
+ * When a map entry is not set, the behaviour of JMS specifies that it is
equivalent to a null value,
+ * and the accessors should either return null, throw NPE, or behave in
the same fashion as <primitive>.valueOf(String).
+ *
+ * Test that this is the case.
+ */
+ @Test
+ public void testGetMissingMapEntryResultsInExpectedBehaviour() throws
Exception
+ {
+ MapMessageImpl mapMessageImpl = new
MapMessageImpl(_mockSessionImpl,_mockConnectionImpl);
+
+ String name = "does_not_exist";
+
+ //expect null
+ assertNull(mapMessageImpl.getBytes(name));
+ assertNull(mapMessageImpl.getString(name));
+
+ //expect false from Boolean.valueOf(null).
+ assertFalse(mapMessageImpl.getBoolean(name));
+
+ //expect an NFE from the primitive integral <type>.valueOf(null)
conversions
+ assertGetMapEntryThrowsNumberFormatException(mapMessageImpl, name,
Byte.class);
+ assertGetMapEntryThrowsNumberFormatException(mapMessageImpl, name,
Short.class);
+ assertGetMapEntryThrowsNumberFormatException(mapMessageImpl, name,
Integer.class);
+ assertGetMapEntryThrowsNumberFormatException(mapMessageImpl, name,
Long.class);
+
+ //expect an NPE from the primitive float, double, and char
<type>.valuleOf(null) conversions
+ assertGetMapEntryThrowsNullPointerException(mapMessageImpl, name,
Float.class);
+ assertGetMapEntryThrowsNullPointerException(mapMessageImpl, name,
Double.class);
+ assertGetMapEntryThrowsNullPointerException(mapMessageImpl, name,
Character.class);
+ }
+
+ // ======= object =========
+
+ /**
* Test that the {@link MapMessageImpl#setObject(String, Object)} method
rejects Objects of unexpected types
*/
@Test
@@ -267,9 +308,11 @@ public class MapMessageImplTest extends
mapMessageImpl.setObject(keyName, entryValue);
assertEquals(entryValue, mapMessageImpl.getObject(keyName));
- entryValue = new byte[] { (byte)1, (byte) 0, (byte)1};
- mapMessageImpl.setObject(keyName, entryValue);
- assertEquals(entryValue, mapMessageImpl.getObject(keyName));
+ byte[] bytes = new byte[] { (byte)1, (byte) 0, (byte)1};
+ mapMessageImpl.setObject(keyName, bytes);
+ Object retrieved = mapMessageImpl.getObject(keyName);
+ assertTrue(retrieved instanceof byte[]);
+ assertTrue(Arrays.equals(bytes, (byte[])retrieved));
}
// ======= Strings =========
@@ -618,6 +661,9 @@ public class MapMessageImplTest extends
// ======= double =========
+ /**
+ * Set a double, then retrieve it as all of the legal type combinations to
verify it is parsed correctly
+ */
@Test
public void testSetDoubleGetLegal() throws Exception
{
@@ -632,6 +678,9 @@ public class MapMessageImplTest extends
assertGetMapEntryEquals(mapMessageImpl, name, String.valueOf(value),
String.class);
}
+ /**
+ * Set a double, then retrieve it as all of the illegal type combinations
to verify it fails as expected
+ */
@Test
public void testSetDoubleGetIllegal() throws Exception
{
@@ -654,6 +703,9 @@ public class MapMessageImplTest extends
// ======= character =========
+ /**
+ * Set a char, then retrieve it as all of the legal type combinations to
verify it is parsed correctly
+ */
@Test
public void testSetCharGetLegal() throws Exception
{
@@ -668,6 +720,9 @@ public class MapMessageImplTest extends
assertGetMapEntryEquals(mapMessageImpl, name, String.valueOf(value),
String.class);
}
+ /**
+ * Set a char, then retrieve it as all of the illegal type combinations to
verify it fails as expected
+ */
@Test
public void testSetCharGetIllegal() throws Exception
{
@@ -685,26 +740,174 @@ public class MapMessageImplTest extends
assertGetMapEntryThrowsMessageFormatException(mapMessageImpl, name,
Integer.class);
assertGetMapEntryThrowsMessageFormatException(mapMessageImpl, name,
Long.class);
assertGetMapEntryThrowsMessageFormatException(mapMessageImpl, name,
Float.class);
+ assertGetMapEntryThrowsMessageFormatException(mapMessageImpl, name,
Double.class);
}
+ //========= bytes ========
+
/**
- * Verify behaviour when retrieving a character with null value (i.e
missing).
- * Unlike all the other types, this should throw an explicit NPE.
+ * Set bytes, then retrieve it as all of the legal type combinations to
verify it is parsed correctly
*/
@Test
- public void testGetCharWithMissingValueThrowsNPE() throws Exception
+ public void testSetBytesGetLegal() throws Exception
{
MapMessageImpl mapMessageImpl = new
MapMessageImpl(_mockSessionImpl,_mockConnectionImpl);
- try
- {
- mapMessageImpl.getChar("does.not.exist");
- fail("expected exception to be thrown");
- }
- catch(NullPointerException npe)
- {
- //expected
- }
+ String name = "myName";
+ byte[] value = "myBytes".getBytes();
+
+ mapMessageImpl.setBytes(name, value);
+ assertTrue(Arrays.equals(value, mapMessageImpl.getBytes(name)));
+ }
+
+ /**
+ * Set bytes, then retrieve it as all of the illegal type combinations to
verify it fails as expected
+ */
+ @Test
+ public void testSetBytesGetIllegal() throws Exception
+ {
+ MapMessageImpl mapMessageImpl = new
MapMessageImpl(_mockSessionImpl,_mockConnectionImpl);
+
+ String name = "myName";
+ byte[] value = "myBytes".getBytes();
+
+ mapMessageImpl.setBytes(name, value);
+
+ assertGetMapEntryThrowsMessageFormatException(mapMessageImpl, name,
Character.class);
+ assertGetMapEntryThrowsMessageFormatException(mapMessageImpl, name,
String.class);
+ assertGetMapEntryThrowsMessageFormatException(mapMessageImpl, name,
Boolean.class);
+ assertGetMapEntryThrowsMessageFormatException(mapMessageImpl, name,
Byte.class);
+ assertGetMapEntryThrowsMessageFormatException(mapMessageImpl, name,
Short.class);
+ assertGetMapEntryThrowsMessageFormatException(mapMessageImpl, name,
Integer.class);
+ assertGetMapEntryThrowsMessageFormatException(mapMessageImpl, name,
Long.class);
+ assertGetMapEntryThrowsMessageFormatException(mapMessageImpl, name,
Float.class);
+ assertGetMapEntryThrowsMessageFormatException(mapMessageImpl, name,
Double.class);
+ }
+
+ /**
+ * Verify that for a message received with an AmqpValue containing a Map
with a
+ * Binary entry value, we are able to read it back as a byte[].
+ */
+ @Test
+ public void testReceivedMapWithBinaryEntryReturnsByteArray() throws
Exception
+ {
+ String myKey1 = "key1";
+ String bytesSource = "myBytesAmqpValue";
+
+ Map<String,Object> origMap = new HashMap<String,Object>();
+ byte[] bytes = bytesSource.getBytes();
+ origMap.put(myKey1, new Binary(bytes));
+
+ org.apache.qpid.proton.codec.Data payloadData = new DataImpl();
+ payloadData.putDescribedType(new AmqpValueDescribedType(origMap));
+ Binary b = payloadData.encode();
+
+ System.out.println("Using encoded AMQP message payload: " + b);
+
+ Message message = Proton.message();
+ int decoded = message.decode(b.getArray(), b.getArrayOffset(),
b.getLength());
+ assertEquals(decoded, b.getLength());
+
+ AmqpMapMessage amqpMapMessage = new AmqpMapMessage(message,
_mockDelivery, _mockAmqpConnection);
+
+ MapMessageImpl mapMessageImpl = new MapMessageImpl(amqpMapMessage,
_mockSessionImpl,_mockConnectionImpl, null);
+
+ //retrieve the bytes using getBytes, check they match expectation
+ byte[] receivedBytes = mapMessageImpl.getBytes(myKey1);
+ assertTrue(Arrays.equals(bytes, receivedBytes));
+
+ //retrieve the bytes using getObject, check they match expectation
+ Object o = mapMessageImpl.getObject(myKey1);
+ assertTrue(o instanceof byte[]);
+ assertTrue(Arrays.equals(bytes, (byte[]) o));
+ }
+
+ /**
+ * Verify that setting bytes takes a copy of the array.
+ * Set bytes, then modify them, then retrieve the map entry and verify the
two differ.
+ */
+ @Test
+ public void testSetBytesTakesSnapshot() throws Exception
+ {
+ MapMessageImpl mapMessageImpl = new
MapMessageImpl(_mockSessionImpl,_mockConnectionImpl);
+
+ String name = "myName";
+ byte[] orig = "myBytes".getBytes();
+ byte[] copy = Arrays.copyOf(orig, orig.length);
+
+ //set the original bytes
+ mapMessageImpl.setBytes(name, orig);
+
+ //corrupt the original bytes
+ orig[0] = (byte)0;
+
+ //verify retrieving the bytes still matches the copy but not the
original array
+ byte[] retrieved = mapMessageImpl.getBytes(name);
+ assertFalse(Arrays.equals(orig, retrieved));
+ assertTrue(Arrays.equals(copy, retrieved));
+ }
+
+ /**
+ * Verify that getting bytes returns a copy of the array.
+ * Set bytes, then get them, modify the retrieved value, then get them
again and verify the two differ.
+ */
+ @Test
+ public void testGetBytesReturnsSnapshot() throws Exception
+ {
+ MapMessageImpl mapMessageImpl = new
MapMessageImpl(_mockSessionImpl,_mockConnectionImpl);
+
+ String name = "myName";
+ byte[] orig = "myBytes".getBytes();
+
+ //set the original bytes
+ mapMessageImpl.setBytes(name, orig);
+
+ //retrieve them
+ byte[] retrieved1 = mapMessageImpl.getBytes(name);;
+
+ //corrupt the retrieved bytes
+ retrieved1[0] = (byte)0;
+
+ //verify retrieving the bytes again still matches the original array,
but not the previously retrieved (and now corrupted) bytes.
+ byte[] retrieved2 = mapMessageImpl.getBytes(name);
+ assertTrue(Arrays.equals(orig, retrieved2));
+ assertFalse(Arrays.equals(retrieved1, retrieved2));
+ }
+
+ /**
+ * Verify that setting bytes takes a copy of the array.
+ * Set bytes, then modify them, then retrieve the map entry and verify the
two differ.
+ */
+ @Test
+ public void testSetBytesWithOffsetAndLength() throws Exception
+ {
+ MapMessageImpl mapMessageImpl = new
MapMessageImpl(_mockSessionImpl,_mockConnectionImpl);
+
+ String name = "myName";
+ byte[] orig = "myBytesAll".getBytes();
+
+ //extract the segment containing 'Bytes'
+ int offset = 2;
+ int length = 5;
+ byte[] segment = Arrays.copyOfRange(orig, offset, offset + length);
+
+ //set the same section from the original bytes
+ mapMessageImpl.setBytes(name, orig, offset, length);
+
+ //verify the retrieved bytes from the map match the segment but not
the full original array
+ byte[] retrieved = mapMessageImpl.getBytes(name);
+ assertFalse(Arrays.equals(orig, retrieved));
+ assertTrue(Arrays.equals(segment, retrieved));
+ }
+
+ @Test
+ public void testSetBytesWithNull() throws Exception
+ {
+ MapMessageImpl mapMessageImpl = new
MapMessageImpl(_mockSessionImpl,_mockConnectionImpl);
+
+ String name = "myName";
+ mapMessageImpl.setBytes(name, null);
+ assertNull(mapMessageImpl.getBytes(name));
}
//========= utility methods ========
@@ -733,6 +936,38 @@ public class MapMessageImplTest extends
}
}
+ private void assertGetMapEntryThrowsNumberFormatException(MapMessageImpl
testMessage,
+ String name,
+ Class<?> clazz)
throws JMSException
+ {
+ try
+ {
+ getMapEntryUsingTypeMethod(testMessage, name, clazz);
+
+ fail("expected exception to be thrown");
+ }
+ catch(NumberFormatException nfe)
+ {
+ //expected
+ }
+ }
+
+ private void assertGetMapEntryThrowsNullPointerException(MapMessageImpl
testMessage,
+ String name,
+ Class<?> clazz)
throws JMSException
+ {
+ try
+ {
+ getMapEntryUsingTypeMethod(testMessage, name, clazz);
+
+ fail("expected exception to be thrown");
+ }
+ catch(NullPointerException npe)
+ {
+ //expected
+ }
+ }
+
private Object getMapEntryUsingTypeMethod(MapMessageImpl testMessage,
String name, Class<?> clazz) throws JMSException
{
if(clazz == Boolean.class)
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]