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:
@@ -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 {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user