Project init
All checks were successful
Maven build / build (push) Successful in 1m47s

This commit is contained in:
Guillaume Dugas
2025-09-09 10:00:01 +02:00
commit de339f9554
102 changed files with 3781 additions and 0 deletions

View File

@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>fr.codeanddata.semrack</groupId>
<artifactId>semrack-api-rest-parent</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>semrack-api-rest-deployment</artifactId>
<name>Semrack Api Rest - Deployment</name>
<dependencies>
<dependency>
<groupId>fr.codeanddata.semrack</groupId>
<artifactId>semrack-core-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-jackson-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-openapi-deployment</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>io.quarkus</groupId>-->
<!-- <artifactId>quarkus-oidc-deployment</artifactId>-->
<!-- </dependency>-->
<dependency>
<groupId>fr.codeanddata.semrack</groupId>
<artifactId>semrack-api-rest</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-internal</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<execution>
<id>default-compile</id>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-extension-processor</artifactId>
<version>${quarkus.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,14 @@
package fr.codeanddata.semrack.api.rest.deployment;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.FeatureBuildItem;
class SemrackApiRestProcessor {
private static final String FEATURE = "semrack-api-rest";
@BuildStep
FeatureBuildItem feature() {
return new FeatureBuildItem(FEATURE);
}
}

View File

@@ -0,0 +1,23 @@
package fr.codeanddata.semrack.api.rest.test;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import io.quarkus.test.QuarkusDevModeTest;
public class SemrackApiRestDevModeTest {
// Start hot reload (DevMode) test with your extension loaded
@RegisterExtension
static final QuarkusDevModeTest devModeTest = new QuarkusDevModeTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class));
@Test
public void writeYourOwnDevModeTest() {
// Write your dev mode tests here - see the testing extension guide https://quarkus.io/guides/writing-extensions#testing-hot-reload for more information
Assertions.assertTrue(true, "Add dev mode assertions to " + getClass().getName());
}
}

View File

@@ -0,0 +1,23 @@
package fr.codeanddata.semrack.api.rest.test;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import io.quarkus.test.QuarkusUnitTest;
public class SemrackApiRestTest {
// Start unit test with your extension loaded
@RegisterExtension
static final QuarkusUnitTest unitTest = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class));
@Test
public void writeYourOwnUnitTest() {
// Write your unit tests here - see the testing extension guide https://quarkus.io/guides/writing-extensions#testing-extensions for more information
Assertions.assertTrue(true, "Add some assertions to " + getClass().getName());
}
}

View File

@@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>fr.codeanddata.semrack</groupId>
<artifactId>semrack-api-rest-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>semrack-api-rest-integration-tests</artifactId>
<name>Semrack Api Rest - Integration Tests</name>
<properties>
<skipITs>true</skipITs>
</properties>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest</artifactId>
</dependency>
<dependency>
<groupId>fr.codeanddata.semrack</groupId>
<artifactId>semrack-api-rest</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
<configuration>
<systemPropertyVariables>
<native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>native-image</id>
<activation>
<property>
<name>native</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipTests>${native.surefire.skip}</skipTests>
</configuration>
</plugin>
</plugins>
</build>
<properties>
<skipITs>false</skipITs>
<quarkus.native.enabled>true</quarkus.native.enabled>
</properties>
</profile>
</profiles>
</project>

View File

@@ -0,0 +1,32 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package fr.codeanddata.semrack.api.rest.it;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
@Path("/semrack-api-rest")
@ApplicationScoped
public class SemrackApiRestResource {
// add some rest methods here
@GET
public String hello() {
return "Hello semrack-api-rest";
}
}

View File

@@ -0,0 +1,7 @@
package fr.codeanddata.semrack.api.rest.it;
import io.quarkus.test.junit.QuarkusIntegrationTest;
@QuarkusIntegrationTest
public class SemrackApiRestResourceIT extends SemrackApiRestResourceTest {
}

View File

@@ -0,0 +1,21 @@
package fr.codeanddata.semrack.api.rest.it;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.is;
import org.junit.jupiter.api.Test;
import io.quarkus.test.junit.QuarkusTest;
@QuarkusTest
public class SemrackApiRestResourceTest {
@Test
public void testHelloEndpoint() {
given()
.when().get("/semrack-api-rest")
.then()
.statusCode(200)
.body(is("Hello semrack-api-rest"));
}
}

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>fr.codeanddata.semrack</groupId>
<artifactId>semrack-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>semrack-api-rest-parent</artifactId>
<packaging>pom</packaging>
<name>Semrack Api Rest - Parent</name>
<modules>
<module>deployment</module>
<module>runtime</module>
</modules>
</project>

View File

@@ -0,0 +1,78 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>fr.codeanddata.semrack</groupId>
<artifactId>semrack-api-rest-parent</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>semrack-api-rest</artifactId>
<name>Semrack Api Rest - Runtime</name>
<dependencies>
<dependency>
<groupId>fr.codeanddata.semrack</groupId>
<artifactId>semrack-core</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-smallrye-openapi</artifactId>
</dependency>
<!-- <dependency>-->
<!-- <groupId>io.quarkus</groupId>-->
<!-- <artifactId>quarkus-oidc</artifactId>-->
<!-- </dependency>-->
</dependencies>
<build>
<plugins>
<plugin>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-extension-maven-plugin</artifactId>
<version>${quarkus.version}</version>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>extension-descriptor</goal>
</goals>
<configuration>
<deployment>${project.groupId}:${project.artifactId}-deployment:${project.version}</deployment>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<execution>
<id>default-compile</id>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-extension-processor</artifactId>
<version>${quarkus.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,54 @@
package fr.codeanddata.semrack.api.rest;
import fr.codeanddata.semrack.core.SemdocStorage;
import fr.codeanddata.semrack.core.models.SemrackDocument;
import fr.codeanddata.semrack.core.models.PushDocumentRequest;
import fr.codeanddata.semrack.core.models.SearchRequest;
import fr.codeanddata.semrack.core.models.SearchResult;
import fr.codeanddata.semrack.core.repositories.SemdocRepository;
import io.smallrye.mutiny.Uni;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import org.jboss.resteasy.reactive.RestPath;
@Path("/semrack/documents")
//@Authenticated
public class SemrackApi {
@Inject
SemdocStorage storage;
@Inject
SemdocRepository repository;
@POST
@Produces(MediaType.APPLICATION_JSON)
public Uni<SemrackDocument> pushDocument(PushDocumentRequest semrackDocument) {
return repository.pushDocument(semrackDocument);
}
@POST
@Path("/search")
@Produces(MediaType.APPLICATION_JSON)
public Uni<SearchResult> searchDocument(SearchRequest query) {
return storage.searchDocument(query);
}
@GET
@Path("{uid}")
@Produces(MediaType.APPLICATION_JSON)
public Uni<SemrackDocument> getDocument(@RestPath String uid) {
return storage.readDocument(uid);
}
@POST
@Path("{uid}")
@Produces(MediaType.APPLICATION_JSON)
public Uni<SemrackDocument> updateDocument(@RestPath String uid, PushDocumentRequest semrackDocument) {
return repository.pushDocument(semrackDocument);
}
}

View File

@@ -0,0 +1,9 @@
name: Semrack Api Rest
#description: Do something useful.
metadata:
# keywords:
# - semrack-api-rest
# guide: ... # To create and publish this guide, see https://github.com/quarkiverse/quarkiverse/wiki#documenting-your-extension
# categories:
# - "miscellaneous"
# status: "preview"

View File

@@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>fr.codeanddata.semrack</groupId>
<artifactId>semrack-core-parent</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>semrack-core-deployment</artifactId>
<name>Semrack Core - Deployment</name>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-security-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jackson-deployment</artifactId>
</dependency>
<dependency>
<groupId>fr.codeanddata.semrack</groupId>
<artifactId>semrack-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-internal</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<execution>
<id>default-compile</id>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-extension-processor</artifactId>
<version>${quarkus.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,14 @@
package fr.codeanddata.semrack.core.deployment;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.FeatureBuildItem;
class SemrackCoreProcessor {
private static final String FEATURE = "semrack-core";
@BuildStep
FeatureBuildItem feature() {
return new FeatureBuildItem(FEATURE);
}
}

View File

@@ -0,0 +1,36 @@
package fr.codeanddata.semrack.core.test;
import fr.codeanddata.semrack.core.SemdocStorage;
import fr.codeanddata.semrack.core.models.SearchRequest;
import fr.codeanddata.semrack.core.models.SearchResult;
import fr.codeanddata.semrack.core.models.SemrackDocument;
import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;
@ApplicationScoped
public class MockedStorageImpl implements SemdocStorage {
@Override
public Uni<SemrackDocument> readDocument(String uid) {
return null;
}
@Override
public Uni<SearchResult> searchDocument(SearchRequest query) {
return null;
}
@Override
public Uni<Long> countDocuments(SearchRequest request) {
return null;
}
@Override
public Uni<Boolean> documentsExist(SearchRequest query) {
return null;
}
@Override
public Uni<SemrackDocument> storeDocument(SemrackDocument document) {
return null;
}
}

View File

@@ -0,0 +1,23 @@
package fr.codeanddata.semrack.core.test;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import io.quarkus.test.QuarkusDevModeTest;
public class SemrackCoreDevModeTest {
// Start hot reload (DevMode) test with your extension loaded
@RegisterExtension
static final QuarkusDevModeTest devModeTest = new QuarkusDevModeTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class));
@Test
public void writeYourOwnDevModeTest() {
// Write your dev mode tests here - see the testing extension guide https://quarkus.io/guides/writing-extensions#testing-hot-reload for more information
Assertions.assertTrue(true, "Add dev mode assertions to " + getClass().getName());
}
}

View File

@@ -0,0 +1,23 @@
package fr.codeanddata.semrack.core.test;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import io.quarkus.test.QuarkusUnitTest;
public class SemrackCoreTest {
// Start unit test with your extension loaded
@RegisterExtension
static final QuarkusUnitTest unitTest = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class));
@Test
public void writeYourOwnUnitTest() {
// Write your unit tests here - see the testing extension guide https://quarkus.io/guides/writing-extensions#testing-extensions for more information
Assertions.assertTrue(true, "Add some assertions to " + getClass().getName());
}
}

View File

@@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>fr.codeanddata.semrack</groupId>
<artifactId>semrack-core-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>semrack-core-integration-tests</artifactId>
<name>Semrack Core - Integration Tests</name>
<properties>
<skipITs>true</skipITs>
</properties>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest</artifactId>
</dependency>
<dependency>
<groupId>fr.codeanddata.semrack</groupId>
<artifactId>semrack-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
<configuration>
<systemPropertyVariables>
<native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>native-image</id>
<activation>
<property>
<name>native</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipTests>${native.surefire.skip}</skipTests>
</configuration>
</plugin>
</plugins>
</build>
<properties>
<skipITs>false</skipITs>
<quarkus.native.enabled>true</quarkus.native.enabled>
</properties>
</profile>
</profiles>
</project>

View File

@@ -0,0 +1,32 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package fr.codeanddata.semrack.core.it;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
@Path("/semrack-core")
@ApplicationScoped
public class SemrackCoreResource {
// add some rest methods here
@GET
public String hello() {
return "Hello semrack-core";
}
}

View File

@@ -0,0 +1,7 @@
package fr.codeanddata.semrack.core.it;
import io.quarkus.test.junit.QuarkusIntegrationTest;
@QuarkusIntegrationTest
public class SemrackCoreResourceIT extends SemrackCoreResourceTest {
}

View File

@@ -0,0 +1,21 @@
package fr.codeanddata.semrack.core.it;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.is;
import org.junit.jupiter.api.Test;
import io.quarkus.test.junit.QuarkusTest;
@QuarkusTest
public class SemrackCoreResourceTest {
@Test
public void testHelloEndpoint() {
given()
.when().get("/semrack-core")
.then()
.statusCode(200)
.body(is("Hello semrack-core"));
}
}

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>fr.codeanddata.semrack</groupId>
<artifactId>semrack-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>semrack-core-parent</artifactId>
<packaging>pom</packaging>
<name>Semrack Core - Parent</name>
<modules>
<module>deployment</module>
<module>runtime</module>
</modules>
</project>

View File

@@ -0,0 +1,94 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>fr.codeanddata.semrack</groupId>
<artifactId>semrack-core-parent</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>semrack-core</artifactId>
<name>Semrack Core - Runtime</name>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-security</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-extension-maven-plugin</artifactId>
<version>${quarkus.version}</version>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>extension-descriptor</goal>
</goals>
<configuration>
<deployment>${project.groupId}:${project.artifactId}-deployment:${project.version}</deployment>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<execution>
<id>default-compile</id>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<path>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-extension-processor</artifactId>
<version>${quarkus.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,8 @@
package fr.codeanddata.semrack.core;
import fr.codeanddata.semrack.core.models.SemdocReadContext;
import io.smallrye.mutiny.Uni;
public interface SemdocReadInterceptor {
Uni<Void> interceptSemdocRead(SemdocReadContext context);
}

View File

@@ -0,0 +1,8 @@
package fr.codeanddata.semrack.core;
import fr.codeanddata.semrack.core.models.SemdocSearchContext;
import io.smallrye.mutiny.Uni;
public interface SemdocSearchInterceptor {
Uni<Void> interceptSemdocSearch(SemdocSearchContext context);
}

View File

@@ -0,0 +1,14 @@
package fr.codeanddata.semrack.core;
import fr.codeanddata.semrack.core.models.SearchRequest;
import fr.codeanddata.semrack.core.models.SearchResult;
import fr.codeanddata.semrack.core.models.SemrackDocument;
import io.smallrye.mutiny.Uni;
public interface SemdocStorage {
Uni<SemrackDocument> readDocument(String uid);
Uni<SearchResult> searchDocument(SearchRequest query);
Uni<Long> countDocuments(SearchRequest request);
Uni<Boolean> documentsExist(SearchRequest query);
Uni<SemrackDocument> storeDocument(SemrackDocument document);
}

View File

@@ -0,0 +1,8 @@
package fr.codeanddata.semrack.core;
import fr.codeanddata.semrack.core.models.SemdocWriteContext;
import io.smallrye.mutiny.Uni;
public interface SemdocWriteInterceptor {
Uni<Void> interceptSemdocWrite(SemdocWriteContext context);
}

View File

@@ -0,0 +1,17 @@
package fr.codeanddata.semrack.core;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.enterprise.inject.spi.CDI;
public interface SemrackLookupExpression<T> {
String apply(Object params);
default T convert(Object params) {
return CDI.current().select(ObjectMapper.class).get().convertValue(params, new TypeReference<T>() {});
}
default T convert(Object params, Class<T> clazz) {
return CDI.current().select(ObjectMapper.class).get().convertValue(params, clazz);
}
}

View File

@@ -0,0 +1,5 @@
package fr.codeanddata.semrack.core.enums;
public enum SemrackSortDirection {
asc, desc
}

View File

@@ -0,0 +1,18 @@
package fr.codeanddata.semrack.core.exceptions;
public class SemrackException extends Exception {
public SemrackException() {
}
public SemrackException(String message) {
super(message);
}
public SemrackException(Throwable cause) {
super(cause);
}
public SemrackException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,18 @@
package fr.codeanddata.semrack.core.exceptions;
public class SemrackRuntimeException extends RuntimeException {
public SemrackRuntimeException() {
}
public SemrackRuntimeException(String message) {
super(message);
}
public SemrackRuntimeException(Throwable cause) {
super(cause);
}
public SemrackRuntimeException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -0,0 +1,33 @@
package fr.codeanddata.semrack.core.interceptors;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import fr.codeanddata.semrack.core.SemdocWriteInterceptor;
import fr.codeanddata.semrack.core.models.SemdocWriteContext;
import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import java.util.Map;
@ApplicationScoped
public class AnnotateWriteInterceptor implements SemdocWriteInterceptor {
public static final String ANNOTATE_KEY = "annotate";
public static final String CUSTOM_PREFIX = "custom/";
@Inject
ObjectMapper objectMapper;
@Override
public Uni<Void> interceptSemdocWrite(SemdocWriteContext context) {
final Map<String, Object> directives = context.getDirectives();
if (directives.containsKey(ANNOTATE_KEY)) {
final Map<String, Object> annotate = objectMapper.convertValue(directives.get(ANNOTATE_KEY), new TypeReference<Map<String, Object>>() {});
annotate.keySet().forEach(key -> context.getNextDocument().getAnnotations().put(CUSTOM_PREFIX + key, annotate.get(key)));
}
return Uni.createFrom().voidItem();
}
}

View File

@@ -0,0 +1,51 @@
package fr.codeanddata.semrack.core.interceptors;
import fr.codeanddata.semrack.core.SemdocWriteInterceptor;
import fr.codeanddata.semrack.core.models.SemdocWriteContext;
import fr.codeanddata.semrack.core.models.SemrackDocument;
import fr.codeanddata.semrack.core.utils.UIDGenerator;
import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import java.util.Map;
import java.util.Optional;
@ApplicationScoped
public class PublishWriteInterceptor implements SemdocWriteInterceptor {
final String PUBLICATION_PREFIX = "publication/";
@Inject
UIDGenerator uidGenerator;
@Override
public Uni<Void> interceptSemdocWrite(SemdocWriteContext context) {
final Map<String, Object> directives = context.getDirectives();
final SemrackDocument current = context.getCurrentDocument();
final SemrackDocument next = context.getNextDocument();
int version = (int) Optional.ofNullable(current)
.map(SemrackDocument::getAnnotations)
.map(annotations -> annotations.getOrDefault(PUBLICATION_PREFIX + "version", 0))
.orElse(0);
next.getAnnotations().put(PUBLICATION_PREFIX + "reference", Optional.ofNullable(current)
.map(SemrackDocument::getAnnotations)
.map(annotations -> annotations.getOrDefault(PUBLICATION_PREFIX + "reference", next.getUid()))
.orElse(next.getUid()));
final boolean publish = directives.getOrDefault("publish", false).equals(true);
final boolean snapshot = directives.getOrDefault("snapshot", publish).equals(true);
next.getAnnotations().put(PUBLICATION_PREFIX + "publish", publish);
next.getAnnotations().put(PUBLICATION_PREFIX + "snapshot", snapshot);
next.getAnnotations().put(PUBLICATION_PREFIX + "version", snapshot ? version + 1 : version);
if (snapshot && current != null) {
next.setUid(uidGenerator.apply(next));
}
return Uni.createFrom().voidItem();
}
}

View File

@@ -0,0 +1,14 @@
package fr.codeanddata.semrack.core.mappers;
import fr.codeanddata.semrack.core.models.SemrackDocument;
import fr.codeanddata.semrack.core.models.PushDocumentRequest;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.MappingConstants;
@Mapper(componentModel = MappingConstants.ComponentModel.CDI)
public abstract class SemrackDocumentMapper {
@Mapping(source = "uid", target = "uid")
@Mapping(source = "metadata", target = "metadata")
abstract SemrackDocument toDocument(PushDocumentRequest document);
}

View File

@@ -0,0 +1,14 @@
package fr.codeanddata.semrack.core.models;
import lombok.*;
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PaginationInfo {
long page;
long size;
long total;
}

View File

@@ -0,0 +1,29 @@
package fr.codeanddata.semrack.core.models;
import lombok.*;
import java.util.Map;
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class PushDocumentRequest {
/**
* L'identifiant unique du document.
*/
String uid;
/**
* Les directives sont les paramètres d'extensions.
* Elles ont pour vocation de générer des actions sur les documents, comme le publish, etc...
*/
Map<String, Object> directives;
/**
* Le contenu du document.
*/
Map<String, Object> metadata;
}

View File

@@ -0,0 +1,18 @@
package fr.codeanddata.semrack.core.models;
import lombok.*;
import java.util.List;
import java.util.Map;
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class SearchRequest {
Map<String, Object> filter;
List<SemrackSort> sort;
SemrackPagination paginate;
List<String> fields;
}

View File

@@ -0,0 +1,15 @@
package fr.codeanddata.semrack.core.models;
import lombok.*;
import java.util.List;
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class SearchResult {
List<SemrackDocument> documents;
PaginationInfo pagination;
}

View File

@@ -0,0 +1,16 @@
package fr.codeanddata.semrack.core.models;
import lombok.*;
import java.util.Map;
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SemdocReadContext {
SemrackDocument currentDocument;
}

View File

@@ -0,0 +1,15 @@
package fr.codeanddata.semrack.core.models;
import lombok.*;
import java.util.Map;
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SemdocSearchContext {
Map<String, Object> search;
}

View File

@@ -0,0 +1,30 @@
package fr.codeanddata.semrack.core.models;
import lombok.*;
import java.util.Map;
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SemdocWriteContext {
/**
* Previous stored document
*/
SemrackDocument currentDocument;
/**
* Document to be store. It initialized with :
* - the currentDocument uid if exists, or a new one
* - the metadata to be persisted
*/
SemrackDocument nextDocument;
/**
* The directives to be applied
*/
Map<String, Object> directives;
}

View File

@@ -0,0 +1,32 @@
package fr.codeanddata.semrack.core.models;
import io.quarkus.runtime.annotations.RegisterForReflection;
import lombok.*;
import java.util.Map;
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@RegisterForReflection(serialization = true)
public class SemrackDocument {
/**
* L'identifiant unique du document.
*/
String uid;
/**
* Les annotations sont créées par le système, au travers des extensions. Elles sont en lecture seule.
* Elles ont pour vocation d'aider au classement et à la recherche du document (tags, catégories, date de création, etc.).
*/
Map<String, Object> annotations;
/**
* Le contenu du document.
*/
Map<String, Object> metadata;
}

View File

@@ -0,0 +1,13 @@
package fr.codeanddata.semrack.core.models;
import lombok.*;
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class SemrackPagination {
Integer page;
Integer size;
}

View File

@@ -0,0 +1,14 @@
package fr.codeanddata.semrack.core.models;
import fr.codeanddata.semrack.core.enums.SemrackSortDirection;
import lombok.*;
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class SemrackSort {
String field;
SemrackSortDirection direction;
}

View File

@@ -0,0 +1,68 @@
package fr.codeanddata.semrack.core.repositories;
import fr.codeanddata.semrack.core.SemdocStorage;
import fr.codeanddata.semrack.core.SemdocWriteInterceptor;
import fr.codeanddata.semrack.core.exceptions.SemrackRuntimeException;
import fr.codeanddata.semrack.core.models.*;
import fr.codeanddata.semrack.core.utils.UIDGenerator;
import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Any;
import jakarta.enterprise.inject.Instance;
import jakarta.inject.Inject;
import java.util.HashMap;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicReference;
@ApplicationScoped
public class SemdocRepository {
@Inject
SemdocStorage semdocStorage;
@Inject
@Any
Instance<SemdocWriteInterceptor> writeInterceptors;
@Inject
UIDGenerator uidGenerator;
Uni<SearchResult> searchDocument(SearchRequest query) {
return semdocStorage.searchDocument(query);
}
public Uni<SemrackDocument> pushDocument(PushDocumentRequest pushDocument) {
return Optional.ofNullable(pushDocument.getUid()).map(semdocStorage::readDocument).orElse(Uni.createFrom().nullItem())
.chain(currentDocument -> {
final SemdocWriteContext context = SemdocWriteContext.builder()
.directives(pushDocument.getDirectives())
.currentDocument(currentDocument)
.nextDocument(SemrackDocument.builder()
.uid(Optional.ofNullable(currentDocument).map(SemrackDocument::getUid).orElse(uidGenerator.apply(null)))
.metadata(pushDocument.getMetadata())
.annotations(new HashMap<>())
.build())
.build();
final AtomicReference<Uni<Void>> documentUni = new AtomicReference<>(Uni.createFrom().voidItem());
writeInterceptors.stream().forEach(filter -> documentUni.set(documentUni.get().chain(() -> filter.interceptSemdocWrite(context))));
return documentUni.get().chain(() -> semdocStorage.storeDocument(context.getNextDocument()));
});
}
public Uni<SemrackDocument> getOrCreate(SearchRequest query, PushDocumentRequest toCreate) {
return searchDocument(query)
.chain(searchResult -> {
if (searchResult.getDocuments().isEmpty()) {
return pushDocument(toCreate);
} else if (searchResult.getDocuments().size() > 1) {
throw new SemrackRuntimeException("Multiple documents found");
} else {
return Uni.createFrom().item(searchResult.getDocuments().getFirst());
}
});
}
}

View File

@@ -0,0 +1,52 @@
package fr.codeanddata.semrack.core.services;
import fr.codeanddata.semrack.core.SemrackLookupExpression;
import io.quarkus.runtime.StartupEvent;
import io.smallrye.common.annotation.Identifier;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
import jakarta.enterprise.inject.Any;
import jakarta.enterprise.inject.Instance;
import jakarta.inject.Inject;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@ApplicationScoped
public class SemrackLookupService {
@Inject
@Any
Instance<SemrackLookupExpression<?>> operators;
final Map<String, SemrackLookupExpression<?>> operatorsIndex = new HashMap<>();
void startup(@Observes StartupEvent event) {
operators.stream()
.filter(s -> s.getClass().getSuperclass().isAnnotationPresent(Identifier.class))
.forEach(s -> {
Identifier identifier = s.getClass().getSuperclass().getAnnotation(Identifier.class);
operatorsIndex.put(identifier.value(), s);
});
}
public String lookup(Map<String, Object> lookupExpression) {
final Set<String> lookupKeys = lookupExpression.keySet();
if (lookupKeys.isEmpty()) {
return "";
} else if (lookupKeys.size() > 1) {
throw new RuntimeException("Only one lookup expression is allowed");
} else {
final String lookupKey = lookupKeys.stream().findFirst().get();
if (! operatorsIndex.containsKey(lookupKey)) {
throw new RuntimeException("Unknown lookup expression '" + lookupKey + "'");
} else {
final SemrackLookupExpression<?> lookup = operatorsIndex.get(lookupKey);
final Object lookupParams = lookupExpression.get(lookupKey);
return lookup.apply(lookupParams);
}
}
}
}

View File

@@ -0,0 +1,27 @@
package fr.codeanddata.semrack.core.utils;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import java.util.Map;
@ApplicationScoped
public class SemdocUtils {
@Inject
ObjectMapper objectMapper;
public <T> T map(Object map, Class<T> clazz) {
return objectMapper.convertValue(map, clazz);
}
public <T> T map(Object map, TypeReference<T> typeRef) {
return objectMapper.convertValue(map, typeRef);
}
public Map<String, Object> remap(Object map) {
return objectMapper.convertValue(map, new TypeReference<>() {});
}
}

View File

@@ -0,0 +1,23 @@
package fr.codeanddata.semrack.core.utils;
import fr.codeanddata.semrack.core.models.SemrackDocument;
import jakarta.enterprise.context.ApplicationScoped;
import org.apache.commons.codec.digest.DigestUtils;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.UUID;
import java.util.function.Function;
@ApplicationScoped
public class UIDGenerator implements Function<SemrackDocument, String> {
@Override
public String apply(SemrackDocument semrackDocument) {
final String uuid = UUID.randomUUID().toString();
final LocalDateTime now = LocalDateTime.now();
final long timestamp = now.toInstant(ZoneOffset.UTC).toEpochMilli();
return DigestUtils.sha256Hex(uuid + timestamp);
}
}

View File

@@ -0,0 +1,9 @@
name: Semrack Core
#description: Do something useful.
metadata:
# keywords:
# - semrack-core
# guide: ... # To create and publish this guide, see https://github.com/quarkiverse/quarkiverse/wiki#documenting-your-extension
# categories:
# - "miscellaneous"
# status: "preview"

View File

@@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>fr.codeanddata.semrack</groupId>
<artifactId>semrack-storage-postgres-parent</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>semrack-storage-postgres-deployment</artifactId>
<name>Semrack Storage Postgres - Deployment</name>
<dependencies>
<dependency>
<groupId>fr.codeanddata.semrack</groupId>
<artifactId>semrack-core-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-reactive-panache-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-reactive-pg-client-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-flyway-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-postgresql-deployment</artifactId>
</dependency>
<dependency>
<groupId>fr.codeanddata.semrack</groupId>
<artifactId>semrack-storage-postgres</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-internal</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<execution>
<id>default-compile</id>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-extension-processor</artifactId>
<version>${quarkus.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,14 @@
package fr.codeanddata.semrack.storage.postgres.deployment;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.FeatureBuildItem;
class SemrackStoragePostgresProcessor {
private static final String FEATURE = "semrack-storage-postgres";
@BuildStep
FeatureBuildItem feature() {
return new FeatureBuildItem(FEATURE);
}
}

View File

@@ -0,0 +1,23 @@
package fr.codeanddata.semrack.storage.postgres.test;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import io.quarkus.test.QuarkusDevModeTest;
public class SemrackStoragePostgresDevModeTest {
// Start hot reload (DevMode) test with your extension loaded
@RegisterExtension
static final QuarkusDevModeTest devModeTest = new QuarkusDevModeTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class));
@Test
public void writeYourOwnDevModeTest() {
// Write your dev mode tests here - see the testing extension guide https://quarkus.io/guides/writing-extensions#testing-hot-reload for more information
Assertions.assertTrue(true, "Add dev mode assertions to " + getClass().getName());
}
}

View File

@@ -0,0 +1,23 @@
package fr.codeanddata.semrack.storage.postgres.test;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import io.quarkus.test.QuarkusUnitTest;
public class SemrackStoragePostgresTest {
// Start unit test with your extension loaded
@RegisterExtension
static final QuarkusUnitTest unitTest = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class));
@Test
public void writeYourOwnUnitTest() {
// Write your unit tests here - see the testing extension guide https://quarkus.io/guides/writing-extensions#testing-extensions for more information
Assertions.assertTrue(true, "Add some assertions to " + getClass().getName());
}
}

