Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoakim Erdfelt2015-12-14 18:17:31 +0000
committerJoakim Erdfelt2015-12-14 18:17:31 +0000
commitdddba5b0043c7f821602b22f5ddab38a6e813309 (patch)
treeaf4938d73ee2c40cae5889a0fb82bb65ef9b3a35
parent4ce3882ae25bb5a77ea39e83bb6224cba652b2ad (diff)
downloadorg.eclipse.jetty.project-dddba5b0043c7f821602b22f5ddab38a6e813309.tar.gz
org.eclipse.jetty.project-dddba5b0043c7f821602b22f5ddab38a6e813309.tar.xz
org.eclipse.jetty.project-dddba5b0043c7f821602b22f5ddab38a6e813309.zip
484349 - Promote WebSocket PathMappings / PathSpec to Jetty Http
+ Moving PathMappings from jetty-websocket to jetty-http + Renaming WebSocketPathSpec to UriTemplatePathSpec + Improving implementation with knowledge gained from PathMap and PathMapTest cases.
-rw-r--r--jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/MappedResource.java101
-rw-r--r--jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathMappings.java145
-rw-r--r--jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpec.java167
-rw-r--r--jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpecGroup.java101
-rw-r--r--jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/RegexPathSpec.java176
-rw-r--r--jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/ServletPathSpec.java261
-rw-r--r--jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/UriTemplatePathSpec.java341
-rw-r--r--jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/PathMappingsTest.java320
-rw-r--r--jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/PathSpecAssert.java37
-rw-r--r--jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/RegexPathSpecTest.java135
-rw-r--r--jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/ServletPathSpecMatchListTest.java101
-rw-r--r--jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/ServletPathSpecOrderTest.java103
-rw-r--r--jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/ServletPathSpecTest.java188
-rw-r--r--jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/UriTemplatePathSpecBadSpecsTest.java87
-rw-r--r--jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/UriTemplatePathSpecTest.java284
15 files changed, 2547 insertions, 0 deletions
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/MappedResource.java b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/MappedResource.java
new file mode 100644
index 0000000000..b44aff11d5
--- /dev/null
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/MappedResource.java
@@ -0,0 +1,101 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// 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 org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+
+@ManagedObject("Mapped Resource")
+public class MappedResource<E> implements Comparable<MappedResource<E>>
+{
+ private final PathSpec pathSpec;
+ private final E resource;
+
+ public MappedResource(PathSpec pathSpec, E resource)
+ {
+ this.pathSpec = pathSpec;
+ this.resource = resource;
+ }
+
+ /**
+ * Comparison is based solely on the pathSpec
+ */
+ @Override
+ public int compareTo(MappedResource<E> other)
+ {
+ return this.pathSpec.compareTo(other.pathSpec);
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ {
+ return true;
+ }
+ if (obj == null)
+ {
+ return false;
+ }
+ if (getClass() != obj.getClass())
+ {
+ return false;
+ }
+ MappedResource<?> other = (MappedResource<?>)obj;
+ if (pathSpec == null)
+ {
+ if (other.pathSpec != null)
+ {
+ return false;
+ }
+ }
+ else if (!pathSpec.equals(other.pathSpec))
+ {
+ return false;
+ }
+ return true;
+ }
+
+ @ManagedAttribute(value = "path spec", readonly = true)
+ public PathSpec getPathSpec()
+ {
+ return pathSpec;
+ }
+
+ @ManagedAttribute(value = "resource", readonly = true)
+ public E getResource()
+ {
+ return resource;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ final int prime = 31;
+ int result = 1;
+ result = (prime * result) + ((pathSpec == null) ? 0 : pathSpec.hashCode());
+ return result;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("MappedResource[pathSpec=%s,resource=%s]",pathSpec,resource);
+ }
+} \ No newline at end of file
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathMappings.java b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathMappings.java
new file mode 100644
index 0000000000..e0d975ca7d
--- /dev/null
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathMappings.java
@@ -0,0 +1,145 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// 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.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.jetty.util.annotation.ManagedAttribute;
+import org.eclipse.jetty.util.annotation.ManagedObject;
+import org.eclipse.jetty.util.component.ContainerLifeCycle;
+import org.eclipse.jetty.util.component.Dumpable;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * Path Mappings of PathSpec to Resource.
+ * <p>
+ * Sorted into search order upon entry into the Set
+ *
+ * @param <E> the type of mapping endpoint
+ */
+@ManagedObject("Path Mappings")
+public class PathMappings<E> implements Iterable<MappedResource<E>>, Dumpable
+{
+ private static final Logger LOG = Log.getLogger(PathMappings.class);
+ private List<MappedResource<E>> mappings = new ArrayList<MappedResource<E>>();
+ private MappedResource<E> defaultResource = null;
+
+ @Override
+ public String dump()
+ {
+ return ContainerLifeCycle.dump(this);
+ }
+
+ @Override
+ public void dump(Appendable out, String indent) throws IOException
+ {
+ ContainerLifeCycle.dump(out,indent,mappings);
+ }
+
+ @ManagedAttribute(value = "mappings", readonly = true)
+ public List<MappedResource<E>> getMappings()
+ {
+ return mappings;
+ }
+
+ public void reset()
+ {
+ mappings.clear();
+ }
+
+ /**
+ * Return a list of MappedResource matches for the specified path.
+ *
+ * @param path the path to return matches on
+ * @return the list of mapped resource the path matches on
+ */
+ public List<MappedResource<E>> getMatches(String path)
+ {
+ boolean matchRoot = "/".equals(path);
+
+ List<MappedResource<E>> ret = new ArrayList<>();
+ int len = mappings.size();
+ for (int i = 0; i < len; i++)
+ {
+ MappedResource<E> mr = mappings.get(i);
+
+ switch (mr.getPathSpec().group)
+ {
+ case ROOT:
+ if (matchRoot)
+ ret.add(mr);
+ break;
+ case DEFAULT:
+ if (matchRoot || mr.getPathSpec().matches(path))
+ ret.add(mr);
+ break;
+ default:
+ if (mr.getPathSpec().matches(path))
+ ret.add(mr);
+ break;
+ }
+ }
+ return ret;
+ }
+
+ public MappedResource<E> getMatch(String path)
+ {
+ int len = mappings.size();
+ for (int i = 0; i < len; i++)
+ {
+ MappedResource<E> mr = mappings.get(i);
+ if (mr.getPathSpec().matches(path))
+ {
+ return mr;
+ }
+ }
+ return defaultResource;
+ }
+
+ @Override
+ public Iterator<MappedResource<E>> iterator()
+ {
+ return mappings.iterator();
+ }
+
+ public void put(PathSpec pathSpec, E resource)
+ {
+ MappedResource<E> entry = new MappedResource<>(pathSpec,resource);
+ if (pathSpec.group == PathSpecGroup.DEFAULT)
+ {
+ defaultResource = entry;
+ }
+ // TODO: warning on replacement of existing mapping?
+ mappings.add(entry);
+ if (LOG.isDebugEnabled())
+ LOG.debug("Added {} to {}",entry,this);
+ Collections.sort(mappings);
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s[size=%d]",this.getClass().getSimpleName(),mappings.size());
+ }
+}
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpec.java b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpec.java
new file mode 100644
index 0000000000..122211a558
--- /dev/null
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpec.java
@@ -0,0 +1,167 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// 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;
+
+/**
+ * The base PathSpec, what all other path specs are based on
+ */
+public abstract class PathSpec implements Comparable<PathSpec>
+{
+ protected String pathSpec;
+ protected PathSpecGroup group;
+ protected int pathDepth;
+ protected int specLength;
+
+ @Override
+ public int compareTo(PathSpec other)
+ {
+ // Grouping (increasing)
+ int diff = this.group.ordinal() - other.group.ordinal();
+ if (diff != 0)
+ {
+ return diff;
+ }
+
+ // Spec Length (decreasing)
+ diff = other.specLength - this.specLength;
+ if (diff != 0)
+ {
+ return diff;
+ }
+
+ // Path Spec Name (alphabetical)
+ return this.pathSpec.compareTo(other.pathSpec);
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ {
+ return true;
+ }
+ if (obj == null)
+ {
+ return false;
+ }
+ if (getClass() != obj.getClass())
+ {
+ return false;
+ }
+ PathSpec other = (PathSpec)obj;
+ if (pathSpec == null)
+ {
+ if (other.pathSpec != null)
+ {
+ return false;
+ }
+ }
+ else if (!pathSpec.equals(other.pathSpec))
+ {
+ return false;
+ }
+ return true;
+ }
+
+ public PathSpecGroup getGroup()
+ {
+ return group;
+ }
+
+ /**
+ * Get the number of path elements that this path spec declares.
+ * <p>
+ * This is used to determine longest match logic.
+ *
+ * @return the depth of the path segments that this spec declares
+ */
+ public int getPathDepth()
+ {
+ return pathDepth;
+ }
+
+ /**
+ * Return the portion of the path that is after the path spec.
+ *
+ * @param path
+ * the path to match against
+ * @return the path info portion of the string
+ */
+ public abstract String getPathInfo(String path);
+
+ /**
+ * Return the portion of the path that matches a path spec.
+ *
+ * @param path
+ * the path to match against
+ * @return the match, or null if no match at all
+ */
+ public abstract String getPathMatch(String path);
+
+ /**
+ * The as-provided path spec.
+ *
+ * @return the as-provided path spec
+ */
+ public String getPathSpec()
+ {
+ return pathSpec;
+ }
+
+ /**
+ * Get the relative path.
+ *
+ * @param base
+ * the base the path is relative to
+ * @param path
+ * the additional path
+ * @return the base plus path with pathSpec portion removed
+ */
+ public abstract String getRelativePath(String base, String path);
+
+ @Override
+ public int hashCode()
+ {
+ final int prime = 31;
+ int result = 1;
+ result = (prime * result) + ((pathSpec == null)?0:pathSpec.hashCode());
+ return result;
+ }
+
+ /**
+ * Test to see if the provided path matches this path spec
+ *
+ * @param path
+ * the path to test
+ * @return true if the path matches this path spec, false otherwise
+ */
+ public abstract boolean matches(String path);
+
+ @Override
+ public String toString()
+ {
+ StringBuilder str = new StringBuilder();
+ str.append(this.getClass().getSimpleName()).append("[\"");
+ str.append(pathSpec);
+ str.append("\",pathDepth=").append(pathDepth);
+ str.append(",group=").append(group);
+ str.append("]");
+ return str.toString();
+ }
+}
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpecGroup.java b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpecGroup.java
new file mode 100644
index 0000000000..3a191a94e8
--- /dev/null
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/PathSpecGroup.java
@@ -0,0 +1,101 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// 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;
+
+/**
+ * Types of path spec groups.
+ * <p>
+ * This is used to facilitate proper pathspec search order.
+ * <p>
+ * Search Order:
+ * <ol>
+ * <li>{@link PathSpecGroup#ordinal()} [increasing]</li>
+ * <li>{@link PathSpec#specLength} [decreasing]</li>
+ * <li>{@link PathSpec#pathSpec} [natural sort order]</li>
+ * </ol>
+ */
+public enum PathSpecGroup
+{
+ // NOTE: Order of enums determines order of Groups.
+
+ /**
+ * For exactly defined path specs, no glob.
+ */
+ EXACT,
+ /**
+ * For path specs that have a hardcoded prefix and suffix with wildcard glob in the middle.
+ *
+ * <pre>
+ * "^/downloads/[^/]*.zip$" - regex spec
+ * "/a/{var}/c" - uri-template spec
+ * </pre>
+ *
+ * Note: there is no known servlet spec variant of this kind of path spec
+ */
+ MIDDLE_GLOB,
+ /**
+ * For path specs that have a hardcoded prefix and a trailing wildcard glob.
+ * <p>
+ *
+ * <pre>
+ * "/downloads/*" - servlet spec
+ * "/api/*" - servlet spec
+ * "^/rest/.*$" - regex spec
+ * "/bookings/{guest-id}" - uri-template spec
+ * "/rewards/{vip-level}" - uri-template spec
+ * </pre>
+ */
+ PREFIX_GLOB,
+ /**
+ * For path specs that have a wildcard glob with a hardcoded suffix
+ *
+ * <pre>
+ * "*.do" - servlet spec
+ * "*.css" - servlet spec
+ * "^.*\.zip$" - regex spec
+ * </pre>
+ *
+ * Note: there is no known uri-template spec variant of this kind of path spec
+ */
+ SUFFIX_GLOB,
+ /**
+ * The root spec for accessing the Root behavior.
+ *
+ * <pre>
+ * "" - servlet spec (Root Servlet)
+ * null - servlet spec (Root Servlet)
+ * </pre>
+ *
+ * Note: there is no known uri-template spec variant of this kind of path spec
+ */
+ ROOT,
+ /**
+ * The default spec for accessing the Default path behavior.
+ *
+ * <pre>
+ * "/" - servlet spec (Default Servlet)
+ * "/" - uri-template spec (Root Context)
+ * "^/$" - regex spec (Root Context)
+ * </pre>
+ *
+ * Per Servlet Spec, pathInfo is always null for these specs.
+ * If nothing above matches, then default will match.
+ */
+ DEFAULT,
+}
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/RegexPathSpec.java b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/RegexPathSpec.java
new file mode 100644
index 0000000000..4f9a0a23ab
--- /dev/null
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/RegexPathSpec.java
@@ -0,0 +1,176 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// 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.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class RegexPathSpec extends PathSpec
+{
+ protected Pattern pattern;
+
+ protected RegexPathSpec()
+ {
+ super();
+ }
+
+ public RegexPathSpec(String regex)
+ {
+ super.pathSpec = regex;
+ boolean inGrouping = false;
+ this.pathDepth = 0;
+ this.specLength = pathSpec.length();
+ // build up a simple signature we can use to identify the grouping
+ StringBuilder signature = new StringBuilder();
+ for (char c : pathSpec.toCharArray())
+ {
+ switch (c)
+ {
+ case '[':
+ inGrouping = true;
+ break;
+ case ']':
+ inGrouping = false;
+ signature.append('g'); // glob
+ break;
+ case '*':
+ signature.append('g'); // glob
+ break;
+ case '/':
+ if (!inGrouping)
+ {
+ this.pathDepth++;
+ }
+ break;
+ default:
+ if (!inGrouping)
+ {
+ if (Character.isLetterOrDigit(c))
+ {
+ signature.append('l'); // literal (exact)
+ }
+ }
+ break;
+ }
+ }
+ this.pattern = Pattern.compile(pathSpec);
+
+ // Figure out the grouping based on the signature
+ String sig = signature.toString();
+
+ if (Pattern.matches("^l*$",sig))
+ {
+ this.group = PathSpecGroup.EXACT;
+ }
+ else if (Pattern.matches("^l*g+",sig))
+ {
+ this.group = PathSpecGroup.PREFIX_GLOB;
+ }
+ else if (Pattern.matches("^g+l+$",sig))
+ {
+ this.group = PathSpecGroup.SUFFIX_GLOB;
+ }
+ else
+ {
+ this.group = PathSpecGroup.MIDDLE_GLOB;
+ }
+ }
+
+ public Matcher getMatcher(String path)
+ {
+ return this.pattern.matcher(path);
+ }
+
+ @Override
+ public String getPathInfo(String path)
+ {
+ // Path Info only valid for PREFIX_GLOB types
+ if (group == PathSpecGroup.PREFIX_GLOB)
+ {
+ Matcher matcher = getMatcher(path);
+ if (matcher.matches())
+ {
+ if (matcher.groupCount() >= 1)
+ {
+ String pathInfo = matcher.group(1);
+ if ("".equals(pathInfo))
+ {
+ return "/";
+ }
+ else
+ {
+ return pathInfo;
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String getPathMatch(String path)
+ {
+ Matcher matcher = getMatcher(path);
+ if (matcher.matches())
+ {
+ if (matcher.groupCount() >= 1)
+ {
+ int idx = matcher.start(1);
+ if (idx > 0)
+ {
+ if (path.charAt(idx - 1) == '/')
+ {
+ idx--;
+ }
+ return path.substring(0,idx);
+ }
+ }
+ return path;
+ }
+ return null;
+ }
+
+ public Pattern getPattern()
+ {
+ return this.pattern;
+ }
+
+ @Override
+ public String getRelativePath(String base, String path)
+ {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public boolean matches(final String path)
+ {
+ int idx = path.indexOf('?');
+ if (idx >= 0)
+ {
+ // match only non-query part
+ return getMatcher(path.substring(0,idx)).matches();
+ }
+ else
+ {
+ // match entire path
+ return getMatcher(path).matches();
+ }
+ }
+}
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/ServletPathSpec.java b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/ServletPathSpec.java
new file mode 100644
index 0000000000..55399748e2
--- /dev/null
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/ServletPathSpec.java
@@ -0,0 +1,261 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// 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 org.eclipse.jetty.util.URIUtil;
+
+public class ServletPathSpec extends PathSpec
+{
+ public ServletPathSpec(String servletPathSpec)
+ {
+ super();
+ assertValidServletPathSpec(servletPathSpec);
+
+ // The Root Path Spec
+ if ((servletPathSpec == null) || (servletPathSpec.length() == 0))
+ {
+ super.pathSpec = "";
+ super.pathDepth = -1; // force this to be at the end of the sort order
+ this.specLength = 1;
+ this.group = PathSpecGroup.ROOT;
+ return;
+ }
+
+ // The Default Path Spec
+ if("/".equals(servletPathSpec))
+ {
+ super.pathSpec = "/";
+ super.pathDepth = -1; // force this to be at the end of the sort order
+ this.specLength = 1;
+ this.group = PathSpecGroup.DEFAULT;
+ return;
+ }
+
+ this.specLength = servletPathSpec.length();
+ super.pathDepth = 0;
+ char lastChar = servletPathSpec.charAt(specLength - 1);
+ // prefix based
+ if ((servletPathSpec.charAt(0) == '/') && (specLength > 1) && (lastChar == '*'))
+ {
+ this.group = PathSpecGroup.PREFIX_GLOB;
+ }
+ // suffix based
+ else if (servletPathSpec.charAt(0) == '*')
+ {
+ this.group = PathSpecGroup.SUFFIX_GLOB;
+ }
+ else
+ {
+ this.group = PathSpecGroup.EXACT;
+ }
+
+ for (int i = 0; i < specLength; i++)
+ {
+ int cp = servletPathSpec.codePointAt(i);
+ if (cp < 128)
+ {
+ char c = (char)cp;
+ switch (c)
+ {
+ case '/':
+ super.pathDepth++;
+ break;
+ }
+ }
+ }
+
+ super.pathSpec = servletPathSpec;
+ }
+
+ private void assertValidServletPathSpec(String servletPathSpec)
+ {
+ if ((servletPathSpec == null) || servletPathSpec.equals(""))
+ {
+ return; // empty path spec
+ }
+
+ int len = servletPathSpec.length();
+ // path spec must either start with '/' or '*.'
+ if (servletPathSpec.charAt(0) == '/')
+ {
+ // Prefix Based
+ if (len == 1)
+ {
+ return; // simple '/' path spec
+ }
+ int idx = servletPathSpec.indexOf('*');
+ if (idx < 0)
+ {
+ return; // no hit on glob '*'
+ }
+ // only allowed to have '*' at the end of the path spec
+ if (idx != (len - 1))
+ {
+ throw new IllegalArgumentException("Servlet Spec 12.2 violation: glob '*' can only exist at end of prefix based matches: bad spec \""+ servletPathSpec +"\"");
+ }
+ }
+ else if (servletPathSpec.startsWith("*."))
+ {
+ // Suffix Based
+ int idx = servletPathSpec.indexOf('/');
+ // cannot have path separator
+ if (idx >= 0)
+ {
+ throw new IllegalArgumentException("Servlet Spec 12.2 violation: suffix based path spec cannot have path separators: bad spec \""+ servletPathSpec +"\"");
+ }
+
+ idx = servletPathSpec.indexOf('*',2);
+ // only allowed to have 1 glob '*', at the start of the path spec
+ if (idx >= 1)
+ {
+ throw new IllegalArgumentException("Servlet Spec 12.2 violation: suffix based path spec cannot have multiple glob '*': bad spec \""+ servletPathSpec +"\"");
+ }
+ }
+ else
+ {
+ throw new IllegalArgumentException("Servlet Spec 12.2 violation: path spec must start with \"/\" or \"*.\": bad spec \""+ servletPathSpec +"\"");
+ }
+ }
+
+ @Override
+ public String getPathInfo(String path)
+ {
+ // Path Info only valid for PREFIX_GLOB types
+ if (group == PathSpecGroup.PREFIX_GLOB)
+ {
+ if (path.length() == (specLength - 2))
+ {
+ return null;
+ }
+ return path.substring(specLength - 2);
+ }
+
+ return null;
+ }
+
+ @Override
+ public String getPathMatch(String path)
+ {
+ switch (group)
+ {
+ case EXACT:
+ if (pathSpec.equals(path))
+ {
+ return path;
+ }
+ else
+ {
+ return null;
+ }
+ case PREFIX_GLOB:
+ if (isWildcardMatch(path))
+ {
+ return path.substring(0,specLength - 2);
+ }
+ else
+ {
+ return null;
+ }
+ case SUFFIX_GLOB:
+ if (path.regionMatches(path.length() - (specLength - 1),pathSpec,1,specLength - 1))
+ {
+ return path;
+ }
+ else
+ {
+ return null;
+ }
+ case DEFAULT:
+ return path;
+ default:
+ return null;
+ }
+ }
+
+ @Override
+ public String getRelativePath(String base, String path)
+ {
+ String info = getPathInfo(path);
+ if (info == null)
+ {
+ info = path;
+ }
+
+ if (info.startsWith("./"))
+ {
+ info = info.substring(2);
+ }
+ if (base.endsWith(URIUtil.SLASH))
+ {
+ if (info.startsWith(URIUtil.SLASH))
+ {
+ path = base + info.substring(1);
+ }
+ else
+ {
+ path = base + info;
+ }
+ }
+ else if (info.startsWith(URIUtil.SLASH))
+ {
+ path = base + info;
+ }
+ else
+ {
+ path = base + URIUtil.SLASH + info;
+ }
+ return path;
+ }
+
+ private boolean isWildcardMatch(String path)
+ {
+ // For a spec of "/foo/*" match "/foo" , "/foo/..." but not "/foobar"
+ int cpl = specLength - 2;
+ if ((group == PathSpecGroup.PREFIX_GLOB) && (path.regionMatches(0,pathSpec,0,cpl)))
+ {
+ if ((path.length() == cpl) || ('/' == path.charAt(cpl)))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean matches(String path)
+ {
+ switch (group)
+ {
+ case EXACT:
+ return pathSpec.equals(path);
+ case PREFIX_GLOB:
+ return (!"/".equals(path) && isWildcardMatch(path));
+ case SUFFIX_GLOB:
+ return path.regionMatches((path.length() - specLength) + 1,pathSpec,1,specLength - 1);
+ case ROOT:
+ // Only "/" matches
+ return ("/".equals(path));
+ case DEFAULT:
+ // If we reached this point, then everything matches
+ return true;
+ default:
+ return false;
+ }
+ }
+}
diff --git a/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/UriTemplatePathSpec.java b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/UriTemplatePathSpec.java
new file mode 100644
index 0000000000..fe25230b77
--- /dev/null
+++ b/jetty-http/src/main/java/org/eclipse/jetty/http/pathmap/UriTemplatePathSpec.java
@@ -0,0 +1,341 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// 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.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.eclipse.jetty.util.TypeUtil;
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+
+/**
+ * PathSpec for URI Template based declarations
+ *
+ * @see <a href="https://tools.ietf.org/html/rfc6570">URI Templates (Level 1)</a>
+ */
+public class UriTemplatePathSpec extends RegexPathSpec
+{
+ private static final Logger LOG = Log.getLogger(UriTemplatePathSpec.class);
+
+ private static final Pattern VARIABLE_PATTERN = Pattern.compile("\\{(.*)\\}");
+ /** Reserved Symbols in URI Template variable */
+ private static final String VARIABLE_RESERVED = ":/?#[]@" + // gen-delims
+ "!$&'()*+,;="; // sub-delims
+ /** Allowed Symbols in a URI Template variable */
+ private static final String VARIABLE_SYMBOLS="-._";
+ private static final Set<String> FORBIDDEN_SEGMENTS;
+
+ static
+ {
+ FORBIDDEN_SEGMENTS = new HashSet<>();
+ FORBIDDEN_SEGMENTS.add("/./");
+ FORBIDDEN_SEGMENTS.add("/../");
+ FORBIDDEN_SEGMENTS.add("//");
+ }
+
+ private String variables[];
+
+ public UriTemplatePathSpec(String rawSpec)
+ {
+ super();
+ Objects.requireNonNull(rawSpec,"Path Param Spec cannot be null");
+
+ if ("".equals(rawSpec) || "/".equals(rawSpec))
+ {
+ super.pathSpec = "/";
+ super.pattern = Pattern.compile("^/$");
+ super.pathDepth = 1;
+ this.specLength = 1;
+ this.variables = new String[0];
+ this.group = PathSpecGroup.EXACT;
+ return;
+ }
+
+ if (rawSpec.charAt(0) != '/')
+ {
+ // path specs must start with '/'
+ StringBuilder err = new StringBuilder();
+ err.append("Syntax Error: path spec \"");
+ err.append(rawSpec);
+ err.append("\" must start with '/'");
+ throw new IllegalArgumentException(err.toString());
+ }
+
+ for (String forbidden : FORBIDDEN_SEGMENTS)
+ {
+ if (rawSpec.contains(forbidden))
+ {
+ StringBuilder err = new StringBuilder();
+ err.append("Syntax Error: segment ");
+ err.append(forbidden);
+ err.append(" is forbidden in path spec: ");
+ err.append(rawSpec);
+ throw new IllegalArgumentException(err.toString());
+ }
+ }
+
+ this.pathSpec = rawSpec;
+
+ StringBuilder regex = new StringBuilder();
+ regex.append('^');
+
+ List<String> varNames = new ArrayList<>();
+ // 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];
+ this.pathDepth = segments.length;
+ for (int i = 0; i < segments.length; i++)
+ {
+ String segment = segments[i];
+ Matcher mat = VARIABLE_PATTERN.matcher(segment);
+
+ if (mat.matches())
+ {
+ // entire path segment is a variable.
+ String variable = mat.group(1);
+ if (varNames.contains(variable))
+ {
+ // duplicate variable names
+ StringBuilder err = new StringBuilder();
+ err.append("Syntax Error: variable ");
+ err.append(variable);
+ err.append(" is duplicated in path spec: ");
+ err.append(rawSpec);
+ throw new IllegalArgumentException(err.toString());
+ }
+
+ assertIsValidVariableLiteral(variable);
+
+ segmentSignature[i] = 'v'; // variable
+ // valid variable name
+ varNames.add(variable);
+ // build regex
+ regex.append("/([^/]+)");
+ }
+ else if (mat.find(0))
+ {
+ // variable exists as partial segment
+ StringBuilder err = new StringBuilder();
+ err.append("Syntax Error: variable ");
+ err.append(mat.group());
+ err.append(" must exist as entire path segment: ");
+ err.append(rawSpec);
+ throw new IllegalArgumentException(err.toString());
+ }
+ else if ((segment.indexOf('{') >= 0) || (segment.indexOf('}') >= 0))
+ {
+ // variable is split with a path separator
+ StringBuilder err = new StringBuilder();
+ err.append("Syntax Error: invalid path segment /");
+ err.append(segment);
+ err.append("/ variable declaration incomplete: ");
+ err.append(rawSpec);
+ throw new IllegalArgumentException(err.toString());
+ }
+ else if (segment.indexOf('*') >= 0)
+ {
+ // glob segment
+ StringBuilder err = new StringBuilder();
+ err.append("Syntax Error: path segment /");
+ err.append(segment);
+ err.append("/ contains a wildcard symbol (not supported by this uri-template implementation): ");
+ err.append(rawSpec);
+ throw new IllegalArgumentException(err.toString());
+ }
+ else
+ {
+ // valid path segment
+ segmentSignature[i] = 'e'; // exact
+ // build regex
+ regex.append('/');
+ // escape regex special characters
+ for (char c : segment.toCharArray())
+ {
+ if ((c == '.') || (c == '[') || (c == ']') || (c == '\\'))
+ {
+ regex.append('\\');
+ }
+ regex.append(c);
+ }
+ }
+ }
+
+ // Handle trailing slash (which is not picked up during split)
+ if(rawSpec.charAt(rawSpec.length()-1) == '/')
+ {
+ regex.append('/');
+ }
+
+ regex.append('$');
+
+ this.pattern = Pattern.compile(regex.toString());
+
+ int varcount = varNames.size();
+ this.variables = varNames.toArray(new String[varcount]);
+
+ // Convert signature to group
+ String sig = String.valueOf(segmentSignature);
+
+ if (Pattern.matches("^e*$",sig))
+ {
+ this.group = PathSpecGroup.EXACT;
+ }
+ else if (Pattern.matches("^e*v+",sig))
+ {
+ this.group = PathSpecGroup.PREFIX_GLOB;
+ }
+ else if (Pattern.matches("^v+e+",sig))
+ {
+ this.group = PathSpecGroup.SUFFIX_GLOB;
+ }
+ else
+ {
+ this.group = PathSpecGroup.MIDDLE_GLOB;
+ }
+ }
+
+ /**
+ * Validate variable literal name, per RFC6570, Section 2.1 Literals
+ * @param variable
+ * @param pathParamSpec
+ */
+ private void assertIsValidVariableLiteral(String variable)
+ {
+ int len = variable.length();
+
+ int i = 0;
+ int codepoint;
+ boolean valid = (len > 0); // must not be zero length
+
+ while (valid && i < len)
+ {
+ codepoint = variable.codePointAt(i);
+ i += Character.charCount(codepoint);
+
+ // basic letters, digits, or symbols
+ if (isValidBasicLiteralCodepoint(codepoint))
+ {
+ continue;
+ }
+
+ // The ucschar and iprivate pieces
+ if (Character.isSupplementaryCodePoint(codepoint))
+ {
+ continue;
+ }
+
+ // pct-encoded
+ if (codepoint == '%')
+ {
+ if (i + 2 > len)
+ {
+ // invalid percent encoding, missing extra 2 chars
+ valid = false;
+ continue;
+ }
+ codepoint = TypeUtil.convertHexDigit(variable.codePointAt(i++)) << 4;
+ codepoint |= TypeUtil.convertHexDigit(variable.codePointAt(i++));
+
+ // validate basic literal
+ if (isValidBasicLiteralCodepoint(codepoint))
+ {
+ continue;
+ }
+ }
+
+ valid = false;
+ }
+
+ if (!valid)
+ {
+ // invalid variable name
+ StringBuilder err = new StringBuilder();
+ err.append("Syntax Error: variable {");
+ err.append(variable);
+ err.append("} an invalid variable name: ");
+ err.append(pathSpec);
+ throw new IllegalArgumentException(err.toString());
+ }
+ }
+
+ private boolean isValidBasicLiteralCodepoint(int codepoint)
+ {
+ // basic letters or digits
+ if((codepoint >= 'a' && codepoint <= 'z') ||
+ (codepoint >= 'A' && codepoint <= 'Z') ||
+ (codepoint >= '0' && codepoint <= '9'))
+ {
+ return true;
+ }
+
+ // basic allowed symbols
+ if(VARIABLE_SYMBOLS.indexOf(codepoint) >= 0)
+ {
+ return true; // valid simple value
+ }
+
+ // basic reserved symbols
+ if(VARIABLE_RESERVED.indexOf(codepoint) >= 0)
+ {
+ LOG.warn("Detected URI Template reserved symbol [{}] in path spec \"{}\"",(char)codepoint,pathSpec);
+ return false; // valid simple value
+ }
+
+ return false;
+ }
+
+ public Map<String, String> getPathParams(String path)
+ {
+ Matcher matcher = getMatcher(path);
+ if (matcher.matches())
+ {
+ if (group == PathSpecGroup.EXACT)
+ {
+ return Collections.emptyMap();
+ }
+ Map<String, String> ret = new HashMap<>();
+ int groupCount = matcher.groupCount();
+ for (int i = 1; i <= groupCount; i++)
+ {
+ ret.put(this.variables[i - 1],matcher.group(i));
+ }
+ return ret;
+ }
+ return null;
+ }
+
+ public int getVariableCount()
+ {
+ return variables.length;
+ }
+
+ public String[] getVariables()
+ {
+ return this.variables;
+ }
+}
diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/PathMappingsTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/PathMappingsTest.java
new file mode 100644
index 0000000000..06cd14bcce
--- /dev/null
+++ b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/PathMappingsTest.java
@@ -0,0 +1,320 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// 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 static org.hamcrest.Matchers.notNullValue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class PathMappingsTest
+{
+ private void assertMatch(PathMappings<String> pathmap, String path, String expectedValue)
+ {
+ String msg = String.format(".getMatch(\"%s\")",path);
+ MappedResource<String> match = pathmap.getMatch(path);
+ Assert.assertThat(msg,match,notNullValue());
+ String actualMatch = match.getResource();
+ Assert.assertEquals(msg,expectedValue,actualMatch);
+ }
+
+ public void dumpMappings(PathMappings<String> p)
+ {
+ for (MappedResource<String> res : p)
+ {
+ System.out.printf(" %s%n",res);
+ }
+ }
+
+ /**
+ * Test the match order rules with a mixed Servlet and regex path specs
+ * <p>
+ * <ul>
+ * <li>Exact match</li>
+ * <li>Longest prefix match</li>
+ * <li>Longest suffix match</li>
+ * </ul>
+ */
+ @Test
+ public void testMixedMatchOrder()
+ {
+ PathMappings<String> p = new PathMappings<>();
+
+ p.put(new ServletPathSpec("/"),"default");
+ p.put(new ServletPathSpec("/animal/bird/*"),"birds");
+ p.put(new ServletPathSpec("/animal/fish/*"),"fishes");
+ p.put(new ServletPathSpec("/animal/*"),"animals");
+ p.put(new RegexPathSpec("^/animal/.*/chat$"),"animalChat");
+ p.put(new RegexPathSpec("^/animal/.*/cam$"),"animalCam");
+ p.put(new RegexPathSpec("^/entrance/cam$"),"entranceCam");
+
+ // dumpMappings(p);
+
+ assertMatch(p,"/animal/bird/eagle","birds");
+ assertMatch(p,"/animal/fish/bass/sea","fishes");
+ assertMatch(p,"/animal/peccary/javalina/evolution","animals");
+ assertMatch(p,"/","default");
+ assertMatch(p,"/animal/bird/eagle/chat","animalChat");
+ assertMatch(p,"/animal/bird/penguin/chat","animalChat");
+ assertMatch(p,"/animal/fish/trout/cam","animalCam");
+ assertMatch(p,"/entrance/cam","entranceCam");
+ }
+
+ /**
+ * Test the match order rules imposed by the Servlet API.
+ * <p>
+ * <ul>
+ * <li>Exact match</li>
+ * <li>Longest prefix match</li>
+ * <li>Longest suffix match</li>
+ * <li>default</li>
+ * </ul>
+ */
+ @Test
+ public void testServletMatchOrder()
+ {
+ PathMappings<String> p = new PathMappings<>();
+
+ p.put(new ServletPathSpec("/abs/path"),"abspath"); // 1
+ p.put(new ServletPathSpec("/abs/path/longer"),"longpath"); // 2
+ p.put(new ServletPathSpec("/animal/bird/*"),"birds"); // 3
+ p.put(new ServletPathSpec("/animal/fish/*"),"fishes"); // 4
+ p.put(new ServletPathSpec("/animal/*"),"animals"); // 5
+ p.put(new ServletPathSpec("*.tar.gz"),"tarball"); // 6
+ p.put(new ServletPathSpec("*.gz"),"gzipped"); // 7
+ p.put(new ServletPathSpec("/"),"default"); // 8
+ // 9 was the old Jetty ":" spec delimited case (no longer valid)
+ p.put(new ServletPathSpec(""),"root"); // 10
+ p.put(new ServletPathSpec("/\u20ACuro/*"),"money"); // 11
+
+ // dumpMappings(p);
+
+ // From old PathMapTest
+ assertMatch(p,"/abs/path","abspath");
+ assertMatch(p,"/abs/path/xxx","default");
+ assertMatch(p,"/abs/pith","default");
+ assertMatch(p,"/abs/path/longer","longpath");
+ assertMatch(p,"/abs/path/","default");
+ assertMatch(p,"/abs/path/foo","default");
+ assertMatch(p,"/animal/bird/eagle/bald","birds");
+ assertMatch(p,"/animal/fish/shark/hammerhead","fishes");
+ assertMatch(p,"/animal/insect/ladybug","animals");
+ assertMatch(p,"/animal","animals");
+ assertMatch(p,"/animal/","animals");
+ assertMatch(p,"/animal/other","animals");
+ assertMatch(p,"/animal/*","animals");
+ assertMatch(p,"/downloads/distribution.tar.gz","tarball");
+ assertMatch(p,"/downloads/script.gz","gzipped");
+ assertMatch(p,"/animal/arhive.gz","animals");
+ assertMatch(p,"/Other/path","default");
+ assertMatch(p,"/\u20ACuro/path","money");
+ assertMatch(p,"/","root");
+
+ // Extra tests
+ assertMatch(p,"/downloads/readme.txt","default");
+ assertMatch(p,"/downloads/logs.tgz","default");
+ assertMatch(p,"/main.css","default");
+ }
+
+ /**
+ * Test the match order rules with a mixed Servlet and URI Template path specs
+ * <p>
+ * <ul>
+ * <li>Exact match</li>
+ * <li>Longest prefix match</li>
+ * <li>Longest suffix match</li>
+ * </ul>
+ */
+ @Test
+ public void testMixedMatchUriOrder()
+ {
+ PathMappings<String> p = new PathMappings<>();
+
+ p.put(new ServletPathSpec("/"),"default");
+ p.put(new ServletPathSpec("/animal/bird/*"),"birds");
+ p.put(new ServletPathSpec("/animal/fish/*"),"fishes");
+ p.put(new ServletPathSpec("/animal/*"),"animals");
+ p.put(new UriTemplatePathSpec("/animal/{type}/{name}/chat"),"animalChat");
+ p.put(new UriTemplatePathSpec("/animal/{type}/{name}/cam"),"animalCam");
+ p.put(new UriTemplatePathSpec("/entrance/cam"),"entranceCam");
+
+ // dumpMappings(p);
+
+ assertMatch(p,"/animal/bird/eagle","birds");
+ assertMatch(p,"/animal/fish/bass/sea","fishes");
+ assertMatch(p,"/animal/peccary/javalina/evolution","animals");
+ assertMatch(p,"/","default");
+ assertMatch(p,"/animal/bird/eagle/chat","animalChat");
+ assertMatch(p,"/animal/bird/penguin/chat","animalChat");
+ assertMatch(p,"/animal/fish/trout/cam","animalCam");
+ assertMatch(p,"/entrance/cam","entranceCam");
+ }
+
+ /**
+ * Test the match order rules for URI Template based specs
+ * <p>
+ * <ul>
+ * <li>Exact match</li>
+ * <li>Longest prefix match</li>
+ * <li>Longest suffix match</li>
+ * </ul>
+ */
+ @Test
+ public void testUriTemplateMatchOrder()
+ {
+ PathMappings<String> p = new PathMappings<>();
+
+ p.put(new UriTemplatePathSpec("/a/{var}/c"),"endpointA");
+ p.put(new UriTemplatePathSpec("/a/b/c"),"endpointB");
+ p.put(new UriTemplatePathSpec("/a/{var1}/{var2}"),"endpointC");
+ p.put(new UriTemplatePathSpec("/{var1}/d"),"endpointD");
+ p.put(new UriTemplatePathSpec("/b/{var2}"),"endpointE");
+
+ // dumpMappings(p);
+
+ assertMatch(p,"/a/b/c","endpointB");
+ assertMatch(p,"/a/d/c","endpointA");
+ assertMatch(p,"/a/x/y","endpointC");
+
+ assertMatch(p,"/b/d","endpointE");
+ }
+
+ @Test
+ public void testPathMap() throws Exception
+ {
+ PathMappings<String> p = new PathMappings<>();
+
+ p.put(new ServletPathSpec("/abs/path"), "1");
+ p.put(new ServletPathSpec("/abs/path/longer"), "2");
+ p.put(new ServletPathSpec("/animal/bird/*"), "3");
+ p.put(new ServletPathSpec("/animal/fish/*"), "4");
+ p.put(new ServletPathSpec("/animal/*"), "5");
+ p.put(new ServletPathSpec("*.tar.gz"), "6");
+ p.put(new ServletPathSpec("*.gz"), "7");
+ p.put(new ServletPathSpec("/"), "8");
+ // p.put(new ServletPathSpec("/XXX:/YYY"), "9"); // special syntax from Jetty 3.1.x
+ p.put(new ServletPathSpec(""), "10");
+ p.put(new ServletPathSpec("/\u20ACuro/*"), "11");
+
+ assertEquals("pathMatch exact", "/Foo/bar", new ServletPathSpec("/Foo/bar").getPathMatch("/Foo/bar"));
+ assertEquals("pathMatch prefix", "/Foo", new ServletPathSpec("/Foo/*").getPathMatch("/Foo/bar"));
+ assertEquals("pathMatch prefix", "/Foo", new ServletPathSpec("/Foo/*").getPathMatch("/Foo/"));
+ assertEquals("pathMatch prefix", "/Foo", new ServletPathSpec("/Foo/*").getPathMatch("/Foo"));
+ assertEquals("pathMatch suffix", "/Foo/bar.ext", new ServletPathSpec("*.ext").getPathMatch("/Foo/bar.ext"));
+ assertEquals("pathMatch default", "/Foo/bar.ext", new ServletPathSpec("/").getPathMatch("/Foo/bar.ext"));
+
+ assertEquals("pathInfo exact", null, new ServletPathSpec("/Foo/bar").getPathInfo("/Foo/bar"));
+ assertEquals("pathInfo prefix", "/bar", new ServletPathSpec("/Foo/*").getPathInfo("/Foo/bar"));
+ assertEquals("pathInfo prefix", "/*", new ServletPathSpec("/Foo/*").getPathInfo("/Foo/*"));
+ assertEquals("pathInfo prefix", "/", new ServletPathSpec("/Foo/*").getPathInfo("/Foo/"));
+ assertEquals("pathInfo prefix", null, new ServletPathSpec("/Foo/*").getPathInfo("/Foo"));
+ assertEquals("pathInfo suffix", null, new ServletPathSpec("*.ext").getPathInfo("/Foo/bar.ext"));
+ assertEquals("pathInfo default", null, new ServletPathSpec("/").getPathInfo("/Foo/bar.ext"));
+
+ p.put(new ServletPathSpec("/*"), "0");
+
+ // assertEquals("Get absolute path", "1", p.get("/abs/path"));
+ assertEquals("Match absolute path", "/abs/path", p.getMatch("/abs/path").getPathSpec().pathSpec);
+ assertEquals("Match absolute path", "1", p.getMatch("/abs/path").getResource());
+ assertEquals("Mismatch absolute path", "0", p.getMatch("/abs/path/xxx").getResource());
+ assertEquals("Mismatch absolute path", "0", p.getMatch("/abs/pith").getResource());
+ assertEquals("Match longer absolute path", "2", p.getMatch("/abs/path/longer").getResource());
+ assertEquals("Not exact absolute path", "0", p.getMatch("/abs/path/").getResource());
+ assertEquals("Not exact absolute path", "0", p.getMatch("/abs/path/xxx").getResource());
+
+ assertEquals("Match longest prefix", "3", p.getMatch("/animal/bird/eagle/bald").getResource());
+ assertEquals("Match longest prefix", "4", p.getMatch("/animal/fish/shark/grey").getResource());
+ assertEquals("Match longest prefix", "5", p.getMatch("/animal/insect/bug").getResource());
+ assertEquals("mismatch exact prefix", "5", p.getMatch("/animal").getResource());
+ assertEquals("mismatch exact prefix", "5", p.getMatch("/animal/").getResource());
+
+ assertEquals("Match longest suffix", "0", p.getMatch("/suffix/path.tar.gz").getResource());
+ assertEquals("Match longest suffix", "0", p.getMatch("/suffix/path.gz").getResource());
+ assertEquals("prefix rather than suffix", "5", p.getMatch("/animal/path.gz").getResource());
+
+ assertEquals("default", "0", p.getMatch("/Other/path").getResource());
+
+ assertEquals("pathMatch /*", "", new ServletPathSpec("/*").getPathMatch("/xxx/zzz"));
+ assertEquals("pathInfo /*", "/xxx/zzz", new ServletPathSpec("/*").getPathInfo("/xxx/zzz"));
+
+ assertTrue("match /", new ServletPathSpec("/").matches("/anything"));
+ assertTrue("match /*", new ServletPathSpec("/*").matches("/anything"));
+ assertTrue("match /foo", new ServletPathSpec("/foo").matches("/foo"));
+ assertTrue("!match /foo", !new ServletPathSpec("/foo").matches("/bar"));
+ assertTrue("match /foo/*", new ServletPathSpec("/foo/*").matches("/foo"));
+ assertTrue("match /foo/*", new ServletPathSpec("/foo/*").matches("/foo/"));
+ assertTrue("match /foo/*", new ServletPathSpec("/foo/*").matches("/foo/anything"));
+ assertTrue("!match /foo/*", !new ServletPathSpec("/foo/*").matches("/bar"));
+ assertTrue("!match /foo/*", !new ServletPathSpec("/foo/*").matches("/bar/"));
+ assertTrue("!match /foo/*", !new ServletPathSpec("/foo/*").matches("/bar/anything"));
+ assertTrue("match *.foo", new ServletPathSpec("*.foo").matches("anything.foo"));
+ assertTrue("!match *.foo", !new ServletPathSpec("*.foo").matches("anything.bar"));
+
+ assertEquals("match / with ''", "10", p.getMatch("/").getResource());
+
+ assertTrue("match \"\"", new ServletPathSpec("").matches("/"));
+ }
+
+ /**
+ * See JIRA issue: JETTY-88.
+ * @throws Exception failed test
+ */
+ @Test
+ public void testPathMappingsOnlyMatchOnDirectoryNames() throws Exception
+ {
+ ServletPathSpec spec = new ServletPathSpec("/xyz/*");
+
+ PathSpecAssert.assertMatch(spec, "/xyz");
+ PathSpecAssert.assertMatch(spec, "/xyz/");
+ PathSpecAssert.assertMatch(spec, "/xyz/123");
+ PathSpecAssert.assertMatch(spec, "/xyz/123/");
+ PathSpecAssert.assertMatch(spec, "/xyz/123.txt");
+ PathSpecAssert.assertNotMatch(spec, "/xyz123");
+ PathSpecAssert.assertNotMatch(spec, "/xyz123;jessionid=99");
+ PathSpecAssert.assertNotMatch(spec, "/xyz123/");
+ PathSpecAssert.assertNotMatch(spec, "/xyz123/456");
+ PathSpecAssert.assertNotMatch(spec, "/xyz.123");
+ PathSpecAssert.assertNotMatch(spec, "/xyz;123"); // as if the ; was encoded and part of the path
+ PathSpecAssert.assertNotMatch(spec, "/xyz?123"); // as if the ? was encoded and part of the path
+ }
+
+ @Test
+ public void testPrecidenceVsOrdering() throws Exception
+ {
+ PathMappings<String> p = new PathMappings<>();
+ p.put(new ServletPathSpec("/dump/gzip/*"),"prefix");
+ p.put(new ServletPathSpec("*.txt"),"suffix");
+
+ assertEquals(null,p.getMatch("/foo/bar"));
+ assertEquals("prefix",p.getMatch("/dump/gzip/something").getResource());
+ assertEquals("suffix",p.getMatch("/foo/something.txt").getResource());
+ assertEquals("prefix",p.getMatch("/dump/gzip/something.txt").getResource());
+
+ p = new PathMappings<>();
+ p.put(new ServletPathSpec("*.txt"),"suffix");
+ p.put(new ServletPathSpec("/dump/gzip/*"),"prefix");
+
+ assertEquals(null,p.getMatch("/foo/bar"));
+ assertEquals("prefix",p.getMatch("/dump/gzip/something").getResource());
+ assertEquals("suffix",p.getMatch("/foo/something.txt").getResource());
+ assertEquals("prefix",p.getMatch("/dump/gzip/something.txt").getResource());
+ }
+}
diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/PathSpecAssert.java b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/PathSpecAssert.java
new file mode 100644
index 0000000000..57546e74bb
--- /dev/null
+++ b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/PathSpecAssert.java
@@ -0,0 +1,37 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// 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 static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+public class PathSpecAssert
+{
+ public static void assertMatch(PathSpec spec, String path)
+ {
+ boolean match = spec.matches(path);
+ assertThat(spec.getClass().getSimpleName() + " '" + spec + "' should match path '" + path + "'", match, is(true));
+ }
+
+ public static void assertNotMatch(PathSpec spec, String path)
+ {
+ boolean match = spec.matches(path);
+ assertThat(spec.getClass().getSimpleName() + " '" + spec + "' should not match path '" + path + "'", match, is(false));
+ }
+}
diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/RegexPathSpecTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/RegexPathSpecTest.java
new file mode 100644
index 0000000000..a44c4dca50
--- /dev/null
+++ b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/RegexPathSpecTest.java
@@ -0,0 +1,135 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// 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 static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import org.junit.Test;
+
+public class RegexPathSpecTest
+{
+ public static void assertMatches(PathSpec spec, String path)
+ {
+ String msg = String.format("Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path);
+ assertThat(msg,spec.matches(path),is(true));
+ }
+
+ public static void assertNotMatches(PathSpec spec, String path)
+ {
+ String msg = String.format("!Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path);
+ assertThat(msg,spec.matches(path),is(false));
+ }
+
+ @Test
+ public void testExactSpec()
+ {
+ RegexPathSpec spec = new RegexPathSpec("^/a$");
+ assertEquals("Spec.pathSpec","^/a$",spec.getPathSpec());
+ assertEquals("Spec.pattern","^/a$",spec.getPattern().pattern());
+ assertEquals("Spec.pathDepth",1,spec.getPathDepth());
+ assertEquals("Spec.group",PathSpecGroup.EXACT,spec.group);
+
+ assertMatches(spec,"/a");
+
+ assertNotMatches(spec,"/aa");
+ assertNotMatches(spec,"/a/");
+ }
+
+ @Test
+ public void testMiddleSpec()
+ {
+ RegexPathSpec spec = new RegexPathSpec("^/rest/([^/]*)/list$");
+ assertEquals("Spec.pathSpec","^/rest/([^/]*)/list$",spec.getPathSpec());
+ assertEquals("Spec.pattern","^/rest/([^/]*)/list$",spec.getPattern().pattern());
+ assertEquals("Spec.pathDepth",3,spec.getPathDepth());
+ assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.group);
+
+ assertMatches(spec,"/rest/api/list");
+ assertMatches(spec,"/rest/1.0/list");
+ assertMatches(spec,"/rest/2.0/list");
+ assertMatches(spec,"/rest/accounts/list");
+
+ assertNotMatches(spec,"/a");
+ assertNotMatches(spec,"/aa");
+ assertNotMatches(spec,"/aa/bb");
+ assertNotMatches(spec,"/rest/admin/delete");
+ assertNotMatches(spec,"/rest/list");
+ }
+
+ @Test
+ public void testMiddleSpecNoGrouping()
+ {
+ RegexPathSpec spec = new RegexPathSpec("^/rest/[^/]+/list$");
+ assertEquals("Spec.pathSpec","^/rest/[^/]+/list$",spec.getPathSpec());
+ assertEquals("Spec.pattern","^/rest/[^/]+/list$",spec.getPattern().pattern());
+ assertEquals("Spec.pathDepth",3,spec.getPathDepth());
+ assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.group);
+
+ assertMatches(spec,"/rest/api/list");
+ assertMatches(spec,"/rest/1.0/list");
+ assertMatches(spec,"/rest/2.0/list");
+ assertMatches(spec,"/rest/accounts/list");
+
+ assertNotMatches(spec,"/a");
+ assertNotMatches(spec,"/aa");
+ assertNotMatches(spec,"/aa/bb");
+ assertNotMatches(spec,"/rest/admin/delete");
+ assertNotMatches(spec,"/rest/list");
+ }
+
+ @Test
+ public void testPrefixSpec()
+ {
+ RegexPathSpec spec = new RegexPathSpec("^/a/(.*)$");
+ assertEquals("Spec.pathSpec","^/a/(.*)$",spec.getPathSpec());
+ assertEquals("Spec.pattern","^/a/(.*)$",spec.getPattern().pattern());
+ assertEquals("Spec.pathDepth",2,spec.getPathDepth());
+ assertEquals("Spec.group",PathSpecGroup.PREFIX_GLOB,spec.group);
+
+ assertMatches(spec,"/a/");
+ assertMatches(spec,"/a/b");
+ assertMatches(spec,"/a/b/c/d/e");
+
+ assertNotMatches(spec,"/a");
+ assertNotMatches(spec,"/aa");
+ assertNotMatches(spec,"/aa/bb");
+ }
+
+ @Test
+ public void testSuffixSpec()
+ {
+ RegexPathSpec spec = new RegexPathSpec("^(.*).do$");
+ assertEquals("Spec.pathSpec","^(.*).do$",spec.getPathSpec());
+ assertEquals("Spec.pattern","^(.*).do$",spec.getPattern().pattern());
+ assertEquals("Spec.pathDepth",0,spec.getPathDepth());
+ assertEquals("Spec.group",PathSpecGroup.SUFFIX_GLOB,spec.group);
+
+ assertMatches(spec,"/a.do");
+ assertMatches(spec,"/a/b/c.do");
+ assertMatches(spec,"/abcde.do");
+ assertMatches(spec,"/abc/efg.do");
+
+ assertNotMatches(spec,"/a");
+ assertNotMatches(spec,"/aa");
+ assertNotMatches(spec,"/aa/bb");
+ assertNotMatches(spec,"/aa/bb.do/more");
+ }
+}
diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/ServletPathSpecMatchListTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/ServletPathSpecMatchListTest.java
new file mode 100644
index 0000000000..b1295a7d9c
--- /dev/null
+++ b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/ServletPathSpecMatchListTest.java
@@ -0,0 +1,101 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// 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 static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Tests of {@link PathMappings#getMatches(String)}
+ */
+@RunWith(Parameterized.class)
+public class ServletPathSpecMatchListTest
+{
+ @Parameters(name="{0} = {1}")
+ public static List<String[]> testCases()
+ {
+ String data[][] = new String[][] {
+ // From old PathMapTest
+ { "All matches", "/animal/bird/path.tar.gz", "[/animal/bird/*=birds, /animal/*=animals, *.tar.gz=tarball, *.gz=gzipped, /=default]"},
+ { "Dir matches", "/animal/fish/", "[/animal/fish/*=fishes, /animal/*=animals, /=default]"},
+ { "Dir matches", "/animal/fish", "[/animal/fish/*=fishes, /animal/*=animals, /=default]"},
+ { "Root matches", "/", "[=root, /=default]"},
+ { "Dir matches", "", "[/=default]"}
+ };
+
+ return Arrays.asList(data);
+ }
+
+ private static PathMappings<String> mappings;
+
+ static {
+ mappings = new PathMappings<>();
+
+ // From old PathMapTest
+ mappings.put(new ServletPathSpec("/abs/path"),"abspath"); // 1
+ mappings.put(new ServletPathSpec("/abs/path/longer"),"longpath"); // 2
+ mappings.put(new ServletPathSpec("/animal/bird/*"),"birds"); // 3
+ mappings.put(new ServletPathSpec("/animal/fish/*"),"fishes"); // 4
+ mappings.put(new ServletPathSpec("/animal/*"),"animals"); // 5
+ mappings.put(new ServletPathSpec("*.tar.gz"),"tarball"); // 6
+ mappings.put(new ServletPathSpec("*.gz"),"gzipped"); // 7
+ mappings.put(new ServletPathSpec("/"),"default"); // 8
+ // 9 was the old Jetty ":" spec delimited case (no longer valid)
+ mappings.put(new ServletPathSpec(""),"root"); // 10
+ mappings.put(new ServletPathSpec("/\u20ACuro/*"),"money"); // 11
+ }
+
+ @Parameter(0)
+ public String message;
+
+ @Parameter(1)
+ public String inputPath;
+
+ @Parameter(2)
+ public String expectedListing;
+
+ @Test
+ public void testGetMatches()
+ {
+ List<MappedResource<String>> matches = mappings.getMatches(inputPath);
+
+ StringBuilder actual = new StringBuilder();
+ actual.append('[');
+ boolean delim = false;
+ for (MappedResource<String> res : matches)
+ {
+ if (delim)
+ actual.append(", ");
+ actual.append(res.getPathSpec().pathSpec).append('=').append(res.getResource());
+ delim = true;
+ }
+ actual.append(']');
+
+ assertThat(message + " on [" + inputPath + "]",actual.toString(),is(expectedListing));
+ }
+}
diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/ServletPathSpecOrderTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/ServletPathSpecOrderTest.java
new file mode 100644
index 0000000000..ea5dddb3f8
--- /dev/null
+++ b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/ServletPathSpecOrderTest.java
@@ -0,0 +1,103 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// 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 static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Tests of {@link PathMappings#getMatch(String)}, with a focus on correct mapping selection order
+ */
+@RunWith(Parameterized.class)
+public class ServletPathSpecOrderTest
+{
+ @Parameters(name="{0} = {1}")
+ public static List<String[]> testCases()
+ {
+ String data[][] = new String[][] {
+ // From old PathMapTest
+ {"/abs/path","abspath"},
+ {"/abs/path/xxx","default"},
+ {"/abs/pith","default"},
+ {"/abs/path/longer","longpath"},
+ {"/abs/path/","default"},
+ {"/abs/path/foo","default"},
+ {"/animal/bird/eagle/bald","birds"},
+ {"/animal/fish/shark/hammerhead","fishes"},
+ {"/animal/insect/ladybug","animals"},
+ {"/animal","animals"},
+ {"/animal/","animals"},
+ {"/animal/other","animals"},
+ {"/animal/*","animals"},
+ {"/downloads/distribution.tar.gz","tarball"},
+ {"/downloads/script.gz","gzipped"},
+ {"/animal/arhive.gz","animals"},
+ {"/Other/path","default"},
+ {"/\u20ACuro/path","money"},
+ {"/","root"},
+
+ // Extra tests
+ {"/downloads/readme.txt","default"},
+ {"/downloads/logs.tgz","default"},
+ {"/main.css","default"}
+ };
+
+ return Arrays.asList(data);
+ }
+
+ private static PathMappings<String> mappings;
+
+ static {
+ mappings = new PathMappings<>();
+
+ // From old PathMapTest
+ mappings.put(new ServletPathSpec("/abs/path"),"abspath"); // 1
+ mappings.put(new ServletPathSpec("/abs/path/longer"),"longpath"); // 2
+ mappings.put(new ServletPathSpec("/animal/bird/*"),"birds"); // 3
+ mappings.put(new ServletPathSpec("/animal/fish/*"),"fishes"); // 4
+ mappings.put(new ServletPathSpec("/animal/*"),"animals"); // 5
+ mappings.put(new ServletPathSpec("*.tar.gz"),"tarball"); // 6
+ mappings.put(new ServletPathSpec("*.gz"),"gzipped"); // 7
+ mappings.put(new ServletPathSpec("/"),"default"); // 8
+ // 9 was the old Jetty ":" spec delimited case (no longer valid)
+ mappings.put(new ServletPathSpec(""),"root"); // 10
+ mappings.put(new ServletPathSpec("/\u20ACuro/*"),"money"); // 11
+ }
+
+ @Parameter(0)
+ public String inputPath;
+
+ @Parameter(1)
+ public String expectedResource;
+
+ @Test
+ public void testMatch()
+ {
+ assertThat("Match on ["+ inputPath+ "]", mappings.getMatch(inputPath).getResource(), is(expectedResource));
+ }
+}
diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/ServletPathSpecTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/ServletPathSpecTest.java
new file mode 100644
index 0000000000..7252a9c1b4
--- /dev/null
+++ b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/ServletPathSpecTest.java
@@ -0,0 +1,188 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// 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 static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+import org.junit.Test;
+
+public class ServletPathSpecTest
+{
+ private void assertBadServletPathSpec(String pathSpec)
+ {
+ try
+ {
+ new ServletPathSpec(pathSpec);
+ fail("Expected IllegalArgumentException for a bad servlet pathspec on: " + pathSpec);
+ }
+ catch (IllegalArgumentException e)
+ {
+ // expected path
+ System.out.println(e);
+ }
+ }
+
+ private void assertMatches(ServletPathSpec spec, String path)
+ {
+ String msg = String.format("Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path);
+ assertThat(msg,spec.matches(path),is(true));
+ }
+
+ private void assertNotMatches(ServletPathSpec spec, String path)
+ {
+ String msg = String.format("!Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path);
+ assertThat(msg,spec.matches(path),is(false));
+ }
+
+ @Test
+ public void testBadServletPathSpecA()
+ {
+ assertBadServletPathSpec("foo");
+ }
+
+ @Test
+ public void testBadServletPathSpecB()
+ {
+ assertBadServletPathSpec("/foo/*.do");
+ }
+
+ @Test
+ public void testBadServletPathSpecC()
+ {
+ assertBadServletPathSpec("foo/*.do");
+ }
+
+ @Test
+ public void testBadServletPathSpecD()
+ {
+ assertBadServletPathSpec("foo/*.*do");
+ }
+
+ @Test
+ public void testBadServletPathSpecE()
+ {
+ assertBadServletPathSpec("*do");
+ }
+
+ @Test
+ public void testDefaultPathSpec()
+ {
+ ServletPathSpec spec = new ServletPathSpec("/");
+ assertEquals("Spec.pathSpec","/",spec.getPathSpec());
+ assertEquals("Spec.pathDepth",-1,spec.getPathDepth());
+ }
+
+ @Test
+ public void testExactPathSpec()
+ {
+ ServletPathSpec spec = new ServletPathSpec("/abs/path");
+ assertEquals("Spec.pathSpec","/abs/path",spec.getPathSpec());
+ assertEquals("Spec.pathDepth",2,spec.getPathDepth());
+
+ assertMatches(spec,"/abs/path");
+
+ assertNotMatches(spec,"/abs/path/");
+ assertNotMatches(spec,"/abs/path/more");
+ assertNotMatches(spec,"/foo");
+ assertNotMatches(spec,"/foo/abs/path");
+ assertNotMatches(spec,"/foo/abs/path/");
+ }
+
+ @Test
+ public void testGetPathInfo()
+ {
+ assertEquals("pathInfo exact",null,new ServletPathSpec("/Foo/bar").getPathInfo("/Foo/bar"));
+ assertEquals("pathInfo prefix","/bar",new ServletPathSpec("/Foo/*").getPathInfo("/Foo/bar"));
+ assertEquals("pathInfo prefix","/*",new ServletPathSpec("/Foo/*").getPathInfo("/Foo/*"));
+ assertEquals("pathInfo prefix","/",new ServletPathSpec("/Foo/*").getPathInfo("/Foo/"));
+ assertEquals("pathInfo prefix",null,new ServletPathSpec("/Foo/*").getPathInfo("/Foo"));
+ assertEquals("pathInfo suffix",null,new ServletPathSpec("*.ext").getPathInfo("/Foo/bar.ext"));
+ assertEquals("pathInfo default",null,new ServletPathSpec("/").getPathInfo("/Foo/bar.ext"));
+
+ assertEquals("pathInfo default","/xxx/zzz",new ServletPathSpec("/*").getPathInfo("/xxx/zzz"));
+ }
+
+ @Test
+ public void testNullPathSpec()
+ {
+ ServletPathSpec spec = new ServletPathSpec(null);
+ assertEquals("Spec.pathSpec","",spec.getPathSpec());
+ assertEquals("Spec.pathDepth",-1,spec.getPathDepth());
+ }
+
+ @Test
+ public void testRootPathSpec()
+ {
+ ServletPathSpec spec = new ServletPathSpec("");
+ assertEquals("Spec.pathSpec","",spec.getPathSpec());
+ assertEquals("Spec.pathDepth",-1,spec.getPathDepth());
+ }
+
+ @Test
+ public void testPathMatch()
+ {
+ assertEquals("pathMatch exact","/Foo/bar",new ServletPathSpec("/Foo/bar").getPathMatch("/Foo/bar"));
+ assertEquals("pathMatch prefix","/Foo",new ServletPathSpec("/Foo/*").getPathMatch("/Foo/bar"));
+ assertEquals("pathMatch prefix","/Foo",new ServletPathSpec("/Foo/*").getPathMatch("/Foo/"));
+ assertEquals("pathMatch prefix","/Foo",new ServletPathSpec("/Foo/*").getPathMatch("/Foo"));
+ assertEquals("pathMatch suffix","/Foo/bar.ext",new ServletPathSpec("*.ext").getPathMatch("/Foo/bar.ext"));
+ assertEquals("pathMatch default","/Foo/bar.ext",new ServletPathSpec("/").getPathMatch("/Foo/bar.ext"));
+
+ assertEquals("pathMatch default","",new ServletPathSpec("/*").getPathMatch("/xxx/zzz"));
+ }
+
+ @Test
+ public void testPrefixPathSpec()
+ {
+ ServletPathSpec spec = new ServletPathSpec("/downloads/*");
+ assertEquals("Spec.pathSpec","/downloads/*",spec.getPathSpec());
+ assertEquals("Spec.pathDepth",2,spec.getPathDepth());
+
+ assertMatches(spec,"/downloads/logo.jpg");
+ assertMatches(spec,"/downloads/distribution.tar.gz");
+ assertMatches(spec,"/downloads/distribution.tgz");
+ assertMatches(spec,"/downloads/distribution.zip");
+
+ assertMatches(spec,"/downloads");
+
+ assertEquals("Spec.pathInfo","/",spec.getPathInfo("/downloads/"));
+ assertEquals("Spec.pathInfo","/distribution.zip",spec.getPathInfo("/downloads/distribution.zip"));
+ assertEquals("Spec.pathInfo","/dist/9.0/distribution.tar.gz",spec.getPathInfo("/downloads/dist/9.0/distribution.tar.gz"));
+ }
+
+ @Test
+ public void testSuffixPathSpec()
+ {
+ ServletPathSpec spec = new ServletPathSpec("*.gz");
+ assertEquals("Spec.pathSpec","*.gz",spec.getPathSpec());
+ assertEquals("Spec.pathDepth",0,spec.getPathDepth());
+
+ assertMatches(spec,"/downloads/distribution.tar.gz");
+ assertMatches(spec,"/downloads/jetty.log.gz");
+
+ assertNotMatches(spec,"/downloads/distribution.zip");
+ assertNotMatches(spec,"/downloads/distribution.tgz");
+ assertNotMatches(spec,"/abs/path");
+
+ assertEquals("Spec.pathInfo",null,spec.getPathInfo("/downloads/distribution.tar.gz"));
+ }
+}
diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/UriTemplatePathSpecBadSpecsTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/UriTemplatePathSpecBadSpecsTest.java
new file mode 100644
index 0000000000..4136ba2f29
--- /dev/null
+++ b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/UriTemplatePathSpecBadSpecsTest.java
@@ -0,0 +1,87 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// 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 static org.junit.Assert.fail;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+/**
+ * Tests for bad path specs on ServerEndpoint Path Param / URI Template
+ */
+@RunWith(Parameterized.class)
+public class UriTemplatePathSpecBadSpecsTest
+{
+ private static void bad(List<String[]> data, String str)
+ {
+ data.add(new String[]
+ { str });
+ }
+
+ @Parameters
+ public static Collection<String[]> data()
+ {
+ List<String[]> data = new ArrayList<>();
+ bad(data,"/a/b{var}"); // bad syntax - variable does not encompass whole path segment
+ bad(data,"a/{var}"); // bad syntax - no start slash
+ bad(data,"/a/{var/b}"); // path segment separator in variable name
+ bad(data,"/{var}/*"); // bad syntax - no globs allowed
+ bad(data,"/{var}.do"); // bad syntax - variable does not encompass whole path segment
+ bad(data,"/a/{var*}"); // use of glob character not allowed in variable name
+ bad(data,"/a/{}"); // bad syntax - no variable name
+ // MIGHT BE ALLOWED bad(data,"/a/{---}"); // no alpha in variable name
+ bad(data,"{var}"); // bad syntax - no start slash
+ bad(data,"/a/{my special variable}"); // bad syntax - space in variable name
+ bad(data,"/a/{var}/{var}"); // variable name duplicate
+ // MIGHT BE ALLOWED bad(data,"/a/{var}/{Var}/{vAR}"); // variable name duplicated (diff case)
+ bad(data,"/a/../../../{var}"); // path navigation not allowed
+ bad(data,"/a/./{var}"); // path navigation not allowed
+ bad(data,"/a//{var}"); // bad syntax - double path slash (no path segment)
+ return data;
+ }
+
+ private String pathSpec;
+
+ public UriTemplatePathSpecBadSpecsTest(String pathSpec)
+ {
+ this.pathSpec = pathSpec;
+ }
+
+ @Test
+ public void testBadPathSpec()
+ {
+ try
+ {
+ new UriTemplatePathSpec(this.pathSpec);
+ fail("Expected IllegalArgumentException for a bad PathParam pathspec on: " + pathSpec);
+ }
+ catch (IllegalArgumentException e)
+ {
+ // expected path
+ System.out.println(e.getMessage());
+ }
+ }
+}
diff --git a/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/UriTemplatePathSpecTest.java b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/UriTemplatePathSpecTest.java
new file mode 100644
index 0000000000..7908344e09
--- /dev/null
+++ b/jetty-http/src/test/java/org/eclipse/jetty/http/pathmap/UriTemplatePathSpecTest.java
@@ -0,0 +1,284 @@
+//
+// ========================================================================
+// Copyright (c) 1995-2015 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// 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 static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import java.util.Map;
+
+import org.junit.Test;
+
+/**
+ * Tests for URI Template Path Specs
+ */
+public class UriTemplatePathSpecTest
+{
+ private void assertDetectedVars(UriTemplatePathSpec spec, String... expectedVars)
+ {
+ String prefix = String.format("Spec(\"%s\")",spec.getPathSpec());
+ assertEquals(prefix + ".variableCount",expectedVars.length,spec.getVariableCount());
+ assertEquals(prefix + ".variable.length",expectedVars.length,spec.getVariables().length);
+ for (int i = 0; i < expectedVars.length; i++)
+ {
+ assertEquals(String.format("%s.variable[%d]",prefix,i),expectedVars[i],spec.getVariables()[i]);
+ }
+ }
+
+ private void assertMatches(PathSpec spec, String path)
+ {
+ String msg = String.format("Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path);
+ assertThat(msg,spec.matches(path),is(true));
+ }
+
+ private void assertNotMatches(PathSpec spec, String path)
+ {
+ String msg = String.format("!Spec(\"%s\").matches(\"%s\")",spec.getPathSpec(),path);
+ assertThat(msg,spec.matches(path),is(false));
+ }
+
+ @Test
+ public void testDefaultPathSpec()
+ {
+ UriTemplatePathSpec spec = new UriTemplatePathSpec("/");
+ assertEquals("Spec.pathSpec","/",spec.getPathSpec());
+ assertEquals("Spec.pattern","^/$",spec.getPattern().pattern());
+ assertEquals("Spec.pathDepth",1,spec.getPathDepth());
+ assertEquals("Spec.group",PathSpecGroup.EXACT,spec.getGroup());
+
+ assertEquals("Spec.variableCount",0,spec.getVariableCount());
+ assertEquals("Spec.variable.length",0,spec.getVariables().length);
+ }
+
+ @Test
+ public void testExactOnePathSpec()
+ {
+ UriTemplatePathSpec spec = new UriTemplatePathSpec("/a");
+ assertEquals("Spec.pathSpec","/a",spec.getPathSpec());
+ assertEquals("Spec.pattern","^/a$",spec.getPattern().pattern());
+ assertEquals("Spec.pathDepth",1,spec.getPathDepth());
+ assertEquals("Spec.group",PathSpecGroup.EXACT,spec.getGroup());
+
+ assertMatches(spec,"/a");
+ assertMatches(spec,"/a?type=other");
+ assertNotMatches(spec,"/a/b");
+ assertNotMatches(spec,"/a/");
+
+ assertEquals("Spec.variableCount",0,spec.getVariableCount());
+ assertEquals("Spec.variable.length",0,spec.getVariables().length);
+ }
+
+ @Test
+ public void testExactPathSpec_TestWebapp()
+ {
+ UriTemplatePathSpec spec = new UriTemplatePathSpec("/deep.thought/");
+ assertEquals("Spec.pathSpec","/deep.thought/",spec.getPathSpec());
+ assertEquals("Spec.pattern","^/deep\\.thought/$",spec.getPattern().pattern());
+ assertEquals("Spec.pathDepth",1,spec.getPathDepth());
+ assertEquals("Spec.group",PathSpecGroup.EXACT,spec.getGroup());
+
+ assertMatches(spec,"/deep.thought/");
+ assertNotMatches(spec,"/deep.thought");
+
+ assertEquals("Spec.variableCount",0,spec.getVariableCount());
+ assertEquals("Spec.variable.length",0,spec.getVariables().length);
+ }
+
+ @Test
+ public void testExactTwoPathSpec()
+ {
+ UriTemplatePathSpec spec = new UriTemplatePathSpec("/a/b");
+ assertEquals("Spec.pathSpec","/a/b",spec.getPathSpec());
+ assertEquals("Spec.pattern","^/a/b$",spec.getPattern().pattern());
+ assertEquals("Spec.pathDepth",2,spec.getPathDepth());
+ assertEquals("Spec.group",PathSpecGroup.EXACT,spec.getGroup());
+
+ assertEquals("Spec.variableCount",0,spec.getVariableCount());
+ assertEquals("Spec.variable.length",0,spec.getVariables().length);
+
+ assertMatches(spec,"/a/b");
+
+ assertNotMatches(spec,"/a/b/");
+ assertNotMatches(spec,"/a/");
+ assertNotMatches(spec,"/a/bb");
+ }
+
+ @Test
+ public void testMiddleVarPathSpec()
+ {
+ UriTemplatePathSpec spec = new UriTemplatePathSpec("/a/{var}/c");
+ assertEquals("Spec.pathSpec","/a/{var}/c",spec.getPathSpec());
+ assertEquals("Spec.pattern","^/a/([^/]+)/c$",spec.getPattern().pattern());
+ assertEquals("Spec.pathDepth",3,spec.getPathDepth());
+ assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.getGroup());
+
+ assertDetectedVars(spec,"var");
+
+ assertMatches(spec,"/a/b/c");
+ assertMatches(spec,"/a/zz/c");
+ assertMatches(spec,"/a/hello+world/c");
+ assertNotMatches(spec,"/a/bc");
+ assertNotMatches(spec,"/a/b/");
+ assertNotMatches(spec,"/a/b");
+
+ Map<String, String> mapped = spec.getPathParams("/a/b/c");
+ assertThat("Spec.pathParams",mapped,notNullValue());
+ assertThat("Spec.pathParams.size",mapped.size(),is(1));
+ assertEquals("Spec.pathParams[var]","b",mapped.get("var"));
+ }
+
+ @Test
+ public void testOneVarPathSpec()
+ {
+ UriTemplatePathSpec spec = new UriTemplatePathSpec("/a/{foo}");
+ assertEquals("Spec.pathSpec","/a/{foo}",spec.getPathSpec());
+ assertEquals("Spec.pattern","^/a/([^/]+)$",spec.getPattern().pattern());
+ assertEquals("Spec.pathDepth",2,spec.getPathDepth());
+ assertEquals("Spec.group",PathSpecGroup.PREFIX_GLOB,spec.getGroup());
+
+ assertDetectedVars(spec,"foo");
+
+ assertMatches(spec,"/a/b");
+ assertNotMatches(spec,"/a/");
+ assertNotMatches(spec,"/a");
+
+ Map<String, String> mapped = spec.getPathParams("/a/b");
+ assertThat("Spec.pathParams",mapped,notNullValue());
+ assertThat("Spec.pathParams.size",mapped.size(),is(1));
+ assertEquals("Spec.pathParams[foo]","b",mapped.get("foo"));
+ }
+
+ @Test
+ public void testOneVarSuffixPathSpec()
+ {
+ UriTemplatePathSpec spec = new UriTemplatePathSpec("/{var}/b/c");
+ assertEquals("Spec.pathSpec","/{var}/b/c",spec.getPathSpec());
+ assertEquals("Spec.pattern","^/([^/]+)/b/c$",spec.getPattern().pattern());
+ assertEquals("Spec.pathDepth",3,spec.getPathDepth());
+ assertEquals("Spec.group",PathSpecGroup.SUFFIX_GLOB,spec.getGroup());
+
+ assertDetectedVars(spec,"var");
+
+ assertMatches(spec,"/a/b/c");
+ assertMatches(spec,"/az/b/c");
+ assertMatches(spec,"/hello+world/b/c");
+ assertNotMatches(spec,"/a/bc");
+ assertNotMatches(spec,"/a/b/");
+ assertNotMatches(spec,"/a/b");
+
+ Map<String, String> mapped = spec.getPathParams("/a/b/c");
+ assertThat("Spec.pathParams",mapped,notNullValue());
+ assertThat("Spec.pathParams.size",mapped.size(),is(1));
+ assertEquals("Spec.pathParams[var]","a",mapped.get("var"));
+ }
+
+ @Test
+ public void testTwoVarComplexInnerPathSpec()
+ {
+ UriTemplatePathSpec spec = new UriTemplatePathSpec("/a/{var1}/c/{var2}/e");
+ assertEquals("Spec.pathSpec","/a/{var1}/c/{var2}/e",spec.getPathSpec());
+ assertEquals("Spec.pattern","^/a/([^/]+)/c/([^/]+)/e$",spec.getPattern().pattern());
+ assertEquals("Spec.pathDepth",5,spec.getPathDepth());
+ assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.getGroup());
+
+ assertDetectedVars(spec,"var1","var2");
+
+ assertMatches(spec,"/a/b/c/d/e");
+ assertNotMatches(spec,"/a/bc/d/e");
+ assertNotMatches(spec,"/a/b/d/e");
+ assertNotMatches(spec,"/a/b//d/e");
+
+ Map<String, String> mapped = spec.getPathParams("/a/b/c/d/e");
+ assertThat("Spec.pathParams",mapped,notNullValue());
+ assertThat("Spec.pathParams.size",mapped.size(),is(2));
+ assertEquals("Spec.pathParams[var1]","b",mapped.get("var1"));
+ assertEquals("Spec.pathParams[var2]","d",mapped.get("var2"));
+ }
+
+ @Test
+ public void testTwoVarComplexOuterPathSpec()
+ {
+ UriTemplatePathSpec spec = new UriTemplatePathSpec("/{var1}/b/{var2}/{var3}");
+ assertEquals("Spec.pathSpec","/{var1}/b/{var2}/{var3}",spec.getPathSpec());
+ assertEquals("Spec.pattern","^/([^/]+)/b/([^/]+)/([^/]+)$",spec.getPattern().pattern());
+ assertEquals("Spec.pathDepth",4,spec.getPathDepth());
+ assertEquals("Spec.group",PathSpecGroup.MIDDLE_GLOB,spec.getGroup());
+
+ assertDetectedVars(spec,"var1","var2","var3");
+
+ assertMatches(spec,"/a/b/c/d");
+ assertNotMatches(spec,"/a/bc/d/e");
+ assertNotMatches(spec,"/a/c/d/e");
+ assertNotMatches(spec,"/a//d/e");
+
+ Map<String, String> mapped = spec.getPathParams("/a/b/c/d");
+ assertThat("Spec.pathParams",mapped,notNullValue());
+ assertThat("Spec.pathParams.size",mapped.size(),is(3));
+ assertEquals("Spec.pathParams[var1]","a",mapped.get("var1"));
+ assertEquals("Spec.pathParams[var2]","c",mapped.get("var2"));
+ assertEquals("Spec.pathParams[var3]","d",mapped.get("var3"));
+ }
+
+ @Test
+ public void testTwoVarPrefixPathSpec()
+ {
+ UriTemplatePathSpec spec = new UriTemplatePathSpec("/a/{var1}/{var2}");
+ assertEquals("Spec.pathSpec","/a/{var1}/{var2}",spec.getPathSpec());
+ assertEquals("Spec.pattern","^/a/([^/]+)/([^/]+)$",spec.getPattern().pattern());
+ assertEquals("Spec.pathDepth",3,spec.getPathDepth());
+ assertEquals("Spec.group",PathSpecGroup.PREFIX_GLOB,spec.getGroup());
+
+ assertDetectedVars(spec,"var1","var2");
+
+ assertMatches(spec,"/a/b/c");
+ assertNotMatches(spec,"/a/bc");
+ assertNotMatches(spec,"/a/b/");
+ assertNotMatches(spec,"/a/b");
+
+ Map<String, String> mapped = spec.getPathParams("/a/b/c");
+ assertThat("Spec.pathParams",mapped,notNullValue());
+ assertThat("Spec.pathParams.size",mapped.size(),is(2));
+ assertEquals("Spec.pathParams[var1]","b",mapped.get("var1"));
+ assertEquals("Spec.pathParams[var2]","c",mapped.get("var2"));
+ }
+
+ @Test
+ public void testVarOnlyPathSpec()
+ {
+ UriTemplatePathSpec spec = new UriTemplatePathSpec("/{var1}");
+ assertEquals("Spec.pathSpec","/{var1}",spec.getPathSpec());
+ assertEquals("Spec.pattern","^/([^/]+)$",spec.getPattern().pattern());
+ assertEquals("Spec.pathDepth",1,spec.getPathDepth());
+ assertEquals("Spec.group",PathSpecGroup.PREFIX_GLOB,spec.getGroup());
+
+ assertDetectedVars(spec,"var1");
+
+ assertMatches(spec,"/a");
+ assertNotMatches(spec,"/");
+ assertNotMatches(spec,"/a/b");
+ assertNotMatches(spec,"/a/b/c");
+
+ Map<String, String> mapped = spec.getPathParams("/a");
+ assertThat("Spec.pathParams",mapped,notNullValue());
+ assertThat("Spec.pathParams.size",mapped.size(),is(1));
+ assertEquals("Spec.pathParams[var1]","a",mapped.get("var1"));
+ }
+}

Back to the top