Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add preventBinding attribute at @ModelAttribute #866

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2012 the original author or authors.
* Copyright 2002-2015 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.
Expand All @@ -22,6 +22,7 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.core.annotation.AliasFor;
import org.springframework.ui.Model;

/**
Expand Down Expand Up @@ -49,21 +50,40 @@
* access to a {@link Model} argument.
*
* @author Juergen Hoeller
* @author Kazuki Shimizu
* @since 2.5
*/
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ModelAttribute {

/**
* Alias for {@link #name}.
*/
@AliasFor("name")
String value() default "";

/**
* The name of the model attribute to bind to.
* <p>The default model attribute name is inferred from the declared
* attribute type (i.e. the method parameter type or method return type),
* based on the non-qualified class name:
* e.g. "orderAddress" for class "mypackage.OrderAddress",
* or "orderAddressList" for "List&lt;mypackage.OrderAddress&gt;".
* @return 4.2.1
*/
String value() default "";
@AliasFor("value")
String name() default "";

/**
* Indicate whether bind a request parameter.
*
* <p>If {@code true} is specified, request parameters does not bind to model. (default is {@code false})
*
* <p> Note: This attribute is available at the handler method parameter.
* @since 4.2.1
*/
boolean preventBinding() default false;

}
Expand Up @@ -45,13 +45,17 @@
* created with a default constructor if it is available. Once created, the
* attributed is populated with request data via data binding and also
* validation may be applied if the argument is annotated with
* {@code @javax.validation.Valid}.
* {@code @javax.validation.Valid} or {@link Validated}.
* From 4.2.1, if {@link ModelAttribute#preventBinding} is {@code true},
* this resolver obtain an object from the model.
* In other words, binding for request values does not perform.
*
* <p>When this handler is created with {@code annotationNotRequired=true},
* any non-simple type argument and return value is regarded as a model
* attribute with or without the presence of an {@code @ModelAttribute}.
*
* @author Rossen Stoyanchev
* @author Kazuki Shimizu
* @since 3.1
*/
public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
Expand Down Expand Up @@ -93,6 +97,9 @@ else if (this.annotationNotRequired) {
* its default if it is available. The model attribute is then populated
* with request values via data binding and optionally validated
* if {@code @java.validation.Valid} is present on the argument.
* From 4.2.1, if {@link ModelAttribute#preventBinding} is {@code true},
* this method return an object which obtain from the model.
* In other words, binding for request values does not perform.
* @throws BindException if data binding and validation result in an error
* and the next method parameter is not of type {@link Errors}.
* @throws Exception if WebDataBinder initialization fails.
Expand All @@ -102,6 +109,13 @@ public final Object resolveArgument(MethodParameter parameter, ModelAndViewConta
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

String name = ModelFactory.getNameForParameter(parameter);

ModelAttribute modelAttribute = parameter.getParameterAnnotation(ModelAttribute.class);
boolean preventBinding = (modelAttribute != null && modelAttribute.preventBinding());
if (preventBinding) {
return mavContainer.getModel().get(name);
}

Object attribute = (mavContainer.containsAttribute(name) ?
mavContainer.getModel().get(name) : createAttribute(name, parameter, binderFactory, webRequest));

Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 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.
Expand Down Expand Up @@ -55,6 +55,7 @@
* {@link BindingResult} attributes are added where missing.
*
* @author Rossen Stoyanchev
* @author Kazuki Shimizu
* @since 3.1
*/
public final class ModelFactory {
Expand Down Expand Up @@ -128,7 +129,7 @@ private void invokeModelAttributeMethods(NativeWebRequest request, ModelAndViewC

while (!this.modelMethods.isEmpty()) {
InvocableHandlerMethod attrMethod = getNextModelMethod(mavContainer).getHandlerMethod();
String modelName = attrMethod.getMethodAnnotation(ModelAttribute.class).value();
String modelName = attrMethod.getMethodAnnotation(ModelAttribute.class).name();
if (mavContainer.containsAttribute(modelName)) {
continue;
}
Expand Down Expand Up @@ -189,7 +190,7 @@ private List<String> findSessionAttributeArguments(HandlerMethod handlerMethod)
*/
public static String getNameForParameter(MethodParameter parameter) {
ModelAttribute annot = parameter.getParameterAnnotation(ModelAttribute.class);
String attrName = (annot != null) ? annot.value() : null;
String attrName = (annot != null) ? annot.name() : null;
return StringUtils.hasText(attrName) ? attrName : Conventions.getVariableNameForParameter(parameter);
}

Expand All @@ -206,8 +207,8 @@ public static String getNameForParameter(MethodParameter parameter) {
*/
public static String getNameForReturnValue(Object returnValue, MethodParameter returnType) {
ModelAttribute annotation = returnType.getMethodAnnotation(ModelAttribute.class);
if (annotation != null && StringUtils.hasText(annotation.value())) {
return annotation.value();
if (annotation != null && StringUtils.hasText(annotation.name())) {
return annotation.name();
}
else {
Method method = returnType.getMethod();
Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2013 the original author or authors.
* Copyright 2002-2015 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.
Expand All @@ -24,6 +24,7 @@
import org.junit.Test;

import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.SynthesizingMethodParameter;
import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.tests.sample.beans.TestBean;
import org.springframework.validation.BindException;
Expand All @@ -42,12 +43,14 @@
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;
import static org.junit.Assert.*;
import static org.hamcrest.core.Is.*;
import static org.mockito.BDDMockito.*;

/**
* Test fixture with {@link ModelAttributeMethodProcessor}.
*
* @author Rossen Stoyanchev
* @author Kazuki Shimizu
*/
public class ModelAttributeMethodProcessorTests {

Expand All @@ -63,33 +66,39 @@ public class ModelAttributeMethodProcessorTests {

private MethodParameter paramNonSimpleType;

private MethodParameter paramPreventBindingAttr;

private MethodParameter returnParamNamedModelAttr;

private MethodParameter returnParamNonSimpleType;

private ModelAndViewContainer mavContainer;

private MockHttpServletRequest mockHttpServletRequest;

private NativeWebRequest webRequest;

@Before
public void setUp() throws Exception {
processor = new ModelAttributeMethodProcessor(false);

Method method = ModelAttributeHandler.class.getDeclaredMethod("modelAttribute",
TestBean.class, Errors.class, int.class, TestBean.class, TestBean.class);
TestBean.class, Errors.class, int.class, TestBean.class, TestBean.class, TestBean.class);

paramNamedValidModelAttr = new MethodParameter(method, 0);
paramErrors = new MethodParameter(method, 1);
paramInt = new MethodParameter(method, 2);
paramModelAttr = new MethodParameter(method, 3);
paramNonSimpleType = new MethodParameter(method, 4);
paramNamedValidModelAttr = new SynthesizingMethodParameter(method, 0);
paramErrors = new SynthesizingMethodParameter(method, 1);
paramInt = new SynthesizingMethodParameter(method, 2);
paramModelAttr = new SynthesizingMethodParameter(method, 3);
paramNonSimpleType = new SynthesizingMethodParameter(method, 4);
paramPreventBindingAttr = new SynthesizingMethodParameter(method, 5);

returnParamNamedModelAttr = new MethodParameter(getClass().getDeclaredMethod("annotatedReturnValue"), -1);
returnParamNonSimpleType = new MethodParameter(getClass().getDeclaredMethod("notAnnotatedReturnValue"), -1);
returnParamNamedModelAttr = new SynthesizingMethodParameter(getClass().getDeclaredMethod("annotatedReturnValue"), -1);
returnParamNonSimpleType = new SynthesizingMethodParameter(getClass().getDeclaredMethod("notAnnotatedReturnValue"), -1);

mavContainer = new ModelAndViewContainer();

webRequest = new ServletWebRequest(new MockHttpServletRequest());
mockHttpServletRequest = new MockHttpServletRequest();
webRequest = new ServletWebRequest(mockHttpServletRequest);
}

@Test
Expand Down Expand Up @@ -227,6 +236,44 @@ public void resolveArgumentOrdering() throws Exception {
dataBinder.getBindingResult(), mavContainer.getModel().values().toArray()[2]);
}

// SPR-13402
@Test
public void resolveArgumentWithBinding() throws Exception {

TestBean target = new TestBean();
target.setAge(30);
mavContainer.addAttribute("testBean", target);

WebDataBinderFactory factory = new InitBinderDataBinderFactory(null, null);
mockHttpServletRequest.addParameter("age", "40");

Object resolvedObject = processor.resolveArgument(paramModelAttr, mavContainer, webRequest, factory);

TestBean resolvedTestBean = TestBean.class.cast(resolvedObject);

assertThat(resolvedTestBean.getAge(), is(40));

}

// SPR-13402
@Test
public void resolveArgumentWithPreventBinding() throws Exception {

TestBean target = new TestBean();
target.setAge(30);
mavContainer.addAttribute("preventBindingAttr", target);

WebDataBinderFactory factory = new InitBinderDataBinderFactory(null, null);
mockHttpServletRequest.addParameter("age", "40");

Object resolvedObject = processor.resolveArgument(paramPreventBindingAttr, mavContainer, webRequest, factory);

TestBean resolvedTestBean = TestBean.class.cast(resolvedObject);

assertThat(resolvedTestBean.getAge(), is(30));

}

@Test
public void handleAnnotatedReturnValue() throws Exception {
processor.handleReturnValue("expected", returnParamNamedModelAttr, mavContainer, webRequest);
Expand Down Expand Up @@ -289,7 +336,8 @@ public void modelAttribute(@ModelAttribute("attrName") @Valid TestBean annotated
Errors errors,
int intArg,
@ModelAttribute TestBean defaultNameAttr,
TestBean notAnnotatedAttr) {
TestBean notAnnotatedAttr,
@ModelAttribute(name = "preventBindingAttr", preventBinding = true) TestBean preventBindingAttr) {
}
}

Expand Down
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 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.
Expand All @@ -25,6 +25,7 @@
import org.junit.Test;

import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.SynthesizingMethodParameter;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.tests.sample.beans.TestBean;
Expand All @@ -43,6 +44,7 @@
* Also see org.springframework.web.method.annotation.support.ModelAttributeMethodProcessorTests
*
* @author Rossen Stoyanchev
* @author Kazuki Shimizu
*/
public class ServletModelAttributeMethodProcessorTests {

Expand Down Expand Up @@ -70,9 +72,9 @@ public void setUp() throws Exception {
Method method = getClass().getDeclaredMethod("modelAttribute",
TestBean.class, TestBeanWithoutStringConstructor.class, Optional.class);

this.testBeanModelAttr = new MethodParameter(method, 0);
this.testBeanWithoutStringConstructorModelAttr = new MethodParameter(method, 1);
this.testBeanWithOptionalModelAttr = new MethodParameter(method, 2);
this.testBeanModelAttr = new SynthesizingMethodParameter(method, 0);
this.testBeanWithoutStringConstructorModelAttr = new SynthesizingMethodParameter(method, 1);
this.testBeanWithOptionalModelAttr = new SynthesizingMethodParameter(method, 2);

ConfigurableWebBindingInitializer initializer = new ConfigurableWebBindingInitializer();
initializer.setConversionService(new DefaultConversionService());
Expand Down