/*
* 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
*
* 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.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import java.time.Duration;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicReference;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.integration.annotation.Gateway;
import org.springframework.integration.annotation.GatewayHeader;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.channel.QueueChannel;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.PollableChannel;
import org.springframework.messaging.support.ChannelInterceptorAdapter;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureCallback;
import reactor.core.publisher.Mono;
/**
* @author Mark Fisher
* @author Oleg Zhurakousky
* @author Gary Russell
* @author Artem Bilan
* @since 2.0
*/
public class AsyncGatewayTests {
@Test
public void futureWithMessageReturned() throws Exception {
QueueChannel requestChannel = new QueueChannel();
startResponder(requestChannel);
GatewayProxyFactoryBean proxyFactory = new GatewayProxyFactoryBean();
proxyFactory.setDefaultRequestChannel(requestChannel);
proxyFactory.setServiceInterface(TestEchoService.class);
proxyFactory.setBeanName("testGateway");
proxyFactory.setBeanFactory(mock(BeanFactory.class));
proxyFactory.afterPropertiesSet();
TestEchoService service = (TestEchoService) proxyFactory.getObject();
Future<Message<?>> f = service.returnMessage("foo");
Object result = f.get(10000, TimeUnit.MILLISECONDS);
assertNotNull(result);
assertEquals("foobar", ((Message<?>) result).getPayload());
}
@Test
public void futureWithError() throws Exception {
final Error error = new Error("error");
DirectChannel channel = new DirectChannel() {
@Override
protected boolean doSend(Message<?> message, long timeout) {
throw error;
}
};
GatewayProxyFactoryBean proxyFactory = new GatewayProxyFactoryBean();
proxyFactory.setDefaultRequestChannel(channel);
proxyFactory.setServiceInterface(TestEchoService.class);
proxyFactory.setBeanName("testGateway");
proxyFactory.setBeanFactory(mock(BeanFactory.class));
proxyFactory.afterPropertiesSet();
TestEchoService service = (TestEchoService) proxyFactory.getObject();
Future<Message<?>> f = service.returnMessage("foo");
try {
f.get(10000, TimeUnit.MILLISECONDS);
fail("Expected Exception");
}
catch (ExecutionException e) {
assertEquals(error, e.getCause());
}
}
@Test
public void listenableFutureWithMessageReturned() throws Exception {
QueueChannel requestChannel = new QueueChannel();
addThreadEnricher(requestChannel);
startResponder(requestChannel);
GatewayProxyFactoryBean proxyFactory = new GatewayProxyFactoryBean();
proxyFactory.setDefaultRequestChannel(requestChannel);
proxyFactory.setServiceInterface(TestEchoService.class);
proxyFactory.setBeanName("testGateway");
proxyFactory.setBeanFactory(mock(BeanFactory.class));
proxyFactory.afterPropertiesSet();
TestEchoService service = (TestEchoService) proxyFactory.getObject();
ListenableFuture<Message<?>> f = service.returnMessageListenable("foo");
long start = System.currentTimeMillis();
final AtomicReference<Message<?>> result = new AtomicReference<Message<?>>();
final CountDownLatch latch = new CountDownLatch(1);
f.addCallback(new ListenableFutureCallback<Message<?>>() {
@Override
public void onSuccess(Message<?> msg) {
result.set(msg);
latch.countDown();
}
@Override
public void onFailure(Throwable t) {
}
});
assertTrue(latch.await(10, TimeUnit.SECONDS));
long elapsed = System.currentTimeMillis() - start;
assertTrue(elapsed >= 200);
assertEquals("foobar", result.get().getPayload());
Object thread = result.get().getHeaders().get("thread");
assertNotEquals(Thread.currentThread(), thread);
}
@Test
public void customFutureReturned() throws Exception {
QueueChannel requestChannel = new QueueChannel();
addThreadEnricher(requestChannel);
startResponder(requestChannel);
GatewayProxyFactoryBean proxyFactory = new GatewayProxyFactoryBean();
proxyFactory.setDefaultRequestChannel(requestChannel);
proxyFactory.setServiceInterface(TestEchoService.class);
proxyFactory.setBeanName("testGateway");
proxyFactory.setBeanFactory(mock(BeanFactory.class));
proxyFactory.afterPropertiesSet();
TestEchoService service = (TestEchoService) proxyFactory.getObject();
CustomFuture f = service.returnCustomFuture("foo");
String result = f.get(10000, TimeUnit.MILLISECONDS);
assertEquals("foobar", result);
assertEquals(Thread.currentThread(), f.thread);
}
@Test
public void nonAsyncFutureReturned() throws Exception {
QueueChannel requestChannel = new QueueChannel();
addThreadEnricher(requestChannel);
startResponder(requestChannel);
GatewayProxyFactoryBean proxyFactory = new GatewayProxyFactoryBean();
proxyFactory.setDefaultRequestChannel(requestChannel);
proxyFactory.setServiceInterface(TestEchoService.class);
proxyFactory.setBeanName("testGateway");
proxyFactory.setBeanFactory(mock(BeanFactory.class));
proxyFactory.setAsyncExecutor(null); // Not async - user flow returns Future<?>
proxyFactory.afterPropertiesSet();
TestEchoService service = (TestEchoService) proxyFactory.getObject();
CustomFuture f = (CustomFuture) service.returnCustomFutureWithTypeFuture("foo");
String result = f.get(10000, TimeUnit.MILLISECONDS);
assertEquals("foobar", result);
assertEquals(Thread.currentThread(), f.thread);
}
protected void addThreadEnricher(QueueChannel requestChannel) {
requestChannel.addInterceptor(new ChannelInterceptorAdapter() {
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
return MessageBuilder.fromMessage(message)
.setHeader("thread", Thread.currentThread())
.build();
}
});
}
@Test
public void futureWithPayloadReturned() throws Exception {
QueueChannel requestChannel = new QueueChannel();
startResponder(requestChannel);
GatewayProxyFactoryBean proxyFactory = new GatewayProxyFactoryBean();
proxyFactory.setDefaultRequestChannel(requestChannel);
proxyFactory.setServiceInterface(TestEchoService.class);
proxyFactory.setBeanName("testGateway");
proxyFactory.setBeanFactory(mock(BeanFactory.class));
proxyFactory.afterPropertiesSet();
TestEchoService service = (TestEchoService) proxyFactory.getObject();
Future<String> f = service.returnString("foo");
Object result = f.get(10000, TimeUnit.MILLISECONDS);
assertNotNull(result);
assertEquals("foobar", result);
}
@Test
public void futureWithWildcardReturned() throws Exception {
QueueChannel requestChannel = new QueueChannel();
startResponder(requestChannel);
GatewayProxyFactoryBean proxyFactory = new GatewayProxyFactoryBean();
proxyFactory.setDefaultRequestChannel(requestChannel);
proxyFactory.setServiceInterface(TestEchoService.class);
proxyFactory.setBeanName("testGateway");
proxyFactory.setBeanFactory(mock(BeanFactory.class));
proxyFactory.afterPropertiesSet();
TestEchoService service = (TestEchoService) proxyFactory.getObject();
Future<?> f = service.returnSomething("foo");
Object result = f.get(10000, TimeUnit.MILLISECONDS);
assertTrue(result instanceof String);
assertEquals("foobar", result);
}
@Test
public void monoWithMessageReturned() throws Exception {
QueueChannel requestChannel = new QueueChannel();
startResponder(requestChannel);
GatewayProxyFactoryBean proxyFactory = new GatewayProxyFactoryBean();
proxyFactory.setDefaultRequestChannel(requestChannel);
proxyFactory.setServiceInterface(TestEchoService.class);
proxyFactory.setBeanFactory(mock(BeanFactory.class));
proxyFactory.setBeanName("testGateway");
proxyFactory.afterPropertiesSet();
TestEchoService service = (TestEchoService) proxyFactory.getObject();
Mono<Message<?>> mono = service.returnMessagePromise("foo");
Object result = mono.block(Duration.ofSeconds(10));
assertEquals("foobar", ((Message<?>) result).getPayload());
}
@Test
public void monoWithPayloadReturned() throws Exception {
QueueChannel requestChannel = new QueueChannel();
startResponder(requestChannel);
GatewayProxyFactoryBean proxyFactory = new GatewayProxyFactoryBean();
proxyFactory.setDefaultRequestChannel(requestChannel);
proxyFactory.setServiceInterface(TestEchoService.class);
proxyFactory.setBeanFactory(mock(BeanFactory.class));
proxyFactory.setBeanName("testGateway");
proxyFactory.afterPropertiesSet();
TestEchoService service = (TestEchoService) proxyFactory.getObject();
Mono<String> mono = service.returnStringPromise("foo");
Object result = mono.block(Duration.ofSeconds(10));
assertEquals("foobar", result);
}
@Test
public void monoWithWildcardReturned() throws Exception {
QueueChannel requestChannel = new QueueChannel();
startResponder(requestChannel);
GatewayProxyFactoryBean proxyFactory = new GatewayProxyFactoryBean();
proxyFactory.setDefaultRequestChannel(requestChannel);
proxyFactory.setServiceInterface(TestEchoService.class);
proxyFactory.setBeanFactory(mock(BeanFactory.class));
proxyFactory.setBeanName("testGateway");
proxyFactory.afterPropertiesSet();
TestEchoService service = (TestEchoService) proxyFactory.getObject();
Mono<?> mono = service.returnSomethingPromise("foo");
Object result = mono.block(Duration.ofSeconds(10));
assertNotNull(result);
assertEquals("foobar", result);
}
@Test
public void monoWithConsumer() throws Exception {
QueueChannel requestChannel = new QueueChannel();
startResponder(requestChannel);
GatewayProxyFactoryBean proxyFactory = new GatewayProxyFactoryBean();
proxyFactory.setDefaultRequestChannel(requestChannel);
proxyFactory.setServiceInterface(TestEchoService.class);
proxyFactory.setBeanFactory(mock(BeanFactory.class));
proxyFactory.setBeanName("testGateway");
proxyFactory.afterPropertiesSet();
TestEchoService service = (TestEchoService) proxyFactory.getObject();
Mono<String> mono = service.returnStringPromise("foo");
final AtomicReference<String> result = new AtomicReference<String>();
final CountDownLatch latch = new CountDownLatch(1);
mono.subscribe(s -> {
result.set(s);
latch.countDown();
});
latch.await(10, TimeUnit.SECONDS);
assertEquals("foobar", result.get());
}
private static void startResponder(final PollableChannel requestChannel) {
new Thread(() -> {
Message<?> input = requestChannel.receive();
String payload = input.getPayload() + "bar";
Message<?> reply = MessageBuilder.withPayload(payload)
.copyHeaders(input.getHeaders())
.build();
try {
Thread.sleep(200);
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
String header = (String) input.getHeaders().get("method");
if (header != null && header.startsWith("returnCustomFuture")) {
reply = MessageBuilder.withPayload(new CustomFuture(payload,
(Thread) input.getHeaders().get("thread")))
.copyHeaders(input.getHeaders())
.build();
}
((MessageChannel) input.getHeaders().getReplyChannel()).send(reply);
}).start();
}
private interface TestEchoService {
Future<String> returnString(String s);
Future<Message<?>> returnMessage(String s);
Future<?> returnSomething(String s);
ListenableFuture<Message<?>> returnMessageListenable(String s);
@Gateway(headers = @GatewayHeader(name = "method", expression = "#gatewayMethod.name"))
CustomFuture returnCustomFuture(String s);
@Gateway(headers = @GatewayHeader(name = "method", expression = "#gatewayMethod.name"))
Future<?> returnCustomFutureWithTypeFuture(String s);
Mono<String> returnStringPromise(String s);
Mono<Message<?>> returnMessagePromise(String s);
Mono<?> returnSomethingPromise(String s);
}
private static class CustomFuture implements Future<String> {
private final String result;
private final Thread thread;
private CustomFuture(String result, Thread thread) {
this.result = result;
this.thread = thread;
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
return false;
}
@Override
public boolean isCancelled() {
return false;
}
@Override
public boolean isDone() {
return true;
}
@Override
public String get() throws InterruptedException, ExecutionException {
return result;
}
@Override
public String get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException,
TimeoutException {
return result;
}
}
}