Skip to content

Commit 80ee8d3

Browse files
PLUGINAPI-42 Reduce code duplication on UrlPattern class
1 parent 42ca0a6 commit 80ee8d3

File tree

3 files changed

+199
-291
lines changed

3 files changed

+199
-291
lines changed
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
/*
2+
* Sonar Plugin API
3+
* Copyright (C) 2009-2023 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
package org.sonar.api.web;
21+
22+
import java.util.ArrayList;
23+
import java.util.Arrays;
24+
import java.util.Collection;
25+
import java.util.HashSet;
26+
import java.util.LinkedHashSet;
27+
import java.util.List;
28+
import java.util.Set;
29+
import java.util.function.Predicate;
30+
import java.util.stream.Collectors;
31+
32+
import static java.util.Arrays.asList;
33+
import static java.util.Collections.unmodifiableList;
34+
import static org.apache.commons.lang.StringUtils.substringBeforeLast;
35+
import static org.sonar.api.utils.Preconditions.checkArgument;
36+
37+
/**
38+
* Logic of this class should be moved to URLPattern class after deprecation period.
39+
*/
40+
abstract class AbstractUrlPattern {
41+
42+
private static final String MATCH_ALL = "/*";
43+
44+
private final List<String> inclusions;
45+
private final List<String> exclusions;
46+
private final Predicate<String>[] inclusionPredicates;
47+
private final Predicate<String>[] exclusionPredicates;
48+
49+
AbstractUrlPattern(Builder builder) {
50+
this.inclusions = unmodifiableList(new ArrayList<>(builder.inclusions));
51+
this.exclusions = unmodifiableList(new ArrayList<>(builder.exclusions));
52+
if (builder.inclusionPredicates.isEmpty()) {
53+
// because Stream#anyMatch() returns false if stream is empty
54+
this.inclusionPredicates = new Predicate[]{s -> true};
55+
} else {
56+
this.inclusionPredicates = (Predicate<String>[]) builder.inclusionPredicates.stream().toArray(Predicate[]::new);
57+
}
58+
this.exclusionPredicates = (Predicate<String>[]) builder.exclusionPredicates.stream().toArray(Predicate[]::new);
59+
}
60+
61+
public boolean matches(String path) {
62+
return Arrays.stream(exclusionPredicates).noneMatch(pattern -> pattern.test(path)) &&
63+
Arrays.stream(inclusionPredicates).anyMatch(pattern -> pattern.test(path));
64+
}
65+
66+
/**
67+
* @since 6.0
68+
*/
69+
public Collection<String> getInclusions() {
70+
return inclusions;
71+
}
72+
73+
/**
74+
* @since 6.0
75+
*/
76+
public Collection<String> getExclusions() {
77+
return exclusions;
78+
}
79+
80+
public String label() {
81+
return "UrlPattern{" +
82+
"inclusions=[" + convertPatternsToString(inclusions) + "]" +
83+
", exclusions=[" + convertPatternsToString(exclusions) + "]" +
84+
'}';
85+
}
86+
87+
private static String convertPatternsToString(List<String> input) {
88+
StringBuilder output = new StringBuilder();
89+
if (input.isEmpty()) {
90+
return "";
91+
}
92+
if (input.size() == 1) {
93+
return output.append(input.get(0)).toString();
94+
}
95+
return output.append(input.get(0)).append(", ...").toString();
96+
}
97+
98+
/**
99+
* @since 6.0
100+
*/
101+
public abstract static class Builder<T extends AbstractUrlPattern, B extends Builder> {
102+
private static final String WILDCARD_CHAR = "*";
103+
static final Collection<String> STATIC_RESOURCES = List.of("*.css", "*.css.map", "*.ico", "*.png",
104+
"*.jpg", "*.jpeg", "*.gif", "*.svg", "*.js", "*.js.map", "*.pdf", "/json/*", "*.woff2", "/static/*",
105+
"/robots.txt", "/favicon.ico", "/apple-touch-icon*", "/mstile*");
106+
107+
private final Set<String> inclusions = new LinkedHashSet<>();
108+
private final Set<String> exclusions = new LinkedHashSet<>();
109+
private final Set<Predicate<String>> inclusionPredicates = new HashSet<>();
110+
private final Set<Predicate<String>> exclusionPredicates = new HashSet<>();
111+
112+
Builder() {
113+
}
114+
115+
public static Collection<String> staticResourcePatterns() {
116+
return STATIC_RESOURCES;
117+
}
118+
119+
/**
120+
* Add inclusion patterns. Supported formats are:
121+
* <ul>
122+
* <li>path prefixed by / and ended by * or /*, for example "/api/foo/*", to match all paths "/api/foo" and "api/api/foo/something/else"</li>
123+
* <li>path prefixed by / and ended by .*, for example "/api/foo.*", to match exact path "/api/foo" with any suffix like "/api/foo.protobuf"</li>
124+
* <li>path prefixed by *, for example "*\/foo", to match all paths "/api/foo" and "something/else/foo"</li>
125+
* <li>path with leading slash and no wildcard, for example "/api/foo", to match exact path "/api/foo"</li>
126+
* </ul>
127+
*/
128+
public B includes(String... includePatterns) {
129+
return includes(asList(includePatterns));
130+
}
131+
132+
/**
133+
* Add exclusion patterns. See format described in {@link #includes(String...)}
134+
*/
135+
public B includes(Collection<String> includePatterns) {
136+
this.inclusions.addAll(includePatterns);
137+
this.inclusionPredicates.addAll(includePatterns.stream()
138+
.filter(pattern -> !MATCH_ALL.equals(pattern))
139+
.map(Builder::compile)
140+
.collect(Collectors.toList()));
141+
return (B) this;
142+
}
143+
144+
public B excludes(String... excludePatterns) {
145+
return excludes(asList(excludePatterns));
146+
}
147+
148+
public B excludes(Collection<String> excludePatterns) {
149+
this.exclusions.addAll(excludePatterns);
150+
this.exclusionPredicates.addAll(excludePatterns.stream()
151+
.map(Builder::compile)
152+
.collect(Collectors.toList()));
153+
return (B) this;
154+
}
155+
156+
157+
public abstract T build();
158+
159+
private static Predicate<String> compile(String pattern) {
160+
int countStars = pattern.length() - pattern.replace(WILDCARD_CHAR, "").length();
161+
if (countStars == 0) {
162+
checkArgument(pattern.startsWith("/"), "URL pattern must start with slash '/': %s", pattern);
163+
return url -> url.equals(pattern);
164+
}
165+
checkArgument(countStars == 1, "URL pattern accepts only zero or one wildcard character '*': %s", pattern);
166+
if (pattern.charAt(0) == '/') {
167+
checkArgument(pattern.endsWith(WILDCARD_CHAR), "URL pattern must end with wildcard character '*': %s", pattern);
168+
if (pattern.endsWith("/*")) {
169+
String path = pattern.substring(0, pattern.length() - "/*".length());
170+
return url -> url.startsWith(path);
171+
}
172+
if (pattern.endsWith(".*")) {
173+
String path = pattern.substring(0, pattern.length() - ".*".length());
174+
return url -> substringBeforeLast(url, ".").equals(path);
175+
}
176+
String path = pattern.substring(0, pattern.length() - "*".length());
177+
return url -> url.startsWith(path);
178+
}
179+
checkArgument(pattern.startsWith(WILDCARD_CHAR), "URL pattern must start with wildcard character '*': %s", pattern);
180+
// remove the leading *
181+
String path = pattern.substring(1);
182+
return url -> url.endsWith(path);
183+
}
184+
}
185+
}

