diff --git a/gradle.properties b/gradle.properties index b4985398..fa8f8863 100644 --- a/gradle.properties +++ b/gradle.properties @@ -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 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2e81bae7..0955ff5e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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" } diff --git a/repo-client/build.gradle.kts b/repo-client/build.gradle.kts index e015748d..aebe77cc 100644 --- a/repo-client/build.gradle.kts +++ b/repo-client/build.gradle.kts @@ -24,13 +24,6 @@ tasks.withType().configureEach { } } -dependencies { - implementation(project(":core")) - implementation(libs.okhttp) - implementation(libs.gson) - testImplementation(libs.junit) -} - tasks.register("sourcesJar") { archiveClassifier.set("sources") // See https://discuss.gradle.org/t/why-subproject-sourceset-dirs-project-sourceset-dirs/7376/5 @@ -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")) diff --git a/repo-client/src/functionalTest/java/io/lionweb/repoclient/LionWebRepoClientBulkApiFunctionalTest.java b/repo-client/src/functionalTest/java/io/lionweb/repoclient/LionWebRepoClientBulkApiFunctionalTest.java index 07fee0a6..868098c3 100644 --- a/repo-client/src/functionalTest/java/io/lionweb/repoclient/LionWebRepoClientBulkApiFunctionalTest.java +++ b/repo-client/src/functionalTest/java/io/lionweb/repoclient/LionWebRepoClientBulkApiFunctionalTest.java @@ -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; diff --git a/repo-client/src/functionalTest/java/io/lionweb/repoclient/LionWebRepoClientHistoryApiFunctionalTest.java b/repo-client/src/functionalTest/java/io/lionweb/repoclient/LionWebRepoClientHistoryApiFunctionalTest.java new file mode 100644 index 00000000..7d421710 --- /dev/null +++ b/repo-client/src/functionalTest/java/io/lionweb/repoclient/LionWebRepoClientHistoryApiFunctionalTest.java @@ -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 partitionsAt0 = client.listPartitions(v0); + assertEquals(0, partitionsAt0.size()); + + List 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 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")); + } +} diff --git a/repo-client/src/functionalTest/java/io/lionweb/repoclient/LionWebRepoClientInspectionApiFunctionalTest.java b/repo-client/src/functionalTest/java/io/lionweb/repoclient/LionWebRepoClientInspectionApiFunctionalTest.java index b9cd4603..7d09f975 100644 --- a/repo-client/src/functionalTest/java/io/lionweb/repoclient/LionWebRepoClientInspectionApiFunctionalTest.java +++ b/repo-client/src/functionalTest/java/io/lionweb/repoclient/LionWebRepoClientInspectionApiFunctionalTest.java @@ -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.*; diff --git a/repo-client/src/functionalTest/java/io/lionweb/repoclient/PropertiesLanguage.java b/repo-client/src/functionalTest/java/io/lionweb/repoclient/languages/PropertiesLanguage.java similarity index 98% rename from repo-client/src/functionalTest/java/io/lionweb/repoclient/PropertiesLanguage.java rename to repo-client/src/functionalTest/java/io/lionweb/repoclient/languages/PropertiesLanguage.java index b2245079..747b7637 100644 --- a/repo-client/src/functionalTest/java/io/lionweb/repoclient/PropertiesLanguage.java +++ b/repo-client/src/functionalTest/java/io/lionweb/repoclient/languages/PropertiesLanguage.java @@ -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.*; diff --git a/repo-client/src/main/java/io/lionweb/repoclient/LionWebRepoClient.java b/repo-client/src/main/java/io/lionweb/repoclient/LionWebRepoClient.java index 364a1488..2ed99a71 100644 --- a/repo-client/src/main/java/io/lionweb/repoclient/LionWebRepoClient.java +++ b/repo-client/src/main/java/io/lionweb/repoclient/LionWebRepoClient.java @@ -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; @@ -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; @@ -98,6 +95,7 @@ public LionWebRepoClient build() { private final ClientForInspectionAPIs inspectionAPIs; private final ClientForDBAdminAPIs dbAdminAPIs; private final ClientForBulkAPIs bulkAPIs; + private final ClientForHistoryAPIs historyAPIs; // // Constructors @@ -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() { @@ -168,21 +167,23 @@ protected RepoClientConfiguration buildRepositoryConfiguration() { // @Override - public void createPartitions(List partitions) throws IOException { - bulkAPIs.createPartitions(partitions); + public @Nullable RepositoryVersionToken createPartitions(List 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 ids) throws IOException { - bulkAPIs.deletePartitions(ids); + public @Nullable RepositoryVersionToken deletePartitions(List ids) throws IOException { + return bulkAPIs.deletePartitions(ids); } @Override @@ -200,12 +201,12 @@ public List ids(int count) throws IOException { } @Override - public void store(List nodes) throws IOException { - bulkAPIs.store(nodes); + public @Nullable RepositoryVersionToken store(List 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 retrieve(List nodeIds) throws IOException { @@ -266,4 +267,19 @@ public Map nodesByClassifier() throws IOExcepti public Map nodesByLanguage() throws IOException { return inspectionAPIs.nodesByLanguage(); } + + // + // History APIs + // + + @Override + public List listPartitions(RepositoryVersionToken repoVersion) throws IOException { + return historyAPIs.listPartitions(repoVersion); + } + + @Override + public List retrieve(RepositoryVersionToken repoVersion, List nodeIds, int limit) + throws IOException { + return historyAPIs.retrieve(repoVersion, nodeIds, limit); + } } diff --git a/repo-client/src/main/java/io/lionweb/repoclient/api/BulkAPIClient.java b/repo-client/src/main/java/io/lionweb/repoclient/api/BulkAPIClient.java index d33f0031..2151a1dc 100644 --- a/repo-client/src/main/java/io/lionweb/repoclient/api/BulkAPIClient.java +++ b/repo-client/src/main/java/io/lionweb/repoclient/api/BulkAPIClient.java @@ -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 partitions) throws IOException; + @Nullable + RepositoryVersionToken createPartitions(List partitions) throws IOException; - void deletePartitions(List ids) throws IOException; + @Nullable + RepositoryVersionToken deletePartitions(List ids) throws IOException; List listPartitions() throws IOException; List ids(int count) throws IOException; - void store(List nodes) throws IOException; + @Nullable + RepositoryVersionToken store(List nodes) throws IOException; List retrieve(List nodeIds, int limit) throws IOException; } diff --git a/repo-client/src/main/java/io/lionweb/repoclient/api/HistoryAPIClient.java b/repo-client/src/main/java/io/lionweb/repoclient/api/HistoryAPIClient.java new file mode 100644 index 00000000..f782a611 --- /dev/null +++ b/repo-client/src/main/java/io/lionweb/repoclient/api/HistoryAPIClient.java @@ -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 listPartitions(RepositoryVersionToken repoVersion) throws IOException; + + @NotNull + List retrieve(RepositoryVersionToken repoVersion, @NotNull List nodeIds, int limit) + throws IOException; + + default Node retrieve(RepositoryVersionToken repoVersion, @NotNull String nodeId, int limit) + throws IOException { + List 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); + } +} diff --git a/repo-client/src/main/java/io/lionweb/repoclient/api/RepositoryVersionToken.java b/repo-client/src/main/java/io/lionweb/repoclient/api/RepositoryVersionToken.java new file mode 100644 index 00000000..203f9754 --- /dev/null +++ b/repo-client/src/main/java/io/lionweb/repoclient/api/RepositoryVersionToken.java @@ -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); + } +} diff --git a/repo-client/src/main/java/io/lionweb/repoclient/impl/ClientForBulkAPIs.java b/repo-client/src/main/java/io/lionweb/repoclient/impl/ClientForBulkAPIs.java index 78f81ef2..ca25280a 100644 --- a/repo-client/src/main/java/io/lionweb/repoclient/impl/ClientForBulkAPIs.java +++ b/repo-client/src/main/java/io/lionweb/repoclient/impl/ClientForBulkAPIs.java @@ -1,6 +1,7 @@ package io.lionweb.repoclient.impl; import com.google.gson.JsonArray; +import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import io.lionweb.lioncore.java.model.ClassifierInstance; @@ -10,6 +11,7 @@ import io.lionweb.repoclient.CompressionSupport; import io.lionweb.repoclient.RequestFailureException; import io.lionweb.repoclient.api.BulkAPIClient; +import io.lionweb.repoclient.api.RepositoryVersionToken; import java.io.IOException; import java.net.ConnectException; import java.net.HttpURLConnection; @@ -23,6 +25,7 @@ import okio.BufferedSink; import okio.GzipSink; import okio.Okio; +import org.jetbrains.annotations.Nullable; public class ClientForBulkAPIs extends LionWebRepoClientImplHelper implements BulkAPIClient { @@ -31,18 +34,19 @@ public ClientForBulkAPIs(RepoClientConfiguration repoClientConfiguration) { } @Override - public void createPartitions(List partitions) throws IOException { - createPartitions( + public @Nullable RepositoryVersionToken createPartitions(List partitions) + throws IOException { + return createPartitions( conf.getJsonSerialization() .serializeTreesToJsonString(partitions.toArray(new ClassifierInstance[0]))); } - public void createPartitions(String data) throws IOException { - nodesStoringOperation(data, "createPartitions"); + public @Nullable RepositoryVersionToken createPartitions(String data) throws IOException { + return nodesStoringOperation(data, "createPartitions"); } @Override - public void deletePartitions(List ids) throws IOException { + public @Nullable RepositoryVersionToken deletePartitions(List ids) throws IOException { JsonArray ja = new JsonArray(); for (String id : ids) { ja.add(id); @@ -54,7 +58,8 @@ public void deletePartitions(List ids) throws IOException { Request.Builder rq = buildRequest("/bulk/deletePartitions"); Request request = rq.post(body).build(); - performCall(request, (response, responseBody) -> null); + return performCall( + request, (response, responseBody) -> getRepoVersionFromResponse(responseBody)); } @Override @@ -104,9 +109,9 @@ public List ids(int count) throws IOException { } @Override - public void store(List nodes) throws IOException { + public @Nullable RepositoryVersionToken store(List nodes) throws IOException { if (nodes.isEmpty()) { - return; + return null; } Request.Builder rq = buildRequest("/bulk/store"); rq = addGZipCompressionHeader(rq); @@ -115,7 +120,7 @@ public void store(List nodes) throws IOException { .serializeTreesToJsonString(nodes.toArray(new ClassifierInstance[0])); RequestBody uncompressedBody = RequestBody.create(json, JSON); Request request = rq.post(gzipCompress(uncompressedBody)).build(); - performCall( + return performCall( request, (response, responseBody) -> { JsonObject responseData = JsonParser.parseString(responseBody).getAsJsonObject(); @@ -124,7 +129,7 @@ public void store(List nodes) throws IOException { throw new RequestFailureException( request.url().toString(), response.code(), responseBody); } - return null; + return getRepoVersionFromResponse(responseBody); }); } @@ -176,7 +181,8 @@ public List retrieve(List nodeIds, int limit) throws IOException { }); } - private void nodesStoringOperation(final String json, final String operation) { + private @Nullable RepositoryVersionToken nodesStoringOperation( + final String json, final String operation) { // Build the request Request.Builder rb = buildRequest("/bulk/" + operation); rb = addGZipCompressionHeader(rb); @@ -188,9 +194,11 @@ private void nodesStoringOperation(final String json, final String operation) { String url = request.url().toString(); try { try (Response response = conf.getHttpClient().newCall(request).execute()) { + String responseBody = response.body() != null ? response.body().string() : null; if (response.code() != HttpURLConnection.HTTP_OK) { - String responseBody = response.body() != null ? response.body().string() : null; throw new RequestFailureException(url, response.code(), responseBody); + } else { + return getRepoVersionFromResponse(responseBody); } } } catch (ConnectException e) { @@ -233,4 +241,25 @@ public void writeTo(BufferedSink sink) throws IOException { return gzippedBody; } + + private @Nullable RepositoryVersionToken getRepoVersionFromResponse(String responseBody) { + JsonArray data = + JsonParser.parseString(responseBody).getAsJsonObject().get("messages").getAsJsonArray(); + Optional repoVersionMessage = + data.asList().stream() + .filter(e -> e.getAsJsonObject().get("kind").getAsString().equals("RepoVersion")) + .findFirst(); + if (!repoVersionMessage.isPresent()) { + return null; + } + long version = + repoVersionMessage + .get() + .getAsJsonObject() + .get("data") + .getAsJsonObject() + .get("version") + .getAsLong(); + return new RepositoryVersionToken(Long.toString(version)); + } } diff --git a/repo-client/src/main/java/io/lionweb/repoclient/impl/ClientForHistoryAPIs.java b/repo-client/src/main/java/io/lionweb/repoclient/impl/ClientForHistoryAPIs.java new file mode 100644 index 00000000..8ae82713 --- /dev/null +++ b/repo-client/src/main/java/io/lionweb/repoclient/impl/ClientForHistoryAPIs.java @@ -0,0 +1,95 @@ +package io.lionweb.repoclient.impl; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import io.lionweb.lioncore.java.model.Node; +import io.lionweb.lioncore.java.model.impl.ProxyNode; +import io.lionweb.lioncore.java.utils.CommonChecks; +import io.lionweb.repoclient.RequestFailureException; +import io.lionweb.repoclient.api.HistoryAPIClient; +import io.lionweb.repoclient.api.RepositoryVersionToken; +import java.io.IOException; +import java.util.*; +import java.util.stream.Collectors; +import okhttp3.Request; +import okhttp3.RequestBody; +import org.jetbrains.annotations.NotNull; + +public class ClientForHistoryAPIs extends LionWebRepoClientImplHelper implements HistoryAPIClient { + + public ClientForHistoryAPIs(RepoClientConfiguration repoClientConfiguration) { + super(repoClientConfiguration); + } + + @Override + public @NotNull List listPartitions(RepositoryVersionToken repoVersion) throws IOException { + Map params = new HashMap<>(); + params.put("repoVersion", repoVersion.getToken()); + Request.Builder rq = buildRequest("/history/listPartitions", true, true, true, params); + Request request = + rq.addHeader("Accept-Encoding", "gzip").post(RequestBody.create(new byte[0], null)).build(); + + return performCall( + request, + (response, responseBody) -> { + JsonObject responseData = JsonParser.parseString(responseBody).getAsJsonObject(); + boolean success = responseData.get("success").getAsBoolean(); + if (!success) { + throw new RequestFailureException( + request.url().toString(), response.code(), responseBody); + } + return conf.getJsonSerialization().deserializeToNodes(responseData.get("chunk")); + }); + } + + @Override + public @NotNull List retrieve( + RepositoryVersionToken repoVersion, @NotNull List nodeIds, int limit) + throws IOException { + if (nodeIds.isEmpty()) { + return Collections.emptyList(); + } + List invalidIDs = + nodeIds.stream().filter(id -> !CommonChecks.isValidID(id)).collect(Collectors.toList()); + if (!invalidIDs.isEmpty()) { + throw new IllegalArgumentException("IDs must all be valid. Invalid IDs found: " + invalidIDs); + } + + String bodyJson = + "{\"ids\":[" + + String.join(", ", nodeIds.stream().map(id -> "\"" + id + "\"").toArray(String[]::new)) + + "]}"; + Map params = new HashMap<>(); + params.put("depthLimit", String.valueOf(limit)); + params.put("repoVersion", String.valueOf(repoVersion.getToken())); + Request.Builder rq = buildRequest("/history/retrieve", true, true, true, params); + Request request = rq.post(RequestBody.create(bodyJson, JSON)).build(); + + return performCall( + request, + (response, responseBody) -> { + JsonObject responseData = JsonParser.parseString(responseBody).getAsJsonObject(); + boolean success = responseData.get("success").getAsBoolean(); + if (!success) { + throw new RequestFailureException( + request.url().toString(), response.code(), responseBody); + } + List allNodes = + conf.getJsonSerialization().deserializeToNodes(responseData.get("chunk")); + Set idsReturned = + allNodes.stream() + .filter(n -> !(n instanceof ProxyNode)) + .map(n -> n.getID()) + .collect(Collectors.toSet()); + // We want to return only the roots of the trees returned. From those, the other nodes can + // be accessed + return allNodes.stream() + .filter( + n -> + !(n instanceof ProxyNode) + && (n.getParent() == null + || !idsReturned.contains(n.getParent().getID()))) + .collect(Collectors.toList()); + }); + } +}