|
|
|
@ -64,26 +64,32 @@ import org.springframework.web.util.WebUtils; |
|
|
|
* request. This media type is determined by using the following criteria: |
|
|
|
* request. This media type is determined by using the following criteria: |
|
|
|
* <ol> |
|
|
|
* <ol> |
|
|
|
* <li>If the requested path has a file extension and if the {@link #setFavorPathExtension(boolean)} property is |
|
|
|
* <li>If the requested path has a file extension and if the {@link #setFavorPathExtension(boolean)} property is |
|
|
|
* <code>true</code>, the {@link #setMediaTypes(Map) mediaTypes} property is inspected for a matching media type.</li> |
|
|
|
* {@code true}, the {@link #setMediaTypes(Map) mediaTypes} property is inspected for a matching media type.</li> |
|
|
|
* <li>If the request contains a parameter defining the extension and if the {@link #setFavorParameter(boolean)} |
|
|
|
* <li>If the request contains a parameter defining the extension and if the {@link #setFavorParameter(boolean)} |
|
|
|
* property is <code>true</code>, the {@link #setMediaTypes(Map) mediaTypes} property is inspected for a matching media |
|
|
|
* property is <code>true</code>, the {@link #setMediaTypes(Map) mediaTypes} property is inspected for a matching |
|
|
|
* type. The default name of the parameter is <code>format</code> and it can be configured using the |
|
|
|
* media type. The default name of the parameter is <code>format</code> and it can be configured using the |
|
|
|
* {@link #setParameterName(String) parameterName} property.</li> |
|
|
|
* {@link #setParameterName(String) parameterName} property.</li> |
|
|
|
* <li>If there is no match and if the Java Activation Framework (JAF) is present on the class path, {@link |
|
|
|
* <li>If there is no match in the {@link #setMediaTypes(Map) mediaTypes} property and if the Java Activation |
|
|
|
* FileTypeMap#getContentType(String)} is used.</li> |
|
|
|
* Framework (JAF) is present on the class path, {@link FileTypeMap#getContentType(String)} is used instead.</li> |
|
|
|
* <li>If the previous steps did not result in a media type, the request {@code Accept} header is used.</li> |
|
|
|
* <li>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.</li> |
|
|
|
* </ol> |
|
|
|
* </ol> |
|
|
|
|
|
|
|
* |
|
|
|
* Once the requested media type has been determined, this resolver queries each delegate view resolver for a |
|
|
|
* 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} |
|
|
|
* {@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. |
|
|
|
* with the view's {@linkplain View#getContentType() content type}). The most compatible view is returned. |
|
|
|
* |
|
|
|
* |
|
|
|
|
|
|
|
* <p>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. |
|
|
|
|
|
|
|
* |
|
|
|
* <p>For example, if the request path is {@code /view.html}, this view resolver will look for a view that has the |
|
|
|
* <p>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 |
|
|
|
* {@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. |
|
|
|
* text/html} request {@code Accept} header has the same result. |
|
|
|
* |
|
|
|
* |
|
|
|
* <p>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 Arjen Poutsma |
|
|
|
* @author Rostislav Hristov |
|
|
|
* @author Rostislav Hristov |
|
|
|
* @see ViewResolver |
|
|
|
* @see ViewResolver |
|
|
|
@ -103,7 +109,9 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport |
|
|
|
private boolean favorPathExtension = true; |
|
|
|
private boolean favorPathExtension = true; |
|
|
|
|
|
|
|
|
|
|
|
private boolean favorParameter = false; |
|
|
|
private boolean favorParameter = false; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private boolean ignoreAcceptHeader = false; |
|
|
|
|
|
|
|
|
|
|
|
private String parameterName = "format"; |
|
|
|
private String parameterName = "format"; |
|
|
|
|
|
|
|
|
|
|
|
private int order = Ordered.HIGHEST_PRECEDENCE; |
|
|
|
private int order = Ordered.HIGHEST_PRECEDENCE; |
|
|
|
@ -114,6 +122,8 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport |
|
|
|
|
|
|
|
|
|
|
|
private List<ViewResolver> viewResolvers; |
|
|
|
private List<ViewResolver> viewResolvers; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private MediaType defaultContentType; |
|
|
|
|
|
|
|
|
|
|
|
public void setOrder(int order) { |
|
|
|
public void setOrder(int order) { |
|
|
|
this.order = 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 |
|
|
|
* Indicates whether a request parameter should be used to determine the requested media type, in favor of looking at |
|
|
|
* of looking at the {@code Accept} header. The default value is {@code false}. |
|
|
|
* the {@code Accept} header. The default value is {@code false}. |
|
|
|
* |
|
|
|
* |
|
|
|
* <p>For instance, when this flag is <code>true</code>, a request for {@code /hotels?format=pdf} will result in |
|
|
|
* <p>For instance, when this flag is <code>true</code>, a request for {@code /hotels?format=pdf} will result in an |
|
|
|
* an {@code AbstractPdfView} being resolved, while the {@code Accept} header can be the browser-defined {@code |
|
|
|
* {@code AbstractPdfView} being resolved, while the {@code Accept} header can be the browser-defined {@code |
|
|
|
* text/html,application/xhtml+xml}. |
|
|
|
* text/html,application/xhtml+xml}. |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public void setFavorParameter(boolean favorParameter) { |
|
|
|
public void setFavorParameter(boolean favorParameter) { |
|
|
|
this.favorParameter = favorParameter; |
|
|
|
this.favorParameter = favorParameter; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Sets the parameter name that can be used to determine the requested media type if the |
|
|
|
* Sets the parameter name that can be used to determine the requested media type if the {@link |
|
|
|
* {@link #setFavorParameter(boolean)} property is {@code true}. The default parameter name is {@code format}. |
|
|
|
* #setFavorParameter(boolean)} property is {@code true}. The default parameter name is {@code format}. |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
public void setParameterName(String parameterName) { |
|
|
|
public void setParameterName(String parameterName) { |
|
|
|
this.parameterName = 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. |
|
|
|
* 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<View> defaultViews) { |
|
|
|
public void setDefaultViews(List<View> defaultViews) { |
|
|
|
this.defaultViews = 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. |
|
|
|
* Sets the view resolvers to be wrapped by this view resolver. |
|
|
|
* |
|
|
|
* |
|
|
|
@ -231,14 +258,15 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport |
|
|
|
return mediaTypes; |
|
|
|
return mediaTypes; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
if (favorParameter) { |
|
|
|
if (favorParameter) { |
|
|
|
if (request.getParameter(parameterName) != null) { |
|
|
|
if (request.getParameter(parameterName) != null) { |
|
|
|
String parameterValue = request.getParameter(parameterName); |
|
|
|
String parameterValue = request.getParameter(parameterName); |
|
|
|
MediaType mediaType = getMediaTypeFromParameter(parameterValue); |
|
|
|
MediaType mediaType = getMediaTypeFromParameter(parameterValue); |
|
|
|
if (mediaType != null) { |
|
|
|
if (mediaType != null) { |
|
|
|
if (logger.isDebugEnabled()) { |
|
|
|
if (logger.isDebugEnabled()) { |
|
|
|
logger.debug("Requested media type is '" + mediaType + "' (based on parameter '" + |
|
|
|
logger.debug( |
|
|
|
parameterValue + "')"); |
|
|
|
"Requested media type is '" + mediaType + "' (based on parameter '" + parameterName + |
|
|
|
|
|
|
|
"'='" + parameterValue + "')"); |
|
|
|
} |
|
|
|
} |
|
|
|
List<MediaType> mediaTypes = new ArrayList<MediaType>(); |
|
|
|
List<MediaType> mediaTypes = new ArrayList<MediaType>(); |
|
|
|
mediaTypes.add(mediaType); |
|
|
|
mediaTypes.add(mediaType); |
|
|
|
@ -246,13 +274,18 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
String acceptHeader = request.getHeader(ACCEPT_HEADER); |
|
|
|
if (!ignoreAcceptHeader) { |
|
|
|
if (StringUtils.hasText(acceptHeader)) { |
|
|
|
String acceptHeader = request.getHeader(ACCEPT_HEADER); |
|
|
|
List<MediaType> mediaTypes = MediaType.parseMediaTypes(acceptHeader); |
|
|
|
if (StringUtils.hasText(acceptHeader)) { |
|
|
|
if (logger.isDebugEnabled()) { |
|
|
|
List<MediaType> mediaTypes = MediaType.parseMediaTypes(acceptHeader); |
|
|
|
logger.debug("Requested media types are " + mediaTypes + " (based on Accept header)"); |
|
|
|
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 { |
|
|
|
else { |
|
|
|
return Collections.emptyList(); |
|
|
|
return Collections.emptyList(); |
|
|
|
@ -286,12 +319,12 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport |
|
|
|
} |
|
|
|
} |
|
|
|
return mediaType; |
|
|
|
return mediaType; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Determines the {@link MediaType} for the given parameter value. |
|
|
|
* Determines the {@link MediaType} for the given parameter value. |
|
|
|
* |
|
|
|
* |
|
|
|
* <p>The default implementation will check the {@linkplain #setMediaTypes(Map) media types} property for a |
|
|
|
* <p>The default implementation will check the {@linkplain #setMediaTypes(Map) media types} property for a defined |
|
|
|
* defined mapping. |
|
|
|
* mapping. |
|
|
|
* |
|
|
|
* |
|
|
|
* <p>This method can be overriden to provide a different algorithm. |
|
|
|
* <p>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 class ActivationMediaTypeFactory { |
|
|
|
|
|
|
|
|
|
|
|
private static final FileTypeMap fileTypeMap; |
|
|
|
private static final FileTypeMap fileTypeMap; |
|
|
|
|