Fix content negotiation issue with sort by q-value

Before this fix the q-value of media types in the Accept header were
ignored when using the new RequestMappingHandlerAdapter in combination
with @ResponseBody and HttpMessageConverters.

Issue: SPR-9160
This commit is contained in:
Rossen Stoyanchev
2012-05-11 14:39:31 -04:00
parent 75578d4e88
commit 982cb2f258
7 changed files with 148 additions and 55 deletions

View File

@@ -48,25 +48,25 @@ import org.springframework.http.MediaType;
import org.springframework.http.converter.ByteArrayHttpMessageConverter;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor;
/**
* Test fixture with {@link RequestResponseBodyMethodProcessor} and mock {@link HttpMessageConverter}.
*
*
* @author Arjen Poutsma
* @author Rossen Stoyanchev
*/
@@ -100,7 +100,7 @@ public class RequestResponseBodyMethodProcessorTests {
processor = new RequestResponseBodyMethodProcessor(Collections.<HttpMessageConverter<?>>singletonList(messageConverter));
reset(messageConverter);
Method handle = getClass().getMethod("handle1", String.class, Integer.TYPE);
paramRequestBodyString = new MethodParameter(handle, 0);
paramInt = new MethodParameter(handle, 1);
@@ -110,7 +110,7 @@ public class RequestResponseBodyMethodProcessorTests {
paramValidBean = new MethodParameter(getClass().getMethod("handle4", SimpleBean.class), 0);
mavContainer = new ModelAndViewContainer();
servletRequest = new MockHttpServletRequest();
servletResponse = new MockHttpServletResponse();
webRequest = new ServletWebRequest(servletRequest, servletResponse);
@@ -175,10 +175,10 @@ public class RequestResponseBodyMethodProcessorTests {
processor = new RequestResponseBodyMethodProcessor(Collections.<HttpMessageConverter<?>>singletonList(beanConverter));
processor.resolveArgument(paramValidBean, mavContainer, webRequest, new ValidatingBinderFactory());
verify(beanConverter);
}
@Test(expected = HttpMediaTypeNotSupportedException.class)
public void resolveArgumentNotReadable() throws Exception {
MediaType contentType = MediaType.TEXT_PLAIN;
@@ -248,7 +248,7 @@ public class RequestResponseBodyMethodProcessorTests {
fail("Expected exception");
}
@Test(expected = HttpMediaTypeNotAcceptableException.class)
public void handleReturnValueNotAcceptableProduces() throws Exception {
MediaType accepted = MediaType.TEXT_PLAIN;
@@ -264,19 +264,35 @@ public class RequestResponseBodyMethodProcessorTests {
fail("Expected exception");
}
// SPR-9160
@Test
public void handleStringReturnValue() throws Exception {
public void handleReturnValueSortByQuality() throws Exception {
this.servletRequest.addHeader("Accept", "text/plain; q=0.5, application/json");
List<HttpMessageConverter<?>> converters = new ArrayList<HttpMessageConverter<?>>();
converters.add(new MappingJackson2HttpMessageConverter());
converters.add(new StringHttpMessageConverter());
RequestResponseBodyMethodProcessor handler = new RequestResponseBodyMethodProcessor(converters);
handler.writeWithMessageConverters("Foo", returnTypeStringProduces, webRequest);
assertEquals("application/json;charset=UTF-8", servletResponse.getHeader("Content-Type"));
}
@Test
public void handleReturnValueString() throws Exception {
List<HttpMessageConverter<?>>converters = new ArrayList<HttpMessageConverter<?>>();
converters.add(new ByteArrayHttpMessageConverter());
converters.add(new StringHttpMessageConverter());
processor = new RequestResponseBodyMethodProcessor(converters);
processor.handleReturnValue("Foo", returnTypeString, mavContainer, webRequest);
assertEquals("text/plain;charset=ISO-8859-1", servletResponse.getHeader("Content-Type"));
assertEquals("Foo", servletResponse.getContentAsString());
}
@ResponseBody
public String handle1(@RequestBody String s, int i) {
return s;
@@ -293,7 +309,7 @@ public class RequestResponseBodyMethodProcessorTests {
public void handle4(@Valid @RequestBody SimpleBean b) {
}
private final class ValidatingBinderFactory implements WebDataBinderFactory {
public WebDataBinder createBinder(NativeWebRequest webRequest, Object target, String objectName) throws Exception {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
@@ -318,5 +334,5 @@ public class RequestResponseBodyMethodProcessorTests {
return name;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* Copyright 2002-2012 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,6 +16,16 @@
package org.springframework.web.servlet.view;
import static org.easymock.EasyMock.createMock;
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.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -28,7 +38,6 @@ import java.util.Set;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
@@ -40,9 +49,6 @@ import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import static org.easymock.EasyMock.*;
import static org.junit.Assert.*;
/**
* @author Arjen Poutsma
*/
@@ -148,7 +154,6 @@ public class ContentNegotiatingViewResolverTests {
request.setAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE, producibleTypes);
request.addHeader("Accept", "text/html,application/xml;q=0.9,application/xhtml+xml,*/*;q=0.8");
List<MediaType> result = viewResolver.getMediaTypes(request);
assertEquals("Invalid amount of media types", 1, result.size());
assertEquals("Invalid content type", new MediaType("application", "xhtml+xml"), result.get(0));
}
@@ -303,6 +308,35 @@ public class ContentNegotiatingViewResolverTests {
verify(viewResolverMock1, viewResolverMock2, viewMock1, viewMock2);
}
// SPR-9160
@Test
public void resolveViewNameAcceptHeaderSortByQuality() throws Exception {
request.addHeader("Accept", "text/plain;q=0.5, application/json");
ViewResolver htmlViewResolver = createMock(ViewResolver.class);
ViewResolver jsonViewResolver = createMock(ViewResolver.class);
viewResolver.setViewResolvers(Arrays.asList(htmlViewResolver, jsonViewResolver));
View htmlView = createMock("text_html", View.class);
View jsonViewMock = createMock("application_json", View.class);
String viewName = "view";
Locale locale = Locale.ENGLISH;
expect(htmlViewResolver.resolveViewName(viewName, locale)).andReturn(htmlView);
expect(jsonViewResolver.resolveViewName(viewName, locale)).andReturn(jsonViewMock);
expect(htmlView.getContentType()).andReturn("text/html").anyTimes();
expect(jsonViewMock.getContentType()).andReturn("application/json").anyTimes();
replay(htmlViewResolver, jsonViewResolver, htmlView, jsonViewMock);
viewResolver.setFavorPathExtension(false);
View result = viewResolver.resolveViewName(viewName, locale);
assertSame("Invalid view", jsonViewMock, result);
verify(htmlViewResolver, jsonViewResolver, htmlView, jsonViewMock);
}
@Test
public void resolveViewNameAcceptHeaderDefaultView() throws Exception {
request.addHeader("Accept", "application/json");