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 674aac4851..1043a3cb24 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 @@ -64,26 +64,32 @@ import org.springframework.web.util.WebUtils; * request. This media type is determined by using the following criteria: *
    *
  1. If the requested path has a file extension and if the {@link #setFavorPathExtension(boolean)} property is - * true, the {@link #setMediaTypes(Map) mediaTypes} property is inspected for a matching media type.
  2. + * {@code true}, the {@link #setMediaTypes(Map) mediaTypes} property is inspected for a matching media type. *
  3. If the request contains a parameter defining the extension and if the {@link #setFavorParameter(boolean)} - * property is true, the {@link #setMediaTypes(Map) mediaTypes} property is inspected for a matching media - * type. The default name of the parameter is format and it can be configured using the + * property is true, the {@link #setMediaTypes(Map) mediaTypes} property is inspected for a matching + * media type. The default name of the parameter is format and it can be configured using the * {@link #setParameterName(String) parameterName} property.
  4. - *
  5. If there is no match and if the Java Activation Framework (JAF) is present on the class path, {@link - * FileTypeMap#getContentType(String)} is used.
  6. - *
  7. If the previous steps did not result in a media type, the request {@code Accept} header is used.
  8. + *
  9. If there is no match in the {@link #setMediaTypes(Map) mediaTypes} property and if the Java Activation + * Framework (JAF) is present on the class path, {@link FileTypeMap#getContentType(String)} is used instead.
  10. + *
  11. If the previous steps did not result in a media type, and + * {@link #setIgnoreAcceptHeader(boolean) ignoreAcceptHeader} is {@code false}, the request {@code Accept} header is + * used.
  12. *
+ * * Once the requested media type has been determined, this resolver queries each delegate view resolver for a * {@link View} and determines if the requested media type is {@linkplain MediaType#includes(MediaType) compatible} * with the view's {@linkplain View#getContentType() content type}). The most compatible view is returned. * + *

Additionally, this view resolver exposes the {@link #setDefaultViews(List) defaultViews} property, allowing you to + * override the views provided by the view resolvers. Note that these default views are offered as candicates, and + * still need have the content type requested (via file extension, parameter, or {@code Accept} header, described above). + * You can also set the {@linkplain #setDefaultContentType(MediaType) default content type} directly, which will be + * returned when the other mechanisms ({@code Accept} header, file extension or parameter) do not result in a match. + * *

For example, if the request path is {@code /view.html}, this view resolver will look for a view that has the * {@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 Rostislav Hristov * @see ViewResolver @@ -103,7 +109,9 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport private boolean favorPathExtension = true; private boolean favorParameter = false; - + + private boolean ignoreAcceptHeader = false; + private String parameterName = "format"; private int order = Ordered.HIGHEST_PRECEDENCE; @@ -114,6 +122,8 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport private List viewResolvers; + private MediaType defaultContentType; + public void setOrder(int order) { this.order = order; } @@ -135,25 +145,36 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport } /** - * Indicates whether a request parameter should be used to determine the requested media type, in favor - * of looking at the {@code Accept} header. The default value is {@code false}. + * Indicates whether a request parameter should be used to determine the requested media type, in favor of looking at + * the {@code Accept} header. The default value is {@code false}. * - *

For instance, when this flag is true, a request for {@code /hotels?format=pdf} will result in - * an {@code AbstractPdfView} being resolved, while the {@code Accept} header can be the browser-defined {@code + *

For instance, when this flag is true, a request for {@code /hotels?format=pdf} will result in an + * {@code AbstractPdfView} being resolved, while the {@code Accept} header can be the browser-defined {@code * text/html,application/xhtml+xml}. */ public void setFavorParameter(boolean favorParameter) { this.favorParameter = favorParameter; } - + /** - * Sets the parameter name that can be used to determine the requested media type if the - * {@link #setFavorParameter(boolean)} property is {@code true}. The default parameter name is {@code format}. + * Sets the parameter name that can be used to determine the requested media type if the {@link + * #setFavorParameter(boolean)} property is {@code true}. The default parameter name is {@code format}. */ public void setParameterName(String parameterName) { this.parameterName = parameterName; } - + + /** + * Indicates whether the HTTP {@code Accept} header should be ignored. Default is {@code false}. + * + * If set to {@code true}, this view resolver will only refer to the file extension and/or paramter, as indicated by + * the {@link #setFavorPathExtension(boolean) favorPathExtension} and {@link #setFavorParameter(boolean) + * favorParameter} properties. + */ + public void setIgnoreAcceptHeader(boolean ignoreAcceptHeader) { + this.ignoreAcceptHeader = ignoreAcceptHeader; + } + /** * Sets the mapping from file extensions to media types. * @@ -169,13 +190,19 @@ 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. - */ + /** 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 default content type. This content type will be used when file extension, parameter, nor {@code Accept} + * header define a content-type, either through being disabled or empty. + */ + public void setDefaultContentType(MediaType defaultContentType) { + this.defaultContentType = defaultContentType; + } + /** * Sets the view resolvers to be wrapped by this view resolver. * @@ -231,14 +258,15 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport return mediaTypes; } } - if (favorParameter) { + if (favorParameter) { if (request.getParameter(parameterName) != null) { String parameterValue = request.getParameter(parameterName); MediaType mediaType = getMediaTypeFromParameter(parameterValue); if (mediaType != null) { if (logger.isDebugEnabled()) { - logger.debug("Requested media type is '" + mediaType + "' (based on parameter '" + - parameterValue + "')"); + logger.debug( + "Requested media type is '" + mediaType + "' (based on parameter '" + parameterName + + "'='" + parameterValue + "')"); } List mediaTypes = new ArrayList(); mediaTypes.add(mediaType); @@ -246,13 +274,18 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport } } } - String acceptHeader = request.getHeader(ACCEPT_HEADER); - if (StringUtils.hasText(acceptHeader)) { - List mediaTypes = MediaType.parseMediaTypes(acceptHeader); - if (logger.isDebugEnabled()) { - logger.debug("Requested media types are " + mediaTypes + " (based on Accept header)"); + if (!ignoreAcceptHeader) { + String acceptHeader = request.getHeader(ACCEPT_HEADER); + if (StringUtils.hasText(acceptHeader)) { + List mediaTypes = MediaType.parseMediaTypes(acceptHeader); + if (logger.isDebugEnabled()) { + logger.debug("Requested media types are " + mediaTypes + " (based on Accept header)"); + } + return mediaTypes; } - return mediaTypes; + } + if (defaultContentType != null) { + return Collections.singletonList(defaultContentType); } else { return Collections.emptyList(); @@ -286,12 +319,12 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport } return mediaType; } - + /** * Determines the {@link MediaType} for the given parameter value. * - *

The default implementation will check the {@linkplain #setMediaTypes(Map) media types} property for a - * defined mapping. + *

The default implementation will check the {@linkplain #setMediaTypes(Map) media types} property for a defined + * mapping. * *

This method can be overriden to provide a different algorithm. * @@ -345,9 +378,7 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport } } - /** - * Inner class to avoid hard-coded JAF dependency. - */ + /** Inner class to avoid hard-coded JAF dependency. */ private static class ActivationMediaTypeFactory { private static final FileTypeMap fileTypeMap; 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 9eb0e93f90..d94b8a0ab0 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 @@ -92,6 +92,17 @@ public class ContentNegotiatingViewResolverTests { result.get(3)); } + @Test + public void getDefaultMediaType() { + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test"); + request.addHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"); + viewResolver.setDefaultContentType(new MediaType("application", "pdf")); + viewResolver.setIgnoreAcceptHeader(true); + List result = viewResolver.getMediaTypes(request); + assertEquals("Invalid amount of media types", 1, result.size()); + assertEquals("Invalid content type", new MediaType("application", "pdf"), result.get(0)); + } + @Test public void resolveViewNameAcceptHeader() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test");