Skip to content

Implement History APIs Client #213

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ jvmVersion=1.8
org.gradle.jvmargs=-Dfile.encoding=UTF-8
SONATYPE_CONNECT_TIMEOUT_SECONDS=200
SONATYPE_CLOSE_TIMEOUT_SECONDS=2000
lionwebRepositoryCommitID=0a7d7f61f221dec8367a31eb2a3eaa8cf4de94ba
lionwebRepositoryCommitID=74d2c6ea6f719f27e6274182d5ab92c9b53cb3c6
4 changes: 2 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@ okhttp = { module = "com.squareup.okhttp3:okhttp", version = "4.12.0" }
testcontainers = { module="org.testcontainers:testcontainers", version.ref = "testcontainersVersion" }
testcontainersjunit = { module="org.testcontainers:junit-jupiter", version.ref = "testcontainersVersion" }
testcontainerspg = { module="org.testcontainers:postgresql", version.ref = "testcontainersVersion" }
junit-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junitVersion"}
junit-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junitVersion"}
junit-api = { module = "org.junit.jupiter:junit-jupiter-api", version.ref = "junitVersion" }
junit-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junitVersion" }
12 changes: 5 additions & 7 deletions repo-client/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,6 @@ tasks.withType<Jar>().configureEach {
}
}

dependencies {
implementation(project(":core"))
implementation(libs.okhttp)
implementation(libs.gson)
testImplementation(libs.junit)
}

