SPR-7018 - Support for HttpEntity<?> in @MVC
This commit is contained in:
parent
69cbadf7e9
commit
b07d02f1bf
|
|
@ -55,6 +55,8 @@ import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
|
|||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.ParameterNameDiscoverer;
|
||||
import org.springframework.core.annotation.AnnotationUtils;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.HttpOutputMessage;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
|
@ -797,6 +799,10 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator
|
|||
handleResponseBody(returnValue, webRequest);
|
||||
return null;
|
||||
}
|
||||
if (returnValue instanceof HttpEntity) {
|
||||
handleHttpEntityResponse((HttpEntity<?>) returnValue, webRequest);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (returnValue instanceof ModelAndView) {
|
||||
ModelAndView mav = (ModelAndView) returnValue;
|
||||
|
|
@ -839,7 +845,6 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void handleResponseBody(Object returnValue, ServletWebRequest webRequest)
|
||||
throws ServletException, IOException {
|
||||
if (returnValue == null) {
|
||||
|
|
@ -847,12 +852,35 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator
|
|||
}
|
||||
|
||||
HttpInputMessage inputMessage = new ServletServerHttpRequest(webRequest.getRequest());
|
||||
HttpOutputMessage outputMessage = new ServletServerHttpResponse(webRequest.getResponse());
|
||||
|
||||
writeWithMessageConverters(returnValue, inputMessage, outputMessage);
|
||||
}
|
||||
|
||||
private void handleHttpEntityResponse(HttpEntity<?> responseEntity, ServletWebRequest webRequest)
|
||||
throws ServletException, IOException {
|
||||
if (responseEntity == null) {
|
||||
return;
|
||||
}
|
||||
HttpInputMessage inputMessage = new ServletServerHttpRequest(webRequest.getRequest());
|
||||
HttpOutputMessage outputMessage = new ServletServerHttpResponse(webRequest.getResponse());
|
||||
|
||||
HttpHeaders entityHeaders = responseEntity.getHeaders();
|
||||
if (!entityHeaders.isEmpty()) {
|
||||
outputMessage.getHeaders().putAll(entityHeaders);
|
||||
}
|
||||
writeWithMessageConverters(responseEntity.getBody(), inputMessage, outputMessage);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void writeWithMessageConverters(Object returnValue,
|
||||
HttpInputMessage inputMessage, HttpOutputMessage outputMessage)
|
||||
throws IOException, HttpMediaTypeNotAcceptableException {
|
||||
List<MediaType> acceptedMediaTypes = inputMessage.getHeaders().getAccept();
|
||||
if (acceptedMediaTypes.isEmpty()) {
|
||||
acceptedMediaTypes = Collections.singletonList(MediaType.ALL);
|
||||
}
|
||||
MediaType.sortBySpecificity(acceptedMediaTypes);
|
||||
HttpOutputMessage outputMessage = new ServletServerHttpResponse(webRequest.getResponse());
|
||||
Class<?> returnValueType = returnValue.getClass();
|
||||
List<MediaType> allSupportedMediaTypes = new ArrayList<MediaType>();
|
||||
if (getMessageConverters() != null) {
|
||||
|
|
@ -879,6 +907,7 @@ public class AnnotationMethodHandlerAdapter extends WebContentGenerator
|
|||
}
|
||||
throw new HttpMediaTypeNotAcceptableException(allSupportedMediaTypes);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package org.springframework.web.servlet.mvc.annotation;
|
|||
|
||||
import java.io.IOException;
|
||||
import java.io.Serializable;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.io.Writer;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
|
|
@ -64,6 +65,7 @@ import org.springframework.beans.factory.support.RootBeanDefinition;
|
|||
import org.springframework.beans.propertyeditors.CustomDateEditor;
|
||||
import org.springframework.context.annotation.AnnotationConfigUtils;
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.HttpOutputMessage;
|
||||
|
|
@ -1155,6 +1157,24 @@ public class ServletAnnotationControllerTests {
|
|||
assertEquals("Invalid response status code", HttpServletResponse.SC_BAD_REQUEST, response.getStatus());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void httpEntity() throws ServletException, IOException {
|
||||
initServlet(HttpEntityController.class);
|
||||
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("PUT", "/handle");
|
||||
String requestBody = "Hello World";
|
||||
request.setContent(requestBody.getBytes("UTF-8"));
|
||||
request.addHeader("Content-Type", "text/plain; charset=utf-8");
|
||||
request.addHeader("Accept", "text/*, */*");
|
||||
request.addHeader("MyRequestHeader", "MyValue");
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
servlet.service(request, response);
|
||||
assertEquals(200, response.getStatus());
|
||||
assertEquals(requestBody, response.getContentAsString());
|
||||
assertEquals("MyValue", response.getHeader("MyResponseHeader"));
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* See SPR-6877
|
||||
*/
|
||||
|
|
@ -2502,5 +2522,21 @@ public class ServletAnnotationControllerTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Controller
|
||||
public static class HttpEntityController {
|
||||
|
||||
@RequestMapping("/handle")
|
||||
public HttpEntity<String> handle(HttpEntity<byte[]> requestEntity) throws UnsupportedEncodingException {
|
||||
assertNotNull(requestEntity);
|
||||
assertEquals("MyValue", requestEntity.getHeaders().getFirst("MyRequestHeader"));
|
||||
String requestBody = new String(requestEntity.getBody(), "UTF-8");
|
||||
assertEquals("Hello World", requestBody);
|
||||
|
||||
HttpHeaders responseHeaders = new HttpHeaders();
|
||||
responseHeaders.set("MyResponseHeader", "MyValue");
|
||||
return new HttpEntity<String>(requestBody, responseHeaders);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,8 +17,12 @@
|
|||
package org.springframework.web.bind.annotation.support;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.GenericArrayType;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
|
@ -40,12 +44,14 @@ 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.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
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.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
|
@ -192,7 +198,7 @@ public class HandlerMethodInvoker {
|
|||
boolean required = false;
|
||||
String defaultValue = null;
|
||||
boolean validate = false;
|
||||
int found = 0;
|
||||
int annotationsFound = 0;
|
||||
Annotation[] paramAnns = methodParam.getParameterAnnotations();
|
||||
|
||||
for (Annotation paramAnn : paramAnns) {
|
||||
|
|
@ -201,35 +207,35 @@ public class HandlerMethodInvoker {
|
|||
paramName = requestParam.value();
|
||||
required = requestParam.required();
|
||||
defaultValue = parseDefaultValueAttribute(requestParam.defaultValue());
|
||||
found++;
|
||||
annotationsFound++;
|
||||
}
|
||||
else if (RequestHeader.class.isInstance(paramAnn)) {
|
||||
RequestHeader requestHeader = (RequestHeader) paramAnn;
|
||||
headerName = requestHeader.value();
|
||||
required = requestHeader.required();
|
||||
defaultValue = parseDefaultValueAttribute(requestHeader.defaultValue());
|
||||
found++;
|
||||
annotationsFound++;
|
||||
}
|
||||
else if (RequestBody.class.isInstance(paramAnn)) {
|
||||
requestBodyFound = true;
|
||||
found++;
|
||||
annotationsFound++;
|
||||
}
|
||||
else if (CookieValue.class.isInstance(paramAnn)) {
|
||||
CookieValue cookieValue = (CookieValue) paramAnn;
|
||||
cookieName = cookieValue.value();
|
||||
required = cookieValue.required();
|
||||
defaultValue = parseDefaultValueAttribute(cookieValue.defaultValue());
|
||||
found++;
|
||||
annotationsFound++;
|
||||
}
|
||||
else if (PathVariable.class.isInstance(paramAnn)) {
|
||||
PathVariable pathVar = (PathVariable) paramAnn;
|
||||
pathVarName = pathVar.value();
|
||||
found++;
|
||||
annotationsFound++;
|
||||
}
|
||||
else if (ModelAttribute.class.isInstance(paramAnn)) {
|
||||
ModelAttribute attr = (ModelAttribute) paramAnn;
|
||||
attrName = attr.value();
|
||||
found++;
|
||||
annotationsFound++;
|
||||
}
|
||||
else if (Value.class.isInstance(paramAnn)) {
|
||||
defaultValue = ((Value) paramAnn).value();
|
||||
|
|
@ -239,12 +245,12 @@ public class HandlerMethodInvoker {
|
|||
}
|
||||
}
|
||||
|
||||
if (found > 1) {
|
||||
if (annotationsFound > 1) {
|
||||
throw new IllegalStateException("Handler parameter annotations are exclusive choices - " +
|
||||
"do not specify more than one such annotation on the same parameter: " + handlerMethod);
|
||||
}
|
||||
|
||||
if (found == 0) {
|
||||
if (annotationsFound == 0) {
|
||||
Object argValue = resolveCommonArgument(methodParam, webRequest);
|
||||
if (argValue != WebArgumentResolver.UNRESOLVED) {
|
||||
args[i] = argValue;
|
||||
|
|
@ -260,6 +266,9 @@ public class HandlerMethodInvoker {
|
|||
else if (SessionStatus.class.isAssignableFrom(paramType)) {
|
||||
args[i] = this.sessionStatus;
|
||||
}
|
||||
else if (HttpEntity.class.isAssignableFrom(paramType)) {
|
||||
args[i] = resolveHttpEntityRequest(methodParam, webRequest, handler);
|
||||
}
|
||||
else if (Errors.class.isAssignableFrom(paramType)) {
|
||||
throw new IllegalStateException("Errors/BindingResult argument declared " +
|
||||
"without preceding model attribute. Check your handler method signature!");
|
||||
|
|
@ -527,12 +536,22 @@ public class HandlerMethodInvoker {
|
|||
/**
|
||||
* Resolves the given {@link RequestBody @RequestBody} annotation.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
protected Object resolveRequestBody(MethodParameter methodParam, NativeWebRequest webRequest, Object handler)
|
||||
throws Exception {
|
||||
return readWithMessageConverters(methodParam, createHttpInputMessage(webRequest), methodParam.getParameterType());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private HttpEntity resolveHttpEntityRequest(MethodParameter methodParam, NativeWebRequest webRequest, Object handler)
|
||||
throws Exception {
|
||||
HttpInputMessage inputMessage = createHttpInputMessage(webRequest);
|
||||
Class paramType = methodParam.getParameterType();
|
||||
Class<?> paramType = getHttpEntityType(methodParam);
|
||||
Object body = readWithMessageConverters(methodParam, inputMessage, paramType);
|
||||
return new HttpEntity(body, inputMessage.getHeaders());
|
||||
}
|
||||
|
||||
private Object readWithMessageConverters(MethodParameter methodParam, HttpInputMessage inputMessage, Class paramType)
|
||||
throws Exception{
|
||||
MediaType contentType = inputMessage.getHeaders().getContentType();
|
||||
if (contentType == null) {
|
||||
StringBuilder builder = new StringBuilder(ClassUtils.getShortName(methodParam.getParameterType()));
|
||||
|
|
@ -542,8 +561,9 @@ public class HandlerMethodInvoker {
|
|||
builder.append(paramName);
|
||||
}
|
||||
throw new HttpMediaTypeNotSupportedException(
|
||||
"Cannot extract @RequestBody parameter (" + builder.toString() + "): no Content-Type found");
|
||||
"Cannot extract parameter (" + builder.toString() + "): no Content-Type found");
|
||||
}
|
||||
|
||||
List<MediaType> allSupportedMediaTypes = new ArrayList<MediaType>();
|
||||
if (this.messageConverters != null) {
|
||||
for (HttpMessageConverter<?> messageConverter : this.messageConverters) {
|
||||
|
|
@ -560,6 +580,28 @@ public class HandlerMethodInvoker {
|
|||
throw new HttpMediaTypeNotSupportedException(contentType, allSupportedMediaTypes);
|
||||
}
|
||||
|
||||
private Class<?> getHttpEntityType(MethodParameter methodParam) {
|
||||
Assert.isAssignable(HttpEntity.class, methodParam.getParameterType());
|
||||
ParameterizedType type = (ParameterizedType) methodParam.getGenericParameterType();
|
||||
if (type.getActualTypeArguments().length == 1) {
|
||||
Type typeArgument = type.getActualTypeArguments()[0];
|
||||
if (typeArgument instanceof Class) {
|
||||
return (Class<?>) typeArgument;
|
||||
}
|
||||
else if (typeArgument instanceof GenericArrayType) {
|
||||
Type componentType = ((GenericArrayType) typeArgument).getGenericComponentType();
|
||||
if (componentType instanceof Class) {
|
||||
// Surely, there should be a nicer way to do this
|
||||
Object array = Array.newInstance((Class<?>) componentType, 0);
|
||||
return array.getClass();
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException(
|
||||
"HttpEntity parameter (" + methodParam.getParameterName() + ") is not parameterized");
|
||||
|
||||
}
|
||||
|
||||
private Object resolveCookieValue(String cookieName, boolean required, String defaultValue,
|
||||
MethodParameter methodParam, NativeWebRequest webRequest, Object handlerForInitBinderCall)
|
||||
throws Exception {
|
||||
|
|
|
|||
Loading…
Reference in New Issue