Issue #4776 - UriTemplatePathSpec sorting on simplified path spec.

+ Moved testcase to jetty-http (better place for it)
+ Introduced simplified path spec to aid in sorting
+ Made .getMatches(String) return sorted list by best match.

Signed-off-by: Joakim Erdfelt <joakim.erdfelt@gmail.com>
Signed-off-by: Ludovic Orban <lorban@bitronix.be>
This commit is contained in:
Joakim Erdfelt 2020-04-17 18:30:36 +02:00 committed by Ludovic Orban
parent 00d68e2144
commit 79e76544ff
4 changed files with 112 additions and 26 deletions

View File

@ -0,0 +1,35 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http.pathmap;
import java.util.Comparator;
/**
* Sort {@link MappedResource}s by their {@link MappedResource#getPathSpec()} logical declarations.
*/
public class LogicalDeclarationComparator implements Comparator<MappedResource>
{
public static final LogicalDeclarationComparator INSTANCE = new LogicalDeclarationComparator();
@Override
public int compare(MappedResource o1, MappedResource o2)
{
return o1.getPathSpec().compareTo(o2.getPathSpec());
}
}

View File

@ -116,6 +116,7 @@ public class PathMappings<E> implements Iterable<MappedResource<E>>, Dumpable
break;
}
}
ret.sort(LogicalDeclarationComparator.INSTANCE);
return ret;
}

View File

