/*
* Copyright 2002-2017 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.integration.amqp.config;
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.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.logging.Log;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.connection.Connection;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.support.CorrelationData;
import org.springframework.amqp.rabbit.support.PublisherCallbackChannelImpl;
import org.springframework.amqp.support.AmqpHeaders;
import org.springframework.beans.BeansException;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.integration.amqp.outbound.AmqpOutboundEndpoint;
import org.springframework.integration.amqp.support.AmqpHeaderMapper;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.NullChannel;
import org.springframework.integration.endpoint.EventDrivenConsumer;
import org.springframework.integration.handler.advice.AbstractRequestHandlerAdvice;
import org.springframework.integration.support.MessageBuilder;
import org.springframework.integration.support.context.NamedComponent;
import org.springframework.integration.test.util.TestUtils;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.support.GenericMessage;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.ReflectionUtils;
import com.rabbitmq.client.AMQP.BasicProperties;
import com.rabbitmq.client.Channel;
/**
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Gary Russell
* @author Artem Bilan
* @author Gunnar Hillert
* @since 2.1
*/
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@DirtiesContext
public class AmqpOutboundChannelAdapterParserTests {
private static volatile int adviceCalled;
@Autowired
private ApplicationContext context;
@Autowired
@Qualifier("withCustomHeaderMapper.handler")
private MessageHandler amqpMessageHandlerWithCustomHeaderMapper;
@Test
public void verifyIdAsChannel() {
Object channel = context.getBean("rabbitOutbound");
Object adapter = context.getBean("rabbitOutbound.adapter");
assertEquals(DirectChannel.class, channel.getClass());
assertEquals(EventDrivenConsumer.class, adapter.getClass());
MessageHandler handler = TestUtils.getPropertyValue(adapter, "handler", MessageHandler.class);
assertTrue(handler instanceof NamedComponent);
assertEquals("amqp:outbound-channel-adapter", ((NamedComponent) handler).getComponentType());
handler.handleMessage(new GenericMessage<String>("foo"));
assertEquals(1, adviceCalled);
assertTrue(TestUtils.getPropertyValue(handler, "lazyConnect", Boolean.class));
}
@Test
public void withHeaderMapperCustomHeaders() {
Object eventDrivenConsumer = context.getBean("withHeaderMapperCustomHeaders");
AmqpOutboundEndpoint endpoint = TestUtils.getPropertyValue(eventDrivenConsumer, "handler",
AmqpOutboundEndpoint.class);
assertNotNull(TestUtils.getPropertyValue(endpoint, "defaultDeliveryMode"));
assertFalse(TestUtils.getPropertyValue(endpoint, "lazyConnect", Boolean.class));
assertEquals("42",
TestUtils.getPropertyValue(endpoint, "delayExpression", org.springframework.expression.Expression.class)
.getExpressionString());
assertFalse(TestUtils.getPropertyValue(endpoint, "headersMappedLast", Boolean.class));
Field amqpTemplateField = ReflectionUtils.findField(AmqpOutboundEndpoint.class, "amqpTemplate");
amqpTemplateField.setAccessible(true);
RabbitTemplate amqpTemplate = TestUtils.getPropertyValue(endpoint, "amqpTemplate", RabbitTemplate.class);
amqpTemplate = Mockito.spy(amqpTemplate);
final AtomicBoolean shouldBePersistent = new AtomicBoolean();
Mockito.doAnswer(invocation -> {
Object[] args = invocation.getArguments();
org.springframework.amqp.core.Message amqpMessage = (org.springframework.amqp.core.Message) args[2];
MessageProperties properties = amqpMessage.getMessageProperties();
assertEquals("foo", properties.getHeaders().get("foo"));
assertEquals("foobar", properties.getHeaders().get("foobar"));
assertNull(properties.getHeaders().get("bar"));
assertEquals(shouldBePersistent.get() ? MessageDeliveryMode.PERSISTENT
: MessageDeliveryMode.NON_PERSISTENT, properties.getDeliveryMode());
return null;
})
.when(amqpTemplate).send(Mockito.any(String.class), Mockito.any(String.class),
Mockito.any(org.springframework.amqp.core.Message.class), Mockito.any(CorrelationData.class));
ReflectionUtils.setField(amqpTemplateField, endpoint, amqpTemplate);
MessageChannel requestChannel = context.getBean("requestChannel", MessageChannel.class);
Message<?> message = MessageBuilder.withPayload("hello")
.setHeader("foo", "foo")
.setHeader("bar", "bar")
.setHeader("foobar", "foobar")
.build();
requestChannel.send(message);
Mockito.verify(amqpTemplate, Mockito.times(1)).send(anyString(),
isNull(), Mockito.any(org.springframework.amqp.core.Message.class), isNull());
shouldBePersistent.set(true);
message = MessageBuilder.withPayload("hello")
.setHeader("foo", "foo")
.setHeader("bar", "bar")
.setHeader("foobar", "foobar")
.setHeader(AmqpHeaders.DELIVERY_MODE, MessageDeliveryMode.PERSISTENT)
.build();
requestChannel.send(message);
}
@Test
public void parseWithPublisherConfirms() {
Object eventDrivenConsumer = context.getBean("withPublisherConfirms");
AmqpOutboundEndpoint endpoint = TestUtils.getPropertyValue(eventDrivenConsumer, "handler",
AmqpOutboundEndpoint.class);
NullChannel nullChannel = context.getBean(NullChannel.class);
MessageChannel ackChannel = context.getBean("ackChannel", MessageChannel.class);
assertSame(ackChannel, TestUtils.getPropertyValue(endpoint, "confirmAckChannel"));
assertSame(nullChannel, TestUtils.getPropertyValue(endpoint, "confirmNackChannel"));
}
@SuppressWarnings("rawtypes")
@Test
public void amqpOutboundChannelAdapterWithinChain() {
Object eventDrivenConsumer = context.getBean("chainWithRabbitOutbound");
List chainHandlers = TestUtils.getPropertyValue(eventDrivenConsumer, "handler.handlers", List.class);
AmqpOutboundEndpoint endpoint = (AmqpOutboundEndpoint) chainHandlers.get(0);
assertNull(TestUtils.getPropertyValue(endpoint, "defaultDeliveryMode"));
Field amqpTemplateField = ReflectionUtils.findField(AmqpOutboundEndpoint.class, "amqpTemplate");
amqpTemplateField.setAccessible(true);
RabbitTemplate amqpTemplate = TestUtils.getPropertyValue(endpoint, "amqpTemplate", RabbitTemplate.class);
amqpTemplate = Mockito.spy(amqpTemplate);
Mockito.doAnswer(invocation -> {
Object[] args = invocation.getArguments();
org.springframework.amqp.core.Message amqpMessage = (org.springframework.amqp.core.Message) args[2];
MessageProperties properties = amqpMessage.getMessageProperties();
assertEquals("hello", new String(amqpMessage.getBody()));
assertEquals(MessageDeliveryMode.PERSISTENT, properties.getDeliveryMode());
return null;
})
.when(amqpTemplate).send(Mockito.any(String.class), Mockito.any(String.class),
Mockito.any(org.springframework.amqp.core.Message.class),
Mockito.any(CorrelationData.class));
ReflectionUtils.setField(amqpTemplateField, endpoint, amqpTemplate);
MessageChannel requestChannel = context.getBean("amqpOutboundChannelAdapterWithinChain", MessageChannel.class);
Message<?> message = MessageBuilder.withPayload("hello").build();
requestChannel.send(message);
Mockito.verify(amqpTemplate, Mockito.times(1)).send(Mockito.any(String.class),
isNull(), Mockito.any(org.springframework.amqp.core.Message.class), isNull());
}
@Test
public void testInt2718FailForOutboundAdapterChannelAttribute() {
try {
new ClassPathXmlApplicationContext("AmqpOutboundChannelAdapterWithinChainParserTests-fail-context.xml",
this.getClass()).close();
fail("Expected BeanDefinitionParsingException");
}
catch (BeansException e) {
assertTrue(e instanceof BeanDefinitionParsingException);
assertTrue(e.getMessage().contains("The 'channel' attribute isn't allowed for " +
"'amqp:outbound-channel-adapter' when it is used as a nested element"));
}
}
@Test
public void testInt2773UseDefaultAmqpTemplateExchangeAndRoutingLey() throws IOException {
ConnectionFactory connectionFactory = context.getBean(ConnectionFactory.class);
Connection mockConnection = mock(Connection.class);
Channel mockChannel = mock(Channel.class);
when(connectionFactory.createConnection()).thenReturn(mockConnection);
PublisherCallbackChannelImpl publisherCallbackChannel = new PublisherCallbackChannelImpl(mockChannel);
when(mockConnection.createChannel(false)).thenReturn(publisherCallbackChannel);
MessageChannel requestChannel = context.getBean("toRabbitOnlyWithTemplateChannel", MessageChannel.class);
requestChannel.send(MessageBuilder.withPayload("test").build());
Mockito.verify(mockChannel, Mockito.times(1)).basicPublish(Mockito.eq("default.test.exchange"),
Mockito.eq("default.routing.key"),
Mockito.anyBoolean(), Mockito.any(BasicProperties.class), Mockito.any(byte[].class));
}
@Test
public void testInt2773WithDefaultAmqpTemplateExchangeAndRoutingKey() throws IOException {
ConnectionFactory connectionFactory = context.getBean(ConnectionFactory.class);
Connection mockConnection = mock(Connection.class);
Channel mockChannel = mock(Channel.class);
when(connectionFactory.createConnection()).thenReturn(mockConnection);
PublisherCallbackChannelImpl publisherCallbackChannel = new PublisherCallbackChannelImpl(mockChannel);
when(mockConnection.createChannel(false)).thenReturn(publisherCallbackChannel);
MessageChannel requestChannel = context.getBean("withDefaultAmqpTemplateExchangeAndRoutingKey",
MessageChannel.class);
requestChannel.send(MessageBuilder.withPayload("test").build());
Mockito.verify(mockChannel, Mockito.times(1)).basicPublish(Mockito.eq(""), Mockito.eq(""),
Mockito.anyBoolean(), Mockito.any(BasicProperties.class), Mockito.any(byte[].class));
}
@Test
public void testInt2773WithOverrideToDefaultAmqpTemplateExchangeAndRoutingLey() throws IOException {
ConnectionFactory connectionFactory = context.getBean(ConnectionFactory.class);
Connection mockConnection = mock(Connection.class);
Channel mockChannel = mock(Channel.class);
when(connectionFactory.createConnection()).thenReturn(mockConnection);
PublisherCallbackChannelImpl publisherCallbackChannel = new PublisherCallbackChannelImpl(mockChannel);
when(mockConnection.createChannel(false)).thenReturn(publisherCallbackChannel);
MessageChannel requestChannel = context.getBean("overrideTemplateAttributesToEmpty", MessageChannel.class);
requestChannel.send(MessageBuilder.withPayload("test").build());
Mockito.verify(mockChannel, Mockito.times(1)).basicPublish(Mockito.eq(""), Mockito.eq(""),
Mockito.anyBoolean(), Mockito.any(BasicProperties.class), Mockito.any(byte[].class));
}
@Test
public void testInt2971HeaderMapperAndMappedHeadersExclusivity() {
try {
new ClassPathXmlApplicationContext("AmqpOutboundChannelAdapterParserTests-headerMapper-fail-context.xml",
this.getClass()).close();
}
catch (BeanDefinitionParsingException e) {
assertTrue(e.getMessage().startsWith("Configuration problem: The 'header-mapper' attribute " +
"is mutually exclusive with 'mapped-request-headers' or 'mapped-reply-headers'"));
}
}
@Test
public void testInt2971AmqpOutboundChannelAdapterWithCustomHeaderMapper() {
AmqpHeaderMapper headerMapper = TestUtils.getPropertyValue(this.amqpMessageHandlerWithCustomHeaderMapper,
"headerMapper", AmqpHeaderMapper.class);
assertSame(this.context.getBean("customHeaderMapper"), headerMapper);
assertTrue(TestUtils.getPropertyValue(this.amqpMessageHandlerWithCustomHeaderMapper,
"headersMappedLast", Boolean.class));
}
@Test
public void testInt3430FailForNotLazyConnect() {
RabbitTemplate amqpTemplate = spy(new RabbitTemplate());
ConnectionFactory connectionFactory = mock(ConnectionFactory.class);
RuntimeException toBeThrown = new RuntimeException("Test Connection Exception");
doThrow(toBeThrown).when(connectionFactory).createConnection();
when(amqpTemplate.getConnectionFactory()).thenReturn(connectionFactory);
AmqpOutboundEndpoint handler = new AmqpOutboundEndpoint(amqpTemplate);
Log logger = spy(TestUtils.getPropertyValue(handler, "logger", Log.class));
new DirectFieldAccessor(handler).setPropertyValue("logger", logger);
doNothing().when(logger).error("Failed to eagerly establish the connection.", toBeThrown);
ApplicationContext context = mock(ApplicationContext.class);
handler.setApplicationContext(context);
handler.setBeanFactory(context);
handler.afterPropertiesSet();
handler.start();
handler.stop();
verify(logger, never()).error(anyString(), any(RuntimeException.class));
handler.setLazyConnect(false);
handler.start();
verify(logger).error("Failed to eagerly establish the connection.", toBeThrown);
handler.stop();
}
public static class FooAdvice extends AbstractRequestHandlerAdvice {
@Override
protected Object doInvoke(ExecutionCallback callback, Object target, Message<?> message) throws Exception {
adviceCalled++;
return null;
}
}
}