tasks.register<Jar>("sourcesJar") {
archiveClassifier.set("sources")
// See https://discuss.gradle.org/t/why-subproject-sourceset-dirs-project-sourceset-dirs/7376/5
Expand Down Expand Up @@ -128,6 +121,11 @@ testing {
}

dependencies {
implementation(project(":core"))
implementation(libs.okhttp)
implementation(libs.gson)
testImplementation(libs.junit)

"functionalTestImplementation"(project(":repo-client"))
"functionalTestImplementation"(project(":core"))
"functionalTestImplementation"(project(":repo-client-testing"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import io.lionweb.lioncore.java.utils.CommonChecks;
import io.lionweb.repoclient.api.HistorySupport;
import io.lionweb.repoclient.api.RepositoryConfiguration;
import io.lionweb.repoclient.languages.PropertiesLanguage;
import io.lionweb.repoclient.testing.AbstractRepoClientFunctionalTest;
import java.io.IOException;
import java.util.Arrays;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package io.lionweb.repoclient;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import io.lionweb.lioncore.java.LionWebVersion;
import io.lionweb.lioncore.java.model.ClassifierInstanceUtils;
import io.lionweb.lioncore.java.model.Node;
import io.lionweb.lioncore.java.model.impl.DynamicNode;
import io.lionweb.repoclient.api.HistorySupport;
import io.lionweb.repoclient.api.RepositoryConfiguration;
import io.lionweb.repoclient.api.RepositoryVersionToken;
import io.lionweb.repoclient.languages.PropertiesLanguage;
import io.lionweb.repoclient.testing.AbstractRepoClientFunctionalTest;
import java.io.IOException;
import java.util.*;
import org.junit.jupiter.api.Test;
import org.testcontainers.junit.jupiter.Testcontainers;

@Testcontainers
public class LionWebRepoClientHistoryApiFunctionalTest extends AbstractRepoClientFunctionalTest {

public LionWebRepoClientHistoryApiFunctionalTest() {
super(LionWebVersion.v2023_1, false);
}

@Test
public void partitionsCRUD() throws IOException {
String repoName = "myHistoryDB1";
LionWebRepoClient client =
new LionWebRepoClient(LionWebVersion.v2023_1, "localhost", getModelRepoPort(), repoName);
client.createRepository(
new RepositoryConfiguration(repoName, LionWebVersion.v2023_1, HistorySupport.ENABLED));
client.getJsonSerialization().registerLanguage(PropertiesLanguage.propertiesLanguage);

// Create partition
DynamicNode f1 = new DynamicNode("f1", PropertiesLanguage.propertiesPartition);
DynamicNode f2 = new DynamicNode("f2", PropertiesLanguage.propertiesPartition);
RepositoryVersionToken v1 =
client.createPartitions(client.getJsonSerialization().serializeNodesToJsonString(f1, f2));

// Delete partitions
RepositoryVersionToken v2 = client.deletePartitions(Arrays.asList("f1"));

// Check list
RepositoryVersionToken v0 = new RepositoryVersionToken("0");
List<Node> partitionsAt0 = client.listPartitions(v0);
assertEquals(0, partitionsAt0.size());

List<Node> partitionsAt1 = client.listPartitions(v1);
assertEquals(2, partitionsAt1.size());
assertTrue(partitionsAt1.stream().anyMatch(p -> p.getID().equals("f1")));
assertTrue(partitionsAt1.stream().anyMatch(p -> p.getID().equals("f2")));

List<Node> partitionsAt2 = client.listPartitions(v2);
assertEquals(1, partitionsAt2.size());
assertEquals("f2", partitionsAt2.get(0).getID());
}

@Test
public void partitionHistory() throws IOException {
String repoName = "myHistoryDB2";
LionWebRepoClient client =
new LionWebRepoClient(LionWebVersion.v2023_1, "localhost", getModelRepoPort(), repoName);
client.createRepository(
new RepositoryConfiguration(repoName, LionWebVersion.v2023_1, HistorySupport.ENABLED));
client.getJsonSerialization().registerLanguage(PropertiesLanguage.propertiesLanguage);

// Create partition, initially empty
DynamicNode p1 = new DynamicNode("p1", PropertiesLanguage.propertiesPartition);
RepositoryVersionToken v0 =
client.createPartitions(client.getJsonSerialization().serializeNodesToJsonString(p1));

// Populate partition
DynamicNode f1 = new DynamicNode("f1", PropertiesLanguage.propertiesFile);
ClassifierInstanceUtils.setPropertyValueByName(f1, "path", "a/b/c");
ClassifierInstanceUtils.addChild(p1, "files", f1);
RepositoryVersionToken v1 = client.store(p1);

// Modify property
ClassifierInstanceUtils.setPropertyValueByName(f1, "path", "a/b/foo");
RepositoryVersionToken v2 = client.store(p1);

// Add child
DynamicNode f2 = new DynamicNode("f2", PropertiesLanguage.propertiesFile);
ClassifierInstanceUtils.setPropertyValueByName(f2, "path", "a/b/c2");
ClassifierInstanceUtils.addChild(p1, "files", f2);
RepositoryVersionToken v3 = client.store(p1);

// Delete child
p1.removeChild(f1);
RepositoryVersionToken v4 = client.store(p1);

// Check data
Node p1_v0 = client.retrieve(v0, p1.getID());
assertEquals(0, ClassifierInstanceUtils.getChildrenByContainmentName(p1_v0, "files").size());

Node p1_v1 = client.retrieve(v1, p1.getID());
assertEquals(1, ClassifierInstanceUtils.getChildrenByContainmentName(p1_v1, "files").size());
Node f1_v1 = ClassifierInstanceUtils.getChildrenByContainmentName(p1_v1, "files").get(0);
assertEquals("a/b/c", ClassifierInstanceUtils.getPropertyValueByName(f1_v1, "path"));

Node p1_v2 = client.retrieve(v2, p1.getID());
assertEquals(1, ClassifierInstanceUtils.getChildrenByContainmentName(p1_v2, "files").size());
Node f1_v2 = ClassifierInstanceUtils.getChildrenByContainmentName(p1_v2, "files").get(0);
assertEquals("a/b/foo", ClassifierInstanceUtils.getPropertyValueByName(f1_v2, "path"));

Node p1_v3 = client.retrieve(v3, p1.getID());
assertEquals(2, ClassifierInstanceUtils.getChildrenByContainmentName(p1_v3, "files").size());
Node f1_v3 = ClassifierInstanceUtils.getChildrenByContainmentName(p1_v3, "files").get(0);
assertEquals("a/b/foo", ClassifierInstanceUtils.getPropertyValueByName(f1_v3, "path"));
Node f2_v3 = ClassifierInstanceUtils.getChildrenByContainmentName(p1_v3, "files").get(1);
assertEquals("a/b/c2", ClassifierInstanceUtils.getPropertyValueByName(f2_v3, "path"));

Node p1_v4 = client.retrieve(v4, p1.getID());
assertEquals(1, ClassifierInstanceUtils.getChildrenByContainmentName(p1_v4, "files").size());
Node f2_v4 = ClassifierInstanceUtils.getChildrenByContainmentName(p1_v4, "files").get(0);
assertEquals("a/b/c2", ClassifierInstanceUtils.getPropertyValueByName(f2_v4, "path"));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import io.lionweb.repoclient.api.ClassifierResult;
import io.lionweb.repoclient.api.HistorySupport;
import io.lionweb.repoclient.api.RepositoryConfiguration;
import io.lionweb.repoclient.languages.PropertiesLanguage;
import io.lionweb.repoclient.testing.AbstractRepoClientFunctionalTest;
import java.io.IOException;
import java.util.*;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.lionweb.repoclient;
package io.lionweb.repoclient.languages;

import io.lionweb.lioncore.java.LionWebVersion;
import io.lionweb.lioncore.java.language.*;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,12 @@
package io.lionweb.repoclient;

import com.google.gson.*;
import io.lionweb.lioncore.java.LionWebVersion;
import io.lionweb.lioncore.java.model.Node;
import io.lionweb.lioncore.java.serialization.JsonSerialization;
import io.lionweb.lioncore.java.serialization.SerializationProvider;
import io.lionweb.lioncore.java.serialization.UnavailableNodePolicy;
import io.lionweb.repoclient.api.*;
import io.lionweb.repoclient.impl.ClientForBulkAPIs;
import io.lionweb.repoclient.impl.ClientForDBAdminAPIs;
import io.lionweb.repoclient.impl.ClientForInspectionAPIs;
import io.lionweb.repoclient.impl.RepoClientConfiguration;
import io.lionweb.repoclient.impl.*;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;
Expand All @@ -19,7 +15,8 @@
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public class LionWebRepoClient implements BulkAPIClient, DBAdminAPIClient, InspectionAPIClient {
public class LionWebRepoClient
implements BulkAPIClient, DBAdminAPIClient, InspectionAPIClient, HistoryAPIClient {

public class Builder {
protected LionWebVersion lionWebVersion = LionWebVersion.currentVersion;
Expand Down Expand Up @@ -98,6 +95,7 @@ public LionWebRepoClient build() {
private final ClientForInspectionAPIs inspectionAPIs;
private final ClientForDBAdminAPIs dbAdminAPIs;
private final ClientForBulkAPIs bulkAPIs;
private final ClientForHistoryAPIs historyAPIs;

//
// Constructors
Expand Down Expand Up @@ -141,6 +139,7 @@ public LionWebRepoClient(
this.inspectionAPIs = new ClientForInspectionAPIs(conf);
this.dbAdminAPIs = new ClientForDBAdminAPIs(conf);
this.bulkAPIs = new ClientForBulkAPIs(conf);
this.historyAPIs = new ClientForHistoryAPIs(conf);
}

protected RepoClientConfiguration buildRepositoryConfiguration() {
Expand Down Expand Up @@ -168,21 +167,23 @@ protected RepoClientConfiguration buildRepositoryConfiguration() {
//

@Override
public void createPartitions(List<Node> partitions) throws IOException {
bulkAPIs.createPartitions(partitions);
public @Nullable RepositoryVersionToken createPartitions(List<Node> partitions)
throws IOException {
return bulkAPIs.createPartitions(partitions);
}

public void createPartition(@NotNull Node partition) throws IOException {
createPartitions(Collections.singletonList(partition));
public @Nullable RepositoryVersionToken createPartition(@NotNull Node partition)
throws IOException {
return createPartitions(Collections.singletonList(partition));
}

public void createPartitions(String data) throws IOException {
bulkAPIs.createPartitions(data);
public @Nullable RepositoryVersionToken createPartitions(String data) throws IOException {
return bulkAPIs.createPartitions(data);
}

@Override
public void deletePartitions(List<String> ids) throws IOException {
bulkAPIs.deletePartitions(ids);
public @Nullable RepositoryVersionToken deletePartitions(List<String> ids) throws IOException {
return bulkAPIs.deletePartitions(ids);
}

@Override
Expand All @@ -200,12 +201,12 @@ public List<String> ids(int count) throws IOException {
}

@Override
public void store(List<Node> nodes) throws IOException {
bulkAPIs.store(nodes);
public @Nullable RepositoryVersionToken store(List<Node> nodes) throws IOException {
return bulkAPIs.store(nodes);
}

public void store(@NotNull Node node) throws IOException {
store(Collections.singletonList(node));
public @Nullable RepositoryVersionToken store(@NotNull Node node) throws IOException {
return store(Collections.singletonList(node));
}

public List<Node> retrieve(List<String> nodeIds) throws IOException {
Expand Down Expand Up @@ -266,4 +267,19 @@ public Map<ClassifierKey, ClassifierResult> nodesByClassifier() throws IOExcepti
public Map<String, ClassifierResult> nodesByLanguage() throws IOException {
return inspectionAPIs.nodesByLanguage();
}

//
// History APIs
//

@Override
public List<Node> listPartitions(RepositoryVersionToken repoVersion) throws IOException {
return historyAPIs.listPartitions(repoVersion);
}

@Override
public List<Node> retrieve(RepositoryVersionToken repoVersion, List<String> nodeIds, int limit)
throws IOException {
return historyAPIs.retrieve(repoVersion, nodeIds, limit);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,21 @@
import io.lionweb.lioncore.java.model.Node;
import java.io.IOException;
import java.util.List;
import org.jetbrains.annotations.Nullable;

public interface BulkAPIClient {
void createPartitions(List<Node> partitions) throws IOException;
@Nullable
RepositoryVersionToken createPartitions(List<Node> partitions) throws IOException;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we separate regular client from history client, do we want to introduce history stuff (RepositoryVersionToken) to the regular client?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have separate interfaces: one for each module of the repository.

We do that for two reasons:

  • because of a previous comment by Meinte on having a more modular implementation
  • because in this way, one could introduce other implementations for a subset of the interface

For the implementation currently in this codebase I think it makes sense to support all the interfaces because most methods work both for repository with or without history.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was thinking whether we want to have anything history-related in the non-history Interface. We still can implement all interfaces on one class.


void deletePartitions(List<String> ids) throws IOException;
@Nullable
RepositoryVersionToken deletePartitions(List<String> ids) throws IOException;

List<Node> listPartitions() throws IOException;

List<String> ids(int count) throws IOException;

void store(List<Node> nodes) throws IOException;
@Nullable
RepositoryVersionToken store(List<Node> nodes) throws IOException;

List<Node> retrieve(List<String> nodeIds, int limit) throws IOException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.lionweb.repoclient.api;

import io.lionweb.lioncore.java.model.Node;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import org.jetbrains.annotations.NotNull;

public interface HistoryAPIClient {
@NotNull
List<Node> listPartitions(RepositoryVersionToken repoVersion) throws IOException;

@NotNull
List<Node> retrieve(RepositoryVersionToken repoVersion, @NotNull List<String> nodeIds, int limit)
throws IOException;

default Node retrieve(RepositoryVersionToken repoVersion, @NotNull String nodeId, int limit)
throws IOException {
List<Node> res = retrieve(repoVersion, Arrays.asList(nodeId), limit);
Node node = res.stream().filter(n -> n.getID().equals(nodeId)).findFirst().get();
return node;
}

default Node retrieve(RepositoryVersionToken repoVersion, @NotNull String nodeId)
throws IOException {
return retrieve(repoVersion, nodeId, Integer.MAX_VALUE);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.lionweb.repoclient.api;

import java.util.Objects;

/** Value distinguishing a version of a particular repository. */
public class RepositoryVersionToken {
private final String token;

public RepositoryVersionToken(String token) {
this.token = token;
}

public String getToken() {
return token;
}

@Override
public boolean equals(Object o) {
if (!(o instanceof RepositoryVersionToken)) return false;
RepositoryVersionToken that = (RepositoryVersionToken) o;
return Objects.equals(token, that.token);
}

@Override
public int hashCode() {
return Objects.hashCode(token);
}
}
Loading