View File

@@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>fr.codeanddata.semrack</groupId>
<artifactId>semrack-storage-postgres-parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>semrack-storage-postgres-integration-tests</artifactId>
<name>Semrack Storage Postgres - Integration Tests</name>
<properties>
<skipITs>true</skipITs>
</properties>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest</artifactId>
</dependency>
<dependency>
<groupId>fr.codeanddata.semrack</groupId>
<artifactId>semrack-storage-postgres</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>build</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
</execution>
</executions>
<configuration>
<systemPropertyVariables>
<native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>native-image</id>
<activation>
<property>
<name>native</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipTests>${native.surefire.skip}</skipTests>
</configuration>
</plugin>
</plugins>
</build>
<properties>
<skipITs>false</skipITs>
<quarkus.native.enabled>true</quarkus.native.enabled>
</properties>
</profile>
</profiles>
</project>

View File

@@ -0,0 +1,32 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package fr.codeanddata.semrack.storage.postgres.it;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
@Path("/semrack-storage-postgres")
@ApplicationScoped
public class SemrackStoragePostgresResource {
// add some rest methods here
@GET
public String hello() {
return "Hello semrack-storage-postgres";
}
}

View File

@@ -0,0 +1,7 @@
package fr.codeanddata.semrack.storage.postgres.it;
import io.quarkus.test.junit.QuarkusIntegrationTest;
@QuarkusIntegrationTest
public class SemrackStoragePostgresResourceIT extends SemrackStoragePostgresResourceTest {
}

