diff --git a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/ContentNegotiatingViewResolver.java b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/ContentNegotiatingViewResolver.java index 6c4b466214500a73c4db1e741b5184242c2efc65..c27eee3497d61d4991f1898d03a31965288f4369 100644 --- a/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/ContentNegotiatingViewResolver.java +++ b/org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/view/ContentNegotiatingViewResolver.java @@ -37,9 +37,10 @@ import org.springframework.core.OrderComparator; import org.springframework.core.Ordered; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; +import org.springframework.http.MediaType; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; -import org.springframework.http.MediaType; +import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import org.springframework.web.context.request.RequestAttributes; import org.springframework.web.context.request.RequestContextHolder; @@ -73,7 +74,11 @@ import org.springframework.web.util.WebUtils; * {@code text/html} content type (based on the {@code html} file extension). A request for {@code /view} with a {@code * text/html} request {@code Accept} header has the same result. * + *

Additionally, this view resolver exposes the {@link #setDefaultViews(List) defaultViews} property, allowing you to + * override the views provided by the view resolvers. + * * @author Arjen Poutsma + * @author Jeremy Grelle * @see ViewResolver * @see InternalResourceViewResolver * @see BeanNameViewResolver @@ -94,6 +99,8 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport private ConcurrentMap mediaTypes = new ConcurrentHashMap(); + private List defaultViews; + private List viewResolvers; public void setOrder(int order) { @@ -131,6 +138,13 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport } } + /** + * Sets the default views to use when a more specific view can not be obtained from the {@link ViewResolver} chain. + */ + public void setDefaultViews(List defaultViews) { + this.defaultViews = defaultViews; + } + /** * Sets the view resolvers to be wrapped by this view resolver. * @@ -235,16 +249,23 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport Collections.sort(requestedMediaTypes); SortedMap views = new TreeMap(); + List candidateViews = new ArrayList(); for (ViewResolver viewResolver : viewResolvers) { View view = viewResolver.resolveViewName(viewName, locale); if (view != null) { - MediaType viewMediaType = MediaType.parseMediaType(view.getContentType()); - for (MediaType requestedMediaType : requestedMediaTypes) { - if (requestedMediaType.includes(viewMediaType)) { - if (!views.containsKey(requestedMediaType)) { - views.put(requestedMediaType, view); - break; - } + candidateViews.add(view); + } + } + if (!CollectionUtils.isEmpty(defaultViews)) { + candidateViews.addAll(defaultViews); + } + for (View candidateView : candidateViews) { + MediaType viewMediaType = MediaType.parseMediaType(candidateView.getContentType()); + for (MediaType requestedMediaType : requestedMediaTypes) { + if (requestedMediaType.includes(viewMediaType)) { + if (!views.containsKey(requestedMediaType)) { + views.put(requestedMediaType, candidateView); + break; } } } diff --git a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/view/ContentNegotiatingViewResolverTests.java b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/view/ContentNegotiatingViewResolverTests.java index 3fa76d36d60c211de8bd8b2839618d55526e34d1..d78c0a8e0faea037bd7849eb2aecdbc97a8b5a7d 100644 --- a/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/view/ContentNegotiatingViewResolverTests.java +++ b/org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/view/ContentNegotiatingViewResolverTests.java @@ -18,23 +18,26 @@ package org.springframework.web.servlet.view; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Locale; +import java.util.Map; import static org.easymock.EasyMock.*; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertSame; +import static org.junit.Assert.*; import org.junit.Before; import org.junit.Test; -import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.http.MediaType; +import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import org.springframework.web.servlet.View; import org.springframework.web.servlet.ViewResolver; -/** @author Arjen Poutsma */ +/** + * @author Arjen Poutsma + */ public class ContentNegotiatingViewResolverTests { private ContentNegotiatingViewResolver viewResolver; @@ -109,7 +112,45 @@ public class ContentNegotiatingViewResolverTests { verify(viewResolverMock1, viewResolverMock2, viewMock1, viewMock2); } - + + @Test + public void resolveViewNameAcceptHeaderDefaultView() throws Exception { + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test"); + request.addHeader("Accept", "application/json"); + RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request)); + + ViewResolver viewResolverMock1 = createMock(ViewResolver.class); + ViewResolver viewResolverMock2 = createMock(ViewResolver.class); + List viewResolverMocks = new ArrayList(); + viewResolverMocks.add(viewResolverMock1); + viewResolverMocks.add(viewResolverMock2); + viewResolver.setViewResolvers(viewResolverMocks); + + View viewMock1 = createMock("application_xml", View.class); + View viewMock2 = createMock("text_html", View.class); + View viewMock3 = createMock("application_json", View.class); + + List defaultViews = new ArrayList(); + defaultViews.add(viewMock3); + viewResolver.setDefaultViews(defaultViews); + + String viewName = "view"; + Locale locale = Locale.ENGLISH; + + expect(viewResolverMock1.resolveViewName(viewName, locale)).andReturn(viewMock1); + expect(viewResolverMock2.resolveViewName(viewName, locale)).andReturn(viewMock2); + expect(viewMock1.getContentType()).andReturn("application/xml"); + expect(viewMock2.getContentType()).andReturn("text/html;charset=ISO-8859-1"); + expect(viewMock3.getContentType()).andReturn("application/json"); + + replay(viewResolverMock1, viewResolverMock2, viewMock1, viewMock2, viewMock3); + + View result = viewResolver.resolveViewName(viewName, locale); + assertSame("Invalid view", viewMock3, result); + + verify(viewResolverMock1, viewResolverMock2, viewMock1, viewMock2, viewMock3); + } + @Test public void resolveViewNameFilename() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test.html"); @@ -141,4 +182,45 @@ public class ContentNegotiatingViewResolverTests { verify(viewResolverMock1, viewResolverMock2, viewMock1, viewMock2); } + @Test + public void resolveViewNameFilenameDefaultView() throws Exception { + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test.json"); + RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request)); + + Map mediaTypes = new HashMap(); + mediaTypes.put("json", "application/json"); + viewResolver.setMediaTypes(mediaTypes); + + ViewResolver viewResolverMock1 = createMock(ViewResolver.class); + ViewResolver viewResolverMock2 = createMock(ViewResolver.class); + List viewResolverMocks = new ArrayList(); + viewResolverMocks.add(viewResolverMock1); + viewResolverMocks.add(viewResolverMock2); + viewResolver.setViewResolvers(viewResolverMocks); + + View viewMock1 = createMock("application_xml", View.class); + View viewMock2 = createMock("text_html", View.class); + View viewMock3 = createMock("application_json", View.class); + + List defaultViews = new ArrayList(); + defaultViews.add(viewMock3); + viewResolver.setDefaultViews(defaultViews); + + String viewName = "view"; + Locale locale = Locale.ENGLISH; + + expect(viewResolverMock1.resolveViewName(viewName, locale)).andReturn(viewMock1); + expect(viewResolverMock2.resolveViewName(viewName, locale)).andReturn(viewMock2); + expect(viewMock1.getContentType()).andReturn("application/xml"); + expect(viewMock2.getContentType()).andReturn("text/html;charset=ISO-8859-1"); + expect(viewMock3.getContentType()).andReturn("application/json"); + + replay(viewResolverMock1, viewResolverMock2, viewMock1, viewMock2, viewMock3); + + View result = viewResolver.resolveViewName(viewName, locale); + assertSame("Invalid view", viewMock3, result); + + verify(viewResolverMock1, viewResolverMock2, viewMock1, viewMock2, viewMock3); + } + }