From ff1dac83817d7be5963f61efae1a1aae5e4b1c35 Mon Sep 17 00:00:00 2001 From: Arjen Poutsma Date: Mon, 13 Jul 2009 09:44:07 +0000 Subject: [PATCH] SPR-5870 - Add support for content negotiation based on a request parameter value --- .../view/ContentNegotiatingViewResolver.java | 84 ++++++++++++++++--- .../ContentNegotiatingViewResolverTests.java | 11 +++ 2 files changed, 84 insertions(+), 11 deletions(-) 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 c27eee3497..674aac4851 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 @@ -56,19 +56,26 @@ import org.springframework.web.util.WebUtils; * *

The {@code ContentNegotiatingViewResolver} does not resolve views itself, but delegates to other {@link * ViewResolver}s. By default, these other view resolvers are picked up automatically from the application context, - * though they can also be set explicitely by using the {@link #setViewResolvers(List) viewResolvers} property. + * though they can also be set explicitly by using the {@link #setViewResolvers(List) viewResolvers} property. * Note that in order for this view resolver to work properly, the {@link #setOrder(int) order} * property needs to be set to a higher precedence than the others (the default is {@link Ordered#HIGHEST_PRECEDENCE}.) * *

This view resolver uses the requested {@linkplain MediaType media type} to select a suitable {@link View} for a - * 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. If there is no match and - * if the Java Activation Framework (JAF) is present on the class path, {@link FileTypeMap#getContentType(String)} is - * used.
  3. If the previous steps did not result in a media type, the request {@code Accept} header is used.
  4. - *
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. + * 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. + *
  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 + * {@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. + *
+ * 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. * *

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 @@ -78,7 +85,7 @@ import org.springframework.web.util.WebUtils; * override the views provided by the view resolvers. * * @author Arjen Poutsma - * @author Jeremy Grelle + * @author Rostislav Hristov * @see ViewResolver * @see InternalResourceViewResolver * @see BeanNameViewResolver @@ -95,6 +102,10 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport private boolean favorPathExtension = true; + private boolean favorParameter = false; + + private String parameterName = "format"; + private int order = Ordered.HIGHEST_PRECEDENCE; private ConcurrentMap mediaTypes = new ConcurrentHashMap(); @@ -113,7 +124,7 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport /** * Indicates whether the extension of the request path should be used to determine the requested media type, in favor - * of looking at the {@code Accept} header. + * of looking at the {@code Accept} header. The default value is {@code true}. * *

For instance, when this flag is true (the default), a request for {@code /hotels.pdf} will result in * an {@code AbstractPdfView} being resolved, while the {@code Accept} header can be the browser-defined {@code @@ -123,6 +134,26 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport this.favorPathExtension = favorPathExtension; } + /** + * 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 + * 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}. + */ + public void setParameterName(String parameterName) { + this.parameterName = parameterName; + } + /** * Sets the mapping from file extensions to media types. * @@ -200,6 +231,21 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport return mediaTypes; } } + 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 + "')"); + } + List mediaTypes = new ArrayList(); + mediaTypes.add(mediaType); + return mediaTypes; + } + } + } String acceptHeader = request.getHeader(ACCEPT_HEADER); if (StringUtils.hasText(acceptHeader)) { List mediaTypes = MediaType.parseMediaTypes(acceptHeader); @@ -240,6 +286,22 @@ 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. + * + *

This method can be overriden to provide a different algorithm. + * + * @param parameterValue the parameter value (i.e. {@code pdf}). + * @return the media type, if any + */ + protected MediaType getMediaTypeFromParameter(String parameterValue) { + parameterValue = parameterValue.toLowerCase(Locale.ENGLISH); + return mediaTypes.get(parameterValue); + } public View resolveViewName(String viewName, Locale locale) throws Exception { RequestAttributes attrs = RequestContextHolder.getRequestAttributes(); 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 d78c0a8e0f..9eb0e93f90 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 @@ -66,6 +66,17 @@ public class ContentNegotiatingViewResolverTests { assertEquals("Invalid content type", Collections.singletonList(new MediaType("application", "xhtml+xml")), result); } + + @Test + public void getMediaTypeParameter() { + viewResolver.setFavorParameter(true); + viewResolver.setMediaTypes(Collections.singletonMap("html", "application/xhtml+xml")); + MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test"); + request.addParameter("format", "html"); + List result = viewResolver.getMediaTypes(request); + assertEquals("Invalid content type", Collections.singletonList(new MediaType("application", "xhtml+xml")), + result); + } @Test public void getMediaTypeAcceptHeader() {