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

5
.dockerignore Normal file
View File

@@ -0,0 +1,5 @@
*
!target/*-runner
!target/*-runner.jar
!target/lib/*
!target/quarkus-app/*

25
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,25 @@
name: Maven build
run-name: Build application with maven
on: [ push ]
jobs:
build:
runs-on: ubuntu-latest
env:
MAVEN_ACCESS_TOKEN: ${{ secrets.CI_TOKEN }}
MAVEN_REPO_OWNER: ${{ gitea.actor }}
steps:
- name: Check out latest repository code
uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
- name: Build project
run: ./mvnw -s .mvn/settings.xml clean install -DskipTests
- name: Deploy project
run: ./mvnw -s .mvn/settings.xml deploy -DskipTests

45
.gitignore vendored Normal file
View File

@@ -0,0 +1,45 @@
#Maven
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
release.properties
.flattened-pom.xml
# Eclipse
.project
.classpath
.settings/
bin/
# IntelliJ
.idea
*.ipr
*.iml
*.iws
# NetBeans
nb-configuration.xml
# Visual Studio Code
.vscode
.factorypath
# OSX
.DS_Store
# Vim
*.swp
*.swo
# patch
*.orig
*.rej
# Local environment
.env
# Plugin directory
/.quarkus/cli/plugins/
# TLS Certificates
.certs/

19
.mvn/settings.xml Normal file
View File

@@ -0,0 +1,19 @@
<settings xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
https://maven.apache.org/xsd/settings-1.0.0.xsd">
<servers>
<server>
<id>gitea</id>
<configuration>
<httpHeaders>
<property>
<name>Authorization</name>
<value>token ${env.MAVEN_ACCESS_TOKEN}</value>
</property>
</httpHeaders>
</configuration>
</server>
</servers>
</settings>

1
.mvn/wrapper/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
maven-wrapper.jar

View File

@@ -0,0 +1,93 @@
/*
* 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.
*/
import java.io.IOException;
import java.io.InputStream;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.net.URI;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.concurrent.ThreadLocalRandom;
public final class MavenWrapperDownloader {
private static final String WRAPPER_VERSION = "3.3.2";
private static final boolean VERBOSE = Boolean.parseBoolean(System.getenv("MVNW_VERBOSE"));
public static void main(String[] args) {
log("Apache Maven Wrapper Downloader " + WRAPPER_VERSION);
if (args.length != 2) {
System.err.println(" - ERROR wrapperUrl or wrapperJarPath parameter missing");
System.exit(1);
}
try {
log(" - Downloader started");
final URL wrapperUrl = URI.create(args[0]).toURL();
final String jarPath = args[1].replace("..", ""); // Sanitize path
final Path wrapperJarPath = Paths.get(jarPath).toAbsolutePath().normalize();
downloadFileFromURL(wrapperUrl, wrapperJarPath);
log("Done");
} catch (IOException e) {
System.err.println("- Error downloading: " + e.getMessage());
if (VERBOSE) {
e.printStackTrace();
}
System.exit(1);
}
}
private static void downloadFileFromURL(URL wrapperUrl, Path wrapperJarPath)
throws IOException {
log(" - Downloading to: " + wrapperJarPath);
if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
final String username = System.getenv("MVNW_USERNAME");
final char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
Authenticator.setDefault(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});
}
Path temp = wrapperJarPath
.getParent()
.resolve(wrapperJarPath.getFileName() + "."
+ Long.toUnsignedString(ThreadLocalRandom.current().nextLong()) + ".tmp");
try (InputStream inStream = wrapperUrl.openStream()) {
Files.copy(inStream, temp, StandardCopyOption.REPLACE_EXISTING);
Files.move(temp, wrapperJarPath, StandardCopyOption.REPLACE_EXISTING);
} finally {
Files.deleteIfExists(temp);
}
log(" - Downloader complete");
}
private static void log(String msg) {
if (VERBOSE) {
System.out.println(msg);
}
}
}

20
.mvn/wrapper/maven-wrapper.properties vendored Normal file
View File

@@ -0,0 +1,20 @@
# 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.
wrapperVersion=3.3.2
distributionType=source
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar

34
README.md Normal file
View File

@@ -0,0 +1,34 @@
# semrack
This project uses Quarkus, the Supersonic Subatomic Java Framework.
If you want to learn more about Quarkus, please visit its website: <https://quarkus.io/>.
## Quickstart
### Dev mode local execution
```shell script
./mvnw quarkus:dev
```
### Build project
```shell script
./mvnw clean install
```
## Configuration
### Storage Postgres
The storage-postgres module use the quarkus jdbc-postgresql extension.
In dev mode, the database devservices is used. To configure database in production, you need to set the quarkus jdbc parameters :
quarkus.datasource.jdbc.url=jdbc:postgresql://${semrack.db.host}:${semrack.db.port}/${semrack.db.name}
quarkus.datasource.reactive.url=postgresql://${semrack.db.host}:${semrack.db.port}/${semrack.db.name}
quarkus.datasource.username=${semrack.db.user}
quarkus.datasource.password=${semrack.db.password}

27
docs/semrack-core.md Normal file
View File

@@ -0,0 +1,27 @@
# Semrack Core
Fourni les entités / fonctionalités suivantes :
- Semdoc : l'entité document, la base de travail
- Storage : backend de récupération / stockage des documents
- Repository : s'interface en amont du storage. Active les fonctionnalité d'interceptions de lecture, d'écriture et recherche
- ReadInterceptor : interception du document en lecture
- WriteInterceptor : interception du document en écriture
- SearchInterceptor : interception des filtres de recherche
## Semdoc
- uid : identifiant unique dans le rack
- annotations: écrites uniquement par les intercepteurs
- métadonnées: écrite par l'utilisateur, peuvent être altérées par les intercepteurs (validation, securité, etc.)
- directives: lors d'un push, renseigne les intercepteurs sur les actions à mener pour l'écriture du document (versioning, annotations, etc.)
## Directives
### Snapshot
La directive snapshot permet de créer une capture lors du push du document précédent
directives:
snapshot: true | false
publish: true | false

View File

@@ -0,0 +1,24 @@
services:
postgres:
image: postgres
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: quarkus
ports:
- "35532:5432"
semrack:
build:
context: .
dockerfile: src/main/docker/Dockerfile.native-micro
environment:
QUARKUS_FLYWAY_ACTIVE: true
ports:
- "9090:8080"
links:
- postgres
volumes:
- ./.env:/work/.env
- ./src/main/resources/application.properties:/work/config/application.properties

View File

@@ -0,0 +1,136 @@
<?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>
<version>1.0-SNAPSHOT</version>
<artifactId>semrack-parent</artifactId>
<relativePath>../../pom.xml</relativePath>
</parent>
<artifactId>semrack-integration</artifactId>
<dependencies>
<dependency>
<groupId>fr.codeanddata.semrack</groupId>
<artifactId>semrack-core</artifactId>
</dependency>
<dependency>
<groupId>fr.codeanddata.semrack</groupId>
<artifactId>semrack-storage-postgres</artifactId>
</dependency>
<dependency>
<groupId>fr.codeanddata.semrack</groupId>
<artifactId>semrack-api-rest</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>${quarkus.platform.group-id}</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<version>${quarkus.platform.version}</version>
<extensions>true</extensions>
<executions>
<execution>
<goals>
<goal>build</goal>
<goal>generate-code</goal>
<goal>generate-code-tests</goal>
<goal>native-image-agent</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>${compiler-plugin.version}</version>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
<path>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
<version>${org.mapstruct.version}</version>
</path>
</annotationProcessorPaths>
<parameters>true</parameters>
</configuration>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<configuration>
<systemPropertyVariables>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
</configuration>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<version>${surefire-plugin.version}</version>
<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</id>
<activation>
<property>
<name>native</name>
</property>
</activation>
<properties>
<skipITs>false</skipITs>
<quarkus.native.enabled>true</quarkus.native.enabled>
</properties>
</profile>
</profiles>
</project>

View File

@@ -0,0 +1,98 @@
####
# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode
#
# Before building the container image run:
#
# ./mvnw package
#
# Then, build the image with:
#
# docker build -f src/main/docker/Dockerfile.jvm -t quarkus/semrack-jvm .
#
# Then run the container using:
#
# docker run -i --rm -p 8080:8080 quarkus/semrack-jvm
#
# If you want to include the debug port into your docker image
# you will have to expose the debug port (default 5005 being the default) like this : EXPOSE 8080 5005.
# Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005
# when running the container
#
# Then run the container using :
#
# docker run -i --rm -p 8080:8080 quarkus/semrack-jvm
#
# This image uses the `run-java.sh` script to run the application.
# This scripts computes the command line to execute your Java application, and
# includes memory/GC tuning.
# You can configure the behavior using the following environment properties:
# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class") - Be aware that this will override
# the default JVM options, use `JAVA_OPTS_APPEND` to append options
# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options
# in JAVA_OPTS (example: "-Dsome.property=foo")
# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is
# used to calculate a default maximal heap memory based on a containers restriction.
# If used in a container without any memory constraints for the container then this
# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio
# of the container available memory as set here. The default is `50` which means 50%
# of the available memory is used as an upper boundary. You can skip this mechanism by
# setting this value to `0` in which case no `-Xmx` option is added.
# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This
# is used to calculate a default initial heap memory based on the maximum heap memory.
# If used in a container without any memory constraints for the container then this
# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio
# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx`
# is used as the initial heap size. You can skip this mechanism by setting this value
# to `0` in which case no `-Xms` option is added (example: "25")
# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS.
# This is used to calculate the maximum value of the initial heap memory. If used in
# a container without any memory constraints for the container then this option has
# no effect. If there is a memory constraint then `-Xms` is limited to the value set
# here. The default is 4096MB which means the calculated value of `-Xms` never will
# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096")
# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output
# when things are happening. This option, if set to true, will set
# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true").
# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example:
# true").
# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787").
# - CONTAINER_CORE_LIMIT: A calculated core limit as described in
# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2")
# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024").
# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion.
# (example: "20")
# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking.
# (example: "40")
# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection.
# (example: "4")
# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus
# previous GC times. (example: "90")
# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20")
# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100")
# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should
# contain the necessary JRE command-line options to specify the required GC, which
# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC).
# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080")
# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080")
# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be
# accessed directly. (example: "foo.example.com,bar.example.com")
#
###
FROM registry.access.redhat.com/ubi9/openjdk-21:1.21
ENV LANGUAGE='en_US:en'
# We make four distinct layers so if there are application changes the library layers can be re-used
COPY --chown=185 target/quarkus-app/lib/ /deployments/lib/
COPY --chown=185 target/quarkus-app/*.jar /deployments/
COPY --chown=185 target/quarkus-app/app/ /deployments/app/
COPY --chown=185 target/quarkus-app/quarkus/ /deployments/quarkus/
EXPOSE 8080
USER 185
ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ]

View File

@@ -0,0 +1,94 @@
####
# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode
#
# Before building the container image run:
#
# ./mvnw package -Dquarkus.package.jar.type=legacy-jar
#
# Then, build the image with:
#
# docker build -f src/main/docker/Dockerfile.legacy-jar -t quarkus/semrack-legacy-jar .
#
# Then run the container using:
#
# docker run -i --rm -p 8080:8080 quarkus/semrack-legacy-jar
#
# If you want to include the debug port into your docker image
# you will have to expose the debug port (default 5005 being the default) like this : EXPOSE 8080 5005.
# Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005
# when running the container
#
# Then run the container using :
#
# docker run -i --rm -p 8080:8080 quarkus/semrack-legacy-jar
#
# This image uses the `run-java.sh` script to run the application.
# This scripts computes the command line to execute your Java application, and
# includes memory/GC tuning.
# You can configure the behavior using the following environment properties:
# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class") - Be aware that this will override
# the default JVM options, use `JAVA_OPTS_APPEND` to append options
# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options
# in JAVA_OPTS (example: "-Dsome.property=foo")
# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is
# used to calculate a default maximal heap memory based on a containers restriction.
# If used in a container without any memory constraints for the container then this
# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio
# of the container available memory as set here. The default is `50` which means 50%
# of the available memory is used as an upper boundary. You can skip this mechanism by
# setting this value to `0` in which case no `-Xmx` option is added.
# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This
# is used to calculate a default initial heap memory based on the maximum heap memory.
# If used in a container without any memory constraints for the container then this
# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio
# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx`
# is used as the initial heap size. You can skip this mechanism by setting this value
# to `0` in which case no `-Xms` option is added (example: "25")
# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS.
# This is used to calculate the maximum value of the initial heap memory. If used in
# a container without any memory constraints for the container then this option has
# no effect. If there is a memory constraint then `-Xms` is limited to the value set
# here. The default is 4096MB which means the calculated value of `-Xms` never will
# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096")
# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output
# when things are happening. This option, if set to true, will set
# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true").
# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example:
# true").
# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787").
# - CONTAINER_CORE_LIMIT: A calculated core limit as described in
# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2")
# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024").
# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion.
# (example: "20")
# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking.
# (example: "40")
# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection.
# (example: "4")
# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus
# previous GC times. (example: "90")
# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20")
# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100")
# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should
# contain the necessary JRE command-line options to specify the required GC, which
# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC).
# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080")
# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080")
# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be
# accessed directly. (example: "foo.example.com,bar.example.com")
#
###
FROM registry.access.redhat.com/ubi9/openjdk-21:1.21
ENV LANGUAGE='en_US:en'
COPY target/lib/* /deployments/lib/
COPY target/*-runner.jar /deployments/quarkus-run.jar
EXPOSE 8080
USER 185
ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ]

View File

@@ -0,0 +1,29 @@
####
# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode.
#
# Before building the container image run:
#
# ./mvnw package -Dnative
#
# Then, build the image with:
#
# docker build -f src/main/docker/Dockerfile.native -t quarkus/semrack .
#
# Then run the container using:
#
# docker run -i --rm -p 8080:8080 quarkus/semrack
#
# The ` registry.access.redhat.com/ubi9/ubi-minimal:9.5` base image is based on UBI 9.
# To use UBI 8, switch to `quay.io/ubi8/ubi-minimal:8.10`.
###
FROM registry.access.redhat.com/ubi9/ubi-minimal:9.5
WORKDIR /work/
RUN chown 1001 /work \
&& chmod "g+rwX" /work \
&& chown 1001:root /work
COPY --chown=1001:root --chmod=0755 target/*-runner /work/application
EXPOSE 8080
USER 1001
ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"]

View File

@@ -0,0 +1,32 @@
####
# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode.
# It uses a micro base image, tuned for Quarkus native executables.
# It reduces the size of the resulting container image.
# Check https://quarkus.io/guides/quarkus-runtime-base-image for further information about this image.
#
# Before building the container image run:
#
# ./mvnw package -Dnative
#
# Then, build the image with:
#
# docker build -f src/main/docker/Dockerfile.native-micro -t quarkus/semrack .
#
# Then run the container using:
#
# docker run -i --rm -p 8080:8080 quarkus/semrack
#
# The `quay.io/quarkus/ubi9-quarkus-micro-image:2.0` base image is based on UBI 9.
# To use UBI 8, switch to `quay.io/quarkus/quarkus-micro-image:2.0`.
###
FROM quay.io/quarkus/ubi9-quarkus-micro-image:2.0
WORKDIR /work/
RUN chown 1001 /work \
&& chmod "g+rwX" /work \
&& chown 1001:root /work
COPY --chown=1001:root --chmod=0755 target/*-runner /work/application
EXPOSE 8080
USER 1001
ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"]

View File

@@ -0,0 +1,24 @@
%dev.quarkus.log.min-level=TRACE
quarkus.hibernate-orm.mapping.format.global=ignore
# Datasource
quarkus.datasource.devservices.port=35432
quarkus.hibernate-orm.scripts.generation=none
%prod.quarkus.datasource.jdbc.url=jdbc:postgresql://${semrack.db.host}:${semrack.db.port}/${semrack.db.name}
%prod.quarkus.datasource.reactive.url=postgresql://${semrack.db.host}:${semrack.db.port}/${semrack.db.name}
%prod.quarkus.datasource.username=${semrack.db.user}
%prod.quarkus.datasource.password=${semrack.db.password}
## Flyway
quarkus.flyway.active=false
# Security
quarkus.keycloak.devservices.port=35180
quarkus.oidc.client-id=m2m-client
quarkus.oidc.credentials.secret=m2m-password
quarkus.oidc.discovery-enabled=true
%prod.quarkus.oidc.auth-server-url=${semrack.oidc.url}
%prod.quarkus.oidc.client-id=${semrack.oidc.client-id}
%prod.quarkus.oidc.credentials.secret=${semrack.oidc.client-secret}

View File

@@ -0,0 +1,8 @@
package fr.codeanddata;
import io.quarkus.test.junit.QuarkusIntegrationTest;
@QuarkusIntegrationTest
class SemrackApiIT extends SemrackApiTest {
// Execute the same tests but in packaged mode.
}

View File

@@ -0,0 +1,20 @@
package fr.codeanddata;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.is;
@QuarkusTest
class SemrackApiTest {
@Test
void testHelloEndpoint() {
given()
.when().get("/hello")
.then()
.statusCode(200)
.body(is("Hello from Quarkus REST"));
}
}

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<>() {});
}
}

332
mvnw vendored Executable file
View File

@@ -0,0 +1,332 @@
#!/bin/sh
# ----------------------------------------------------------------------------
# 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.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Apache Maven Wrapper startup batch script, publish 3.3.2
#
# Required ENV vars:
# ------------------
# JAVA_HOME - location of a JDK home dir
#
# Optional ENV vars
# -----------------
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
# ----------------------------------------------------------------------------
if [ -z "$MAVEN_SKIP_RC" ]; then
if [ -f /usr/local/etc/mavenrc ]; then
. /usr/local/etc/mavenrc
fi
if [ -f /etc/mavenrc ]; then
. /etc/mavenrc
fi
if [ -f "$HOME/.mavenrc" ]; then
. "$HOME/.mavenrc"
fi
fi
# OS specific support. $var _must_ be set to either true or false.
cygwin=false
darwin=false
mingw=false
case "$(uname)" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true ;;
Darwin*)
darwin=true
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
JAVA_HOME="$(/usr/libexec/java_home)"
export JAVA_HOME
else
JAVA_HOME="/Library/Java/Home"
export JAVA_HOME
fi
fi
;;
esac
if [ -z "$JAVA_HOME" ]; then
if [ -r /etc/gentoo-release ]; then
JAVA_HOME=$(java-config --jre-home)
fi
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin; then
[ -n "$JAVA_HOME" ] \
&& JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
[ -n "$CLASSPATH" ] \
&& CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
fi
# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw; then
[ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] \
&& JAVA_HOME="$(
cd "$JAVA_HOME" || (
echo "cannot cd into $JAVA_HOME." >&2
exit 1
)
pwd
)"
fi
if [ -z "$JAVA_HOME" ]; then
javaExecutable="$(which javac)"
if [ -n "$javaExecutable" ] && ! [ "$(expr "$javaExecutable" : '\([^ ]*\)')" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
readLink=$(which readlink)
if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
if $darwin; then
javaHome="$(dirname "$javaExecutable")"
javaExecutable="$(cd "$javaHome" && pwd -P)/javac"
else
javaExecutable="$(readlink -f "$javaExecutable")"
fi
javaHome="$(dirname "$javaExecutable")"
javaHome=$(expr "$javaHome" : '\(.*\)/bin')
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
fi
fi
if [ -z "$JAVACMD" ]; then
if [ -n "$JAVA_HOME" ]; then
if [ -x "$JAVA_HOME/jre/sh/java" ]; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="$(
\unset -f command 2>/dev/null
\command -v java
)"
fi
fi
if [ ! -x "$JAVACMD" ]; then
echo "Error: JAVA_HOME is not defined correctly." >&2
echo " We cannot execute $JAVACMD" >&2
exit 1
fi
if [ -z "$JAVA_HOME" ]; then
echo "Warning: JAVA_HOME environment variable is not set." >&2
fi
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
if [ -z "$1" ]; then
echo "Path not specified to find_maven_basedir" >&2
return 1
fi
basedir="$1"
wdir="$1"
while [ "$wdir" != '/' ]; do
if [ -d "$wdir"/.mvn ]; then
basedir=$wdir
break
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
wdir=$(
cd "$wdir/.." || exit 1
pwd
)
fi
# end of workaround
done
printf '%s' "$(
cd "$basedir" || exit 1
pwd
)"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
# Remove \r in case we run on Windows within Git Bash
# and check out the storage with auto CRLF management
# enabled. Otherwise, we may read lines that are delimited with
# \r\n and produce $'-Xarg\r' rather than -Xarg due to word
# splitting rules.
tr -s '\r\n' ' ' <"$1"
fi
}
log() {
if [ "$MVNW_VERBOSE" = true ]; then
printf '%s\n' "$1"
fi
}
BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
if [ -z "$BASE_DIR" ]; then
exit 1
fi
MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
export MAVEN_PROJECTBASEDIR
log "$MAVEN_PROJECTBASEDIR"
##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
if [ -r "$wrapperJarPath" ]; then
log "Found $wrapperJarPath"
else
log "Couldn't find $wrapperJarPath, downloading it ..."
if [ -n "$MVNW_REPOURL" ]; then
wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar"
else
wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.3.2/maven-wrapper-3.3.2.jar"
fi
while IFS="=" read -r key value; do
# Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
safeValue=$(echo "$value" | tr -d '\r')
case "$key" in wrapperUrl)
wrapperUrl="$safeValue"
break
;;
esac
done <"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
log "Downloading from: $wrapperUrl"
if $cygwin; then
wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
fi
if command -v wget >/dev/null; then
log "Found wget ... using wget"
[ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
else
wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
fi
elif command -v curl >/dev/null; then
log "Found curl ... using curl"
[ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
else
curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
fi
else
log "Falling back to using Java to download"
javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
# For Cygwin, switch paths to Windows format before running javac
if $cygwin; then
javaSource=$(cygpath --path --windows "$javaSource")
javaClass=$(cygpath --path --windows "$javaClass")
fi
if [ -e "$javaSource" ]; then
if [ ! -e "$javaClass" ]; then
log " - Compiling MavenWrapperDownloader.java ..."
("$JAVA_HOME/bin/javac" "$javaSource")
fi
if [ -e "$javaClass" ]; then
log " - Running MavenWrapperDownloader.java ..."
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
fi
fi
fi
fi
##########################################################################################
# End of extension
##########################################################################################
# If specified, validate the SHA-256 sum of the Maven wrapper jar file
wrapperSha256Sum=""
while IFS="=" read -r key value; do
case "$key" in wrapperSha256Sum)
wrapperSha256Sum=$value
break
;;
esac
done <"$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
if [ -n "$wrapperSha256Sum" ]; then
wrapperSha256Result=false
if command -v sha256sum >/dev/null; then
if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c >/dev/null 2>&1; then
wrapperSha256Result=true
fi
elif command -v shasum >/dev/null; then
if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c >/dev/null 2>&1; then
wrapperSha256Result=true
fi
else
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2
echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." >&2
exit 1
fi
if [ $wrapperSha256Result = false ]; then
echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
exit 1
fi
fi
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$JAVA_HOME" ] \
&& JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
[ -n "$CLASSPATH" ] \
&& CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
[ -n "$MAVEN_PROJECTBASEDIR" ] \
&& MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
fi
# Provide a "standardized" way to retrieve the CLI args that will
# work with both Windows and non-Windows executions.
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
export MAVEN_CMD_LINE_ARGS
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
# shellcheck disable=SC2086 # safe args
exec "$JAVACMD" \
$MAVEN_OPTS \
$MAVEN_DEBUG_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"

Some files were not shown because too many files have changed in this diff Show More