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 921948ae185..c24378e189c 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 @@ -50,21 +50,47 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi private boolean useSuffixPatternMatch = true; + private boolean useRegisteredSuffixPatternMatch = false; + private boolean useTrailingSlashMatch = true; private ContentNegotiationManager contentNegotiationManager = new ContentNegotiationManager(); - private final List contentNegotiationFileExtensions = new ArrayList(); + private final List fileExtensions = new ArrayList(); /** * Whether to use suffix pattern match (".*") when matching patterns to * requests. If enabled a method mapped to "/users" also matches to "/users.*". *

The default value is {@code true}. + *

Also see {@link #setUseRegisteredSuffixPatternMatch(boolean)} for + * more fine-grained control over specific suffices to allow. */ public void setUseSuffixPatternMatch(boolean useSuffixPatternMatch) { this.useSuffixPatternMatch = useSuffixPatternMatch; } + /** + * Whether to use suffix pattern match for registered file extensions only + * when matching patterns to requests. + * + *

If enabled, a controller method mapped to "/users" also matches to + * "/users.json" assuming ".json" is a file extension registered with the + * provided {@link #setContentNegotiationManager(ContentNegotiationManager) + * contentNegotiationManager}. This can be useful for allowing only specific + * URL extensions to be used as well as in cases where a "." in the URL path + * can lead to ambiguous interpretation of path variable content, (e.g. given + * "/users/{user}" and incoming URLs such as "/users/john.j.joe" and + * "/users/john.j.joe.json"). + * + *

If enabled, this flag also enables + * {@link #setUseSuffixPatternMatch(boolean) useSuffixPatternMatch}. The + * default value is {@code false}. + */ + public void setUseRegisteredSuffixPatternMatch(boolean useRegsiteredSuffixPatternMatch) { + this.useRegisteredSuffixPatternMatch = useRegsiteredSuffixPatternMatch; + this.useSuffixPatternMatch = useRegsiteredSuffixPatternMatch ? true : this.useSuffixPatternMatch; + } + /** * Whether to match to URLs irrespective of the presence of a trailing slash. * If enabled a method mapped to "/users" also matches to "/users/". @@ -81,7 +107,6 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi public void setContentNegotiationManager(ContentNegotiationManager contentNegotiationManager) { Assert.notNull(contentNegotiationManager); this.contentNegotiationManager = contentNegotiationManager; - this.contentNegotiationFileExtensions.addAll(contentNegotiationManager.getAllFileExtensions()); } /** @@ -90,6 +115,14 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi public boolean useSuffixPatternMatch() { return this.useSuffixPatternMatch; } + + /** + * Whether to use registered suffixes for pattern matching. + */ + public boolean useRegisteredSuffixPatternMatch() { + return useRegisteredSuffixPatternMatch; + } + /** * Whether to match to URLs irrespective of the presence of a trailing slash. */ @@ -105,10 +138,18 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi } /** - * Return the known file extensions for content negotiation. + * Return the file extensions to use for suffix pattern matching. */ - public List getContentNegotiationFileExtensions() { - return this.contentNegotiationFileExtensions; + public List getFileExtensions() { + return fileExtensions; + } + + @Override + public void afterPropertiesSet() { + super.afterPropertiesSet(); + if (this.useRegisteredSuffixPatternMatch) { + this.fileExtensions.addAll(contentNegotiationManager.getAllFileExtensions()); + } } /** @@ -187,7 +228,7 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi private RequestMappingInfo createRequestMappingInfo(RequestMapping annotation, RequestCondition customCondition) { return new RequestMappingInfo( new PatternsRequestCondition(annotation.value(), getUrlPathHelper(), getPathMatcher(), - this.useSuffixPatternMatch, this.useTrailingSlashMatch, this.contentNegotiationFileExtensions), + this.useSuffixPatternMatch, this.useTrailingSlashMatch, this.fileExtensions), new RequestMethodsRequestCondition(annotation.method()), new ParamsRequestCondition(annotation.params()), new HeadersRequestCondition(annotation.headers()), diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMappingTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMappingTests.java new file mode 100644 index 00000000000..b5a6bd139e5 --- /dev/null +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMappingTests.java @@ -0,0 +1,81 @@ +/* + * Copyright 2002-2012 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.mvc.method.annotation; + +import static org.junit.Assert.*; + +import java.util.Arrays; +import java.util.Collections; +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.http.MediaType; +import org.springframework.web.accept.ContentNegotiationManager; +import org.springframework.web.accept.PathExtensionContentNegotiationStrategy; +import org.springframework.web.context.support.StaticWebApplicationContext; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + +/** + * Tests for {@link RequestMappingHandlerMapping}. + * + * @author Rossen Stoyanchev + */ +public class RequestMappingHandlerMappingTests { + + private RequestMappingHandlerMapping handlerMapping; + + @Before + public void setup() { + this.handlerMapping = new RequestMappingHandlerMapping(); + this.handlerMapping.setApplicationContext(new StaticWebApplicationContext()); + } + + @Test + public void useRegsiteredSuffixPatternMatch() { + assertTrue(this.handlerMapping.useSuffixPatternMatch()); + assertFalse(this.handlerMapping.useRegisteredSuffixPatternMatch()); + + Map fileExtensions = Collections.singletonMap("json", MediaType.APPLICATION_JSON); + PathExtensionContentNegotiationStrategy strategy = new PathExtensionContentNegotiationStrategy(fileExtensions); + ContentNegotiationManager manager = new ContentNegotiationManager(strategy); + + this.handlerMapping.setContentNegotiationManager(manager); + this.handlerMapping.setUseRegisteredSuffixPatternMatch(true); + this.handlerMapping.afterPropertiesSet(); + + assertTrue(this.handlerMapping.useSuffixPatternMatch()); + assertTrue(this.handlerMapping.useRegisteredSuffixPatternMatch()); + assertEquals(Arrays.asList("json"), this.handlerMapping.getFileExtensions()); + } + + @Test + public void useSuffixPatternMatch() { + assertTrue(this.handlerMapping.useSuffixPatternMatch()); + + this.handlerMapping.setUseSuffixPatternMatch(false); + assertFalse(this.handlerMapping.useSuffixPatternMatch()); + + this.handlerMapping.setUseRegisteredSuffixPatternMatch(false); + assertFalse("'false' registeredSuffixPatternMatch shouldn't impact suffixPatternMatch", + this.handlerMapping.useSuffixPatternMatch()); + + this.handlerMapping.setUseRegisteredSuffixPatternMatch(true); + assertTrue("'true' registeredSuffixPatternMatch should enable suffixPatternMatch", + this.handlerMapping.useSuffixPatternMatch()); + } + +}