View File

@@ -0,0 +1,21 @@
package fr.codeanddata.semrack.storage.postgres.it;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.is;
import org.junit.jupiter.api.Test;
import io.quarkus.test.junit.QuarkusTest;
@QuarkusTest
public class SemrackStoragePostgresResourceTest {
@Test
public void testHelloEndpoint() {
given()
.when().get("/semrack-storage-postgres")
.then()
.statusCode(200)
.body(is("Hello semrack-storage-postgres"));
}
}

View File

@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>fr.codeanddata.semrack</groupId>
<artifactId>semrack-parent</artifactId>
<version>1.0-SNAPSHOT</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>semrack-storage-postgres-parent</artifactId>
<name>Semrack Storage Postgres - Parent</name>
<packaging>pom</packaging>
<modules>
<module>deployment</module>
<module>runtime</module>
</modules>
</project>

View File

@@ -0,0 +1,96 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>fr.codeanddata.semrack</groupId>
<artifactId>semrack-storage-postgres-parent</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>semrack-storage-postgres</artifactId>
<name>Semrack Storage Postgres - Runtime</name>
<dependencies>
<dependency>
<groupId>fr.codeanddata.semrack</groupId>
<artifactId>semrack-core</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-reactive-panache</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-reactive-pg-client</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-flyway</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-postgresql</artifactId>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-database-postgresql</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-extension-maven-plugin</artifactId>
<version>${quarkus.version}</version>
<executions>
<execution>
<phase>compile</phase>
<goals>
<goal>extension-descriptor</goal>
</goals>
<configuration>
<deployment>${project.groupId}:${project.artifactId}-deployment:${project.version}</deployment>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<executions>
<execution>
<id>default-compile</id>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<path>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-extension-processor</artifactId>
<version>${quarkus.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,15 @@
package fr.codeanddata.semrack.storage.postgres.dtos;
import lombok.*;
import java.util.List;
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class InLookupParams {
String field;
List<String> values;
}

View File

@@ -0,0 +1,27 @@
package fr.codeanddata.semrack.storage.postgres.entities;
import fr.codeanddata.semrack.core.models.SemrackDocument;
import io.quarkus.hibernate.reactive.panache.PanacheEntity;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import lombok.*;
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.type.SqlTypes;
import java.io.Serializable;
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Entity(name = "SemrackDocument")
@Table(name = "semrack_document")
public class SemrackDocumentEntity extends PanacheEntity implements Serializable {
@Column(unique = true, columnDefinition = "varchar(256)")
String uid;
@JdbcTypeCode(SqlTypes.JSON)
SemrackDocument document;
}

View File

@@ -0,0 +1,23 @@
package fr.codeanddata.semrack.storage.postgres.operators;
import fr.codeanddata.semrack.core.services.SemrackLookupService;
import io.smallrye.common.annotation.Identifier;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import java.util.List;
import java.util.Map;
@Identifier("and")
@ApplicationScoped
public class AndLookup implements SemrackJpaLookupExpression<List<Map<String, Object>>> {
@Inject
SemrackLookupService lookupService;
@Override
public String apply(Object expressions) {
final List<Map<String, Object>> typedExpressions = convert(expressions);
return String.join(" AND ", typedExpressions.stream().map(lookupService::lookup).toList());
}
}

View File

@@ -0,0 +1,24 @@
package fr.codeanddata.semrack.storage.postgres.operators;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.smallrye.common.annotation.Identifier;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
@Identifier("contains")
@ApplicationScoped
public class ContainsLookup implements SemrackJpaLookupExpression<Object> {
@Inject
ObjectMapper objectMapper;
@Override
public String apply(Object params) {
try {
return "document @> '" + objectMapper.writeValueAsString(params) + "'";
} catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,31 @@
package fr.codeanddata.semrack.storage.postgres.operators;
import fr.codeanddata.semrack.core.exceptions.SemrackRuntimeException;
import fr.codeanddata.semrack.storage.postgres.dtos.InLookupParams;
import io.smallrye.common.annotation.Identifier;
import jakarta.enterprise.context.ApplicationScoped;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@Identifier("in")
@ApplicationScoped
public class InLookup implements SemrackJpaLookupExpression<InLookupParams> {
@Override
public String apply(Object params) {
final InLookupParams inLookupParams = convert(params, InLookupParams.class);
final List<String> fields = new ArrayList<>(Arrays.asList(inLookupParams.getField().split("\\.")));
if (fields.isEmpty()) {
throw new SemrackRuntimeException("Field expected");
}
String query = "->> '" + fields.removeLast() + "'";
if (!fields.isEmpty()) {
query = "-> '" + String.join("' -> '", fields) + "' " + query;
}
return "document " + query + " in " + "('" + String.join("', '", inLookupParams.getValues()) + "')";
}
}

View File

@@ -0,0 +1,23 @@
package fr.codeanddata.semrack.storage.postgres.operators;
import fr.codeanddata.semrack.core.services.SemrackLookupService;
import io.smallrye.common.annotation.Identifier;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import java.util.List;
import java.util.Map;
@Identifier("or")
@ApplicationScoped
public class OrLookup implements SemrackJpaLookupExpression<List<Map<String, Object>>> {
@Inject
SemrackLookupService lookupService;
@Override
public String apply(Object expressions) {
final List<Map<String, Object>> typedExpressions = convert(expressions);
return String.join(" OR ", typedExpressions.stream().map(lookupService::lookup).toList());
}
}

View File

@@ -0,0 +1,6 @@
package fr.codeanddata.semrack.storage.postgres.operators;
import fr.codeanddata.semrack.core.SemrackLookupExpression;
public interface SemrackJpaLookupExpression<T> extends SemrackLookupExpression<T> {
}

View File

@@ -0,0 +1,168 @@
package fr.codeanddata.semrack.storage.postgres.storage;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import fr.codeanddata.semrack.core.SemdocStorage;
import fr.codeanddata.semrack.core.enums.SemrackSortDirection;
import fr.codeanddata.semrack.core.models.*;
import fr.codeanddata.semrack.core.services.SemrackLookupService;
import fr.codeanddata.semrack.core.utils.UIDGenerator;
import fr.codeanddata.semrack.storage.postgres.entities.SemrackDocumentEntity;
import fr.codeanddata.semrack.storage.postgres.utils.PathMapper;
import io.quarkus.hibernate.reactive.panache.PanacheRepository;
import io.quarkus.hibernate.reactive.panache.common.WithSession;
import io.quarkus.hibernate.reactive.panache.common.WithTransaction;
import io.smallrye.mutiny.Uni;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import java.util.*;
@ApplicationScoped
public class SemdocJpaStorage implements SemdocStorage, PanacheRepository<SemrackDocumentEntity> {
@Inject
UIDGenerator generator;
@Inject
PathMapper pathMapper;
@Inject
SemrackLookupService lookupService;
@Inject
ObjectMapper objectMapper;
@Override
@WithSession
public Uni<SemrackDocument> readDocument(String uid) {
return find("uid = ?1", uid)
.firstResult().map(d -> Optional.ofNullable(d).map(SemrackDocumentEntity::getDocument).orElse(null));
}
@Override
@WithSession
public Uni<SearchResult> searchDocument(SearchRequest query) {
return search(query);
}
@Override
public Uni<Long> countDocuments(SearchRequest request) {
final String lookup = lookupService.lookup(request.getFilter());
final String whereClause = lookup.isEmpty() ? "" : " WHERE " + lookup;
return getSession()
.chain(s -> s.createNativeQuery("SELECT count(id) FROM semrack_document" + whereClause, Long.class).getSingleResultOrNull())
.map(count -> count == null ? 0 : count);
}
@Override
public Uni<Boolean> documentsExist(SearchRequest query) {
return countDocuments(query)
.map(c -> c > 0);
}
@WithTransaction
@Override
public Uni<SemrackDocument> storeDocument(SemrackDocument document) {
if (document.getUid() == null) {
return createDocument(document);
} else {
return find("uid = ?1", document.getUid())
.count()
.chain(n -> n == 0 ? createDocument(document) : updateDocument(document));
}
}
// TODO err : handle existing document
@WithTransaction
Uni<SemrackDocument> createDocument(SemrackDocument document) {
final String uid = document.getUid() == null ? generator.apply(document) : document.getUid();
document.setUid(uid);
final SemrackDocumentEntity entity = SemrackDocumentEntity.builder()
.uid(uid)
.document(document)
.build();
return persist(entity).map(SemrackDocumentEntity::getDocument);
}
// TODO err : document not exists
@WithTransaction
Uni<SemrackDocument> updateDocument(SemrackDocument document) {
return update("uid = ?1, document = ?2 WHERE uid = ?1", document.getUid(), document)
.map(x -> document);
}
Uni<SearchResult> search(SearchRequest request) {
final String baseQuery = buildBaseQuery(request);
final StringBuilder sorting = new StringBuilder();
if (request.getSort() != null && !request.getSort().isEmpty()) {
sorting.append(" ORDER BY ");
sorting.append(String.join(", ", request.getSort().stream().map(sort -> sort.getField() + " " + Optional.ofNullable(sort.getDirection()).orElse(SemrackSortDirection.asc).name()).toList()));
}
final StringBuilder paginate = new StringBuilder();
if (request.getPaginate() != null) {
final SemrackPagination pagination = request.getPaginate();
final Integer size = pagination.getSize() == null ? 200 : pagination.getSize();
final Integer page = pagination.getPage() == null ? 0 : pagination.getPage();
paginate.append(" LIMIT ").append(size).append(" OFFSET ").append(size * page);
}
return getSession()
.chain(s -> {
final SearchResult searchResult = SearchResult.builder().build();
return countDocuments(request)
.invoke(count -> searchResult.setPagination(PaginationInfo.builder()
.page(Optional.ofNullable(request.getPaginate()).map(SemrackPagination::getPage).orElse(0))
.size(Optional.ofNullable(request.getPaginate()).map(SemrackPagination::getSize).orElse(0))
.total(count)
.build()))
.call(() -> s
.createNativeQuery(baseQuery + sorting + paginate, Object.class).getResultList()
.invoke(results -> searchResult.setDocuments(project(request.getFields(), results))))
.map(count -> searchResult);
});
}
String buildBaseQuery(SearchRequest request) {
final String lookup = lookupService.lookup(request.getFilter());
final String whereClause = lookup.isEmpty() ? "" : " WHERE " + lookup;
if (request.getFields() == null || request.getFields().isEmpty()) {
return "SELECT document FROM semrack_document" + whereClause;
} else {
return "SELECT " + toJsonbPathExtract(request.getFields()) + " FROM semrack_document" + whereClause;
}
}
String toJsonbPathExtract(List<String> fields) {
return String.join(",", fields.stream().map(field -> {
final String serializedField = "'" + String.join("','", field.split("\\.")) + "'";
return "jsonb_extract_path(document, " + serializedField + ")";
}).toList());
}
List<SemrackDocument> project(List<String> fields, Object results) {
final List<?> projectedResults = objectMapper.convertValue(results, List.class);
if (fields == null || fields.isEmpty()) {
return projectedResults.stream().map(result -> objectMapper.convertValue(result, SemrackDocument.class)).toList();
} else {
return projectedResults.stream().map(result -> {
final List<Object> row = fields.size() == 1 ? List.of(result) : objectMapper.convertValue(result, new TypeReference<>() {
});
final List<Object> rowMut = new ArrayList<>(row);
final List<String> fieldCp = new ArrayList<>(fields);
final Map<String, Object> document = new HashMap<>();
while (!fieldCp.isEmpty() && !row.isEmpty()) {
final String field = fieldCp.removeFirst();
final Object value = rowMut.removeFirst();
pathMapper.map(field, document, value);
}
return objectMapper.convertValue(document, SemrackDocument.class);
}).toList();
}
}
}

View File

@@ -0,0 +1,44 @@
package fr.codeanddata.semrack.storage.postgres.utils;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import java.util.*;
import java.util.stream.Stream;
@ApplicationScoped
public class PathMapper {
@Inject
ObjectMapper objectMapper;
public Map<String, Object> map(String path, Object value) {
return map(path, new HashMap<>(), value);
}
public Map<String, Object> map(String path, Map<String, Object> target, Object value) {
setValue(new ArrayList<>(Stream.of(path.split("\\.")).toList()), target, value);
return target;
}
void setValue(List<String> paths, Map<String, Object> container, Object value) {
if (!paths.isEmpty()) {
final String key = paths.removeFirst();
if (paths.isEmpty()) {
container.put(key, value);
return;
}
if (!container.containsKey(key) || !(container.get(key) instanceof Map)) {
container.put(key, new HashMap<>());
}
final Map<String, Object> target = objectMapper.convertValue(container.get(key), new TypeReference<>() {});
container.put(key, target);
setValue(paths, target, value);
}
}
}

View File

@@ -0,0 +1,9 @@
name: Semrack Storage Postgres
#description: Do something useful.
metadata:
# keywords:
# - semrack-storage-postgres
# guide: ... # To create and publish this guide, see https://github.com/quarkiverse/quarkiverse/wiki#documenting-your-extension
# categories:
# - "miscellaneous"
# status: "preview"

View File

@@ -0,0 +1,7 @@
quarkus.datasource.db-kind=postgresql
quarkus.flyway.enabled=true
quarkus.flyway.active=false
quarkus.flyway.migrate-at-start=true
quarkus.native.resources.includes=src/main/resources/db/migration/**
quarkus.native.additional-build-args=-H:SerializationConfigurationResources=serialization-config.json

View File

@@ -0,0 +1,10 @@
create sequence semrack_document_SEQ start with 1 increment by 50;
create table semrack_document
(
id bigint not null,
uid varchar(256) unique,
document jsonb,
primary key (id)
);
alter table semrack_document
add constraint UK307dbjrfh151l0r7bb36xdwew unique (uid);

View File

@@ -0,0 +1,3 @@
[
{"name": "java.lang.String"}
]

View File

@@ -0,0 +1,25 @@
package fr.codeanddata.semrack.storage.postgres.operators;
import io.quarkus.test.junit.QuarkusTest;
import io.smallrye.common.annotation.Identifier;
import jakarta.inject.Inject;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.Map;
@QuarkusTest
class ContainsLookupTest {
@Inject
@Identifier("contains")
ContainsLookup subject;
@Test
void testLookup() {
final String lookup = subject.apply(Map.of("product", Map.of("sku", "xxx")));
final String expectedLookup = "document @> '{\"product\":{\"sku\":\"xxx\"}}'";
Assertions.assertEquals(expectedLookup, lookup);
}
}

View File

@@ -0,0 +1,43 @@
package fr.codeanddata.semrack.storage.postgres.operators;
import fr.codeanddata.semrack.storage.postgres.dtos.InLookupParams;
import io.quarkus.test.junit.QuarkusTest;
import io.smallrye.common.annotation.Identifier;
import jakarta.inject.Inject;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.List;
import java.util.Map;
@QuarkusTest
class InLookupTest {
@Inject
@Identifier("in")
InLookup subject;
@Test
void testConversion() {
final String field = "root.parent.child.field";
final List<String> values = List.of("un", "deux", "trois");
final Map<String, Object> inParams = Map.of(
"field", field,
"values", values
);
final InLookupParams convertedParams = subject.convert(inParams, InLookupParams.class);
Assertions.assertEquals(field, convertedParams.getField());
Assertions.assertEquals(values, convertedParams.getValues());
}
@Test
void testLookup() {
final String lookup = subject.apply(Map.of(
"field", "lookup.field",
"values", List.of("list", "of", "values")));
final String expectedLookup = "document -> 'lookup' ->> 'field' in ('list', 'of', 'values')";
Assertions.assertEquals(expectedLookup, lookup);
}
}

View File

@@ -0,0 +1,42 @@
package fr.codeanddata.semrack.storage.postgres.utils;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.util.HashMap;
import java.util.Map;
@QuarkusTest
public class PathMapperTest {
@Inject
PathMapper pathMapper;
@Inject
ObjectMapper objectMapper;
@Test
void testMapper() {
final Map<String, Object> map = new HashMap<>();
final Map<String, Object> root = new HashMap<>();
map.put("root", root);
root.put("name", "bob");
pathMapper.map("root.child.subchild1.value", map, 1);
pathMapper.map("root.child.subchild2.value", map, 2);
pathMapper.map("hello", map, "world");
Assertions.assertEquals("world", map.get("hello"));
Assertions.assertEquals(1, conv(conv(conv(map, "root"), "child"), "subchild1").get("value"));
Assertions.assertEquals(2, conv(conv(conv(map, "root"), "child"), "subchild2").get("value"));
Assertions.assertEquals("bob", conv(map, "root").get( "name"));
}
Map<String, Object> conv(Map<String, Object> container, String key) {
return objectMapper.convertValue(container.get(key), new TypeReference<>() {});
}
}