SPR-5409 - Support for PUTting and POSTing non-form data
This commit is contained in:
@@ -0,0 +1,23 @@
|
||||
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 which indicates that a method parameter should be bound to the web request body. Supported for annotated
|
||||
* handler methods in Servlet environments.
|
||||
*
|
||||
* @author Arjen Poutsma
|
||||
* @see RequestHeader
|
||||
* @see org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter
|
||||
* @since 3.0
|
||||
*/
|
||||
@Target(ElementType.PARAMETER)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
public @interface RequestBody {
|
||||
|
||||
}
|
||||
@@ -19,9 +19,11 @@ package org.springframework.web.bind.annotation.support;
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -35,6 +37,9 @@ import org.springframework.core.GenericTypeResolver;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ParameterNameDiscoverer;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.ui.ExtendedModelMap;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.util.ClassUtils;
|
||||
@@ -42,11 +47,13 @@ import org.springframework.util.ReflectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.validation.BindingResult;
|
||||
import org.springframework.validation.Errors;
|
||||
import org.springframework.web.HttpMediaTypeNotSupportedException;
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
import org.springframework.web.bind.annotation.CookieValue;
|
||||
import org.springframework.web.bind.annotation.InitBinder;
|
||||
import org.springframework.web.bind.annotation.ModelAttribute;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.support.DefaultSessionAttributeStore;
|
||||
@@ -89,24 +96,28 @@ public class HandlerMethodInvoker {
|
||||
|
||||
private final SimpleSessionStatus sessionStatus = new SimpleSessionStatus();
|
||||
|
||||
private final HttpMessageConverter[] messageConverters;
|
||||
|
||||
|
||||
public HandlerMethodInvoker(HandlerMethodResolver methodResolver) {
|
||||
this(methodResolver, null);
|
||||
}
|
||||
|
||||
public HandlerMethodInvoker(HandlerMethodResolver methodResolver, WebBindingInitializer bindingInitializer) {
|
||||
this(methodResolver, bindingInitializer, new DefaultSessionAttributeStore(), null);
|
||||
this(methodResolver, bindingInitializer, new DefaultSessionAttributeStore(), null, new WebArgumentResolver[0],
|
||||
new HttpMessageConverter[0]);
|
||||
}
|
||||
|
||||
public HandlerMethodInvoker(HandlerMethodResolver methodResolver, WebBindingInitializer bindingInitializer,
|
||||
SessionAttributeStore sessionAttributeStore, ParameterNameDiscoverer parameterNameDiscoverer,
|
||||
WebArgumentResolver... customArgumentResolvers) {
|
||||
WebArgumentResolver[] customArgumentResolvers, HttpMessageConverter[] messageConverters) {
|
||||
|
||||
this.methodResolver = methodResolver;
|
||||
this.bindingInitializer = bindingInitializer;
|
||||
this.sessionAttributeStore = sessionAttributeStore;
|
||||
this.parameterNameDiscoverer = parameterNameDiscoverer;
|
||||
this.customArgumentResolvers = customArgumentResolvers;
|
||||
this.messageConverters = messageConverters;
|
||||
}
|
||||
|
||||
|
||||
@@ -159,6 +170,7 @@ public class HandlerMethodInvoker {
|
||||
GenericTypeResolver.resolveParameterType(methodParam, handler.getClass());
|
||||
String paramName = null;
|
||||
String headerName = null;
|
||||
boolean requestBodyFound = false;
|
||||
String cookieName = null;
|
||||
String pathVarName = null;
|
||||
String attrName = null;
|
||||
@@ -182,6 +194,10 @@ public class HandlerMethodInvoker {
|
||||
defaultValue = requestHeader.defaultValue();
|
||||
found++;
|
||||
}
|
||||
else if (RequestBody.class.isInstance(paramAnn)) {
|
||||
requestBodyFound = true;
|
||||
found++;
|
||||
}
|
||||
else if (CookieValue.class.isInstance(paramAnn)) {
|
||||
CookieValue cookieValue = (CookieValue) paramAnn;
|
||||
cookieName = cookieValue.value();
|
||||
@@ -238,6 +254,9 @@ public class HandlerMethodInvoker {
|
||||
else if (headerName != null) {
|
||||
args[i] = resolveRequestHeader(headerName, required, defaultValue, methodParam, webRequest, handler);
|
||||
}
|
||||
else if (requestBodyFound) {
|
||||
args[i] = resolveRequestBody(methodParam, webRequest, handler);
|
||||
}
|
||||
else if (cookieName != null) {
|
||||
args[i] = resolveCookieValue(cookieName, required, defaultValue, methodParam, webRequest, handler);
|
||||
}
|
||||
@@ -418,6 +437,45 @@ public class HandlerMethodInvoker {
|
||||
return binder.convertIfNecessary(headerValue, paramType, methodParam);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the given {@link RequestBody @RequestBody} annotation.
|
||||
* Throws an UnsupportedOperationException by default.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Object resolveRequestBody(MethodParameter methodParam, NativeWebRequest webRequest, Object handler)
|
||||
throws Exception {
|
||||
|
||||
HttpInputMessage inputMessage = createHttpInputMessage(webRequest);
|
||||
|
||||
Class paramType = methodParam.getParameterType();
|
||||
MediaType contentType = inputMessage.getHeaders().getContentType();
|
||||
if (contentType == null) {
|
||||
throw new IllegalStateException("Cannot extract response: no Content-Type found");
|
||||
}
|
||||
List<MediaType> allSupportedMediaTypes = new ArrayList<MediaType>();
|
||||
for (HttpMessageConverter<?> messageConverter : messageConverters) {
|
||||
allSupportedMediaTypes.addAll(messageConverter.getSupportedMediaTypes());
|
||||
if (messageConverter.supports(paramType)) {
|
||||
for (MediaType supportedMediaType : messageConverter.getSupportedMediaTypes()) {
|
||||
if (supportedMediaType.includes(contentType)) {
|
||||
return messageConverter.read(paramType, inputMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw new HttpMediaTypeNotSupportedException(contentType, allSupportedMediaTypes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link HttpInputMessage} for the given {@link NativeWebRequest}.
|
||||
* Throws an UnsupportedOperationException by default.
|
||||
*/
|
||||
protected HttpInputMessage createHttpInputMessage(NativeWebRequest webRequest) throws Exception {
|
||||
|
||||
throw new UnsupportedOperationException("@RequestBody not supported");
|
||||
}
|
||||
|
||||
private Object resolveCookieValue(String cookieName, boolean required, String defaultValue,
|
||||
MethodParameter methodParam, NativeWebRequest webRequest, Object handlerForInitBinderCall)
|
||||
throws Exception {
|
||||
|
||||
@@ -51,6 +51,14 @@ import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ParameterNameDiscoverer;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
|
||||
import org.springframework.http.converter.FormHttpMessageConverter;
|
||||
import org.springframework.http.converter.HttpMessageConverter;
|
||||
import org.springframework.http.converter.StringHttpMessageConverter;
|
||||
import org.springframework.http.converter.xml.SourceHttpMessageConverter;
|
||||
import org.springframework.http.server.ServletServerHttpRequest;
|
||||
import org.springframework.ui.ExtendedModelMap;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
@@ -60,6 +68,7 @@ import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.PathMatcher;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.validation.support.BindingAwareModelMap;
|
||||
import org.springframework.web.HttpMediaTypeNotSupportedException;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.web.HttpSessionRequiredException;
|
||||
import org.springframework.web.bind.MissingServletRequestParameterException;
|
||||
@@ -149,6 +158,9 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
|
||||
private final Map<Class<?>, ServletHandlerMethodResolver> methodResolverCache =
|
||||
new ConcurrentHashMap<Class<?>, ServletHandlerMethodResolver>();
|
||||
|
||||
private HttpMessageConverter<?>[] messageConverters =
|
||||
new HttpMessageConverter[]{new ByteArrayHttpMessageConverter(), new StringHttpMessageConverter(),
|
||||
new FormHttpMessageConverter(), new SourceHttpMessageConverter()};
|
||||
|
||||
public AnnotationMethodHandlerAdapter() {
|
||||
// no restriction of HTTP methods by default
|
||||
@@ -291,6 +303,16 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
|
||||
this.customArgumentResolvers = argumentResolvers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the message body converters to use. These converters are used to convert
|
||||
* from and to HTTP requests and responses.
|
||||
*/
|
||||
public void setMessageConverters(HttpMessageConverter<?>[] messageConverters) {
|
||||
Assert.notEmpty(messageConverters, "'messageConverters' must not be empty");
|
||||
this.messageConverters = messageConverters;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public boolean supports(Object handler) {
|
||||
return getMethodResolver(handler).hasHandlerMethods();
|
||||
@@ -346,13 +368,15 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
|
||||
catch (HttpRequestMethodNotSupportedException ex) {
|
||||
return handleHttpRequestMethodNotSupportedException(ex, request, response);
|
||||
}
|
||||
catch (HttpMediaTypeNotSupportedException ex) {
|
||||
return handleHttpMediaTypeNotSupportedException(ex, request, response);
|
||||
}
|
||||
}
|
||||
|
||||
public long getLastModified(HttpServletRequest request, Object handler) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Handle the case where no request handler method was found.
|
||||
* <p>The default implementation logs a warning and sends an HTTP 404 error.
|
||||
@@ -394,6 +418,27 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the case where no {@linkplain HttpMessageConverter message converters} was found for the PUT or POSTed
|
||||
* content.
|
||||
* <p>The default implementation logs a warning, sends an HTTP 415 error and sets the "Allow" header.
|
||||
* Alternatively, a fallback view could be chosen, or the HttpMediaTypeNotSupportedException
|
||||
* could be rethrown as-is.
|
||||
* @param ex the HttpMediaTypeNotSupportedException to be handled
|
||||
* @param request current HTTP request
|
||||
* @param response current HTTP response
|
||||
* @return a ModelAndView to render, or <code>null</code> if handled directly
|
||||
* @throws Exception an Exception that should be thrown as result of the servlet request
|
||||
*/
|
||||
protected ModelAndView handleHttpMediaTypeNotSupportedException(
|
||||
HttpMediaTypeNotSupportedException ex, HttpServletRequest request, HttpServletResponse response)
|
||||
throws Exception {
|
||||
|
||||
response.sendError(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);
|
||||
response.addHeader("Accept", MediaType.toString(ex.getSupportedMediaTypes()));
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Template method for creating a new ServletRequestDataBinder instance.
|
||||
* <p>The default implementation creates a standard ServletRequestDataBinder.
|
||||
@@ -593,7 +638,7 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
|
||||
|
||||
private ServletHandlerMethodInvoker(HandlerMethodResolver resolver) {
|
||||
super(resolver, webBindingInitializer, sessionAttributeStore,
|
||||
parameterNameDiscoverer, customArgumentResolvers);
|
||||
parameterNameDiscoverer, customArgumentResolvers, messageConverters);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -625,6 +670,12 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator implemen
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected HttpInputMessage createHttpInputMessage(NativeWebRequest webRequest) throws Exception {
|
||||
HttpServletRequest servletRequest = (HttpServletRequest) webRequest.getNativeRequest();
|
||||
return new ServletServerHttpRequest(servletRequest);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object resolveCookieValue(String cookieName, Class paramType, NativeWebRequest webRequest)
|
||||
throws Exception {
|
||||
|
||||
Reference in New Issue
Block a user