plugin-api/src/main/java/org/sonar/api/web/ServletFilter.java

Lines changed: 6 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -19,25 +19,12 @@
1919
*/
2020
package org.sonar.api.web;
2121

22-
import java.util.ArrayList;
23-
import java.util.Arrays;
24-
import java.util.Collection;
25-
import java.util.HashSet;
26-
import java.util.LinkedHashSet;
27-
import java.util.List;
28-
import java.util.Set;
29-
import java.util.function.Predicate;
30-
import java.util.stream.Collectors;
3122
import org.sonar.api.ExtensionPoint;
3223
import org.sonar.api.server.ServerSide;
3324

34-
import static java.util.Arrays.asList;
35-
import static java.util.Collections.unmodifiableList;
36-
import static org.apache.commons.lang.StringUtils.substringBeforeLast;
37-
import static org.sonar.api.utils.Preconditions.checkArgument;
38-
3925
/**
4026
* {@code @deprecated} since 9.16. Use {@link org.sonar.api.web.HttpFilter} instead.
27+
*
4128
* @since 3.1
4229
*/
4330
@ServerSide
@@ -52,62 +39,10 @@ public UrlPattern doGetPattern() {
5239
return UrlPattern.builder().build();
5340
}
5441

55-
public static final class UrlPattern {
56-
57-
private static final String MATCH_ALL = "/*";
58-
59-
private final List<String> inclusions;
60-
private final List<String> exclusions;
61-
private final Predicate<String>[] inclusionPredicates;
62-
private final Predicate<String>[] exclusionPredicates;
42+
public static final class UrlPattern extends AbstractUrlPattern {
6343

6444
private UrlPattern(Builder builder) {
65-
this.inclusions = unmodifiableList(new ArrayList<>(builder.inclusions));
66-
this.exclusions = unmodifiableList(new ArrayList<>(builder.exclusions));
67-
if (builder.inclusionPredicates.isEmpty()) {
68-
// because Stream#anyMatch() returns false if stream is empty
69-
this.inclusionPredicates = new Predicate[]{s -> true};
70-
} else {
71-
this.inclusionPredicates = builder.inclusionPredicates.stream().toArray(Predicate[]::new);
72-
}
73-
this.exclusionPredicates = builder.exclusionPredicates.stream().toArray(Predicate[]::new);
74-
}
75-
76-
public boolean matches(String path) {
77-
return !Arrays.stream(exclusionPredicates).anyMatch(pattern -> pattern.test(path)) &&
78-
Arrays.stream(inclusionPredicates).anyMatch(pattern -> pattern.test(path));
79-
}
80-
81-
/**
82-
* @since 6.0
83-
*/
84-
public Collection<String> getInclusions() {
85-
return inclusions;
86-
}
87-
88-
/**
89-
* @since 6.0
90-
*/
91-
public Collection<String> getExclusions() {
92-
return exclusions;
93-
}
94-
95-
public String label() {
96-
return "UrlPattern{" +
97-
"inclusions=[" + convertPatternsToString(inclusions) + "]" +
98-
", exclusions=[" + convertPatternsToString(exclusions) + "]" +
99-
'}';
100-
}
101-
102-
private static String convertPatternsToString(List<String> input) {
103-
StringBuilder output = new StringBuilder();
104-
if (input.isEmpty()) {
105-
return "";
106-
}
107-
if (input.size() == 1) {
108-
return output.append(input.get(0)).toString();
109-
}
110-
return output.append(input.get(0)).append(", ...").toString();
45+
super(builder);
11146
}
11247

11348
/**
@@ -127,90 +62,17 @@ public static Builder builder() {
12762
/**
12863
* @since 6.0
12964
*/
130-
public static class Builder {
131-
private static final String WILDCARD_CHAR = "*";
132-
private static final Collection<String> STATIC_RESOURCES = unmodifiableList(asList(
133-
"*.css", "*.css.map", "*.ico", "*.png", "*.jpg", "*.jpeg", "*.gif", "*.svg", "*.js", "*.js.map", "*.pdf", "/json/*", "*.woff2",
134-
"/static/*", "/robots.txt", "/favicon.ico", "/apple-touch-icon*", "/mstile*"));
65+
public static class Builder extends AbstractUrlPattern.Builder<UrlPattern, Builder> {
13566

136-
private final Set<String> inclusions = new LinkedHashSet<>();
137-
private final Set<String> exclusions = new LinkedHashSet<>();
138-
private final Set<Predicate<String>> inclusionPredicates = new HashSet<>();
139-
private final Set<Predicate<String>> exclusionPredicates = new HashSet<>();
14067

14168
private Builder() {
69+
super();
14270
}
14371

144-
public static Collection<String> staticResourcePatterns() {
145-
return STATIC_RESOURCES;
146-
}
147-
148-
/**
149-
* Add inclusion patterns. Supported formats are:
150-
* <ul>
151-
* <li>path prefixed by / and ended by * or /*, for example "/api/foo/*", to match all paths "/api/foo" and "api/api/foo/something/else"</li>
152-
* <li>path prefixed by / and ended by .*, for example "/api/foo.*", to match exact path "/api/foo" with any suffix like "/api/foo.protobuf"</li>
153-
* <li>path prefixed by *, for example "*\/foo", to match all paths "/api/foo" and "something/else/foo"</li>
154-
* <li>path with leading slash and no wildcard, for example "/api/foo", to match exact path "/api/foo"</li>
155-
* </ul>
156-
*/
157-
public Builder includes(String... includePatterns) {
158-
return includes(asList(includePatterns));
159-
}
160-
161-
/**
162-
* Add exclusion patterns. See format described in {@link #includes(String...)}
163-
*/
164-
public Builder includes(Collection<String> includePatterns) {
165-
this.inclusions.addAll(includePatterns);
166-
this.inclusionPredicates.addAll(includePatterns.stream()
167-
.filter(pattern -> !MATCH_ALL.equals(pattern))
168-
.map(Builder::compile)
169-
.collect(Collectors.toList()));
170-
return this;
171-
}
172-
173-
public Builder excludes(String... excludePatterns) {
174-
return excludes(asList(excludePatterns));
175-
}
176-
177-
public Builder excludes(Collection<String> excludePatterns) {
178-
this.exclusions.addAll(excludePatterns);
179-
this.exclusionPredicates.addAll(excludePatterns.stream()
180-
.map(Builder::compile)
181-
.collect(Collectors.toList()));
182-
return this;
183-
}
184-
72+
@Override
18573
public UrlPattern build() {
18674
return new UrlPattern(this);
18775
}
188-
189-
private static Predicate<String> compile(String pattern) {
190-
int countStars = pattern.length() - pattern.replace(WILDCARD_CHAR, "").length();
191-
if (countStars == 0) {
192-
checkArgument(pattern.startsWith("/"), "URL pattern must start with slash '/': %s", pattern);
193-
return url -> url.equals(pattern);
194-
}
195-
checkArgument(countStars == 1, "URL pattern accepts only zero or one wildcard character '*': %s", pattern);
196-
if (pattern.charAt(0) == '/') {
197-
checkArgument(pattern.endsWith(WILDCARD_CHAR), "URL pattern must end with wildcard character '*': %s", pattern);
198-
if (pattern.endsWith("/*")) {
199-
String path = pattern.substring(0, pattern.length() - "/*".length());
200-
return url -> url.startsWith(path);
201-
}
202-
if (pattern.endsWith(".*")) {
203-
String path = pattern.substring(0, pattern.length() - ".*".length());
204-
return url -> substringBeforeLast(url, ".").equals(path);
205-
}
206-
String path = pattern.substring(0, pattern.length() - "*".length());
207-
return url -> url.startsWith(path);
208-
}
209-
checkArgument(pattern.startsWith(WILDCARD_CHAR), "URL pattern must start with wildcard character '*': %s", pattern);
210-
// remove the leading *
211-
String path = pattern.substring(1);
212-
return url -> url.endsWith(path);
213-
}
21476
}
21577
}
21678
}

0 commit comments

Comments
 (0)