All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.undertow.util.PathTemplate Maven / Gradle / Ivy

There is a newer version: 62
Show newest version
/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2014 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package io.undertow.util;

import io.undertow.UndertowMessages;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;


/**
 * Represents a parsed web socket path template.
 * 

* This class can be compared to other path templates, with templates that are considered * lower have a higher priority, and should be checked first. *

* This comparison can also be used to check for semantically equal paths, if * a.compareTo(b) == 0 then the two paths are equivalent, which will generally * result in a deployment exception. * * @author Stuart Douglas */ public class PathTemplate implements Comparable { private final String templateString; private final boolean template; private final String base; final List parts; private final Set parameterNames; private final boolean trailingSlash; private PathTemplate(String templateString, final boolean template, final String base, final List parts, Set parameterNames, boolean trailingSlash) { this.templateString = templateString; this.template = template; this.base = base; this.parts = parts; this.parameterNames = Collections.unmodifiableSet(parameterNames); this.trailingSlash = trailingSlash; } public static PathTemplate create(final String inputPath) { // a path is required if(inputPath == null) { throw UndertowMessages.MESSAGES.pathMustBeSpecified(); } // prepend a "/" if none is present if(!inputPath.startsWith("/")) { return PathTemplate.create("/" + inputPath); } // create string from modified string final String path = inputPath; int state = 0; String base = ""; List parts = new ArrayList<>(); int stringStart = 0; //0 parsing base //1 parsing base, last char was / //2 in template part //3 just after template part, expecting / //4 expecting either template or segment //5 in segment for (int i = 0; i < path.length(); ++i) { final int c = path.charAt(i); switch (state) { case 0: { if (c == '/') { state = 1; } else if (c == '*') { base = path.substring(0, i + 1); stringStart = i; state = 5; } else { state = 0; } break; } case 1: { if (c == '{') { base = path.substring(0, i); stringStart = i + 1; state = 2; } else if (c == '*') { base = path.substring(0, i + 1); stringStart = i; state = 5; } else if (c != '/') { state = 0; } break; } case 2: { if (c == '}') { Part part = new Part(true, path.substring(stringStart, i)); parts.add(part); stringStart = i; state = 3; } break; } case 3: { if (c == '/') { state = 4; } else { throw UndertowMessages.MESSAGES.couldNotParseUriTemplate(path, i); } break; } case 4: { if (c == '{') { stringStart = i + 1; state = 2; } else if (c != '/') { stringStart = i; state = 5; } break; } case 5: { if (c == '/') { Part part = new Part(false, path.substring(stringStart, i)); parts.add(part); stringStart = i + 1; state = 4; } break; } } } boolean trailingSlash = false; switch (state) { case 1: trailingSlash = true; //fall through case 0: { base = path; break; } case 2: { throw UndertowMessages.MESSAGES.couldNotParseUriTemplate(path, path.length()); } case 4: { trailingSlash = true; break; } case 5: { Part part = new Part(false, path.substring(stringStart)); parts.add(part); break; } } final Set templates = new HashSet<>(); for(Part part : parts) { if(part.template) { templates.add(part.part); } } return new PathTemplate(path, state > 1 && !base.contains("*"), base, parts, templates, trailingSlash); } /** * Check if the given uri matches the template. If so then it will return true and * place the value of any path parameters into the given map. *

* Note the map may be modified even if the match in unsuccessful, however in this case * it will be emptied before the method returns * * @param path The request path, relative to the context root * @param pathParameters The path parameters map to fill out * @return true if the URI is a match */ public boolean matches(final String path, final Map pathParameters) { if (!template && base.contains("*")) { final int indexOf = base.indexOf("*"); final String startBase = base.substring(0, indexOf); if (!path.startsWith(startBase)) { return false; } pathParameters.put("*", path.substring(indexOf,path.length())); return true; } if (!path.startsWith(base)) { return false; } int baseLength = base.length(); if (!template) { return path.length() == baseLength; } if(trailingSlash) { //the template has a trailing slash //we verify this first as it is cheap //and it simplifies the matching algorithm below if(path.charAt(path.length() -1 ) != '/') { return false; } } int currentPartPosition = 0; PathTemplate.Part current = parts.get(currentPartPosition); int stringStart = baseLength; int i; for (i = baseLength; i < path.length(); ++i) { final char currentChar = path.charAt(i); if (currentChar == '?' || current.part.equals("*")) { break; } else if (currentChar == '/') { String result = path.substring(stringStart, i); if (current.template) { pathParameters.put(current.part, result); } else if (!result.equals(current.part)) { pathParameters.clear(); return false; } ++currentPartPosition; if (currentPartPosition == parts.size()) { //this is a match if this is the last character return i == (path.length() - 1); } current = parts.get(currentPartPosition); stringStart = i + 1; } } if (currentPartPosition + 1 != parts.size()) { pathParameters.clear(); return false; } String result = path.substring(stringStart, i); if (current.part.equals("*")) { pathParameters.put(current.part, path.substring(stringStart,path.length())); return true; } if (current.template) { pathParameters.put(current.part, result); } else if (!result.equals(current.part)) { pathParameters.clear(); return false; } return true; } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof PathTemplate)) return false; PathTemplate that = (PathTemplate) o; return this.compareTo(that) == 0; } @Override public int hashCode() { int result = getTemplateString() != null ? getTemplateString().hashCode() : 0; result = 31 * result + (template ? 1 : 0); result = 31 * result + (getBase() != null ? getBase().hashCode() : 0); result = 31 * result + (parts != null ? parts.hashCode() : 0); result = 31 * result + (getParameterNames() != null ? getParameterNames().hashCode() : 0); return result; } @Override public int compareTo(final PathTemplate o) { //we want templates with the highest priority to sort first //so we sort in reverse priority order //templates have lower priority if (template && !o.template) { return 1; } else if (o.template && !template) { return -1; } int res = base.compareTo(o.base); if (res > 0) { //our base is longer return -1; } else if (res < 0) { return 1; } else if (!template) { //they are the same path return 0; } //the first path with a non-template element int i = 0; for (; ; ) { if (parts.size() == i) { if (o.parts.size() == i) { return base.compareTo(o.base); } return 1; } else if (o.parts.size() == i) { //we have more parts, so should be checked first return -1; } Part thisPath = parts.get(i); Part otherPart = o.parts.get(i); if (thisPath.template && !otherPart.template) { //non template part sorts first return 1; } else if (!thisPath.template && otherPart.template) { return -1; } else if (!thisPath.template) { int r = thisPath.part.compareTo(otherPart.part); if (r != 0) { return r; } } ++i; } } public String getBase() { return base; } public String getTemplateString() { return templateString; } public Set getParameterNames() { return parameterNames; } private static class Part { final boolean template; final String part; private Part(final boolean template, final String part) { this.template = template; this.part = part; } @Override public String toString() { return "Part{" + "template=" + template + ", part='" + part + '\'' + '}'; } } @Override public String toString() { return "PathTemplate{" + "template=" + template + ", base='" + base + '\'' + ", parts=" + parts + '}'; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy