From 2c8f670d5fd129de470e1ddf415ad76305087996 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Thu, 2 Jan 2014 15:33:07 +0100 Subject: [PATCH] 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 --- .../handler/annotation/MessageMapping.java | 5 +- .../MethodArgumentNotValidException.java | 56 +++++++++++++ .../support/PayloadArgumentResolver.java | 48 ++++++++++- .../SimpAnnotationMethodMessageHandler.java | 31 +++++-- .../AbstractMessageBrokerConfiguration.java | 82 ++++++++++++++++++- .../support/PayloadArgumentResolverTests.java | 48 +++++++++-- ...mpAnnotationMethodMessageHandlerTests.java | 47 +++++++++-- .../MessageBrokerConfigurationTests.java | 43 +++++++++- src/asciidoc/index.adoc | 4 +- 9 files changed, 335 insertions(+), 29 deletions(-) create mode 100644 spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/MethodArgumentNotValidException.java diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/MessageMapping.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/MessageMapping.java index 8e039045ea..0c040f503e 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/MessageMapping.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/MessageMapping.java @@ -41,7 +41,10 @@ import org.springframework.messaging.Message; * a message and optionally convert it using a * {@link org.springframework.messaging.converter.MessageConverter}. * The presence of the annotation is not required since it is assumed by default - * for method arguments that are not annotated. + * for method arguments that are not annotated. Payload method arguments annotated + * with Validation annotations (like + * {@link org.springframework.validation.annotation.Validated}) will be subject to + * JSR-303 validation. *
  • {@link Header}-annotated method arguments to extract a specific * header value along with type conversion with a * {@link org.springframework.core.convert.converter.Converter} if necessary.
  • diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/MethodArgumentNotValidException.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/MethodArgumentNotValidException.java new file mode 100644 index 0000000000..06d0f838a7 --- /dev/null +++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/MethodArgumentNotValidException.java @@ -0,0 +1,56 @@ +/* + * 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. + * 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 org.springframework.core.MethodParameter; +import org.springframework.messaging.Message; +import org.springframework.messaging.MessagingException; +import org.springframework.validation.BindingResult; +import org.springframework.validation.ObjectError; + +/** + * Exception to be thrown when validation on an method parameter annotated with {@code @Valid} fails. + * @author Brian Clozel + * @since 4.0.1 + */ +@SuppressWarnings("serial") +public class MethodArgumentNotValidException extends MessagingException { + + private final MethodParameter parameter; + + private final BindingResult bindingResult; + + + public MethodArgumentNotValidException(Message message, MethodParameter parameter, BindingResult bindingResult) { + super(message); + this.parameter = parameter; + this.bindingResult = bindingResult; + } + + @Override + public String getMessage() { + StringBuilder sb = new StringBuilder("Validation failed for parameter at index ") + .append(this.parameter.getParameterIndex()).append(" in method: ") + .append(this.parameter.getMethod().toGenericString()) + .append(", with ").append(this.bindingResult.getErrorCount()).append(" error(s): "); + for (ObjectError error : this.bindingResult.getAllErrors()) { + sb.append("[").append(error).append("] "); + } + return sb.toString(); + } + +} diff --git a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/PayloadArgumentResolver.java b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/PayloadArgumentResolver.java index ebba7db716..ba1fa9c8eb 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/PayloadArgumentResolver.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/handler/annotation/support/PayloadArgumentResolver.java @@ -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. @@ -17,32 +17,45 @@ package org.springframework.messaging.handler.annotation.support; import org.springframework.core.MethodParameter; +import org.springframework.core.annotation.AnnotationUtils; import org.springframework.messaging.Message; import org.springframework.messaging.handler.annotation.Payload; import org.springframework.messaging.handler.invocation.HandlerMethodArgumentResolver; import org.springframework.messaging.converter.MessageConverter; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; +import org.springframework.util.ObjectUtils; import org.springframework.util.StringUtils; +import org.springframework.validation.BeanPropertyBindingResult; +import org.springframework.validation.SmartValidator; +import org.springframework.validation.Validator; + +import java.lang.annotation.Annotation; /** * A resolver to extract and convert the payload of a message using a - * {@link MessageConverter}. + * {@link MessageConverter}. It also validates the payload using a + * {@link Validator} if the argument is annotated with a Validation annotation. * *

    This {@link HandlerMethodArgumentResolver} should be ordered last as it supports all * types and does not require the {@link Payload} annotation. * * @author Rossen Stoyanchev + * @author Brian Clozel * @since 4.0 */ public class PayloadArgumentResolver implements HandlerMethodArgumentResolver { private final MessageConverter converter; + private final Validator validator; + - public PayloadArgumentResolver(MessageConverter messageConverter) { + public PayloadArgumentResolver(MessageConverter messageConverter, Validator validator) { Assert.notNull(messageConverter, "converter must not be null"); + Assert.notNull(validator, "validator must not be null"); this.converter = messageConverter; + this.validator = validator; } @Override @@ -72,7 +85,10 @@ public class PayloadArgumentResolver implements HandlerMethodArgumentResolver { throw new IllegalStateException("@Payload SpEL expressions not supported by this resolver."); } - return this.converter.fromMessage(message, targetClass); + Object target = this.converter.fromMessage(message, targetClass); + validate(message, parameter, target); + + return target; } protected boolean isEmptyPayload(Message message) { @@ -88,4 +104,28 @@ public class PayloadArgumentResolver implements HandlerMethodArgumentResolver { } } + protected void validate(Message message, MethodParameter parameter, Object target) { + Annotation[] annotations = parameter.getParameterAnnotations(); + for (Annotation annot : annotations) { + if (annot.annotationType().getSimpleName().startsWith("Valid")) { + BeanPropertyBindingResult bindingResult = new BeanPropertyBindingResult(target, parameter.getParameterName()); + Object hints = AnnotationUtils.getValue(annot); + Object[] validationHints = hints instanceof Object[] ? (Object[]) hints : new Object[] {hints}; + + if (!ObjectUtils.isEmpty(validationHints) && this.validator instanceof SmartValidator) { + ((SmartValidator) this.validator).validate(target, bindingResult, validationHints); + } + else if (this.validator != null) { + this.validator.validate(target, bindingResult); + } + + if (bindingResult.hasErrors()) { + throw new MethodArgumentNotValidException(message, parameter, bindingResult); + } + + break; + } + } + } + } diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SimpAnnotationMethodMessageHandler.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SimpAnnotationMethodMessageHandler.java index 1a3ed620a6..40fdb884a2 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SimpAnnotationMethodMessageHandler.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/annotation/support/SimpAnnotationMethodMessageHandler.java @@ -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. @@ -17,13 +17,7 @@ package org.springframework.messaging.simp.annotation.support; import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Comparator; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import org.springframework.beans.factory.config.ConfigurableBeanFactory; import org.springframework.context.ConfigurableApplicationContext; @@ -60,6 +54,7 @@ import org.springframework.util.AntPathMatcher; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.PathMatcher; +import org.springframework.validation.Validator; /** * A handler for messages delegating to @@ -87,6 +82,8 @@ public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHan private PathMatcher pathMatcher = new AntPathMatcher(); + private Validator validator; + private final Object lifecycleMonitor = new Object(); private volatile boolean running = false; @@ -171,6 +168,22 @@ public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHan return this.pathMatcher; } + /** + * The configured Validator instance + */ + public Validator getValidator() { + return validator; + } + + /** + * Set the Validator instance used for validating @Payload arguments + * @see org.springframework.validation.annotation.Validated + * @see PayloadArgumentResolver + */ + public void setValidator(Validator validator) { + this.validator = validator; + } + @Override public boolean isAutoStartup() { return true; @@ -230,7 +243,7 @@ public class SimpAnnotationMethodMessageHandler extends AbstractMethodMessageHan resolvers.add(new MessageMethodArgumentResolver()); resolvers.addAll(getCustomArgumentResolvers()); - resolvers.add(new PayloadArgumentResolver(this.messageConverter)); + resolvers.add(new PayloadArgumentResolver(this.messageConverter, this.validator)); return resolvers; } diff --git a/spring-messaging/src/main/java/org/springframework/messaging/simp/config/AbstractMessageBrokerConfiguration.java b/spring-messaging/src/main/java/org/springframework/messaging/simp/config/AbstractMessageBrokerConfiguration.java index f76b0dd3c3..aa94adec83 100644 --- a/spring-messaging/src/main/java/org/springframework/messaging/simp/config/AbstractMessageBrokerConfiguration.java +++ b/spring-messaging/src/main/java/org/springframework/messaging/simp/config/AbstractMessageBrokerConfiguration.java @@ -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,11 @@ package org.springframework.messaging.simp.config; import java.util.ArrayList; import java.util.List; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanInitializationException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; import org.springframework.context.annotation.Bean; import org.springframework.messaging.Message; import org.springframework.messaging.converter.ByteArrayMessageConverter; @@ -41,6 +46,8 @@ import org.springframework.messaging.support.ExecutorSubscribableChannel; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.util.ClassUtils; import org.springframework.util.MimeTypeUtils; +import org.springframework.validation.Errors; +import org.springframework.validation.Validator; /** * Provides essential configuration for handling messages with simple messaging @@ -62,13 +69,15 @@ import org.springframework.util.MimeTypeUtils; * to and from the client inbound/outbound channels (e.g. STOMP over WebSocket). * * @author Rossen Stoyanchev + * @author Brian Clozel * @since 4.0 */ -public abstract class AbstractMessageBrokerConfiguration { +public abstract class AbstractMessageBrokerConfiguration implements ApplicationContextAware { private static final boolean jackson2Present= ClassUtils.isPresent( "com.fasterxml.jackson.databind.ObjectMapper", AbstractMessageBrokerConfiguration.class.getClassLoader()); + private static final String MVC_VALIDATOR_NAME = "mvcValidator"; private ChannelRegistration clientInboundChannelRegistration; @@ -76,6 +85,8 @@ public abstract class AbstractMessageBrokerConfiguration { private MessageBrokerRegistry brokerRegistry; + private ApplicationContext applicationContext; + /** * Protected constructor. @@ -204,6 +215,7 @@ public abstract class AbstractMessageBrokerConfiguration { handler.setDestinationPrefixes(getBrokerRegistry().getApplicationDestinationPrefixes()); handler.setMessageConverter(brokerMessageConverter()); + handler.setValidator(simpValidator()); return handler; } @@ -268,6 +280,61 @@ public abstract class AbstractMessageBrokerConfiguration { return new DefaultUserSessionRegistry(); } + /** + * Override this method to provide a custom {@link Validator}. + * @since 4.0.1 + */ + public Validator getValidator() { + return null; + } + + @Override + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + + public ApplicationContext getApplicationContext() { + return applicationContext; + } + + /** + * Return a {@link org.springframework.validation.Validator}s instance for validating + * {@code @Payload} method arguments. + * In order, this method tries to get a Validator instance: + *

    + */ + protected Validator simpValidator() { + Validator validator = getValidator(); + if (validator == null) { + if(this.applicationContext.containsBean(MVC_VALIDATOR_NAME)) { + validator = this.applicationContext.getBean(MVC_VALIDATOR_NAME, Validator.class); + } + else if (ClassUtils.isPresent("javax.validation.Validator", getClass().getClassLoader())) { + Class clazz; + try { + String className = "org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"; + clazz = ClassUtils.forName(className, AbstractMessageBrokerConfiguration.class.getClassLoader()); + } + catch (ClassNotFoundException e) { + throw new BeanInitializationException("Could not find default validator", e); + } + catch (LinkageError e) { + throw new BeanInitializationException("Could not find default validator", e); + } + validator = (Validator) BeanUtils.instantiate(clazz); + } + else { + validator = noopValidator; + } + } + return validator; + } private static final AbstractBrokerMessageHandler noopBroker = new AbstractBrokerMessageHandler(null) { @@ -285,4 +352,15 @@ public abstract class AbstractMessageBrokerConfiguration { }; + private static final Validator noopValidator = new Validator() { + @Override + public boolean supports(Class clazz) { + return false; + } + @Override + public void validate(Object target, Errors errors) { + } + }; + + } diff --git a/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/PayloadArgumentResolverTests.java b/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/PayloadArgumentResolverTests.java index f318437da0..acf88bcf8c 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/PayloadArgumentResolverTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/handler/annotation/support/PayloadArgumentResolverTests.java @@ -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) { } } diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/annotation/support/SimpAnnotationMethodMessageHandlerTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/annotation/support/SimpAnnotationMethodMessageHandlerTests.java index 043b3d0b85..4fe4d88428 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/simp/annotation/support/SimpAnnotationMethodMessageHandlerTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/annotation/support/SimpAnnotationMethodMessageHandlerTests.java @@ -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"); + } + } + } + } diff --git a/spring-messaging/src/test/java/org/springframework/messaging/simp/config/MessageBrokerConfigurationTests.java b/spring-messaging/src/test/java/org/springframework/messaging/simp/config/MessageBrokerConfigurationTests.java index 3495c28ac5..7888cc43f1 100644 --- a/spring-messaging/src/test/java/org/springframework/messaging/simp/config/MessageBrokerConfigurationTests.java +++ b/spring-messaging/src/test/java/org/springframework/messaging/simp/config/MessageBrokerConfigurationTests.java @@ -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 { diff --git a/src/asciidoc/index.adoc b/src/asciidoc/index.adoc index 0f32d285c4..e2ba6a10f5 100644 --- a/src/asciidoc/index.adoc +++ b/src/asciidoc/index.adoc @@ -1,5 +1,5 @@ = Spring Framework Reference Documentation -Rod Johnson; Juergen Hoeller; Keith Donald; Colin Sampaleanu; Rob Harrop; Thomas Risberg; Alef Arendsen; Darren Davison; Dmitriy Kopylenko; Mark Pollack; Thierry Templier; Erwin Vervaet; Portia Tung; Ben Hale; Adrian Colyer; John Lewis; Costin Leau; Mark Fisher; Sam Brannen; Ramnivas Laddad; Arjen Poutsma; Chris Beams; Tareq Abedrabbo; Andy Clement; Dave Syer; Oliver Gierke; Rossen Stoyanchev; Phillip Webb; Rob Winch +Rod Johnson; Juergen Hoeller; Keith Donald; Colin Sampaleanu; Rob Harrop; Thomas Risberg; Alef Arendsen; Darren Davison; Dmitriy Kopylenko; Mark Pollack; Thierry Templier; Erwin Vervaet; Portia Tung; Ben Hale; Adrian Colyer; John Lewis; Costin Leau; Mark Fisher; Sam Brannen; Ramnivas Laddad; Arjen Poutsma; Chris Beams; Tareq Abedrabbo; Andy Clement; Dave Syer; Oliver Gierke; Rossen Stoyanchev; Phillip Webb; Rob Winch; Brian Clozel :javadoc-baseurl: http://docs.spring.io/spring/docs/current/javadoc-api @@ -37402,6 +37402,8 @@ The following method arguments are supported for `@MessageMapping` methods: * `@Payload`-annotated argument for access to the payload of a message, converted with a `org.springframework.messaging.converter.MessageConverter`. The presence of the annotation is not required since it is assumed by default. +Payload method arguments annotated with Validation annotations (like `@Validated`) will +be subject to JSR-303 validation. * `@Header`-annotated arguments for access to a specific header value along with type conversion using an `org.springframework.core.convert.converter.Converter` if necessary.