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

com.linecorp.armeria.server.DefaultPathMapping Maven / Gradle / Ivy

/*
 * Copyright 2017 LINE Corporation
 *
 * LINE Corporation licenses this file to you 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 com.linecorp.armeria.server;

import static java.util.Objects.requireNonNull;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.StringJoiner;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.annotation.Nullable;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;

/**
 * The default {@link PathMapping} implementation. It holds three things:
 * 
    *
  • The regex-compiled form of the path. It is used for matching and extracting.
  • *
  • The skeleton of the path. It is used for duplication detecting.
  • *
  • A set of path parameters declared in the path pattern
  • *
*/ final class DefaultPathMapping extends AbstractPathMapping { private static final Pattern VALID_PATTERN = Pattern.compile("(/[^/{}:]+|/:[^/{}]+|/\\{[^/{}]+})+/?"); /** * The original path pattern specified in the constructor. */ private final String pathPattern; /** * Regex form of given path, which will be used for matching or extracting. * *

e.g. "/{x}/{y}/{x}" -> "/(?<x>[^/]+)/(?<y>[^/]+)/(\\k<x>)" */ private final Pattern pattern; /** * Skeletal form of given path, which is used for duplicated routing rule detection. * For example, "/{a}/{b}" and "/{c}/{d}" has same skeletal form and regarded as duplicated. * *

e.g. "/{x}/{y}/{z}" -> "/{}/{}/{}" */ private final String skeleton; /** * The names of the path parameters in the order of appearance. */ private final String[] paramNameArray; /** * The names of the path parameters this mapping will extract. */ private final Set paramNames; private final String loggerName; private final String metricName; /** * Create a {@link DefaultPathMapping} instance from given {@code pathPattern}. * * @param pathPattern the {@link String} that contains path params. * e.g. {@code /users/{name}} or {@code /users/:name} * * @throws IllegalArgumentException if the {@code pathPattern} is invalid. */ DefaultPathMapping(String pathPattern) { requireNonNull(pathPattern, "pathPattern"); if (!pathPattern.startsWith("/")) { throw new IllegalArgumentException("pathPattern: " + pathPattern + " (must start with '/')"); } if (!VALID_PATTERN.matcher(pathPattern).matches()) { throw new IllegalArgumentException("pathPattern: " + pathPattern + " (invalid pattern)"); } final StringJoiner patternJoiner = new StringJoiner("/"); final StringJoiner skeletonJoiner = new StringJoiner("/"); final List paramNames = new ArrayList<>(); for (String token : pathPattern.split("/")) { final String paramName = paramName(token); if (paramName == null) { // If the given token is a constant, do not manipulate it. patternJoiner.add(token); skeletonJoiner.add(token); continue; } final int paramNameIdx = paramNames.indexOf(paramName); if (paramNameIdx < 0) { // If the given token appeared first time, add it to the set and // replace it with a capturing group expression in regex. paramNames.add(paramName); patternJoiner.add("([^/]+)"); } else { // If the given token appeared before, replace it with a back-reference expression // in regex. patternJoiner.add("\\" + (paramNameIdx + 1)); } skeletonJoiner.add("{}"); } this.pathPattern = pathPattern; pattern = Pattern.compile(patternJoiner.toString()); skeleton = skeletonJoiner.toString(); paramNameArray = paramNames.toArray(new String[paramNames.size()]); this.paramNames = ImmutableSet.copyOf(paramNames); loggerName = loggerName(pathPattern); metricName = pathPattern; } /** * Returns the name of the path parameter contained in the path element. If it contains no path parameter, * {@code null} is returned. e.g. *

    *
  • {@code "{foo}"} -> {@code "foo"}
  • *
  • {@code ":bar"} -> {@code "bar"}
  • *
  • {@code "baz"} -> {@code null}
  • *
*/ private static String paramName(String token) { if (token.startsWith("{") && token.endsWith("}")) { return token.substring(1, token.length() - 1); } if (token.startsWith(":")) { return token.substring(1); } return null; } /** * Returns the skeleton. */ String skeleton() { return skeleton; } @Override public Set paramNames() { return paramNames; } @Override public String loggerName() { return loggerName; } @Override public String metricName() { return metricName; } @Override protected PathMappingResult doApply(String path, @Nullable String query) { final Matcher matcher = pattern.matcher(path); if (!matcher.matches()) { return PathMappingResult.empty(); } if (paramNameArray.length == 0) { return PathMappingResult.of(path, query); } final ImmutableMap.Builder pathParams = ImmutableMap.builder(); for (int i = 0; i < paramNameArray.length; i++) { pathParams.put(paramNameArray[i], matcher.group(i + 1)); } return PathMappingResult.of(path, query, pathParams.build()); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } DefaultPathMapping that = (DefaultPathMapping) o; return skeleton.equals(that.skeleton) && Arrays.equals(paramNameArray, that.paramNameArray); } @Override public int hashCode() { return skeleton.hashCode() * 31 + Arrays.hashCode(paramNameArray); } @Override public String toString() { return pathPattern; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy