Support ListenableFuture in @MessageMapping handler methods

This commit introduces support for asynchronous return values thanks
to the new AsyncHandlerMethodReturnValueHandler interface. Out of
the box support for ListenableFuture is also provided.

Issue: SPR-12168
This commit is contained in:
Sebastien Deleuze
2015-05-22 10:34:07 +02:00
parent 792b7b9d11
commit d3db99c201
9 changed files with 353 additions and 7 deletions

View File

@@ -25,12 +25,19 @@ import java.util.concurrent.ConcurrentHashMap;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.mockito.ArgumentCaptor;
import static org.mockito.BDDMockito.given;
import org.mockito.Captor;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyObject;
import org.mockito.Mock;
import org.springframework.context.support.StaticApplicationContext;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.SubscribableChannel;
import org.springframework.messaging.converter.MessageConverter;
import org.springframework.messaging.handler.annotation.DestinationVariable;
import org.springframework.messaging.handler.annotation.Header;
import org.springframework.messaging.handler.annotation.Headers;
@@ -48,12 +55,15 @@ import org.springframework.messaging.simp.annotation.SubscribeMapping;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.stereotype.Controller;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.concurrent.ListenableFutureTask;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import org.springframework.validation.annotation.Validated;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.mockito.Mockito.verify;
import org.mockito.MockitoAnnotations;
/**
* Test fixture for
@@ -61,6 +71,7 @@ import static org.junit.Assert.*;
*
* @author Rossen Stoyanchev
* @author Brian Clozel
* @author Sebastien Deleuze
*/
public class SimpAnnotationMethodMessageHandlerTests {
@@ -70,13 +81,24 @@ public class SimpAnnotationMethodMessageHandlerTests {
private TestController testController;
@Mock
private SubscribableChannel channel;
@Mock
private MessageConverter converter;
@Captor
private ArgumentCaptor<Object> payloadCaptor;
@Before
public void setup() {
SubscribableChannel channel = Mockito.mock(SubscribableChannel.class);
SimpMessageSendingOperations brokerTemplate = new SimpMessagingTemplate(channel);
MockitoAnnotations.initMocks(this);
this.messageHandler = new TestSimpAnnotationMethodMessageHandler(brokerTemplate, channel, channel);
SimpMessagingTemplate brokerTemplate = new SimpMessagingTemplate(this.channel);
brokerTemplate.setMessageConverter(converter);
this.messageHandler = new TestSimpAnnotationMethodMessageHandler(brokerTemplate, this.channel, this.channel);
this.messageHandler.setApplicationContext(new StaticApplicationContext());
this.messageHandler.setValidator(new StringTestValidator(TEST_INVALID_VALUE));
this.messageHandler.afterPropertiesSet();
@@ -225,6 +247,53 @@ public class SimpAnnotationMethodMessageHandlerTests {
assertEquals("handleFoo", controller.method);
}
@Test
@SuppressWarnings("unchecked")
public void listenableFutureSuccess() {
given(this.channel.send(any(Message.class))).willReturn(true);
given(this.converter.toMessage(anyObject(), any(MessageHeaders.class)))
.willReturn((Message) MessageBuilder.withPayload(new byte[0]).build());
ListenableFutureController controller = new ListenableFutureController();
this.messageHandler.registerHandler(controller);
this.messageHandler.setDestinationPrefixes(Arrays.asList("/app1", "/app2/"));
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create();
headers.setSessionId("session1");
headers.setSessionAttributes(new HashMap<>());
headers.setDestination("/app1/listenable-future/success");
Message<?> message = MessageBuilder.withPayload(new byte[0]).setHeaders(headers).build();
this.messageHandler.handleMessage(message);
assertNotNull(controller.future);
controller.future.run();
verify(this.converter).toMessage(this.payloadCaptor.capture(), any(MessageHeaders.class));
assertEquals("foo", this.payloadCaptor.getValue());
}
@Test
@SuppressWarnings("unchecked")
public void listenableFutureFailure() {
given(this.channel.send(any(Message.class))).willReturn(true);
given(this.converter.toMessage(anyObject(), any(MessageHeaders.class)))
.willReturn((Message) MessageBuilder.withPayload(new byte[0]).build());
ListenableFutureController controller = new ListenableFutureController();
this.messageHandler.registerHandler(controller);
this.messageHandler.setDestinationPrefixes(Arrays.asList("/app1", "/app2/"));
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create();
headers.setSessionId("session1");
headers.setSessionAttributes(new HashMap<>());
headers.setDestination("/app1/listenable-future/failure");
Message<?> message = MessageBuilder.withPayload(new byte[0]).setHeaders(headers).build();
this.messageHandler.handleMessage(message);
controller.future.run();
assertTrue(controller.exceptionCatched);
}
private static class TestSimpAnnotationMethodMessageHandler extends SimpAnnotationMethodMessageHandler {
@@ -316,6 +385,34 @@ public class SimpAnnotationMethodMessageHandlerTests {
}
}
@Controller
@MessageMapping("listenable-future")
private static class ListenableFutureController {
private ListenableFutureTask<String> future;
private boolean exceptionCatched = false;
@MessageMapping("success")
public ListenableFutureTask<String> handleListenableFuture() {
this.future = new ListenableFutureTask<String>(() -> "foo");
return this.future;
}
@MessageMapping("failure")
public ListenableFutureTask<String> handleListenableFutureException() {
this.future = new ListenableFutureTask<String>(() -> {
throw new IllegalStateException();
});
return this.future;
}
@MessageExceptionHandler(IllegalStateException.class)
public void handleValidationException() {
this.exceptionCatched = true;
}
}
private static class StringTestValidator implements Validator {