diff --git a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/support/DefaultMessagePropertiesConverter.java b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/support/DefaultMessagePropertiesConverter.java index c6409db4..d17d482f 100644 --- a/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/support/DefaultMessagePropertiesConverter.java +++ b/spring-rabbit/src/main/java/org/springframework/amqp/rabbit/support/DefaultMessagePropertiesConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2014 the original author or authors. * * Licensed 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 @@ -15,6 +15,7 @@ package org.springframework.amqp.rabbit.support; import java.io.UnsupportedEncodingException; import java.math.BigDecimal; +import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; @@ -35,6 +36,7 @@ import com.rabbitmq.client.LongString; * * @author Mark Fisher * @author Gary Russell + * @author Soeren Unruh * @since 1.0 */ public class DefaultMessagePropertiesConverter implements MessagePropertiesConverter { @@ -45,11 +47,7 @@ public class DefaultMessagePropertiesConverter implements MessagePropertiesConve Map headers = source.getHeaders(); if (!CollectionUtils.isEmpty(headers)) { for (Map.Entry entry : headers.entrySet()) { - Object value = entry.getValue(); - if (value instanceof LongString) { - value = this.convertLongString((LongString) value, charset); - } - target.setHeader(entry.getKey(), value); + target.setHeader(entry.getKey(), convertLongStringIfNecessary(entry.getValue(), charset)); } } target.setTimestamp(source.getTimestamp()); @@ -130,6 +128,11 @@ public class DefaultMessagePropertiesConverter implements MessagePropertiesConve return writableHeaders; } + /** + * Converts a header value to a String if the value type is unsupported by AMQP, also handling values + * nested inside Lists or Maps. + *

{@code null} values are passed through, although Rabbit client will throw an IllegalArgumentException. + */ private Object convertHeaderValueIfNecessary(Object value) { boolean valid = (value instanceof String) || (value instanceof byte[]) || (value instanceof Boolean) || (value instanceof LongString) || (value instanceof Integer) || (value instanceof Long) @@ -139,6 +142,22 @@ public class DefaultMessagePropertiesConverter implements MessagePropertiesConve if (!valid && value != null) { value = value.toString(); } + else if (value instanceof List) { + List writableList = new ArrayList(((List) value).size()); + for (Object listValue : (List) value) { + writableList.add(convertHeaderValueIfNecessary(listValue)); + } + value = writableList; + } + else if (value instanceof Map) { + @SuppressWarnings("unchecked") + Map originalMap = (Map) value; + Map writableMap = new HashMap(originalMap.size()); + for (Map.Entry entry : originalMap.entrySet()) { + writableMap.put(entry.getKey(), this.convertHeaderValueIfNecessary(entry.getValue())); + } + value = writableMap; + } return value; } @@ -158,4 +177,31 @@ public class DefaultMessagePropertiesConverter implements MessagePropertiesConve } } + /** + * Converts a LongString value using {@link #convertLongString(LongString, String)}, also handling values + * nested in Lists or Maps. + */ + private Object convertLongStringIfNecessary(Object value, String charset) { + if (value instanceof LongString) { + value = convertLongString((LongString) value, charset); + } + else if (value instanceof List) { + List convertedList = new ArrayList(((List) value).size()); + for (Object listValue : (List) value) { + convertedList.add(this.convertLongStringIfNecessary(listValue, charset)); + } + value = convertedList; + } + else if (value instanceof Map) { + @SuppressWarnings("unchecked") + Map originalMap = (Map) value; + Map convertedMap = new HashMap(); + for (Map.Entry entry : originalMap.entrySet()) { + convertedMap.put(entry.getKey(), this.convertLongStringIfNecessary(entry.getValue(), charset)); + } + value = convertedMap; + } + return value; + } + } diff --git a/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/support/DefaultMessagePropertiesConverterTests.java b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/support/DefaultMessagePropertiesConverterTests.java new file mode 100644 index 00000000..661e92dd --- /dev/null +++ b/spring-rabbit/src/test/java/org/springframework/amqp/rabbit/support/DefaultMessagePropertiesConverterTests.java @@ -0,0 +1,147 @@ +/* + * Copyright 2014 the original author or authors. + * + * Licensed 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.springframework.amqp.rabbit.support; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.UnsupportedEncodingException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; + +import org.springframework.amqp.core.MessageProperties; + +import com.rabbitmq.client.AMQP.BasicProperties; +import com.rabbitmq.client.Envelope; +import com.rabbitmq.client.LongString; +import com.rabbitmq.client.impl.LongStringHelper; + +/** + * @author Soeren Unruh + * @since 1.3 + */ +public class DefaultMessagePropertiesConverterTests { + + private final MessagePropertiesConverter messagePropertiesConverter = new DefaultMessagePropertiesConverter(); + + private final Envelope envelope = new Envelope(0, false, null, null); + + private final LongString longString = LongStringHelper.asLongString("longString"); + + private String longStringString; + + @Before + public void init() throws UnsupportedEncodingException { + longStringString = new String(longString.getBytes(), "UTF-8"); + } + + @Test + public void testToMessagePropertiesLongString() { + Map headers = new HashMap(); + headers.put("longString", longString); + BasicProperties source = new BasicProperties.Builder() + .headers(headers) + .build(); + MessageProperties messageProperties = messagePropertiesConverter.toMessageProperties(source, envelope, "UTF-8"); + assertEquals("LongString not converted to String", + longStringString, messageProperties.getHeaders().get("longString")); + } + + @Test + public void testToMessagePropertiesLongStringInList() { + Map headers = new HashMap(); + headers.put("list", Arrays.asList(longString)); + BasicProperties source = new BasicProperties.Builder() + .headers(headers) + .build(); + MessageProperties messageProperties = messagePropertiesConverter.toMessageProperties(source, envelope, "UTF-8"); + assertEquals("LongString nested in List not converted to String", + longStringString, ((List) messageProperties.getHeaders().get("list")).get(0)); + } + + @Test + @SuppressWarnings("unchecked") + public void testToMessagePropertiesLongStringDeepInList() { + Map headers = new HashMap(); + headers.put("list", Arrays.asList(Arrays.asList(longString))); + BasicProperties source = new BasicProperties.Builder() + .headers(headers) + .build(); + MessageProperties messageProperties = messagePropertiesConverter.toMessageProperties(source, envelope, "UTF-8"); + assertEquals("LongString deeply nested in List not converted to String", + longStringString, ((List) ((List) messageProperties.getHeaders().get("list")).get(0)).get(0)); + } + + @Test + @SuppressWarnings("unchecked") + public void testToMessagePropertiesLongStringInMap() { + Map mapWithLongString = new HashMap(); + mapWithLongString.put("longString", longString); + Map headers = new HashMap(); + headers.put("map", mapWithLongString); + BasicProperties source = new BasicProperties.Builder() + .headers(headers) + .build(); + MessageProperties messageProperties = messagePropertiesConverter.toMessageProperties(source, envelope, "UTF-8"); + assertEquals("LongString nested in Map not converted to String", + longStringString, ((Map) messageProperties.getHeaders().get("map")).get("longString")); + } + + @Test + public void testFromUnsupportedValue() { + MessageProperties messageProperties = new MessageProperties(); + messageProperties.setHeader("unsupported", new Object()); + BasicProperties basicProps = messagePropertiesConverter.fromMessageProperties(messageProperties, "UTF-8"); + assertTrue("Unsupported value not converted to String", + basicProps.getHeaders().get("unsupported") instanceof String); + } + + @Test + public void testFromUnsupportedValueInList() { + MessageProperties messageProperties = new MessageProperties(); + List listWithUnsupportedValue = Arrays.asList(new Object()); + messageProperties.setHeader("list", listWithUnsupportedValue); + BasicProperties basicProps = messagePropertiesConverter.fromMessageProperties(messageProperties, "UTF-8"); + assertTrue("Unsupported value nested in List not converted to String", + ((List) basicProps.getHeaders().get("list")).get(0) instanceof String); + } + + @Test + @SuppressWarnings("unchecked") + public void testFromUnsupportedValueDeepInList() { + MessageProperties messageProperties = new MessageProperties(); + List> listWithUnsupportedValue = Arrays.asList(Arrays.asList(new Object())); + messageProperties.setHeader("list", listWithUnsupportedValue); + BasicProperties basicProps = messagePropertiesConverter.fromMessageProperties(messageProperties, "UTF-8"); + assertTrue("Unsupported value deeply nested in List not converted to String", + ((List) ((List) basicProps.getHeaders().get("list")).get(0)).get(0) instanceof String); + } + + @Test + @SuppressWarnings("unchecked") + public void testFromUnsupportedValueInMap() { + MessageProperties messageProperties = new MessageProperties(); + Map mapWithUnsupportedValue = new HashMap(); + mapWithUnsupportedValue.put("unsupported", new Object()); + messageProperties.setHeader("map", mapWithUnsupportedValue); + BasicProperties basicProps = messagePropertiesConverter.fromMessageProperties(messageProperties, "UTF-8"); + assertTrue("Unsupported value nested in Map not converted to String", + ((Map) basicProps.getHeaders().get("map")).get("unsupported") instanceof String); + } + +}