Configurable UrlPathHelper in PathExtensionContentNegotiationStrategy

This commit also aligns ResourceUrlProvider's and RequestMappingInfo's UrlPathHelper setter/getter signatures.

Issue: SPR-14454
This commit is contained in:
Juergen Hoeller 2016-07-13 15:14:42 +02:00
parent e91c1cd5a7
commit 84afc601b8
5 changed files with 66 additions and 37 deletions

View File

@ -52,41 +52,45 @@ import org.springframework.web.util.WebUtils;
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @since 3.2 * @since 3.2
*/ */
public class PathExtensionContentNegotiationStrategy public class PathExtensionContentNegotiationStrategy extends AbstractMappingContentNegotiationStrategy {
extends AbstractMappingContentNegotiationStrategy {
private static final Log logger = LogFactory.getLog(PathExtensionContentNegotiationStrategy.class);
private static final boolean JAF_PRESENT = ClassUtils.isPresent("javax.activation.FileTypeMap", private static final boolean JAF_PRESENT = ClassUtils.isPresent("javax.activation.FileTypeMap",
PathExtensionContentNegotiationStrategy.class.getClassLoader()); PathExtensionContentNegotiationStrategy.class.getClassLoader());
private static final UrlPathHelper PATH_HELPER = new UrlPathHelper(); private static final Log logger = LogFactory.getLog(PathExtensionContentNegotiationStrategy.class);
static {
PATH_HELPER.setUrlDecode(false);
}
private UrlPathHelper urlPathHelper = new UrlPathHelper();
private boolean useJaf = true; private boolean useJaf = true;
private boolean ignoreUnknownExtensions = true; private boolean ignoreUnknownExtensions = true;
/**
* Create an instance with the given map of file extensions and media types.
*/
public PathExtensionContentNegotiationStrategy(Map<String, MediaType> mediaTypes) {
super(mediaTypes);
}
/** /**
* Create an instance without any mappings to start with. Mappings may be added * Create an instance without any mappings to start with. Mappings may be added
* later on if any extensions are resolved through the Java Activation framework. * later on if any extensions are resolved through the Java Activation framework.
*/ */
public PathExtensionContentNegotiationStrategy() { public PathExtensionContentNegotiationStrategy() {
super(null); this(null);
} }
/**
* Create an instance with the given map of file extensions and media types.
*/
public PathExtensionContentNegotiationStrategy(Map<String, MediaType> mediaTypes) {
super(mediaTypes);
this.urlPathHelper.setUrlDecode(false);
}
/**
* Configure a {@code UrlPathHelper} to use in {@link #getMediaTypeKey}
* in order to derive the lookup path for a target request URL path.
* @since 4.2.8
*/
public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
this.urlPathHelper = urlPathHelper;
}
/** /**
* Whether to use the Java Activation Framework to look up file extensions. * Whether to use the Java Activation Framework to look up file extensions.
@ -113,7 +117,7 @@ public class PathExtensionContentNegotiationStrategy
logger.warn("An HttpServletRequest is required to determine the media type key"); logger.warn("An HttpServletRequest is required to determine the media type key");
return null; return null;
} }
String path = PATH_HELPER.getLookupPathForRequest(request); String path = this.urlPathHelper.getLookupPathForRequest(request);
String filename = WebUtils.extractFullFilenameFromUrlPath(path); String filename = WebUtils.extractFullFilenameFromUrlPath(path);
String extension = StringUtils.getFilenameExtension(filename); String extension = StringUtils.getFilenameExtension(filename);
return (StringUtils.hasText(extension)) ? extension.toLowerCase(Locale.ENGLISH) : null; return (StringUtils.hasText(extension)) ? extension.toLowerCase(Locale.ENGLISH) : null;

View File

@ -523,11 +523,15 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
/** /**
* Set a custom UrlPathHelper to use for the PatternsRequestCondition. * Set a custom UrlPathHelper to use for the PatternsRequestCondition.
* <p>By default this is not set. * <p>By default this is not set.
* @since 4.2.8
*/ */
public void setPathHelper(UrlPathHelper pathHelper) { public void setUrlPathHelper(UrlPathHelper pathHelper) {
this.urlPathHelper = pathHelper; this.urlPathHelper = pathHelper;
} }
/**
* Return a custom UrlPathHelper to use for the PatternsRequestCondition, if any.
*/
public UrlPathHelper getUrlPathHelper() { public UrlPathHelper getUrlPathHelper() {
return this.urlPathHelper; return this.urlPathHelper;
} }
@ -540,24 +544,30 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
this.pathMatcher = pathMatcher; this.pathMatcher = pathMatcher;
} }
/**
* Return a custom PathMatcher to use for the PatternsRequestCondition, if any.
*/
public PathMatcher getPathMatcher() { public PathMatcher getPathMatcher() {
return this.pathMatcher; return this.pathMatcher;
} }
/** /**
* Whether to apply trailing slash matching in PatternsRequestCondition. * Set whether to apply trailing slash matching in PatternsRequestCondition.
* <p>By default this is set to 'true'. * <p>By default this is set to 'true'.
*/ */
public void setTrailingSlashMatch(boolean trailingSlashMatch) { public void setTrailingSlashMatch(boolean trailingSlashMatch) {
this.trailingSlashMatch = trailingSlashMatch; this.trailingSlashMatch = trailingSlashMatch;
} }
/**
* Return whether to apply trailing slash matching in PatternsRequestCondition.
*/
public boolean useTrailingSlashMatch() { public boolean useTrailingSlashMatch() {
return this.trailingSlashMatch; return this.trailingSlashMatch;
} }
/** /**
* Whether to apply suffix pattern matching in PatternsRequestCondition. * Set whether to apply suffix pattern matching in PatternsRequestCondition.
* <p>By default this is set to 'true'. * <p>By default this is set to 'true'.
* @see #setRegisteredSuffixPatternMatch(boolean) * @see #setRegisteredSuffixPatternMatch(boolean)
*/ */
@ -565,14 +575,17 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
this.suffixPatternMatch = suffixPatternMatch; this.suffixPatternMatch = suffixPatternMatch;
} }
/**
* Return whether to apply suffix pattern matching in PatternsRequestCondition.
*/
public boolean useSuffixPatternMatch() { public boolean useSuffixPatternMatch() {
return this.suffixPatternMatch; return this.suffixPatternMatch;
} }
/** /**
* Whether suffix pattern matching should be restricted to registered * Set whether suffix pattern matching should be restricted to registered
* file extensions only. Setting this property also sets * file extensions only. Setting this property also sets
* suffixPatternMatch=true and requires that a * {@code suffixPatternMatch=true} and requires that a
* {@link #setContentNegotiationManager} is also configured in order to * {@link #setContentNegotiationManager} is also configured in order to
* obtain the registered file extensions. * obtain the registered file extensions.
*/ */
@ -581,6 +594,10 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
this.suffixPatternMatch = (registeredSuffixPatternMatch || this.suffixPatternMatch); this.suffixPatternMatch = (registeredSuffixPatternMatch || this.suffixPatternMatch);
} }
/**
* Return whether suffix pattern matching should be restricted to registered
* file extensions only.
*/
public boolean useRegisteredSuffixPatternMatch() { public boolean useRegisteredSuffixPatternMatch() {
return this.registeredSuffixPatternMatch; return this.registeredSuffixPatternMatch;
} }
@ -601,10 +618,14 @@ public final class RequestMappingInfo implements RequestCondition<RequestMapping
* Set the ContentNegotiationManager to use for the ProducesRequestCondition. * Set the ContentNegotiationManager to use for the ProducesRequestCondition.
* <p>By default this is not set. * <p>By default this is not set.
*/ */
public void setContentNegotiationManager(ContentNegotiationManager manager) { public void setContentNegotiationManager(ContentNegotiationManager contentNegotiationManager) {
this.contentNegotiationManager = manager; this.contentNegotiationManager = contentNegotiationManager;
} }
/**
* Return the ContentNegotiationManager to use for the ProducesRequestCondition,
* if any.
*/
public ContentNegotiationManager getContentNegotiationManager() { public ContentNegotiationManager getContentNegotiationManager() {
return this.contentNegotiationManager; return this.contentNegotiationManager;
} }

