aboutsummaryrefslogtreecommitdiffstats
path: root/vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/FeedClientBuilderImpl.java
diff options
context:
space:
mode:
Diffstat (limited to 'vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/FeedClientBuilderImpl.java')
-rw-r--r--vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/FeedClientBuilderImpl.java244
1 files changed, 244 insertions, 0 deletions
diff --git a/vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/FeedClientBuilderImpl.java b/vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/FeedClientBuilderImpl.java
new file mode 100644
index 00000000000..7dafeb0b541
--- /dev/null
+++ b/vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/FeedClientBuilderImpl.java
@@ -0,0 +1,244 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package ai.vespa.feed.client.impl;
+
+import ai.vespa.feed.client.FeedClient;
+import ai.vespa.feed.client.FeedClientBuilder;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLContext;
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.net.URI;
+import java.nio.file.Path;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Supplier;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Builder for creating a {@link FeedClient} instance.
+ *
+ * @author bjorncs
+ * @author jonmv
+ */
+public class FeedClientBuilderImpl implements FeedClientBuilder {
+
+ static final FeedClient.RetryStrategy defaultRetryStrategy = new FeedClient.RetryStrategy() { };
+
+ List<URI> endpoints;
+ final Map<String, Supplier<String>> requestHeaders = new HashMap<>();
+ SSLContext sslContext;
+ HostnameVerifier hostnameVerifier;
+ int connectionsPerEndpoint = 4;
+ int maxStreamsPerConnection = 4096;
+ FeedClient.RetryStrategy retryStrategy = defaultRetryStrategy;
+ FeedClient.CircuitBreaker circuitBreaker = new GracePeriodCircuitBreaker(Duration.ofSeconds(10));
+ Path certificateFile;
+ Path privateKeyFile;
+ Path caCertificatesFile;
+ Collection<X509Certificate> certificate;
+ PrivateKey privateKey;
+ Collection<X509Certificate> caCertificates;
+ boolean benchmark = true;
+ boolean dryrun = false;
+
+
+
+ public FeedClientBuilderImpl() {
+ }
+
+ FeedClientBuilderImpl(List<URI> endpoints) {
+ this();
+ setEndpointUris(endpoints);
+ }
+
+ @Override
+ public FeedClientBuilder setEndpointUris(List<URI> endpoints) {
+ if (endpoints.isEmpty())
+ throw new IllegalArgumentException("At least one endpoint must be provided");
+
+ for (URI endpoint : endpoints)
+ requireNonNull(endpoint.getHost());
+ this.endpoints = new ArrayList<>(endpoints);
+ return this;
+ }
+
+ @Override
+ public FeedClientBuilderImpl setConnectionsPerEndpoint(int max) {
+ if (max < 1) throw new IllegalArgumentException("Max connections must be at least 1, but was " + max);
+ this.connectionsPerEndpoint = max;
+ return this;
+ }
+
+ @Override
+ public FeedClientBuilderImpl setMaxStreamPerConnection(int max) {
+ if (max < 1) throw new IllegalArgumentException("Max streams per connection must be at least 1, but was " + max);
+ this.maxStreamsPerConnection = max;
+ return this;
+ }
+
+ /** Sets {@link SSLContext} instance. */
+ @Override
+ public FeedClientBuilderImpl setSslContext(SSLContext context) {
+ this.sslContext = requireNonNull(context);
+ return this;
+ }
+
+ /** Sets {@link HostnameVerifier} instance (e.g for disabling default SSL hostname verification). */
+ @Override
+ public FeedClientBuilderImpl setHostnameVerifier(HostnameVerifier verifier) {
+ this.hostnameVerifier = requireNonNull(verifier);
+ return this;
+ }
+
+ /** Turns off benchmarking. Attempting to get {@link FeedClient#stats()} will result in an exception. */
+ @Override
+ public FeedClientBuilderImpl noBenchmarking() {
+ this.benchmark = false;
+ return this;
+ }
+
+ /** Adds HTTP request header to all client requests. */
+ @Override
+ public FeedClientBuilderImpl addRequestHeader(String name, String value) {
+ return addRequestHeader(name, () -> requireNonNull(value));
+ }
+
+ /**
+ * Adds HTTP request header to all client requests. Value {@link Supplier} is invoked for each HTTP request,
+ * i.e. value can be dynamically updated during a feed.
+ */
+ @Override
+ public FeedClientBuilderImpl addRequestHeader(String name, Supplier<String> valueSupplier) {
+ this.requestHeaders.put(requireNonNull(name), requireNonNull(valueSupplier));
+ return this;
+ }
+
+ /**
+ * Overrides default retry strategy.
+ * @see FeedClient.RetryStrategy
+ */
+ @Override
+ public FeedClientBuilderImpl setRetryStrategy(FeedClient.RetryStrategy strategy) {
+ this.retryStrategy = requireNonNull(strategy);
+ return this;
+ }
+
+ /**
+ * Overrides default circuit breaker.
+ * @see FeedClient.CircuitBreaker
+ */
+ @Override
+ public FeedClientBuilderImpl setCircuitBreaker(FeedClient.CircuitBreaker breaker) {
+ this.circuitBreaker = requireNonNull(breaker);
+ return this;
+ }
+
+ /** Sets path to client SSL certificate/key PEM files */
+ @Override
+ public FeedClientBuilderImpl setCertificate(Path certificatePemFile, Path privateKeyPemFile) {
+ this.certificateFile = certificatePemFile;
+ this.privateKeyFile = privateKeyPemFile;
+ return this;
+ }
+
+ /** Sets client SSL certificates/key */
+ @Override
+ public FeedClientBuilderImpl setCertificate(Collection<X509Certificate> certificate, PrivateKey privateKey) {
+ this.certificate = certificate;
+ this.privateKey = privateKey;
+ return this;
+ }
+
+ /** Sets client SSL certificate/key */
+ @Override
+ public FeedClientBuilderImpl setCertificate(X509Certificate certificate, PrivateKey privateKey) {
+ return setCertificate(Collections.singletonList(certificate), privateKey);
+ }
+
+ @Override
+ public FeedClientBuilderImpl setDryrun(boolean enabled) {
+ this.dryrun = enabled;
+ return this;
+ }
+
+ /**
+ * Overrides JVM default SSL truststore
+ * @param caCertificatesFile Path to PEM encoded file containing trusted certificates
+ */
+ @Override
+ public FeedClientBuilderImpl setCaCertificatesFile(Path caCertificatesFile) {
+ this.caCertificatesFile = caCertificatesFile;
+ return this;
+ }
+
+ /** Overrides JVM default SSL truststore */
+ @Override
+ public FeedClientBuilderImpl setCaCertificates(Collection<X509Certificate> caCertificates) {
+ this.caCertificates = caCertificates;
+ return this;
+ }
+
+ /** Constructs instance of {@link ai.vespa.feed.client.FeedClient} from builder configuration */
+ @Override
+ public FeedClient build() {
+ try {
+ validateConfiguration();
+ return new HttpFeedClient(this);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ SSLContext constructSslContext() throws IOException {
+ if (sslContext != null) return sslContext;
+ SslContextBuilder sslContextBuilder = new SslContextBuilder();
+ if (certificateFile != null && privateKeyFile != null) {
+ sslContextBuilder.withCertificateAndKey(certificateFile, privateKeyFile);
+ } else if (certificate != null && privateKey != null) {
+ sslContextBuilder.withCertificateAndKey(certificate, privateKey);
+ }
+ if (caCertificatesFile != null) {
+ sslContextBuilder.withCaCertificates(caCertificatesFile);
+ } else if (caCertificates != null) {
+ sslContextBuilder.withCaCertificates(caCertificates);
+ }
+ return sslContextBuilder.build();
+ }
+
+ private void validateConfiguration() {
+ if (endpoints == null) {
+ throw new IllegalArgumentException("At least one endpoint must be provided");
+ }
+ if (sslContext != null && (
+ certificateFile != null || caCertificatesFile != null || privateKeyFile != null ||
+ certificate != null || caCertificates != null || privateKey != null)) {
+ throw new IllegalArgumentException("Cannot set both SSLContext and certificate / CA certificates");
+ }
+ if (certificate != null && certificateFile != null) {
+ throw new IllegalArgumentException("Cannot set both certificate directly and as file");
+ }
+ if (privateKey != null && privateKeyFile != null) {
+ throw new IllegalArgumentException("Cannot set both private key directly and as file");
+ }
+ if (caCertificates != null && caCertificatesFile != null) {
+ throw new IllegalArgumentException("Cannot set both CA certificates directly and as file");
+ }
+ if (certificate != null && certificate.isEmpty()) {
+ throw new IllegalArgumentException("Certificate cannot be empty");
+ }
+ if (caCertificates != null && caCertificates.isEmpty()) {
+ throw new IllegalArgumentException("CA certificates cannot be empty");
+ }
+ }
+
+}