Use request attribute to check producible media types when writing to the response body

This commit is contained in:
Rossen Stoyanchev
2011-05-17 13:02:48 +00:00
parent 1bbdb0d2ff
commit af1dfd3577
7 changed files with 68 additions and 50 deletions

View File

@@ -92,6 +92,14 @@ public interface HandlerMapping {
*/
String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";
/**
* Name of the {@link HttpServletRequest} attribute that contains the set of producible MediaTypes
* applicable to the mapped handler.
* <p>Note: This attribute is not required to be supported by all HandlerMapping implementations.
* Handlers should not necessarily expect this request attribute to be present in all scenarios.
*/
String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";
/**
* Return a handler and any interceptors for this request. The choice may be made
* on request URL, session state, or any factor the implementing class chooses.

View File

@@ -25,6 +25,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
@@ -173,11 +174,17 @@ public class RequestMappingHandlerMapping extends AbstractHandlerMethodMapping<R
}
@Override
protected void handleMatch(RequestMappingInfo mapping, String lookupPath, HttpServletRequest request) {
super.handleMatch(mapping, lookupPath, request);
String pattern = mapping.getPatterns().iterator().next();
protected void handleMatch(RequestMappingInfo info, String lookupPath, HttpServletRequest request) {
super.handleMatch(info, lookupPath, request);
String pattern = info.getPatterns().iterator().next();
Map<String, String> uriTemplateVariables = pathMatcher.extractUriTemplateVariables(pattern, lookupPath);
request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVariables);
Set<MediaType> mediaTypes = info.getProduces().getMediaTypes();
if (mediaTypes.size() > 1 || !MediaType.ALL.equals(mediaTypes.iterator().next())) {
request.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mediaTypes);
}
}
/**

View File

@@ -17,18 +17,17 @@
package org.springframework.web.servlet.mvc.method.annotation.support;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
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.HttpOutputMessage;
@@ -37,12 +36,13 @@ import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.HandlerMapping;
/**
* A base class for resolving method argument values by reading from the body of a request with {@link
@@ -124,22 +124,43 @@ public abstract class AbstractMessageConverterMethodProcessor
* @return the output message
*/
protected HttpOutputMessage createOutputMessage(NativeWebRequest webRequest) {
HttpServletResponse servletResponse = webRequest.getNativeResponse(HttpServletResponse.class);
return new ServletServerHttpResponse(servletResponse);
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
return new ServletServerHttpResponse(response);
}
/**
* Returns the media types that can be produced:
* <ul>
* <li>The set of producible media types specified in the request mappings, or
* <li>The set of supported media types by all configured message converters, or
* <li>{@link MediaType#ALL}
*/
@SuppressWarnings("unchecked")
protected Set<MediaType> getProducibleMediaTypes(NativeWebRequest webRequest) {
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
Set<MediaType> mediaTypes = (Set<MediaType>) request.getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
if (!CollectionUtils.isEmpty(mediaTypes)) {
return mediaTypes;
}
else if (!allSupportedMediaTypes.isEmpty()) {
return new HashSet<MediaType>(allSupportedMediaTypes);
}
else {
return Collections.singleton(MediaType.ALL);
}
}
@SuppressWarnings("unchecked")
protected <T> void writeWithMessageConverters(T returnValue,
MethodParameter returnType,
HttpInputMessage inputMessage,
HttpOutputMessage outputMessage)
HttpOutputMessage outputMessage,
Set<MediaType> producibleMediaTypes)
throws IOException, HttpMediaTypeNotAcceptableException {
Set<MediaType> producibleMediaTypes = getProducibleMediaTypes(returnType.getMethod(), returnValue.getClass());
Set<MediaType> acceptableMediaTypes = getAcceptableMediaTypes(inputMessage);
List<MediaType> mediaTypes = new ArrayList<MediaType>();
for (MediaType acceptableMediaType : acceptableMediaTypes) {
for (MediaType acceptableMediaType : getAcceptableMediaTypes(inputMessage)) {
for (MediaType producibleMediaType : producibleMediaTypes) {
if (acceptableMediaType.isCompatibleWith(producibleMediaType)) {
mediaTypes.add(getMostSpecificMediaType(acceptableMediaType, producibleMediaType));
@@ -173,33 +194,7 @@ public abstract class AbstractMessageConverterMethodProcessor
}
}
}
else {
throw new HttpMediaTypeNotAcceptableException(allSupportedMediaTypes);
}
}
private Set<MediaType> getProducibleMediaTypes(Method handlerMethod, Class<?> returnValueClass) {
RequestMapping requestMappingAnn = handlerMethod.getAnnotation(RequestMapping.class);
if (requestMappingAnn == null) {
requestMappingAnn = handlerMethod.getClass().getAnnotation(RequestMapping.class);
}
Set<MediaType> result = new HashSet<MediaType>();
if (requestMappingAnn != null) {
for (String produce : requestMappingAnn.produces()) {
result.add(MediaType.parseMediaType(produce));
}
}
else {
for (HttpMessageConverter<?> messageConverter : messageConverters) {
if (messageConverter.canWrite(returnValueClass, null)) {
result.addAll(messageConverter.getSupportedMediaTypes());
}
}
}
if (result.isEmpty()) {
result.add(MediaType.ALL);
}
return result;
throw new HttpMediaTypeNotAcceptableException(allSupportedMediaTypes);
}
private Set<MediaType> getAcceptableMediaTypes(HttpInputMessage inputMessage) {
@@ -209,7 +204,7 @@ public abstract class AbstractMessageConverterMethodProcessor
}
return result;
}
private MediaType getMostSpecificMediaType(MediaType type1, MediaType type2) {
return MediaType.SPECIFICITY_COMPARATOR.compare(type1, type2) < 0 ? type1 : type2;
}

View File

@@ -22,12 +22,14 @@ import java.lang.reflect.GenericArrayType;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Set;
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;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpResponse;
@@ -121,7 +123,8 @@ public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodPro
Object body = responseEntity.getBody();
if (body != null) {
writeWithMessageConverters(body, returnType, createInputMessage(webRequest), outputMessage);
Set<MediaType> mediaTypes = getProducibleMediaTypes(webRequest);
writeWithMessageConverters(body, returnType, createInputMessage(webRequest), outputMessage, mediaTypes);
}
else {
// flush headers to the HttpServletResponse

View File

@@ -18,10 +18,14 @@ package org.springframework.web.servlet.mvc.method.annotation.support;
import java.io.IOException;
import java.util.List;
import java.util.Set;
import javax.servlet.http.HttpServletResponse;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
@@ -68,8 +72,10 @@ public class RequestResponseBodyMethodProcessor extends AbstractMessageConverter
NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException {
mavContainer.setResolveView(false);
if (returnValue != null) {
writeWithMessageConverters(returnValue, returnType, createInputMessage(webRequest),
createOutputMessage(webRequest));
HttpInputMessage inputMessage = createInputMessage(webRequest);
HttpOutputMessage outputMessage = createOutputMessage(webRequest);
Set<MediaType> mediaTypes = getProducibleMediaTypes(webRequest);
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage, mediaTypes);
}
}