Modify getPathRemaining to return remaining path and bound variables
Without this change it was necessary to call getPathRemaining and then chop up the path and make a call to matchAndExtract to get the bound variables for the path part that matched. With this change this is all done in the call to getPathRemaining which returns an object holding the remaining path and the bound variables. Issue: SPR-15419
This commit is contained in:
parent
88f8df4dce
commit
316a680577
|
@ -151,35 +151,37 @@ public class PathPattern implements Comparable<PathPattern> {
|
|||
* For a given path return the remaining piece that is not covered by this PathPattern.
|
||||
*
|
||||
* @param path a path that may or may not match this path pattern
|
||||
* @return the remaining path after as much has been consumed as possible by this pattern,
|
||||
* result can be the empty string if the path is entirely consumed or it will be null
|
||||
* if the path does not match
|
||||
* @return a {@link PathRemainingMatchInfo} describing the match result or null if the path does not match
|
||||
* this pattern
|
||||
*/
|
||||
public String getPathRemaining(String path) {
|
||||
public PathRemainingMatchInfo getPathRemaining(String path) {
|
||||
if (head == null) {
|
||||
if (path == null) {
|
||||
return path;
|
||||
return new PathRemainingMatchInfo(path);
|
||||
}
|
||||
else {
|
||||
return hasLength(path)?path:"";
|
||||
return new PathRemainingMatchInfo(hasLength(path)?path:"");
|
||||
}
|
||||
}
|
||||
else if (!hasLength(path)) {
|
||||
return null;
|
||||
}
|
||||
MatchingContext matchingContext = new MatchingContext(path, false);
|
||||
MatchingContext matchingContext = new MatchingContext(path, true);
|
||||
matchingContext.setMatchAllowExtraPath();
|
||||
boolean matches = head.matches(0, matchingContext);
|
||||
if (!matches) {
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
PathRemainingMatchInfo info;
|
||||
if (matchingContext.remainingPathIndex == path.length()) {
|
||||
return "";
|
||||
info = new PathRemainingMatchInfo("", matchingContext.getExtractedVariables());
|
||||
}
|
||||
else {
|
||||
return path.substring(matchingContext.remainingPathIndex);
|
||||
info = new PathRemainingMatchInfo(path.substring(matchingContext.remainingPathIndex),
|
||||
matchingContext.getExtractedVariables());
|
||||
}
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -200,8 +202,9 @@ public class PathPattern implements Comparable<PathPattern> {
|
|||
}
|
||||
|
||||
/**
|
||||
* @param path a path to match against this pattern
|
||||
* @param path a path that matches this pattern from which to extract variables
|
||||
* @return a map of extracted variables - an empty map if no variables extracted.
|
||||
* @throws IllegalStateException if the path does not match the pattern
|
||||
*/
|
||||
public Map<String, String> matchAndExtract(String path) {
|
||||
MatchingContext matchingContext = new MatchingContext(path, true);
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright 2002-2017 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.util.patterns;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* A holder for the result of a {@link PathPattern#getPathRemaining(String)} call. Holds
|
||||
* information on the path left after the first part has successfully matched a pattern
|
||||
* and any variables bound in that first part that matched.
|
||||
*
|
||||
* @author Andy Clement
|
||||
* @since 5.0
|
||||
*/
|
||||
public class PathRemainingMatchInfo {
|
||||
|
||||
private String pathRemaining;
|
||||
|
||||
private Map<String, String> matchingVariables;
|
||||
|
||||
PathRemainingMatchInfo(String pathRemaining) {
|
||||
this.pathRemaining = pathRemaining;
|
||||
}
|
||||
|
||||
PathRemainingMatchInfo(String pathRemaining, Map<String, String> matchingVariables) {
|
||||
this.pathRemaining = pathRemaining;
|
||||
this.matchingVariables = matchingVariables;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the part of a path that was not matched by a pattern
|
||||
*/
|
||||
public String getPathRemaining() {
|
||||
return pathRemaining;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return variables that were bound in the part of the path that was successfully matched.
|
||||
* Will be an empty map if no variables were bound
|
||||
*/
|
||||
public Map<String, String> getMatchingVariables() {
|
||||
return matchingVariables;
|
||||
}
|
||||
}
|
|
@ -40,25 +40,22 @@ public class PathPatternMatcherTests {
|
|||
|
||||
@Test
|
||||
public void pathRemainderBasicCases_spr15336() {
|
||||
// getPathRemaining: Given some pattern and some path, return the bit of the path
|
||||
// that was left over after the pattern part was matched.
|
||||
|
||||
// Cover all PathElement kinds:
|
||||
assertEquals("/bar", parse("/foo").getPathRemaining("/foo/bar"));
|
||||
assertEquals("/", parse("/foo").getPathRemaining("/foo/"));
|
||||
assertEquals("/bar",parse("/foo*").getPathRemaining("/foo/bar"));
|
||||
assertEquals("/bar", parse("/*").getPathRemaining("/foo/bar"));
|
||||
assertEquals("/bar", parse("/{foo}").getPathRemaining("/foo/bar"));
|
||||
// Cover all PathElement kinds
|
||||
assertEquals("/bar", parse("/foo").getPathRemaining("/foo/bar").getPathRemaining());
|
||||
assertEquals("/", parse("/foo").getPathRemaining("/foo/").getPathRemaining());
|
||||
assertEquals("/bar",parse("/foo*").getPathRemaining("/foo/bar").getPathRemaining());
|
||||
assertEquals("/bar", parse("/*").getPathRemaining("/foo/bar").getPathRemaining());
|
||||
assertEquals("/bar", parse("/{foo}").getPathRemaining("/foo/bar").getPathRemaining());
|
||||
assertNull(parse("/foo").getPathRemaining("/bar/baz"));
|
||||
assertEquals("",parse("/**").getPathRemaining("/foo/bar"));
|
||||
assertEquals("",parse("/{*bar}").getPathRemaining("/foo/bar"));
|
||||
assertEquals("/bar",parse("/a?b/d?e").getPathRemaining("/aab/dde/bar"));
|
||||
assertEquals("/bar",parse("/{abc}abc").getPathRemaining("/xyzabc/bar"));
|
||||
assertEquals("/bar",parse("/*y*").getPathRemaining("/xyzxyz/bar"));
|
||||
assertEquals("",parse("/").getPathRemaining("/"));
|
||||
assertEquals("a",parse("/").getPathRemaining("/a"));
|
||||
assertEquals("a/",parse("/").getPathRemaining("/a/"));
|
||||
assertEquals("/bar",parse("/a{abc}").getPathRemaining("/a/bar"));
|
||||
assertEquals("",parse("/**").getPathRemaining("/foo/bar").getPathRemaining());
|
||||
assertEquals("",parse("/{*bar}").getPathRemaining("/foo/bar").getPathRemaining());
|
||||
assertEquals("/bar",parse("/a?b/d?e").getPathRemaining("/aab/dde/bar").getPathRemaining());
|
||||
assertEquals("/bar",parse("/{abc}abc").getPathRemaining("/xyzabc/bar").getPathRemaining());
|
||||
assertEquals("/bar",parse("/*y*").getPathRemaining("/xyzxyz/bar").getPathRemaining());
|
||||
assertEquals("",parse("/").getPathRemaining("/").getPathRemaining());
|
||||
assertEquals("a",parse("/").getPathRemaining("/a").getPathRemaining());
|
||||
assertEquals("a/",parse("/").getPathRemaining("/a/").getPathRemaining());
|
||||
assertEquals("/bar",parse("/a{abc}").getPathRemaining("/a/bar").getPathRemaining());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -72,19 +69,27 @@ public class PathPatternMatcherTests {
|
|||
// With a /** on the end have to check if there is any more data post
|
||||
// 'the match' it starts with a separator
|
||||
assertNull(parse("/resource/**").getPathRemaining("/resourceX"));
|
||||
assertEquals("",parse("/resource/**").getPathRemaining("/resource"));
|
||||
assertEquals("",parse("/resource/**").getPathRemaining("/resource").getPathRemaining());
|
||||
|
||||
// Similar to above for the capture-the-rest variant
|
||||
assertNull(parse("/resource/{*foo}").getPathRemaining("/resourceX"));
|
||||
assertEquals("",parse("/resource/{*foo}").getPathRemaining("/resource"));
|
||||
assertEquals("",parse("/resource/{*foo}").getPathRemaining("/resource").getPathRemaining());
|
||||
|
||||
PathRemainingMatchInfo pri = parse("/aaa/{bbb}/c?d/e*f/*/g").getPathRemaining("/aaa/b/ccd/ef/x/g/i");
|
||||
assertEquals("/i",pri.getPathRemaining());
|
||||
assertEquals("b",pri.getMatchingVariables().get("bbb"));
|
||||
|
||||
pri = parse("/{aaa}_{bbb}/e*f/{x}/g").getPathRemaining("/aa_bb/ef/x/g/i");
|
||||
assertEquals("/i",pri.getPathRemaining());
|
||||
assertEquals("aa",pri.getMatchingVariables().get("aaa"));
|
||||
assertEquals("bb",pri.getMatchingVariables().get("bbb"));
|
||||
assertEquals("x",pri.getMatchingVariables().get("x"));
|
||||
|
||||
assertEquals("/i",parse("/aaa/{bbb}/c?d/e*f/*/g").getPathRemaining("/aaa/b/ccd/ef/x/g/i"));
|
||||
|
||||
assertNull(parse("/a/b").getPathRemaining(""));
|
||||
assertNull(parse("/a/b").getPathRemaining(null));
|
||||
assertEquals("/a/b",parse("").getPathRemaining("/a/b"));
|
||||
assertEquals("",parse("").getPathRemaining(""));
|
||||
assertNull(parse("").getPathRemaining(null));
|
||||
assertEquals("/a/b",parse("").getPathRemaining("/a/b").getPathRemaining());
|
||||
assertEquals("",parse("").getPathRemaining("").getPathRemaining());
|
||||
assertNull(parse("").getPathRemaining(null).getPathRemaining());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -275,6 +280,38 @@ public class PathPatternMatcherTests {
|
|||
checkCapture("/{bla}.*", "/testing.html", "bla", "testing");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void pathRemainingEnhancements_spr15419() {
|
||||
// It would be nice to partially match a path and get any bound variables in one step
|
||||
PathPattern pp = parse("/{this}/{one}/{here}");
|
||||
PathRemainingMatchInfo pri = pp.getPathRemaining("/foo/bar/goo/boo");
|
||||
assertEquals("/boo",pri.getPathRemaining());
|
||||
assertEquals("foo",pri.getMatchingVariables().get("this"));
|
||||
assertEquals("bar",pri.getMatchingVariables().get("one"));
|
||||
assertEquals("goo",pri.getMatchingVariables().get("here"));
|
||||
|
||||
pp = parse("/aaa/{foo}");
|
||||
pri = pp.getPathRemaining("/aaa/bbb");
|
||||
assertEquals("",pri.getPathRemaining());
|
||||
assertEquals("bbb",pri.getMatchingVariables().get("foo"));
|
||||
|
||||
pp = parse("/aaa/bbb");
|
||||
pri = pp.getPathRemaining("/aaa/bbb");
|
||||
assertEquals("",pri.getPathRemaining());
|
||||
assertEquals(0,pri.getMatchingVariables().size());
|
||||
|
||||
pp = parse("/*/{foo}/b*");
|
||||
pri = pp.getPathRemaining("/foo");
|
||||
assertNull(pri);
|
||||
pri = pp.getPathRemaining("/abc/def/bhi");
|
||||
assertEquals("",pri.getPathRemaining());
|
||||
assertEquals("def",pri.getMatchingVariables().get("foo"));
|
||||
|
||||
pri = pp.getPathRemaining("/abc/def/bhi/jkl");
|
||||
assertEquals("/jkl",pri.getPathRemaining());
|
||||
assertEquals("def",pri.getMatchingVariables().get("foo"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void matchStart() {
|
||||
checkStartMatches("test/{a}_{b}/foo", "test/a_b");
|
||||
|
|
|
@ -42,6 +42,7 @@ import org.springframework.web.server.WebSession;
|
|||
import org.springframework.web.util.UriUtils;
|
||||
import org.springframework.web.util.patterns.PathPattern;
|
||||
import org.springframework.web.util.patterns.PathPatternParser;
|
||||
import org.springframework.web.util.patterns.PathRemainingMatchInfo;
|
||||
|
||||
/**
|
||||
* Implementations of {@link RequestPredicate} that implement various useful
|
||||
|
@ -353,7 +354,8 @@ public abstract class RequestPredicates {
|
|||
|
||||
@Override
|
||||
public Optional<ServerRequest> nest(ServerRequest request) {
|
||||
String remainingPath = this.pattern.getPathRemaining(request.path());
|
||||
PathRemainingMatchInfo info = this.pattern.getPathRemaining(request.path());
|
||||
String remainingPath = (info == null ? null : info.getPathRemaining());
|
||||
return Optional.ofNullable(remainingPath)
|
||||
.map(path -> !path.startsWith("/") ? "/" + path : path)
|
||||
.map(path -> {
|
||||
|
|
Loading…
Reference in New Issue