summaryrefslogtreecommitdiffstats
path: root/config-model
diff options
context:
space:
mode:
authorBjørn Christian Seime <bjorncs@verizonmedia.com>2020-07-20 11:50:29 +0200
committerBjørn Christian Seime <bjorncs@verizonmedia.com>2020-08-27 10:28:02 +0200
commit27c20c876938a664b96812cb7bac1764f615fbbb (patch)
tree4a09c6c5db22605ddd9096c19035558bb37b474d /config-model
parent35a8d53194a329e80abc52fa30d04c85a7ce96de (diff)
Introduce type for binding patterns
Diffstat (limited to 'config-model')
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/component/BindingPattern.java127
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/container/component/BindingPatternTest.java53
2 files changed, 180 insertions, 0 deletions
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/component/BindingPattern.java b/config-model/src/main/java/com/yahoo/vespa/model/container/component/BindingPattern.java
new file mode 100644
index 00000000000..835dca722ac
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/component/BindingPattern.java
@@ -0,0 +1,127 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.model.container.component;
+
+import java.util.Comparator;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * URI binding pattern used by filter and handler bindings.
+ *
+ * There are two types of binding; user generated and model generated bindings.
+ * - User generated bindings are bindings which are constructed from directly from 'binding' elements from services.xml
+ * - Model generated bindings are binding which are implicitly constructed by the model, e.g built-in handlers.
+ *
+ * @author bjorncs
+ */
+public class BindingPattern implements Comparable<BindingPattern> {
+
+ private static final Pattern BINDING_PATTERN =
+ Pattern.compile("([^:]+)://([^:/]+)(:((\\*)|([0-9]+)))?(/.*)", Pattern.UNICODE_CASE | Pattern.CANON_EQ);
+
+ private final String scheme;
+ private final String host;
+ private final String port;
+ private final String path;
+ private final boolean isUserGenerated;
+
+ private BindingPattern(
+ String scheme,
+ String host,
+ String port,
+ String path,
+ boolean isUserGenerated) {
+ this.scheme = Objects.requireNonNull(scheme, "Scheme in binding must be specified");
+ this.host = Objects.requireNonNull(host, "Host must be specified");
+ this.port = port;
+ this.path = validatePath(path);
+ this.isUserGenerated = isUserGenerated;
+ }
+
+ private static String validatePath(String path) {
+ Objects.requireNonNull(path, "Path must be specified");
+ if (!path.startsWith("/")) throw new IllegalArgumentException("Path has not '/' as prefix: " + path);
+ return path;
+ }
+
+ public static BindingPattern createUserGeneratedFromPattern(String pattern) {
+ return createFromBindingString(pattern, true);
+ }
+
+ public static BindingPattern createUserGeneratedFromHttpPath(String path) {
+ return new BindingPattern("http", "*", null, path, true);
+ }
+
+ public static BindingPattern createModelGeneratedFromPattern(String pattern) {
+ return createFromBindingString(pattern, false);
+ }
+
+ public static BindingPattern createModelGeneratedFromHttpPath(String path) {
+ return new BindingPattern("http", "*", null, path, false);
+ }
+
+ private static BindingPattern createFromBindingString(String binding, boolean isUserGenerated) {
+ Matcher matcher = BINDING_PATTERN.matcher(binding);
+ if (!matcher.matches()) throw new IllegalArgumentException("Invalid binding: " + binding);
+ String scheme = matcher.group(1);
+ String host = matcher.group(2);
+ String port = matcher.group(4);
+ String path = matcher.group(7);
+ return new BindingPattern(scheme, host, port, path, isUserGenerated);
+ }
+
+ public String scheme() { return scheme; }
+ public String host() { return host; }
+ public Optional<String> port() { return Optional.ofNullable(port); }
+ public String path() { return path; }
+ public boolean isUserGenerated() { return isUserGenerated; }
+
+ public String patternString() {
+ StringBuilder builder = new StringBuilder(scheme).append("://").append(host);
+ if (port != null) {
+ builder.append(':').append(port);
+ }
+ return builder.append(path).toString();
+ }
+
+ /** Compares the underlying pattern string for equality */
+ public boolean hasSamePattern(BindingPattern other) { return this.patternString().equals(other.patternString()); }
+
+ @Override
+ public String toString() {
+ return "BindingPattern{" +
+ "scheme='" + scheme + '\'' +
+ ", host='" + host + '\'' +
+ ", port='" + port + '\'' +
+ ", path='" + path + '\'' +
+ ", isUserGenerated=" + isUserGenerated +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ BindingPattern that = (BindingPattern) o;
+ return isUserGenerated == that.isUserGenerated &&
+ Objects.equals(scheme, that.scheme) &&
+ Objects.equals(host, that.host) &&
+ Objects.equals(port, that.port) &&
+ Objects.equals(path, that.path);
+ }
+
+ @Override public int hashCode() { return Objects.hash(scheme, host, port, path, isUserGenerated); }
+
+
+ @Override
+ public int compareTo(BindingPattern o) {
+ return Comparator.comparing(BindingPattern::scheme)
+ .thenComparing(BindingPattern::host)
+ .thenComparing(pattern -> pattern.port().orElse(null))
+ .thenComparing(BindingPattern::path)
+ .thenComparing(BindingPattern::isUserGenerated)
+ .compare(this, o);
+ }
+}
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/component/BindingPatternTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/component/BindingPatternTest.java
new file mode 100644
index 00000000000..069ef156913
--- /dev/null
+++ b/config-model/src/test/java/com/yahoo/vespa/model/container/component/BindingPatternTest.java
@@ -0,0 +1,53 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.model.container.component;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+/**
+ * @author bjorncs
+ */
+public class BindingPatternTest {
+
+ @Test
+ public void parses_valid_bindings_correctly() {
+ assertBindingParses("http://host:1234/path");
+ assertBindingParses("http://host/path");
+ assertBindingParses("http://host/");
+ assertBindingParses("*://*:*/*");
+ assertBindingParses("http://*/*");
+ assertBindingParses("https://*/my/path");
+ assertBindingParses("https://*/path/*");
+ assertBindingParses("https://host:*/path/*");
+ assertBindingParses("https://host:1234/*");
+ }
+
+ @Test
+ public void getters_returns_correct_components() {
+ {
+ BindingPattern pattern = BindingPattern.createModelGeneratedFromPattern("http://host:1234/path/*");
+ assertEquals("http", pattern.scheme());
+ assertEquals("host", pattern.host());
+ assertEquals("1234", pattern.port().get());
+ assertEquals("/path/*", pattern.path());
+ }
+ {
+ BindingPattern pattern = BindingPattern.createModelGeneratedFromPattern("https://*/path/v1/");
+ assertEquals("https", pattern.scheme());
+ assertEquals("*", pattern.host());
+ assertFalse(pattern.port().isPresent());
+ assertEquals("/path/v1/", pattern.path());
+ }
+ }
+
+ private static void assertBindingParses(String binding) {
+ BindingPattern pattern = BindingPattern.createModelGeneratedFromPattern(binding);
+ String stringRepresentation = pattern.patternString();
+ assertEquals(
+ "Expected string representation of parsed binding to match original binding string",
+ binding, stringRepresentation);
+ }
+
+} \ No newline at end of file