This commit is contained in:
Rossen Stoyanchev
2015-09-30 14:04:38 -04:00
parent 39726740e5
commit 42e5ad4111
5 changed files with 225 additions and 176 deletions

View File

@@ -23,21 +23,64 @@ import org.springframework.http.MediaType;
import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.accept.ContentNegotiationManagerFactoryBean;
import org.springframework.web.accept.ContentNegotiationStrategy;
import org.springframework.web.accept.FixedContentNegotiationStrategy;
import org.springframework.web.accept.HeaderContentNegotiationStrategy;
import org.springframework.web.accept.ParameterContentNegotiationStrategy;
import org.springframework.web.accept.PathExtensionContentNegotiationStrategy;
/**
* Help to create and configure a {@link ContentNegotiationManager}.
* Creates a {@code ContentNegotiationManager} and configures it with
* one or more {@link ContentNegotiationStrategy} instances. The following shows
* the resulting strategy instances, the methods used to configured them, and
* whether enabled by default:
*
* <p>By default strategies for checking the extension of the request path and
* the {@code Accept} header are registered. The path extension check will perform
* lookups through the {@link ServletContext} and the Java Activation Framework
* (if present) unless {@linkplain #mediaTypes(Map) media types} are configured.
* <table>
* <tr>
* <td>{@link #favorPathExtension}</td>
* <td>{@link PathExtensionContentNegotiationStrategy}</td>
* <td>Yes</td>
* </tr>
* <tr>
* <td>{@link #favorParameter}</td>
* <td>{@link ParameterContentNegotiationStrategy}</td>
* <td>-</td>
* </tr>
* <tr>
* <td>{@link #ignoreAcceptHeader}</td>
* <td>{@link HeaderContentNegotiationStrategy}</td>
* <td>Yes</td>
* </tr>
* <tr>
* <td>{@link #defaultContentType}</td>
* <td>{@link FixedContentNegotiationStrategy}</td>
* <td>-</td>
* </tr>
* <tr>
* <td>{@link #defaultContentTypeStrategy}</td>
* <td>{@link ContentNegotiationStrategy}</td>
* <td>-</td>
* </tr>
* </table>
*
* <p>The order in which strategies are configured is fixed. You can only turn
* them on or off.
*
* <p>For the path extension and parameter strategies you may explicitly add
* {@link #mediaType MediaType mappings}. Those will be used to resolve path
* extensions and/or a query parameter value such as "json" to a concrete media
* type such as "application/json".
*
* <p>The path extension strategy will also use {@link ServletContext#getMimeType}
* and the Java Activation framework (JAF), if available, to resolve a path
* extension to a MediaType. You may however {@link #useJaf suppress} the use
* of JAF.
*
* @author Rossen Stoyanchev
* @since 3.2
*/
public class ContentNegotiationConfigurer {
private final ContentNegotiationManagerFactoryBean factoryBean =
private final ContentNegotiationManagerFactoryBean factory =
new ContentNegotiationManagerFactoryBean();
private final Map<String, MediaType> mediaTypes = new HashMap<String, MediaType>();
@@ -47,25 +90,32 @@ public class ContentNegotiationConfigurer {
* Class constructor with {@link javax.servlet.ServletContext}.
*/
public ContentNegotiationConfigurer(ServletContext servletContext) {
this.factoryBean.setServletContext(servletContext);
this.factory.setServletContext(servletContext);
}
/**
* Indicate whether the extension of the request path should be used to determine
* the requested media type with the <em>highest priority</em>.
* <p>By default this value is set to {@code true} in which case a request
* Whether the path extension in the URL path should be used to determine
* the requested media type.
* <p>By default this is set to {@code true} in which case a request
* for {@code /hotels.pdf} will be interpreted as a request for
* {@code "application/pdf"} regardless of the {@code Accept} header.
* {@code "application/pdf"} regardless of the 'Accept' header.
*/
public ContentNegotiationConfigurer favorPathExtension(boolean favorPathExtension) {
this.factoryBean.setFavorPathExtension(favorPathExtension);
this.factory.setFavorPathExtension(favorPathExtension);
return this;
}
/**
* Add mappings from file extensions to media types.
* <p>If this property is not set, the Java Action Framework, if available, may
* still be used in conjunction with {@link #favorPathExtension(boolean)}.
* Add a mapping from a key, extracted from a path extension or a query
* parameter, to a MediaType. This is required in order for the parameter
* strategy to work. The path extension strategy will also try
* {@link ServletContext#getMimeType} and JAF if it is present and is not
* suppressed via {@link #useJaf}.
* @param extension the key to look up
* @param mediaType the media type
* @see #mediaTypes(Map)
* @see #replaceMediaTypes(Map)
*/
public ContentNegotiationConfigurer mediaType(String extension, MediaType mediaType) {
this.mediaTypes.put(extension, mediaType);
@@ -73,9 +123,9 @@ public class ContentNegotiationConfigurer {
}
/**
* Add mappings from file extensions to media types.
* <p>If this property is not set, the Java Action Framework, if available, may
* still be used in conjunction with {@link #favorPathExtension(boolean)}.
* An alternative to {@link #mediaType}.
* @see #mediaType(String, MediaType)
* @see #replaceMediaTypes(Map)
*/
public ContentNegotiationConfigurer mediaTypes(Map<String, MediaType> mediaTypes) {
if (mediaTypes != null) {
@@ -85,9 +135,9 @@ public class ContentNegotiationConfigurer {
}
/**
* Add mappings from file extensions to media types replacing any previous mappings.
* <p>If this property is not set, the Java Action Framework, if available, may
* still be used in conjunction with {@link #favorPathExtension(boolean)}.
* Similar to {@link #mediaType} but for replacing existing mappings.
* @see #mediaType(String, MediaType)
* @see #mediaTypes(Map)
*/
public ContentNegotiationConfigurer replaceMediaTypes(Map<String, MediaType> mediaTypes) {
this.mediaTypes.clear();
@@ -96,101 +146,83 @@ public class ContentNegotiationConfigurer {
}
/**
* Whether to ignore requests that have a file extension that does not match
* any mapped media types. Setting this to {@code false} will result in a
* {@code HttpMediaTypeNotAcceptableException} when there is no match.
*
* Whether to ignore requests with path extension that cannot be resolved
* to any media type. Setting this to {@code false} will result in an
* {@code HttpMediaTypeNotAcceptableException} if there is no match.
* <p>By default this is set to {@code true}.
*/
public ContentNegotiationConfigurer ignoreUnknownPathExtensions(boolean ignore) {
this.factoryBean.setIgnoreUnknownPathExtensions(ignore);
this.factory.setIgnoreUnknownPathExtensions(ignore);
return this;
}
/**
* Indicate whether to use the Java Activation Framework as a fallback option
* to map from file extensions to media types. This is used only when
* {@link #favorPathExtension(boolean)} is set to {@code true}.
* <p>The default value is {@code true}.
* @see #parameterName
* @see #mediaTypes(Map)
* When {@link #favorPathExtension} is set, this property determines whether
* to allow use of JAF (Java Activation Framework) to resolve a path
* extension to a specific MediaType.
* <p>By default this is not set in which case
* {@code PathExtensionContentNegotiationStrategy} will use JAF if available.
*/
public ContentNegotiationConfigurer useJaf(boolean useJaf) {
this.factoryBean.setUseJaf(useJaf);
this.factory.setUseJaf(useJaf);
return this;
}
/**
* Indicate whether a request parameter should be used to determine the
* requested media type with the <em>2nd highest priority</em>, i.e.
* after path extensions but before the {@code Accept} header.
* <p>The default value is {@code false}. If set to to {@code true}, a request
* for {@code /hotels?format=pdf} will be interpreted as a request for
* {@code "application/pdf"} regardless of the {@code Accept} header.
* <p>To use this option effectively you must also configure the MediaType
* type mappings via {@link #mediaTypes(Map)}.
* Whether a request parameter ("format" by default) should be used to
* determine the requested media type. For this option to work you must
* register {@link #mediaType(String, MediaType) media type mappings}.
* <p>By default this is set to {@code false}.
* @see #parameterName(String)
*/
public ContentNegotiationConfigurer favorParameter(boolean favorParameter) {
this.factoryBean.setFavorParameter(favorParameter);
this.factory.setFavorParameter(favorParameter);
return this;
}
/**
* Set the parameter name that can be used to determine the requested media type
* if the {@link #favorParameter(boolean)} property is {@code true}.
* Set the query parameter name to use when {@link #favorParameter} is on.
* <p>The default parameter name is {@code "format"}.
*/
public ContentNegotiationConfigurer parameterName(String parameterName) {
this.factoryBean.setParameterName(parameterName);
this.factory.setParameterName(parameterName);
return this;
}
/**
* Indicate whether the HTTP {@code Accept} header should be ignored altogether.
* If set the {@code Accept} header is checked at the
* <em>3rd highest priority</em>, i.e. after the request path extension and
* possibly a request parameter if configured.
* Whether to disable checking the 'Accept' request header.
* <p>By default this value is set to {@code false}.
*/
public ContentNegotiationConfigurer ignoreAcceptHeader(boolean ignoreAcceptHeader) {
this.factoryBean.setIgnoreAcceptHeader(ignoreAcceptHeader);
this.factory.setIgnoreAcceptHeader(ignoreAcceptHeader);
return this;
}
/**
* Set the default content type to use when no content type was requested.
* <p>Note that internally this method creates and adds a
* {@link org.springframework.web.accept.FixedContentNegotiationStrategy
* FixedContentNegotiationStrategy}. Alternatively you can also provide a
* custom strategy via {@link #defaultContentTypeStrategy}.
* Set the default content type to use when no content type is requested.
* <p>By default this is not set.
* @see #defaultContentTypeStrategy
*/
public ContentNegotiationConfigurer defaultContentType(MediaType defaultContentType) {
this.factoryBean.setDefaultContentType(defaultContentType);
this.factory.setDefaultContentType(defaultContentType);
return this;
}
/**
* Configure a custom {@link ContentNegotiationStrategy} to use to determine
* the default content type to use when no content type was requested.
* <p>However also consider using {@link #defaultContentType} which provides
* a simpler alternative to doing the same.
* Set a custom {@link ContentNegotiationStrategy} to use to determine
* the content type to use when no content type is requested.
* @see #defaultContentType
* @since 4.1.2
*/
public ContentNegotiationConfigurer defaultContentTypeStrategy(ContentNegotiationStrategy defaultStrategy) {
this.factoryBean.setDefaultContentTypeStrategy(defaultStrategy);
this.factory.setDefaultContentTypeStrategy(defaultStrategy);
return this;
}
/**
* Return the configured {@link ContentNegotiationManager} instance
*/
protected ContentNegotiationManager getContentNegotiationManager() throws Exception {
if (!this.mediaTypes.isEmpty()) {
this.factoryBean.addMediaTypes(mediaTypes);
}
this.factoryBean.afterPropertiesSet();
return this.factoryBean.getObject();
this.factory.addMediaTypes(this.mediaTypes);
this.factory.afterPropertiesSet();
return this.factory.getObject();
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2014 the original author or authors.
* Copyright 2002-2015 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.
@@ -20,8 +20,8 @@ import org.springframework.util.PathMatcher;
import org.springframework.web.util.UrlPathHelper;
/**
* Helps with configuring HandlerMappings path matching options such as trailing slash match,
* suffix registration, path matcher and path helper.
* Helps with configuring HandlerMappings path matching options such as trailing
* slash match, suffix registration, path matcher and path helper.
*
* <p>Configured path matcher and path helper instances are shared for:
* <ul>
@@ -37,11 +37,11 @@ import org.springframework.web.util.UrlPathHelper;
*/
public class PathMatchConfigurer {
private Boolean useSuffixPatternMatch;
private Boolean suffixPatternMatch;
private Boolean useTrailingSlashMatch;
private Boolean trailingSlashMatch;
private Boolean useRegisteredSuffixPatternMatch;
private Boolean registeredSuffixPatternMatch;
private UrlPathHelper urlPathHelper;
@@ -51,10 +51,11 @@ public class PathMatchConfigurer {
/**
* Whether to use suffix pattern match (".*") when matching patterns to
* requests. If enabled a method mapped to "/users" also matches to "/users.*".
* <p>The default value is {@code true}.
* <p>By default this is set to {@code true}.
* @see #registeredSuffixPatternMatch
*/
public PathMatchConfigurer setUseSuffixPatternMatch(Boolean useSuffixPatternMatch) {
this.useSuffixPatternMatch = useSuffixPatternMatch;
public PathMatchConfigurer setUseSuffixPatternMatch(Boolean suffixPatternMatch) {
this.suffixPatternMatch = suffixPatternMatch;
return this;
}
@@ -63,28 +64,24 @@ public class PathMatchConfigurer {
* If enabled a method mapped to "/users" also matches to "/users/".
* <p>The default value is {@code true}.
*/
public PathMatchConfigurer setUseTrailingSlashMatch(Boolean useTrailingSlashMatch) {
this.useTrailingSlashMatch = useTrailingSlashMatch;
public PathMatchConfigurer setUseTrailingSlashMatch(Boolean trailingSlashMatch) {
this.trailingSlashMatch = trailingSlashMatch;
return this;
}
/**
* Whether to use suffix pattern match for registered file extensions only
* when matching patterns to requests.
* <p>If enabled, a controller method mapped to "/users" also matches to
* "/users.json" assuming ".json" is a file extension registered with the
* provided {@link org.springframework.web.accept.ContentNegotiationManager}.</p>
* <p>The {@link org.springframework.web.accept.ContentNegotiationManager} can be customized
* using a {@link ContentNegotiationConfigurer}.</p>
* <p>If enabled, this flag also enables
* {@link #setUseSuffixPatternMatch(Boolean) useSuffixPatternMatch}. The
* default value is {@code false}.</p>
* @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
* @see ContentNegotiationConfigurer
*
* Whether suffix pattern matching should work only against path extensions
* explicitly registered when you
* {@link WebMvcConfigurer#configureContentNegotiation configure content
* negotiation}. This is generally recommended to reduce ambiguity and to
* avoid issues such as when a "." appears in the path for other reasons.
* <p>By default this is set to "false".
* @see WebMvcConfigurer#configureContentNegotiation
*/
public PathMatchConfigurer setUseRegisteredSuffixPatternMatch(Boolean useRegisteredSuffixPatternMatch) {
this.useRegisteredSuffixPatternMatch = useRegisteredSuffixPatternMatch;
public PathMatchConfigurer setUseRegisteredSuffixPatternMatch(
Boolean registeredSuffixPatternMatch) {
this.registeredSuffixPatternMatch = registeredSuffixPatternMatch;
return this;
}
@@ -110,15 +107,15 @@ public class PathMatchConfigurer {
}
public Boolean isUseSuffixPatternMatch() {
return this.useSuffixPatternMatch;
return this.suffixPatternMatch;
}
public Boolean isUseTrailingSlashMatch() {
return this.useTrailingSlashMatch;
return this.trailingSlashMatch;
}
public Boolean isUseRegisteredSuffixPatternMatch() {
return this.useRegisteredSuffixPatternMatch;
return this.registeredSuffixPatternMatch;
}
public UrlPathHelper getUrlPathHelper() {

View File

@@ -78,19 +78,11 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
}
/**
* Whether to use suffix pattern match for registered file extensions only
* when matching patterns to requests.
* <p>If enabled, a controller method mapped to "/users" also matches to
* "/users.json" assuming ".json" is a file extension registered with the
* provided {@link #setContentNegotiationManager(ContentNegotiationManager)
* contentNegotiationManager}. This can be useful for allowing only specific
* URL extensions to be used as well as in cases where a "." in the URL path
* can lead to ambiguous interpretation of path variable content, (e.g. given
* "/users/{user}" and incoming URLs such as "/users/john.j.joe" and
* "/users/john.j.joe.json").
* <p>If enabled, this flag also enables
* {@link #setUseSuffixPatternMatch(boolean) useSuffixPatternMatch}. The
* default value is {@code false}.
* Whether suffix pattern matching should work only against path extensions
* explicitly registered with the {@link ContentNegotiationManager}. This
* is generally recommended to reduce ambiguity and to avoid issues such as
* when a "." appears in the path for other reasons.
* <p>By default this is set to "false".
*/
public void setUseRegisteredSuffixPatternMatch(boolean useRegisteredSuffixPatternMatch) {
this.useRegisteredSuffixPatternMatch = useRegisteredSuffixPatternMatch;