SPR-5628 Add HttpPutFormContentFilter in order to make form encoded data available via ServletRequest.getParameter*()

This commit is contained in:
Rossen Stoyanchev 2011-07-25 18:58:34 +00:00
parent c31b17fef2
commit 2cf2fc195e
4 changed files with 544 additions and 130 deletions

View File

@ -0,0 +1,151 @@
/*
* 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.filter;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.FormHttpMessageConverter;
import org.springframework.http.converter.xml.XmlAwareFormHttpMessageConverter;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
/**
* {@link javax.servlet.Filter} that makes form encoded data available through the
* {@code ServletRequest.getParameter*()} family of methods during HTTP PUT requests.
*
* <p>The Servlet spec requires form data to be available for HTTP POST but not for
* HTTP PUT requests. This filter intercepts HTTP PUT requests
* where {@code 'Content-Type:application/x-www-form-urlencoded'}, reads the form
* data from the body of the request, and wraps the ServletRequest in order to make
* the form data available as request parameters.
*
* @author Rossen Stoyanchev
* @since 3.1
*/
public class HttpPutFormContentFilter extends OncePerRequestFilter {
private static final String FORM_CONTENT_TYPE = "application/x-www-form-urlencoded";
private final FormHttpMessageConverter formConverter = new XmlAwareFormHttpMessageConverter();
/**
* The default character set to use for reading form data.
*/
public void setCharset(Charset charset) {
this.formConverter.setCharset(charset);
}
@Override
protected void doFilterInternal(final HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if ("PUT".equals(request.getMethod()) && isFormContentType(request)) {
HttpInputMessage inputMessage = new ServletServerHttpRequest(request) {
@Override
public InputStream getBody() throws IOException {
return request.getInputStream();
}
};
MultiValueMap<String, String> formParameters = formConverter.read(null, inputMessage);
HttpServletRequest wrapper = new HttpPutFormContentRequestWrapper(request, formParameters);
filterChain.doFilter(wrapper, response);
}
else {
filterChain.doFilter(request, response);
}
}
private boolean isFormContentType(HttpServletRequest request) {
String contentType = request.getContentType();
return ((contentType != null) && contentType.equals(FORM_CONTENT_TYPE));
}
private static class HttpPutFormContentRequestWrapper extends HttpServletRequestWrapper {
private MultiValueMap<String, String> formParameters;
public HttpPutFormContentRequestWrapper(HttpServletRequest request, MultiValueMap<String, String> parameters) {
super(request);
this.formParameters = (parameters != null) ? parameters : new LinkedMultiValueMap<String, String>();
}
@Override
public String getParameter(String name) {
String queryStringValue = super.getParameter(name);
String formValue = this.formParameters.getFirst(name);
return (queryStringValue != null) ? queryStringValue : formValue;
}
@Override
public Map<String, String[]> getParameterMap() {
Map<String, String[]> result = new LinkedHashMap<String, String[]>();
Enumeration<String> names = this.getParameterNames();
while (names.hasMoreElements()) {
String name = names.nextElement();
result.put(name, this.getParameterValues(name));
}
return result;
}
@Override
public Enumeration<String> getParameterNames() {
Set<String> names = new LinkedHashSet<String>();
names.addAll(Collections.list(super.getParameterNames()));
names.addAll(this.formParameters.keySet());
return Collections.enumeration(names);
}
@Override
public String[] getParameterValues(String name) {
String[] queryStringValues = super.getParameterValues(name);
List<String> formValues = this.formParameters.get(name);
if (formValues == null) {
return queryStringValues;
}
else if (queryStringValues == null) {
return formValues.toArray(new String[formValues.size()]);
}
else {
List<String> result = new ArrayList<String>();
result.addAll(Arrays.asList(queryStringValues));
result.addAll(formValues);
return result.toArray(new String[result.size()]);
}
}
}
}

View File

