Browse Source

Refine PathContainer.Segment value representation

Segment.value() now returns the actual original path segment value
including path parameters while semicolonContent() is removed.

valueDecoded() is renamed to valueToMatch() to reflect it is the value
for pattern matching which is not only decoded but also has path
parameters removed.
pull/1475/head
Rossen Stoyanchev 9 years ago
parent
commit
1d201a55db
  1. 59
      spring-web/src/main/java/org/springframework/http/server/reactive/DefaultPathContainer.java
  2. 3
      spring-web/src/main/java/org/springframework/http/server/reactive/DefaultRequestPath.java
  3. 17
      spring-web/src/main/java/org/springframework/http/server/reactive/PathContainer.java
  4. 2
      spring-web/src/main/java/org/springframework/web/util/pattern/CaptureTheRestPathElement.java
  5. 4
      spring-web/src/main/java/org/springframework/web/util/pattern/LiteralPathElement.java
  6. 2
      spring-web/src/main/java/org/springframework/web/util/pattern/PathPattern.java
  7. 4
      spring-web/src/main/java/org/springframework/web/util/pattern/SingleCharWildcardedPathElement.java
  8. 2
      spring-web/src/main/java/org/springframework/web/util/pattern/WildcardPathElement.java
  9. 28
      spring-web/src/test/java/org/springframework/http/server/reactive/DefaultPathContainerTests.java

59
spring-web/src/main/java/org/springframework/http/server/reactive/DefaultPathContainer.java

@ -119,14 +119,15 @@ class DefaultPathContainer implements PathContainer {
private static PathContainer.Segment parsePathSegment(String input, Charset charset) { private static PathContainer.Segment parsePathSegment(String input, Charset charset) {
int index = input.indexOf(';'); int index = input.indexOf(';');
if (index == -1) { if (index == -1) {
String inputDecoded = StringUtils.uriDecode(input, charset); String valueToMatch = StringUtils.uriDecode(input, charset);
return new DefaultPathSegment(input, inputDecoded, "", EMPTY_MAP); return new DefaultPathSegment(input, valueToMatch, EMPTY_MAP);
}
else {
String valueToMatch = StringUtils.uriDecode(input.substring(0, index), charset);
String pathParameterContent = input.substring(index);
MultiValueMap<String, String> parameters = parseParams(pathParameterContent, charset);
return new DefaultPathSegment(input, valueToMatch, parameters);
} }
String value = input.substring(0, index);
String valueDecoded = StringUtils.uriDecode(value, charset);
String semicolonContent = input.substring(index);
MultiValueMap<String, String> parameters = parseParams(semicolonContent, charset);
return new DefaultPathSegment(value, valueDecoded, semicolonContent, parameters);
} }
private static MultiValueMap<String, String> parseParams(String input, Charset charset) { private static MultiValueMap<String, String> parseParams(String input, Charset charset) {
@ -189,22 +190,17 @@ class DefaultPathContainer implements PathContainer {
private final String value; private final String value;
private final String valueDecoded; private final String valueToMatch;
private final char[] valueDecodedChars;
private final String semicolonContent; private final char[] valueToMatchAsChars;
private final MultiValueMap<String, String> parameters; private final MultiValueMap<String, String> parameters;
DefaultPathSegment(String value, String valueDecoded, String semicolonContent, DefaultPathSegment(String value, String valueToMatch, MultiValueMap<String, String> params) {
MultiValueMap<String, String> params) {
Assert.isTrue(!value.contains("/"), () -> "Invalid path segment value: " + value); Assert.isTrue(!value.contains("/"), () -> "Invalid path segment value: " + value);
this.value = value; this.value = value;
this.valueDecoded = valueDecoded; this.valueToMatch = valueToMatch;
this.valueDecodedChars = valueDecoded.toCharArray(); this.valueToMatchAsChars = valueToMatch.toCharArray();
this.semicolonContent = semicolonContent;
this.parameters = CollectionUtils.unmodifiableMultiValueMap(params); this.parameters = CollectionUtils.unmodifiableMultiValueMap(params);
} }
@ -214,18 +210,13 @@ class DefaultPathContainer implements PathContainer {
} }
@Override @Override
public String valueDecoded() { public String valueToMatch() {
return this.valueDecoded; return this.valueToMatch;
} }
@Override @Override
public char[] valueDecodedChars() { public char[] valueToMatchAsChars() {
return this.valueDecodedChars; return this.valueToMatchAsChars;
}
@Override
public String semicolonContent() {
return this.semicolonContent;
} }
@Override @Override
@ -241,26 +232,16 @@ class DefaultPathContainer implements PathContainer {
if (other == null || getClass() != other.getClass()) { if (other == null || getClass() != other.getClass()) {
return false; return false;
} }
return this.value.equals(((DefaultPathSegment) other).value);
DefaultPathSegment segment = (DefaultPathSegment) other;
return (this.value.equals(segment.value) &&
this.semicolonContent.equals(segment.semicolonContent) &&
this.parameters.equals(segment.parameters));
} }
@Override @Override
public int hashCode() { public int hashCode() {
int result = this.value.hashCode(); return this.value.hashCode();
result = 31 * result + this.semicolonContent.hashCode();
result = 31 * result + this.parameters.hashCode();
return result;
} }
public String toString() { public String toString() {
return "[value='" + this.value + "\', " + return "[value='" + this.value + "', parameters=" + this.parameters + "']"; }
"semicolonContent='" + this.semicolonContent + "\', " +
"parameters=" + this.parameters + "']";
}
} }
} }

