Skip to content
90 changes: 90 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ applicationDefaultJvmArgs += listOf(

## Supported APIs


### Tucked Builder

Tucked Builder is an iteration of the Builder pattern that reduces boilerplate and leverages static typing and autocompletion to help API discovery.
Expand Down Expand Up @@ -193,6 +194,7 @@ WeaviateClient wcd = WeaviateClient.connectToWeaviateCloud("my-cluster-url.io",
> ```
> WeaviateClient will be automatically closed when execution exits the block.


#### Authentication

Weaviate supports several authentication methods:
Expand All @@ -214,6 +216,7 @@ WeaviateClient.connectToCustom(

Follow the [documentation](https://docs.weaviate.io/deploy/configuration/authentication) for a detailed discussion.


### Collection management

```java
Expand Down Expand Up @@ -249,6 +252,7 @@ Other methods in `collections` namespace include:
- `list()` to fetch collection configurations for all existing collections
- `deleteAll()` to drop all collections and their data


#### Using a Collection Handle

Once a collection is created, you can obtain another client object that's scoped to that collection, called a _"handle"_.
Expand All @@ -274,6 +278,7 @@ Thread.run(() -> popSongs.forEach(song -> songs.data.insert(song)));

For the rest of the document, assume `songs` is handle for the "Songs" collection defined elsewhere.


#### Generic `PropertiesT`

Weaviate client lets you insert object properties in different "shapes". The compile-time type in which the properties must be passed is determined by a generic paramter in CollectionHandle object.
Expand All @@ -283,10 +288,12 @@ In practice this means you'll be passing an instance of `Map<String, Object>` to

If you prefer stricter typing, you can leverage our built-in ORM to work with properties as custom Java types. We will return to this in the **ORM** section later. Assume for now that properties are always being passed around as an "untyped" map.


### Ingesting data

Data operations are concentrated behind the `.data` namespace.


#### Insert single object

```java
Expand Down Expand Up @@ -401,6 +408,7 @@ songs.query.nearImage("base64-encoded-image");
> [!TIP]
> The first object returned in a NearObject query will _always_ be the search object itself. To filter it out, use the `.excludeSelf()` helper as in the example above.


#### Keyword and Hybrid search

```java
Expand Down Expand Up @@ -481,6 +489,7 @@ Where.property("title").like("summer").not();

Passing `null` and and empty `Where[]` to any of the logical operators as well as to the `.where()` method is safe -- the empty operators will simply be ignored.


#### Grouping results

Every query above has an overloaded variant that accepts a group-by clause.
Expand All @@ -502,6 +511,7 @@ songs.query.bm25(

The shape of the response object is different too, see [`QueryResponseGrouped`](./src/main/java/io/weaviate/client6/v1/api/collections/query/QueryResponseGrouped.java).


### Pagination

Paginating a Weaviate collection is straighforward and its API should is instantly familiar. `CursorSpliterator` powers 2 patterns for iterating over objects:
Expand Down Expand Up @@ -700,6 +710,7 @@ System.out.println(

Some of these features may be added in future releases.


### Collection alias

```java
Expand All @@ -710,6 +721,85 @@ client.collections.update("Songs_Alias", "PopSongs");
client.collections.delete("Songs_Alias");
```

### RBAC

#### Roles

The client supports all permission types existing as of `v1.33`.

```java
import io.weaviate.client6.v1.api.rbac.Permission;

client.roles.create(
"ManagerRole",
Permission.collections("Songs", CollectionsPermission.Action.READ, CollectionsPermission.Action.DELETE),
Permission.backups("Albums", BackupsPermission.Action.MANAGE)
);
assert !client.roles.hasPermission("ManagerRole", Permission.collections("Songs", CollectionsPermission.Action.UPDATE));

client.roles.create(
"ArtistRole",
Permission.collections("Songs", CollectionsPermission.Action.CREATE)
);

client.roles.delete("PromoterRole");
```

#### Users

> [!NOTE]
> Not all modifications which can be done to _DB_ users (managed by Weaviate) are equally applicable to _OIDC_ users (managed by an external IdP).
> For this reason their APIs are separated into two distinct namespaces: `users.db` and `users.oidc`.

```java
// DB users must be either defined in the server's environment configuration or created explicitly
if (!client.users.db.exists("ManagerUser")) {
client.users.db.create("ManagerUser");
}

client.users.db.assignRole("ManagerUser", "ManagerRole");


// OIDC users originate from the IdP and do not need to be (and cannot) be created.
client.users.oidc.assignRole("DaveMustaine", "ArtistRole");
client.users.oidc.assignRole("Tarkan", "ArtistRole");


// There's a number of other actions you can take on a DB user:
Optional<DbUser> user = client.users.db.byName("ManagerUser");
assert user.isPresent();

DbUser manager = user.get();
if (!manager.active()) {
client.users.db.activate(manager.id());
}

String newApiKey = client.users.db.rotateKey(manager.id());
client.users.db.deactivate(manager.id());
client.users.db.delete(manager.id());
```

You can get a brief information about the currently authenticated user:

```java
User current = client.users.myUser();
System.out.println(current.userType()); // Prints "DB_USER", "DB_ENV", or "OIDC".
```

#### Groups

RBAC groups are created by assigning roles to a previously-inexisted groups and remove when no roles are longer assigned to a group.

```java
client.groups.assignRoles("./friend-group", "BestFriendRole", "OldFriendRole");

assert client.groups.knownGroupNames().size() == 1; // "./friend-group"
assert client.groups.assignedRoles("./friend-group").size() == 2;

client.groups.assignRoles("./friend-group", "BestFriendRole", "OldFriendRole");
assert client.groups.knownGroupNames().isEmpty();
```

## Useful resources

- [Documentation](https://weaviate.io/developers/weaviate/current/client-libraries/java.html).
Expand Down
93 changes: 67 additions & 26 deletions src/it/java/io/weaviate/containers/Weaviate.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,12 @@
import io.weaviate.client6.v1.internal.ObjectBuilder;

public class Weaviate extends WeaviateContainer {
public static final String VERSION = "1.32.3";
public static final String VERSION = "1.33.0";
public static final String DOCKER_IMAGE = "semitechnologies/weaviate";
public static String OIDC_ISSUER = "https://auth.wcs.api.weaviate.io/auth/realms/SeMI";

private volatile SharedClient clientInstance;

public WeaviateClient getClient() {
return getClient(ObjectBuilder.identity());
}

/**
* Create a new instance of WeaviateClient connected to this container if none
* exist. Get an existing client otherwise.
Expand All @@ -32,7 +29,7 @@ public WeaviateClient getClient() {
* that you do not need to {@code close} it manually. It will only truly close
* after the parent Testcontainer is stopped.
*/
public WeaviateClient getClient(Function<Config.Custom, ObjectBuilder<Config>> fn) {
public WeaviateClient getClient() {
if (!isRunning()) {
start();
}
Expand All @@ -42,17 +39,8 @@ public WeaviateClient getClient(Function<Config.Custom, ObjectBuilder<Config>> f

synchronized (this) {
if (clientInstance == null) {
var host = getHost();
var customFn = ObjectBuilder.partial(fn,
conn -> conn
.scheme("http")
.httpHost(host)
.grpcHost(host)
.httpPort(getMappedPort(8080))
.grpcPort(getMappedPort(50051)));
var config = customFn.apply(new Config.Custom()).build();
try {
clientInstance = new SharedClient(config, this);
clientInstance = new SharedClient(Config.of(defaultConfigFn()), this);
} catch (Exception e) {
throw new RuntimeException("create WeaviateClient for Weaviate container", e);
}
Expand All @@ -66,19 +54,26 @@ public WeaviateClient getClient(Function<Config.Custom, ObjectBuilder<Config>> f
* Prefer using {@link #getClient} unless your test needs the initialization
* steps to run, e.g. OIDC authorization grant exchange.
*/
public WeaviateClient getNewClient(Function<Config.Custom, ObjectBuilder<Config>> fn) {
public WeaviateClient getClient(Function<Config.Custom, ObjectBuilder<Config>> fn) {
if (!isRunning()) {
start();
}

var customFn = ObjectBuilder.partial(fn, defaultConfigFn());
var config = customFn.apply(new Config.Custom()).build();
try {
return new WeaviateClient(config);
} catch (Exception e) {
throw new RuntimeException("create WeaviateClient for Weaviate container", e);
}
}

private Function<Config.Custom, ObjectBuilder<Config>> defaultConfigFn() {
var host = getHost();
var customFn = ObjectBuilder.partial(fn,
conn -> conn
.scheme("http")
.httpHost(host)
.grpcHost(host)
.httpPort(getMappedPort(8080))
.grpcPort(getMappedPort(50051)));
return WeaviateClient.connectToCustom(customFn);
return conn -> conn
.scheme("http")
.httpHost(host).httpPort(getMappedPort(8080))
.grpcHost(host).grpcPort(getMappedPort(50051));
}

public static Weaviate createDefault() {
Expand All @@ -92,7 +87,8 @@ public static Weaviate.Builder custom() {
public static class Builder {
private String versionTag;
private Set<String> enableModules = new HashSet<>();

private Set<String> adminUsers = new HashSet<>();
private Set<String> viewerUsers = new HashSet<>();
private Map<String, String> environment = new HashMap<>();

public Builder() {
Expand Down Expand Up @@ -137,6 +133,37 @@ public Builder withOffloadS3(String accessKey, String secretKey) {
return this;
}

public Builder withAdminUsers(String... admins) {
adminUsers.addAll(Arrays.asList(admins));
return this;
}

public Builder withViewerUsers(String... viewers) {
viewerUsers.addAll(Arrays.asList(viewers));
return this;
}

/** Enable RBAC authorization for this container. */
public Builder withRbac() {
environment.put("AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED", "false");
environment.put("AUTHENTICATION_APIKEY_ENABLED", "true");
environment.put("AUTHORIZATION_RBAC_ENABLED", "true");
environment.put("AUTHENTICATION_DB_USERS_ENABLED", "true");
return this;
}

/**
* Enable API-Key authentication for this container.
*
* @param apiKeys Allowed API keys.
*/
public Builder withApiKeys(String... apiKeys) {
environment.put("AUTHENTICATION_APIKEY_ENABLED", "true");
environment.put("AUTHENTICATION_APIKEY_ALLOWED_KEYS", String.join(",",
apiKeys));
return this;
}

public Builder enableTelemetry(boolean enable) {
environment.put("DISABLE_TELEMETRY", Boolean.toString(!enable));
return this;
Expand Down Expand Up @@ -170,6 +197,20 @@ public Weaviate build() {
c.withEnv("ENABLE_MODULES", String.join(",", enableModules));
}

var apiKeyUsers = new HashSet<String>();
apiKeyUsers.addAll(adminUsers);
apiKeyUsers.addAll(viewerUsers);

if (!adminUsers.isEmpty()) {
environment.put("AUTHORIZATION_ADMIN_USERS", String.join(",", adminUsers));
}
if (!viewerUsers.isEmpty()) {
environment.put("AUTHORIZATION_VIEWER_USERS", String.join(",", viewerUsers));
}
if (!apiKeyUsers.isEmpty()) {
environment.put("AUTHENTICATION_APIKEY_USERS", String.join(",", apiKeyUsers));
}

environment.forEach((name, value) -> c.withEnv(name, value));
c.withCreateContainerCmdModifier(cmd -> cmd.withHostName("weaviate"));
return c;
Expand Down
4 changes: 2 additions & 2 deletions src/it/java/io/weaviate/integration/OIDCSupportITest.java
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ public void test_clientCredentials() throws Exception {

/** Send an HTTP and gRPC requests using a "sync" client. */
private static void pingWeaviate(final Weaviate container, Authentication auth) throws Exception {
try (final var client = container.getNewClient(conn -> conn.authentication(auth))) {
try (final var client = container.getClient(conn -> conn.authentication(auth))) {
// Make an authenticated HTTP call
Assertions.assertThat(client.isLive()).isTrue();

Expand All @@ -143,7 +143,7 @@ private static void pingWeaviate(final Weaviate container, Authentication auth)

/** Send an HTTP and gRPC requests using an "async" client. */
private static void pingWeaviateAsync(final Weaviate container, Authentication auth) throws Exception {
try (final var client = container.getNewClient(conn -> conn.authentication(auth))) {
try (final var client = container.getClient(conn -> conn.authentication(auth))) {
try (final var async = client.async()) {
// Make an authenticated HTTP call
Assertions.assertThat(async.isLive().join()).isTrue();
Expand Down
Loading