From 59cbc843c5bc1a244b73b10ebe92d18ce3977a24 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Fri, 13 Jun 2025 17:47:08 +0200 Subject: [PATCH 01/13] feat: implement TokenProvider for API key authorization Extended Rest/GrpcTransport to use custom authorization interceptors, if tokenProvider != null. Added Config.Builder for providing flexible options. Added connection helpers: .local() and .wcd() --- .../client6/v1/api/Authorization.java | 10 ++ .../io/weaviate/client6/v1/api/Config.java | 116 ++++++++++++------ .../client6/v1/api/WeaviateClient.java | 28 ++++- .../client6/v1/api/WeaviateClientAsync.java | 28 ++++- .../client6/v1/internal/TokenProvider.java | 13 ++ .../client6/v1/internal/TransportOptions.java | 29 +++++ .../internal/grpc/DefaultGrpcTransport.java | 38 +++--- .../v1/internal/grpc/GrpcChannelOptions.java | 24 ++-- .../internal/grpc/TokenCallCredentials.java | 33 +++++ .../rest/AuthorizationInterceptor.java | 29 +++++ .../internal/rest/DefaultRestTransport.java | 26 +++- .../internal/rest/RestTransportOptions.java | 26 ++++ .../v1/internal/rest/TransportOptions.java | 12 -- 13 files changed, 327 insertions(+), 85 deletions(-) create mode 100644 src/main/java/io/weaviate/client6/v1/api/Authorization.java create mode 100644 src/main/java/io/weaviate/client6/v1/internal/TokenProvider.java create mode 100644 src/main/java/io/weaviate/client6/v1/internal/TransportOptions.java create mode 100644 src/main/java/io/weaviate/client6/v1/internal/grpc/TokenCallCredentials.java create mode 100644 src/main/java/io/weaviate/client6/v1/internal/rest/AuthorizationInterceptor.java create mode 100644 src/main/java/io/weaviate/client6/v1/internal/rest/RestTransportOptions.java delete mode 100644 src/main/java/io/weaviate/client6/v1/internal/rest/TransportOptions.java diff --git a/src/main/java/io/weaviate/client6/v1/api/Authorization.java b/src/main/java/io/weaviate/client6/v1/api/Authorization.java new file mode 100644 index 000000000..9bcfc2ed1 --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/api/Authorization.java @@ -0,0 +1,10 @@ +package io.weaviate.client6.v1.api; + +import io.weaviate.client6.v1.internal.TokenProvider; +import io.weaviate.client6.v1.internal.TokenProvider.Token; + +public class Authorization { + public static TokenProvider apiKey(String apiKey) { + return TokenProvider.staticToken(new Token(apiKey)); + } +} diff --git a/src/main/java/io/weaviate/client6/v1/api/Config.java b/src/main/java/io/weaviate/client6/v1/api/Config.java index 2e7d9391d..7f4b1cab1 100644 --- a/src/main/java/io/weaviate/client6/v1/api/Config.java +++ b/src/main/java/io/weaviate/client6/v1/api/Config.java @@ -1,68 +1,108 @@ package io.weaviate.client6.v1.api; -import java.util.Collections; +import java.net.URI; +import java.util.HashMap; import java.util.Map; +import io.weaviate.client6.v1.internal.ObjectBuilder; +import io.weaviate.client6.v1.internal.TokenProvider; import io.weaviate.client6.v1.internal.grpc.GrpcChannelOptions; -import io.weaviate.client6.v1.internal.rest.TransportOptions; +import io.weaviate.client6.v1.internal.rest.RestTransportOptions; public class Config { private final String version = "v1"; private final String scheme; private final String httpHost; private final String grpcHost; - private final Map headers = Collections.emptyMap(); + private final Map headers; + private final TokenProvider tokenProvider; - public Config(String scheme, String httpHost, String grpcHost) { + public Config( + String scheme, + String httpHost, + String grpcHost, + Map headers, + TokenProvider tokenProvider) { this.scheme = scheme; this.httpHost = httpHost; this.grpcHost = grpcHost; + this.headers = headers; + this.tokenProvider = tokenProvider; } - public String baseUrl() { - return scheme + "://" + httpHost + "/" + version; + protected String baseUrl(String hostname) { + return scheme + "://" + hostname + "/" + version; } - public String grpcAddress() { - if (grpcHost.contains(":")) { - return grpcHost; - } - // FIXME: use secure port (433) if scheme == https - return String.format("%s:80", grpcHost); + public RestTransportOptions restTransportOptions() { + return new RestTransportOptions(baseUrl(httpHost), headers, tokenProvider); } - public TransportOptions rest() { - return new TransportOptions() { + public GrpcChannelOptions grpcTransportOptions() { + return new GrpcChannelOptions(baseUrl(grpcHost), headers, tokenProvider); + } - @Override - public String host() { - return baseUrl(); - } + public static class Builder implements ObjectBuilder { + // Required parameters + private final String scheme; + private final String httpHost; - @Override - public Map headers() { - return headers; - } + public Builder(String url) { + this(URI.create(url)); + } - }; - } + public Builder(URI url) { + this(url.getScheme(), url.getHost()); + } - public GrpcChannelOptions grpc() { - return new GrpcChannelOptions() { - @Override - public String host() { - return grpcAddress(); - } + public Builder(String scheme, String httpHost) { + this.scheme = scheme; + this.httpHost = httpHost; + } - @Override - public boolean useTls() { - return scheme.equals("https"); - } + private String grpcPrefix; + private String grpcHost; + private TokenProvider tokenProvider; + + private Map headers = new HashMap<>(); - @Override - public Map headers() { - return headers; + public Builder grpcPrefix(String prefix) { + this.grpcPrefix = prefix; + return this; + } + + public Builder grpcHost(String host) { + this.grpcHost = host; + return this; + } + + public Builder authorization(TokenProvider tokenProvider) { + this.tokenProvider = tokenProvider; + return this; + } + + public Builder setHeader(String key, String value) { + this.headers.put(key, value); + return this; + } + + public Builder setHeaders(Map headers) { + this.headers = Map.copyOf(headers); + return this; + } + + @Override + public Config build() { + if (grpcHost == null && grpcPrefix == null) { + throw new RuntimeException("grpcHost and grpcPrefix cannot both be null"); } - }; + + return new Config( + scheme, + httpHost, + grpcHost != null ? grpcHost : grpcPrefix + httpHost, + headers, + tokenProvider); + } } } diff --git a/src/main/java/io/weaviate/client6/v1/api/WeaviateClient.java b/src/main/java/io/weaviate/client6/v1/api/WeaviateClient.java index f2ceeff24..07ad58c41 100644 --- a/src/main/java/io/weaviate/client6/v1/api/WeaviateClient.java +++ b/src/main/java/io/weaviate/client6/v1/api/WeaviateClient.java @@ -2,8 +2,10 @@ import java.io.Closeable; import java.io.IOException; +import java.util.function.Function; import io.weaviate.client6.v1.api.collections.WeaviateCollectionsClient; +import io.weaviate.client6.v1.internal.ObjectBuilder; import io.weaviate.client6.v1.internal.grpc.DefaultGrpcTransport; import io.weaviate.client6.v1.internal.grpc.GrpcTransport; import io.weaviate.client6.v1.internal.rest.DefaultRestTransport; @@ -20,8 +22,8 @@ public class WeaviateClient implements Closeable { public WeaviateClient(Config config) { this.config = config; - this.restTransport = new DefaultRestTransport(config.rest()); - this.grpcTransport = new DefaultGrpcTransport(config.grpc()); + this.restTransport = new DefaultRestTransport(config.restTransportOptions()); + this.grpcTransport = new DefaultGrpcTransport(config.grpcTransportOptions()); this.collections = new WeaviateCollectionsClient(restTransport, grpcTransport); } @@ -30,6 +32,28 @@ public WeaviateClientAsync async() { return new WeaviateClientAsync(config); } + public static WeaviateClient local() { + return local(ObjectBuilder.identity()); + } + + public static WeaviateClient local(Function> fn) { + var config = new Config.Builder("http", "localhost:8080") + .grpcHost("locahost:50051"); + return new WeaviateClient(fn.apply(config).build()); + } + + public static WeaviateClient wcd(String clusterUrl, String apiKey) { + return wcd(clusterUrl, apiKey, ObjectBuilder.identity()); + } + + public static WeaviateClient wcd(String clusterUrl, String apiKey, + Function> fn) { + var config = new Config.Builder(clusterUrl) + .grpcPrefix("grpc-") + .authorization(Authorization.apiKey(apiKey)); + return new WeaviateClient(fn.apply(config).build()); + } + @Override public void close() throws IOException { this.restTransport.close(); diff --git a/src/main/java/io/weaviate/client6/v1/api/WeaviateClientAsync.java b/src/main/java/io/weaviate/client6/v1/api/WeaviateClientAsync.java index a33927292..efa7bfd15 100644 --- a/src/main/java/io/weaviate/client6/v1/api/WeaviateClientAsync.java +++ b/src/main/java/io/weaviate/client6/v1/api/WeaviateClientAsync.java @@ -2,8 +2,10 @@ import java.io.Closeable; import java.io.IOException; +import java.util.function.Function; import io.weaviate.client6.v1.api.collections.WeaviateCollectionsClientAsync; +import io.weaviate.client6.v1.internal.ObjectBuilder; import io.weaviate.client6.v1.internal.grpc.DefaultGrpcTransport; import io.weaviate.client6.v1.internal.grpc.GrpcTransport; import io.weaviate.client6.v1.internal.rest.DefaultRestTransport; @@ -16,12 +18,34 @@ public class WeaviateClientAsync implements Closeable { public final WeaviateCollectionsClientAsync collections; public WeaviateClientAsync(Config config) { - this.restTransport = new DefaultRestTransport(config.rest()); - this.grpcTransport = new DefaultGrpcTransport(config.grpc()); + this.restTransport = new DefaultRestTransport(config.restTransportOptions()); + this.grpcTransport = new DefaultGrpcTransport(config.grpcTransportOptions()); this.collections = new WeaviateCollectionsClientAsync(restTransport, grpcTransport); } + public static WeaviateClientAsync local() { + return local(ObjectBuilder.identity()); + } + + public static WeaviateClientAsync local(Function> fn) { + var config = new Config.Builder("http", "localhost:8080") + .grpcHost("locahost:50051"); + return new WeaviateClientAsync(fn.apply(config).build()); + } + + public static WeaviateClientAsync wcd(String clusterUrl, String apiKey) { + return wcd(clusterUrl, apiKey, ObjectBuilder.identity()); + } + + public static WeaviateClientAsync wcd(String clusterUrl, String apiKey, + Function> fn) { + var config = new Config.Builder(clusterUrl) + .grpcPrefix("grpc-") + .authorization(Authorization.apiKey(apiKey)); + return new WeaviateClientAsync(fn.apply(config).build()); + } + @Override public void close() throws IOException { this.restTransport.close(); diff --git a/src/main/java/io/weaviate/client6/v1/internal/TokenProvider.java b/src/main/java/io/weaviate/client6/v1/internal/TokenProvider.java new file mode 100644 index 000000000..af69a456b --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/internal/TokenProvider.java @@ -0,0 +1,13 @@ +package io.weaviate.client6.v1.internal; + +@FunctionalInterface +public interface TokenProvider { + Token getToken(); + + public record Token(String accessToken) { + } + + public static TokenProvider staticToken(Token token) { + return () -> token; + } +} diff --git a/src/main/java/io/weaviate/client6/v1/internal/TransportOptions.java b/src/main/java/io/weaviate/client6/v1/internal/TransportOptions.java new file mode 100644 index 000000000..7970ba0e8 --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/internal/TransportOptions.java @@ -0,0 +1,29 @@ +package io.weaviate.client6.v1.internal; + +import java.util.Map; + +public abstract class TransportOptions { + private final String host; + private final TokenProvider tokenProvider; + private final H headers; + + protected TransportOptions(String host, Map headers, TokenProvider tokenProvider) { + this.host = host; + this.tokenProvider = tokenProvider; + this.headers = buildHeaders(headers); + } + + protected abstract H buildHeaders(Map headers); + + public String host() { + return this.host; + } + + public TokenProvider tokenProvider() { + return this.tokenProvider; + } + + public H headers() { + return this.headers; + } +} diff --git a/src/main/java/io/weaviate/client6/v1/internal/grpc/DefaultGrpcTransport.java b/src/main/java/io/weaviate/client6/v1/internal/grpc/DefaultGrpcTransport.java index f071c9005..0ebb8e101 100644 --- a/src/main/java/io/weaviate/client6/v1/internal/grpc/DefaultGrpcTransport.java +++ b/src/main/java/io/weaviate/client6/v1/internal/grpc/DefaultGrpcTransport.java @@ -9,7 +9,6 @@ import io.grpc.ManagedChannel; import io.grpc.ManagedChannelBuilder; -import io.grpc.Metadata; import io.grpc.stub.MetadataUtils; import io.weaviate.client6.v1.internal.grpc.protocol.WeaviateGrpc; import io.weaviate.client6.v1.internal.grpc.protocol.WeaviateGrpc.WeaviateBlockingStub; @@ -21,13 +20,23 @@ public final class DefaultGrpcTransport implements GrpcTransport { private final WeaviateBlockingStub blockingStub; private final WeaviateFutureStub futureStub; - private static final int HTTP_PORT = 80; - private static final int HTTPS_PORT = 443; - public DefaultGrpcTransport(GrpcChannelOptions channelOptions) { this.channel = buildChannel(channelOptions); - this.blockingStub = WeaviateGrpc.newBlockingStub(channel); - this.futureStub = WeaviateGrpc.newFutureStub(channel); + + var blockingStub = WeaviateGrpc.newBlockingStub(channel) + .withInterceptors(MetadataUtils.newAttachHeadersInterceptor(channelOptions.headers())); + + var futureStub = WeaviateGrpc.newFutureStub(channel) + .withInterceptors(MetadataUtils.newAttachHeadersInterceptor(channelOptions.headers())); + + if (channelOptions.tokenProvider() != null) { + var credentials = new TokenCallCredentials(channelOptions.tokenProvider()); + blockingStub = blockingStub.withCallCredentials(credentials); + futureStub = futureStub.withCallCredentials(credentials); + } + + this.blockingStub = blockingStub; + this.futureStub = futureStub; } @Override @@ -70,24 +79,17 @@ public void onFailure(Throwable t) { return completable; } - private static ManagedChannel buildChannel(GrpcChannelOptions options) { - // var port = options.useTls() ? HTTPS_PORT : HTTP_PORT; - // var channel = ManagedChannelBuilder.forAddress(options.host(), port); - var channel = ManagedChannelBuilder.forTarget(options.host()); + private static ManagedChannel buildChannel(GrpcChannelOptions channelOptions) { + var host = channelOptions.host(); + var channel = ManagedChannelBuilder.forTarget(host); - if (options.useTls()) { + if (host.startsWith("https://")) { channel.useTransportSecurity(); } else { channel.usePlaintext(); } - var headers = new Metadata(); - for (final var header : options.headers().entrySet()) { - var key = Metadata.Key.of(header.getKey(), Metadata.ASCII_STRING_MARSHALLER); - headers.put(key, header.getValue()); - - } - channel.intercept(MetadataUtils.newAttachHeadersInterceptor(headers)); + channel.intercept(MetadataUtils.newAttachHeadersInterceptor(channelOptions.metadata())); return channel.build(); } diff --git a/src/main/java/io/weaviate/client6/v1/internal/grpc/GrpcChannelOptions.java b/src/main/java/io/weaviate/client6/v1/internal/grpc/GrpcChannelOptions.java index 517345844..42f575c57 100644 --- a/src/main/java/io/weaviate/client6/v1/internal/grpc/GrpcChannelOptions.java +++ b/src/main/java/io/weaviate/client6/v1/internal/grpc/GrpcChannelOptions.java @@ -1,15 +1,25 @@ package io.weaviate.client6.v1.internal.grpc; -import java.util.Collections; import java.util.Map; -// TODO: unify with rest.TransportOptions? -public interface GrpcChannelOptions { - String host(); +import io.grpc.Metadata; +import io.weaviate.client6.v1.internal.TokenProvider; +import io.weaviate.client6.v1.internal.TransportOptions; - default Map headers() { - return Collections.emptyMap(); +public class GrpcChannelOptions extends TransportOptions { + + public GrpcChannelOptions(String host, Map headers, TokenProvider tokenProvider) { + super(host, headers, tokenProvider); } - boolean useTls(); + @Override + protected final Metadata buildHeaders(Map headers) { + var metadata = new Metadata(); + for (var header : headers.entrySet()) { + metadata.put( + Metadata.Key.of(header.getKey(), Metadata.ASCII_STRING_MARSHALLER), + header.getValue()); + } + return metadata; + } } diff --git a/src/main/java/io/weaviate/client6/v1/internal/grpc/TokenCallCredentials.java b/src/main/java/io/weaviate/client6/v1/internal/grpc/TokenCallCredentials.java new file mode 100644 index 000000000..c24a9093a --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/internal/grpc/TokenCallCredentials.java @@ -0,0 +1,33 @@ +package io.weaviate.client6.v1.internal.grpc; + +import java.util.concurrent.Executor; + +import io.grpc.CallCredentials; +import io.grpc.Metadata; +import io.grpc.Status; +import io.weaviate.client6.v1.internal.TokenProvider; + +class TokenCallCredentials extends CallCredentials { + private static final Metadata.Key AUTHORIZATION = Metadata.Key.of("Authorization", + Metadata.ASCII_STRING_MARSHALLER); + + private final TokenProvider tokenProvider; + + TokenCallCredentials(TokenProvider tokenProvider) { + this.tokenProvider = tokenProvider; + } + + @Override + public void applyRequestMetadata(RequestInfo requestInfo, Executor executor, MetadataApplier metadataApplier) { + executor.execute(() -> { + try { + var headers = new Metadata(); + var token = tokenProvider.getToken().accessToken(); + headers.put(AUTHORIZATION, "Bearer " + token); + metadataApplier.apply(headers); + } catch (Exception e) { + metadataApplier.fail(Status.UNAUTHENTICATED.withCause(e)); + } + }); + } +} diff --git a/src/main/java/io/weaviate/client6/v1/internal/rest/AuthorizationInterceptor.java b/src/main/java/io/weaviate/client6/v1/internal/rest/AuthorizationInterceptor.java new file mode 100644 index 000000000..9fe109d23 --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/internal/rest/AuthorizationInterceptor.java @@ -0,0 +1,29 @@ +package io.weaviate.client6.v1.internal.rest; + +import java.io.IOException; + +import org.apache.hc.core5.http.EntityDetails; +import org.apache.hc.core5.http.HttpException; +import org.apache.hc.core5.http.HttpRequest; +import org.apache.hc.core5.http.HttpRequestInterceptor; +import org.apache.hc.core5.http.message.BasicHeader; +import org.apache.hc.core5.http.protocol.HttpContext; + +import io.weaviate.client6.v1.internal.TokenProvider; + +class AuthorizationInterceptor implements HttpRequestInterceptor { + private static final String AUTHORIZATION = "Authorization"; + + private final TokenProvider tokenProvider; + + AuthorizationInterceptor(TokenProvider tokenProvider) { + this.tokenProvider = tokenProvider; + } + + @Override + public void process(HttpRequest request, EntityDetails entity, HttpContext context) + throws HttpException, IOException { + var token = tokenProvider.getToken().accessToken(); + request.addHeader(new BasicHeader(AUTHORIZATION, "Bearer " + token)); + } +} diff --git a/src/main/java/io/weaviate/client6/v1/internal/rest/DefaultRestTransport.java b/src/main/java/io/weaviate/client6/v1/internal/rest/DefaultRestTransport.java index 470df5e89..3b75860fd 100644 --- a/src/main/java/io/weaviate/client6/v1/internal/rest/DefaultRestTransport.java +++ b/src/main/java/io/weaviate/client6/v1/internal/rest/DefaultRestTransport.java @@ -22,15 +22,29 @@ public class DefaultRestTransport implements RestTransport { private final CloseableHttpClient httpClient; private final CloseableHttpAsyncClient httpClientAsync; - private final TransportOptions transportOptions; + private final RestTransportOptions transportOptions; + // TODO: retire private static final Gson gson = new GsonBuilder().create(); - public DefaultRestTransport(TransportOptions options) { - this.transportOptions = options; - this.httpClient = HttpClients.createDefault(); - this.httpClientAsync = HttpAsyncClients.createDefault(); - httpClientAsync.start(); + public DefaultRestTransport(RestTransportOptions transportOptions) { + this.transportOptions = transportOptions; + + // TODO: doesn't make sense to spin up both? + var httpClient = HttpClients.custom() + .setDefaultHeaders(transportOptions.headers()); + var httpClientAsync = HttpAsyncClients.custom() + .setDefaultHeaders(transportOptions.headers()); + + if (transportOptions.tokenProvider() != null) { + var interceptor = new AuthorizationInterceptor(transportOptions.tokenProvider()); + httpClient.addRequestInterceptorFirst(interceptor); + httpClientAsync.addRequestInterceptorFirst(interceptor); + } + + this.httpClient = httpClient.build(); + this.httpClientAsync = httpClientAsync.build(); + this.httpClientAsync.start(); } @Override diff --git a/src/main/java/io/weaviate/client6/v1/internal/rest/RestTransportOptions.java b/src/main/java/io/weaviate/client6/v1/internal/rest/RestTransportOptions.java new file mode 100644 index 000000000..616dd2ac1 --- /dev/null +++ b/src/main/java/io/weaviate/client6/v1/internal/rest/RestTransportOptions.java @@ -0,0 +1,26 @@ +package io.weaviate.client6.v1.internal.rest; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; + +import org.apache.hc.core5.http.message.BasicHeader; + +import io.weaviate.client6.v1.internal.TokenProvider; +import io.weaviate.client6.v1.internal.TransportOptions; + +public final class RestTransportOptions extends TransportOptions> { + + public RestTransportOptions(String host, Map headers, TokenProvider tokenProvider) { + super(host, headers, tokenProvider); + } + + @Override + protected final Collection buildHeaders(Map headers) { + var basicHeaders = new HashSet(); + for (var header : headers.entrySet()) { + basicHeaders.add(new BasicHeader(header.getKey(), header.getValue())); + } + return basicHeaders; + } +} diff --git a/src/main/java/io/weaviate/client6/v1/internal/rest/TransportOptions.java b/src/main/java/io/weaviate/client6/v1/internal/rest/TransportOptions.java deleted file mode 100644 index 9ddb3fa70..000000000 --- a/src/main/java/io/weaviate/client6/v1/internal/rest/TransportOptions.java +++ /dev/null @@ -1,12 +0,0 @@ -package io.weaviate.client6.v1.internal.rest; - -import java.util.Collections; -import java.util.Map; - -public interface TransportOptions { - String host(); - - default Map headers() { - return Collections.emptyMap(); - } -} From 10252e44e4cedde03a51f338994329920e90c6a7 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Fri, 13 Jun 2025 18:00:43 +0200 Subject: [PATCH 02/13] ci: bump httpclient5 version to patch high-severity vulnerability https://eu.app.orcasecurity.io/shift-left/file_system_vulnerabilities/scan-log/7f0e5f45-b347-47ec-a566-02a079bd24d2 --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index a5d5c559a..eb21a924e 100644 --- a/pom.xml +++ b/pom.xml @@ -65,7 +65,7 @@ 17 1.18.36 2.12.1 - 5.4.2 + 5.4.3 3.17.0 5.12.0 1.20.5 From 853af8bda8241b650018d80512c974013a5b0bf1 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Fri, 13 Jun 2025 18:06:58 +0200 Subject: [PATCH 03/13] test: fix container client setup --- src/it/java/io/weaviate/containers/Weaviate.java | 1 - .../java/io/weaviate/client6/v1/api/Config.java | 13 +++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/it/java/io/weaviate/containers/Weaviate.java b/src/it/java/io/weaviate/containers/Weaviate.java index c70342fd1..dfaaabafe 100644 --- a/src/it/java/io/weaviate/containers/Weaviate.java +++ b/src/it/java/io/weaviate/containers/Weaviate.java @@ -46,7 +46,6 @@ public static Weaviate.Builder custom() { public static class Builder { private String versionTag; private Set enableModules = new HashSet<>(); - private String defaultVectorizerModule; private boolean telemetry; private Map environment = new HashMap<>(); diff --git a/src/main/java/io/weaviate/client6/v1/api/Config.java b/src/main/java/io/weaviate/client6/v1/api/Config.java index 7f4b1cab1..80859de62 100644 --- a/src/main/java/io/weaviate/client6/v1/api/Config.java +++ b/src/main/java/io/weaviate/client6/v1/api/Config.java @@ -3,6 +3,7 @@ import java.net.URI; import java.util.HashMap; import java.util.Map; +import java.util.function.Function; import io.weaviate.client6.v1.internal.ObjectBuilder; import io.weaviate.client6.v1.internal.TokenProvider; @@ -30,6 +31,18 @@ public Config( this.tokenProvider = tokenProvider; } + public Config(String scheme, String httpHost, String grpcHost) { + this.scheme = scheme; + this.httpHost = httpHost; + this.grpcHost = grpcHost; + this.headers = new HashMap<>(); + this.tokenProvider = null; + } + + public static Config of(String scheme, String httpHost, Function> fn) { + return fn.apply(new Builder(scheme, httpHost)).build(); + } + protected String baseUrl(String hostname) { return scheme + "://" + hostname + "/" + version; } From 62a333bd8f2420249453a8dde97c9476bb6eb71f Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Fri, 13 Jun 2025 18:10:44 +0200 Subject: [PATCH 04/13] fix: fix invalid method invocation --- .../weaviate/client6/v1/internal/grpc/DefaultGrpcTransport.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/weaviate/client6/v1/internal/grpc/DefaultGrpcTransport.java b/src/main/java/io/weaviate/client6/v1/internal/grpc/DefaultGrpcTransport.java index 0ebb8e101..43646d3b5 100644 --- a/src/main/java/io/weaviate/client6/v1/internal/grpc/DefaultGrpcTransport.java +++ b/src/main/java/io/weaviate/client6/v1/internal/grpc/DefaultGrpcTransport.java @@ -89,7 +89,7 @@ private static ManagedChannel buildChannel(GrpcChannelOptions channelOptions) { channel.usePlaintext(); } - channel.intercept(MetadataUtils.newAttachHeadersInterceptor(channelOptions.metadata())); + channel.intercept(MetadataUtils.newAttachHeadersInterceptor(channelOptions.headers())); return channel.build(); } From 9c05a8a7885c817560d05bec3d60a80268cfd8f1 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Sun, 15 Jun 2025 15:26:56 +0200 Subject: [PATCH 05/13] fix: pass grpc address without the http/https prefix --- src/it/java/io/weaviate/containers/Weaviate.java | 6 +++++- src/main/java/io/weaviate/client6/v1/api/Config.java | 6 +++++- .../io/weaviate/client6/v1/internal/TransportOptions.java | 6 ++---- .../client6/v1/internal/grpc/GrpcChannelOptions.java | 5 ++--- .../client6/v1/internal/rest/RestTransportOptions.java | 5 ++--- 5 files changed, 16 insertions(+), 12 deletions(-) diff --git a/src/it/java/io/weaviate/containers/Weaviate.java b/src/it/java/io/weaviate/containers/Weaviate.java index dfaaabafe..bb54b6b36 100644 --- a/src/it/java/io/weaviate/containers/Weaviate.java +++ b/src/it/java/io/weaviate/containers/Weaviate.java @@ -30,7 +30,11 @@ public WeaviateClient getClient() { } if (clientInstance == null) { var config = new Config("http", getHttpHostAddress(), getGrpcHostAddress()); - clientInstance = new WeaviateClient(config); + try { + clientInstance = new WeaviateClient(config); + } catch (Exception e) { + throw new RuntimeException("create WeaviateClient for Weaviate container", e); + } } return clientInstance; } diff --git a/src/main/java/io/weaviate/client6/v1/api/Config.java b/src/main/java/io/weaviate/client6/v1/api/Config.java index 80859de62..fd12d3f51 100644 --- a/src/main/java/io/weaviate/client6/v1/api/Config.java +++ b/src/main/java/io/weaviate/client6/v1/api/Config.java @@ -52,7 +52,11 @@ public RestTransportOptions restTransportOptions() { } public GrpcChannelOptions grpcTransportOptions() { - return new GrpcChannelOptions(baseUrl(grpcHost), headers, tokenProvider); + return new GrpcChannelOptions( + grpcHost.contains(":") + ? grpcHost + : grpcHost + (scheme == "https" ? ":433" : ":80"), + headers, tokenProvider); } public static class Builder implements ObjectBuilder { diff --git a/src/main/java/io/weaviate/client6/v1/internal/TransportOptions.java b/src/main/java/io/weaviate/client6/v1/internal/TransportOptions.java index 7970ba0e8..285e910dd 100644 --- a/src/main/java/io/weaviate/client6/v1/internal/TransportOptions.java +++ b/src/main/java/io/weaviate/client6/v1/internal/TransportOptions.java @@ -7,14 +7,12 @@ public abstract class TransportOptions { private final TokenProvider tokenProvider; private final H headers; - protected TransportOptions(String host, Map headers, TokenProvider tokenProvider) { + protected TransportOptions(String host, H headers, TokenProvider tokenProvider) { this.host = host; this.tokenProvider = tokenProvider; - this.headers = buildHeaders(headers); + this.headers = headers; } - protected abstract H buildHeaders(Map headers); - public String host() { return this.host; } diff --git a/src/main/java/io/weaviate/client6/v1/internal/grpc/GrpcChannelOptions.java b/src/main/java/io/weaviate/client6/v1/internal/grpc/GrpcChannelOptions.java index 42f575c57..dce50afad 100644 --- a/src/main/java/io/weaviate/client6/v1/internal/grpc/GrpcChannelOptions.java +++ b/src/main/java/io/weaviate/client6/v1/internal/grpc/GrpcChannelOptions.java @@ -9,11 +9,10 @@ public class GrpcChannelOptions extends TransportOptions { public GrpcChannelOptions(String host, Map headers, TokenProvider tokenProvider) { - super(host, headers, tokenProvider); + super(host, buildMetadata(headers), tokenProvider); } - @Override - protected final Metadata buildHeaders(Map headers) { + private static final Metadata buildMetadata(Map headers) { var metadata = new Metadata(); for (var header : headers.entrySet()) { metadata.put( diff --git a/src/main/java/io/weaviate/client6/v1/internal/rest/RestTransportOptions.java b/src/main/java/io/weaviate/client6/v1/internal/rest/RestTransportOptions.java index 616dd2ac1..6a621e3aa 100644 --- a/src/main/java/io/weaviate/client6/v1/internal/rest/RestTransportOptions.java +++ b/src/main/java/io/weaviate/client6/v1/internal/rest/RestTransportOptions.java @@ -12,11 +12,10 @@ public final class RestTransportOptions extends TransportOptions> { public RestTransportOptions(String host, Map headers, TokenProvider tokenProvider) { - super(host, headers, tokenProvider); + super(host, buildHeaders(headers), tokenProvider); } - @Override - protected final Collection buildHeaders(Map headers) { + private static final Collection buildHeaders(Map headers) { var basicHeaders = new HashSet(); for (var header : headers.entrySet()) { basicHeaders.add(new BasicHeader(header.getKey(), header.getValue())); From 0cda9dc6a4424375ad3eedfdcea2eb000a837048 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Sun, 15 Jun 2025 18:24:08 +0200 Subject: [PATCH 06/13] refactor: introduce dedicated builders for local and wcd connections --- .../java/io/weaviate/containers/Weaviate.java | 8 +- .../io/weaviate/client6/v1/api/Config.java | 175 ++++++++++-------- .../client6/v1/api/WeaviateClient.java | 11 +- .../client6/v1/api/WeaviateClientAsync.java | 13 +- .../client6/v1/internal/TransportOptions.java | 20 +- .../internal/grpc/DefaultGrpcTransport.java | 21 +-- .../v1/internal/grpc/GrpcChannelOptions.java | 6 +- .../internal/rest/DefaultRestTransport.java | 4 +- .../internal/rest/RestTransportOptions.java | 10 +- 9 files changed, 154 insertions(+), 114 deletions(-) diff --git a/src/it/java/io/weaviate/containers/Weaviate.java b/src/it/java/io/weaviate/containers/Weaviate.java index bb54b6b36..bcc1ba7d4 100644 --- a/src/it/java/io/weaviate/containers/Weaviate.java +++ b/src/it/java/io/weaviate/containers/Weaviate.java @@ -9,7 +9,6 @@ import org.testcontainers.weaviate.WeaviateContainer; -import io.weaviate.client6.v1.api.Config; import io.weaviate.client6.v1.api.WeaviateClient; public class Weaviate extends WeaviateContainer { @@ -29,9 +28,12 @@ public WeaviateClient getClient() { start(); } if (clientInstance == null) { - var config = new Config("http", getHttpHostAddress(), getGrpcHostAddress()); try { - clientInstance = new WeaviateClient(config); + clientInstance = WeaviateClient.local( + conn -> conn + .host(getHost()) + .httpPort(getMappedPort(8080)) + .grpcPort(getMappedPort(50051))); } catch (Exception e) { throw new RuntimeException("create WeaviateClient for Weaviate container", e); } diff --git a/src/main/java/io/weaviate/client6/v1/api/Config.java b/src/main/java/io/weaviate/client6/v1/api/Config.java index fd12d3f51..2244fbbab 100644 --- a/src/main/java/io/weaviate/client6/v1/api/Config.java +++ b/src/main/java/io/weaviate/client6/v1/api/Config.java @@ -10,116 +10,141 @@ import io.weaviate.client6.v1.internal.grpc.GrpcChannelOptions; import io.weaviate.client6.v1.internal.rest.RestTransportOptions; -public class Config { - private final String version = "v1"; - private final String scheme; - private final String httpHost; - private final String grpcHost; - private final Map headers; - private final TokenProvider tokenProvider; - - public Config( - String scheme, - String httpHost, - String grpcHost, - Map headers, - TokenProvider tokenProvider) { - this.scheme = scheme; - this.httpHost = httpHost; - this.grpcHost = grpcHost; - this.headers = headers; - this.tokenProvider = tokenProvider; +public record Config( + String scheme, + String httpHost, + int httpPort, + String grpcHost, + int grpcPort, + Map headers, + TokenProvider tokenProvider) { + + public static Config of(String scheme, Function> fn) { + return fn.apply(new Custom(scheme)).build(); } - public Config(String scheme, String httpHost, String grpcHost) { - this.scheme = scheme; - this.httpHost = httpHost; - this.grpcHost = grpcHost; - this.headers = new HashMap<>(); - this.tokenProvider = null; - } - - public static Config of(String scheme, String httpHost, Function> fn) { - return fn.apply(new Builder(scheme, httpHost)).build(); - } - - protected String baseUrl(String hostname) { - return scheme + "://" + hostname + "/" + version; + public Config(Builder builder) { + this( + builder.scheme, + builder.httpHost, + builder.httpPort, + builder.grpcHost, + builder.grpcPort, + builder.headers, + builder.tokenProvider); } public RestTransportOptions restTransportOptions() { - return new RestTransportOptions(baseUrl(httpHost), headers, tokenProvider); + return new RestTransportOptions(scheme, httpHost, httpPort, headers, tokenProvider); } public GrpcChannelOptions grpcTransportOptions() { - return new GrpcChannelOptions( - grpcHost.contains(":") - ? grpcHost - : grpcHost + (scheme == "https" ? ":433" : ":80"), - headers, tokenProvider); + return new GrpcChannelOptions(scheme, grpcHost, grpcPort, headers, tokenProvider); } - public static class Builder implements ObjectBuilder { - // Required parameters - private final String scheme; - private final String httpHost; + abstract static class Builder> implements ObjectBuilder { + // Required parameters; + protected final String scheme; + + protected String httpHost; + protected int httpPort; + protected String grpcHost; + protected int grpcPort; + protected TokenProvider tokenProvider; + protected Map headers = new HashMap<>(); - public Builder(String url) { - this(URI.create(url)); + protected Builder(String scheme) { + this.scheme = scheme; } - public Builder(URI url) { - this(url.getScheme(), url.getHost()); + @SuppressWarnings("unchecked") + public SELF setHeader(String key, String value) { + this.headers.put(key, value); + return (SELF) this; } - public Builder(String scheme, String httpHost) { - this.scheme = scheme; - this.httpHost = httpHost; + @SuppressWarnings("unchecked") + public SELF setHeaders(Map headers) { + this.headers = Map.copyOf(headers); + return (SELF) this; } - private String grpcPrefix; - private String grpcHost; - private TokenProvider tokenProvider; + @Override + public Config build() { + return new Config(this); + } + } - private Map headers = new HashMap<>(); + public static class Local extends Builder { + public Local() { + super("http"); + host("localhost"); + httpPort(8080); + grpcPort(50051); + } - public Builder grpcPrefix(String prefix) { - this.grpcPrefix = prefix; + public Local host(String host) { + this.httpHost = host; + this.grpcHost = host; return this; } - public Builder grpcHost(String host) { - this.grpcHost = host; + public Local httpPort(int port) { + this.httpPort = port; return this; } - public Builder authorization(TokenProvider tokenProvider) { + public Local grpcPort(int port) { + this.grpcPort = port; + return this; + } + } + + public static class WeaviateCloud extends Builder { + public WeaviateCloud(String clusterUrl, TokenProvider tokenProvider) { + this(URI.create(clusterUrl), tokenProvider); + } + + public WeaviateCloud(URI clusterUrl, TokenProvider tokenProvider) { + super("https"); + this.httpHost = clusterUrl.getHost(); + this.httpPort = 443; + this.grpcHost = "grpc-" + httpPort; + this.grpcPort = 443; this.tokenProvider = tokenProvider; + } + } + + public static class Custom extends Builder { + public Custom(String scheme) { + super(scheme); + this.httpPort = scheme == "https" ? 443 : 80; + this.grpcPort = scheme == "https" ? 443 : 80; + } + + public Custom httpHost(String host) { + this.httpHost = host; return this; } - public Builder setHeader(String key, String value) { - this.headers.put(key, value); + public Custom httpPort(int port) { + this.grpcPort = port; return this; } - public Builder setHeaders(Map headers) { - this.headers = Map.copyOf(headers); + public Custom grpcHost(String host) { + this.grpcHost = host; return this; } - @Override - public Config build() { - if (grpcHost == null && grpcPrefix == null) { - throw new RuntimeException("grpcHost and grpcPrefix cannot both be null"); - } - - return new Config( - scheme, - httpHost, - grpcHost != null ? grpcHost : grpcPrefix + httpHost, - headers, - tokenProvider); + public Custom grpcPort(int port) { + this.grpcPort = port; + return this; + } + + public Custom authorization(TokenProvider tokenProvider) { + this.tokenProvider = tokenProvider; + return this; } } } diff --git a/src/main/java/io/weaviate/client6/v1/api/WeaviateClient.java b/src/main/java/io/weaviate/client6/v1/api/WeaviateClient.java index 07ad58c41..7f41fbffc 100644 --- a/src/main/java/io/weaviate/client6/v1/api/WeaviateClient.java +++ b/src/main/java/io/weaviate/client6/v1/api/WeaviateClient.java @@ -36,9 +36,8 @@ public static WeaviateClient local() { return local(ObjectBuilder.identity()); } - public static WeaviateClient local(Function> fn) { - var config = new Config.Builder("http", "localhost:8080") - .grpcHost("locahost:50051"); + public static WeaviateClient local(Function> fn) { + var config = new Config.Local(); return new WeaviateClient(fn.apply(config).build()); } @@ -47,10 +46,8 @@ public static WeaviateClient wcd(String clusterUrl, String apiKey) { } public static WeaviateClient wcd(String clusterUrl, String apiKey, - Function> fn) { - var config = new Config.Builder(clusterUrl) - .grpcPrefix("grpc-") - .authorization(Authorization.apiKey(apiKey)); + Function> fn) { + var config = new Config.WeaviateCloud(clusterUrl, Authorization.apiKey(apiKey)); return new WeaviateClient(fn.apply(config).build()); } diff --git a/src/main/java/io/weaviate/client6/v1/api/WeaviateClientAsync.java b/src/main/java/io/weaviate/client6/v1/api/WeaviateClientAsync.java index efa7bfd15..498d2fd74 100644 --- a/src/main/java/io/weaviate/client6/v1/api/WeaviateClientAsync.java +++ b/src/main/java/io/weaviate/client6/v1/api/WeaviateClientAsync.java @@ -17,7 +17,7 @@ public class WeaviateClientAsync implements Closeable { public final WeaviateCollectionsClientAsync collections; - public WeaviateClientAsync(Config config) { + public WeaviateClientAsync(ConnectionParams config) { this.restTransport = new DefaultRestTransport(config.restTransportOptions()); this.grpcTransport = new DefaultGrpcTransport(config.grpcTransportOptions()); @@ -28,9 +28,8 @@ public static WeaviateClientAsync local() { return local(ObjectBuilder.identity()); } - public static WeaviateClientAsync local(Function> fn) { - var config = new Config.Builder("http", "localhost:8080") - .grpcHost("locahost:50051"); + public static WeaviateClientAsync local(Function> fn) { + var config = new ConnectionParams.Local(); return new WeaviateClientAsync(fn.apply(config).build()); } @@ -39,10 +38,8 @@ public static WeaviateClientAsync wcd(String clusterUrl, String apiKey) { } public static WeaviateClientAsync wcd(String clusterUrl, String apiKey, - Function> fn) { - var config = new Config.Builder(clusterUrl) - .grpcPrefix("grpc-") - .authorization(Authorization.apiKey(apiKey)); + Function> fn) { + var config = new ConnectionParams.WeaviateCloud(clusterUrl, Authorization.apiKey(apiKey)); return new WeaviateClientAsync(fn.apply(config).build()); } diff --git a/src/main/java/io/weaviate/client6/v1/internal/TransportOptions.java b/src/main/java/io/weaviate/client6/v1/internal/TransportOptions.java index 285e910dd..03ee045b7 100644 --- a/src/main/java/io/weaviate/client6/v1/internal/TransportOptions.java +++ b/src/main/java/io/weaviate/client6/v1/internal/TransportOptions.java @@ -1,22 +1,36 @@ package io.weaviate.client6.v1.internal; -import java.util.Map; - public abstract class TransportOptions { + private final String scheme; private final String host; + private final int port; private final TokenProvider tokenProvider; private final H headers; - protected TransportOptions(String host, H headers, TokenProvider tokenProvider) { + protected TransportOptions(String scheme, String host, int port, H headers, TokenProvider tokenProvider) { + this.scheme = scheme; this.host = host; + this.port = port; this.tokenProvider = tokenProvider; this.headers = headers; } + public boolean isSecure() { + return scheme == "https"; + } + + public String scheme() { + return this.scheme; + } + public String host() { return this.host; } + public int port() { + return this.port; + } + public TokenProvider tokenProvider() { return this.tokenProvider; } diff --git a/src/main/java/io/weaviate/client6/v1/internal/grpc/DefaultGrpcTransport.java b/src/main/java/io/weaviate/client6/v1/internal/grpc/DefaultGrpcTransport.java index 43646d3b5..82aea9598 100644 --- a/src/main/java/io/weaviate/client6/v1/internal/grpc/DefaultGrpcTransport.java +++ b/src/main/java/io/weaviate/client6/v1/internal/grpc/DefaultGrpcTransport.java @@ -20,17 +20,17 @@ public final class DefaultGrpcTransport implements GrpcTransport { private final WeaviateBlockingStub blockingStub; private final WeaviateFutureStub futureStub; - public DefaultGrpcTransport(GrpcChannelOptions channelOptions) { - this.channel = buildChannel(channelOptions); + public DefaultGrpcTransport(GrpcChannelOptions transportOptions) { + this.channel = buildChannel(transportOptions); var blockingStub = WeaviateGrpc.newBlockingStub(channel) - .withInterceptors(MetadataUtils.newAttachHeadersInterceptor(channelOptions.headers())); + .withInterceptors(MetadataUtils.newAttachHeadersInterceptor(transportOptions.headers())); var futureStub = WeaviateGrpc.newFutureStub(channel) - .withInterceptors(MetadataUtils.newAttachHeadersInterceptor(channelOptions.headers())); + .withInterceptors(MetadataUtils.newAttachHeadersInterceptor(transportOptions.headers())); - if (channelOptions.tokenProvider() != null) { - var credentials = new TokenCallCredentials(channelOptions.tokenProvider()); + if (transportOptions.tokenProvider() != null) { + var credentials = new TokenCallCredentials(transportOptions.tokenProvider()); blockingStub = blockingStub.withCallCredentials(credentials); futureStub = futureStub.withCallCredentials(credentials); } @@ -79,17 +79,16 @@ public void onFailure(Throwable t) { return completable; } - private static ManagedChannel buildChannel(GrpcChannelOptions channelOptions) { - var host = channelOptions.host(); - var channel = ManagedChannelBuilder.forTarget(host); + private static ManagedChannel buildChannel(GrpcChannelOptions transportOptions) { + var channel = ManagedChannelBuilder.forAddress(transportOptions.host(), transportOptions.port()); - if (host.startsWith("https://")) { + if (transportOptions.isSecure()) { channel.useTransportSecurity(); } else { channel.usePlaintext(); } - channel.intercept(MetadataUtils.newAttachHeadersInterceptor(channelOptions.headers())); + channel.intercept(MetadataUtils.newAttachHeadersInterceptor(transportOptions.headers())); return channel.build(); } diff --git a/src/main/java/io/weaviate/client6/v1/internal/grpc/GrpcChannelOptions.java b/src/main/java/io/weaviate/client6/v1/internal/grpc/GrpcChannelOptions.java index dce50afad..da67cb0c2 100644 --- a/src/main/java/io/weaviate/client6/v1/internal/grpc/GrpcChannelOptions.java +++ b/src/main/java/io/weaviate/client6/v1/internal/grpc/GrpcChannelOptions.java @@ -7,9 +7,9 @@ import io.weaviate.client6.v1.internal.TransportOptions; public class GrpcChannelOptions extends TransportOptions { - - public GrpcChannelOptions(String host, Map headers, TokenProvider tokenProvider) { - super(host, buildMetadata(headers), tokenProvider); + public GrpcChannelOptions(String scheme, String host, int port, Map headers, + TokenProvider tokenProvider) { + super(scheme, host, port, buildMetadata(headers), tokenProvider); } private static final Metadata buildMetadata(Map headers) { diff --git a/src/main/java/io/weaviate/client6/v1/internal/rest/DefaultRestTransport.java b/src/main/java/io/weaviate/client6/v1/internal/rest/DefaultRestTransport.java index 3b75860fd..f2b12f3b3 100644 --- a/src/main/java/io/weaviate/client6/v1/internal/rest/DefaultRestTransport.java +++ b/src/main/java/io/weaviate/client6/v1/internal/rest/DefaultRestTransport.java @@ -90,7 +90,7 @@ public void cancelled() { private SimpleHttpRequest prepareSimpleRequest(RequestT request, Endpoint endpoint) { var method = endpoint.method(request); - var uri = transportOptions.host() + endpoint.requestUrl(request); + var uri = transportOptions.baseUrl() + endpoint.requestUrl(request); // TODO: apply options; var body = endpoint.body(gson, request); @@ -103,7 +103,7 @@ private SimpleHttpRequest prepareSimpleRequest(RequestT request, Endp private ClassicHttpRequest prepareClassicRequest(RequestT request, Endpoint endpoint) { var method = endpoint.method(request); - var uri = transportOptions.host() + endpoint.requestUrl(request); + var uri = transportOptions.baseUrl() + endpoint.requestUrl(request); // TODO: apply options; var req = ClassicRequestBuilder.create(method).setUri(uri); diff --git a/src/main/java/io/weaviate/client6/v1/internal/rest/RestTransportOptions.java b/src/main/java/io/weaviate/client6/v1/internal/rest/RestTransportOptions.java index 6a621e3aa..795695e72 100644 --- a/src/main/java/io/weaviate/client6/v1/internal/rest/RestTransportOptions.java +++ b/src/main/java/io/weaviate/client6/v1/internal/rest/RestTransportOptions.java @@ -10,9 +10,11 @@ import io.weaviate.client6.v1.internal.TransportOptions; public final class RestTransportOptions extends TransportOptions> { + private static final String API_VERSION = "v1"; - public RestTransportOptions(String host, Map headers, TokenProvider tokenProvider) { - super(host, buildHeaders(headers), tokenProvider); + public RestTransportOptions(String scheme, String host, int port, Map headers, + TokenProvider tokenProvider) { + super(scheme, host, port, buildHeaders(headers), tokenProvider); } private static final Collection buildHeaders(Map headers) { @@ -22,4 +24,8 @@ private static final Collection buildHeaders(Map he } return basicHeaders; } + + public String baseUrl() { + return scheme() + "://" + host() + ":" + port() + "/" + API_VERSION; + } } From 8d2a7a5879dfde43cede2b25a79209f87a223fc5 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Sun, 15 Jun 2025 18:34:07 +0200 Subject: [PATCH 07/13] fix: rename import --- .../weaviate/client6/v1/api/WeaviateClientAsync.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/io/weaviate/client6/v1/api/WeaviateClientAsync.java b/src/main/java/io/weaviate/client6/v1/api/WeaviateClientAsync.java index 498d2fd74..af7d7acc3 100644 --- a/src/main/java/io/weaviate/client6/v1/api/WeaviateClientAsync.java +++ b/src/main/java/io/weaviate/client6/v1/api/WeaviateClientAsync.java @@ -17,7 +17,7 @@ public class WeaviateClientAsync implements Closeable { public final WeaviateCollectionsClientAsync collections; - public WeaviateClientAsync(ConnectionParams config) { + public WeaviateClientAsync(Config config) { this.restTransport = new DefaultRestTransport(config.restTransportOptions()); this.grpcTransport = new DefaultGrpcTransport(config.grpcTransportOptions()); @@ -28,8 +28,8 @@ public static WeaviateClientAsync local() { return local(ObjectBuilder.identity()); } - public static WeaviateClientAsync local(Function> fn) { - var config = new ConnectionParams.Local(); + public static WeaviateClientAsync local(Function> fn) { + var config = new Config.Local(); return new WeaviateClientAsync(fn.apply(config).build()); } @@ -38,8 +38,8 @@ public static WeaviateClientAsync wcd(String clusterUrl, String apiKey) { } public static WeaviateClientAsync wcd(String clusterUrl, String apiKey, - Function> fn) { - var config = new ConnectionParams.WeaviateCloud(clusterUrl, Authorization.apiKey(apiKey)); + Function> fn) { + var config = new Config.WeaviateCloud(clusterUrl, Authorization.apiKey(apiKey)); return new WeaviateClientAsync(fn.apply(config).build()); } From 2a0d64d84cb6319f16c1d2c4892248bc155fe164 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Sun, 15 Jun 2025 19:02:26 +0200 Subject: [PATCH 08/13] test: add integration test for api key token provider --- pom.xml | 1 + .../integration/AuthorizationITest.java | 53 +++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 src/it/java/io/weaviate/integration/AuthorizationITest.java diff --git a/pom.xml b/pom.xml index eb21a924e..6503ced8a 100644 --- a/pom.xml +++ b/pom.xml @@ -194,6 +194,7 @@ org.mock-server + mockserver-netty ${mock-server.version} test diff --git a/src/it/java/io/weaviate/integration/AuthorizationITest.java b/src/it/java/io/weaviate/integration/AuthorizationITest.java new file mode 100644 index 000000000..30422c67a --- /dev/null +++ b/src/it/java/io/weaviate/integration/AuthorizationITest.java @@ -0,0 +1,53 @@ +package io.weaviate.integration; + +import java.io.IOException; +import java.util.Collections; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockserver.integration.ClientAndServer; +import org.mockserver.model.HttpRequest; + +import io.weaviate.ConcurrentTest; +import io.weaviate.client6.v1.api.Authorization; +import io.weaviate.client6.v1.internal.rest.DefaultRestTransport; +import io.weaviate.client6.v1.internal.rest.Endpoint; +import io.weaviate.client6.v1.internal.rest.RestTransportOptions; + +public class AuthorizationITest extends ConcurrentTest { + private ClientAndServer mockServer; + + @Before + public void startMockServer() { + mockServer = ClientAndServer.startClientAndServer(8080); + } + + @Test + public void testAuthorization_apiKey() throws IOException { + var transportOptions = new RestTransportOptions( + "http", "localhost", 8080, + Collections.emptyMap(), Authorization.apiKey("my-api-key")); + + try (final var restClient = new DefaultRestTransport(transportOptions)) { + restClient.performRequest(null, Endpoint.of( + request -> "GET", + request -> "/", + (gson, request) -> null, + request -> null, + code -> code != 200, + (gson, response) -> null)); + } + + mockServer.verify( + HttpRequest.request() + .withMethod("GET") + .withPath("/v1/") + .withHeader("Authorization", "Bearer my-api-key")); + } + + @After + public void stopMockServer() { + mockServer.stop(); + } +} From 05f92c04948fcb1a492a46aec4ba67b485cb8965 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Sun, 15 Jun 2025 19:14:45 +0200 Subject: [PATCH 09/13] feat: add headers for Weaviate Embedding service --- .../io/weaviate/client6/v1/api/Config.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/main/java/io/weaviate/client6/v1/api/Config.java b/src/main/java/io/weaviate/client6/v1/api/Config.java index 2244fbbab..7a2cd67b1 100644 --- a/src/main/java/io/weaviate/client6/v1/api/Config.java +++ b/src/main/java/io/weaviate/client6/v1/api/Config.java @@ -69,8 +69,27 @@ public SELF setHeaders(Map headers) { return (SELF) this; } + private static final String HEADER_X_WEAVIATE_API_KEY = "X-Weaviate-Api-Key"; + private static final String HEADER_X_WEAVIATE_CLUSTER_URL = "X-Weaviate-Cluster-URL"; + + /** + * isWeaviateDomain returns true if the host matches weaviate.io, + * semi.technology, or weaviate.cloud domain. + */ + private static boolean isWeaviateDomain(String host) { + var lower = host.toLowerCase(); + return lower.contains("weaviate.io") || + lower.contains("semi.technology") || + lower.contains("weaviate.cloud"); + } + @Override public Config build() { + if (isWeaviateDomain(httpHost) && tokenProvider != null) { + // TODO: verify token is static (does not expire) as we add move authz methods. + setHeader(HEADER_X_WEAVIATE_API_KEY, tokenProvider.getToken().accessToken()); + setHeader(HEADER_X_WEAVIATE_CLUSTER_URL, "https://" + httpHost + ":" + httpPort); + } return new Config(this); } } From 3c344cf92ddba9543dc7ef96dddaf95baae2f76d Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Mon, 16 Jun 2025 08:35:07 +0200 Subject: [PATCH 10/13] chore: fix typo in TODO comment --- src/main/java/io/weaviate/client6/v1/api/Config.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/io/weaviate/client6/v1/api/Config.java b/src/main/java/io/weaviate/client6/v1/api/Config.java index 7a2cd67b1..c3050e202 100644 --- a/src/main/java/io/weaviate/client6/v1/api/Config.java +++ b/src/main/java/io/weaviate/client6/v1/api/Config.java @@ -86,7 +86,7 @@ private static boolean isWeaviateDomain(String host) { @Override public Config build() { if (isWeaviateDomain(httpHost) && tokenProvider != null) { - // TODO: verify token is static (does not expire) as we add move authz methods. + // TODO: verify token is static (does not expire) as we add more authz methods. setHeader(HEADER_X_WEAVIATE_API_KEY, tokenProvider.getToken().accessToken()); setHeader(HEADER_X_WEAVIATE_CLUSTER_URL, "https://" + httpHost + ":" + httpPort); } From f943934586d8aa4e08d2ddd7cadaa8bea0f45fd3 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Mon, 16 Jun 2025 09:18:16 +0200 Subject: [PATCH 11/13] refactor: remove redundant X-Weaviate-Api header Weaviate Embedding Service re-uses the Authorization header directly. --- src/main/java/io/weaviate/client6/v1/api/Config.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/io/weaviate/client6/v1/api/Config.java b/src/main/java/io/weaviate/client6/v1/api/Config.java index c3050e202..49b29c6f9 100644 --- a/src/main/java/io/weaviate/client6/v1/api/Config.java +++ b/src/main/java/io/weaviate/client6/v1/api/Config.java @@ -69,7 +69,6 @@ public SELF setHeaders(Map headers) { return (SELF) this; } - private static final String HEADER_X_WEAVIATE_API_KEY = "X-Weaviate-Api-Key"; private static final String HEADER_X_WEAVIATE_CLUSTER_URL = "X-Weaviate-Cluster-URL"; /** @@ -86,8 +85,6 @@ private static boolean isWeaviateDomain(String host) { @Override public Config build() { if (isWeaviateDomain(httpHost) && tokenProvider != null) { - // TODO: verify token is static (does not expire) as we add more authz methods. - setHeader(HEADER_X_WEAVIATE_API_KEY, tokenProvider.getToken().accessToken()); setHeader(HEADER_X_WEAVIATE_CLUSTER_URL, "https://" + httpHost + ":" + httpPort); } return new Config(this); From dba9f261e6cd7b0e98c662aec67d5f89cd5331a2 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Mon, 16 Jun 2025 09:23:35 +0200 Subject: [PATCH 12/13] refactor: use builder setters in default constructor --- src/main/java/io/weaviate/client6/v1/api/Config.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/io/weaviate/client6/v1/api/Config.java b/src/main/java/io/weaviate/client6/v1/api/Config.java index 49b29c6f9..bd005d167 100644 --- a/src/main/java/io/weaviate/client6/v1/api/Config.java +++ b/src/main/java/io/weaviate/client6/v1/api/Config.java @@ -134,8 +134,8 @@ public WeaviateCloud(URI clusterUrl, TokenProvider tokenProvider) { public static class Custom extends Builder { public Custom(String scheme) { super(scheme); - this.httpPort = scheme == "https" ? 443 : 80; - this.grpcPort = scheme == "https" ? 443 : 80; + httpPort(scheme == "https" ? 443 : 80); + grpcPort(scheme == "https" ? 443 : 80); } public Custom httpHost(String host) { From ffd721400709580edb5c34643f92796b2b6e2808 Mon Sep 17 00:00:00 2001 From: dyma solovei Date: Mon, 16 Jun 2025 12:29:22 +0200 Subject: [PATCH 13/13] chore: rename id -> uuid and cleanup old files --- .../client6/v1/api/collections/ObjectMetadata.java | 8 ++++---- .../client6/v1/api/collections/ObjectReference.java | 9 --------- .../client6/v1/api/collections/WeaviateObject.java | 2 +- .../v1/api/collections/data/InsertObjectRequest.java | 2 +- .../client6/v1/api/collections/query/QueryMetadata.java | 2 +- .../client6/v1/api/collections/query/QueryRequest.java | 4 ++-- .../io/weaviate/client6/v1/internal/json/JSONTest.java | 2 +- 7 files changed, 10 insertions(+), 19 deletions(-) delete mode 100644 src/main/java/io/weaviate/client6/v1/api/collections/ObjectReference.java diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/ObjectMetadata.java b/src/main/java/io/weaviate/client6/v1/api/collections/ObjectMetadata.java index d0aa0a815..6fdecbb84 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/ObjectMetadata.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/ObjectMetadata.java @@ -13,7 +13,7 @@ public record ObjectMetadata( @SerializedName("lastUpdateTImeUnix") Long lastUpdatedAt) implements WeaviateMetadata { public ObjectMetadata(Builder builder) { - this(builder.id, builder.vectors, null, null); + this(builder.uuid, builder.vectors, null, null); } public static ObjectMetadata of(Function> fn) { @@ -21,11 +21,11 @@ public static ObjectMetadata of(Function> } public static class Builder implements ObjectBuilder { - private String id; + private String uuid; private Vectors vectors; - public Builder id(String id) { - this.id = id; + public Builder uuid(String uuid) { + this.uuid = uuid; return this; } diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/ObjectReference.java b/src/main/java/io/weaviate/client6/v1/api/collections/ObjectReference.java deleted file mode 100644 index b7f5f9128..000000000 --- a/src/main/java/io/weaviate/client6/v1/api/collections/ObjectReference.java +++ /dev/null @@ -1,9 +0,0 @@ -package io.weaviate.client6.v1.api.collections; - -import java.util.List; - -import io.weaviate.client6.v1.api.collections.query.QueryMetadata; - -public record ObjectReference( - List, QueryMetadata>> objects) { -} diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/WeaviateObject.java b/src/main/java/io/weaviate/client6/v1/api/collections/WeaviateObject.java index 84367b67e..2f7345f2a 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/WeaviateObject.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/WeaviateObject.java @@ -166,7 +166,7 @@ public void write(JsonWriter out, WeaviateObject value) throws IOExcept builder.properties(propertiesAdapter.fromJsonTree(trueProperties)); - metadata.id(object.get("id").getAsString()); + metadata.uuid(object.get("id").getAsString()); builder.metadata(metadata.build()); return builder.build(); diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/data/InsertObjectRequest.java b/src/main/java/io/weaviate/client6/v1/api/collections/data/InsertObjectRequest.java index 6654c9c8d..f44515536 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/data/InsertObjectRequest.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/data/InsertObjectRequest.java @@ -54,7 +54,7 @@ public Builder(String collectionName, T properties) { } public Builder uuid(String uuid) { - this.metadata.id(uuid); + this.metadata.uuid(uuid); return this; } diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/query/QueryMetadata.java b/src/main/java/io/weaviate/client6/v1/api/collections/query/QueryMetadata.java index 59cdee22a..d54678e67 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/query/QueryMetadata.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/query/QueryMetadata.java @@ -16,7 +16,7 @@ public static class Builder implements ObjectBuilder { private Float certainty; private Vectors vectors; - public final Builder id(String uuid) { + public final Builder uuid(String uuid) { this.uuid = uuid; return this; } diff --git a/src/main/java/io/weaviate/client6/v1/api/collections/query/QueryRequest.java b/src/main/java/io/weaviate/client6/v1/api/collections/query/QueryRequest.java index fd40952e5..becf356b1 100644 --- a/src/main/java/io/weaviate/client6/v1/api/collections/query/QueryRequest.java +++ b/src/main/java/io/weaviate/client6/v1/api/collections/query/QueryRequest.java @@ -88,7 +88,7 @@ private static WeaviateObject unmarshalResultObjec CollectionDescriptor descriptor) { var res = unmarshalReferences(propertiesResult, metadataResult, descriptor); var metadata = new QueryMetadata.Builder() - .id(res.metadata().uuid()) + .uuid(res.metadata().uuid()) .distance(metadataResult.getDistance()) .certainty(metadataResult.getCertainty()) .vectors(res.metadata().vectors()); @@ -146,7 +146,7 @@ private static WeaviateObject unmarshalReferences ObjectMetadata metadata = null; if (metadataResult != null) { var metadataBuilder = new ObjectMetadata.Builder() - .id(metadataResult.getId()); + .uuid(metadataResult.getId()); var vectors = new Vectors.Builder(); for (final var vector : metadataResult.getVectorsList()) { diff --git a/src/test/java/io/weaviate/client6/v1/internal/json/JSONTest.java b/src/test/java/io/weaviate/client6/v1/internal/json/JSONTest.java index 0be90cba7..e1ab5a9ee 100644 --- a/src/test/java/io/weaviate/client6/v1/internal/json/JSONTest.java +++ b/src/test/java/io/weaviate/client6/v1/internal/json/JSONTest.java @@ -237,7 +237,7 @@ public static Object[][] testCases() { "Things", Map.of("title", "ThingOne"), Map.of("hasRef", List.of(Reference.uuids("ref-1"))), - ObjectMetadata.of(meta -> meta.id("thing-1"))), + ObjectMetadata.of(meta -> meta.uuid("thing-1"))), """ { "class": "Things",