View File

@ -118,7 +118,7 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
@Override @Override
public void afterPropertiesSet() { public void afterPropertiesSet() {
this.config = new RequestMappingInfo.BuilderConfiguration(); this.config = new RequestMappingInfo.BuilderConfiguration();
this.config.setPathHelper(getUrlPathHelper()); this.config.setUrlPathHelper(getUrlPathHelper());
this.config.setPathMatcher(getPathMatcher()); this.config.setPathMatcher(getPathMatcher());
this.config.setSuffixPatternMatch(this.useSuffixPatternMatch); this.config.setSuffixPatternMatch(this.useSuffixPatternMatch);
this.config.setTrailingSlashMatch(this.useTrailingSlashMatch); this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);

View File

@ -27,6 +27,7 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.UrlPathHelper;
/** /**
* A filter that wraps the {@link HttpServletResponse} and overrides its * A filter that wraps the {@link HttpServletResponse} and overrides its
@ -96,13 +97,14 @@ public class ResourceUrlEncodingFilter extends OncePerRequestFilter {
private void initLookupPath(ResourceUrlProvider urlProvider) { private void initLookupPath(ResourceUrlProvider urlProvider) {
if (this.indexLookupPath == null) { if (this.indexLookupPath == null) {
String requestUri = urlProvider.getPathHelper().getRequestUri(this.request); UrlPathHelper pathHelper = urlProvider.getUrlPathHelper();
String lookupPath = urlProvider.getPathHelper().getLookupPathForRequest(this.request); String requestUri = pathHelper.getRequestUri(this.request);
String lookupPath = pathHelper.getLookupPathForRequest(this.request);
this.indexLookupPath = requestUri.lastIndexOf(lookupPath); this.indexLookupPath = requestUri.lastIndexOf(lookupPath);
this.prefixLookupPath = requestUri.substring(0, this.indexLookupPath); this.prefixLookupPath = requestUri.substring(0, this.indexLookupPath);
if ("/".equals(lookupPath) && !"/".equals(requestUri)) { if ("/".equals(lookupPath) && !"/".equals(requestUri)) {
String contextPath = urlProvider.getPathHelper().getContextPath(this.request); String contextPath = pathHelper.getContextPath(this.request);
if (requestUri.equals(contextPath)) { if (requestUri.equals(contextPath)) {
this.indexLookupPath = requestUri.length(); this.indexLookupPath = requestUri.length();
this.prefixLookupPath = requestUri; this.prefixLookupPath = requestUri;

View File

@ -51,7 +51,7 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
protected final Log logger = LogFactory.getLog(getClass()); protected final Log logger = LogFactory.getLog(getClass());
private UrlPathHelper pathHelper = new UrlPathHelper(); private UrlPathHelper urlPathHelper = new UrlPathHelper();
private PathMatcher pathMatcher = new AntPathMatcher(); private PathMatcher pathMatcher = new AntPathMatcher();
@ -65,15 +65,16 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
* {@link #getForRequestUrl(javax.servlet.http.HttpServletRequest, String)} * {@link #getForRequestUrl(javax.servlet.http.HttpServletRequest, String)}
* in order to derive the lookup path for a target request URL path. * in order to derive the lookup path for a target request URL path.
*/ */
public void setUrlPathHelper(UrlPathHelper pathHelper) { public void setUrlPathHelper(UrlPathHelper urlPathHelper) {
this.pathHelper = pathHelper; this.urlPathHelper = urlPathHelper;
} }
/** /**
* Return the configured {@code UrlPathHelper}. * Return the configured {@code UrlPathHelper}.
* @since 4.2.8
*/ */
public UrlPathHelper getPathHelper() { public UrlPathHelper getUrlPathHelper() {
return this.pathHelper; return this.urlPathHelper;
} }
/** /**
@ -135,6 +136,7 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
} }
} }
protected void detectResourceHandlers(ApplicationContext appContext) { protected void detectResourceHandlers(ApplicationContext appContext) {
logger.debug("Looking for resource handler mappings"); logger.debug("Looking for resource handler mappings");
@ -158,7 +160,6 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
} }
} }
/** /**
* A variation on {@link #getForLookupPath(String)} that accepts a full request * A variation on {@link #getForLookupPath(String)} that accepts a full request
* URL path (i.e. including context and servlet path) and returns the full request * URL path (i.e. including context and servlet path) and returns the full request
@ -181,8 +182,9 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
} }
private int getLookupPathIndex(HttpServletRequest request) { private int getLookupPathIndex(HttpServletRequest request) {
String requestUri = getPathHelper().getRequestUri(request); UrlPathHelper pathHelper = getUrlPathHelper();
String lookupPath = getPathHelper().getLookupPathForRequest(request); String requestUri = pathHelper.getRequestUri(request);
String lookupPath = pathHelper.getLookupPathForRequest(request);
return requestUri.indexOf(lookupPath); return requestUri.indexOf(lookupPath);
} }