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:
Rossen Stoyanchev
2013-10-13 11:04:22 -04:00
parent ca9df4d2ef
commit 8ae88c20d1
18 changed files with 1155 additions and 112 deletions

View File

@@ -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) {
}
}

View File

@@ -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);
}
}
}

View File

@@ -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) {
}
}

View File

@@ -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() { }