diff --git a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/outbound/AmqpOutboundEndpoint.java b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/outbound/AmqpOutboundEndpoint.java index 71fb21b085..56fb4b4463 100644 --- a/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/outbound/AmqpOutboundEndpoint.java +++ b/spring-integration-amqp/src/main/java/org/springframework/integration/amqp/outbound/AmqpOutboundEndpoint.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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 @@ -27,6 +27,7 @@ import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.amqp.rabbit.core.RabbitTemplate.ReturnCallback; import org.springframework.amqp.rabbit.support.CorrelationData; import org.springframework.amqp.support.AmqpHeaders; +import org.springframework.amqp.support.converter.ContentTypeDelegatingMessageConverter; import org.springframework.amqp.support.converter.MessageConverter; import org.springframework.beans.factory.BeanFactory; import org.springframework.context.ApplicationListener; @@ -340,18 +341,9 @@ public class AmqpOutboundEndpoint extends AbstractReplyProducingMessageHandler private void send(String exchangeName, String routingKey, final Message requestMessage, CorrelationData correlationData) { if (this.amqpTemplate instanceof RabbitTemplate) { - ((RabbitTemplate) this.amqpTemplate).convertAndSend(exchangeName, routingKey, requestMessage.getPayload(), - new MessagePostProcessor() { - @Override - public org.springframework.amqp.core.Message postProcessMessage( - org.springframework.amqp.core.Message message) throws AmqpException { - headerMapper.fromHeadersToRequest(requestMessage.getHeaders(), - message.getMessageProperties()); - checkDeliveryMode(requestMessage, message.getMessageProperties()); - return message; - } - }, - correlationData); + MessageConverter converter = ((RabbitTemplate) this.amqpTemplate).getMessageConverter(); + org.springframework.amqp.core.Message amqpMessage = mapMessage(requestMessage, converter); + ((RabbitTemplate) this.amqpTemplate).send(exchangeName, routingKey, amqpMessage, correlationData); } else { this.amqpTemplate.convertAndSend(exchangeName, routingKey, requestMessage.getPayload(), @@ -372,13 +364,9 @@ public class AmqpOutboundEndpoint extends AbstractReplyProducingMessageHandler Assert.isInstanceOf(RabbitTemplate.class, this.amqpTemplate, "RabbitTemplate implementation is required for publisher confirms"); MessageConverter converter = ((RabbitTemplate) this.amqpTemplate).getMessageConverter(); - MessageProperties amqpMessageProperties = new MessageProperties(); - org.springframework.amqp.core.Message amqpMessage = - converter.toMessage(requestMessage.getPayload(), amqpMessageProperties); - this.headerMapper.fromHeadersToRequest(requestMessage.getHeaders(), amqpMessageProperties); - checkDeliveryMode(requestMessage, amqpMessageProperties); + org.springframework.amqp.core.Message amqpMessage = mapMessage(requestMessage, converter); org.springframework.amqp.core.Message amqpReplyMessage = - ((RabbitTemplate) this.amqpTemplate).sendAndReceive(exchangeName, routingKey,amqpMessage, + ((RabbitTemplate) this.amqpTemplate).sendAndReceive(exchangeName, routingKey, amqpMessage, correlationData); if (amqpReplyMessage == null) { @@ -393,6 +381,21 @@ public class AmqpOutboundEndpoint extends AbstractReplyProducingMessageHandler return builder.build(); } + protected org.springframework.amqp.core.Message mapMessage(Message requestMessage, MessageConverter converter) { + MessageProperties amqpMessageProperties = new MessageProperties(); + org.springframework.amqp.core.Message amqpMessage; + if (converter instanceof ContentTypeDelegatingMessageConverter) { + this.headerMapper.fromHeadersToRequest(requestMessage.getHeaders(), amqpMessageProperties); + amqpMessage = converter.toMessage(requestMessage.getPayload(), amqpMessageProperties); + } + else { // See INT-3002 - map headers last if we're not using a CTDMC + amqpMessage = converter.toMessage(requestMessage.getPayload(), amqpMessageProperties); + this.headerMapper.fromHeadersToRequest(requestMessage.getHeaders(), amqpMessageProperties); + } + checkDeliveryMode(requestMessage, amqpMessageProperties); + return amqpMessage; + } + private void checkDeliveryMode(Message requestMessage, MessageProperties messageProperties) { if (this.defaultDeliveryMode != null && requestMessage.getHeaders().get(AmqpHeaders.DELIVERY_MODE) == null) { diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/outbound/AmqpOutboundEndpointTests-context.xml b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/outbound/AmqpOutboundEndpointTests-context.xml index 6b2eaf2918..3fd372132c 100644 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/outbound/AmqpOutboundEndpointTests-context.xml +++ b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/outbound/AmqpOutboundEndpointTests-context.xml @@ -13,14 +13,15 @@ - + - - + @@ -73,11 +74,31 @@ - + + + + + + + + + + + + + + + + - + diff --git a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/outbound/AmqpOutboundEndpointTests.java b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/outbound/AmqpOutboundEndpointTests.java index 2ef609ef53..16185de12b 100644 --- a/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/outbound/AmqpOutboundEndpointTests.java +++ b/spring-integration-amqp/src/test/java/org/springframework/integration/amqp/outbound/AmqpOutboundEndpointTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -25,10 +25,12 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.amqp.core.Queue; +import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.amqp.support.AmqpHeaders; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.integration.amqp.rule.BrokerRunning; +import org.springframework.integration.mapping.support.JsonHeaders; import org.springframework.integration.support.MessageBuilder; import org.springframework.messaging.Message; import org.springframework.messaging.MessageChannel; @@ -71,7 +73,7 @@ public class AmqpOutboundEndpointTests { private PollableChannel ackChannel; @Autowired - private MessageChannel pcRequestChannelAdapter; + private MessageChannel pcRequestChannelForAdapter; @Autowired private MessageChannel returnRequestChannel; @@ -79,11 +81,21 @@ public class AmqpOutboundEndpointTests { @Autowired private PollableChannel returnChannel; + @Autowired + private MessageChannel ctRequestChannel; + + @Autowired + private ConnectionFactory connectionFactory; + @Test public void testGatewayPublisherConfirms() throws Exception { + while (this.amqpTemplateConfirms.receive(this.queue.getName()) != null) { + ; + } Message message = MessageBuilder.withPayload("hello") .setHeader("amqp_confirmCorrelationData", "foo") + .setHeader(AmqpHeaders.CONTENT_TYPE, "application/json") .build(); this.pcRequestChannel.send(message); Message ack = this.ackChannel.receive(10000); @@ -91,6 +103,12 @@ public class AmqpOutboundEndpointTests { assertEquals("foo", ack.getPayload()); assertEquals(Boolean.TRUE, ack.getHeaders().get(AmqpHeaders.PUBLISH_CONFIRM)); + org.springframework.amqp.core.Message received = this.amqpTemplateConfirms.receive(this.queue.getName()); + assertEquals("\"hello\"", new String(received.getBody(), "UTF-8")); + assertEquals("application/json", received.getMessageProperties().getContentType()); + assertEquals("java.lang.String", received.getMessageProperties().getHeaders() + .get(JsonHeaders.TYPE_ID.replaceFirst(JsonHeaders.PREFIX, ""))); + // test whole message is correlation message = MessageBuilder.withPayload("hello") .build(); @@ -100,8 +118,9 @@ public class AmqpOutboundEndpointTests { assertSame(message.getPayload(), ack.getPayload()); assertEquals(Boolean.TRUE, ack.getHeaders().get(AmqpHeaders.PUBLISH_CONFIRM)); - this.amqpTemplateConfirms.receive(this.queue.getName()); // so queue is deleted - + while (this.amqpTemplateConfirms.receive(this.queue.getName()) != null) { + ; + } } @Test @@ -109,7 +128,7 @@ public class AmqpOutboundEndpointTests { Message message = MessageBuilder.withPayload("hello") .setHeader("amqp_confirmCorrelationData", "foo") .build(); - this.pcRequestChannelAdapter.send(message); + this.pcRequestChannelForAdapter.send(message); Message ack = this.ackChannel.receive(10000); assertNotNull(ack); assertEquals("foo", ack.getPayload()); @@ -125,5 +144,33 @@ public class AmqpOutboundEndpointTests { assertEquals(message.getPayload(), returned.getPayload()); } + @Test + public void adapterWithContentType() throws Exception { + RabbitTemplate template = new RabbitTemplate(this.connectionFactory); + template.setQueue(this.queue.getName()); + while (template.receive() != null) { + ; + } + Message message = MessageBuilder.withPayload("hello") + .setHeader(AmqpHeaders.CONTENT_TYPE, "application/json") + .build(); + this.ctRequestChannel.send(message); + org.springframework.amqp.core.Message m = template.receive(); + assertNotNull(m); + assertEquals("\"hello\"", new String(m.getBody(), "UTF-8")); + assertEquals("application/json", m.getMessageProperties().getContentType()); + assertEquals("java.lang.String", + m.getMessageProperties().getHeaders().get(JsonHeaders.TYPE_ID.replaceFirst(JsonHeaders.PREFIX, ""))); + message = MessageBuilder.withPayload("hello") + .build(); + this.ctRequestChannel.send(message); + m = template.receive(); + assertNotNull(m); + assertEquals("hello", new String(m.getBody(), "UTF-8")); + assertEquals("text/plain", m.getMessageProperties().getContentType()); + while (template.receive() != null) { + ; + } + } } diff --git a/src/reference/asciidoc/amqp.adoc b/src/reference/asciidoc/amqp.adoc index 165193000d..f235f2aa5f 100644 --- a/src/reference/asciidoc/amqp.adoc +++ b/src/reference/asciidoc/amqp.adoc @@ -906,6 +906,48 @@ public class AmqpJavaApplication { } ---- +[[content-type-conversion-outbound]] +=== Outbound Message Conversion + +Spring AMQP 1.4 introduced the `ContentTypeDelegatingMessageConverter` where the actual converter is selected based +on the incoming content type message property. +This could be used by inbound endpoints. + +Spring Integration _version 4.3_ now allows the `ContentTypeDelegatingMessageConverter` to be used on outbound +endpoints as well - with the `contentType` header specifiying which converter will be used. + +The following configures a `ContentTypeDelegatingMessageConverter` with the default converter being the +`SimpleMessageConverter` (which handles java serialization and plain text), together with a JSON converter: + +[source, xml] +---- + + + + + + + + + + + + + + + +---- + +Sending a message to `ctRequestChannel` with the `contentType` header set to `application/json` will cause the +JSON converter to be selected. + +This applies to both the outbound channel adapter and gateway. + [[amqp-channels]] === AMQP Backed Message Channels diff --git a/src/reference/asciidoc/whats-new.adoc b/src/reference/asciidoc/whats-new.adoc index bf1e39953d..e6bd77b48e 100644 --- a/src/reference/asciidoc/whats-new.adoc +++ b/src/reference/asciidoc/whats-new.adoc @@ -52,3 +52,9 @@ See <> for more information. The generated file name for the `FileWritingMessageHandler` can represent _sub-path_ to save the desired directory structure for file in the target directory. See <> for more information. + +==== AMQP Changes + +The outbound endpoints now support a `RabbitTemplate` configured with a `ContentTypeDelegatingMessageConverter` such +that the converter can be chosen based on the message content type. +See <> for more information.