org.sonar.api.web.AbstractUrlPattern Maven / Gradle / Ivy
/*
* Sonar Plugin API
* Copyright (C) 2009-2024 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.api.web;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import static java.util.Arrays.asList;
import static java.util.Collections.unmodifiableList;
import static org.apache.commons.lang3.StringUtils.substringBeforeLast;
import static org.sonar.api.utils.Preconditions.checkArgument;
/**
* Logic of this class should be moved to URLPattern class after deprecation period.
*/
abstract class AbstractUrlPattern {
private static final String MATCH_ALL = "/*";
private final List inclusions;
private final List exclusions;
private final Predicate[] inclusionPredicates;
private final Predicate[] exclusionPredicates;
AbstractUrlPattern(Builder builder) {
this.inclusions = unmodifiableList(new ArrayList<>(builder.inclusions));
this.exclusions = unmodifiableList(new ArrayList<>(builder.exclusions));
if (builder.inclusionPredicates.isEmpty()) {
// because Stream#anyMatch() returns false if stream is empty
this.inclusionPredicates = new Predicate[] {s -> true};
} else {
this.inclusionPredicates = (Predicate[]) builder.inclusionPredicates.stream().toArray(Predicate[]::new);
}
this.exclusionPredicates = (Predicate[]) builder.exclusionPredicates.stream().toArray(Predicate[]::new);
}
public boolean matches(String path) {
return Arrays.stream(exclusionPredicates).noneMatch(pattern -> pattern.test(path)) &&
Arrays.stream(inclusionPredicates).anyMatch(pattern -> pattern.test(path));
}
/**
* @since 6.0
*/
public Collection getInclusions() {
return inclusions;
}
/**
* @since 6.0
*/
public Collection getExclusions() {
return exclusions;
}
public String label() {
return "UrlPattern{" +
"inclusions=[" + convertPatternsToString(inclusions) + "]" +
", exclusions=[" + convertPatternsToString(exclusions) + "]" +
'}';
}
private static String convertPatternsToString(List input) {
StringBuilder output = new StringBuilder();
if (input.isEmpty()) {
return "";
}
if (input.size() == 1) {
return output.append(input.get(0)).toString();
}
return output.append(input.get(0)).append(", ...").toString();
}
/**
* @since 6.0
*/
public abstract static class Builder {
private static final String WILDCARD_CHAR = "*";
static final Collection STATIC_RESOURCES = List.of("*.css", "*.css.map", "*.ico", "*.png",
"*.jpg", "*.jpeg", "*.gif", "*.svg", "*.js", "*.js.map", "*.pdf", "/json/*", "*.woff2", "/static/*",
"/robots.txt", "/favicon.ico", "/apple-touch-icon*", "/mstile*");
private final Set inclusions = new LinkedHashSet<>();
private final Set exclusions = new LinkedHashSet<>();
private final Set> inclusionPredicates = new HashSet<>();
private final Set> exclusionPredicates = new HashSet<>();
Builder() {
}
/**
* @deprecated since 10.0. Products implementing the API should define this internally.
*/
@Deprecated(since = "10.0")
public static Collection staticResourcePatterns() {
return STATIC_RESOURCES;
}
/**
* Add inclusion patterns. Supported formats are:
*
* - path prefixed by / and ended by * or /*, for example "/api/foo/*", to match all paths "/api/foo" and "api/api/foo/something/else"
* - path prefixed by / and ended by .*, for example "/api/foo.*", to match exact path "/api/foo" with any suffix like "/api/foo.protobuf"
* - path prefixed by *, for example "*\/foo", to match all paths "/api/foo" and "something/else/foo"
* - path with leading slash and no wildcard, for example "/api/foo", to match exact path "/api/foo"
*
*/
public B includes(String... includePatterns) {
return includes(asList(includePatterns));
}
/**
* Add exclusion patterns. See format described in {@link #includes(String...)}
*/
public B includes(Collection includePatterns) {
this.inclusions.addAll(includePatterns);
this.inclusionPredicates.addAll(includePatterns.stream()
.filter(pattern -> !MATCH_ALL.equals(pattern))
.map(Builder::compile)
.collect(Collectors.toList()));
return (B) this;
}
public B excludes(String... excludePatterns) {
return excludes(asList(excludePatterns));
}
public B excludes(Collection excludePatterns) {
this.exclusions.addAll(excludePatterns);
this.exclusionPredicates.addAll(excludePatterns.stream()
.map(Builder::compile)
.collect(Collectors.toList()));
return (B) this;
}
public abstract T build();
private static Predicate compile(String pattern) {
int countStars = pattern.length() - pattern.replace(WILDCARD_CHAR, "").length();
if (countStars == 0) {
checkArgument(pattern.startsWith("/"), "URL pattern must start with slash '/': %s", pattern);
return url -> url.equals(pattern);
}
checkArgument(countStars == 1, "URL pattern accepts only zero or one wildcard character '*': %s", pattern);
if (pattern.charAt(0) == '/') {
checkArgument(pattern.endsWith(WILDCARD_CHAR), "URL pattern must end with wildcard character '*': %s", pattern);
if (pattern.endsWith("/*")) {
String path = pattern.substring(0, pattern.length() - "/*".length());
return url -> url.startsWith(path);
}
if (pattern.endsWith(".*")) {
String path = pattern.substring(0, pattern.length() - ".*".length());
return url -> substringBeforeLast(url, ".").equals(path);
}
String path = pattern.substring(0, pattern.length() - "*".length());
return url -> url.startsWith(path);
}
checkArgument(pattern.startsWith(WILDCARD_CHAR), "URL pattern must start with wildcard character '*': %s", pattern);
// remove the leading *
String path = pattern.substring(1);
return url -> url.endsWith(path);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy