-
Notifications
You must be signed in to change notification settings - Fork 37.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
SPR-8483 Add support for @RequestPart annotated method parameters
- Loading branch information
1 parent
3bbefb3
commit 3a87d8e
Showing
23 changed files
with
912 additions
and
114 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
137 changes: 137 additions & 0 deletions
137
...servlet/mvc/method/annotation/support/AbstractMessageConverterMethodArgumentResolver.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
124 changes: 124 additions & 0 deletions
124
...ramework/web/servlet/mvc/method/annotation/support/RequestPartMethodArgumentResolver.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
|
||
} |
Oops, something went wrong.