From 50117dce40fa3bcf87acfca6ef26b7607a311518 Mon Sep 17 00:00:00 2001 From: Rossen Stoyanchev Date: Tue, 26 Apr 2011 11:54:54 +0000 Subject: [PATCH] SPR-6909 Include URI template vars in data binding --- .../RequestMappingHandlerMethodAdapter.java | 11 ++- ...vletInitBinderMethodDataBinderFactory.java | 32 +++++++- ...nitBinderMethodDataBinderFactoryTests.java | 77 +++++++++++++++++++ .../InitBinderMethodDataBinderFactory.java | 7 +- .../ModelAttributeMethodProcessor.java | 14 +++- .../method/support/ModelAndViewContainer.java | 4 +- .../ModelAttributeMethodProcessorTests.java | 8 +- 7 files changed, 136 insertions(+), 17 deletions(-) create mode 100644 org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletInitBinderMethodDataBinderFactoryTests.java diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMethodAdapter.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMethodAdapter.java index e5679f1b7c..11ec8651da 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMethodAdapter.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMethodAdapter.java @@ -42,6 +42,7 @@ import org.springframework.http.converter.StringHttpMessageConverter; import org.springframework.http.converter.xml.SourceHttpMessageConverter; import org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter; import org.springframework.util.ReflectionUtils.MethodFilter; +import org.springframework.validation.DataBinder; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.InitBinder; import org.springframework.web.bind.annotation.ModelAttribute; @@ -257,13 +258,19 @@ public class RequestMappingHandlerMethodAdapter extends AbstractHandlerMethodAda } /** - * Specify a WebBindingInitializer which will apply pre-configured - * configuration to every DataBinder that this controller uses. + * Set a WebBindingInitializer to apply configure every DataBinder instance this controller uses. */ public void setWebBindingInitializer(WebBindingInitializer webBindingInitializer) { this.webBindingInitializer = webBindingInitializer; } + /** + * Return the WebBindingInitializer which applies pre-configured configuration to {@link DataBinder} instances. + */ + public WebBindingInitializer getWebBindingInitializer() { + return webBindingInitializer; + } + /** * Specify the strategy to store session attributes with. *

Default is {@link org.springframework.web.bind.support.DefaultSessionAttributeStore}, diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInitBinderMethodDataBinderFactory.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInitBinderMethodDataBinderFactory.java index 8f96204952..4c19f4d218 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInitBinderMethodDataBinderFactory.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletInitBinderMethodDataBinderFactory.java @@ -17,12 +17,17 @@ package org.springframework.web.servlet.mvc.method.annotation; import java.util.List; +import java.util.Map; +import org.springframework.beans.MutablePropertyValues; import org.springframework.web.bind.ServletRequestDataBinder; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.support.WebBindingInitializer; +import org.springframework.web.context.request.RequestAttributes; +import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.method.annotation.InitBinderMethodDataBinderFactory; import org.springframework.web.method.support.InvocableHandlerMethod; +import org.springframework.web.servlet.HandlerMapping; /** * An {@link InitBinderMethodDataBinderFactory} that creates a {@link ServletRequestDataBinder}. @@ -47,7 +52,30 @@ public class ServletInitBinderMethodDataBinderFactory extends InitBinderMethodDa */ @Override protected WebDataBinder createBinderInstance(Object target, String objectName) { - return new ServletRequestDataBinder(target, objectName); + return new ServletRequestPathVarDataBinder(target, objectName); } -} + /** + * Adds URI template variables to the map of request values used to do data binding. + */ + private static class ServletRequestPathVarDataBinder extends ServletRequestDataBinder { + + public ServletRequestPathVarDataBinder(Object target, String objectName) { + super(target, objectName); + } + + @SuppressWarnings("unchecked") + @Override + protected void doBind(MutablePropertyValues mpvs) { + RequestAttributes requestAttrs = RequestContextHolder.getRequestAttributes(); + if (requestAttrs != null) { + String key = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE; + int scope = RequestAttributes.SCOPE_REQUEST; + Map uriTemplateVars = (Map) requestAttrs.getAttribute(key, scope); + mpvs.addPropertyValues(uriTemplateVars); + } + super.doBind(mpvs); + } + } + +} \ No newline at end of file diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletInitBinderMethodDataBinderFactoryTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletInitBinderMethodDataBinderFactoryTests.java new file mode 100644 index 0000000000..37bd0b5022 --- /dev/null +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/ServletInitBinderMethodDataBinderFactoryTests.java @@ -0,0 +1,77 @@ +/* + * Copyright 2002-2011 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.web.servlet.mvc.method.annotation; + +import static org.junit.Assert.assertEquals; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.TestBean; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.web.bind.ServletRequestDataBinder; +import org.springframework.web.bind.WebDataBinder; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletWebRequest; +import org.springframework.web.servlet.HandlerMapping; + +/** + * Test fixture with {@link ServletInitBinderMethodDataBinderFactory}. + * + * @author Rossen Stoyanchev + */ +public class ServletInitBinderMethodDataBinderFactoryTests { + + private ServletInitBinderMethodDataBinderFactory binderFactory; + + private MockHttpServletRequest request; + + private NativeWebRequest webRequest; + + @Before + public void setup() { + binderFactory = new ServletInitBinderMethodDataBinderFactory(null, null); + request = new MockHttpServletRequest(); + webRequest = new ServletWebRequest(request); + RequestContextHolder.setRequestAttributes(webRequest); + } + + @After + public void teardown() { + RequestContextHolder.resetRequestAttributes(); + } + + @Test + public void createBinder() throws Exception { + Map uriTemplateVars = new HashMap(); + uriTemplateVars.put("name", "nameValue"); + uriTemplateVars.put("age", "25"); + request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVars); + + TestBean target = new TestBean(); + WebDataBinder binder = binderFactory.createBinder(webRequest, target, ""); + ((ServletRequestDataBinder) binder).bind(request); + + assertEquals("nameValue", target.getName()); + assertEquals(25, target.getAge()); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/InitBinderMethodDataBinderFactory.java b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/InitBinderMethodDataBinderFactory.java index bcc7ef3b88..548ca7d0e4 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/InitBinderMethodDataBinderFactory.java +++ b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/InitBinderMethodDataBinderFactory.java @@ -16,6 +16,7 @@ package org.springframework.web.method.annotation; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; @@ -41,13 +42,13 @@ public class InitBinderMethodDataBinderFactory extends DefaultDataBinderFactory /** * Create an {@code InitBinderMethodDataBinderFactory} instance with the given {@link InitBinder} methods. - * @param initBinderMethods {@link InitBinder} methods to use to invoke to initialize new data binder instances + * @param binderMethods {@link InitBinder} methods to use to invoke to initialize new data binder instances * @param bindingInitializer a {@link WebBindingInitializer} to initialize new data binder instances with */ - public InitBinderMethodDataBinderFactory(List initBinderMethods, + public InitBinderMethodDataBinderFactory(List binderMethods, WebBindingInitializer bindingInitializer) { super(bindingInitializer); - this.initBinderMethods = initBinderMethods; + this.initBinderMethods = (binderMethods != null) ? binderMethods : new ArrayList(); } /** diff --git a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ModelAttributeMethodProcessor.java b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ModelAttributeMethodProcessor.java index 46e756a0fc..3ac47dc2c8 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ModelAttributeMethodProcessor.java +++ b/org.springframework.web/src/main/java/org/springframework/web/method/annotation/support/ModelAttributeMethodProcessor.java @@ -94,11 +94,11 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol if (binder.getTarget() != null) { doBind(binder, webRequest); - if (shouldValidate(parameter)) { + if (shouldValidate(binder, parameter)) { binder.validate(); } - if (failOnError(parameter) && binder.getBindingResult().hasErrors()) { + if (failOnError(binder, parameter) && binder.getBindingResult().hasErrors()) { throw new BindException(binder.getBindingResult()); } } @@ -133,9 +133,12 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol } /** + * Whether to validate the target object of the given {@link WebDataBinder} instance. + * @param binder the data binder containing the validation candidate + * @param parameter the method argument for which data binding is performed * @return true if {@link DataBinder#validate()} should be invoked, false otherwise. */ - protected boolean shouldValidate(MethodParameter parameter) { + protected boolean shouldValidate(WebDataBinder binder, MethodParameter parameter) { Annotation[] annotations = parameter.getParameterAnnotations(); for (Annotation annot : annotations) { if ("Valid".equals(annot.annotationType().getSimpleName())) { @@ -146,9 +149,12 @@ public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResol } /** + * Whether to raise a {@link BindException} in case of data binding or validation errors. + * @param binder the binder on which validation is to be invoked + * @param parameter the method argument for which data binding is performed * @return true if the binding or validation errors should result in a {@link BindException}, false otherwise. */ - protected boolean failOnError(MethodParameter parameter) { + protected boolean failOnError(WebDataBinder binder, MethodParameter parameter) { int i = parameter.getParameterIndex(); Class[] paramTypes = parameter.getMethod().getParameterTypes(); boolean hasBindingResult = (paramTypes.length > (i + 1) && Errors.class.isAssignableFrom(paramTypes[i + 1])); diff --git a/org.springframework.web/src/main/java/org/springframework/web/method/support/ModelAndViewContainer.java b/org.springframework.web/src/main/java/org/springframework/web/method/support/ModelAndViewContainer.java index 315e5d4d49..77673fb164 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/method/support/ModelAndViewContainer.java +++ b/org.springframework.web/src/main/java/org/springframework/web/method/support/ModelAndViewContainer.java @@ -24,8 +24,8 @@ import org.springframework.util.StringUtils; import org.springframework.validation.support.BindingAwareModelMap; /** - * Provides access to the model and a place to record model and view related decisions to all - * {@link HandlerMethodArgumentResolver}s and {@link HandlerMethodReturnValueHandler}s . + * Provides access to the model and a place to record model and view related decisions made by + * {@link HandlerMethodArgumentResolver}s or a {@link HandlerMethodReturnValueHandler}. * *

In addition to storing model attributes and a view, the {@link ModelAndViewContainer} also provides * a {@link #setResolveView(boolean)} flag, which can be used to request or bypass a view resolution phase. diff --git a/org.springframework.web/src/test/java/org/springframework/web/method/annotation/support/ModelAttributeMethodProcessorTests.java b/org.springframework.web/src/test/java/org/springframework/web/method/annotation/support/ModelAttributeMethodProcessorTests.java index 02eb152ae7..7d7b34b71c 100644 --- a/org.springframework.web/src/test/java/org/springframework/web/method/annotation/support/ModelAttributeMethodProcessorTests.java +++ b/org.springframework.web/src/test/java/org/springframework/web/method/annotation/support/ModelAttributeMethodProcessorTests.java @@ -132,14 +132,14 @@ public class ModelAttributeMethodProcessorTests { @Test public void shouldValidate() throws Exception { - assertTrue(processor.shouldValidate(paramNamedValidModelAttr)); - assertFalse(processor.shouldValidate(paramNonSimpleType)); + assertTrue(processor.shouldValidate(null, paramNamedValidModelAttr)); + assertFalse(processor.shouldValidate(null, paramNonSimpleType)); } @Test public void failOnError() throws Exception { - assertFalse("Shouldn't failOnError with BindingResult", processor.failOnError(paramNamedValidModelAttr)); - assertTrue("Should failOnError without BindingResult", processor.failOnError(paramNonSimpleType)); + assertFalse("Shouldn't failOnError with BindingResult", processor.failOnError(null, paramNamedValidModelAttr)); + assertTrue("Should failOnError without BindingResult", processor.failOnError(null, paramNonSimpleType)); } @Test