/*
* Copyright 2015 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.Optional;
import java.util.regex.Pattern;
import com.linecorp.armeria.server.PathManipulators.Prepend;
import com.linecorp.armeria.server.PathManipulators.StripParents;
import com.linecorp.armeria.server.PathManipulators.StripPrefixByNumPathComponents;
import com.linecorp.armeria.server.PathManipulators.StripPrefixByPathPrefix;
import com.linecorp.armeria.server.docs.DocService;
/**
* Matches the absolute path part of a URI and translates the matched path to another path string.
* The translated path, returned by {@link #apply(String)}, determines the value of
* {@link ServiceRequestContext#mappedPath()}.
*/
public interface PathMapping {
/**
* Creates a new {@link PathMapping} that matches a {@linkplain ServiceRequestContext#path() path} with
* the specified regular expression, as defined in {@link Pattern}. The returned {@link PathMapping} does
* not perform any translation. To create a {@link PathMapping} that performs a translation, use the
* decorator methods like {@link #stripPrefix(String)}.
*/
static PathMapping ofRegex(Pattern regex) {
return new RegexPathMapping(regex);
}
/**
* Creates a new {@link PathMapping} that matches a {@linkplain ServiceRequestContext#path() path} with
* the specified regular expression, as defined in {@link Pattern}. The returned {@link PathMapping} does
* not perform any translation. To create a {@link PathMapping} that performs a translation, use the
* decorator methods like {@link #stripPrefix(String)}.
*/
static PathMapping ofRegex(String regex) {
return ofRegex(Pattern.compile(requireNonNull(regex, "regex")));
}
/**
* Creates a new {@link PathMapping} that matches a {@linkplain ServiceRequestContext#path() path} with
* the specified glob expression, where {@code "*"} matches a path component non-recursively and
* {@code "**"} matches path components recursively. The returned {@link PathMapping} does not perform any
* translation. To create a {@link PathMapping} that performs a translation, use the decorator methods like
* {@link #stripPrefix(String)}.
*/
static PathMapping ofGlob(String glob) {
requireNonNull(glob, "glob");
if (glob.startsWith("/") && !glob.contains("*")) {
// Does not have a pattern matcher.
return ofExact(glob);
}
return new GlobPathMapping(glob);
}
/**
* Creates a new {@link PathMapping} that matches a {@linkplain ServiceRequestContext#path() path}
* under the specified directory prefix. It also removes the specified directory prefix from the matched
* path so that {@link ServiceRequestContext#mappedPath()} does not have the specified directory prefix.
* For example, when {@code pathPrefix} is {@code "/foo/"}:
* <ul>
* <li>{@code "/foo/"} translates to {@code "/"}</li>
* <li>{@code "/foo/bar"} translates to {@code "/bar"}</li>
* <li>{@code "/foo/bar/baz"} translates to {@code "/bar/baz"}</li>
* </ul>
* This method is a shortcut to {@link #ofPrefix(String, boolean) ofPrefix(pathPrefix, true)}.
*/
static PathMapping ofPrefix(String pathPrefix) {
return ofPrefix(pathPrefix, true);
}
/**
* Creates a new {@link PathMapping} that matches a {@linkplain ServiceRequestContext#path() path}
* under the specified directory prefix. When {@code stripPrefix} is {@code true}, it also removes the
* specified directory prefix from the matched path so that {@link ServiceRequestContext#mappedPath()}
* does not have the specified directory prefix. For example, when {@code pathPrefix} is {@code "/foo/"}:
* <ul>
* <li>{@code "/foo/"} translates to {@code "/"}</li>
* <li>{@code "/foo/bar"} translates to {@code "/bar"}</li>
* <li>{@code "/foo/bar/baz"} translates to {@code "/bar/baz"}</li>
* </ul>
*/
static PathMapping ofPrefix(String pathPrefix, boolean stripPrefix) {
requireNonNull(pathPrefix, "pathPrefix");
if ("/".equals(pathPrefix)) {
// Every path starts with '/'.
return ofCatchAll();
}
return new PrefixPathMapping(pathPrefix, stripPrefix);
}
/**
* Creates a new {@link PathMapping} that performs an exact match. The returned {@link PathMapping} always
* translates the matched path to {@code "/"}.
*/
static PathMapping ofExact(String exactPath) {
return new ExactPathMapping(exactPath);
}
/**
* Returns a singleton {@link PathMapping} that always matches any path. The returned {@link PathMapping}
* does not perform any translation. To create a {@link PathMapping} that performs a translation, use the
* decorator methods like {@link #stripPrefix(String)}.
*/
static PathMapping ofCatchAll() {
return CatchAllPathMapping.INSTANCE;
}
/**
* Matches the specified {@code path} and translates the matched {@code path} to another path string.
*
* @param path an absolute path as defined in <a href="https://tools.ietf.org/html/rfc3986">RFC3986</a>
* @return the translated path which is used as the value of {@link ServiceRequestContext#mappedPath()}.
* {@code null} if the specified {@code path} does not match this mapping.
*/
String apply(String path);
/**
* Returns the logger name.
*
* @return the logger name
*/
String loggerName();
/**
* Returns the metric name.
*
* @return the metric name whose components are separated by a dot (.)
*/
String metricName();
/**
* Returns the exact path of this path mapping if it is an exact path mapping, or {@link Optional#empty}
* otherwise. This can be useful for services which provide logic after scanning the server's mapped
* services, e.g. {@link DocService}
*/
Optional<String> exactPath();
/**
* Returns the prefix of this path mapping if it is a prefix mapping, or {@link Optional#empty}
* otherwise. This can be useful for services which provide logic after scanning the server's mapped
* services, e.g. {@link DocService}
*/
Optional<String> prefix();
/**
* Creates a new {@link PathMapping} that removes the specified {@code pathPrefix} from the matched path
* so that the {@link ServiceRequestContext#mappedPath()} does not have the specified {@code pathPrefix}.
* This method is useful when used with {@link #ofRegex(Pattern)} or {@link #ofGlob(String)}. For example:
* <pre>{@code
* PathMapping.ofRegex("^/foo/[^/]+/bar/.*$").stripPrefix("/foo/");
* PathMapping.ofGlob("^/foo/*/bar/**").stripPrefix("/foo/");
* }</pre>
*/
default PathMapping stripPrefix(String pathPrefix) {
requireNonNull(pathPrefix, "pathPrefix");
if (pathPrefix.isEmpty()) {
return this;
}
return new StripPrefixByPathPrefix(this, pathPrefix);
}
/**
* Creates a new {@link PathMapping} that removes the first {@code <numPathComponents>} path components
* from the matched path so that the {@link ServiceRequestContext#mappedPath()} does not have
* unnecessary path prefixes. This method is useful when used with {@link #ofRegex(Pattern)} or
* {@link #ofGlob(String)}. For example:
* <pre>{@code
* PathMapping.ofRegex("^/(foo|bar)/[^/]+/baz/.*$").stripPrefix(1);
* PathMapping.ofGlob("^/foo/*/baz/**").stripPrefix(1);
* }</pre>
*/
default PathMapping stripPrefix(int numPathComponents) {
if (numPathComponents == 0) {
return this;
}
return new StripPrefixByNumPathComponents(this, numPathComponents);
}
/**
* Creates a new {@link PathMapping} that removes all parent components from the matched path so that
* the {@link ServiceRequestContext#mappedPath()} contains only a single path component. This method
* is useful when you are interested only in the file name part of the path.
*/
default PathMapping stripParents() {
return new StripParents(this);
}
/**
* Creates a new {@link PathMapping} that prepends the specified {@code pathPrefix} to the matched path.
*/
default PathMapping prepend(String pathPrefix) {
requireNonNull(pathPrefix, "pathPrefix");
if (pathPrefix.isEmpty() || "/".equals(pathPrefix)) {
return this;
}
return new Prepend(this, pathPrefix);
}
}