3
spring-web/src/main/java/org/springframework/http/server/reactive/DefaultRequestPath.java

@ -65,9 +65,6 @@ class DefaultRequestPath implements RequestPath {
for (int i=0; i < path.elements().size(); i++) { for (int i=0; i < path.elements().size(); i++) {
PathContainer.Element element = path.elements().get(i); PathContainer.Element element = path.elements().get(i);
counter += element.value().length(); counter += element.value().length();
if (element instanceof PathContainer.Segment) {
counter += ((Segment) element).semicolonContent().length();
}
if (length == counter) { if (length == counter) {
return path.subPath(0, i + 1); return path.subPath(0, i + 1);
} }

17
spring-web/src/main/java/org/springframework/http/server/reactive/PathContainer.java

@ -104,21 +104,16 @@ public interface PathContainer {
interface Segment extends Element { interface Segment extends Element {
/** /**
* Return the path segment {@link #value()} decoded. * Return the path segment value to use for pattern matching purposes.
* This may differ from {@link #value()} such as being decoded, without
* path parameters, etc.
*/ */
String valueDecoded(); String valueToMatch();
/** /**
* Variant of {@link #valueDecoded()} as a {@code char[]}. * Variant of {@link #valueToMatch()} as a {@code char[]}.
*/ */
char[] valueDecodedChars(); char[] valueToMatchAsChars();
/**
* Return the portion of the path segment after and including the first
* ";" (semicolon) representing path parameters. The actual parsed
* parameters if any can be obtained via {@link #parameters()}.
*/
String semicolonContent();
/** /**
* Path parameters parsed from the path segment. * Path parameters parsed from the path segment.

2
spring-web/src/main/java/org/springframework/web/util/pattern/CaptureTheRestPathElement.java

@ -86,7 +86,7 @@ class CaptureTheRestPathElement extends PathElement {
for (int i = fromSegment, max = pathElements.size(); i < max; i++) { for (int i = fromSegment, max = pathElements.size(); i < max; i++) {
Element element = pathElements.get(i); Element element = pathElements.get(i);
if (element instanceof Segment) { if (element instanceof Segment) {
buf.append(((Segment)element).valueDecoded()); buf.append(((Segment)element).valueToMatch());
} }
else { else {
buf.append(element.value()); buf.append(element.value());

4
spring-web/src/main/java/org/springframework/web/util/pattern/LiteralPathElement.java

@ -62,13 +62,13 @@ class LiteralPathElement extends PathElement {
if (!(element instanceof Segment)) { if (!(element instanceof Segment)) {
return false; return false;
} }
String value = ((Segment)element).valueDecoded(); String value = ((Segment)element).valueToMatch();
if (value.length() != len) { if (value.length() != len) {
// Not enough data to match this path element // Not enough data to match this path element
return false; return false;
} }
char[] data = ((Segment)element).valueDecodedChars(); char[] data = ((Segment)element).valueToMatchAsChars();
if (this.caseSensitive) { if (this.caseSensitive) {
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
if (data[i] != this.text[i]) { if (data[i] != this.text[i]) {

2
spring-web/src/main/java/org/springframework/web/util/pattern/PathPattern.java

@ -683,7 +683,7 @@ public class PathPattern implements Comparable<PathPattern> {
String pathElementValue(int pathIndex) { String pathElementValue(int pathIndex) {
Element element = (pathIndex < pathLength) ? pathElements.get(pathIndex) : null; Element element = (pathIndex < pathLength) ? pathElements.get(pathIndex) : null;
if (element instanceof Segment) { if (element instanceof Segment) {
return ((Segment)element).valueDecoded(); return ((Segment)element).valueToMatch();
} }
return ""; return "";
} }

4
spring-web/src/main/java/org/springframework/web/util/pattern/SingleCharWildcardedPathElement.java

@ -68,13 +68,13 @@ class SingleCharWildcardedPathElement extends PathElement {
if (!(element instanceof Segment)) { if (!(element instanceof Segment)) {
return false; return false;
} }
String value = ((Segment)element).valueDecoded(); String value = ((Segment)element).valueToMatch();
if (value.length() != len) { if (value.length() != len) {
// Not enough data to match this path element // Not enough data to match this path element
return false; return false;
} }
char[] data = ((Segment)element).valueDecodedChars(); char[] data = ((Segment)element).valueToMatchAsChars();
if (this.caseSensitive) { if (this.caseSensitive) {
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
char ch = this.text[i]; char ch = this.text[i];

2
spring-web/src/main/java/org/springframework/web/util/pattern/WildcardPathElement.java

@ -50,7 +50,7 @@ class WildcardPathElement extends PathElement {
// Should not match a separator // Should not match a separator
return false; return false;
} }
segmentData = ((Segment)element).valueDecoded(); segmentData = ((Segment)element).valueToMatch();
pathIndex++; pathIndex++;
} }

28
spring-web/src/test/java/org/springframework/http/server/reactive/DefaultPathContainerTests.java

@ -38,14 +38,14 @@ public class DefaultPathContainerTests {
@Test @Test
public void pathSegment() throws Exception { public void pathSegment() throws Exception {
// basic // basic
testPathSegment("cars", "", "cars", "cars", new LinkedMultiValueMap<>()); testPathSegment("cars", "cars", new LinkedMultiValueMap<>());
// empty // empty
testPathSegment("", "", "", "", new LinkedMultiValueMap<>()); testPathSegment("", "", new LinkedMultiValueMap<>());
// spaces // spaces
testPathSegment("%20%20", "", "%20%20", " ", new LinkedMultiValueMap<>()); testPathSegment("%20%20", " ", new LinkedMultiValueMap<>());
testPathSegment("%20a%20", "", "%20a%20", " a ", new LinkedMultiValueMap<>()); testPathSegment("%20a%20", " a ", new LinkedMultiValueMap<>());
} }
@Test @Test
@ -56,30 +56,29 @@ public class DefaultPathContainerTests {
params.add("colors", "blue"); params.add("colors", "blue");
params.add("colors", "green"); params.add("colors", "green");
params.add("year", "2012"); params.add("year", "2012");
testPathSegment("cars", ";colors=red,blue,green;year=2012", "cars", "cars", params); testPathSegment("cars;colors=red,blue,green;year=2012", "cars", params);
// trailing semicolon // trailing semicolon
params = new LinkedMultiValueMap<>(); params = new LinkedMultiValueMap<>();
params.add("p", "1"); params.add("p", "1");
testPathSegment("path", ";p=1;", "path", "path", params); testPathSegment("path;p=1;", "path", params);
// params with spaces // params with spaces
params = new LinkedMultiValueMap<>(); params = new LinkedMultiValueMap<>();
params.add("param name", "param value"); params.add("param name", "param value");
testPathSegment("path", ";param%20name=param%20value;%20", "path", "path", params); testPathSegment("path;param%20name=param%20value;%20", "path", params);
// empty params // empty params
params = new LinkedMultiValueMap<>(); params = new LinkedMultiValueMap<>();
params.add("p", "1"); params.add("p", "1");
testPathSegment("path", ";;;%20;%20;p=1;%20", "path", "path", params); testPathSegment("path;;;%20;%20;p=1;%20", "path", params);
} }
private void testPathSegment(String rawValue, String semicolonContent, private void testPathSegment(String rawValue, String valueToMatch, MultiValueMap<String, String> params) {
String value, String valueDecoded, MultiValueMap<String, String> params) {
PathContainer container = DefaultPathContainer.parsePath(rawValue + semicolonContent, UTF_8); PathContainer container = DefaultPathContainer.parsePath(rawValue, UTF_8);
if ("".equals(value)) { if ("".equals(rawValue)) {
assertEquals(0, container.elements().size()); assertEquals(0, container.elements().size());
return; return;
} }
@ -87,9 +86,8 @@ public class DefaultPathContainerTests {
assertEquals(1, container.elements().size()); assertEquals(1, container.elements().size());
PathContainer.Segment segment = (PathContainer.Segment) container.elements().get(0); PathContainer.Segment segment = (PathContainer.Segment) container.elements().get(0);
assertEquals("value: '" + rawValue + "'", value, segment.value()); assertEquals("value: '" + rawValue + "'", rawValue, segment.value());
assertEquals("valueDecoded: '" + rawValue + "'", valueDecoded, segment.valueDecoded()); assertEquals("valueToMatch: '" + rawValue + "'", valueToMatch, segment.valueToMatch());
assertEquals("semicolonContent: '" + rawValue + "'", semicolonContent, segment.semicolonContent());
assertEquals("params: '" + rawValue + "'", params, segment.parameters()); assertEquals("params: '" + rawValue + "'", params, segment.parameters());
} }

Loading…
Cancel
Save