Skip to content

Commit de280b0

Browse files
committed
Support custom PathMatcher for MappedInterceptor's
Issue: SPR-11197
1 parent abb8a93 commit de280b0

File tree

8 files changed

+190
-20
lines changed

8 files changed

+190
-20
lines changed

spring-webmvc/src/main/java/org/springframework/web/servlet/config/InterceptorsBeanDefinitionParser.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.util.List;
2020

21+
import org.springframework.beans.factory.config.RuntimeBeanReference;
2122
import org.w3c.dom.Element;
2223

2324
import org.springframework.beans.factory.config.BeanDefinition;
@@ -44,6 +45,11 @@ public BeanDefinition parse(Element element, ParserContext parserContext) {
4445
CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));
4546
parserContext.pushContainingComponent(compDefinition);
4647

48+
RuntimeBeanReference pathMatcherRef = null;
49+
if (element.hasAttribute("path-matcher")) {
50+
pathMatcherRef = new RuntimeBeanReference(element.getAttribute("path-matcher"));
51+
}
52+
4753
List<Element> interceptors = DomUtils.getChildElementsByTagName(element, "bean", "ref", "interceptor");
4854
for (Element interceptor : interceptors) {
4955
RootBeanDefinition mappedInterceptorDef = new RootBeanDefinition(MappedInterceptor.class);
@@ -66,6 +72,10 @@ public BeanDefinition parse(Element element, ParserContext parserContext) {
6672
mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(1, excludePatterns);
6773
mappedInterceptorDef.getConstructorArgumentValues().addIndexedArgumentValue(2, interceptorBean);
6874

75+
if (pathMatcherRef != null) {
76+
mappedInterceptorDef.getPropertyValues().add("pathMatcher", pathMatcherRef);
77+
}
78+
6979
String beanName = parserContext.getReaderContext().registerWithGeneratedName(mappedInterceptorDef);
7080
parserContext.registerComponent(new BeanComponentDefinition(mappedInterceptorDef, beanName));
7181
}

spring-webmvc/src/main/java/org/springframework/web/servlet/config/annotation/InterceptorRegistration.java

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,11 @@
1616

1717
package org.springframework.web.servlet.config.annotation;
1818

19-
import java.util.ArrayList;
20-
import java.util.Arrays;
21-
import java.util.List;
19+
import java.util.*;
2220

2321
import org.springframework.util.Assert;
2422
import org.springframework.util.CollectionUtils;
23+
import org.springframework.util.PathMatcher;
2524
import org.springframework.web.servlet.HandlerInterceptor;
2625
import org.springframework.web.servlet.handler.MappedInterceptor;
2726

@@ -40,6 +39,9 @@ public class InterceptorRegistration {
4039

4140
private final List<String> excludePatterns = new ArrayList<String>();
4241

42+
private PathMatcher pathMatcher;
43+
44+
4345
/**
4446
* Creates an {@link InterceptorRegistration} instance.
4547
*/
@@ -64,6 +66,17 @@ public InterceptorRegistration excludePathPatterns(String... patterns) {
6466
return this;
6567
}
6668

69+
/**
70+
* A PathMatcher implementation to use with this interceptor. This is an optional,
71+
* advanced property required only if using custom PathMatcher implementations
72+
* that support mapping metadata other than the Ant path patterns supported
73+
* by default.
74+
*/
75+
public InterceptorRegistration pathMatcher(PathMatcher pathMatcher) {
76+
this.pathMatcher = pathMatcher;
77+
return this;
78+
}
79+
6780
/**
6881
* Returns the underlying interceptor. If URL patterns are provided the returned type is
6982
* {@link MappedInterceptor}; otherwise {@link HandlerInterceptor}.
@@ -72,7 +85,12 @@ protected Object getInterceptor() {
7285
if (this.includePatterns.isEmpty()) {
7386
return this.interceptor;
7487
}
75-
return new MappedInterceptor(toArray(this.includePatterns), toArray(this.excludePatterns), interceptor);
88+
MappedInterceptor mappedInterceptor = new MappedInterceptor(
89+
toArray(this.includePatterns), toArray(this.excludePatterns), interceptor);
90+
if (this.pathMatcher != null) {
91+
mappedInterceptor.setPathMatcher(this.pathMatcher);
92+
}
93+
return mappedInterceptor;
7694
}
7795

7896
private static String[] toArray(List<String> list) {

spring-webmvc/src/main/java/org/springframework/web/servlet/handler/MappedInterceptor.java

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ public final class MappedInterceptor {
3636

3737
private final HandlerInterceptor interceptor;
3838

39+
private PathMatcher pathMatcher;
40+
3941

4042
/**
4143
* Create a new MappedInterceptor instance.
@@ -58,6 +60,7 @@ public MappedInterceptor(String[] includePatterns, String[] excludePatterns, Han
5860
this.interceptor = interceptor;
5961
}
6062

63+
6164
/**
6265
* Create a new MappedInterceptor instance.
6366
* @param includePatterns the path patterns to map with a {@code null} value matching to all paths
@@ -77,6 +80,26 @@ public MappedInterceptor(String[] includePatterns, String[] excludePatterns, Web
7780
}
7881

7982

83+
/**
84+
* Configure a PathMatcher to use with this MappedInterceptor instead of the
85+
* one passed by default to the {@link #matches(String, org.springframework.util.PathMatcher)}
86+
* method. This is an advanced property that is only required when using custom
87+
* PathMatcher implementations that support mapping metadata other than the
88+
* Ant-style path patterns supported by default.
89+
*
90+
* @param pathMatcher the path matcher to use
91+
*/
92+
public void setPathMatcher(PathMatcher pathMatcher) {
93+
this.pathMatcher = pathMatcher;
94+
}
95+
96+
/**
97+
* The configured PathMatcher, or {@code null}.
98+
*/
99+
public PathMatcher getPathMatcher() {
100+
return this.pathMatcher;
101+
}
102+
80103
/**
81104
* The path into the application the interceptor is mapped to.
82105
*/
@@ -97,9 +120,10 @@ public HandlerInterceptor getInterceptor() {
97120
* @param pathMatcher a path matcher for path pattern matching
98121
*/
99122
public boolean matches(String lookupPath, PathMatcher pathMatcher) {
123+
PathMatcher pathMatcherToUse = (this.pathMatcher != null) ? this.pathMatcher : pathMatcher;
100124
if (this.excludePatterns != null) {
101125
for (String pattern : this.excludePatterns) {
102-
if (pathMatcher.match(pattern, lookupPath)) {
126+
if (pathMatcherToUse.match(pattern, lookupPath)) {
103127
return false;
104128
}
105129
}
@@ -109,7 +133,7 @@ public boolean matches(String lookupPath, PathMatcher pathMatcher) {
109133
}
110134
else {
111135
for (String pattern : this.includePatterns) {
112-
if (pathMatcher.match(pattern, lookupPath)) {
136+
if (pathMatcherToUse.match(pattern, lookupPath)) {
113137
return true;
114138
}
115139
}

spring-webmvc/src/main/resources/org/springframework/web/servlet/config/spring-mvc-4.0.xsd

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -415,6 +415,20 @@
415415
</xsd:complexType>
416416
</xsd:element>
417417
</xsd:choice>
418+
<xsd:attribute name="path-matcher" type="xsd:string">
419+
<xsd:annotation>
420+
<xsd:documentation source="java:org.springframework.util.PathMatcher"><![CDATA[
421+
The bean name of a PathMatcher implementation to use with nested interceptors. This is an optional,
422+
advanced property required only if using custom PathMatcher implementations that support mapping
423+
metadata other than the Ant path patterns supported by default.
424+
]]></xsd:documentation>
425+
<xsd:appinfo>
426+
<tool:annotation kind="ref">
427+
<tool:expected-type type="java:org.springframework.util.PathMatcher" />
428+
</tool:annotation>
429+
</xsd:appinfo>
430+
</xsd:annotation>
431+
</xsd:attribute>
418432
</xsd:complexType>
419433
</xsd:element>
420434

spring-webmvc/src/test/java/org/springframework/web/servlet/config/MvcNamespaceTests.java

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,7 @@
1919
import java.lang.annotation.Retention;
2020
import java.lang.annotation.RetentionPolicy;
2121
import java.lang.reflect.Method;
22-
import java.util.Arrays;
23-
import java.util.Date;
24-
import java.util.List;
25-
import java.util.Locale;
22+
import java.util.*;
2623

2724
import javax.servlet.RequestDispatcher;
2825
import javax.validation.constraints.NotNull;
@@ -47,6 +44,7 @@
4744
import org.springframework.mock.web.test.MockServletContext;
4845
import org.springframework.scheduling.concurrent.ConcurrentTaskExecutor;
4946
import org.springframework.stereotype.Controller;
47+
import org.springframework.util.PathMatcher;
5048
import org.springframework.validation.BindingResult;
5149
import org.springframework.validation.Errors;
5250
import org.springframework.validation.Validator;
@@ -68,10 +66,7 @@
6866
import org.springframework.web.servlet.HandlerExecutionChain;
6967
import org.springframework.web.servlet.HandlerInterceptor;
7068
import org.springframework.web.servlet.ModelAndView;
71-
import org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping;
72-
import org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor;
73-
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
74-
import org.springframework.web.servlet.handler.WebRequestHandlerInterceptorAdapter;
69+
import org.springframework.web.servlet.handler.*;
7570
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
7671
import org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter;
7772
import org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter;
@@ -219,7 +214,7 @@ private void doTestCustomValidator(String xml) throws Exception {
219214

220215
@Test
221216
public void testInterceptors() throws Exception {
222-
loadBeanDefinitions("mvc-config-interceptors.xml", 18);
217+
loadBeanDefinitions("mvc-config-interceptors.xml", 20);
223218

224219
RequestMappingHandlerMapping mapping = appContext.getBean(RequestMappingHandlerMapping.class);
225220
assertNotNull(mapping);
@@ -231,11 +226,12 @@ public void testInterceptors() throws Exception {
231226
request.addParameter("theme", "green");
232227

233228
HandlerExecutionChain chain = mapping.getHandler(request);
234-
assertEquals(4, chain.getInterceptors().length);
229+
assertEquals(5, chain.getInterceptors().length);
235230
assertTrue(chain.getInterceptors()[0] instanceof ConversionServiceExposingInterceptor);
236231
assertTrue(chain.getInterceptors()[1] instanceof LocaleChangeInterceptor);
237232
assertTrue(chain.getInterceptors()[2] instanceof WebRequestHandlerInterceptorAdapter);
238233
assertTrue(chain.getInterceptors()[3] instanceof ThemeChangeInterceptor);
234+
assertTrue(chain.getInterceptors()[4] instanceof UserRoleAuthorizationInterceptor);
239235

240236
request.setRequestURI("/admin/users");
241237
chain = mapping.getHandler(request);
@@ -584,4 +580,42 @@ public static class TestCallableProcessingInterceptor extends CallableProcessing
584580

585581
public static class TestDeferredResultProcessingInterceptor extends DeferredResultProcessingInterceptorAdapter { }
586582

583+
public static class TestPathMatcher implements PathMatcher {
584+
585+
@Override
586+
public boolean isPattern(String path) {
587+
return false;
588+
}
589+
590+
@Override
591+
public boolean match(String pattern, String path) {
592+
return path.matches(pattern);
593+
}
594+
595+
@Override
596+
public boolean matchStart(String pattern, String path) {
597+
return false;
598+
}
599+
600+
@Override
601+
public String extractPathWithinPattern(String pattern, String path) {
602+
return null;
603+
}
604+
605+
@Override
606+
public Map<String, String> extractUriTemplateVariables(String pattern, String path) {
607+
return null;
608+
}
609+
610+
@Override
611+
public Comparator<String> getPatternComparator(String path) {
612+
return null;
613+
}
614+
615+
@Override
616+
public String combine(String pattern1, String pattern2) {
617+
return null;
618+
}
619+
}
620+
587621
}

spring-webmvc/src/test/java/org/springframework/web/servlet/config/annotation/InterceptorRegistryTests.java

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,15 @@
1616

1717
package org.springframework.web.servlet.config.annotation;
1818

19-
import static org.junit.Assert.assertEquals;
20-
import static org.junit.Assert.assertTrue;
21-
import static org.junit.Assert.fail;
22-
2319
import java.util.ArrayList;
2420
import java.util.Arrays;
2521
import java.util.Collections;
2622
import java.util.List;
2723

2824
import org.junit.Before;
2925
import org.junit.Test;
26+
import org.mockito.Mock;
27+
import org.mockito.Mockito;
3028
import org.springframework.mock.web.test.MockHttpServletRequest;
3129
import org.springframework.mock.web.test.MockHttpServletResponse;
3230
import org.springframework.ui.ModelMap;
@@ -40,6 +38,8 @@
4038
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
4139
import org.springframework.web.servlet.theme.ThemeChangeInterceptor;
4240

41+
import static org.junit.Assert.*;
42+
4343
/**
4444
* Test fixture with a {@link InterceptorRegistry}, two {@link HandlerInterceptor}s and two
4545
* {@link WebRequestInterceptor}s.
@@ -116,6 +116,15 @@ public void addWebRequestInterceptors() throws Exception {
116116
verifyAdaptedInterceptor(interceptors.get(1), webRequestInterceptor2);
117117
}
118118

119+
@Test
120+
public void addInterceptorsWithCustomPathMatcher() {
121+
PathMatcher pathMatcher = Mockito.mock(PathMatcher.class);
122+
registry.addInterceptor(interceptor1).addPathPatterns("/path1/**").pathMatcher(pathMatcher);
123+
124+
MappedInterceptor mappedInterceptor = (MappedInterceptor) registry.getInterceptors().get(0);
125+
assertSame(pathMatcher, mappedInterceptor.getPathMatcher());
126+
}
127+
119128
@Test
120129
public void addWebRequestInterceptorsWithUrlPatterns() throws Exception {
121130
registry.addWebRequestInterceptor(webRequestInterceptor1).addPathPatterns("/path1");

spring-webmvc/src/test/java/org/springframework/web/servlet/handler/MappedInterceptorTests.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,12 @@
2020
import org.junit.Before;
2121
import org.junit.Test;
2222
import org.springframework.util.AntPathMatcher;
23+
import org.springframework.util.PathMatcher;
2324
import org.springframework.web.servlet.i18n.LocaleChangeInterceptor;
2425

26+
import java.util.Comparator;
27+
import java.util.Map;
28+
2529
/**
2630
* Test fixture for {@link MappedInterceptor} tests.
2731
*
@@ -75,4 +79,52 @@ public void includeAndExcludePatterns() {
7579
assertFalse(mappedInterceptor.matches("/admin/foo", pathMatcher));
7680
}
7781

82+
@Test
83+
public void customPathMatcher() {
84+
MappedInterceptor mappedInterceptor = new MappedInterceptor(new String[] { "/foo/[0-9]*" }, this.interceptor);
85+
mappedInterceptor.setPathMatcher(new TestPathMatcher());
86+
87+
assertTrue(mappedInterceptor.matches("/foo/123", pathMatcher));
88+
assertFalse(mappedInterceptor.matches("/foo/bar", pathMatcher));
89+
}
90+
91+
92+
93+
public static class TestPathMatcher implements PathMatcher {
94+
95+
@Override
96+
public boolean isPattern(String path) {
97+
return false;
98+
}
99+
100+
@Override
101+
public boolean match(String pattern, String path) {
102+
return path.matches(pattern);
103+
}
104+
105+
@Override
106+
public boolean matchStart(String pattern, String path) {
107+
return false;
108+
}
109+
110+
@Override
111+
public String extractPathWithinPattern(String pattern, String path) {
112+
return null;
113+
}
114+
115+
@Override
116+
public Map<String, String> extractUriTemplateVariables(String pattern, String path) {
117+
return null;
118+
}
119+
120+
@Override
121+
public Comparator<String> getPatternComparator(String path) {
122+
return null;
123+
}
124+
125+
@Override
126+
public String combine(String pattern1, String pattern2) {
127+
return null;
128+
}
129+
}
78130
}

spring-webmvc/src/test/resources/org/springframework/web/servlet/config/mvc-config-interceptors.xml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,13 @@
2626
<bean id="log4jInterceptor"
2727
class="org.springframework.web.context.request.Log4jNestedDiagnosticContextInterceptor" />
2828

29+
<mvc:interceptors path-matcher="pathMatcher">
30+
<mvc:interceptor>
31+
<mvc:mapping path="/accounts/[0-9]*" />
32+
<bean class="org.springframework.web.servlet.handler.UserRoleAuthorizationInterceptor" />
33+
</mvc:interceptor>
34+
</mvc:interceptors>
35+
36+
<bean id="pathMatcher" class="org.springframework.web.servlet.config.MvcNamespaceTests$TestPathMatcher" />
37+
2938
</beans>

0 commit comments

Comments
 (0)