3434import org .springframework .web .util .UrlPathHelper ;
3535
3636/**
37- * A logical disjunction (' || ') request condition that matches a request
38- * against a set of URL path patterns.
39- *
37+ * A logical disjunction (' || ') request condition that matches a request
38+ * against a set of URL path patterns.
39+ *
4040 * @author Rossen Stoyanchev
4141 * @since 3.1
4242 */
4343public final class PatternsRequestCondition extends AbstractRequestCondition <PatternsRequestCondition > {
44-
45- private final Set <String > patterns ;
44+
45+ private final Set <String > patterns ;
4646
4747 private final UrlPathHelper urlPathHelper ;
48-
48+
4949 private final PathMatcher pathMatcher ;
5050
5151 private final boolean useSuffixPatternMatch ;
5252
5353 private final boolean useTrailingSlashMatch ;
54-
54+
55+ private final List <String > fileExtensions = new ArrayList <String >();
56+
5557 /**
5658 * Creates a new instance with the given URL patterns.
57- * Each pattern that is not empty and does not start with "/" is pre-pended with "/".
58- * @param patterns 0 or more URL patterns; if 0 the condition will match to every request.
59+ * Each pattern that is not empty and does not start with "/" is prepended with "/".
60+ * @param patterns 0 or more URL patterns; if 0 the condition will match to every request.
5961 */
6062 public PatternsRequestCondition (String ... patterns ) {
61- this (asList (patterns ), null , null , true , true );
63+ this (asList (patterns ), null , null , true , true , null );
64+ }
65+
66+ /**
67+ * Additional constructor with flags for using suffix pattern (.*) and
68+ * trailing slash matches.
69+ *
70+ * @param patterns the URL patterns to use; if 0, the condition will match to every request.
71+ * @param urlPathHelper for determining the lookup path of a request
72+ * @param pathMatcher for path matching with patterns
73+ * @param useSuffixPatternMatch whether to enable matching by suffix (".*")
74+ * @param useTrailingSlashMatch whether to match irrespective of a trailing slash
75+ */
76+ public PatternsRequestCondition (String [] patterns , UrlPathHelper urlPathHelper , PathMatcher pathMatcher ,
77+ boolean useSuffixPatternMatch , boolean useTrailingSlashMatch ) {
78+
79+ this (asList (patterns ), urlPathHelper , pathMatcher , useSuffixPatternMatch , useTrailingSlashMatch , null );
6280 }
6381
6482 /**
6583 * Creates a new instance with the given URL patterns.
6684 * Each pattern that is not empty and does not start with "/" is pre-pended with "/".
67- * @param patterns the URL patterns to use; if 0, the condition will match to every request.
85+ * @param patterns the URL patterns to use; if 0, the condition will match to every request.
6886 * @param urlPathHelper a {@link UrlPathHelper} for determining the lookup path for a request
6987 * @param pathMatcher a {@link PathMatcher} for pattern path matching
7088 * @param useSuffixPatternMatch whether to enable matching by suffix (".*")
7189 * @param useTrailingSlashMatch whether to match irrespective of a trailing slash
90+ * @param fileExtensions a list of file extensions to consider for path matching
7291 */
73- public PatternsRequestCondition (String [] patterns ,
74- UrlPathHelper urlPathHelper ,
75- PathMatcher pathMatcher ,
76- boolean useSuffixPatternMatch ,
77- boolean useTrailingSlashMatch ) {
78- this (asList (patterns ), urlPathHelper , pathMatcher , useSuffixPatternMatch , useTrailingSlashMatch );
92+ public PatternsRequestCondition (String [] patterns , UrlPathHelper urlPathHelper ,
93+ PathMatcher pathMatcher , boolean useSuffixPatternMatch , boolean useTrailingSlashMatch ,
94+ List <String > fileExtensions ) {
95+
96+ this (asList (patterns ), urlPathHelper , pathMatcher , useSuffixPatternMatch , useTrailingSlashMatch , fileExtensions );
7997 }
8098
8199 /**
82100 * Private constructor accepting a collection of patterns.
101+ * @param fileExtensionResolver
83102 */
84- private PatternsRequestCondition (Collection <String > patterns ,
85- UrlPathHelper urlPathHelper ,
86- PathMatcher pathMatcher ,
87- boolean useSuffixPatternMatch ,
88- boolean useTrailingSlashMatch ) {
103+ private PatternsRequestCondition (Collection <String > patterns , UrlPathHelper urlPathHelper ,
104+ PathMatcher pathMatcher , boolean useSuffixPatternMatch , boolean useTrailingSlashMatch ,
105+ List <String > fileExtensions ) {
106+
89107 this .patterns = Collections .unmodifiableSet (prependLeadingSlash (patterns ));
90108 this .urlPathHelper = urlPathHelper != null ? urlPathHelper : new UrlPathHelper ();
91109 this .pathMatcher = pathMatcher != null ? pathMatcher : new AntPathMatcher ();
92110 this .useSuffixPatternMatch = useSuffixPatternMatch ;
93111 this .useTrailingSlashMatch = useTrailingSlashMatch ;
112+ if (fileExtensions != null ) {
113+ for (String fileExtension : fileExtensions ) {
114+ this .fileExtensions .add ("." + fileExtension );
115+ }
116+ }
94117 }
95118
96119 private static List <String > asList (String ... patterns ) {
@@ -126,15 +149,15 @@ protected String getToStringInfix() {
126149 }
127150
128151 /**
129- * Returns a new instance with URL patterns from the current instance ("this") and
130- * the "other" instance as follows:
152+ * Returns a new instance with URL patterns from the current instance ("this") and
153+ * the "other" instance as follows:
131154 * <ul>
132- * <li>If there are patterns in both instances, combine the patterns in "this" with
155+ * <li>If there are patterns in both instances, combine the patterns in "this" with
133156 * the patterns in "other" using {@link PathMatcher#combine(String, String)}.
134157 * <li>If only one instance has patterns, use them.
135158 * <li>If neither instance has patterns, use an empty String (i.e. "").
136159 * </ul>
137- */
160+ */
138161 public PatternsRequestCondition combine (PatternsRequestCondition other ) {
139162 Set <String > result = new LinkedHashSet <String >();
140163 if (!this .patterns .isEmpty () && !other .patterns .isEmpty ()) {
@@ -154,26 +177,26 @@ else if (!other.patterns.isEmpty()) {
154177 result .add ("" );
155178 }
156179 return new PatternsRequestCondition (result , this .urlPathHelper , this .pathMatcher , this .useSuffixPatternMatch ,
157- this .useTrailingSlashMatch );
180+ this .useTrailingSlashMatch , this . fileExtensions );
158181 }
159182
160183 /**
161- * Checks if any of the patterns match the given request and returns an instance
162- * that is guaranteed to contain matching patterns, sorted via
163- * {@link PathMatcher#getPatternComparator(String)}.
164- *
184+ * Checks if any of the patterns match the given request and returns an instance
185+ * that is guaranteed to contain matching patterns, sorted via
186+ * {@link PathMatcher#getPatternComparator(String)}.
187+ *
165188 * <p>A matching pattern is obtained by making checks in the following order:
166189 * <ul>
167190 * <li>Direct match
168191 * <li>Pattern match with ".*" appended if the pattern doesn't already contain a "."
169192 * <li>Pattern match
170193 * <li>Pattern match with "/" appended if the pattern doesn't already end in "/"
171194 * </ul>
172- *
195+ *
173196 * @param request the current request
174- *
175- * @return the same instance if the condition contains no patterns;
176- * or a new condition with sorted matching patterns;
197+ *
198+ * @return the same instance if the condition contains no patterns;
199+ * or a new condition with sorted matching patterns;
177200 * or {@code null} if no patterns match.
178201 */
179202 public PatternsRequestCondition getMatchingCondition (HttpServletRequest request ) {
@@ -189,19 +212,28 @@ public PatternsRequestCondition getMatchingCondition(HttpServletRequest request)
189212 }
190213 }
191214 Collections .sort (matches , this .pathMatcher .getPatternComparator (lookupPath ));
192- return matches .isEmpty () ? null :
215+ return matches .isEmpty () ? null :
193216 new PatternsRequestCondition (matches , this .urlPathHelper , this .pathMatcher , this .useSuffixPatternMatch ,
194- this .useTrailingSlashMatch );
217+ this .useTrailingSlashMatch , this . fileExtensions );
195218 }
196219
197220 private String getMatchingPattern (String pattern , String lookupPath ) {
198221 if (pattern .equals (lookupPath )) {
199222 return pattern ;
200223 }
201224 if (this .useSuffixPatternMatch ) {
202- boolean hasSuffix = pattern .indexOf ('.' ) != -1 ;
203- if (!hasSuffix && this .pathMatcher .match (pattern + ".*" , lookupPath )) {
204- return pattern + ".*" ;
225+ if (useSmartSuffixPatternMatch (pattern , lookupPath )) {
226+ for (String extension : this .fileExtensions ) {
227+ if (this .pathMatcher .match (pattern + extension , lookupPath )) {
228+ return pattern + extension ;
229+ }
230+ }
231+ }
232+ else {
233+ boolean hasSuffix = pattern .indexOf ('.' ) != -1 ;
234+ if (!hasSuffix && this .pathMatcher .match (pattern + ".*" , lookupPath )) {
235+ return pattern + ".*" ;
236+ }
205237 }
206238 }
207239 if (this .pathMatcher .match (pattern , lookupPath )) {
@@ -217,15 +249,23 @@ private String getMatchingPattern(String pattern, String lookupPath) {
217249 }
218250
219251 /**
220- * Compare the two conditions based on the URL patterns they contain.
221- * Patterns are compared one at a time, from top to bottom via
222- * {@link PathMatcher#getPatternComparator(String)}. If all compared
223- * patterns match equally, but one instance has more patterns, it is
252+ * Whether to match by known file extensions. Return "true" if file extensions
253+ * are configured, and the lookup path has a suffix.
254+ */
255+ private boolean useSmartSuffixPatternMatch (String pattern , String lookupPath ) {
256+ return (!this .fileExtensions .isEmpty () && lookupPath .indexOf ('.' ) != -1 ) ;
257+ }
258+
259+ /**
260+ * Compare the two conditions based on the URL patterns they contain.
261+ * Patterns are compared one at a time, from top to bottom via
262+ * {@link PathMatcher#getPatternComparator(String)}. If all compared
263+ * patterns match equally, but one instance has more patterns, it is
224264 * considered a closer match.
225- *
226- * <p>It is assumed that both instances have been obtained via
227- * {@link #getMatchingCondition(HttpServletRequest)} to ensure they
228- * contain only patterns that match the request and are sorted with
265+ *
266+ * <p>It is assumed that both instances have been obtained via
267+ * {@link #getMatchingCondition(HttpServletRequest)} to ensure they
268+ * contain only patterns that match the request and are sorted with
229269 * the best matches on top.
230270 */
231271 public int compareTo (PatternsRequestCondition other , HttpServletRequest request ) {
0 commit comments