/** * Returns a new instance with URL patterns from the current instance ("this") and * the "other" instance as follows: * <ul> * <li>If there are patterns in both instances, combine the patterns in "this" with * the patterns in "other" using {@link PathPattern#combine(PathPattern)}. * <li>If only one instance has patterns, use them. * <li>If neither instance has patterns, use an empty String (i.e. ""). * </ul> */ @Override public PatternsRequestCondition combine(PatternsRequestCondition other) { List<PathPattern> combined = new ArrayList<>(); if (!this.patterns.isEmpty() && !other.patterns.isEmpty()) { for (PathPattern pattern1 : this.patterns) { for (PathPattern pattern2 : other.patterns) { combined.add(pattern1.combine(pattern2)); } } } else if (!this.patterns.isEmpty()) { combined.addAll(this.patterns); } else if (!other.patterns.isEmpty()) { combined.addAll(other.patterns); } return new PatternsRequestCondition(combined); }
/** * Compare the two conditions based on the URL patterns they contain. * Patterns are compared one at a time, from top to bottom. If all compared * patterns match equally, but one instance has more patterns, it is * considered a closer match. * <p>It is assumed that both instances have been obtained via * {@link #getMatchingCondition(ServerWebExchange)} to ensure they * contain only patterns that match the request and are sorted with * the best matches on top. */ @Override public int compareTo(PatternsRequestCondition other, ServerWebExchange exchange) { Iterator<PathPattern> iterator = this.patterns.iterator(); Iterator<PathPattern> iteratorOther = other.getPatterns().iterator(); while (iterator.hasNext() && iteratorOther.hasNext()) { int result = PathPattern.SPECIFICITY_COMPARATOR.compare(iterator.next(), iteratorOther.next()); if (result != 0) { return result; } } if (iterator.hasNext()) { return -1; } else if (iteratorOther.hasNext()) { return 1; } else { return 0; } }
@Test public void combineEmptySets() { PatternsRequestCondition c1 = new PatternsRequestCondition(); PatternsRequestCondition c2 = new PatternsRequestCondition(); assertEquals(createPatternsCondition(), c1.combine(c2)); }
/** * Checks if any of the patterns match the given request and returns an instance * that is guaranteed to contain matching patterns, sorted. * @param exchange the current exchange * @return the same instance if the condition contains no patterns; * or a new condition with sorted matching patterns; * or {@code null} if no patterns match. */ @Override @Nullable public PatternsRequestCondition getMatchingCondition(ServerWebExchange exchange) { if (this.patterns.isEmpty()) { return this; } SortedSet<PathPattern> matches = getMatchingPatterns(exchange); return (!matches.isEmpty() ? new PatternsRequestCondition(matches) : null); }
@Override public String toString() { StringBuilder builder = new StringBuilder("{"); if (!this.methodsCondition.isEmpty()) { Set<RequestMethod> httpMethods = this.methodsCondition.getMethods(); builder.append(httpMethods.size() == 1 ? httpMethods.iterator().next() : httpMethods); } if (!this.patternsCondition.isEmpty()) { Set<PathPattern> patterns = this.patternsCondition.getPatterns(); builder.append(" ").append(patterns.size() == 1 ? patterns.iterator().next() : patterns); } if (!this.paramsCondition.isEmpty()) { builder.append(", params ").append(this.paramsCondition); } if (!this.headersCondition.isEmpty()) { builder.append(", headers ").append(this.headersCondition); } if (!this.consumesCondition.isEmpty()) { builder.append(", consumes ").append(this.consumesCondition); } if (!this.producesCondition.isEmpty()) { builder.append(", produces ").append(this.producesCondition); } if (!this.customConditionHolder.isEmpty()) { builder.append(", and ").append(this.customConditionHolder); } builder.append('}'); return builder.toString(); }
@Test public void matchTrailingSlash() throws Exception { MockServerWebExchange exchange = MockServerWebExchange.from(get("/foo/")); PatternsRequestCondition condition = createPatternsCondition("/foo"); PatternsRequestCondition match = condition.getMatchingCondition(exchange); assertNotNull(match); assertEquals("Should match by default", "/foo", match.getPatterns().iterator().next().getPatternString()); condition = createPatternsCondition("/foo"); match = condition.getMatchingCondition(exchange); assertNotNull(match); assertEquals("Trailing slash should be insensitive to useSuffixPatternMatch settings (SPR-6164, SPR-5636)", "/foo", match.getPatterns().iterator().next().getPatternString()); PathPatternParser parser = new PathPatternParser(); parser.setMatchOptionalTrailingSeparator(false); condition = new PatternsRequestCondition(parser.parse("/foo")); match = condition.getMatchingCondition(MockServerWebExchange.from(get("/foo/"))); assertNull(match); }
private RequestMappingInfo withPrefix(RequestMappingInfo mapping) { if (!StringUtils.hasText(adminContextPath)) { return mapping; } PatternsRequestCondition patternsCondition = new PatternsRequestCondition( withNewPatterns(mapping.getPatternsCondition().getPatterns())); return new RequestMappingInfo(patternsCondition, mapping.getMethodsCondition(), mapping.getParamsCondition(), mapping.getHeadersCondition(), mapping.getConsumesCondition(), mapping.getProducesCondition(), mapping.getCustomCondition()); }
public PartialMatchHelper(Set<RequestMappingInfo> infos, ServerWebExchange exchange) { this.partialMatches.addAll(infos.stream(). filter(info -> info.getPatternsCondition().getMatchingCondition(exchange) != null). map(info -> new PartialMatch(info, exchange)). collect(Collectors.toList())); }
@Test public void compareNumberOfMatchingPatterns() throws Exception { ServerWebExchange exchange = MockServerWebExchange.from(get("/foo.html")); PatternsRequestCondition c1 = createPatternsCondition("/foo.*", "/foo.jpeg"); PatternsRequestCondition c2 = createPatternsCondition("/foo.*", "/foo.html"); PatternsRequestCondition match1 = c1.getMatchingCondition(exchange); PatternsRequestCondition match2 = c2.getMatchingCondition(exchange); assertNotNull(match1); assertEquals(1, match1.compareTo(match2, exchange)); }
@Test public void comparePatternSpecificity() throws Exception { ServerWebExchange exchange = MockServerWebExchange.from(get("/foo")); PatternsRequestCondition c1 = createPatternsCondition("/fo*"); PatternsRequestCondition c2 = createPatternsCondition("/foo"); assertEquals(1, c1.compareTo(c2, exchange)); c1 = createPatternsCondition("/fo*"); c2 = createPatternsCondition("/*oo"); assertEquals("Patterns are equally specific even if not the same", 0, c1.compareTo(c2, exchange)); }
/** * Combines "this" request mapping info (i.e. the current instance) with another request mapping info instance. * <p>Example: combine type- and method-level request mappings. * @return a new request mapping info instance; never {@code null} */ @Override public RequestMappingInfo combine(RequestMappingInfo other) { String name = combineNames(other); PatternsRequestCondition patterns = this.patternsCondition.combine(other.patternsCondition); RequestMethodsRequestCondition methods = this.methodsCondition.combine(other.methodsCondition); ParamsRequestCondition params = this.paramsCondition.combine(other.paramsCondition); HeadersRequestCondition headers = this.headersCondition.combine(other.headersCondition); ConsumesRequestCondition consumes = this.consumesCondition.combine(other.consumesCondition); ProducesRequestCondition produces = this.producesCondition.combine(other.producesCondition); RequestConditionHolder custom = this.customConditionHolder.combine(other.customConditionHolder); return new RequestMappingInfo(name, patterns, methods, params, headers, consumes, produces, custom.getCondition()); }
@Override public int hashCode() { return (this.patternsCondition.hashCode() * 31 + // primary differentiation this.methodsCondition.hashCode() + this.paramsCondition.hashCode() + this.headersCondition.hashCode() + this.consumesCondition.hashCode() + this.producesCondition.hashCode() + this.customConditionHolder.hashCode()); }
@Override public boolean equals(Object other) { if (this == other) { return true; } if (!(other instanceof RequestMappingInfo)) { return false; } RequestMappingInfo otherInfo = (RequestMappingInfo) other; return (this.patternsCondition.equals(otherInfo.patternsCondition) && this.methodsCondition.equals(otherInfo.methodsCondition) && this.paramsCondition.equals(otherInfo.paramsCondition) && this.headersCondition.equals(otherInfo.headersCondition) && this.consumesCondition.equals(otherInfo.consumesCondition) && this.producesCondition.equals(otherInfo.producesCondition) && this.customConditionHolder.equals(otherInfo.customConditionHolder)); }
/** * Checks if any of the patterns match the given request and returns an instance * that is guaranteed to contain matching patterns, sorted via * {@link PathMatcher#getPatternComparator(String)}. * <p>A matching pattern is obtained by making checks in the following order: * <ul> * <li>Direct match * <li>Pattern match with ".*" appended if the pattern doesn't already contain a "." * <li>Pattern match * <li>Pattern match with "/" appended if the pattern doesn't already end in "/" * </ul> * @param exchange the current exchange * @return the same instance if the condition contains no patterns; * or a new condition with sorted matching patterns; * or {@code null} if no patterns match. */ @Override public PatternsRequestCondition getMatchingCondition(ServerWebExchange exchange) { if (this.patterns.isEmpty()) { return this; } String lookupPath = this.pathHelper.getLookupPathForRequest(exchange); List<String> matches = getMatchingPatterns(lookupPath); return matches.isEmpty() ? null : new PatternsRequestCondition(matches, this.pathHelper, this.pathMatcher, this.useSuffixPatternMatch, this.useTrailingSlashMatch, this.fileExtensions); }
/** * Checks if all conditions in this request mapping info match the provided request and returns * a potentially new request mapping info with conditions tailored to the current request. * <p>For example the returned instance may contain the subset of URL patterns that match to * the current request, sorted with best matching patterns on top. * @return a new instance in case all conditions match; or {@code null} otherwise */ @Override @Nullable public RequestMappingInfo getMatchingCondition(ServerWebExchange exchange) { RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(exchange); ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(exchange); HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(exchange); ConsumesRequestCondition consumes = this.consumesCondition.getMatchingCondition(exchange); ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(exchange); if (methods == null || params == null || headers == null || consumes == null || produces == null) { return null; } PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(exchange); if (patterns == null) { return null; } RequestConditionHolder custom = this.customConditionHolder.getMatchingCondition(exchange); if (custom == null) { return null; } return new RequestMappingInfo(this.name, patterns, methods, params, headers, consumes, produces, custom.getCondition()); }
@Test public void compareToConsistentWithEquals() throws Exception { PatternsRequestCondition c1 = createPatternsCondition("/foo*"); PatternsRequestCondition c2 = createPatternsCondition("/foo*"); assertEquals(0, c1.compareTo(c2, MockServerWebExchange.from(get("/foo")))); }
@Test public void combineMultiplePatterns() { PatternsRequestCondition c1 = createPatternsCondition("/t1", "/t2"); PatternsRequestCondition c2 = createPatternsCondition("/m1", "/m2"); assertEquals(createPatternsCondition("/t1/m1", "/t1/m2", "/t2/m1", "/t2/m2"), c1.combine(c2)); }
@Override public int hashCode() { return (this.patternsCondition.hashCode() * 31 + // primary differentiation this.methodsCondition.hashCode() + this.paramsCondition.hashCode() + this.headersCondition.hashCode() + this.consumesCondition.hashCode() + this.producesCondition.hashCode() + this.customConditionHolder.hashCode()); }
@Override public boolean equals(Object other) { if (this == other) { return true; } if (!(other instanceof RequestMappingInfo)) { return false; } RequestMappingInfo otherInfo = (RequestMappingInfo) other; return (this.patternsCondition.equals(otherInfo.patternsCondition) && this.methodsCondition.equals(otherInfo.methodsCondition) && this.paramsCondition.equals(otherInfo.paramsCondition) && this.headersCondition.equals(otherInfo.headersCondition) && this.consumesCondition.equals(otherInfo.consumesCondition) && this.producesCondition.equals(otherInfo.producesCondition) && this.customConditionHolder.equals(otherInfo.customConditionHolder)); }
@Test public void equallyMatchingPatternsAreBothPresent() throws Exception { PatternsRequestCondition c = createPatternsCondition("/a", "/b"); assertEquals(2, c.getPatterns().size()); Iterator<PathPattern> itr = c.getPatterns().iterator(); assertEquals("/a", itr.next().getPatternString()); assertEquals("/b", itr.next().getPatternString()); }