Support Validation in @MessageMapping annotated methods
Payload parameters in @MessageMapping annotated methods can now also be validated when annotated with a Validation annotation (@Valid, @Validated...). A default Validator is registered by the MessageBroker Configurer, but it is possible to provide a list of custom validators as well. Issue: SPR-11185
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2014 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.
|
||||
@@ -20,12 +20,16 @@ import java.lang.reflect.Method;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
|
||||
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.converter.MessageConverter;
|
||||
import org.springframework.messaging.converter.StringMessageConverter;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.Validator;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
@@ -33,6 +37,7 @@ import static org.junit.Assert.*;
|
||||
* Test fixture for {@link PayloadArgumentResolver}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public class PayloadArgumentResolverTests {
|
||||
|
||||
@@ -41,20 +46,22 @@ public class PayloadArgumentResolverTests {
|
||||
private MethodParameter param;
|
||||
private MethodParameter paramNotRequired;
|
||||
private MethodParameter paramWithSpelExpression;
|
||||
private MethodParameter paramValidated;
|
||||
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
|
||||
MessageConverter messageConverter = new StringMessageConverter();
|
||||
this.resolver = new PayloadArgumentResolver(messageConverter );
|
||||
this.resolver = new PayloadArgumentResolver(new StringMessageConverter(), testValidator());
|
||||
|
||||
Method method = PayloadArgumentResolverTests.class.getDeclaredMethod("handleMessage",
|
||||
String.class, String.class, String.class);
|
||||
String.class, String.class, String.class, String.class);
|
||||
|
||||
this.param = new MethodParameter(method , 0);
|
||||
this.paramNotRequired = new MethodParameter(method , 1);
|
||||
this.paramWithSpelExpression = new MethodParameter(method , 2);
|
||||
this.paramValidated = new MethodParameter(method , 3);
|
||||
this.paramValidated.initParameterNameDiscovery(new LocalVariableTableParameterNameDiscoverer());
|
||||
}
|
||||
|
||||
|
||||
@@ -82,12 +89,41 @@ public class PayloadArgumentResolverTests {
|
||||
this.resolver.resolveArgument(this.paramWithSpelExpression, message);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveValidation() throws Exception {
|
||||
Message<?> message = MessageBuilder.withPayload("ABC".getBytes()).build();
|
||||
this.resolver.resolveArgument(this.paramValidated, message);
|
||||
}
|
||||
|
||||
@Test(expected=MethodArgumentNotValidException.class)
|
||||
public void resolveFailValidation() throws Exception {
|
||||
Message<?> message = MessageBuilder.withPayload("".getBytes()).build();
|
||||
this.resolver.resolveArgument(this.paramValidated, message);
|
||||
}
|
||||
|
||||
private Validator testValidator() {
|
||||
|
||||
return new Validator() {
|
||||
@Override
|
||||
public boolean supports(Class<?> clazz) {
|
||||
return String.class.isAssignableFrom(clazz);
|
||||
}
|
||||
@Override
|
||||
public void validate(Object target, Errors errors) {
|
||||
String value = (String) target;
|
||||
if (StringUtils.isEmpty(value.toString())) {
|
||||
errors.reject("empty value");
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private void handleMessage(
|
||||
@Payload String param,
|
||||
@Payload(required=false) String paramNotRequired,
|
||||
@Payload("foo.bar") String paramWithSpelExpression) {
|
||||
@Payload("foo.bar") String paramWithSpelExpression,
|
||||
@Validated @Payload String validParam) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2014 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.
|
||||
@@ -26,10 +26,8 @@ import org.springframework.context.support.StaticApplicationContext;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.SubscribableChannel;
|
||||
import org.springframework.messaging.handler.annotation.DestinationVariable;
|
||||
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.handler.annotation.*;
|
||||
import org.springframework.messaging.handler.annotation.support.MethodArgumentNotValidException;
|
||||
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
|
||||
import org.springframework.messaging.simp.SimpMessageSendingOperations;
|
||||
import org.springframework.messaging.simp.SimpMessageType;
|
||||
@@ -37,6 +35,10 @@ import org.springframework.messaging.simp.SimpMessagingTemplate;
|
||||
import org.springframework.messaging.simp.annotation.SubscribeMapping;
|
||||
import org.springframework.messaging.support.MessageBuilder;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.Validator;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
@@ -58,6 +60,7 @@ public class SimpAnnotationMethodMessageHandlerTests {
|
||||
SimpMessageSendingOperations brokerTemplate = new SimpMessagingTemplate(channel);
|
||||
this.messageHandler = new TestSimpAnnotationMethodMessageHandler(brokerTemplate, channel, channel);
|
||||
this.messageHandler.setApplicationContext(new StaticApplicationContext());
|
||||
this.messageHandler.setValidator(new StringNotEmptyValidator());
|
||||
this.messageHandler.afterPropertiesSet();
|
||||
|
||||
testController = new TestController();
|
||||
@@ -142,6 +145,15 @@ public class SimpAnnotationMethodMessageHandlerTests {
|
||||
assertEquals(12L, this.testController.arguments.get("id"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void validationError() {
|
||||
SimpMessageHeaderAccessor headers = SimpMessageHeaderAccessor.create();
|
||||
headers.setDestination("/pre/validation/payload");
|
||||
Message<?> message = MessageBuilder.withPayload(new byte[0]).setHeaders(headers).build();
|
||||
this.messageHandler.handleMessage(message);
|
||||
assertEquals("handleValidationException", this.testController.method);
|
||||
}
|
||||
|
||||
|
||||
private static class TestSimpAnnotationMethodMessageHandler extends SimpAnnotationMethodMessageHandler {
|
||||
|
||||
@@ -210,6 +222,17 @@ public class SimpAnnotationMethodMessageHandlerTests {
|
||||
this.method = "simpleBinding";
|
||||
this.arguments.put("id", id);
|
||||
}
|
||||
|
||||
@MessageMapping("/validation/payload")
|
||||
public void payloadValidation(@Validated @Payload String payload) {
|
||||
this.method = "payloadValidation";
|
||||
this.arguments.put("message", payload);
|
||||
}
|
||||
|
||||
@MessageExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public void handleValidationException() {
|
||||
this.method = "handleValidationException";
|
||||
}
|
||||
}
|
||||
|
||||
@Controller
|
||||
@@ -222,4 +245,18 @@ public class SimpAnnotationMethodMessageHandlerTests {
|
||||
public void handle2() { }
|
||||
}
|
||||
|
||||
private static class StringNotEmptyValidator implements Validator {
|
||||
@Override
|
||||
public boolean supports(Class<?> clazz) {
|
||||
return String.class.isAssignableFrom(clazz);
|
||||
}
|
||||
@Override
|
||||
public void validate(Object target, Errors errors) {
|
||||
String value = (String) target;
|
||||
if (StringUtils.isEmpty(value.toString())) {
|
||||
errors.reject("empty value");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2013 the original author or authors.
|
||||
* Copyright 2002-2014 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.
|
||||
@@ -19,6 +19,7 @@ package org.springframework.messaging.simp.config;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -48,6 +49,8 @@ import org.springframework.messaging.support.MessageBuilder;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.validation.Validator;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
@@ -55,6 +58,7 @@ import static org.junit.Assert.*;
|
||||
* Test fixture for {@link AbstractMessageBrokerConfiguration}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Brian Clozel
|
||||
*/
|
||||
public class MessageBrokerConfigurationTests {
|
||||
|
||||
@@ -64,6 +68,7 @@ public class MessageBrokerConfigurationTests {
|
||||
|
||||
private AnnotationConfigApplicationContext cxtCustomizedChannelConfig;
|
||||
|
||||
private AnnotationConfigApplicationContext cxtCustomizedValidator;
|
||||
|
||||
@Before
|
||||
public void setupOnce() {
|
||||
@@ -79,6 +84,10 @@ public class MessageBrokerConfigurationTests {
|
||||
this.cxtCustomizedChannelConfig = new AnnotationConfigApplicationContext();
|
||||
this.cxtCustomizedChannelConfig.register(CustomizedChannelConfig.class);
|
||||
this.cxtCustomizedChannelConfig.refresh();
|
||||
|
||||
this.cxtCustomizedValidator = new AnnotationConfigApplicationContext();
|
||||
this.cxtCustomizedValidator.register(ValidationConfig.class);
|
||||
this.cxtCustomizedValidator.refresh();
|
||||
}
|
||||
|
||||
|
||||
@@ -271,6 +280,20 @@ public class MessageBrokerConfigurationTests {
|
||||
assertEquals(MimeTypeUtils.APPLICATION_JSON, resolver.getDefaultMimeType());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultValidator() {
|
||||
SimpAnnotationMethodMessageHandler messageHandler =
|
||||
this.cxtSimpleBroker.getBean(SimpAnnotationMethodMessageHandler.class);
|
||||
assertThat(messageHandler.getValidator(),Matchers.notNullValue(Validator.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void customValidator() {
|
||||
SimpAnnotationMethodMessageHandler messageHandler =
|
||||
this.cxtCustomizedValidator.getBean(SimpAnnotationMethodMessageHandler.class);
|
||||
assertThat(messageHandler.getValidator(),Matchers.notNullValue(Validator.class));
|
||||
assertThat(messageHandler.getValidator(),Matchers.instanceOf(Validator.class));
|
||||
}
|
||||
|
||||
@Controller
|
||||
static class TestController {
|
||||
@@ -363,6 +386,24 @@ public class MessageBrokerConfigurationTests {
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class ValidationConfig extends TestMessageBrokerConfiguration {
|
||||
@Override
|
||||
public Validator getValidator() {
|
||||
return new TestValidator();
|
||||
}
|
||||
}
|
||||
|
||||
private static class TestValidator implements Validator {
|
||||
@Override
|
||||
public boolean supports(Class<?> clazz) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validate(Object target, Errors errors) {}
|
||||
}
|
||||
|
||||
|
||||
private static class TestChannel extends ExecutorSubscribableChannel {
|
||||
|
||||
|
||||
Reference in New Issue
Block a user