Skip to content

Commit 8143d66

Browse files
committed
Pattern suffix issue in AnnotationMethodHandlerAdapter
Issue: SPR-9333 Backport-Issue: SPR-9419 Backport-Commit: cf5d551
1 parent 9e37020 commit 8143d66

File tree

4 files changed

+52
-16
lines changed

4 files changed

+52
-16
lines changed

org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/handler/AbstractUrlHandlerMapping.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ protected Object lookupHandler(String urlPath, HttpServletRequest request) throw
208208
*/
209209
protected void validateHandler(Object handler, HttpServletRequest request) throws Exception {
210210
}
211-
211+
212212
/**
213213
* Build a handler object for the given raw handler, exposing the actual
214214
* handler, the {@link #PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE}, as well as

org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/AnnotationMethodHandlerAdapter.java

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2011 the original author or authors.
2+
* Copyright 2002-2012 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -443,7 +443,7 @@ protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServl
443443
/**
444444
* This method always returns -1 since an annotated controller can have many methods,
445445
* each requiring separate lastModified calculations. Instead, an
446-
* @{@link RequestMapping}-annotated method can calculate the lastModified value, call
446+
* {@link RequestMapping}-annotated method can calculate the lastModified value, call
447447
* {@link org.springframework.web.context.request.WebRequest#checkNotModified(long)}
448448
* to check it, and return {@code null} if that returns {@code true}.
449449
* @see org.springframework.web.context.request.WebRequest#checkNotModified(long)
@@ -483,7 +483,7 @@ private ServletHandlerMethodResolver getMethodResolver(Object handler) {
483483
* @return the ServletRequestDataBinder instance to use
484484
* @throws Exception in case of invalid state or arguments
485485
* @see ServletRequestDataBinder#bind(javax.servlet.ServletRequest)
486-
* @see ServletRequestDataBinder#convertIfNecessary(Object, Class, org.springframework.core.MethodParameter)
486+
* @see ServletRequestDataBinder#convertIfNecessary(Object, Class, org.springframework.core.MethodParameter)
487487
*/
488488
protected ServletRequestDataBinder createBinder(HttpServletRequest request, Object target, String objectName) throws Exception {
489489
return new ServletRequestDataBinder(target, objectName);
@@ -588,7 +588,8 @@ else if (useTypeLevelMapping(request)) {
588588
if (!typeLevelPattern.startsWith("/")) {
589589
typeLevelPattern = "/" + typeLevelPattern;
590590
}
591-
if (getMatchingPattern(typeLevelPattern, lookupPath) != null) {
591+
boolean useSuffixPattern = useSuffixPattern(request);
592+
if (getMatchingPattern(typeLevelPattern, lookupPath, useSuffixPattern) != null) {
592593
if (mappingInfo.matches(request)) {
593594
match = true;
594595
mappingInfo.addMatchedPattern(typeLevelPattern);
@@ -675,6 +676,11 @@ private boolean useTypeLevelMapping(HttpServletRequest request) {
675676
return (Boolean) request.getAttribute(HandlerMapping.INTROSPECT_TYPE_LEVEL_MAPPING);
676677
}
677678

679+
private boolean useSuffixPattern(HttpServletRequest request) {
680+
Object value = request.getAttribute(DefaultAnnotationHandlerMapping.USE_DEFAULT_SUFFIX_PATTERN);
681+
return (value != null) ? (Boolean) value : Boolean.TRUE;
682+
}
683+
678684
/**
679685
* Determines the combined pattern for the given methodLevelPattern and path.
680686
* <p>Uses the following algorithm:
@@ -687,14 +693,15 @@ private boolean useTypeLevelMapping(HttpServletRequest request) {
687693
* </ol>
688694
*/
689695
private String getCombinedPattern(String methodLevelPattern, String lookupPath, HttpServletRequest request) {
696+
boolean useSuffixPattern = useSuffixPattern(request);
690697
if (useTypeLevelMapping(request)) {
691698
String[] typeLevelPatterns = getTypeLevelMapping().value();
692699
for (String typeLevelPattern : typeLevelPatterns) {
693700
if (!typeLevelPattern.startsWith("/")) {
694701
typeLevelPattern = "/" + typeLevelPattern;
695702
}
696703
String combinedPattern = pathMatcher.combine(typeLevelPattern, methodLevelPattern);
697-
String matchingPattern = getMatchingPattern(combinedPattern, lookupPath);
704+
String matchingPattern = getMatchingPattern(combinedPattern, lookupPath, useSuffixPattern);
698705
if (matchingPattern != null) {
699706
return matchingPattern;
700707
}
@@ -704,20 +711,20 @@ private String getCombinedPattern(String methodLevelPattern, String lookupPath,
704711
String bestMatchingPattern = (String) request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE);
705712
if (StringUtils.hasText(bestMatchingPattern) && bestMatchingPattern.endsWith("*")) {
706713
String combinedPattern = pathMatcher.combine(bestMatchingPattern, methodLevelPattern);
707-
String matchingPattern = getMatchingPattern(combinedPattern, lookupPath);
714+
String matchingPattern = getMatchingPattern(combinedPattern, lookupPath, useSuffixPattern);
708715
if (matchingPattern != null && !matchingPattern.equals(bestMatchingPattern)) {
709716
return matchingPattern;
710717
}
711718
}
712-
return getMatchingPattern(methodLevelPattern, lookupPath);
719+
return getMatchingPattern(methodLevelPattern, lookupPath, useSuffixPattern);
713720
}
714721

715-
private String getMatchingPattern(String pattern, String lookupPath) {
722+
private String getMatchingPattern(String pattern, String lookupPath, boolean useSuffixPattern) {
716723
if (pattern.equals(lookupPath)) {
717724
return pattern;
718725
}
719726
boolean hasSuffix = pattern.indexOf('.') != -1;
720-
if (!hasSuffix) {
727+
if (useSuffixPattern && !hasSuffix) {
721728
String patternWithSuffix = pattern + ".*";
722729
if (pathMatcher.match(patternWithSuffix, lookupPath)) {
723730
return patternWithSuffix;
@@ -727,7 +734,7 @@ private String getMatchingPattern(String pattern, String lookupPath) {
727734
return pattern;
728735
}
729736
boolean endsWithSlash = pattern.endsWith("/");
730-
if (!endsWithSlash) {
737+
if (useSuffixPattern && !endsWithSlash) {
731738
String patternWithSlash = pattern + "/";
732739
if (pathMatcher.match(patternWithSlash, lookupPath)) {
733740
return patternWithSlash;
@@ -1236,7 +1243,7 @@ else if (info2MethodCount == 1 & info1MethodCount > 1) {
12361243
private int compareAcceptHeaders(RequestMappingInfo info1, RequestMappingInfo info2) {
12371244
List<MediaType> requestAccepts = request.getHeaders().getAccept();
12381245
MediaType.sortByQualityValue(requestAccepts);
1239-
1246+
12401247
List<MediaType> info1Accepts = getAcceptHeaderValue(info1);
12411248
List<MediaType> info2Accepts = getAcceptHeaderValue(info2);
12421249

org.springframework.web.servlet/src/main/java/org/springframework/web/servlet/mvc/annotation/DefaultAnnotationHandlerMapping.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2010 the original author or authors.
2+
* Copyright 2002-2012 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@
2222
import java.util.LinkedHashSet;
2323
import java.util.Map;
2424
import java.util.Set;
25+
2526
import javax.servlet.http.HttpServletRequest;
2627

2728
import org.springframework.context.ApplicationContext;
@@ -81,9 +82,11 @@
8182
*/
8283
public class DefaultAnnotationHandlerMapping extends AbstractDetectingUrlHandlerMapping {
8384

85+
static final String USE_DEFAULT_SUFFIX_PATTERN = DefaultAnnotationHandlerMapping.class.getName() + ".useDefaultSuffixPattern";
86+
8487
private boolean useDefaultSuffixPattern = true;
8588

86-
private final Map<Class, RequestMapping> cachedMappings = new HashMap<Class, RequestMapping>();
89+
private final Map<Class<?>, RequestMapping> cachedMappings = new HashMap<Class<?>, RequestMapping>();
8790

8891

8992
/**
@@ -229,6 +232,7 @@ protected void validateHandler(Object handler, HttpServletRequest request) throw
229232
if (mapping != null) {
230233
validateMapping(mapping, request);
231234
}
235+
request.setAttribute(USE_DEFAULT_SUFFIX_PATTERN, this.useDefaultSuffixPattern);
232236
}
233237

234238
/**

org.springframework.web.servlet/src/test/java/org/springframework/web/servlet/mvc/annotation/UriTemplateServletAnnotationControllerTests.java

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2011 the original author or authors.
2+
* Copyright 2002-2012 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -351,6 +351,31 @@ public void variableNamesWithUrlExtension() throws Exception {
351351
assertEquals("foo-foo", response.getContentAsString());
352352
}
353353

354+
// SPR-9333
355+
@Test
356+
@SuppressWarnings("serial")
357+
public void suppressDefaultSuffixPattern() throws Exception {
358+
servlet = new DispatcherServlet() {
359+
@Override
360+
protected WebApplicationContext createWebApplicationContext(WebApplicationContext parent)
361+
throws BeansException {
362+
GenericWebApplicationContext wac = new GenericWebApplicationContext();
363+
wac.registerBeanDefinition("controller", new RootBeanDefinition(VariableNamesController.class));
364+
RootBeanDefinition mappingDef = new RootBeanDefinition(DefaultAnnotationHandlerMapping.class);
365+
mappingDef.getPropertyValues().add("useDefaultSuffixPattern", false);
366+
wac.registerBeanDefinition("handlerMapping", mappingDef);
367+
wac.refresh();
368+
return wac;
369+
}
370+
};
371+
servlet.init(new MockServletConfig());
372+
373+
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test/[email protected]");
374+
MockHttpServletResponse response = new MockHttpServletResponse();
375+
servlet.service(request, response);
376+
assertEquals("[email protected]", response.getContentAsString());
377+
}
378+
354379
// SPR-6906
355380
@Test
356381
public void controllerClassName() throws Exception {
@@ -389,7 +414,7 @@ protected WebApplicationContext createWebApplicationContext(WebApplicationContex
389414
@Test
390415
public void doIt() throws Exception {
391416
initServlet(Spr6978Controller.class);
392-
417+
393418
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/foo/100");
394419
MockHttpServletResponse response = new MockHttpServletResponse();
395420
servlet.service(request, response);

0 commit comments

Comments
 (0)