@ -0,0 +1,70 @@
/*
* 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.mock.web;
import javax.servlet.FilterChain;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.springframework.util.Assert;
/**
* Mock implementation of the {@link javax.servlet.FilterConfig} interface.
*
* <p>Used for testing the web framework; also useful for testing
* custom {@link javax.servlet.Filter} implementations.
*
* @author Juergen Hoeller
* @since 2.0.3
* @see MockFilterConfig
* @see PassThroughFilterChain
*/
public class MockFilterChain implements FilterChain {
private ServletRequest request;
private ServletResponse response;
/**
* Records the request and response.
*/
public void doFilter(ServletRequest request, ServletResponse response) {
Assert.notNull(request, "Request must not be null");
Assert.notNull(response, "Response must not be null");
if (this.request != null) {
throw new IllegalStateException("This FilterChain has already been called!");
}
this.request = request;
this.response = response;
}
/**
* Return the request that {@link #doFilter} has been called with.
*/
public ServletRequest getRequest() {
return this.request;
}
/**
* Return the response that {@link #doFilter} has been called with.
*/
public ServletResponse getResponse() {
return this.response;
}
}

View File

@ -0,0 +1,178 @@
/*
* 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.filter;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.junit.Before;
import org.junit.Test;
import org.springframework.mock.web.MockFilterChain;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
/**
* Test fixture for {@link HttpPutFormContentFilter}.
*
* @author Rossen Stoyanchev
*/
public class HttpPutFormContentFilterTests {
private HttpPutFormContentFilter filter;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
private MockFilterChain filterChain;
@Before
public void setup() {
filter = new HttpPutFormContentFilter();
request = new MockHttpServletRequest("PUT", "/");
request.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=ISO-8859-1");
request.setContentType("application/x-www-form-urlencoded");
response = new MockHttpServletResponse();
filterChain = new MockFilterChain();
}
@Test
public void wrapPutOnly() throws Exception {
request.setContent("".getBytes("ISO-8859-1"));
String[] methods = new String[] {"GET", "POST", "DELETE", "HEAD", "OPTIONS", "TRACE"};
for (String method : methods) {
request.setMethod(method);
filterChain = new MockFilterChain();
filter.doFilter(request, response, filterChain);
assertSame("Should not wrap for HTTP method " + method, request, filterChain.getRequest());
}
}
@Test
public void wrapFormEncodedOnly() throws Exception {
request.setContent("".getBytes("ISO-8859-1"));
String[] contentTypes = new String[] {"text/plain", "multipart/form-data"};
for (String contentType : contentTypes) {
request.setContentType(contentType);
filterChain = new MockFilterChain();
filter.doFilter(request, response, filterChain);
assertSame("Should not wrap for content type " + contentType, request, filterChain.getRequest());
}
}
@Test
public void getParameter() throws Exception {
request.setContent("name=value".getBytes("ISO-8859-1"));
filter.doFilter(request, response, filterChain);
assertEquals("value", filterChain.getRequest().getParameter("name"));
}
@Test
public void queryStringParam() throws Exception {
request.addParameter("name", "value1");
request.setContent("name=value2".getBytes("ISO-8859-1"));
filter.doFilter(request, response, filterChain);
assertEquals("Query string parameters should be listed ahead of form parameters",
"value1", filterChain.getRequest().getParameter("name"));
}
@Test
public void nullParameter() throws Exception {
request.setContent("name=value".getBytes("ISO-8859-1"));
filter.doFilter(request, response, filterChain);
assertNull(filterChain.getRequest().getParameter("noSuchParam"));
}
@Test
public void getParameterNames() throws Exception {
request.addParameter("name1", "value1");
request.addParameter("name2", "value2");
request.setContent("name1=value1&name3=value3&name4=value4".getBytes("ISO-8859-1"));
filter.doFilter(request, response, filterChain);
List<String> names = Collections.list(filterChain.getRequest().getParameterNames());
assertEquals(Arrays.asList("name1", "name2", "name3", "name4"), names);
}
@Test
public void getParameterValues() throws Exception {
request.addParameter("name", "value1");
request.addParameter("name", "value2");
request.setContent("name=value3&name=value4".getBytes("ISO-8859-1"));
filter.doFilter(request, response, filterChain);
String[] values = filterChain.getRequest().getParameterValues("name");
assertArrayEquals(new String[]{"value1", "value2", "value3", "value4"}, values);
}
@Test
public void getQueryStringParameterValuesOnly() throws Exception {
request.addParameter("name", "value1");
request.addParameter("name", "value2");
request.setContent("anotherName=anotherValue".getBytes("ISO-8859-1"));
filter.doFilter(request, response, filterChain);
String[] values = filterChain.getRequest().getParameterValues("name");
assertArrayEquals(new String[]{"value1", "value2"}, values);
}
@Test
public void getFormParameterValuesOnly() throws Exception {
request.addParameter("name", "value1");
request.addParameter("name", "value2");
request.setContent("anotherName=anotherValue".getBytes("ISO-8859-1"));
filter.doFilter(request, response, filterChain);
String[] values = filterChain.getRequest().getParameterValues("anotherName");
assertArrayEquals(new String[]{"anotherValue"}, values);
}
@Test
public void noParameterValuesOnly() throws Exception {
request.addParameter("name", "value1");
request.addParameter("name", "value2");
request.setContent("anotherName=anotherValue".getBytes("ISO-8859-1"));
filter.doFilter(request, response, filterChain);
String[] values = filterChain.getRequest().getParameterValues("noSuchParameter");
assertNull(values);
}
@Test
public void getParameterMap() throws Exception {
request.addParameter("name", "value1");
request.addParameter("name", "value2");
request.setContent("name=value3&name4=value4".getBytes("ISO-8859-1"));
filter.doFilter(request, response, filterChain);
Map<String, String[]> parameters = filterChain.getRequest().getParameterMap();
assertEquals(2, parameters.size());
assertArrayEquals(new String[] {"value1", "value2", "value3"}, parameters.get("name"));
assertArrayEquals(new String[] {"value4"}, parameters.get("name4"));
}
}

