@RequestMapping.consumes() and produces() now default to an empty array, instead of */*

master
Arjen Poutsma 14 years ago
parent a618bcc8cd
commit 1eaca65720
  1. 7
      org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java
  2. 79
      org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/AbstractMessageConverterMethodProcessor.java
  3. 14
      org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/HttpEntityMethodProcessor.java
  4. 18
      org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestResponseBodyMethodProcessor.java
  5. 42
      org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/condition/ConsumesRequestCondition.java
  6. 13
      org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/condition/MediaTypesRequestCondition.java
  7. 23
      org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/condition/ProducesRequestCondition.java
  8. 2
      org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/method/condition/RequestConditionComposite.java
  9. 15
      org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/config/annotation/WebMvcConfigurationTests.java
  10. 2
      org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/annotation/support/RequestResponseBodyMethodProcessorTests.java
  11. 2
      org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/condition/ConsumesRequestConditionTests.java
  12. 2
      org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/method/condition/ProducesRequestConditionTests.java
  13. 4
      org.springframework.web/src/main/java/org/springframework/web/bind/annotation/RequestMapping.java

@ -25,7 +25,6 @@ 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;
@ -180,9 +179,9 @@ public class RequestMappingHandlerMapping extends AbstractHandlerMethodMapping<R
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())) {
if (!info.getProduces().isEmpty()) {
Set<MediaType> mediaTypes = info.getProduces().getMediaTypes();
request.setAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, mediaTypes);
}
}

@ -22,12 +22,12 @@ 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;
@ -112,7 +112,7 @@ public abstract class AbstractMessageConverterMethodProcessor
* @param webRequest the web request to create an input message from
* @return the input message
*/
protected HttpInputMessage createInputMessage(NativeWebRequest webRequest) {
protected ServletServerHttpRequest createInputMessage(NativeWebRequest webRequest) {
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
return new ServletServerHttpRequest(servletRequest);
}
@ -123,44 +123,48 @@ public abstract class AbstractMessageConverterMethodProcessor
* @param webRequest the web request to create an output message from
* @return the output message
*/
protected HttpOutputMessage createOutputMessage(NativeWebRequest webRequest) {
protected ServletServerHttpResponse createOutputMessage(NativeWebRequest webRequest) {
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}
* Writes the given return value to the given web request. Delegates to
* {@link #writeWithMessageConverters(Object, MethodParameter, ServletServerHttpRequest, ServletServerHttpResponse)}
*/
@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);
}
protected <T> void writeWithMessageConverters(T returnValue,
MethodParameter returnType,
NativeWebRequest webRequest)
throws IOException, HttpMediaTypeNotAcceptableException {
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage);
}
/**
* Writes the given return type to the given output message.
*
* @param returnValue the value to write to the output message
* @param returnType the type of the value
* @param inputMessage the input messages. Used to inspect the {@code Accept} header.
* @param outputMessage the output message to write to
* @throws IOException thrown in case of I/O errors
* @throws HttpMediaTypeNotAcceptableException thrown when the conditions indicated by {@code Accept} header on
* the request cannot be met by the message converters
*/
@SuppressWarnings("unchecked")
protected <T> void writeWithMessageConverters(T returnValue,
MethodParameter returnType,
HttpInputMessage inputMessage,
HttpOutputMessage outputMessage,
Set<MediaType> producibleMediaTypes)
ServletServerHttpRequest inputMessage,
ServletServerHttpResponse outputMessage)
throws IOException, HttpMediaTypeNotAcceptableException {
Set<MediaType> acceptableMediaTypes = getAcceptableMediaTypes(inputMessage);
Set<MediaType> producibleMediaTypes = getProducibleMediaTypes(inputMessage.getServletRequest());
List<MediaType> mediaTypes = new ArrayList<MediaType>();
for (MediaType acceptableMediaType : getAcceptableMediaTypes(inputMessage)) {
for (MediaType acceptableMediaType : acceptableMediaTypes) {
for (MediaType producibleMediaType : producibleMediaTypes) {
if (acceptableMediaType.isCompatibleWith(producibleMediaType)) {
mediaTypes.add(getMostSpecificMediaType(acceptableMediaType, producibleMediaType));
@ -197,6 +201,29 @@ public abstract class AbstractMessageConverterMethodProcessor
throw new HttpMediaTypeNotAcceptableException(allSupportedMediaTypes);
}
/**
* 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}
* </ul>
*/
@SuppressWarnings("unchecked")
protected Set<MediaType> getProducibleMediaTypes(HttpServletRequest request) {
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);
}
}
private Set<MediaType> getAcceptableMediaTypes(HttpInputMessage inputMessage) {
Set<MediaType> result = new HashSet<MediaType>(inputMessage.getHeaders().getAccept());
if (result.isEmpty()) {

@ -22,17 +22,15 @@ 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;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.http.server.ServletServerHttpResponse;
import org.springframework.util.Assert;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.bind.support.WebDataBinderFactory;
@ -108,12 +106,13 @@ public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodPro
return;
}
HttpOutputMessage outputMessage = createOutputMessage(webRequest);
ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
Assert.isInstanceOf(HttpEntity.class, returnValue);
HttpEntity<?> responseEntity = (HttpEntity<?>) returnValue;
if (responseEntity instanceof ResponseEntity) {
((ServerHttpResponse) outputMessage).setStatusCode(((ResponseEntity<?>) responseEntity).getStatusCode());
outputMessage.setStatusCode(((ResponseEntity<?>) responseEntity).getStatusCode());
}
HttpHeaders entityHeaders = responseEntity.getHeaders();
@ -123,8 +122,7 @@ public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodPro
Object body = responseEntity.getBody();
if (body != null) {
Set<MediaType> mediaTypes = getProducibleMediaTypes(webRequest);
writeWithMessageConverters(body, returnType, createInputMessage(webRequest), outputMessage, mediaTypes);
writeWithMessageConverters(body, returnType, inputMessage, outputMessage);
}
else {
// flush headers to the HttpServletResponse

@ -18,16 +18,9 @@ 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;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.bind.annotation.RequestBody;
@ -72,17 +65,8 @@ public class RequestResponseBodyMethodProcessor extends AbstractMessageConverter
NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException {
mavContainer.setResolveView(false);
if (returnValue != null) {
HttpInputMessage inputMessage = createInputMessage(webRequest);
HttpOutputMessage outputMessage = createOutputMessage(webRequest);
Set<MediaType> mediaTypes = getProducibleMediaTypes(webRequest);
writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage, mediaTypes);
writeWithMessageConverters(returnValue, returnType, webRequest);
}
}
@Override
protected HttpOutputMessage createOutputMessage(NativeWebRequest webRequest) {
HttpServletResponse servletResponse = (HttpServletResponse) webRequest.getNativeResponse();
return new ServletServerHttpResponse(servletResponse);
}
}

@ -50,9 +50,6 @@ public class ConsumesRequestCondition
}
private static Set<ConsumeRequestCondition> parseConditions(List<String> consumes) {
if (consumes.isEmpty()) {
consumes = Collections.singletonList("*/*");
}
Set<ConsumeRequestCondition> conditions = new LinkedHashSet<ConsumeRequestCondition>(consumes.size());
for (String consume : consumes) {
conditions.add(new ConsumeRequestCondition(consume));
@ -64,7 +61,7 @@ public class ConsumesRequestCondition
* Creates a default set of consumes request conditions.
*/
public ConsumesRequestCondition() {
this(Collections.singleton(new ConsumeRequestCondition(MediaType.ALL, false)));
this(Collections.<ConsumeRequestCondition>emptySet());
}
/**
@ -74,6 +71,9 @@ public class ConsumesRequestCondition
* @return a new request condition that contains all matching attributes, or {@code null} if not all conditions match
*/
public ConsumesRequestCondition getMatchingCondition(HttpServletRequest request) {
if (isEmpty()) {
return this;
}
Set<ConsumeRequestCondition> matchingConditions = new LinkedHashSet<ConsumeRequestCondition>(getConditions());
for (Iterator<ConsumeRequestCondition> iterator = matchingConditions.iterator(); iterator.hasNext();) {
ConsumeRequestCondition condition = iterator.next();
@ -90,29 +90,41 @@ public class ConsumesRequestCondition
}
/**
* Combines this collection of request condition with another. Returns {@code other}, unless it has the default value
* (i.e. <code>&#42;/&#42;</code>).
* Combines this collection of request condition with another. Returns {@code other}, unless it is empty.
*
* @param other the condition to combine with
*/
public ConsumesRequestCondition combine(ConsumesRequestCondition other) {
return !other.hasDefaultValue() ? other : this;
return !other.isEmpty() ? other : this;
}
private boolean hasDefaultValue() {
Set<ConsumeRequestCondition> conditions = getConditions();
if (conditions.size() == 1) {
ConsumeRequestCondition condition = conditions.iterator().next();
return condition.getMediaType().equals(MediaType.ALL);
public int compareTo(ConsumesRequestCondition other) {
MediaTypeRequestCondition thisMostSpecificCondition = this.getMostSpecificCondition();
MediaTypeRequestCondition otherMostSpecificCondition = other.getMostSpecificCondition();
if (thisMostSpecificCondition == null && otherMostSpecificCondition == null) {
return 0;
}
else if (thisMostSpecificCondition == null) {
return 1;
}
else if (otherMostSpecificCondition == null) {
return -1;
}
else {
return false;
return thisMostSpecificCondition.compareTo(otherMostSpecificCondition);
}
}
public int compareTo(ConsumesRequestCondition other) {
return this.getMostSpecificCondition().compareTo(other.getMostSpecificCondition());
private MediaTypeRequestCondition getMostSpecificCondition() {
if (!isEmpty()) {
return getSortedConditions().get(0);
}
else {
return null;
}
}
static class ConsumeRequestCondition extends MediaTypesRequestCondition.MediaTypeRequestCondition {

@ -25,7 +25,6 @@ import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
/**
* @author Arjen Poutsma
@ -37,21 +36,10 @@ class MediaTypesRequestCondition<T extends MediaTypesRequestCondition.MediaTypeR
public MediaTypesRequestCondition(Collection<T> conditions) {
super(conditions);
Assert.notEmpty(conditions, "'conditions' must not be empty");
sortedConditions = new ArrayList<T>(conditions);
Collections.sort(sortedConditions);
}
private MediaTypeRequestCondition getMostSpecificCondition(Collection<T> conditions) {
List<MediaTypeRequestCondition> conditionList = new ArrayList<MediaTypeRequestCondition>(conditions);
Collections.sort(conditionList);
return conditionList.get(0);
}
protected MediaTypeRequestCondition getMostSpecificCondition() {
return sortedConditions.get(0);
}
protected List<T> getSortedConditions() {
return sortedConditions;
}
@ -67,7 +55,6 @@ class MediaTypesRequestCondition<T extends MediaTypesRequestCondition.MediaTypeR
return result;
}
/**
* @author Arjen Poutsma
*/

@ -50,9 +50,6 @@ public class ProducesRequestCondition
}
private static Set<ProduceRequestCondition> parseConditions(List<String> consumes) {
if (consumes.isEmpty()) {
consumes = Collections.singletonList("*/*");
}
Set<ProduceRequestCondition> conditions = new LinkedHashSet<ProduceRequestCondition>(consumes.size());
for (String consume : consumes) {
conditions.add(new ProduceRequestCondition(consume));
@ -61,10 +58,10 @@ public class ProducesRequestCondition
}
/**
* Creates an default set of consumes request conditions.
* Creates an empty set of consumes request conditions.
*/
public ProducesRequestCondition() {
this(Collections.singleton(new ProduceRequestCondition(MediaType.ALL, false)));
this(Collections.<ProduceRequestCondition>emptySet());
}
/**
@ -74,6 +71,9 @@ public class ProducesRequestCondition
* @return a new request condition that contains all matching attributes, or {@code null} if not all conditions match
*/
public ProducesRequestCondition getMatchingCondition(HttpServletRequest request) {
if (isEmpty()) {
return this;
}
Set<ProduceRequestCondition> matchingConditions = new LinkedHashSet<ProduceRequestCondition>(getConditions());
for (Iterator<ProduceRequestCondition> iterator = matchingConditions.iterator(); iterator.hasNext();) {
ProduceRequestCondition condition = iterator.next();
@ -96,18 +96,7 @@ public class ProducesRequestCondition
* @param other the condition to combine with
*/
public ProducesRequestCondition combine(ProducesRequestCondition other) {
return !other.hasDefaultValue() ? other : this;
}
private boolean hasDefaultValue() {
Set<ProduceRequestCondition> conditions = getConditions();
if (conditions.size() == 1) {
ProduceRequestCondition condition = conditions.iterator().next();
return condition.getMediaType().equals(MediaType.ALL);
}
else {
return false;
}
return !other.isEmpty() ? other : this;
}
public int compareTo(ProducesRequestCondition other, List<MediaType> acceptedMediaTypes) {

@ -40,7 +40,7 @@ abstract class RequestConditionComposite<T extends RequestCondition> implements
return conditions;
}
boolean isEmpty() {
public boolean isEmpty() {
return conditions.isEmpty();
}

@ -16,25 +16,16 @@
package org.springframework.web.servlet.config.annotation;
import static org.easymock.EasyMock.capture;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.verify;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.easymock.Capture;
import org.easymock.EasyMock;
import org.junit.Before;
import org.junit.Test;
import org.springframework.format.support.FormattingConversionService;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
@ -55,6 +46,9 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandl
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
import static org.easymock.EasyMock.*;
import static org.junit.Assert.*;
/**
* A test fixture for WebMvcConfiguration tests.
*
@ -192,6 +186,7 @@ public class WebMvcConfigurationTests {
RequestMappingHandlerMapping hm = mvcConfiguration.requestMappingHandlerMapping();
hm.setApplicationContext(context);
HandlerExecutionChain chain = hm.getHandler(request);
assertNotNull("No chain returned", chain);
assertNotNull("Expected at one default converter", chain.getInterceptors());
}

@ -33,7 +33,6 @@ import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest;
@ -217,7 +216,6 @@ public class RequestResponseBodyMethodProcessorTests {
return 42;
}
@RequestMapping(produces = {"text/html", "application/xhtml+xml"})
@ResponseBody
public String handle3() {
return null;

@ -116,7 +116,7 @@ public class ConsumesRequestConditionTests {
@Test
public void combineWithDefault() {
ConsumesRequestCondition condition1 = new ConsumesRequestCondition("text/plain");
ConsumesRequestCondition condition2 = new ConsumesRequestCondition("*/*");
ConsumesRequestCondition condition2 = new ConsumesRequestCondition();
ConsumesRequestCondition result = condition1.combine(condition2);
assertEquals(condition1, result);

@ -156,7 +156,7 @@ public class ProducesRequestConditionTests {
@Test
public void combineWithDefault() {
ProducesRequestCondition condition1 = new ProducesRequestCondition("text/plain");
ProducesRequestCondition condition2 = new ProducesRequestCondition("*/*");
ProducesRequestCondition condition2 = new ProducesRequestCondition();
ProducesRequestCondition result = condition1.combine(condition2);
assertEquals(condition1, result);

@ -309,7 +309,7 @@ public @interface RequestMapping {
* @see org.springframework.http.MediaType
* @see javax.servlet.http.HttpServletRequest#getContentType()
*/
String[] consumes() default "*/*";
String[] consumes() default {};
/**
* The producible media types of the mapped request, narrowing the primary mapping.
@ -322,6 +322,6 @@ public @interface RequestMapping {
* this consumes restriction.
* @see org.springframework.http.MediaType
*/
String[] produces() default "*/*";
String[] produces() default {};
}

Loading…
Cancel
Save