23 changed files with 912 additions and 114 deletions
@ -0,0 +1,137 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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