Skip to content

Commit

Permalink
Polishing and minor refactoring in HandlerMappingIntrospector
Browse files Browse the repository at this point in the history
Closes gh-30127
  • Loading branch information
rstoyanchev committed Mar 20, 2023
1 parent 8010de8 commit 202fa5c
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 83 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public class HandlerMappingIntrospector
@Nullable
private List<HandlerMapping> handlerMappings;

private Map<HandlerMapping, PathPatternMatchableHandlerMapping> pathPatternHandlerMappings = Collections.emptyMap();
private Map<HandlerMapping, PathPatternMatchableHandlerMapping> pathPatternMappings = Collections.emptyMap();


@Override
Expand All @@ -95,10 +95,55 @@ public void afterPropertiesSet() {
if (this.handlerMappings == null) {
Assert.notNull(this.applicationContext, "No ApplicationContext");
this.handlerMappings = initHandlerMappings(this.applicationContext);
this.pathPatternHandlerMappings = initPathPatternMatchableHandlerMappings(this.handlerMappings);

this.pathPatternMappings = this.handlerMappings.stream()
.filter(m -> m instanceof MatchableHandlerMapping hm && hm.getPatternParser() != null)
.map(mapping -> (MatchableHandlerMapping) mapping)
.collect(Collectors.toMap(mapping -> mapping, PathPatternMatchableHandlerMapping::new));
}
}

private static List<HandlerMapping> initHandlerMappings(ApplicationContext context) {

Map<String, HandlerMapping> beans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);

if (!beans.isEmpty()) {
List<HandlerMapping> mappings = new ArrayList<>(beans.values());
AnnotationAwareOrderComparator.sort(mappings);
return Collections.unmodifiableList(mappings);
}

return Collections.unmodifiableList(initFallback(context));
}

private static List<HandlerMapping> initFallback(ApplicationContext applicationContext) {
Properties properties;
try {
Resource resource = new ClassPathResource("DispatcherServlet.properties", DispatcherServlet.class);
properties = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load DispatcherServlet.properties: " + ex.getMessage());
}

String value = properties.getProperty(HandlerMapping.class.getName());
String[] names = StringUtils.commaDelimitedListToStringArray(value);
List<HandlerMapping> result = new ArrayList<>(names.length);
for (String name : names) {
try {
Class<?> clazz = ClassUtils.forName(name, DispatcherServlet.class.getClassLoader());
Object mapping = applicationContext.getAutowireCapableBeanFactory().createBean(clazz);
result.add((HandlerMapping) mapping);
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException("Could not find default HandlerMapping [" + name + "]");
}
}
return result;
}