View File

@ -1538,166 +1538,139 @@ public ResponseEntity&lt;String&gt; handle(HttpEntity&lt;byte[]&gt; requestEntit
</section> </section>
<section id="mvc-ann-modelattrib-methods"> <section id="mvc-ann-modelattrib-methods">
<title>Using <interfacename>@ModelAttribute</interfacename> on a controller method</title> <title>Using <interfacename>@ModelAttribute</interfacename> on a method</title>
<para>The <interfacename>@ModelAttribute</interfacename> annotation can be used on a <para>The <interfacename>@ModelAttribute</interfacename> annotation can be used on
method or on a method argument. This section explains its usage on a method while the methods or on method arguments. This section explains its usage on methods while the
next section explains its usage on a method argument.</para> next section explains its usage on method arguments.</para>
<para>An <interfacename>@ModelAttribute</interfacename> on a method indicates a method <para>An <interfacename>@ModelAttribute</interfacename> on a method indicates the purpose
for adding model attributes. A few examples:</para> of that method is to add one or more model attributes. Such methods support the same
argument types as <interfacename>@RequestMapping</interfacename> methods but cannot
be mapped directly to requests. Instead <interfacename>@ModelAttribute</interfacename>
methods in a controller are invoked before <interfacename>@RequestMapping</interfacename>
methods, within the same controller. A couple of examples:</para>
<programlisting language="java">@ModelAttribute <programlisting language="java">
public void populateModel(Model model) { // Add one attribute
model.addAttribute("types", this.clinic.getPetTypes(); // The return value of the method is added to the model under the name "account"
// add more attributes ... // You can customize the name via @ModelAttribute("myAccount")
}
@ModelAttribute
public void addAccount(@RequestParam String number, Model model) {
Account account = accountManager.findAccount(number);
model.addAttribute(account);
}
@ModelAttribute @ModelAttribute
public Account addAccount(@RequestParam String number) { public Account addAccount(@RequestParam String number) {
return accountManager.findAccount(number); return accountManager.findAccount(number);
} }
// Add multiple attributes
@ModelAttribute
public void populateModel(@RequestParam String number, Model model) {
model.addAttribute(accountManager.findAccount(number));
// add more ...
}
</programlisting> </programlisting>
<para><interfacename>@ModelAttribute</interfacename> methods are not associated <para><interfacename>@ModelAttribute</interfacename> methods are used to populate
with any request mappings. Simply they are invoked prior the model with commonly needed attributes for example to fill a drop-down with
to the invocation of every states or with pet types, or to retrieve a command object like Account in order
<interfacename>@RequestMapping</interfacename> method in the same controller. to use it to represent the data on an HTML form. The latter case is further
In terms of method arguments they support the same argument types as discussed in the next section.</para>
<interfacename>@RequestMapping</interfacename> methods, hence allowing access to request
parameters, path variables, and so on -- see <xref linkend="mvc-ann-arguments"/>
for a complete listing.</para>
<para>There are two common use cases for <interfacename>@ModelAttribute</interfacename>
methods. One, is to add commonly needed model attributes - see the first example
above where the model is populated with pet types to be shown in a drop-down.
The second use case is to pre-load a command object - see the second and third
examples above with the Account object (more on command objects in the next section).</para>
<para>Note the two styles of <interfacename>@ModelAttribute</interfacename> methods. <para>Note the two styles of <interfacename>@ModelAttribute</interfacename> methods.
In one style, the method accepts a <classname>Model</classname> and adds any number In the first, the method adds an attribute implicitly
of model attributes to it. In the second style, the method adds an attribute implicitly by returning it. In the second, the method accepts a <classname>Model</classname>
by having it as its return value. That results in the object being added as an and adds any number of model attributes to it.
attribute to the model. You can choose between the two styles depending on You can choose between the two styles depending on your needs.</para>
whether you need to add one model attribute or multiple.</para>
<para>A controller can have any number of <interfacename>@ModelAttribute</interfacename> <para>A controller can have any number of <interfacename>@ModelAttribute</interfacename>
methods. All such methods are invoked before <interfacename>@RequestMapping</interfacename> methods. All such methods are invoked before <interfacename>@RequestMapping</interfacename>
methods within the same controller each contributing model attributes.</para> methods of the same controller.</para>
<para>What happens when a model attribute name is not explicitly provided? In such cases <tip>
a default name is assigned to the model attribute. <para>What happens when a model attribute name is not explicitly specified?
For example if the method returns an object of type <classname>Account</classname>, In such cases a default name is assigned to the model attribute based on its type.
the default name used is "account". However, you can also change it to something else For example if the method returns an object of type <classname>Account</classname>,
through the value of the <interfacename>@ModelAttribute</interfacename> annotation. the default name used is "account". You can change that through
Or if adding attributes directly to the <classname>Model</classname>, use the the value of the <interfacename>@ModelAttribute</interfacename> annotation.
appropriate overloaded <literal>addAttribute(..)</literal> method providing If adding attributes directly to the <classname>Model</classname>, use the
both the attribute name and the attribute value.</para> appropriate overloaded <literal>addAttribute(..)</literal> method - i.e.
with or without an attribute name.</para>
</tip>
<para>Lastly the <interfacename>@ModelAttribute</interfacename> annotation can also <para>The <interfacename>@ModelAttribute</interfacename> annotation can be used on
be used directly on an <interfacename>@RequestMapping</interfacename> method. In this <interfacename>@RequestMapping</interfacename> methods as well. In that case
cause, the return value of the <interfacename>@RequestMapping</interfacename> method the return value of the <interfacename>@RequestMapping</interfacename> method
will be added as a model attribute and the view name will be derived using is interpreted as a model attribute rather than as a view name.
a convention -- see <xref linkend="mvc-coc-r2vnt"/>.</para> The view name is derived from view name conventions instead much like for methods
returning void -- see <xref linkend="mvc-coc-r2vnt"/>.</para>
</section> </section>
<section id="mvc-ann-modelattrib-method-args"> <section id="mvc-ann-modelattrib-method-args">
<title>Using <interfacename>@ModelAttribute</interfacename> on a controller method argument</title> <title>Using <interfacename>@ModelAttribute</interfacename> on a method argument</title>
<para>As explained in the previous section an <interfacename>@ModelAttribute</interfacename> <para>As explained in the previous section <interfacename>@ModelAttribute</interfacename>
annotation can be used on a method or on a method argument. When used on a method argument, can be used on methods or on method arguments. This section explains its usage on
the <interfacename>@ModelAttribute</interfacename> annotation indicates a command object. A command method arguments.</para>
object is an object with properties (e.g. <classname>Account</classname> rather than
a simple type like <literal>int</literal> or <literal>String</literal>) that is used to
populate a form or reversely to be populated with the data from an HTML form.
As you will see command objects can significantly automate the process of
working with forms -- rather than dealing with individual form
parameters one at a time you'll be have them in a one command object.</para>
<para>To be more precise annotating a method argument with <para>An <interfacename>@ModelAttribute</interfacename> on a method argument indicates
<interfacename>@ModelAttribute</interfacename> means 3 things: the argument should be retrieved from the model. If not present in the model, the argument
<orderedlist> should be instantiated first and then added to the model. Once present in the model, the
<listitem>The command object should be located and/or instantiated.</listitem> argument's fields should be populated from all request parameters that have matching names.
<listitem>It should be populated with request parameters from the current request.</listitem> This is known as data binding in Spring MVC, a very useful mechanism that saves you from
<listitem>The object should be added to the model.</listitem> having to parse each form field individually.
</orderedlist>
See the following example:
</para> </para>
<programlisting language="java"> <programlisting language="java">
@RequestMapping(value="/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST) @RequestMapping(value="/owners/{ownerId}/pets/{petId}/edit", method = RequestMethod.POST)
public String processSubmit(<emphasis role="bold">@ModelAttribute("pet") Pet pet</emphasis>) { public String processSubmit(<emphasis role="bold">@ModelAttribute Pet pet</emphasis>) {
}</programlisting> }</programlisting>
<para>The <interfacename>@ModelAttribute</interfacename> annotation <para>Given the above example where can the Pet instance come from?
designates "pet" as a command object. But where does the object come from? There are two options. There are several options: </para>
It may have been added to the model by an <interfacename>@ModelAttribute</interfacename>
method as explained in the previous section. Or otherwise if it's not already in the model, it
is instantiated through the default constructor. Typically if the object needs to be retrieved
from a database first, you can create an <interfacename>@ModelAttribute</interfacename>
method to do that. Or otherwise rely on the default constructor. If multiple methods in the
controller work on the same object, you can have it stored in the HTTP session between requests.
That is explained in <xref linkend="mvc-ann-sessionattrib"/>.</para>
<note> <itemizedlist>
<title>Using a URI template variable to retrieve a command object</title> <listitem>It may already be in the model due to use of
<interfacename>@SessionAttributes</interfacename>
<para>There is one other more advanced way of instantiating a command object. -- see <xref linkend="mvc-ann-sessionattrib"/>.</listitem>
It is useful when the command object can be retrieved based on a URI template <listitem>It may already be in the model due to an
variable. Here is an example:</para> <interfacename>@ModelAttribute</interfacename> method in the same
controller -- as explained in the previous section.</listitem>
<listitem>It may be retrieved based on a URI template variable
and type converter (explained in more detail below).</listitem>
<listitem>It may be instantiated using its default constructor.</listitem>
</itemizedlist>
<para>An <interfacename>@ModelAttribute</interfacename> method is a common
way to to retrieve an attribute from the database, which may optionally be
stored between requests through the use of
<interfacename>@SessionAttributes</interfacename>.
In some cases it may be convenient to retrieve the attribute by using
an URI template variable and a type converter. Here is an example:</para>
<programlisting language="java"> <programlisting language="java">
@RequestMapping(value="/accounts/{account}", method = RequestMethod.PUT) @RequestMapping(value="/accounts/{account}", method = RequestMethod.PUT)
public String save(@ModelAttribute("account") Account account) { public String save(@ModelAttribute("account") Account account) {
}</programlisting> }</programlisting>
<para>The name of the model attribute "account" matches to the name of a URI
template variable. Assuming there is a registered <para>In this example the name of the model attribute (i.e. "account")
<classname>Converter&lt;String, Account&gt;</classname> matches the name of a URI template variable. If you register
or <classname>PropertyEditor</classname> that can turn a <literal>String</literal> <classname>Converter&lt;String, Account&gt;</classname>
account number into an <classname>Account</classname> instance, those will be used (or <classname>PropertyEditor</classname>) that can turn the
to provision the command object and have it added to the model. <literal>String</literal>-based account into an <classname>Account</classname>
If such a Converter or PropertyEditor does not exist, however the default instance, then the above example will work without the need for an
constructor will still be used.</para> <interfacename>@ModelAttribute</interfacename> method.</para>
</note>
</section> <para>The next step is data binding. The <classname>WebDataBinder</classname>
class matches request parameter names -- including query string parameters and
<section id="mvc-ann-modelattrib-data-binding"> form fields -- to model attribute fields by name. Matching fields are populated
<title>Data binding and validation with <interfacename>@ModelAttribute</interfacename></title> after type conversion (from String to the target field type) has been applied
where necessary.
<para>As mentioned in the previous section an <interfacename>@ModelAttribute</interfacename> Data binding and validation are covered in <xref linkend="validation"/>.
annotation means 3 things: Customizing the data binding process for a controller level is covered in
<orderedlist> <xref linkend="mvc-ann-webdatabinder"/>.</para>
<listitem>The command object should be located and/or instantiated.</listitem>
<listitem>It should be populated with request parameters from the current request.</listitem>
<listitem>The object should be added to the model.</listitem>
</orderedlist>
In this section we'll focus on the second: data binding and validation.</para>
<para>After the command object has been provisioned using one of the methods described in the
previous section, the next step is to apply data binding and validation.
Spring's <classname>WebDataBinder</classname> does that by matching request parameters --
usually form data fields but can also be query string parameters -- to command object
properties, including properties of nested objects, and populates those properties
accordingly also applying type conversion as necessary.
For example an object of type <classname>Person</classname> with
properties <literal>firstName</literal> and <literal>age</literal>
will be populated assuming the presence of such named form fields.
As a result of data binding the command object is populated with the form data.</para>
<para>There are various ways to customize the data binding process.
For example you may wish to specify required fields, allowed and disallowed
fields, or extend type conversion.
See <xref linkend="mvc-ann-webdatabinder"/> and also <xref linkend="validation"/>.</para>
<para>As a result of data binding there may be errors such as missing required <para>As a result of data binding there may be errors such as missing required
fields or type conversion errors. To check for such errors fields or type conversion errors. To check for such errors
@ -1716,11 +1689,11 @@ public String processSubmit(<emphasis role="bold">@ModelAttribute("pet") Pet pet
}</programlisting> }</programlisting>
<para><classname>BindingResult</classname> allows you to check if errors were found in <para>With a <classname>BindingResult</classname> you can check if errors were found in
which case it's common to return to the same form where the errors which case it's common to render the same form where the errors
can be rendered with the help of Spring's <literal>&lt;errors&gt;</literal> form tag.</para> can be shown with the help of Spring's <literal>&lt;errors&gt;</literal> form tag.</para>
<para>In addition to data binding you can apply validation using your own custom <para>In addition to data binding you can also invoke validation using your own custom
validator passing the same <classname>BindingResult</classname> that was used to record validator passing the same <classname>BindingResult</classname> that was used to record
data binding errors. That allows for data binding and validation errors to be accumulated data binding errors. That allows for data binding and validation errors to be accumulated
in one place and subsequently reported back to the user: </para> in one place and subsequently reported back to the user: </para>
@ -1752,13 +1725,13 @@ public String processSubmit(<emphasis role="bold">@Valid @ModelAttribute("pet")
}</programlisting> }</programlisting>
<para>See <xref linkend="validation-beanvalidation"/> and <para>See <xref linkend="validation-beanvalidation"/> and
<xref linkend="validation" /> for details on how to configure and use Spring's Validation support.</para> <xref linkend="validation" /> for details on how to configure and use validation.</para>
</section> </section>
<section id="mvc-ann-sessionattrib"> <section id="mvc-ann-sessionattrib">
<title>Specifying attributes to store in a session with <title>Using <classname>@SessionAttributes</classname> to store model attributes
<classname>@SessionAttributes</classname></title> in the HTTP session between requests</title>
<para>The type-level <classname>@SessionAttributes</classname> <para>The type-level <classname>@SessionAttributes</classname>
annotation declares session attributes used by a specific handler. annotation declares session attributes used by a specific handler.
@ -1786,7 +1759,49 @@ public class EditPetForm {
</para> </para>
</note> </note>
</section> </section>
<section id="mvc-ann-form-urlencoded-data">
<title>Working with <literal>application/x-www-form-urlencoded</literal> data</title>
<para>The previous sections covered use of <interfacename>@ModelAttribute</interfacename>
to support form submission requests from browser clients. The same annotation is
recommended for use with requests from non-browser clients as well. However there is
one notable difference when it comes to working with HTTP PUT requests.
Browsers can submit form data via HTTP GET or HTTP POST. Non-browser
clients can also submit forms via HTTP PUT. This presents a challenge because the Servlet
specification requires the <literal>ServletRequest.getParameter*()</literal> family
of methods to support form field access only for HTTP POST, not for HTTP PUT.
</para>
<para>To support HTTP PUT requests, the <literal>spring-web</literal> module provides
the filter <classname>HttpPutFormContentFilter</classname>, which can be configured
in <filename>web.xml</filename>:</para>
<programlisting language="xml">
&lt;filter&gt;
&lt;filter-name&gt;httpPutFormFilter&lt;/filter-name&gt;
&lt;filter-class&gt;org.springframework.web.filter.HttpPutFormContentFilter&lt;/filter-class&gt;
&lt;/filter&gt;
&lt;filter-mapping&gt;
&lt;filter-name&gt;httpPutFormFilter&lt;/filter-name&gt;
&lt;servlet-name&gt;dispatcherServlet&lt;/servlet-name&gt;
&lt;/filter-mapping&gt;
&lt;servlet&gt;
&lt;servlet-name&gt;dispatcherServlet&lt;/servlet-name&gt;
&lt;servlet-class&gt;org.springframework.web.servlet.DispatcherServlet&lt;/servlet-class&gt;
&lt;/servlet&gt;
</programlisting>
<para>The above filter intercepts HTTP PUT requests with content type
<literal>application/x-www-form-urlencoded</literal>, reads the form data from the
body of the request, and wraps the <classname>ServletRequest</classname> in order
to make the form data available through the <literal>ServletRequest.getParameter*()</literal>
family of methods.</para>
</section>
<section id="mvc-ann-cookievalue"> <section id="mvc-ann-cookievalue">
<title>Mapping cookie values with the @CookieValue annotation</title> <title>Mapping cookie values with the @CookieValue annotation</title>