Skip to content

Commit 46016d0

Browse files
bclozeljhoeller
authored andcommitted
Use LinkedHashmap to preserve insert order
In several places in the spring-webmvc module, URL patterns / objects relationships are kept in `HashMap`s. When matching with actual URLs, the algorithm uses a pattern comparator to sort the matching patterns and select the most specific. But the underlying collection implementation does not keep the original order which can lead to inconsistencies. This commit changes the underlying collection implementation to `LinkedHashmap`s, in order to keep the insert order if the comparator does not reorder entries. Issue: SPR-13798 (cherry picked from commit 3be35c0)
1 parent 241dfbf commit 46016d0

File tree

3 files changed

+37
-33
lines changed

3 files changed

+37
-33
lines changed

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

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2012 the original author or authors.
2+
* Copyright 2002-2015 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.
@@ -16,7 +16,7 @@
1616

1717
package org.springframework.web.servlet.handler;
1818

19-
import java.util.HashMap;
19+
import java.util.LinkedHashMap;
2020
import java.util.Map;
2121
import java.util.Properties;
2222

@@ -41,11 +41,10 @@
4141
* If the path doesn't begin with a slash, one is prepended.
4242
*
4343
* <p>Supports direct matches (given "/test" -> registered "/test") and "*"
44-
* matches (given "/test" -> registered "/t*"). Note that the default is
45-
* to map within the current servlet mapping if applicable; see the
46-
* {@link #setAlwaysUseFullPath "alwaysUseFullPath"} property for details.
47-
* For details on the pattern options, see the
48-
* {@link org.springframework.util.AntPathMatcher} javadoc.
44+
* pattern matches (given "/test" -> registered "/t*"). Note that the default
45+
* is to map within the current servlet mapping if applicable; see the
46+
* {@link #setAlwaysUseFullPath "alwaysUseFullPath"} property. For details on the
47+
* pattern options, see the {@link org.springframework.util.AntPathMatcher} javadoc.
4948
5049
* @author Rod Johnson
5150
* @author Juergen Hoeller
@@ -55,7 +54,7 @@
5554
*/
5655
public class SimpleUrlHandlerMapping extends AbstractUrlHandlerMapping {
5756

58-
private final Map<String, Object> urlMap = new HashMap<String, Object>();
57+
private final Map<String, Object> urlMap = new LinkedHashMap<String, Object>();
5958

6059

6160
/**

spring-webmvc/src/main/java/org/springframework/web/servlet/resource/ResourceUrlProvider.java

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import java.util.ArrayList;
2020
import java.util.Collections;
2121
import java.util.Comparator;
22-
import java.util.HashMap;
22+
import java.util.LinkedHashMap;
2323
import java.util.List;
2424
import java.util.Map;
2525
import javax.servlet.http.HttpServletRequest;
@@ -55,7 +55,7 @@ public class ResourceUrlProvider implements ApplicationListener<ContextRefreshed
5555

5656
private PathMatcher pathMatcher = new AntPathMatcher();
5757

58-
private final Map<String, ResourceHttpRequestHandler> handlerMap = new HashMap<String, ResourceHttpRequestHandler>();
58+
private final Map<String, ResourceHttpRequestHandler> handlerMap = new LinkedHashMap<String, ResourceHttpRequestHandler>();
5959

6060
private boolean autodetect = true;
6161

@@ -165,17 +165,17 @@ protected void detectResourceHandlers(ApplicationContext appContext) {
165165
* URL path to expose for public use.
166166
* @param request the current request
167167
* @param requestUrl the request URL path to resolve
168-
* @return the resolved public URL path or {@code null} if unresolved
168+
* @return the resolved public URL path, or {@code null} if unresolved
169169
*/
170170
public final String getForRequestUrl(HttpServletRequest request, String requestUrl) {
171171
if (logger.isTraceEnabled()) {
172-
logger.trace("Getting resource URL for requestURL=" + requestUrl);
172+
logger.trace("Getting resource URL for request URL \"" + requestUrl + "\"");
173173
}
174174
int index = getLookupPathIndex(request);
175175
String prefix = requestUrl.substring(0, index);
176176
String lookupPath = requestUrl.substring(index);
177177
String resolvedLookupPath = getForLookupPath(lookupPath);
178-
return (resolvedLookupPath != null) ? prefix + resolvedLookupPath : null;
178+
return (resolvedLookupPath != null ? prefix + resolvedLookupPath : null);
179179
}
180180

181181
private int getLookupPathIndex(HttpServletRequest request) {
@@ -198,22 +198,24 @@ private int getLookupPathIndex(HttpServletRequest request) {
198198
*/
199199
public final String getForLookupPath(String lookupPath) {
200200
if (logger.isTraceEnabled()) {
201-
logger.trace("Getting resource URL for lookupPath=" + lookupPath);
201+
logger.trace("Getting resource URL for lookup path \"" + lookupPath + "\"");
202202
}
203+
203204
List<String> matchingPatterns = new ArrayList<String>();
204205
for (String pattern : this.handlerMap.keySet()) {
205206
if (getPathMatcher().match(pattern, lookupPath)) {
206207
matchingPatterns.add(pattern);
207208
}
208209
}
210+
209211
if (!matchingPatterns.isEmpty()) {
210212
Comparator<String> patternComparator = getPathMatcher().getPatternComparator(lookupPath);
211213
Collections.sort(matchingPatterns, patternComparator);
212214
for(String pattern : matchingPatterns) {
213215
String pathWithinMapping = getPathMatcher().extractPathWithinPattern(pattern, lookupPath);
214216
String pathMapping = lookupPath.substring(0, lookupPath.indexOf(pathWithinMapping));
215217
if (logger.isTraceEnabled()) {
216-
logger.trace("Invoking ResourceResolverChain for URL pattern=\"" + pattern + "\"");
218+
logger.trace("Invoking ResourceResolverChain for URL pattern \"" + pattern + "\"");
217219
}
218220
ResourceHttpRequestHandler handler = this.handlerMap.get(pattern);
219221
ResourceResolverChain chain = new DefaultResourceResolverChain(handler.getResourceResolvers());
@@ -222,12 +224,15 @@ public final String getForLookupPath(String lookupPath) {
222224
continue;
223225
}
224226
if (logger.isTraceEnabled()) {
225-
logger.trace("Resolved public resource URL path=\"" + resolved + "\"");
227+
logger.trace("Resolved public resource URL path \"" + resolved + "\"");
226228
}
227229
return pathMapping + resolved;
228230
}
229231
}
230-
logger.debug("No matching resource mapping");
232+
233+
if (logger.isDebugEnabled()) {
234+
logger.debug("No matching resource mapping for lookup path \"" + lookupPath + "\"");
235+
}
231236
return null;
232237
}
233238

spring-webmvc/src/main/java/org/springframework/web/servlet/resource/VersionResourceResolver.java

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import java.util.ArrayList;
2020
import java.util.Collections;
2121
import java.util.Comparator;
22-
import java.util.HashMap;
22+
import java.util.LinkedHashMap;
2323
import java.util.List;
2424
import java.util.Map;
2525
import javax.servlet.http.HttpServletRequest;
@@ -58,7 +58,7 @@ public class VersionResourceResolver extends AbstractResourceResolver {
5858
private AntPathMatcher pathMatcher = new AntPathMatcher();
5959

6060
/** Map from path pattern -> VersionStrategy */
61-
private final Map<String, VersionStrategy> versionStrategyMap = new HashMap<String, VersionStrategy>();
61+
private final Map<String, VersionStrategy> versionStrategyMap = new LinkedHashMap<String, VersionStrategy>();
6262

6363

6464
/**
@@ -146,14 +146,14 @@ protected Resource resolveResourceInternal(HttpServletRequest request, String re
146146
String candidateVersion = versionStrategy.extractVersion(requestPath);
147147
if (StringUtils.isEmpty(candidateVersion)) {
148148
if (logger.isTraceEnabled()) {
149-
logger.trace("No version found in path=\"" + requestPath + "\"");
149+
logger.trace("No version found in path \"" + requestPath + "\"");
150150
}
151151
return null;
152152
}
153153

154154
String simplePath = versionStrategy.removeVersion(requestPath, candidateVersion);
155155
if (logger.isTraceEnabled()) {
156-
logger.trace("Extracted version from path, re-resolving without version, path=\"" + simplePath + "\"");
156+
logger.trace("Extracted version from path, re-resolving without version: \"" + simplePath + "\"");
157157
}
158158

159159
Resource baseResource = chain.resolveResource(request, simplePath, locations);
@@ -164,14 +164,14 @@ protected Resource resolveResourceInternal(HttpServletRequest request, String re
164164
String actualVersion = versionStrategy.getResourceVersion(baseResource);
165165
if (candidateVersion.equals(actualVersion)) {
166166
if (logger.isTraceEnabled()) {
167-
logger.trace("resource matches extracted version");
167+
logger.trace("Resource matches extracted version ["+ candidateVersion + "]");
168168
}
169169
return baseResource;
170170
}
171171
else {
172172
if (logger.isTraceEnabled()) {
173-
logger.trace("Potential resource found for [" + requestPath + "], but version [" +
174-
candidateVersion + "] doesn't match.");
173+
logger.trace("Potential resource found for \"" + requestPath + "\", but version [" +
174+
candidateVersion + "] does not match");
175175
}
176176
return null;
177177
}
@@ -186,12 +186,12 @@ protected String resolveUrlPathInternal(String resourceUrlPath, List<? extends R
186186
return null;
187187
}
188188
if (logger.isTraceEnabled()) {
189-
logger.trace("Getting the original resource to determine version");
189+
logger.trace("Getting the original resource to determine version for path \"" + resourceUrlPath + "\"");
190190
}
191191
Resource resource = chain.resolveResource(null, baseUrl, locations);
192192
String version = versionStrategy.getResourceVersion(resource);
193193
if (logger.isTraceEnabled()) {
194-
logger.trace("Version=" + version);
194+
logger.trace("Determined version [" + version + "] for " + resource);
195195
}
196196
return versionStrategy.addVersion(baseUrl, version);
197197
}
@@ -204,17 +204,17 @@ protected String resolveUrlPathInternal(String resourceUrlPath, List<? extends R
204204
*/
205205
protected VersionStrategy getStrategyForPath(String requestPath) {
206206
String path = "/".concat(requestPath);
207-
List<String> matchingPatterns = new ArrayList<String>();
207+
List<String> matchingPatterns = new ArrayList<String>();
208208
for (String pattern : this.versionStrategyMap.keySet()) {
209209
if (this.pathMatcher.match(pattern, path)) {
210-
matchingPatterns.add(pattern);
210+
matchingPatterns.add(pattern);
211211
}
212212
}
213-
if (!matchingPatterns.isEmpty()) {
214-
Comparator<String> comparator = this.pathMatcher.getPatternComparator(path);
215-
Collections.sort(matchingPatterns, comparator);
216-
return this.versionStrategyMap.get(matchingPatterns.get(0));
217-
}
213+
if (!matchingPatterns.isEmpty()) {
214+
Comparator<String> comparator = this.pathMatcher.getPatternComparator(path);
215+
Collections.sort(matchingPatterns, comparator);
216+
return this.versionStrategyMap.get(matchingPatterns.get(0));
217+
}
218218
return null;
219219
}
220220

0 commit comments

Comments
 (0)