From a30ab30e4e9ae021fdda04e9abfc228476b846b5 Mon Sep 17 00:00:00 2001
From: Rossen Stoyanchev <rstoyanchev@pivotal.io>
Date: Mon, 25 Apr 2016 21:33:50 -0400
Subject: [PATCH] Introduce HandlerMapping introspection API

---
 .../handler/AbstractHandlerMapping.java       |   7 +
 .../handler/AbstractUrlHandlerMapping.java    |  19 +-
 .../RequestMappingHandlerMapping.java         |  18 +-
 .../support/HandlerMappingIntrospector.java   | 192 ++++++++++++++++++
 .../support/MatchableHandlerMapping.java      |  41 ++++
 .../servlet/support/RequestMatchResult.java   |  77 +++++++
 .../HandlerMappingIntrospectorTests.java      | 190 +++++++++++++++++
 7 files changed, 542 insertions(+), 2 deletions(-)
 create mode 100644 spring-webmvc/src/main/java/org/springframework/web/servlet/support/HandlerMappingIntrospector.java
 create mode 100644 spring-webmvc/src/main/java/org/springframework/web/servlet/support/MatchableHandlerMapping.java
 create mode 100644 spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestMatchResult.java
 create mode 100644 spring-webmvc/src/test/java/org/springframework/web/servlet/support/HandlerMappingIntrospectorTests.java

diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMapping.java
index a1464ce..3532502 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMapping.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractHandlerMapping.java
@@ -27,8 +27,15 @@ import org.springframework.core.Ordered;
 import org.springframework.util.AntPathMatcher;
 import org.springframework.util.Assert;
 import org.springframework.util.PathMatcher;
+import org.springframework.web.HttpRequestHandler;
 import org.springframework.web.context.request.WebRequestInterceptor;
 import org.springframework.web.context.support.WebApplicationObjectSupport;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.CorsConfigurationSource;
+import org.springframework.web.cors.CorsProcessor;
+import org.springframework.web.cors.CorsUtils;
+import org.springframework.web.cors.DefaultCorsProcessor;
+import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
 import org.springframework.web.servlet.HandlerExecutionChain;
 import org.springframework.web.servlet.HandlerInterceptor;
 import org.springframework.web.servlet.HandlerMapping;
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java
index df23203..3ef0d98 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java
@@ -31,6 +31,8 @@ import org.springframework.util.Assert;
 import org.springframework.util.CollectionUtils;
 import org.springframework.web.servlet.HandlerExecutionChain;
 import org.springframework.web.servlet.HandlerMapping;
+import org.springframework.web.servlet.support.MatchableHandlerMapping;
+import org.springframework.web.servlet.support.RequestMatchResult;
 
 /**
  * Abstract base class for URL-mapped {@link org.springframework.web.servlet.HandlerMapping}
@@ -51,7 +53,8 @@ import org.springframework.web.servlet.HandlerMapping;
  * @author Arjen Poutsma
  * @since 16.04.2003
  */
-public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
+public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping
+		implements MatchableHandlerMapping {
 
 	private Object rootHandler;
 
@@ -253,6 +256,20 @@ public abstract class AbstractUrlHandlerMapping extends AbstractHandlerMapping {
 		request.setAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, uriTemplateVariables);
 	}
 
+	@Override
+	public RequestMatchResult match(HttpServletRequest request, String pattern) {
+		String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
+		if (getPathMatcher().match(pattern, lookupPath)) {
+			return new RequestMatchResult(pattern, lookupPath, getPathMatcher());
+		}
+		else if (useTrailingSlashMatch()) {
+			if (!pattern.endsWith("/") && getPathMatcher().match(pattern + "/", lookupPath)) {
+				return new RequestMatchResult(pattern + "/", lookupPath, getPathMatcher());
+			}
+		}
+		return null;
+	}
+
 	/**
 	 * Register the specified handler for the given URL paths.
 	 * @param urlPaths the URLs that the bean should be mapped to
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java
index b4a4a0f..c1c74ba 100644
--- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java
@@ -19,6 +19,8 @@ package org.springframework.web.servlet.mvc.method.annotation;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
+import javax.servlet.http.HttpServletRequest;
 
 import org.springframework.context.EmbeddedValueResolverAware;
 import org.springframework.core.annotation.AnnotationUtils;
@@ -38,6 +40,8 @@ import org.springframework.web.servlet.mvc.condition.RequestCondition;
 import org.springframework.web.servlet.mvc.condition.RequestMethodsRequestCondition;
 import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
 import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
+import org.springframework.web.servlet.support.MatchableHandlerMapping;
+import org.springframework.web.servlet.support.RequestMatchResult;
 
 /**
  * Creates {@link RequestMappingInfo} instances from type and method-level
@@ -49,7 +53,7 @@ import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMappi
  * @since 3.1
  */
 public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMapping
-		implements EmbeddedValueResolverAware {
+		implements EmbeddedValueResolverAware, MatchableHandlerMapping {
 
 	private boolean useSuffixPatternMatch = true;
 
@@ -261,4 +265,16 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi
 		}
 	}
 
+       @Override
+       public RequestMatchResult match(HttpServletRequest request, String pattern) {
+	       RequestMappingInfo info = RequestMappingInfo.paths(pattern).options(this.config).build();
+	       RequestMappingInfo matchingInfo = info.getMatchingCondition(request);
+	       if (matchingInfo == null) {
+		       return null;
+	       }
+	       Set<String> patterns = matchingInfo.getPatternsCondition().getPatterns();
+	       String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
+	       return new RequestMatchResult(patterns.iterator().next(), lookupPath, getPathMatcher());
+       }
+       
 }
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/HandlerMappingIntrospector.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/HandlerMappingIntrospector.java
new file mode 100644
index 0000000..cc727c8
--- /dev/null
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/HandlerMappingIntrospector.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2002-2016 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.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.web.servlet.support;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+
+import org.springframework.beans.factory.BeanFactoryUtils;
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.annotation.AnnotationAwareOrderComparator;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.Resource;
+import org.springframework.core.io.support.PropertiesLoaderUtils;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.StringUtils;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.cors.CorsConfigurationSource;
+import org.springframework.web.servlet.DispatcherServlet;
+import org.springframework.web.servlet.HandlerExecutionChain;
+import org.springframework.web.servlet.HandlerInterceptor;
+import org.springframework.web.servlet.HandlerMapping;
+
+/**
+ * Helper class to get information from the {@code HandlerMapping} that would
+ * serve a specific request.
+ *
+ * <p>Provides the following methods:
+ * <ul>
+ * <li>{@link #getMatchableHandlerMapping} -- obtain a {@code HandlerMapping}
+ * to check request-matching criteria against.
+ * <li>{@link #getCorsConfiguration} -- obtain the CORS configuration for the
+ * request.
+ * </ul>
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.3
+ */
+public class HandlerMappingIntrospector implements CorsConfigurationSource {
+
+	private final List<HandlerMapping> handlerMappings;
+
+
+	/**
+	 * Constructor that detects the configured {@code HandlerMapping}s in the
+	 * given {@code ApplicationContext} or falling back on
+	 * "DispatcherServlet.properties" like the {@code DispatcherServlet}.
+	 */
+	public HandlerMappingIntrospector(ApplicationContext context) {
+		this.handlerMappings = initHandlerMappings(context);
+	}
+
+
+	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<HandlerMapping>(beans.values());
+			AnnotationAwareOrderComparator.sort(mappings);
+			return mappings;
+		}
+
+		return initDefaultHandlerMappings(context);
+	}
+
+	private static List<HandlerMapping> initDefaultHandlerMappings(ApplicationContext context) {
+		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<HandlerMapping>(names.length);
+		for (String name : names) {
+			try {
+				Class<?> clazz = ClassUtils.forName(name, DispatcherServlet.class.getClassLoader());
+				Object mapping = context.getAutowireCapableBeanFactory().createBean(clazz);
+				result.add((HandlerMapping) mapping);
+			}
+			catch (ClassNotFoundException ex) {
+				throw new IllegalStateException("Could not find default HandlerMapping [" + name + "]");
+			}
+		}
+		return result;
+	}
+
+
+	/**
+	 * Return the configured HandlerMapping's.
+	 */
+	public List<HandlerMapping> getHandlerMappings() {
+		return this.handlerMappings;
+	}
+
+
+	/**
+	 * 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. If the matching HandlerMapping is not an
+	 * instance of {@link MatchableHandlerMapping}, an IllegalStateException is
+	 * raised.
+	 *
+	 * @param request the current request
+	 * @return the resolved matcher, or {@code null}
+	 * @throws Exception if any of the HandlerMapping's raise an exception
+	 */
+	public MatchableHandlerMapping getMatchableHandlerMapping(HttpServletRequest request) throws Exception {
+		HttpServletRequest wrapper = new RequestAttributeChangeIgnoringWrapper(request);
+		for (HandlerMapping handlerMapping : this.handlerMappings) {
+			Object handler = handlerMapping.getHandler(wrapper);
+			if (handler == null) {
+				continue;
+			}
+			if (handlerMapping instanceof MatchableHandlerMapping) {
+				return ((MatchableHandlerMapping) handlerMapping);
+			}
+			throw new IllegalStateException("HandlerMapping is not a MatchableHandlerMapping");
+		}
+		return null;
+	}
+
+	@Override
+	public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
+		HttpServletRequest wrapper = new RequestAttributeChangeIgnoringWrapper(request);
+		for (HandlerMapping handlerMapping : this.handlerMappings) {
+			HandlerExecutionChain handler = null;
+			try {
+				handler = handlerMapping.getHandler(wrapper);
+			}
+			catch (Exception ex) {
+				// Ignore
+			}
+			if (handler == null) {
+				continue;
+			}
+			if (handler.getInterceptors() != null) {
+				for (HandlerInterceptor interceptor : handler.getInterceptors()) {
+					if (interceptor instanceof CorsConfigurationSource) {
+						return ((CorsConfigurationSource) interceptor).getCorsConfiguration(wrapper);
+					}
+				}
+			}
+			if (handler.getHandler() instanceof CorsConfigurationSource) {
+				return ((CorsConfigurationSource) handler.getHandler()).getCorsConfiguration(wrapper);
+			}
+		}
+		return null;
+	}
+
+
+	/**
+	 * Request wrapper that ignores request attribute changes.
+	 */
+	private static class RequestAttributeChangeIgnoringWrapper extends HttpServletRequestWrapper {
+
+
+		private RequestAttributeChangeIgnoringWrapper(HttpServletRequest request) {
+			super(request);
+		}
+
+		@Override
+		public void setAttribute(String name, Object value) {
+			// Ignore attribute change
+		}
+	}
+
+}
\ No newline at end of file
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/MatchableHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/MatchableHandlerMapping.java
new file mode 100644
index 0000000..1c82586
--- /dev/null
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/MatchableHandlerMapping.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2002-2016 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.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.web.servlet.support;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.web.servlet.HandlerMapping;
+
+/**
+ * Additional interface that a {@link HandlerMapping} can implement to expose
+ * a request matching API aligned with its internal request matching
+ * configuration and implementation.
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.3
+ * @see HandlerMappingIntrospector
+ */
+public interface MatchableHandlerMapping {
+
+	/**
+	 * Whether the given request matches the request criteria.
+	 * @param request the current request
+	 * @param pattern the pattern to match
+	 * @return the result from request matching or {@code null}
+	 */
+	RequestMatchResult match(HttpServletRequest request, String pattern);
+
+}
diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestMatchResult.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestMatchResult.java
new file mode 100644
index 0000000..b196d92
--- /dev/null
+++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/support/RequestMatchResult.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2002-2016 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.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.web.servlet.support;
+
+import java.util.Collections;
+import java.util.Map;
+
+import org.springframework.util.Assert;
+import org.springframework.util.PathMatcher;
+
+/**
+ * Container for the result from request pattern matching via
+ * {@link MatchableHandlerMapping} with a method to further extract URI template
+ * variables from the pattern.
+ *
+ * @author Rossen Stoyanchev
+ * @since 4.3
+ */
+public class RequestMatchResult {
+
+	private final String matchingPattern;
+
+	private final String lookupPath;
+
+	private final PathMatcher pathMatcher;
+
+
+	/**
+	 * Create an instance with a matching pattern.
+	 * @param matchingPattern the matching pattern, possibly not the same as the
+	 * input pattern, e.g. inputPattern="/foo" and matchingPattern="/foo/".
+	 * @param lookupPath the lookup path extracted from the request
+	 * @param pathMatcher the PathMatcher used
+	 */
+	public RequestMatchResult(String matchingPattern, String lookupPath, PathMatcher pathMatcher) {
+		Assert.hasText(matchingPattern, "'matchingPattern' is required");
+		Assert.hasText(lookupPath, "'lookupPath' is required");
+		Assert.notNull(pathMatcher, "'pathMatcher' is required");
+		this.matchingPattern = matchingPattern;
+		this.lookupPath = lookupPath;
+		this.pathMatcher = pathMatcher;
+	}
+
+
+	/**
+	 * Whether the pattern was matched to the request.
+	 */
+	public boolean isMatch() {
+		return (this.matchingPattern != null);
+	}
+
+	/**
+	 * Extract URI template variables from the matching pattern as defined in
+	 * {@link PathMatcher#extractUriTemplateVariables}.
+	 * @return a map with URI template variables
+	 */
+	public Map<String, String> extractUriTemplateVariables() {
+		if (!isMatch()) {
+			return Collections.<String, String>emptyMap();
+		}
+		return this.pathMatcher.extractUriTemplateVariables(this.matchingPattern, this.lookupPath);
+	}
+
+}
diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/support/HandlerMappingIntrospectorTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/support/HandlerMappingIntrospectorTests.java
new file mode 100644
index 0000000..b7e1c76
--- /dev/null
+++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/support/HandlerMappingIntrospectorTests.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright 2002-2016 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.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.web.servlet.support;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import javax.servlet.http.HttpServletRequest;
+
+import org.junit.Test;
+
+import org.springframework.beans.MutablePropertyValues;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpHeaders;
+import org.springframework.mock.web.test.MockHttpServletRequest;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.CrossOrigin;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
+import org.springframework.web.context.support.StaticWebApplicationContext;
+import org.springframework.web.cors.CorsConfiguration;
+import org.springframework.web.servlet.HandlerExecutionChain;
+import org.springframework.web.servlet.HandlerMapping;
+import org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping;
+import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
+import org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.springframework.web.servlet.HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE;
+
+/**
+ * Unit tests for {@link HandlerMappingIntrospector}.
+ * @author Rossen Stoyanchev
+ */
+public class HandlerMappingIntrospectorTests {
+
+	@Test
+	public void detectHandlerMappings() throws Exception {
+		StaticWebApplicationContext cxt = new StaticWebApplicationContext();
+		cxt.registerSingleton("hmA", SimpleUrlHandlerMapping.class);
+		cxt.registerSingleton("hmB", SimpleUrlHandlerMapping.class);
+		cxt.registerSingleton("hmC", SimpleUrlHandlerMapping.class);
+		cxt.refresh();
+
+		List<?> expected = Arrays.asList(cxt.getBean("hmA"), cxt.getBean("hmB"), cxt.getBean("hmC"));
+		List<HandlerMapping> actual = new HandlerMappingIntrospector(cxt).getHandlerMappings();
+
+		assertEquals(expected, actual);
+	}
+
+	@Test
+	public void detectHandlerMappingsOrdered() throws Exception {
+		StaticWebApplicationContext cxt = new StaticWebApplicationContext();
+		MutablePropertyValues pvs = new MutablePropertyValues(Collections.singletonMap("order", "3"));
+		cxt.registerSingleton("hmA", SimpleUrlHandlerMapping.class, pvs);
+		pvs = new MutablePropertyValues(Collections.singletonMap("order", "2"));
+		cxt.registerSingleton("hmB", SimpleUrlHandlerMapping.class, pvs);
+		pvs = new MutablePropertyValues(Collections.singletonMap("order", "1"));
+		cxt.registerSingleton("hmC", SimpleUrlHandlerMapping.class, pvs);
+		cxt.refresh();
+
+		List<?> expected = Arrays.asList(cxt.getBean("hmC"), cxt.getBean("hmB"), cxt.getBean("hmA"));
+		List<HandlerMapping> actual = new HandlerMappingIntrospector(cxt).getHandlerMappings();
+
+		assertEquals(expected, actual);
+	}
+
+	@Test @SuppressWarnings("deprecation")
+	public void defaultHandlerMappings() throws Exception {
+		StaticWebApplicationContext cxt = new StaticWebApplicationContext();
+		cxt.refresh();
+
+		List<HandlerMapping> actual = new HandlerMappingIntrospector(cxt).getHandlerMappings();
+		assertEquals(2, actual.size());
+		assertEquals(BeanNameUrlHandlerMapping.class, actual.get(0).getClass());
+		assertEquals(DefaultAnnotationHandlerMapping.class, actual.get(1).getClass());
+	}
+
+	@Test
+	public void getMatchable() throws Exception {
+
+		MutablePropertyValues pvs = new MutablePropertyValues(
+				Collections.singletonMap("urlMap",
+						Collections.singletonMap("/path", new Object())));
+
+		StaticWebApplicationContext cxt = new StaticWebApplicationContext();
+		cxt.registerSingleton("hm", SimpleUrlHandlerMapping.class, pvs);
+		cxt.refresh();
+
+		MockHttpServletRequest request = new MockHttpServletRequest("GET", "/path");
+		MatchableHandlerMapping hm = new HandlerMappingIntrospector(cxt).getMatchableHandlerMapping(request);
+
+		assertEquals(cxt.getBean("hm"), hm);
+		assertNull("Attributes changes not ignored", request.getAttribute(BEST_MATCHING_PATTERN_ATTRIBUTE));
+	}
+
+	@Test(expected = IllegalStateException.class)
+	public void getMatchableWhereHandlerMappingDoesNotImplementMatchableInterface() throws Exception {
+		StaticWebApplicationContext cxt = new StaticWebApplicationContext();
+		cxt.registerSingleton("hm1", TestHandlerMapping.class);
+		cxt.refresh();
+
+		MockHttpServletRequest request = new MockHttpServletRequest();
+		new HandlerMappingIntrospector(cxt).getMatchableHandlerMapping(request);
+	}
+
+	@Test
+	public void getCorsConfigurationPreFlight() throws Exception {
+		AnnotationConfigWebApplicationContext cxt = new AnnotationConfigWebApplicationContext();
+		cxt.register(TestConfig.class);
+		cxt.refresh();
+
+		// PRE-FLIGHT
+
+		MockHttpServletRequest request = new MockHttpServletRequest("OPTIONS", "/path");
+		request.addHeader("Origin", "http://localhost:9000");
+		request.addHeader(HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD, "POST");
+		CorsConfiguration corsConfig = new HandlerMappingIntrospector(cxt).getCorsConfiguration(request);
+
+		assertNotNull(corsConfig);
+		assertEquals(Collections.singletonList("http://localhost:9000"), corsConfig.getAllowedOrigins());
+		assertEquals(Collections.singletonList("POST"), corsConfig.getAllowedMethods());
+	}
+
+	@Test
+	public void getCorsConfigurationActual() throws Exception {
+		AnnotationConfigWebApplicationContext cxt = new AnnotationConfigWebApplicationContext();
+		cxt.register(TestConfig.class);
+		cxt.refresh();
+
+		MockHttpServletRequest request = new MockHttpServletRequest("POST", "/path");
+		request.addHeader("Origin", "http://localhost:9000");
+		CorsConfiguration corsConfig = new HandlerMappingIntrospector(cxt).getCorsConfiguration(request);
+
+		assertNotNull(corsConfig);
+		assertEquals(Collections.singletonList("http://localhost:9000"), corsConfig.getAllowedOrigins());
+		assertEquals(Collections.singletonList("POST"), corsConfig.getAllowedMethods());
+	}
+
+
+	private static class TestHandlerMapping implements HandlerMapping {
+
+		@Override
+		public HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
+			return new HandlerExecutionChain(new Object());
+		}
+	}
+
+	@Configuration @SuppressWarnings({"WeakerAccess", "unused"})
+	static class TestConfig {
+
+		@Bean
+		public RequestMappingHandlerMapping handlerMapping() {
+			return new RequestMappingHandlerMapping();
+		}
+
+		@Bean
+		public TestController testController() {
+			return new TestController();
+		}
+
+	}
+
+	@CrossOrigin("http://localhost:9000")
+	@Controller
+	private static class TestController {
+
+		@PostMapping("/path")
+		public void handle() {
+		}
+	}
+
+}
-- 
2.23.0

