23 changed files with 912 additions and 114 deletions
@ -0,0 +1,137 @@ |
|||||||
|
/* |
||||||
|
* 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.support; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Collections; |
||||||
|
import java.util.LinkedHashSet; |
||||||
|
import java.util.List; |
||||||
|
import java.util.Set; |
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest; |
||||||
|
|
||||||
|
import org.apache.commons.logging.Log; |
||||||
|
import org.apache.commons.logging.LogFactory; |
||||||
|
import org.springframework.core.MethodParameter; |
||||||
|
import org.springframework.http.HttpInputMessage; |
||||||
|
import org.springframework.http.MediaType; |
||||||
|
import org.springframework.http.converter.HttpMessageConverter; |
||||||
|
import org.springframework.http.server.ServletServerHttpRequest; |
||||||
|
import org.springframework.util.Assert; |
||||||
|
import org.springframework.web.HttpMediaTypeNotSupportedException; |
||||||
|
import org.springframework.web.context.request.NativeWebRequest; |
||||||
|
import org.springframework.web.method.support.HandlerMethodArgumentResolver; |
||||||
|
|
||||||
|
/** |
||||||
|
* A base class for resolving method argument values by reading from the body of a request with {@link HttpMessageConverter}s. |
||||||
|
* |
||||||
|
* @author Arjen Poutsma |
||||||
|
* @author Rossen Stoyanchev |
||||||
|
* @since 3.1 |
||||||
|
*/ |
||||||
|
public abstract class AbstractMessageConverterMethodArgumentResolver implements HandlerMethodArgumentResolver { |
||||||
|
|
||||||
|
protected final Log logger = LogFactory.getLog(getClass()); |
||||||
|
|
||||||
|
protected final List<HttpMessageConverter<?>> messageConverters; |
||||||
|
|
||||||
|
protected final List<MediaType> allSupportedMediaTypes; |
||||||
|
|
||||||
|
public AbstractMessageConverterMethodArgumentResolver(List<HttpMessageConverter<?>> messageConverters) { |
||||||
|
Assert.notEmpty(messageConverters, "'messageConverters' must not be empty"); |
||||||
|
this.messageConverters = messageConverters; |
||||||
|
this.allSupportedMediaTypes = getAllSupportedMediaTypes(messageConverters); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the media types supported by all provided message converters preserving their ordering and |
||||||
|
* further sorting by specificity via {@link MediaType#sortBySpecificity(List)}. |
||||||
|
*/ |
||||||
|
private static List<MediaType> getAllSupportedMediaTypes(List<HttpMessageConverter<?>> messageConverters) { |
||||||
|
Set<MediaType> allSupportedMediaTypes = new LinkedHashSet<MediaType>(); |
||||||
|
for (HttpMessageConverter<?> messageConverter : messageConverters) { |
||||||
|
allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes()); |
||||||
|
} |
||||||
|
List<MediaType> result = new ArrayList<MediaType>(allSupportedMediaTypes); |
||||||
|
MediaType.sortBySpecificity(result); |
||||||
|
return Collections.unmodifiableList(result); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates the method argument value of the expected parameter type by reading from the given request. |
||||||
|
* |
||||||
|
* @param <T> the expected type of the argument value to be created |
||||||
|
* @param webRequest the current request |
||||||
|
* @param methodParam the method argument |
||||||
|
* @param paramType the type of the argument value to be created |
||||||
|
* @return the created method argument value |
||||||
|
* @throws IOException if the reading from the request fails |
||||||
|
* @throws HttpMediaTypeNotSupportedException if no suitable message converter is found |
||||||
|
*/ |
||||||
|
protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter methodParam, Class<T> paramType) throws IOException, |
||||||
|
HttpMediaTypeNotSupportedException { |
||||||
|
|
||||||
|
HttpInputMessage inputMessage = createInputMessage(webRequest); |
||||||
|
return readWithMessageConverters(inputMessage, methodParam, paramType); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates the method argument value of the expected parameter type by reading from the given HttpInputMessage. |
||||||
|
* |
||||||
|
* @param <T> the expected type of the argument value to be created |
||||||
|
* @param inputMessage the HTTP input message representing the current request |
||||||
|
* @param methodParam the method argument |
||||||
|
* @param paramType the type of the argument value to be created |
||||||
|
* @return the created method argument value |
||||||
|
* @throws IOException if the reading from the request fails |
||||||
|
* @throws HttpMediaTypeNotSupportedException if no suitable message converter is found |
||||||
|
*/ |
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter methodParam, Class<T> paramType) throws IOException, |
||||||
|
HttpMediaTypeNotSupportedException { |
||||||
|
|
||||||
|
MediaType contentType = inputMessage.getHeaders().getContentType(); |
||||||
|
if (contentType == null) { |
||||||
|
contentType = MediaType.APPLICATION_OCTET_STREAM; |
||||||
|
} |
||||||
|
|
||||||
|
for (HttpMessageConverter<?> messageConverter : this.messageConverters) { |
||||||
|
if (messageConverter.canRead(paramType, contentType)) { |
||||||
|
if (logger.isDebugEnabled()) { |
||||||
|
logger.debug("Reading [" + paramType.getName() + "] as \"" + contentType + "\" using [" + |
||||||
|
messageConverter + "]"); |
||||||
|
} |
||||||
|
return ((HttpMessageConverter<T>) messageConverter).read(paramType, inputMessage); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
throw new HttpMediaTypeNotSupportedException(contentType, allSupportedMediaTypes); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a new {@link HttpInputMessage} from the given {@link NativeWebRequest}. |
||||||
|
* |
||||||
|
* @param webRequest the web request to create an input message from |
||||||
|
* @return the input message |
||||||
|
*/ |
||||||
|
protected ServletServerHttpRequest createInputMessage(NativeWebRequest webRequest) { |
||||||
|
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class); |
||||||
|
return new ServletServerHttpRequest(servletRequest); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,124 @@ |
|||||||
|
/* |
||||||
|
* 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.support; |
||||||
|
|
||||||
|
import java.lang.annotation.Annotation; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import javax.servlet.ServletRequest; |
||||||
|
|
||||||
|
import org.springframework.core.MethodParameter; |
||||||
|
import org.springframework.http.HttpInputMessage; |
||||||
|
import org.springframework.http.converter.HttpMessageConverter; |
||||||
|
import org.springframework.util.Assert; |
||||||
|
import org.springframework.validation.Errors; |
||||||
|
import org.springframework.validation.Validator; |
||||||
|
import org.springframework.web.bind.WebDataBinder; |
||||||
|
import org.springframework.web.bind.annotation.RequestPart; |
||||||
|
import org.springframework.web.bind.support.WebDataBinderFactory; |
||||||
|
import org.springframework.web.context.request.NativeWebRequest; |
||||||
|
import org.springframework.web.method.support.ModelAndViewContainer; |
||||||
|
import org.springframework.web.multipart.MultipartHttpServletRequest; |
||||||
|
import org.springframework.web.multipart.MultipartRequest; |
||||||
|
import org.springframework.web.multipart.RequestPartServletServerHttpRequest; |
||||||
|
import org.springframework.web.servlet.config.annotation.EnableWebMvc; |
||||||
|
import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver; |
||||||
|
import org.springframework.web.util.WebUtils; |
||||||
|
|
||||||
|
/** |
||||||
|
* Resolves method arguments annotated with @{@link RequestPart} expecting the request to be a |
||||||
|
* {@link MultipartHttpServletRequest} and binding the method argument to a specific part of the multipart request. |
||||||
|
* The name of the part is derived either from the {@link RequestPart} annotation or from the name of the method |
||||||
|
* argument as a fallback. |
||||||
|
* |
||||||
|
* <p>An @{@link RequestPart} method argument will be validated if annotated with {@code @Valid}. In case of |
||||||
|
* validation failure, a {@link RequestPartNotValidException} is thrown and can be handled automatically through |
||||||
|
* the {@link DefaultHandlerExceptionResolver}. A {@link Validator} can be configured globally in XML configuration |
||||||
|
* with the Spring MVC namespace or in Java-based configuration with @{@link EnableWebMvc}. |
||||||
|
* |
||||||
|
* @author Rossen Stoyanchev |
||||||
|
* @since 3.1 |
||||||
|
*/ |
||||||
|
public class RequestPartMethodArgumentResolver extends AbstractMessageConverterMethodArgumentResolver { |
||||||
|
|
||||||
|
public RequestPartMethodArgumentResolver(List<HttpMessageConverter<?>> messageConverters) { |
||||||
|
super(messageConverters); |
||||||
|
} |
||||||
|
|
||||||
|
public boolean supportsParameter(MethodParameter parameter) { |
||||||
|
return parameter.hasParameterAnnotation(RequestPart.class); |
||||||
|
} |
||||||
|
|
||||||
|
public Object resolveArgument(MethodParameter parameter, |
||||||
|
ModelAndViewContainer mavContainer, |
||||||
|
NativeWebRequest webRequest, |
||||||
|
WebDataBinderFactory binderFactory) throws Exception { |
||||||
|
|
||||||
|
ServletRequest servletRequest = webRequest.getNativeRequest(ServletRequest.class); |
||||||
|
MultipartHttpServletRequest multipartServletRequest = |
||||||
|
WebUtils.getNativeRequest(servletRequest, MultipartHttpServletRequest.class); |
||||||
|
if (multipartServletRequest == null) { |
||||||
|
throw new IllegalStateException( |
||||||
|
"Current request is not of type " + MultipartRequest.class.getName()); |
||||||
|
} |
||||||
|
|
||||||
|
String partName = getPartName(parameter); |
||||||
|
HttpInputMessage inputMessage = new RequestPartServletServerHttpRequest(multipartServletRequest, partName); |
||||||
|
|
||||||
|
Object arg = readWithMessageConverters(inputMessage, parameter, parameter.getParameterType()); |
||||||
|
|
||||||
|
if (isValidationApplicable(arg, parameter)) { |
||||||
|
WebDataBinder binder = binderFactory.createBinder(webRequest, arg, partName); |
||||||
|
binder.validate(); |
||||||
|
Errors errors = binder.getBindingResult(); |
||||||
|
if (errors.hasErrors()) { |
||||||
|
throw new RequestPartNotValidException(errors); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return arg; |
||||||
|
} |
||||||
|
|
||||||
|
private String getPartName(MethodParameter parameter) { |
||||||
|
RequestPart annot = parameter.getParameterAnnotation(RequestPart.class); |
||||||
|
String partName = annot.value(); |
||||||
|
if (partName.length() == 0) { |
||||||
|
partName = parameter.getParameterName(); |
||||||
|
Assert.notNull(partName, "Request part name for argument type [" + parameter.getParameterType().getName() |
||||||
|
+ "] not available, and parameter name information not found in class file either."); |
||||||
|
} |
||||||
|
return partName; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Whether to validate the given @{@link RequestPart} method argument. The default implementation checks |
||||||
|
* if the parameter is also annotated with {@code @Valid}. |
||||||
|
* @param argumentValue the validation candidate |
||||||
|
* @param parameter the method argument declaring the validation candidate |
||||||
|
* @return {@code true} if validation should be invoked, {@code false} otherwise. |
||||||
|
*/ |
||||||
|
protected boolean isValidationApplicable(Object argumentValue, MethodParameter parameter) { |
||||||
|
Annotation[] annotations = parameter.getParameterAnnotations(); |
||||||
|
for (Annotation annot : annotations) { |
||||||
|
if ("Valid".equals(annot.annotationType().getSimpleName())) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,61 @@ |
|||||||
|
/* |
||||||
|
* 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.support; |
||||||
|
|
||||||
|
import org.springframework.validation.Errors; |
||||||
|
import org.springframework.validation.ObjectError; |
||||||
|
import org.springframework.web.bind.annotation.RequestBody; |
||||||
|
import org.springframework.web.bind.annotation.RequestPart; |
||||||
|
|
||||||
|
/** |
||||||
|
* Thrown by {@link RequestPartMethodArgumentResolver} when an @{@link RequestPart} argument also annotated with |
||||||
|
* {@code @Valid} results in validation errors. |
||||||
|
* |
||||||
|
* @author Rossen Stoyanchev |
||||||
|
* @since 3.1 |
||||||
|
*/ |
||||||
|
@SuppressWarnings("serial") |
||||||
|
public class RequestPartNotValidException extends RuntimeException { |
||||||
|
|
||||||
|
private final Errors errors; |
||||||
|
|
||||||
|
/** |
||||||
|
* @param errors contains the results of validating an @{@link RequestBody} argument. |
||||||
|
*/ |
||||||
|
public RequestPartNotValidException(Errors errors) { |
||||||
|
this.errors = errors; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns an Errors instance with validation errors. |
||||||
|
*/ |
||||||
|
public Errors getErrors() { |
||||||
|
return errors; |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String getMessage() { |
||||||
|
StringBuilder sb = new StringBuilder( |
||||||
|
"Validation of the content of request part '" + errors.getObjectName() + "' failed: "); |
||||||
|
sb.append(errors.getErrorCount()).append(" errors"); |
||||||
|
for (ObjectError error : errors.getAllErrors()) { |
||||||
|
sb.append('\n').append(error); |
||||||
|
} |
||||||
|
return sb.toString(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,187 @@ |
|||||||
|
/* |
||||||
|
* 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.support; |
||||||
|
|
||||||
|
import static org.easymock.EasyMock.createMock; |
||||||
|
import static org.easymock.EasyMock.eq; |
||||||
|
import static org.easymock.EasyMock.expect; |
||||||
|
import static org.easymock.EasyMock.isA; |
||||||
|
import static org.easymock.EasyMock.replay; |
||||||
|
import static org.easymock.EasyMock.reset; |
||||||
|
import static org.easymock.EasyMock.verify; |
||||||
|
import static org.junit.Assert.assertEquals; |
||||||
|
import static org.junit.Assert.assertFalse; |
||||||
|
import static org.junit.Assert.assertNotNull; |
||||||
|
import static org.junit.Assert.assertTrue; |
||||||
|
import static org.junit.Assert.fail; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
import java.lang.reflect.Method; |
||||||
|
import java.util.Collections; |
||||||
|
|
||||||
|
import javax.validation.Valid; |
||||||
|
import javax.validation.constraints.NotNull; |
||||||
|
|
||||||
|
import org.junit.Before; |
||||||
|
import org.junit.Test; |
||||||
|
import org.springframework.core.LocalVariableTableParameterNameDiscoverer; |
||||||
|
import org.springframework.core.MethodParameter; |
||||||
|
import org.springframework.http.MediaType; |
||||||
|
import org.springframework.http.converter.HttpMessageConverter; |
||||||
|
import org.springframework.mock.web.MockHttpServletResponse; |
||||||
|
import org.springframework.mock.web.MockMultipartFile; |
||||||
|
import org.springframework.mock.web.MockMultipartHttpServletRequest; |
||||||
|
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; |
||||||
|
import org.springframework.web.bind.WebDataBinder; |
||||||
|
import org.springframework.web.bind.annotation.RequestPart; |
||||||
|
import org.springframework.web.bind.support.WebDataBinderFactory; |
||||||
|
import org.springframework.web.context.request.NativeWebRequest; |
||||||
|
import org.springframework.web.context.request.ServletWebRequest; |
||||||
|
import org.springframework.web.method.support.ModelAndViewContainer; |
||||||
|
import org.springframework.web.multipart.MultipartFile; |
||||||
|
import org.springframework.web.multipart.RequestPartServletServerHttpRequest; |
||||||
|
|
||||||
|
/** |
||||||
|
* Test fixture with {@link RequestPartMethodArgumentResolver} and mock {@link HttpMessageConverter}. |
||||||
|
* |
||||||
|
* @author Rossen Stoyanchev |
||||||
|
*/ |
||||||
|
public class RequestPartMethodArgumentResolverTests { |
||||||
|
|
||||||
|
private RequestPartMethodArgumentResolver resolver; |
||||||
|
|
||||||
|
private HttpMessageConverter<SimpleBean> messageConverter; |
||||||
|
|
||||||
|
private MultipartFile multipartFile; |
||||||
|
|
||||||
|
private MethodParameter paramRequestPart; |
||||||
|
private MethodParameter paramNamedRequestPart; |
||||||
|
private MethodParameter paramValidRequestPart; |
||||||
|
private MethodParameter paramInt; |
||||||
|
|
||||||
|
private NativeWebRequest webRequest; |
||||||
|
|
||||||
|
private MockMultipartHttpServletRequest servletRequest; |
||||||
|
|
||||||
|
private MockHttpServletResponse servletResponse; |
||||||
|
|
||||||
|
@SuppressWarnings("unchecked") |
||||||
|
@Before |
||||||
|
public void setUp() throws Exception { |
||||||
|
Method handle = getClass().getMethod("handle", SimpleBean.class, SimpleBean.class, SimpleBean.class, Integer.TYPE); |
||||||
|
paramRequestPart = new MethodParameter(handle, 0); |
||||||
|
paramRequestPart.initParameterNameDiscovery(new LocalVariableTableParameterNameDiscoverer()); |
||||||
|
paramNamedRequestPart = new MethodParameter(handle, 1); |
||||||
|
paramValidRequestPart = new MethodParameter(handle, 2); |
||||||
|
paramInt = new MethodParameter(handle, 3); |
||||||
|
|
||||||
|
messageConverter = createMock(HttpMessageConverter.class); |
||||||
|
expect(messageConverter.getSupportedMediaTypes()).andReturn(Collections.singletonList(MediaType.TEXT_PLAIN)); |
||||||
|
replay(messageConverter); |
||||||
|
|
||||||
|
resolver = new RequestPartMethodArgumentResolver(Collections.<HttpMessageConverter<?>>singletonList(messageConverter)); |
||||||
|
reset(messageConverter); |
||||||
|
|
||||||
|
multipartFile = new MockMultipartFile("requestPart", "", "text/plain", (byte[]) null); |
||||||
|
servletRequest = new MockMultipartHttpServletRequest(); |
||||||
|
servletRequest.addFile(multipartFile); |
||||||
|
servletResponse = new MockHttpServletResponse(); |
||||||
|
webRequest = new ServletWebRequest(servletRequest, servletResponse); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void supportsParameter() { |
||||||
|
assertTrue("RequestPart parameter not supported", resolver.supportsParameter(paramRequestPart)); |
||||||
|
assertFalse("non-RequestPart parameter supported", resolver.supportsParameter(paramInt)); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void resolveRequestPart() throws Exception { |
||||||
|
testResolveArgument(new SimpleBean("foo"), paramRequestPart); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void resolveNamedRequestPart() throws Exception { |
||||||
|
testResolveArgument(new SimpleBean("foo"), paramNamedRequestPart); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void resolveRequestPartNotValid() throws Exception { |
||||||
|
try { |
||||||
|
testResolveArgument(new SimpleBean(null), paramValidRequestPart); |
||||||
|
fail("Expected exception"); |
||||||
|
} catch (RequestPartNotValidException e) { |
||||||
|
assertEquals("requestPart", e.getErrors().getObjectName()); |
||||||
|
assertEquals(1, e.getErrors().getErrorCount()); |
||||||
|
assertNotNull(e.getErrors().getFieldError("name")); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void resolveRequestPartValid() throws Exception { |
||||||
|
testResolveArgument(new SimpleBean("foo"), paramValidRequestPart); |
||||||
|
} |
||||||
|
|
||||||
|
private void testResolveArgument(SimpleBean expectedValue, MethodParameter parameter) throws IOException, Exception { |
||||||
|
MediaType contentType = MediaType.TEXT_PLAIN; |
||||||
|
servletRequest.addHeader("Content-Type", contentType.toString()); |
||||||
|
|
||||||
|
expect(messageConverter.canRead(SimpleBean.class, contentType)).andReturn(true); |
||||||
|
expect(messageConverter.read(eq(SimpleBean.class), isA(RequestPartServletServerHttpRequest.class))).andReturn(expectedValue); |
||||||
|
replay(messageConverter); |
||||||
|
|
||||||
|
ModelAndViewContainer mavContainer = new ModelAndViewContainer(); |
||||||
|
Object actualValue = resolver.resolveArgument(parameter, mavContainer, webRequest, new ValidatingBinderFactory()); |
||||||
|
|
||||||
|
assertEquals("Invalid argument value", expectedValue, actualValue); |
||||||
|
assertTrue("The ResolveView flag shouldn't change", mavContainer.isResolveView()); |
||||||
|
|
||||||
|
verify(messageConverter); |
||||||
|
} |
||||||
|
|
||||||
|
public void handle(@RequestPart SimpleBean requestPart, |
||||||
|
@RequestPart("requestPart") SimpleBean namedRequestPart, |
||||||
|
@Valid @RequestPart("requestPart") SimpleBean validRequestPart, |
||||||
|
int i) { |
||||||
|
} |
||||||
|
|
||||||
|
private static class SimpleBean { |
||||||
|
|
||||||
|
@NotNull |
||||||
|
private final String name; |
||||||
|
|
||||||
|
public SimpleBean(String name) { |
||||||
|
this.name = name; |
||||||
|
} |
||||||
|
|
||||||
|
@SuppressWarnings("unused") |
||||||
|
public String getName() { |
||||||
|
return name; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private final class ValidatingBinderFactory implements WebDataBinderFactory { |
||||||
|
public WebDataBinder createBinder(NativeWebRequest webRequest, Object target, String objectName) throws Exception { |
||||||
|
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean(); |
||||||
|
validator.afterPropertiesSet(); |
||||||
|
WebDataBinder dataBinder = new WebDataBinder(target, objectName); |
||||||
|
dataBinder.setValidator(validator); |
||||||
|
return dataBinder; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,44 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2009 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.bind.annotation; |
||||||
|
|
||||||
|
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; |
||||||
|
|
||||||
|
/** |
||||||
|
* 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. |
||||||
|
* |
||||||
|
* @author Rossen Stoyanchev |
||||||
|
* @author Arjen Poutsma |
||||||
|
* @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter |
||||||
|
* @since 3.1 |
||||||
|
*/ |
||||||
|
@Target(ElementType.PARAMETER) |
||||||
|
@Retention(RetentionPolicy.RUNTIME) |
||||||
|
@Documented |
||||||
|
public @interface RequestPart { |
||||||
|
|
||||||
|
/** |
||||||
|
* The name of the part in the "multipart/form-data" request to bind to. |
||||||
|
*/ |
||||||
|
String value() default ""; |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,98 @@ |
|||||||
|
/* |
||||||
|
* 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.multipart; |
||||||
|
|
||||||
|
import java.io.IOException; |
||||||
|
import java.io.InputStream; |
||||||
|
import java.net.URI; |
||||||
|
import java.net.URISyntaxException; |
||||||
|
import java.util.Iterator; |
||||||
|
|
||||||
|
import org.springframework.http.HttpHeaders; |
||||||
|
import org.springframework.http.HttpMethod; |
||||||
|
import org.springframework.http.server.ServerHttpRequest; |
||||||
|
import org.springframework.util.Assert; |
||||||
|
|
||||||
|
/** |
||||||
|
* {@link ServerHttpRequest} implementation that is based on a part of a {@link MultipartHttpServletRequest}. |
||||||
|
* The part is accessed as {@link MultipartFile} and adapted to the ServerHttpRequest contract. |
||||||
|
* |
||||||
|
* @author Rossen Stoyanchev |
||||||
|
* @since 3.1 |
||||||
|
*/ |
||||||
|
public class RequestPartServletServerHttpRequest implements ServerHttpRequest { |
||||||
|
|
||||||
|
private final MultipartHttpServletRequest request; |
||||||
|
|
||||||
|
private final MultipartFile multipartFile; |
||||||
|
|
||||||
|
private HttpHeaders headers; |
||||||
|
|
||||||
|
/** |
||||||
|
* Creates a new {@link RequestPartServletServerHttpRequest} instance. |
||||||
|
* |
||||||
|
* @param request the multipart request. |
||||||
|
* @param name the name of the part to adapt to the {@link ServerHttpRequest} contract. |
||||||
|
*/ |
||||||
|
public RequestPartServletServerHttpRequest(MultipartHttpServletRequest request, String name) { |
||||||
|
this.request = request; |
||||||
|
this.multipartFile = request.getFile(name); |
||||||
|
Assert.notNull(multipartFile, "Request part named '" + name + "' not found. " + |
||||||
|
"Available request part names: " + request.getMultiFileMap().keySet()); |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
public HttpMethod getMethod() { |
||||||
|
return HttpMethod.valueOf(this.request.getMethod()); |
||||||
|
} |
||||||
|
|
||||||
|
public URI getURI() { |
||||||
|
try { |
||||||
|
return new URI(this.request.getScheme(), null, this.request.getServerName(), |
||||||
|
this.request.getServerPort(), this.request.getRequestURI(), |
||||||
|
this.request.getQueryString(), null); |
||||||
|
} |
||||||
|
catch (URISyntaxException ex) { |
||||||
|
throw new IllegalStateException("Could not get HttpServletRequest URI: " + ex.getMessage(), ex); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Returns the headers associated with the part of the multi-part request associated with this instance. |
||||||
|
* If the underlying implementation supports access to headers, then all headers are returned. |
||||||
|
* Otherwise, the returned headers will have a 'Content-Type' header in the very least. |
||||||
|
*/ |
||||||
|
public HttpHeaders getHeaders() { |
||||||
|
if (this.headers == null) { |
||||||
|
this.headers = new HttpHeaders(); |
||||||
|
Iterator<String> iterator = this.multipartFile.getHeaderNames(); |
||||||
|
while (iterator.hasNext()) { |
||||||
|
String name = iterator.next(); |
||||||
|
String[] values = this.multipartFile.getHeaders(name); |
||||||
|
for (String value : values) { |
||||||
|
this.headers.add(name, value); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return this.headers; |
||||||
|
} |
||||||
|
|
||||||
|
public InputStream getBody() throws IOException { |
||||||
|
return this.multipartFile.getInputStream(); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,87 @@ |
|||||||
|
/* |
||||||
|
* 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.multipart; |
||||||
|
|
||||||
|
import static org.junit.Assert.assertArrayEquals; |
||||||
|
import static org.junit.Assert.assertEquals; |
||||||
|
import static org.junit.Assert.assertNotNull; |
||||||
|
|
||||||
|
import java.net.URI; |
||||||
|
|
||||||
|
import org.junit.Before; |
||||||
|
import org.junit.Test; |
||||||
|
import org.springframework.http.HttpHeaders; |
||||||
|
import org.springframework.http.HttpMethod; |
||||||
|
import org.springframework.http.MediaType; |
||||||
|
import org.springframework.mock.web.MockMultipartFile; |
||||||
|
import org.springframework.mock.web.MockMultipartHttpServletRequest; |
||||||
|
import org.springframework.util.FileCopyUtils; |
||||||
|
|
||||||
|
/** |
||||||
|
* Test fixture for {@link RequestPartServletServerHttpRequest} unit tests. |
||||||
|
* |
||||||
|
* @author Rossen Stoyanchev |
||||||
|
*/ |
||||||
|
public class RequestPartServletServerHttpRequestTests { |
||||||
|
|
||||||
|
private RequestPartServletServerHttpRequest request; |
||||||
|
|
||||||
|
private MockMultipartHttpServletRequest mockRequest; |
||||||
|
|
||||||
|
private MockMultipartFile mockFile; |
||||||
|
|
||||||
|
@Before |
||||||
|
public void create() throws Exception { |
||||||
|
mockFile = new MockMultipartFile("part", "", "application/json" ,"Part Content".getBytes("UTF-8")); |
||||||
|
mockRequest = new MockMultipartHttpServletRequest(); |
||||||
|
mockRequest.addFile(mockFile); |
||||||
|
request = new RequestPartServletServerHttpRequest(mockRequest, "part"); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void getMethod() throws Exception { |
||||||
|
mockRequest.setMethod("POST"); |
||||||
|
assertEquals("Invalid method", HttpMethod.POST, request.getMethod()); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void getURI() throws Exception { |
||||||
|
URI uri = new URI("http://example.com/path?query"); |
||||||
|
mockRequest.setServerName(uri.getHost()); |
||||||
|
mockRequest.setServerPort(uri.getPort()); |
||||||
|
mockRequest.setRequestURI(uri.getPath()); |
||||||
|
mockRequest.setQueryString(uri.getQuery()); |
||||||
|
assertEquals("Invalid uri", uri, request.getURI()); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void getContentType() throws Exception { |
||||||
|
HttpHeaders headers = request.getHeaders(); |
||||||
|
assertNotNull("No HttpHeaders returned", headers); |
||||||
|
|
||||||
|
MediaType expected = MediaType.parseMediaType(mockFile.getContentType()); |
||||||
|
MediaType actual = headers.getContentType(); |
||||||
|
assertEquals("Invalid content type returned", expected, actual); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void getBody() throws Exception { |
||||||
|
byte[] result = FileCopyUtils.copyToByteArray(request.getBody()); |
||||||
|
assertArrayEquals("Invalid content returned", mockFile.getBytes(), result); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
Loading…
Reference in new issue