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

io.undertow.util.PathMatcher 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.UndertowLogger;
import io.undertow.UndertowMessages;

import java.util.Collections;
import java.util.Comparator;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentMap;

/**
 * Handler that dispatches to a given handler based of a prefix match of the path.
 * 

* This only matches a single level of a request, e.g if you have a request that takes the form: *

* /foo/bar *

* * @author Stuart Douglas */ public class PathMatcher { private static final String STRING_PATH_SEPARATOR = "/"; private volatile T defaultHandler; private final SubstringMap paths = new SubstringMap<>(); private final ConcurrentMap exactPathMatches = new CopyOnWriteMap<>(); /** * lengths of all registered paths */ private volatile int[] lengths = {}; public PathMatcher(final T defaultHandler) { this.defaultHandler = defaultHandler; } public PathMatcher() { } public Set getExactPathMatchesSet(){ return Collections.unmodifiableSet(exactPathMatches.keySet()); } public Set getPathMatchesSet(){ return Collections.unmodifiableSet(paths.toMap().keySet()); } /** * Matches a path against the registered handlers. * @param path The relative path to match * @return The match match. This will never be null, however if none matched its value field will be */ public PathMatch match(String path){ if (!exactPathMatches.isEmpty()) { T match = getExactPath(path); if (match != null) { UndertowLogger.REQUEST_LOGGER.debugf("Matched exact path %s", path); return new PathMatch<>(path, "", match); } } int length = path.length(); final int[] lengths = this.lengths; for (int i = 0; i < lengths.length; ++i) { int pathLength = lengths[i]; if (pathLength == length) { SubstringMap.SubstringMatch next = paths.get(path, length); if (next != null) { UndertowLogger.REQUEST_LOGGER.debugf("Matched prefix path %s for path %s", next.getKey(), path); return new PathMatch<>(path, "", next.getValue()); } } else if (pathLength < length) { char c = path.charAt(pathLength); if (c == '/') { SubstringMap.SubstringMatch next = paths.get(path, pathLength); if (next != null) { UndertowLogger.REQUEST_LOGGER.debugf("Matched prefix path %s for path %s", next.getKey(), path); return new PathMatch<>(next.getKey(), path.substring(pathLength), next.getValue()); } } } } UndertowLogger.REQUEST_LOGGER.debugf("Matched default handler path %s", path); return new PathMatch<>("", path, defaultHandler); } /** * Adds a path prefix and a handler for that path. If the path does not start * with a / then one will be prepended. *

* The match is done on a prefix bases, so registering /foo will also match /foo/bar. Exact * path matches are taken into account first. *

* If / is specified as the path then it will replace the default handler. * * @param path The path * @param handler The handler */ public synchronized PathMatcher addPrefixPath(final String path, final T handler) { if (path.isEmpty()) { throw UndertowMessages.MESSAGES.pathMustBeSpecified(); } final String normalizedPath = URLUtils.normalizeSlashes(path); if (PathMatcher.STRING_PATH_SEPARATOR.equals(normalizedPath)) { this.defaultHandler = handler; return this; } paths.put(normalizedPath, handler); buildLengths(); return this; } public synchronized PathMatcher addExactPath(final String path, final T handler) { if (path.isEmpty()) { throw UndertowMessages.MESSAGES.pathMustBeSpecified(); } exactPathMatches.put(URLUtils.normalizeSlashes(path), handler); return this; } public T getExactPath(final String path) { return exactPathMatches.get(URLUtils.normalizeSlashes(path)); } public T getPrefixPath(final String path) { final String normalizedPath = URLUtils.normalizeSlashes(path); // enable the prefix path mechanism to return the default handler SubstringMap.SubstringMatch match = paths.get(normalizedPath); if (PathMatcher.STRING_PATH_SEPARATOR.equals(normalizedPath) && match == null) { return this.defaultHandler; } if(match == null) { return null; } // return the value for the given path return match.getValue(); } private void buildLengths() { final Set lengths = new TreeSet<>(new Comparator() { @Override public int compare(Integer o1, Integer o2) { return -o1.compareTo(o2); } }); for (String p : paths.keys()) { lengths.add(p.length()); } int[] lengthArray = new int[lengths.size()]; int pos = 0; for (int i : lengths) { lengthArray[pos++] = i; } this.lengths = lengthArray; } @Deprecated public synchronized PathMatcher removePath(final String path) { return removePrefixPath(path); } public synchronized PathMatcher removePrefixPath(final String path) { if (path == null || path.isEmpty()) { throw UndertowMessages.MESSAGES.pathMustBeSpecified(); } final String normalizedPath = URLUtils.normalizeSlashes(path); if (PathMatcher.STRING_PATH_SEPARATOR.equals(normalizedPath)) { defaultHandler = null; return this; } paths.remove(normalizedPath); buildLengths(); return this; } public synchronized PathMatcher removeExactPath(final String path) { if (path == null || path.isEmpty()) { throw UndertowMessages.MESSAGES.pathMustBeSpecified(); } exactPathMatches.remove(URLUtils.normalizeSlashes(path)); return this; } public synchronized PathMatcher clearPaths() { paths.clear(); exactPathMatches.clear(); this.lengths = new int[0]; defaultHandler = null; return this; } public Map getPaths() { return paths.toMap(); } public static final class PathMatch { private final String matched; private final String remaining; private final T value; public PathMatch(String matched, String remaining, T value) { this.matched = matched; this.remaining = remaining; this.value = value; } public String getRemaining() { return remaining; } public String getMatched() { return matched; } public T getValue() { return value; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy