Browse Source

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

git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@4314 50f2f4bb-b051-0410-bef5-90022cba6387
pull/1/merge
Rossen Stoyanchev 15 years ago
parent
commit
76abf77077
  1. 8
      org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/HandlerMapping.java
  2. 13
      org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java
  3. 71
      org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/AbstractMessageConverterMethodProcessor.java
  4. 5
      org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/HttpEntityMethodProcessor.java
  5. 10
      org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestResponseBodyMethodProcessor.java
  6. 7
      org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/HttpEntityMethodProcessorTests.java
  7. 4
      org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestResponseBodyMethodProcessorTests.java

8
org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/HandlerMapping.java

@ -92,6 +92,14 @@ public interface HandlerMapping { @@ -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.

13
org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java

@ -25,6 +25,7 @@ import java.util.Iterator; @@ -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 @@ -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);
}
}
/**

71
org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/AbstractMessageConverterMethodProcessor.java

@ -17,18 +17,17 @@ @@ -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; @@ -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 @@ -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 @@ -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 @@ -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;
}

5
org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/HttpEntityMethodProcessor.java

@ -22,12 +22,14 @@ import java.lang.reflect.GenericArrayType; @@ -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 @@ -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

10
org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestResponseBodyMethodProcessor.java

@ -18,10 +18,14 @@ package org.springframework.web.servlet.mvc.method.annotation.support; @@ -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 @@ -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);
}
}

7
org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/HttpEntityMethodProcessorTests.java

@ -15,6 +15,7 @@ @@ -15,6 +15,7 @@
*/
package org.springframework.web.servlet.mvc.method.annotation.support;
import static org.springframework.web.servlet.HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE;
import java.lang.reflect.Method;
import java.util.Arrays;
@ -40,6 +41,7 @@ import org.springframework.web.HttpMediaTypeNotSupportedException; @@ -40,6 +41,7 @@ import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.HandlerMapping;
import static org.easymock.EasyMock.*;
import static org.junit.Assert.*;
@ -162,8 +164,6 @@ public class HttpEntityMethodProcessorTests { @@ -162,8 +164,6 @@ public class HttpEntityMethodProcessorTests {
MediaType accepted = MediaType.TEXT_PLAIN;
servletRequest.addHeader("Accept", accepted.toString());
expect(messageConverter.canWrite(String.class, null)).andReturn(true);
expect(messageConverter.getSupportedMediaTypes()).andReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
expect(messageConverter.canWrite(String.class, accepted)).andReturn(true);
messageConverter.write(eq(body), eq(accepted), isA(HttpOutputMessage.class));
replay(messageConverter);
@ -180,6 +180,7 @@ public class HttpEntityMethodProcessorTests { @@ -180,6 +180,7 @@ public class HttpEntityMethodProcessorTests {
ResponseEntity<String> returnValue = new ResponseEntity<String>(body, HttpStatus.OK);
servletRequest.addHeader("Accept", "text/*");
servletRequest.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, Collections.singleton(MediaType.TEXT_HTML));
expect(messageConverter.canWrite(String.class, MediaType.TEXT_HTML)).andReturn(true);
messageConverter.write(eq(body), eq(MediaType.TEXT_HTML), isA(HttpOutputMessage.class));
@ -244,8 +245,6 @@ public class HttpEntityMethodProcessorTests { @@ -244,8 +245,6 @@ public class HttpEntityMethodProcessorTests {
ResponseEntity<String> returnValue = new ResponseEntity<String>("body", responseHeaders, HttpStatus.ACCEPTED);
Capture<HttpOutputMessage> outputMessage = new Capture<HttpOutputMessage>();
expect(messageConverter.canWrite(String.class, null)).andReturn(true);
expect(messageConverter.getSupportedMediaTypes()).andReturn(Arrays.asList(MediaType.TEXT_PLAIN));
expect(messageConverter.canWrite(String.class, MediaType.TEXT_PLAIN)).andReturn(true);
messageConverter.write(eq("body"), eq(MediaType.TEXT_PLAIN), capture(outputMessage));
replay(messageConverter);

4
org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestResponseBodyMethodProcessorTests.java

@ -38,6 +38,7 @@ import org.springframework.web.bind.annotation.ResponseBody; @@ -38,6 +38,7 @@ import org.springframework.web.bind.annotation.ResponseBody;
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.servlet.HandlerMapping;
import static org.easymock.EasyMock.*;
import static org.junit.Assert.*;
@ -151,8 +152,6 @@ public class RequestResponseBodyMethodProcessorTests { @@ -151,8 +152,6 @@ public class RequestResponseBodyMethodProcessorTests {
servletRequest.addHeader("Accept", accepted.toString());
String body = "Foo";
expect(messageConverter.canWrite(String.class, null)).andReturn(true);
expect(messageConverter.getSupportedMediaTypes()).andReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
expect(messageConverter.canWrite(String.class, accepted)).andReturn(true);
messageConverter.write(eq(body), eq(accepted), isA(HttpOutputMessage.class));
replay(messageConverter);
@ -168,6 +167,7 @@ public class RequestResponseBodyMethodProcessorTests { @@ -168,6 +167,7 @@ public class RequestResponseBodyMethodProcessorTests {
String body = "Foo";
servletRequest.addHeader("Accept", "text/*");
servletRequest.setAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, Collections.singleton(MediaType.TEXT_HTML));
expect(messageConverter.canWrite(String.class, MediaType.TEXT_HTML)).andReturn(true);
messageConverter.write(eq(body), eq(MediaType.TEXT_HTML), isA(HttpOutputMessage.class));

Loading…
Cancel
Save