diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/InterceptorsBeanDefinitionParser.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/InterceptorsBeanDefinitionParser.java index b371cf4f327..050688f0672 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/InterceptorsBeanDefinitionParser.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/InterceptorsBeanDefinitionParser.java @@ -18,6 +18,7 @@ package org.springframework.web.servlet.config; import java.util.List; +import org.springframework.beans.factory.config.RuntimeBeanReference; import org.w3c.dom.Element; import org.springframework.beans.factory.config.BeanDefinition; @@ -44,6 +45,11 @@ class InterceptorsBeanDefinitionParser implements BeanDefinitionParser { CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element)); parserContext.pushContainingComponent(compDefinition); + RuntimeBeanReference pathMatcherRef = null; + if (element.hasAttribute("path-matcher")) { + pathMatcherRef = new RuntimeBeanReference(element.getAttribute("path-matcher")); + } + List interceptors = DomUtils.getChildElementsByTagName(element, "bean", "ref", "interceptor"); for (Element interceptor : interceptors) { RootBeanDefinition mappedInterceptorDef = new RootBeanDefinition(MappedInterceptor.class); @@ -66,6 +72,10 @@ class InterceptorsBeanDefinitionParser implements BeanDefinitionParser { mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, excludePatterns); mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(2, interceptorBean); + if (pathMatcherRef != null) { + mappedInterceptorDef.getPropertyValues().add("pathMatcher", pathMatcherRef); + } + String beanName = parserContext.getReaderContext().registerWithGeneratedName(mappedInterceptorDef); parserContext.registerComponent(new BeanComponentDefinition(mappedInterceptorDef, beanName)); } diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/InterceptorRegistration.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/InterceptorRegistration.java index f1a43d717d8..2a5386987d4 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/InterceptorRegistration.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/InterceptorRegistration.java @@ -16,12 +16,11 @@ package org.springframework.web.servlet.config.annotation; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; +import java.util.*; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; +import org.springframework.util.PathMatcher; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.handler.MappedInterceptor; @@ -40,6 +39,9 @@ public class InterceptorRegistration { private final List excludePatterns = new ArrayList(); + private PathMatcher pathMatcher; + + /** * Creates an {@link InterceptorRegistration} instance. */ @@ -64,6 +66,17 @@ public class InterceptorRegistration { return this; } + /** + * A PathMatcher implementation to use with this interceptor. This is an optional, + * advanced property required only if using custom PathMatcher implementations + * that support mapping metadata other than the Ant path patterns supported + * by default. + */ + public InterceptorRegistration pathMatcher(PathMatcher pathMatcher) { + this.pathMatcher = pathMatcher; + return this; + } + /** * Returns the underlying interceptor. If URL patterns are provided the returned type is * {@link MappedInterceptor}; otherwise {@link HandlerInterceptor}. @@ -72,7 +85,12 @@ public class InterceptorRegistration { if (this.includePatterns.isEmpty()) { return this.interceptor; } - return new MappedInterceptor(toArray(this.includePatterns), toArray(this.excludePatterns), interceptor); + MappedInterceptor mappedInterceptor = new MappedInterceptor( + toArray(this.includePatterns), toArray(this.excludePatterns), interceptor); + if (this.pathMatcher != null) { + mappedInterceptor.setPathMatcher(this.pathMatcher); + } + return mappedInterceptor; } private static String[] toArray(List list) { diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/MappedInterceptor.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/MappedInterceptor.java index 2af452e0bf2..16e1d379a22 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/MappedInterceptor.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/handler/MappedInterceptor.java @@ -36,6 +36,8 @@ public final class MappedInterceptor { private final HandlerInterceptor interceptor; + private PathMatcher pathMatcher; + /** * Create a new MappedInterceptor instance. @@ -58,6 +60,7 @@ public final class MappedInterceptor { this.interceptor = interceptor; } + /** * Create a new MappedInterceptor instance. * @param includePatterns the path patterns to map with a {@code null} value matching to all paths @@ -77,6 +80,26 @@ public final class MappedInterceptor { } + /** + * Configure a PathMatcher to use with this MappedInterceptor instead of the + * one passed by default to the {@link #matches(String, org.springframework.util.PathMatcher)} + * method. This is an advanced property that is only required when using custom + * PathMatcher implementations that support mapping metadata other than the + * Ant-style path patterns supported by default. + * + * @param pathMatcher the path matcher to use + */ + public void setPathMatcher(PathMatcher pathMatcher) { + this.pathMatcher = pathMatcher; + } + + /** + * The configured PathMatcher, or {@code null}. + */ + public PathMatcher getPathMatcher() { + return this.pathMatcher; + } + /** * The path into the application the interceptor is mapped to. */ @@ -97,9 +120,10 @@ public final class MappedInterceptor { * @param pathMatcher a path matcher for path pattern matching */ public boolean matches(String lookupPath, PathMatcher pathMatcher) { + PathMatcher pathMatcherToUse = (this.pathMatcher != null) ? this.pathMatcher : pathMatcher; if (this.excludePatterns != null) { for (String pattern : this.excludePatterns) { - if (pathMatcher.match(pattern, lookupPath)) { + if (pathMatcherToUse.match(pattern, lookupPath)) { return false; } } @@ -109,7 +133,7 @@ public final class MappedInterceptor { } else { for (String pattern : this.includePatterns) { - if (pathMatcher.match(pattern, lookupPath)) { + if (pathMatcherToUse.match(pattern, lookupPath)) { return true; } } diff --git a/spring-webmvc/src/main/resources/org/springframework/web/servlet/config/spring-mvc-4.0.xsd b/spring-webmvc/src/main/resources/org/springframework/web/servlet/config/spring-mvc-4.0.xsd index 7ebb96c72c4..985d54a5846 100644 --- a/spring-webmvc/src/main/resources/org/springframework/web/servlet/config/spring-mvc-4.0.xsd +++ b/spring-webmvc/src/main/resources/org/springframework/web/servlet/config/spring-mvc-4.0.xsd @@ -415,6 +415,20 @@ + + + + + + + + + + diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java index 0acf2453197..389b76b7fa7 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java @@ -19,10 +19,7 @@ package org.springframework.web.servlet.config; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.Date; -import java.util.List; -import java.util.Locale; +import java.util.*; import javax.servlet.RequestDispatcher; import javax.validation.constraints.NotNull; @@ -47,6 +44,7 @@ import org.springframework.mock.web.test.MockRequestDispatcher; import org.springframework.mock.web.test.MockServletContext; import org.springframework.scheduling.concurrent.ConcurrentTaskExecutor; import org.springframework.stereotype.Controller; +import org.springframework.util.PathMatcher; import org.springframework.validation.BindingResult; import org.springframework.validation.Errors; import org.springframework.validation.Validator; @@ -68,10 +66,7 @@ import org.springframework.web.method.support.InvocableHandlerMethod; import org.springframework.web.servlet.HandlerExecutionChain; import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.ModelAndView; -import org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping; -import org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor; -import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping; -import org.springframework.web.servlet.handler.WebRequestHandlerInterceptorAdapter; +import org.springframework.web.servlet.handler.*; import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter; import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter; @@ -219,7 +214,7 @@ public class MvcNamespaceTests { @Test public void testInterceptors() throws Exception { - loadBeanDefinitions("mvc-config-interceptors.xml", 18); + loadBeanDefinitions("mvc-config-interceptors.xml", 20); RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class); assertNotNull(mapping); @@ -231,11 +226,12 @@ public class MvcNamespaceTests { request.addParameter("theme", "green"); HandlerExecutionChain chain = mapping.getHandler(request); - assertEquals(4, chain.getInterceptors().length); + assertEquals(5, chain.getInterceptors().length); assertTrue(chain.getInterceptors()[0] instanceof ConversionServiceExposingInterceptor); assertTrue(chain.getInterceptors()[1] instanceof LocaleChangeInterceptor); assertTrue(chain.getInterceptors()[2] instanceof WebRequestHandlerInterceptorAdapter); assertTrue(chain.getInterceptors()[3] instanceof ThemeChangeInterceptor); + assertTrue(chain.getInterceptors()[4] instanceof UserRoleAuthorizationInterceptor); request.setRequestURI("/admin/users"); chain = mapping.getHandler(request); @@ -584,4 +580,42 @@ public class MvcNamespaceTests { public static class TestDeferredResultProcessingInterceptor extends DeferredResultProcessingInterceptorAdapter { } + public static class TestPathMatcher implements PathMatcher { + + @Override + public boolean isPattern(String path) { + return false; + } + + @Override + public boolean match(String pattern, String path) { + return path.matches(pattern); + } + + @Override + public boolean matchStart(String pattern, String path) { + return false; + } + + @Override + public String extractPathWithinPattern(String pattern, String path) { + return null; + } + + @Override + public Map extractUriTemplateVariables(String pattern, String path) { + return null; + } + + @Override + public Comparator getPatternComparator(String path) { + return null; + } + + @Override + public String combine(String pattern1, String pattern2) { + return null; + } + } + } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/InterceptorRegistryTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/InterceptorRegistryTests.java index b1433e2986e..e5c1e56f2d0 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/InterceptorRegistryTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/InterceptorRegistryTests.java @@ -16,10 +16,6 @@ package org.springframework.web.servlet.config.annotation; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -27,6 +23,8 @@ import java.util.List; import org.junit.Before; import org.junit.Test; +import org.mockito.Mock; +import org.mockito.Mockito; import org.springframework.mock.web.test.MockHttpServletRequest; import org.springframework.mock.web.test.MockHttpServletResponse; import org.springframework.ui.ModelMap; @@ -40,6 +38,8 @@ import org.springframework.web.servlet.handler.WebRequestHandlerInterceptorAdapt import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; import org.springframework.web.servlet.theme.ThemeChangeInterceptor; +import static org.junit.Assert.*; + /** * Test fixture with a {@link InterceptorRegistry}, two {@link HandlerInterceptor}s and two * {@link WebRequestInterceptor}s. @@ -116,6 +116,15 @@ public class InterceptorRegistryTests { verifyAdaptedInterceptor(interceptors.get(1), webRequestInterceptor2); } + @Test + public void addInterceptorsWithCustomPathMatcher() { + PathMatcher pathMatcher = Mockito.mock(PathMatcher.class); + registry.addInterceptor(interceptor1).addPathPatterns("/path1/**").pathMatcher(pathMatcher); + + MappedInterceptor mappedInterceptor = (MappedInterceptor) registry.getInterceptors().get(0); + assertSame(pathMatcher, mappedInterceptor.getPathMatcher()); + } + @Test public void addWebRequestInterceptorsWithUrlPatterns() throws Exception { registry.addWebRequestInterceptor(webRequestInterceptor1).addPathPatterns("/path1"); diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/MappedInterceptorTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/MappedInterceptorTests.java index e3826af2159..6b9b26169cc 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/MappedInterceptorTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/handler/MappedInterceptorTests.java @@ -20,8 +20,12 @@ import static org.junit.Assert.*; import org.junit.Before; import org.junit.Test; import org.springframework.util.AntPathMatcher; +import org.springframework.util.PathMatcher; import org.springframework.web.servlet.i18n.LocaleChangeInterceptor; +import java.util.Comparator; +import java.util.Map; + /** * Test fixture for {@link MappedInterceptor} tests. * @@ -75,4 +79,52 @@ public class MappedInterceptorTests { assertFalse(mappedInterceptor.matches("/admin/foo", pathMatcher)); } + @Test + public void customPathMatcher() { + MappedInterceptor mappedInterceptor = new MappedInterceptor(new String[] { "/foo/[0-9]*" }, this.interceptor); + mappedInterceptor.setPathMatcher(new TestPathMatcher()); + + assertTrue(mappedInterceptor.matches("/foo/123", pathMatcher)); + assertFalse(mappedInterceptor.matches("/foo/bar", pathMatcher)); + } + + + + public static class TestPathMatcher implements PathMatcher { + + @Override + public boolean isPattern(String path) { + return false; + } + + @Override + public boolean match(String pattern, String path) { + return path.matches(pattern); + } + + @Override + public boolean matchStart(String pattern, String path) { + return false; + } + + @Override + public String extractPathWithinPattern(String pattern, String path) { + return null; + } + + @Override + public Map extractUriTemplateVariables(String pattern, String path) { + return null; + } + + @Override + public Comparator getPatternComparator(String path) { + return null; + } + + @Override + public String combine(String pattern1, String pattern2) { + return null; + } + } } diff --git a/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-interceptors.xml b/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-interceptors.xml index 3b4c26f6c30..cdea6884bb8 100644 --- a/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-interceptors.xml +++ b/spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-interceptors.xml @@ -26,4 +26,13 @@ + + + + + + + + +