/**
* Return the configured or detected {@code HandlerMapping}s.
*/
Expand All @@ -109,27 +154,27 @@ public List<HandlerMapping> getHandlerMappings() {

/**
* Find the {@link HandlerMapping} that would handle the given request and
* return it as a {@link MatchableHandlerMapping} that can be used to test
* request-matching criteria.
* <p>If the matching HandlerMapping is not an instance of
* {@link MatchableHandlerMapping}, an IllegalStateException is raised.
* return a {@link MatchableHandlerMapping} to use for path matching.
* @param request the current request
* @return the resolved matcher, or {@code null}
* @return the resolved {@code MatchableHandlerMapping}, or {@code null}
* @throws IllegalStateException if the matching HandlerMapping is not an
* instance of {@link MatchableHandlerMapping}
* @throws Exception if any of the HandlerMapping's raise an exception
*/
@Nullable
public MatchableHandlerMapping getMatchableHandlerMapping(HttpServletRequest request) throws Exception {
HttpServletRequest wrappedRequest = new AttributesPreservingRequest(request);
return doWithMatchingMapping(wrappedRequest, false, (matchedMapping, executionChain) -> {
if (matchedMapping instanceof MatchableHandlerMapping matchableHandlerMapping) {
PathPatternMatchableHandlerMapping mapping = this.pathPatternHandlerMappings.get(matchedMapping);
if (mapping != null) {

return doWithHandlerMapping(wrappedRequest, false, (mapping, executionChain) -> {
if (mapping instanceof MatchableHandlerMapping) {
PathPatternMatchableHandlerMapping pathPatternMapping = this.pathPatternMappings.get(mapping);
if (pathPatternMapping != null) {
RequestPath requestPath = ServletRequestPathUtils.getParsedRequestPath(wrappedRequest);
return new PathSettingHandlerMapping(mapping, requestPath);
return new LookupPathMatchableHandlerMapping(pathPatternMapping, requestPath);
}
else {
String lookupPath = (String) wrappedRequest.getAttribute(UrlPathHelper.PATH_ATTRIBUTE);
return new PathSettingHandlerMapping(matchableHandlerMapping, lookupPath);
return new LookupPathMatchableHandlerMapping((MatchableHandlerMapping) mapping, lookupPath);
}
}
throw new IllegalStateException("HandlerMapping is not a MatchableHandlerMapping");
Expand All @@ -140,7 +185,7 @@ public MatchableHandlerMapping getMatchableHandlerMapping(HttpServletRequest req
@Nullable
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
AttributesPreservingRequest wrappedRequest = new AttributesPreservingRequest(request);
return doWithMatchingMappingIgnoringException(wrappedRequest, (handlerMapping, executionChain) -> {
return doWithHandlerMappingIgnoringException(wrappedRequest, (handlerMapping, executionChain) -> {
for (HandlerInterceptor interceptor : executionChain.getInterceptorList()) {
if (interceptor instanceof CorsConfigurationSource ccs) {
return ccs.getCorsConfiguration(wrappedRequest);
Expand All @@ -154,15 +199,15 @@ public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
}

@Nullable
private <T> T doWithMatchingMapping(
private <T> T doWithHandlerMapping(
HttpServletRequest request, boolean ignoreException,
BiFunction<HandlerMapping, HandlerExecutionChain, T> matchHandler) throws Exception {
BiFunction<HandlerMapping, HandlerExecutionChain, T> extractor) throws Exception {

Assert.state(this.handlerMappings != null, "Handler mappings not initialized");
Assert.state(this.handlerMappings != null, "HandlerMapping's not initialized");

boolean parseRequestPath = !this.pathPatternHandlerMappings.isEmpty();
boolean parsePath = !this.pathPatternMappings.isEmpty();
RequestPath previousPath = null;
if (parseRequestPath) {
if (parsePath) {
previousPath = (RequestPath) request.getAttribute(ServletRequestPathUtils.PATH_ATTRIBUTE);
ServletRequestPathUtils.parseAndCache(request);
}
Expand All @@ -180,79 +225,30 @@ private <T> T doWithMatchingMapping(
if (chain == null) {
continue;
}
return matchHandler.apply(handlerMapping, chain);
return extractor.apply(handlerMapping, chain);
}
}
finally {
if (parseRequestPath) {
if (parsePath) {
ServletRequestPathUtils.setParsedRequestPath(previousPath, request);
}
}
return null;
}

@Nullable
private <T> T doWithMatchingMappingIgnoringException(
private <T> T doWithHandlerMappingIgnoringException(
HttpServletRequest request, BiFunction<HandlerMapping, HandlerExecutionChain, T> matchHandler) {

try {
return doWithMatchingMapping(request, true, matchHandler);
return doWithHandlerMapping(request, true, matchHandler);
}
catch (Exception ex) {
throw new IllegalStateException("HandlerMapping exception not suppressed", ex);
}
}


private static List<HandlerMapping> initHandlerMappings(ApplicationContext applicationContext) {
Map<String, HandlerMapping> beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(
applicationContext, HandlerMapping.class, true, false);
if (!beans.isEmpty()) {
List<HandlerMapping> mappings = new ArrayList<>(beans.values());
AnnotationAwareOrderComparator.sort(mappings);
return Collections.unmodifiableList(mappings);
}
return Collections.unmodifiableList(initFallback(applicationContext));
}

private static List<HandlerMapping> initFallback(ApplicationContext applicationContext) {
Properties props;
String path = "DispatcherServlet.properties";
try {
Resource resource = new ClassPathResource(path, DispatcherServlet.class);
props = PropertiesLoaderUtils.loadProperties(resource);
}
catch (IOException ex) {
throw new IllegalStateException("Could not load '" + path + "': " + ex.getMessage());
}

String value = props.getProperty(HandlerMapping.class.getName());
String[] names = StringUtils.commaDelimitedListToStringArray(value);
List<HandlerMapping> result = new ArrayList<>(names.length);
for (String name : names) {
try {
Class<?> clazz = ClassUtils.forName(name, DispatcherServlet.class.getClassLoader());
Object mapping = applicationContext.getAutowireCapableBeanFactory().createBean(clazz);
result.add((HandlerMapping) mapping);
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException("Could not find default HandlerMapping [" + name + "]");
}
}
return result;
}

private static Map<HandlerMapping, PathPatternMatchableHandlerMapping> initPathPatternMatchableHandlerMappings(
List<HandlerMapping> mappings) {

return mappings.stream()
.filter(MatchableHandlerMapping.class::isInstance)
.map(MatchableHandlerMapping.class::cast)
.filter(mapping -> mapping.getPatternParser() != null)
.collect(Collectors.toMap(mapping -> mapping, PathPatternMatchableHandlerMapping::new));
}


/**
* Request wrapper that buffers request attributes in order protect the
* underlying request from attribute changes.
Expand Down Expand Up @@ -298,26 +294,27 @@ public void removeAttribute(String name) {
}


private static class PathSettingHandlerMapping implements MatchableHandlerMapping {
private static class LookupPathMatchableHandlerMapping implements MatchableHandlerMapping {

private final MatchableHandlerMapping delegate;

private final Object path;
private final Object lookupPath;

private final String pathAttributeName;

PathSettingHandlerMapping(MatchableHandlerMapping delegate, Object path) {
LookupPathMatchableHandlerMapping(MatchableHandlerMapping delegate, Object lookupPath) {
this.delegate = delegate;
this.path = path;
this.pathAttributeName = (path instanceof RequestPath ?
this.lookupPath = lookupPath;
this.pathAttributeName = (lookupPath instanceof RequestPath ?
ServletRequestPathUtils.PATH_ATTRIBUTE : UrlPathHelper.PATH_ATTRIBUTE);
}

@Nullable
@Override
public RequestMatchResult match(HttpServletRequest request, String pattern) {
pattern = (StringUtils.hasLength(pattern) && !pattern.startsWith("/") ? "/" + pattern : pattern);
Object previousPath = request.getAttribute(this.pathAttributeName);
request.setAttribute(this.pathAttributeName, this.path);
request.setAttribute(this.pathAttributeName, this.lookupPath);
try {
return this.delegate.match(request, pattern);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2023 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.
Expand Down Expand Up @@ -30,8 +30,9 @@
import org.springframework.web.util.pattern.PathPatternParser;

/**
* Wraps {@link MatchableHandlerMapping}s configured with a {@link PathPatternParser}
* in order to parse patterns lazily and cache them for re-ues.
* Decorate another {@link MatchableHandlerMapping} that's configured with a
* {@link PathPatternParser} in order to parse and cache String patterns passed
* into the {@code match} method.
*
* @author Rossen Stoyanchev
* @since 5.3
Expand All @@ -49,8 +50,8 @@ class PathPatternMatchableHandlerMapping implements MatchableHandlerMapping {


public PathPatternMatchableHandlerMapping(MatchableHandlerMapping delegate) {
Assert.notNull(delegate, "Delegate MatchableHandlerMapping is required.");
Assert.notNull(delegate.getPatternParser(), "PatternParser is required.");
Assert.notNull(delegate, "HandlerMapping to delegate to is required.");
Assert.notNull(delegate.getPatternParser(), "Expected HandlerMapping configured to use PatternParser.");
this.delegate = delegate;
this.parser = delegate.getPatternParser();
}
Expand Down

0 comments on commit 202fa5c

Please sign in to comment.