/*
* 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.gateway;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.instanceOf;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.hamcrest.Matchers;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.expression.common.LiteralExpression;
import org.springframework.integration.annotation.AnnotationConstants;
import org.springframework.integration.annotation.BridgeTo;
import org.springframework.integration.annotation.Gateway;
import org.springframework.integration.annotation.GatewayHeader;
import org.springframework.integration.annotation.IntegrationComponentScan;
import org.springframework.integration.annotation.MessagingGateway;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.config.EnableIntegration;
import org.springframework.integration.handler.BridgeHandler;
import org.springframework.integration.support.MessageBuilder;
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.MessagingException;
import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.support.ChannelInterceptorAdapter;
import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Component;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureCallback;
/**
* @author Oleg Zhurakousky
* @author Gunnar Hillert
* @author Gary Russell
* @author Artem Bilan
*/
@ContextConfiguration(classes = GatewayInterfaceTests.TestConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
@DirtiesContext
public class GatewayInterfaceTests {
@Autowired
private Int2634Gateway int2634Gateway;
@Autowired
private ExecGateway execGateway;
@Autowired
private NoExecGateway noExecGateway;
@Autowired
@Qualifier("&gatewayInterfaceTests$ExecGateway")
private GatewayProxyFactoryBean execGatewayFB;
@Autowired
@Qualifier("&gatewayInterfaceTests$NoExecGateway")
private GatewayProxyFactoryBean noExecGatewayFB;
@Autowired
private SimpleAsyncTaskExecutor exec;
@Autowired
private AutoCreateChannelService autoCreateChannelService;
@Autowired(required = false)
private NotAGatewayByScanFilter notAGatewayByScanFilter;
@Autowired(required = false)
private GatewayByAnnotationGPFB gatewayByAnnotationGPFB;
@Autowired
@Qualifier("&annotationGatewayProxyFactoryBean")
private GatewayProxyFactoryBean annotationGatewayProxyFactoryBean;
@Autowired
private MessageChannel gatewayChannel;
@Autowired
private MessageChannel errorChannel;
@Test
public void testWithServiceSuperclassAnnotatedMethod() throws Exception {
ConfigurableApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass());
DirectChannel channel = ac.getBean("requestChannelFoo", DirectChannel.class);
final Method fooMethod = Foo.class.getMethod("foo", String.class);
final AtomicBoolean called = new AtomicBoolean();
MessageHandler handler = message -> {
assertThat((String) message.getHeaders().get("name"), equalTo("foo"));
assertThat(
(String) message.getHeaders().get("string"),
equalTo("public abstract void org.springframework.integration.gateway.GatewayInterfaceTests$Foo.foo(java.lang.String)"));
assertThat((Method) message.getHeaders().get("object"), equalTo(fooMethod));
assertThat((String) message.getPayload(), equalTo("hello"));
assertThat(new MessageHeaderAccessor(message).getErrorChannel(), equalTo("errorChannel"));
called.set(true);
};
channel.subscribe(handler);
Bar bar = ac.getBean(Bar.class);
bar.foo("hello");
assertTrue(called.get());
Map<?, ?> gateways = TestUtils.getPropertyValue(ac.getBean("&sampleGateway"), "gatewayMap", Map.class);
assertEquals(5, gateways.size());
ac.close();
}
@Test
public void testWithServiceSuperclassAnnotatedMethodOverridePE() throws Exception {
ConfigurableApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests2-context.xml", this.getClass());
DirectChannel channel = ac.getBean("requestChannelFoo", DirectChannel.class);
final Method fooMethod = Foo.class.getMethod("foo", String.class);
final AtomicBoolean called = new AtomicBoolean();
MessageHandler handler = message -> {
assertThat((String) message.getHeaders().get("name"), equalTo("foo"));
assertThat(
(String) message.getHeaders().get("string"),
equalTo("public abstract void org.springframework.integration.gateway.GatewayInterfaceTests$Foo.foo(java.lang.String)"));
assertThat((Method) message.getHeaders().get("object"), equalTo(fooMethod));
assertThat((String) message.getPayload(), equalTo("foo"));
called.set(true);
};
channel.subscribe(handler);
Bar bar = ac.getBean(Bar.class);
bar.foo("hello");
assertTrue(called.get());
ac.close();
}
@Test
public void testWithServiceAnnotatedMethod() {
ConfigurableApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass());
DirectChannel channel = ac.getBean("requestChannelBar", DirectChannel.class);
MessageHandler handler = mock(MessageHandler.class);
channel.subscribe(handler);
Bar bar = ac.getBean(Bar.class);
bar.bar("hello");
verify(handler, times(1)).handleMessage(Mockito.any(Message.class));
ac.close();
}
@Test
public void testWithServiceSuperclassUnAnnotatedMethod() throws Exception {
ConfigurableApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass());
DirectChannel channel = ac.getBean("requestChannelBaz", DirectChannel.class);
final Method bazMethod = Foo.class.getMethod("baz", String.class);
final AtomicBoolean called = new AtomicBoolean();
MessageHandler handler = message -> {
assertThat((String) message.getHeaders().get("name"), equalTo("overrideGlobal"));
assertThat(
(String) message.getHeaders().get("string"),
equalTo("public abstract void org.springframework.integration.gateway.GatewayInterfaceTests$Foo.baz(java.lang.String)"));
assertThat((Method) message.getHeaders().get("object"), equalTo(bazMethod));
assertThat((String) message.getPayload(), equalTo("hello"));
called.set(true);
};
channel.subscribe(handler);
Bar bar = ac.getBean(Bar.class);
bar.baz("hello");
assertTrue(called.get());
ac.close();
}
@Test
public void testWithServiceUnAnnotatedMethodGlobalHeaderDoesntOverride() throws Exception {
ConfigurableApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass());
DirectChannel channel = ac.getBean("requestChannelBaz", DirectChannel.class);
final Method quxMethod = Bar.class.getMethod("qux", String.class, String.class);
final AtomicBoolean called = new AtomicBoolean();
MessageHandler handler = message -> {
assertThat((String) message.getHeaders().get("name"), equalTo("arg1"));
assertThat(
(String) message.getHeaders().get("string"),
equalTo("public abstract void org.springframework.integration.gateway.GatewayInterfaceTests$Bar.qux(java.lang.String,java.lang.String)"));
assertThat((Method) message.getHeaders().get("object"), equalTo(quxMethod));
assertThat((String) message.getPayload(), equalTo("hello"));
called.set(true);
};
channel.subscribe(handler);
Bar bar = ac.getBean(Bar.class);
bar.qux("hello", "arg1");
assertTrue(called.get());
ac.close();
}
@Test
public void testWithServiceCastAsSuperclassAnnotatedMethod() {
ConfigurableApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass());
DirectChannel channel = ac.getBean("requestChannelFoo", DirectChannel.class);
MessageHandler handler = mock(MessageHandler.class);
channel.subscribe(handler);
Foo foo = ac.getBean(Foo.class);
foo.foo("hello");
verify(handler, times(1)).handleMessage(Mockito.any(Message.class));
ac.close();
}
@Test
public void testWithServiceCastAsSuperclassUnAnnotatedMethod() {
ConfigurableApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass());
DirectChannel channel = ac.getBean("requestChannelBaz", DirectChannel.class);
MessageHandler handler = mock(MessageHandler.class);
channel.subscribe(handler);
Foo foo = ac.getBean(Foo.class);
foo.baz("hello");
verify(handler, times(1)).handleMessage(Mockito.any(Message.class));
ac.close();
}
@Test
public void testWithServiceHashcode() throws Exception {
ConfigurableApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass());
DirectChannel channel = ac.getBean("requestChannelBaz", DirectChannel.class);
MessageHandler handler = mock(MessageHandler.class);
channel.subscribe(handler);
Bar bar = ac.getBean(Bar.class);
assertEquals(bar.hashCode(), ac.getBean(Bar.class).hashCode());
verify(handler, times(0)).handleMessage(Mockito.any(Message.class));
ac.close();
}
@Test
public void testWithServiceToString() {
ConfigurableApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass());
DirectChannel channel = ac.getBean("requestChannelBaz", DirectChannel.class);
MessageHandler handler = mock(MessageHandler.class);
channel.subscribe(handler);
Bar bar = ac.getBean(Bar.class);
bar.toString();
verify(handler, times(0)).handleMessage(Mockito.any(Message.class));
ac.close();
}
@Test
public void testWithServiceEquals() throws Exception {
ConfigurableApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass());
DirectChannel channel = ac.getBean("requestChannelBaz", DirectChannel.class);
MessageHandler handler = mock(MessageHandler.class);
channel.subscribe(handler);
Bar bar = ac.getBean(Bar.class);
assertTrue(bar.equals(ac.getBean(Bar.class)));
GatewayProxyFactoryBean fb = new GatewayProxyFactoryBean(Bar.class);
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
bf.registerSingleton("requestChannelBar", channel);
bf.registerSingleton("requestChannelBaz", channel);
bf.registerSingleton("requestChannelFoo", channel);
fb.setBeanFactory(bf);
fb.afterPropertiesSet();
assertFalse(bar.equals(fb.getObject()));
verify(handler, times(0)).handleMessage(Mockito.any(Message.class));
ac.close();
}
@Test
public void testWithServiceGetClass() {
ConfigurableApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass());
DirectChannel channel = ac.getBean("requestChannelBaz", DirectChannel.class);
MessageHandler handler = mock(MessageHandler.class);
channel.subscribe(handler);
Bar bar = ac.getBean(Bar.class);
bar.getClass();
verify(handler, times(0)).handleMessage(Mockito.any(Message.class));
ac.close();
}
@Test(expected = IllegalArgumentException.class)
public void testWithServiceAsNotAnInterface() {
new GatewayProxyFactoryBean(NotAnInterface.class);
}
@Test
public void testWithCustomMapper() {
ConfigurableApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass());
DirectChannel channel = ac.getBean("requestChannelBaz", DirectChannel.class);
final AtomicBoolean called = new AtomicBoolean();
MessageHandler handler = message -> {
assertThat((String) message.getPayload(), equalTo("fizbuz"));
called.set(true);
};
channel.subscribe(handler);
Baz baz = ac.getBean(Baz.class);
baz.baz("hello");
assertTrue(called.get());
ac.close();
}
@Test
public void testLateReply() throws Exception {
ConfigurableApplicationContext ac = new ClassPathXmlApplicationContext("GatewayInterfaceTests-context.xml", this.getClass());
Bar baz = ac.getBean(Bar.class);
String reply = baz.lateReply("hello");
assertNull(reply);
PollableChannel errorChannel = ac.getBean("errorChannel", PollableChannel.class);
Message<?> receive = errorChannel.receive(5000);
assertNotNull(receive);
MessagingException messagingException = (MessagingException) receive.getPayload();
assertThat(messagingException.getMessage(), Matchers.startsWith("Reply message received but the receiving thread has exited due to a timeout"));
ac.close();
}
@Test
public void testInt2634() {
Map<Object, Object> param = Collections.<Object, Object>singletonMap(1, 1);
Object result = this.int2634Gateway.test2(param);
assertEquals(param, result);
result = this.int2634Gateway.test1(param);
assertEquals(param, result);
}
/*
* Tests use current thread in payload and reply has the thread that actually
* performed the send() on gatewayThreadChannel.
*/
@Test
public void testExecs() throws Exception {
assertSame(exec, TestUtils.getPropertyValue(execGatewayFB, "asyncExecutor"));
assertNull(TestUtils.getPropertyValue(noExecGatewayFB, "asyncExecutor"));
Future<Thread> result = this.int2634Gateway.test3(Thread.currentThread());
assertNotEquals(Thread.currentThread(), result.get());
assertThat(result.get().getName(), startsWith("SimpleAsync"));
result = this.execGateway.test1(Thread.currentThread());
assertNotEquals(Thread.currentThread(), result.get());
assertThat(result.get().getName(), startsWith("exec-"));
result = this.noExecGateway.test1(Thread.currentThread());
assertEquals(Thread.currentThread(), result.get());
ListenableFuture<Thread> result2 = this.execGateway.test2(Thread.currentThread());
final CountDownLatch latch = new CountDownLatch(1);
final AtomicReference<Thread> thread = new AtomicReference<Thread>();
result2.addCallback(new ListenableFutureCallback<Thread>() {
@Override
public void onSuccess(Thread result) {
thread.set(result);
latch.countDown();
}
@Override
public void onFailure(Throwable t) {
}
});
assertTrue(latch.await(10, TimeUnit.SECONDS));
assertThat(result2.get().getName(), startsWith("exec-"));
/*
@IntegrationComponentScan(useDefaultFilters = false,
includeFilters = @ComponentScan.Filter(TestMessagingGateway.class))
excludes this a candidate
*/
assertNull(this.notAGatewayByScanFilter);
}
@Test
public void testAutoCreateChannelGateway() {
assertEquals("foo", this.autoCreateChannelService.service("foo"));
}
@Test
@SuppressWarnings("rawtypes")
public void testAnnotationGatewayProxyFactoryBean() {
assertNotNull(this.gatewayByAnnotationGPFB);
assertSame(this.exec, this.annotationGatewayProxyFactoryBean.getAsyncExecutor());
assertEquals(1111L,
TestUtils.getPropertyValue(this.annotationGatewayProxyFactoryBean, "defaultRequestTimeout"));
assertEquals(222L,
TestUtils.getPropertyValue(this.annotationGatewayProxyFactoryBean, "defaultReplyTimeout"));
Collection<MessagingGatewaySupport> messagingGateways =
this.annotationGatewayProxyFactoryBean.getGateways().values();
assertEquals(1, messagingGateways.size());
MessagingGatewaySupport gateway = messagingGateways.iterator().next();
assertSame(this.gatewayChannel, gateway.getRequestChannel());
assertSame(this.gatewayChannel, gateway.getReplyChannel());
assertSame(this.errorChannel, gateway.getErrorChannel());
Object requestMapper = TestUtils.getPropertyValue(gateway, "requestMapper");
assertEquals("@foo",
TestUtils.getPropertyValue(requestMapper, "payloadExpression.expression"));
Map globalHeaderExpressions = TestUtils.getPropertyValue(requestMapper, "globalHeaderExpressions", Map.class);
assertEquals(1, globalHeaderExpressions.size());
Object barHeaderExpression = globalHeaderExpressions.get("bar");
assertNotNull(barHeaderExpression);
assertThat(barHeaderExpression, instanceOf(LiteralExpression.class));
assertEquals("baz", ((LiteralExpression) barHeaderExpression).getValue());
}
public interface Foo {
@Gateway(requestChannel = "requestChannelFoo")
void foo(String payload);
void baz(String payload);
String lateReply(String payload);
}
public interface Bar extends Foo {
@Gateway(requestChannel = "requestChannelBar")
void bar(String payload);
void qux(String payload, @Header("name") String nameHeader);
}
public static class NotAnInterface {
public void fail(String payload) {
}
}
public interface Baz {
void baz(String payload);
}
public static class BazMapper implements MethodArgsMessageMapper {
@Override
public Message<?> toMessage(MethodArgsHolder object) throws Exception {
return MessageBuilder.withPayload("fizbuz").build();
}
}
@Component
public static class AutoCreateChannelService {
@Autowired
private AutoCreateChannelGateway gateway;
public String service(String request) {
return this.gateway.foo(request);
}
}
@Configuration
@ComponentScan(includeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE,
classes = AutoCreateChannelService.class))
@IntegrationComponentScan(useDefaultFilters = false,
includeFilters = @ComponentScan.Filter(TestMessagingGateway.class))
@EnableIntegration
public static class TestConfig {
@Bean
@BridgeTo
public MessageChannel gatewayChannel() {
return new DirectChannel();
}
@Bean
@BridgeTo
public MessageChannel gatewayThreadChannel() {
DirectChannel channel = new DirectChannel();
channel.addInterceptor(new ChannelInterceptorAdapter() {
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
Object payload;
if (Thread.currentThread().equals(message.getPayload())) {
// running on calling thread - need to return a Future.
payload = new AsyncResult<Thread>(Thread.currentThread());
}
else {
payload = Thread.currentThread();
}
return MessageBuilder.withPayload(payload)
.copyHeaders(message.getHeaders())
.build();
}
});
return channel;
}
@Bean
public AsyncTaskExecutor exec() {
SimpleAsyncTaskExecutor simpleAsyncTaskExecutor = new SimpleAsyncTaskExecutor();
simpleAsyncTaskExecutor.setThreadNamePrefix("exec-");
return simpleAsyncTaskExecutor;
}
@Bean
@ServiceActivator(inputChannel = "autoCreateChannel")
public MessageHandler autoCreateServiceActivator() {
return new BridgeHandler();
}
@Bean
public GatewayProxyFactoryBean annotationGatewayProxyFactoryBean() {
return new AnnotationGatewayProxyFactoryBean(GatewayByAnnotationGPFB.class);
}
}
@MessagingGateway
@TestMessagingGateway
public interface Int2634Gateway {
@Gateway(requestChannel = "gatewayChannel", payloadExpression = "#args[0]")
Object test1(Map<Object, ?> map);
@Gateway(requestChannel = "gatewayChannel")
Object test2(@Payload Map<Object, ?> map);
@Gateway(requestChannel = "gatewayThreadChannel")
Future<Thread> test3(Thread caller);
}
@MessagingGateway(asyncExecutor = "exec")
@TestMessagingGateway
public interface ExecGateway {
@Gateway(requestChannel = "gatewayThreadChannel")
Future<Thread> test1(Thread caller);
@Gateway(requestChannel = "gatewayThreadChannel")
ListenableFuture<Thread> test2(Thread caller);
}
@MessagingGateway(asyncExecutor = AnnotationConstants.NULL)
@TestMessagingGateway
public interface NoExecGateway {
@Gateway(requestChannel = "gatewayThreadChannel")
Future<Thread> test1(Thread caller);
}
@MessagingGateway(defaultRequestChannel = "autoCreateChannel")
@TestMessagingGateway
public interface AutoCreateChannelGateway {
String foo(String payload);
}
@MessagingGateway
public interface NotAGatewayByScanFilter {
String foo(String payload);
}
@MessagingGateway(
defaultRequestChannel = "${gateway.channel:gatewayChannel}",
defaultReplyChannel = "${gateway.channel:gatewayChannel}",
defaultPayloadExpression = "${gateway.payload:@foo}",
errorChannel = "${gateway.channel:errorChannel}",
asyncExecutor = "${gateway.executor:exec}",
defaultRequestTimeout = "${gateway.timeout:1111}",
defaultReplyTimeout = "${gateway.timeout:222}",
defaultHeaders = {
@GatewayHeader(name = "${gateway.header.name:bar}",
value = "${gateway.header.value:baz}")
})
public interface GatewayByAnnotationGPFB {
String foo(String payload);
}
@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
public @interface TestMessagingGateway {
}
}