SPR-8532 Upgrade org.springframework.web.servlet to Servlet 3.0 (as provided dependency) and add support for javax.servlet.Part parameter
This commit is contained in:
@@ -22,9 +22,15 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
|
||||
/**
|
||||
* Annotation which indicates that a method parameter should be bound to the web request body. Supported for annotated
|
||||
* handler methods in Servlet environments.
|
||||
* Annotation indicating a method parameter should be bound to the body of the web request.
|
||||
* The body of the request is passed through an {@link HttpMessageConverter} to resolve the
|
||||
* method argument depending on the content type of the request. Optionally, automatic
|
||||
* validation can be applied by annotating the argument with {@code @Valid}.
|
||||
*
|
||||
* <p>Supported for annotated handler methods in Servlet environments.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @see RequestHeader
|
||||
|
||||
@@ -16,20 +16,44 @@
|
||||
|
||||
package org.springframework.web.bind.annotation;
|
||||
|
||||
import java.beans.PropertyEditor;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.multipart.MultipartResolver;
|
||||
|
||||
/**
|
||||
* Annotation that indicates a method parameter should be bound to the content of a part of a "multipart/form-data" request.
|
||||
* Supported for annotated handler methods in Servlet environments.
|
||||
*
|
||||
* Annotation that can be used to associate the part of a "multipart/form-data" request
|
||||
* with a method argument. Supported method argument types include {@link MultipartFile}
|
||||
* in conjunction with Spring's {@link MultipartResolver} abstraction,
|
||||
* {@code javax.servlet.http.Part} in conjunction with Servlet 3.0 multipart requests,
|
||||
* or otherwise for any other method argument, the content of the part is passed through an
|
||||
* {@link HttpMessageConverter} taking into consideration the 'Content-Type' header
|
||||
* of the request part. This is analogous to what @{@link RequestBody} does to resolve
|
||||
* an argument based on the content of a non-multipart regular request.
|
||||
*
|
||||
* <p>Note that @{@link RequestParam} annotation can also be used to associate the
|
||||
* part of a "multipart/form-data" request with a method argument supporting the same
|
||||
* method argument types. The main difference is that when the method argument is not a
|
||||
* String, @{@link RequestParam} relies on type conversion via a registered
|
||||
* {@link Converter} or {@link PropertyEditor} while @{@link RequestPart} relies
|
||||
* on {@link HttpMessageConverter}s taking into consideration the 'Content-Type' header
|
||||
* of the request part. @{@link RequestParam} is likely to be used with name-value form
|
||||
* fields while @{@link RequestPart} is likely to be used with parts containing more
|
||||
* complex content (e.g. JSON, XML).
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @author Arjen Poutsma
|
||||
* @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
|
||||
* @since 3.1
|
||||
*
|
||||
* @see RequestParam
|
||||
* @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
|
||||
*/
|
||||
@Target(ElementType.PARAMETER)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@@ -41,4 +65,12 @@ public @interface RequestPart {
|
||||
*/
|
||||
String value() default "";
|
||||
|
||||
/**
|
||||
* Whether the part is required.
|
||||
* <p>Default is <code>true</code>, leading to an exception thrown in case
|
||||
* of the part missing in the request. Switch this to <code>false</code>
|
||||
* if you prefer a <code>null</value> in case of the part missing.
|
||||
*/
|
||||
boolean required() default true;
|
||||
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
|
||||
@@ -33,19 +34,25 @@ import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ValueConstants;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.springframework.web.multipart.MultipartRequest;
|
||||
import org.springframework.web.multipart.MultipartHttpServletRequest;
|
||||
import org.springframework.web.multipart.MultipartResolver;
|
||||
import org.springframework.web.util.WebUtils;
|
||||
|
||||
/**
|
||||
* Resolves method arguments annotated with @{@link RequestParam}.
|
||||
* Resolves method arguments annotated with @{@link RequestParam}, arguments of
|
||||
* type {@link MultipartFile} in conjunction with Spring's {@link MultipartResolver}
|
||||
* abstraction, and arguments of type {@code javax.servlet.http.Part} in conjunction
|
||||
* with Servlet 3.0 multipart requests. This resolver can also be created in default
|
||||
* resolution mode in which simple types (int, long, etc.) not annotated
|
||||
* with @{@link RequestParam} are also treated as request parameters with the
|
||||
* parameter name derived from the argument name.
|
||||
*
|
||||
* <p>If the method parameter type is {@link Map}, the request parameter name is resolved and then converted
|
||||
* to a {@link Map} via type conversion assuming a suitable {@link PropertyEditor} or {@link Converter} is
|
||||
* registered. Alternatively, see {@link RequestParamMapMethodArgumentResolver} for access to all request
|
||||
* parameters in a {@link Map}.
|
||||
*
|
||||
* <p>If this class is created with default resolution mode on, simple types not annotated
|
||||
* with @{@link RequestParam} are also treated as request parameters with the parameter name based
|
||||
* on the method argument name. See the class constructor for more details.
|
||||
* <p>If the method parameter type is {@link Map}, the request parameter name is used to
|
||||
* resolve the request parameter String value. The value is then converted to a {@link Map}
|
||||
* via type conversion assuming a suitable {@link Converter} or {@link PropertyEditor} has
|
||||
* been registered. If a request parameter name is not specified with a {@link Map} method
|
||||
* parameter type, the {@link RequestParamMapMethodArgumentResolver} is used instead
|
||||
* providing access to all request parameters in the form of a map.
|
||||
*
|
||||
* <p>A {@link WebDataBinder} is invoked to apply type conversion to resolved request header values that
|
||||
* don't yet match the method parameter type.
|
||||
@@ -72,6 +79,18 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethod
|
||||
this.useDefaultResolution = useDefaultResolution;
|
||||
}
|
||||
|
||||
/**
|
||||
* Supports the following:
|
||||
* <ul>
|
||||
* <li>@RequestParam method arguments. This excludes the case where a parameter is of type
|
||||
* {@link Map} and the annotation does not specify a request parameter name. See
|
||||
* {@link RequestParamMapMethodArgumentResolver} instead for such parameters.
|
||||
* <li>Arguments of type {@link MultipartFile} even if not annotated.
|
||||
* <li>Arguments of type {@code javax.servlet.http.Part} even if not annotated.
|
||||
* </ul>
|
||||
*
|
||||
* <p>In default resolution mode, simple type arguments not annotated with @RequestParam are also supported.
|
||||
*/
|
||||
public boolean supportsParameter(MethodParameter parameter) {
|
||||
Class<?> paramType = parameter.getParameterType();
|
||||
RequestParam requestParamAnnot = parameter.getParameterAnnotation(RequestParam.class);
|
||||
@@ -81,6 +100,12 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethod
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else if (MultipartFile.class.equals(paramType)) {
|
||||
return true;
|
||||
}
|
||||
else if ("javax.servlet.http.Part".equals(parameter.getParameterType().getName())) {
|
||||
return true;
|
||||
}
|
||||
else if (this.useDefaultResolution) {
|
||||
return BeanUtils.isSimpleProperty(paramType);
|
||||
}
|
||||
@@ -99,14 +124,22 @@ public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethod
|
||||
|
||||
@Override
|
||||
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest webRequest) throws Exception {
|
||||
MultipartRequest multipartRequest = webRequest.getNativeRequest(MultipartRequest.class);
|
||||
|
||||
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
|
||||
MultipartHttpServletRequest multipartRequest =
|
||||
WebUtils.getNativeRequest(servletRequest, MultipartHttpServletRequest.class);
|
||||
|
||||
if (multipartRequest != null) {
|
||||
List<MultipartFile> files = multipartRequest.getFiles(name);
|
||||
if (!files.isEmpty()) {
|
||||
return (files.size() == 1 ? files.get(0) : files);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if ("javax.servlet.http.Part".equals(parameter.getParameterType().getName())) {
|
||||
return servletRequest.getPart(name);
|
||||
}
|
||||
|
||||
String[] paramValues = webRequest.getParameterValues(name);
|
||||
if (paramValues != null) {
|
||||
return paramValues.length == 1 ? paramValues[0] : paramValues;
|
||||
|
||||
@@ -28,6 +28,7 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
@@ -145,6 +146,7 @@ public class MockHttpServletRequest implements HttpServletRequest {
|
||||
|
||||
private int localPort = DEFAULT_SERVER_PORT;
|
||||
|
||||
private Map<String, Part> parts = new HashMap<String, Part>();
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// HttpServletRequest properties
|
||||
@@ -890,13 +892,17 @@ public class MockHttpServletRequest implements HttpServletRequest {
|
||||
public boolean authenticate(HttpServletResponse arg0) throws IOException, ServletException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public void addPart(Part part) {
|
||||
parts.put(part.getName(), part);
|
||||
}
|
||||
|
||||
public Part getPart(String arg0) throws IOException, IllegalStateException, ServletException {
|
||||
throw new UnsupportedOperationException();
|
||||
public Part getPart(String key) throws IOException, IllegalStateException, ServletException {
|
||||
return parts.get(key);
|
||||
}
|
||||
|
||||
public Collection<Part> getParts() throws IOException, IllegalStateException, ServletException {
|
||||
throw new UnsupportedOperationException();
|
||||
return parts.values();
|
||||
}
|
||||
|
||||
public void login(String arg0, String arg1) throws ServletException {
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
* 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 java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import javax.servlet.http.Part;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.FileCopyUtils;
|
||||
|
||||
/**
|
||||
* Mock implementation of the {@link Part} interface.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 3.1
|
||||
* @see MockHttpServletRequest
|
||||
*/
|
||||
public class MockPart implements Part {
|
||||
|
||||
private static final String CONTENT_TYPE = "Content-Type";
|
||||
|
||||
private final String name;
|
||||
|
||||
private String contentType;
|
||||
|
||||
private final byte[] content;
|
||||
|
||||
/**
|
||||
* Create a new MockPart with the given content.
|
||||
* @param name the name of the part
|
||||
* @param content the content for the part
|
||||
*/
|
||||
public MockPart(String name, byte[] content) {
|
||||
this(name, "", content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new MockPart with the given content.
|
||||
* @param name the name of the part
|
||||
* @param contentStream the content of the part as stream
|
||||
* @throws IOException if reading from the stream failed
|
||||
*/
|
||||
public MockPart(String name, InputStream contentStream) throws IOException {
|
||||
this(name, "", FileCopyUtils.copyToByteArray(contentStream));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new MockPart with the given content.
|
||||
* @param name the name of the file
|
||||
* @param contentType the content type (if known)
|
||||
* @param content the content of the file
|
||||
*/
|
||||
public MockPart(String name, String contentType, byte[] content) {
|
||||
Assert.hasLength(name, "Name must not be null");
|
||||
this.name = name;
|
||||
this.contentType = contentType;
|
||||
this.content = (content != null ? content : new byte[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new MockPart with the given content.
|
||||
* @param name the name of the file
|
||||
* @param contentType the content type (if known)
|
||||
* @param contentStream the content of the part as stream
|
||||
* @throws IOException if reading from the stream failed
|
||||
*/
|
||||
public MockPart(String name, String contentType, InputStream contentStream)
|
||||
throws IOException {
|
||||
|
||||
this(name, contentType, FileCopyUtils.copyToByteArray(contentStream));
|
||||
}
|
||||
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public String getContentType() {
|
||||
return this.contentType;
|
||||
}
|
||||
|
||||
public long getSize() {
|
||||
return this.content.length;
|
||||
}
|
||||
|
||||
public InputStream getInputStream() throws IOException {
|
||||
return new ByteArrayInputStream(this.content);
|
||||
}
|
||||
|
||||
public String getHeader(String name) {
|
||||
if (CONTENT_TYPE.equalsIgnoreCase(name)) {
|
||||
return this.contentType;
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Collection<String> getHeaders(String name) {
|
||||
if (CONTENT_TYPE.equalsIgnoreCase(name)) {
|
||||
return Collections.singleton(this.contentType);
|
||||
}
|
||||
else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public Collection<String> getHeaderNames() {
|
||||
return Collections.singleton(CONTENT_TYPE);
|
||||
}
|
||||
|
||||
public void write(String fileName) throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public void delete() throws IOException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -24,6 +24,8 @@ import static org.junit.Assert.assertTrue;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.http.Part;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
|
||||
@@ -32,6 +34,7 @@ import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.mock.web.MockMultipartFile;
|
||||
import org.springframework.mock.web.MockMultipartHttpServletRequest;
|
||||
import org.springframework.mock.web.MockPart;
|
||||
import org.springframework.web.bind.MissingServletRequestParameterException;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.context.request.NativeWebRequest;
|
||||
@@ -54,6 +57,8 @@ public class RequestParamMethodArgumentResolverTests {
|
||||
private MethodParameter paramMultiPartFile;
|
||||
private MethodParameter paramMap;
|
||||
private MethodParameter paramStringNotAnnot;
|
||||
private MethodParameter paramMultipartFileNotAnnot;
|
||||
private MethodParameter paramPartNotAnnot;
|
||||
|
||||
private NativeWebRequest webRequest;
|
||||
|
||||
@@ -63,8 +68,8 @@ public class RequestParamMethodArgumentResolverTests {
|
||||
public void setUp() throws Exception {
|
||||
resolver = new RequestParamMethodArgumentResolver(null, true);
|
||||
|
||||
Method method = getClass().getMethod("params",
|
||||
String.class, String[].class, Map.class, MultipartFile.class, Map.class, String.class);
|
||||
Method method = getClass().getMethod("params", String.class, String[].class, Map.class, MultipartFile.class,
|
||||
Map.class, String.class, MultipartFile.class, Part.class);
|
||||
|
||||
paramNamedDefaultValueString = new MethodParameter(method, 0);
|
||||
paramNamedStringArray = new MethodParameter(method, 1);
|
||||
@@ -73,6 +78,10 @@ public class RequestParamMethodArgumentResolverTests {
|
||||
paramMap = new MethodParameter(method, 4);
|
||||
paramStringNotAnnot = new MethodParameter(method, 5);
|
||||
paramStringNotAnnot.initParameterNameDiscovery(new LocalVariableTableParameterNameDiscoverer());
|
||||
paramMultipartFileNotAnnot = new MethodParameter(method, 6);
|
||||
paramMultipartFileNotAnnot.initParameterNameDiscovery(new LocalVariableTableParameterNameDiscoverer());
|
||||
paramPartNotAnnot = new MethodParameter(method, 7);
|
||||
paramPartNotAnnot.initParameterNameDiscovery(new LocalVariableTableParameterNameDiscoverer());
|
||||
|
||||
request = new MockHttpServletRequest();
|
||||
webRequest = new ServletWebRequest(request, new MockHttpServletResponse());
|
||||
@@ -87,6 +96,8 @@ public class RequestParamMethodArgumentResolverTests {
|
||||
assertTrue("MultipartFile parameter not supported", resolver.supportsParameter(paramMultiPartFile));
|
||||
assertFalse("non-@RequestParam parameter supported", resolver.supportsParameter(paramMap));
|
||||
assertTrue("Simple type params supported w/o annotations", resolver.supportsParameter(paramStringNotAnnot));
|
||||
assertTrue("MultipartFile parameter not supported", resolver.supportsParameter(paramMultipartFileNotAnnot));
|
||||
assertTrue("Part parameter not supported", resolver.supportsParameter(paramPartNotAnnot));
|
||||
|
||||
resolver = new RequestParamMethodArgumentResolver(null, false);
|
||||
assertFalse(resolver.supportsParameter(paramStringNotAnnot));
|
||||
@@ -127,6 +138,32 @@ public class RequestParamMethodArgumentResolverTests {
|
||||
assertEquals("Invalid result", expected, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveMultipartFileNotAnnotArgument() throws Exception {
|
||||
MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest();
|
||||
MultipartFile expected = new MockMultipartFile("paramMultipartFileNotAnnot", "Hello World".getBytes());
|
||||
request.addFile(expected);
|
||||
webRequest = new ServletWebRequest(request);
|
||||
|
||||
Object result = resolver.resolveArgument(paramMultipartFileNotAnnot, null, webRequest, null);
|
||||
|
||||
assertTrue(result instanceof MultipartFile);
|
||||
assertEquals("Invalid result", expected, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolvePartArgument() throws Exception {
|
||||
MockPart expected = new MockPart("paramPartNotAnnot", "Hello World".getBytes());
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.addPart(expected);
|
||||
webRequest = new ServletWebRequest(request);
|
||||
|
||||
Object result = resolver.resolveArgument(paramPartNotAnnot, null, webRequest, null);
|
||||
|
||||
assertTrue(result instanceof Part);
|
||||
assertEquals("Invalid result", expected, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveDefaultValue() throws Exception {
|
||||
Object result = resolver.resolveArgument(paramNamedDefaultValueString, null, webRequest, null);
|
||||
@@ -157,7 +194,9 @@ public class RequestParamMethodArgumentResolverTests {
|
||||
@RequestParam("name") Map<?, ?> param3,
|
||||
@RequestParam(value = "file") MultipartFile param4,
|
||||
@RequestParam Map<?, ?> param5,
|
||||
String paramStringNotAnnot) {
|
||||
String paramStringNotAnnot,
|
||||
MultipartFile paramMultipartFileNotAnnot,
|
||||
Part paramPartNotAnnot) {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user