Add support for resolving message headers
This change adds support for @Header and @Headers annotated method arguments to spring-messaging. Also supported are arguments of type MessageHeaders, and MessageHeaderAccessor (including sub-types of MessageHeaderAccessort as long as they provide a wrap(Message<?>) static factory method). This change also renames @MessageBody to @Payload. Issue: SPR-10985
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright 2002-2013 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.messaging.handler.annotation.support;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.context.support.GenericApplicationContext;
|
||||
import org.springframework.core.DefaultParameterNameDiscoverer;
|
||||
import org.springframework.core.GenericTypeResolver;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.convert.support.DefaultConversionService;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.handler.annotation.Header;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
|
||||
/**
|
||||
* Test fixture for {@link HeaderMethodArgumentResolver} tests.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
*/
|
||||
public class HeaderMethodArgumentResolverTests {
|
||||
|
||||
private HeaderMethodArgumentResolver resolver;
|
||||
|
||||
private MethodParameter paramRequired;
|
||||
private MethodParameter paramNamedDefaultValueStringHeader;
|
||||
private MethodParameter paramSystemProperty;
|
||||
private MethodParameter paramNotAnnotated;
|
||||
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
@SuppressWarnings("resource")
|
||||
GenericApplicationContext cxt = new GenericApplicationContext();
|
||||
cxt.refresh();
|
||||
this.resolver = new HeaderMethodArgumentResolver(new DefaultConversionService(), cxt.getBeanFactory());
|
||||
|
||||
Method method = getClass().getDeclaredMethod("handleMessage",
|
||||
String.class, String.class, String.class, String.class);
|
||||
this.paramRequired = new MethodParameter(method, 0);
|
||||
this.paramNamedDefaultValueStringHeader = new MethodParameter(method, 1);
|
||||
this.paramSystemProperty = new MethodParameter(method, 2);
|
||||
this.paramNotAnnotated = new MethodParameter(method, 3);
|
||||
|
||||
this.paramRequired.initParameterNameDiscovery(new DefaultParameterNameDiscoverer());
|
||||
GenericTypeResolver.resolveParameterType(this.paramRequired, HeaderMethodArgumentResolver.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void supportsParameter() {
|
||||
assertTrue(resolver.supportsParameter(paramNamedDefaultValueStringHeader));
|
||||
assertFalse(resolver.supportsParameter(paramNotAnnotated));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveArgument() throws Exception {
|
||||
Message<byte[]> message = MessageBuilder.withPayload(new byte[0]).setHeader("param1", "foo").build();
|
||||
this.resolver.resolveArgument(this.paramRequired, message);
|
||||
}
|
||||
|
||||
@Test(expected = MessageHandlingException.class)
|
||||
public void resolveArgumentNotFound() throws Exception {
|
||||
Message<byte[]> message = MessageBuilder.withPayload(new byte[0]).build();
|
||||
this.resolver.resolveArgument(this.paramRequired, message);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveArgumentDefaultValue() throws Exception {
|
||||
Message<byte[]> message = MessageBuilder.withPayload(new byte[0]).build();
|
||||
Object result = this.resolver.resolveArgument(this.paramNamedDefaultValueStringHeader, message);
|
||||
|
||||
assertEquals("bar", result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveDefaultValueSystemProperty() throws Exception {
|
||||
System.setProperty("systemProperty", "sysbar");
|
||||
try {
|
||||
Message<byte[]> message = MessageBuilder.withPayload(new byte[0]).build();
|
||||
Object result = resolver.resolveArgument(paramSystemProperty, message);
|
||||
assertEquals("sysbar", result);
|
||||
}
|
||||
finally {
|
||||
System.clearProperty("systemProperty");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private void handleMessage(
|
||||
@Header String param1,
|
||||
@Header(value = "name", defaultValue = "bar") String param2,
|
||||
@Header(value = "name", defaultValue="#{systemProperties.systemProperty}") String param3,
|
||||
String param4) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright 2002-2013 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.messaging.handler.annotation.support;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageHeaders;
|
||||
import org.springframework.messaging.handler.annotation.Headers;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
import org.springframework.messaging.support.MessageHeaderAccessor;
|
||||
import org.springframework.messaging.support.NativeMessageHeaderAccessor;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
|
||||
/**
|
||||
* Test fixture for {@link HeadersMethodArgumentResolver} tests.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.0
|
||||
*/
|
||||
public class HeadersMethodArgumentResolverTests {
|
||||
|
||||
private HeadersMethodArgumentResolver resolver;
|
||||
|
||||
private MethodParameter paramAnnotated;
|
||||
private MethodParameter paramAnnotatedNotMap;
|
||||
private MethodParameter paramMessageHeaders;
|
||||
private MethodParameter paramMessageHeaderAccessor;
|
||||
private MethodParameter paramMessageHeaderAccessorSubclass;
|
||||
|
||||
private Message<byte[]> message;
|
||||
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
this.resolver = new HeadersMethodArgumentResolver();
|
||||
|
||||
Method method = getClass().getDeclaredMethod("handleMessage", Map.class, String.class,
|
||||
MessageHeaders.class, MessageHeaderAccessor.class, TestMessageHeaderAccessor.class);
|
||||
|
||||
this.paramAnnotated = new MethodParameter(method, 0);
|
||||
this.paramAnnotatedNotMap = new MethodParameter(method, 1);
|
||||
this.paramMessageHeaders = new MethodParameter(method, 2);
|
||||
this.paramMessageHeaderAccessor = new MethodParameter(method, 3);
|
||||
this.paramMessageHeaderAccessorSubclass = new MethodParameter(method, 4);
|
||||
|
||||
Map<String, Object> headers = new HashMap<String, Object>();
|
||||
headers.put("foo", "bar");
|
||||
this.message = MessageBuilder.withPayload(new byte[0]).copyHeaders(headers).build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void supportsParameter() {
|
||||
assertTrue(this.resolver.supportsParameter(this.paramAnnotated));
|
||||
assertFalse(this.resolver.supportsParameter(this.paramAnnotatedNotMap));
|
||||
assertTrue(this.resolver.supportsParameter(this.paramMessageHeaders));
|
||||
assertTrue(this.resolver.supportsParameter(this.paramMessageHeaderAccessor));
|
||||
assertTrue(this.resolver.supportsParameter(this.paramMessageHeaderAccessorSubclass));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveArgumentAnnotated() throws Exception {
|
||||
Object resolved = this.resolver.resolveArgument(this.paramAnnotated, this.message);
|
||||
|
||||
assertTrue(resolved instanceof Map);
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> headers = (Map<String, Object>) resolved;
|
||||
assertEquals("bar", headers.get("foo"));
|
||||
}
|
||||
|
||||
@Test(expected=IllegalStateException.class)
|
||||
public void resolveArgumentAnnotatedNotMap() throws Exception {
|
||||
this.resolver.resolveArgument(this.paramAnnotatedNotMap, this.message);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveArgumentMessageHeaders() throws Exception {
|
||||
Object resolved = this.resolver.resolveArgument(this.paramMessageHeaders, this.message);
|
||||
|
||||
assertTrue(resolved instanceof MessageHeaders);
|
||||
MessageHeaders headers = (MessageHeaders) resolved;
|
||||
assertEquals("bar", headers.get("foo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveArgumentMessageHeaderAccessor() throws Exception {
|
||||
Object resolved = this.resolver.resolveArgument(this.paramMessageHeaderAccessor, this.message);
|
||||
|
||||
assertTrue(resolved instanceof MessageHeaderAccessor);
|
||||
MessageHeaderAccessor headers = (MessageHeaderAccessor) resolved;
|
||||
assertEquals("bar", headers.getHeader("foo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveArgumentMessageHeaderAccessorSubclass() throws Exception {
|
||||
Object resolved = this.resolver.resolveArgument(this.paramMessageHeaderAccessorSubclass, this.message);
|
||||
|
||||
assertTrue(resolved instanceof TestMessageHeaderAccessor);
|
||||
TestMessageHeaderAccessor headers = (TestMessageHeaderAccessor) resolved;
|
||||
assertEquals("bar", headers.getHeader("foo"));
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private void handleMessage(
|
||||
@Headers Map<String, ?> param1,
|
||||
@Headers String param2,
|
||||
MessageHeaders param3,
|
||||
MessageHeaderAccessor param4,
|
||||
TestMessageHeaderAccessor param5) {
|
||||
}
|
||||
|
||||
|
||||
public static class TestMessageHeaderAccessor extends NativeMessageHeaderAccessor {
|
||||
|
||||
protected TestMessageHeaderAccessor(Message<?> message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public static TestMessageHeaderAccessor wrap(Message<?> message) {
|
||||
return new TestMessageHeaderAccessor(message);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright 2002-2013 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.messaging.handler.annotation.support;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.handler.annotation.Payload;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
import org.springframework.messaging.support.converter.MessageConverter;
|
||||
import org.springframework.messaging.support.converter.StringMessageConverter;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
|
||||
/**
|
||||
* Test fixture for {@link PayloadArgumentResolver}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
*/
|
||||
public class PayloadArgumentResolverTests {
|
||||
|
||||
private PayloadArgumentResolver resolver;
|
||||
|
||||
private MethodParameter param;
|
||||
private MethodParameter paramNotRequired;
|
||||
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
|
||||
MessageConverter messageConverter = new StringMessageConverter();
|
||||
this.resolver = new PayloadArgumentResolver(messageConverter );
|
||||
|
||||
Method method = PayloadArgumentResolverTests.class.getDeclaredMethod("handleMessage",
|
||||
String.class, String.class);
|
||||
|
||||
this.param = new MethodParameter(method , 0);
|
||||
this.paramNotRequired = new MethodParameter(method , 1);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void resolveRequired() throws Exception {
|
||||
Message<?> message = MessageBuilder.withPayload("ABC".getBytes()).build();
|
||||
Object actual = this.resolver.resolveArgument(this.param, message);
|
||||
|
||||
assertEquals("ABC", actual);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveNotRequired() throws Exception {
|
||||
|
||||
Message<?> emptyByteArrayMessage = MessageBuilder.withPayload(new byte[0]).build();
|
||||
assertNull(this.resolver.resolveArgument(this.paramNotRequired, emptyByteArrayMessage));
|
||||
|
||||
Message<?> notEmptyMessage = MessageBuilder.withPayload("ABC".getBytes()).build();
|
||||
assertEquals("ABC", this.resolver.resolveArgument(this.paramNotRequired, notEmptyMessage));
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private void handleMessage(
|
||||
@Payload String param,
|
||||
@Payload(required=false) String paramNotRequired) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,15 +16,26 @@
|
||||
|
||||
package org.springframework.messaging.simp.handler;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.context.support.StaticApplicationContext;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.handler.annotation.Header;
|
||||
import org.springframework.messaging.handler.annotation.Headers;
|
||||
import org.springframework.messaging.handler.annotation.MessageMapping;
|
||||
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
|
||||
import org.springframework.messaging.simp.SimpMessageSendingOperations;
|
||||
import org.springframework.messaging.simp.SimpMessagingTemplate;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
import org.springframework.stereotype.Controller;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
|
||||
/**
|
||||
* Test fixture for {@link AnnotationMethodMessageHandler}.
|
||||
@@ -32,24 +43,76 @@ import org.springframework.stereotype.Controller;
|
||||
*/
|
||||
public class AnnotationMethodMessageHandlerTests {
|
||||
|
||||
private TestAnnotationMethodMessageHandler messageHandler;
|
||||
|
||||
private TestController testController;
|
||||
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
MessageChannel channel = Mockito.mock(MessageChannel.class);
|
||||
SimpMessageSendingOperations brokerTemplate = new SimpMessagingTemplate(channel);
|
||||
this.messageHandler = new TestAnnotationMethodMessageHandler(brokerTemplate, channel);
|
||||
this.messageHandler.setApplicationContext(new StaticApplicationContext());
|
||||
this.messageHandler.afterPropertiesSet();
|
||||
|
||||
testController = new TestController();
|
||||
this.messageHandler.registerHandler(testController);
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Test
|
||||
public void headerArgumentResolution() {
|
||||
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create();
|
||||
headers.setDestination("/headers");
|
||||
headers.setHeader("foo", "bar");
|
||||
Message<?> message = MessageBuilder.withPayload(new byte[0]).setHeaders(headers).build();
|
||||
this.messageHandler.handleMessage(message);
|
||||
|
||||
assertEquals("headers", this.testController.method);
|
||||
assertEquals("bar", this.testController.arguments.get("foo"));
|
||||
assertEquals("bar", ((Map<String, Object>) this.testController.arguments.get("headers")).get("foo"));
|
||||
}
|
||||
|
||||
@Test(expected=IllegalStateException.class)
|
||||
public void duplicateMappings() {
|
||||
this.messageHandler.registerHandler(new DuplicateMappingController());
|
||||
}
|
||||
|
||||
StaticApplicationContext cxt = new StaticApplicationContext();
|
||||
cxt.registerSingleton("d", DuplicateMappingController.class);
|
||||
cxt.refresh();
|
||||
|
||||
MessageChannel channel = Mockito.mock(MessageChannel.class);
|
||||
SimpMessageSendingOperations brokerTemplate = new SimpMessagingTemplate(channel);
|
||||
AnnotationMethodMessageHandler mh = new AnnotationMethodMessageHandler(brokerTemplate, channel);
|
||||
mh.setApplicationContext(cxt);
|
||||
mh.afterPropertiesSet();
|
||||
private static class TestAnnotationMethodMessageHandler extends AnnotationMethodMessageHandler {
|
||||
|
||||
public TestAnnotationMethodMessageHandler(SimpMessageSendingOperations brokerTemplate,
|
||||
MessageChannel webSocketResponseChannel) {
|
||||
|
||||
super(brokerTemplate, webSocketResponseChannel);
|
||||
}
|
||||
|
||||
public void registerHandler(Object handler) {
|
||||
super.detectHandlerMethods(handler);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Controller
|
||||
static class DuplicateMappingController {
|
||||
private static class TestController {
|
||||
|
||||
private String method;
|
||||
|
||||
private Map<String, Object> arguments = new LinkedHashMap<String, Object>();
|
||||
|
||||
|
||||
@MessageMapping("/headers")
|
||||
public void headers(@Header String foo, @Headers Map<String, Object> headers) {
|
||||
this.method = "headers";
|
||||
this.arguments.put("foo", foo);
|
||||
this.arguments.put("headers", headers);
|
||||
}
|
||||
}
|
||||
|
||||
@Controller
|
||||
private static class DuplicateMappingController {
|
||||
|
||||
@MessageMapping(value="/duplicate")
|
||||
public void handle1() { }
|
||||
|
||||
Reference in New Issue
Block a user