@ -68,6 +68,10 @@ public class UriTemplatePathSpec implements PathSpec
private final int _specLength;
private final Pattern _pattern;
private final String[] _variables;
/**
* The logical (simplified) declaration
*/
private final String _logicalDeclaration;
public UriTemplatePathSpec(String rawSpec)
{
@ -81,6 +85,7 @@ public class UriTemplatePathSpec implements PathSpec
_specLength = 1;
_pattern = Pattern.compile("^/$");
_variables = new String[0];
_logicalDeclaration = "/";
return;
}
@ -104,6 +109,7 @@ public class UriTemplatePathSpec implements PathSpec
// split up into path segments (ignoring the first slash that will always be empty)
String[] segments = rawSpec.substring(1).split("/");
char[] segmentSignature = new char[segments.length];
StringBuilder logicalSignature = new StringBuilder();
int pathDepth = segments.length;
for (int i = 0; i < segments.length; i++)
{
@ -123,6 +129,7 @@ public class UriTemplatePathSpec implements PathSpec
assertIsValidVariableLiteral(variable, declaration);
segmentSignature[i] = 'v'; // variable
logicalSignature.append("/*");
// valid variable name
varNames.add(variable);
// build regex
@ -147,6 +154,7 @@ public class UriTemplatePathSpec implements PathSpec
{
// valid path segment
segmentSignature[i] = 'e'; // exact
logicalSignature.append('/').append(segment);
// build regex
regex.append('/');
// escape regex special characters
@ -162,7 +170,10 @@ public class UriTemplatePathSpec implements PathSpec
// Handle trailing slash (which is not picked up during split)
if (rawSpec.charAt(rawSpec.length() - 1) == '/')
{
regex.append('/');
logicalSignature.append('/');
}
regex.append('$');
@ -190,6 +201,7 @@ public class UriTemplatePathSpec implements PathSpec
_specLength = declaration.length();
_pattern = pattern;
_variables = variables;
_logicalDeclaration = logicalSignature.toString();
}
/**
@ -265,6 +277,20 @@ public class UriTemplatePathSpec implements PathSpec
return false;
}
@Override
public int compareTo(PathSpec other)
{
if (other instanceof UriTemplatePathSpec)
{
UriTemplatePathSpec otherUriPathSpec = (UriTemplatePathSpec)other;
return otherUriPathSpec._logicalDeclaration.compareTo(this._logicalDeclaration);
}
else
{
return PathSpec.super.compareTo(other);
}
}
public Map<String, String> getPathParams(String path)
{
Matcher matcher = getMatcher(path);

View File

@ -1,22 +1,44 @@
package org.eclipse.jetty.websocket.servlet;
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.http.pathmap;
import java.util.List;
import org.eclipse.jetty.http.pathmap.MappedResource;
import org.eclipse.jetty.http.pathmap.PathMappings;
import org.eclipse.jetty.http.pathmap.UriTemplatePathSpec;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.jupiter.api.Assertions.assertNull;
public class WebSocketUriMappingTest
{
private PathMappings<String> mapping = new PathMappings<>();
private String getMatch(String pathSpecString)
private String getBestMatch(String uriPath)
{
MappedResource<String> resource = mapping.getMatch(pathSpecString);
return resource == null ? null : resource.getResource();
List<MappedResource<String>> resources = mapping.getMatches(uriPath);
assertThat("Matches on " + uriPath, resources, is(not(nullValue())));
if (resources.isEmpty())
return null;
return resources.get(0).getResource();
}
@Test
@ -24,8 +46,8 @@ public class WebSocketUriMappingTest
{
mapping.put("/a/b", "endpointA");
assertThat(getMatch("/a/b"), is("endpointA"));
assertNull(getMatch("/a/c"));
assertThat(getBestMatch("/a/b"), is("endpointA"));
assertNull(getBestMatch("/a/c"));
}
@Test
@ -33,10 +55,10 @@ public class WebSocketUriMappingTest
{
mapping.put(new UriTemplatePathSpec("/a/{var}"), "endpointA");
assertThat(getMatch("/a/b"), is("endpointA"));
assertThat(getMatch("/a/apple"), is("endpointA"));
assertNull(getMatch("/a"));
assertNull(getMatch("/a/b/c"));
assertThat(getBestMatch("/a/b"), is("endpointA"));
assertThat(getBestMatch("/a/apple"), is("endpointA"));
assertNull(getBestMatch("/a"));
assertNull(getBestMatch("/a/b/c"));
}
@Test
@ -46,9 +68,9 @@ public class WebSocketUriMappingTest
mapping.put(new UriTemplatePathSpec("/a/b/c"), "endpointB");
mapping.put(new UriTemplatePathSpec("/a/{var1}/{var2}"), "endpointC");
assertThat(getMatch("/a/b/c"), is("endpointB"));
assertThat(getMatch("/a/d/c"), is("endpointA"));
assertThat(getMatch("/a/x/y"), is("endpointC"));
assertThat(getBestMatch("/a/b/c"), is("endpointB"));
assertThat(getBestMatch("/a/d/c"), is("endpointA"));
assertThat(getBestMatch("/a/x/y"), is("endpointC"));
}
@Test
@ -57,7 +79,7 @@ public class WebSocketUriMappingTest
mapping.put(new UriTemplatePathSpec("/{var1}/d"), "endpointA");
mapping.put(new UriTemplatePathSpec("/b/{var2}"), "endpointB");
assertThat(getMatch("/b/d"), is("endpointB"));
assertThat(getBestMatch("/b/d"), is("endpointB"));
}
@Test
@ -66,7 +88,9 @@ public class WebSocketUriMappingTest
mapping.put(new UriTemplatePathSpec("/{a}/b"), "suffix");
mapping.put(new UriTemplatePathSpec("/{a}/{b}"), "prefix");
assertThat(getMatch("/a/b"), is("suffix"));
List<MappedResource<String>> matches = mapping.getMatches("/a/b");
assertThat(getBestMatch("/a/b"), is("suffix"));
}
@Test
@ -75,7 +99,7 @@ public class WebSocketUriMappingTest
mapping.put(new UriTemplatePathSpec("/a/{b}/c"), "middle");
mapping.put(new UriTemplatePathSpec("/a/b/{c}"), "suffix");
assertThat(getMatch("/a/b/c"), is("suffix"));
assertThat(getBestMatch("/a/b/c"), is("suffix"));
}
@Test
@ -84,7 +108,7 @@ public class WebSocketUriMappingTest
mapping.put(new UriTemplatePathSpec("/{a}/b/{c}"), "middle");
mapping.put(new UriTemplatePathSpec("/{a}/b/c"), "suffix");
assertThat(getMatch("/a/b/c"), is("suffix"));
assertThat(getBestMatch("/a/b/c"), is("suffix"));
}
@Test
@ -93,7 +117,7 @@ public class WebSocketUriMappingTest
mapping.put(new UriTemplatePathSpec("/a/{b}/{c}/d"), "middle");
mapping.put(new UriTemplatePathSpec("/a/b/c/{d}"), "prefix");
assertThat(getMatch("/a/b/c/d"), is("prefix"));
assertThat(getBestMatch("/a/b/c/d"), is("prefix"));
}
@Test
@ -103,7 +127,7 @@ public class WebSocketUriMappingTest
mapping.put(new UriTemplatePathSpec("/a/{b}/{c}/d"), "middle1");
mapping.put(new UriTemplatePathSpec("/a/{b}/c/d"), "middle2");
assertThat(getMatch("/a/b/c/d"), is("middle2"));
assertThat(getBestMatch("/a/b/c/d"), is("middle2"));
}
@Test
@ -112,7 +136,7 @@ public class WebSocketUriMappingTest
mapping.put(new UriTemplatePathSpec("/{a}/{bz}/c/{d}"), "middle1");
mapping.put(new UriTemplatePathSpec("/{a}/{ba}/{c}/d"), "middle2");
assertThat(getMatch("/a/b/c/d"), is("middle1"));
assertThat(getBestMatch("/a/b/c/d"), is("middle1"));
}
@Test
@ -121,7 +145,7 @@ public class WebSocketUriMappingTest
mapping.put(new UriTemplatePathSpec("/{a}/{ba}/c/{d}"), "middle1");
mapping.put(new UriTemplatePathSpec("/{a}/{bz}/{c}/d"), "middle2");
assertThat(getMatch("/a/b/c/d"), is("middle1"));
assertThat(getBestMatch("/a/b/c/d"), is("middle1"));
}
@Test
@ -131,7 +155,7 @@ public class WebSocketUriMappingTest
mapping.put(new UriTemplatePathSpec("/a/{b}/{c}"), "prefix1");
mapping.put(new UriTemplatePathSpec("/a/b/{c}"), "prefix2");
assertThat(getMatch("/a/b/c"), is("prefix2"));
assertThat(getBestMatch("/a/b/c"), is("prefix2"));
}
@Test
@ -141,6 +165,6 @@ public class WebSocketUriMappingTest
mapping.put(new UriTemplatePathSpec("/{a}/{b}/c"), "suffix1");
mapping.put(new UriTemplatePathSpec("/{a}/b/c"), "suffix2");
assertThat(getMatch("/a/b/c"), is("suffix2"));
assertThat(getBestMatch("/a/b/c"), is("suffix2"));
}
}