Compare commits

..

1 commit

Author SHA1 Message Date
483c31bd1e
feat(deps): add git-build-hook-maven-plugin 3.5.0
All checks were successful
Build, test and publish the Quarkus libraries / build (push) Successful in 1m19s
2024-11-11 10:38:13 +01:00
88 changed files with 183 additions and 2799 deletions

View file

@ -0,0 +1,42 @@
name: Build, test and publish the Quarkus libraries
on: [ push, workflow_dispatch ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout the code
uses: https://github.com/actions/checkout@v4
- name: Set up JDK 21
uses: https://github.com/actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'
- name: Configure Maven CI/CD settings
uses: https://github.com/s4u/maven-settings-action@v3.1.0
with:
servers: |
[{
"id": "kvant",
"configuration": {
"httpHeaders": {
"property": {
"name": "Authorization",
"value": "token ${{ secrets.PHOENIX_PACKAGE_WRITER_TOKEN }}"
}
}
}
}]
- name: Make maven wrapper executable
run: chmod +x mvnw
- name: Build and run tests
run: ./mvnw verify
- name: Upload libs to Gitea Maven Registry
if: gitea.ref == 'refs/heads/main'
run: ./mvnw deploy -Dmaven.test.skip=true -Dmaven.javadoc.skip=true

View file

@ -1,5 +0,0 @@
name: Quarkus Commons
release:
current-version: "1.0.8"
next-version: "1.0.9-SNAPSHOT"

View file

@ -1,99 +0,0 @@
name: Build
on:
push:
branches:
- "main"
tags:
- '[0-9]+.[0-9]+.[0-9]+'
paths-ignore:
- '.gitattributes'
- '.gitignore'
- '.github/renovate.json5'
- '.github/project.yaml'
- 'docs/**'
- 'README.md'
pull_request:
workflow_dispatch:
env:
COMMON_MAVEN_OPTS: "-e -B --fae"
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout the code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Shallow clones should be disabled for a better relevancy of analysis
- name: Set up JDK 21
uses: https://github.com/actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'
- name: Generate cache keys
id: cache-key
run: |
CURRENT_BRANCH="${{ github.head_ref || github.ref_name }}"
CURRENT_MONTH=$(/bin/date -u "+%Y-%m")
CURRENT_DAY=$(/bin/date -u "+%d")
ROOT_CACHE_KEY="m2-cache-quarkus-commons"
echo "m2-monthly-cache-key=${ROOT_CACHE_KEY}-${CURRENT_MONTH}" >> $GITHUB_OUTPUT
echo "m2-monthly-branch-cache-key=${ROOT_CACHE_KEY}-${CURRENT_MONTH}-${CURRENT_BRANCH}" >> $GITHUB_OUTPUT
echo "m2-cache-key=${ROOT_CACHE_KEY}-${CURRENT_MONTH}-${CURRENT_BRANCH}-${CURRENT_DAY}" >> $GITHUB_OUTPUT
- name: Cache Maven Repository
id: cache-maven
uses: actions/cache@v4
with:
path: ~/.m2/repository
# The cache is per branch but in case we don't find a branch for a given branch, we will get a cache from another branch.
key: ${{ steps.cache-key.outputs.m2-cache-key }}
restore-keys: |
${{ steps.cache-key.outputs.m2-monthly-branch-cache-key }}
${{ steps.cache-key.outputs.m2-monthly-cache-key }}
- name: Configure Maven CI/CD settings
uses: https://github.com/s4u/maven-settings-action@v3.1.0
with:
servers: |
[{
"id": "phoenix-oss",
"username": "${{ vars.ORG_PACKAGE_WRITER_USERNAME }}",
"password": "${{ secrets.ORG_PACKAGE_WRITER_TOKEN }}"
}]
- name: Make maven wrapper executable
run: chmod +x mvnw
- name: Validate tag
if: startsWith(github.ref, 'refs/tags/')
run: |
TAG_NAME="${GITHUB_REF#refs/tags/}"
PROJECT_VERSION=$(./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout)
if [[ "$PROJECT_VERSION" != "$TAG_NAME" ]]; then
echo "::error::pom.xml version '$PROJECT_VERSION' does not match tag '$TAG_NAME'"
exit 1
fi
- name: Download dependencies
run: ./mvnw $COMMON_MAVEN_OPTS quarkus:go-offline
- name: Build and run tests
run: ./mvnw $COMMON_MAVEN_OPTS verify
- name: Analyze with Sonar
if: vars.SONAR_ENABLED == 'true'
env:
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
SONAR_HOST_URL: ${{ vars.SONAR_HOST_URL }}
run: ./mvnw $COMMON_MAVEN_OPTS org.sonarsource.scanner.maven:sonar-maven-plugin:sonar -Dsonar.projectKey=quarkus-commons -Dsonar.projectName='quarkus-commons' -Dsonar.coverage.jacoco.xmlReportPaths=../**/target/jacoco-report/jacoco.xml
- name: Publish jars
if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/')
run: ./mvnw $COMMON_MAVEN_OPTS deploy -Dmaven.test.skip=true -Dmaven.javadoc.skip=true

View file

@ -1,97 +0,0 @@
name: Release the current version
on:
push:
branches:
- "main"
paths:
- '.github/project.yaml'
env:
COMMON_MAVEN_OPTS: "-e -B --fae"
jobs:
release:
name: Execute the release
runs-on: ubuntu-latest
steps:
- name: Checkout the code
uses: actions/checkout@v4
- name: Retrieve project metadata
uses: https://github.com/radcortez/project-metadata-action@main
id: metadata
with:
metadata-file-path: '.github/project.yaml'
local-file: true
- name: Validate current version
if: contains(steps.metadata.outputs.current-version, 'SNAPSHOT')
run: |
echo '::error::Cannot release a SNAPSHOT version.'
exit 1
- name: Validate next version
if: contains(steps.metadata.outputs.next-version, 'SNAPSHOT') == 'false'
run: |
echo '::error::Next development version should be a SNAPSHOT version.'
exit 1
- name: Set environment variables
run: |
echo "CURRENT_VERSION=${{steps.metadata.outputs.current-version}}" >> $GITHUB_ENV
echo "NEXT_VERSION=${{steps.metadata.outputs.next-version}}" >> $GITHUB_ENV
- name: Configure SSH and Git
env:
SSH_DIR: /root/.ssh
MAVEN_RELEASE_SSH_KEY: ${{ secrets.MAVEN_RELEASE_SSH_KEY }}
run: |
mkdir -p ${SSH_DIR}/
touch ${SSH_DIR}/known_hosts
ssh-keyscan -t rsa -p 2222 git-ssh.kvant.cloud >> ${SSH_DIR}/known_hosts
echo "${MAVEN_RELEASE_SSH_KEY}" | base64 -d >> ${SSH_DIR}/id_rsa
chmod 600 ${SSH_DIR}/id_rsa
git config --global user.name 'maven_release_technical_account'
git config --global user.email 'maven-release-bot@phoenix-technologies.ch'
git config --global commit.gpgsign true
git config --global gpg.format ssh
git config --global user.signingkey ${SSH_DIR}/id_rsa
- name: Set up JDK 21
uses: https://github.com/actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'
- name: Generate cache keys
id: cache-key
run: |
CURRENT_BRANCH="${{ github.ref_name }}"
CURRENT_MONTH=$(/bin/date -u "+%Y-%m")
CURRENT_DAY=$(/bin/date -u "+%d")
ROOT_CACHE_KEY="m2-cache-quarkus-commons"
echo "m2-monthly-cache-key=${ROOT_CACHE_KEY}-${CURRENT_MONTH}" >> $GITHUB_OUTPUT
echo "m2-monthly-branch-cache-key=${ROOT_CACHE_KEY}-${CURRENT_MONTH}-${CURRENT_BRANCH}" >> $GITHUB_OUTPUT
echo "m2-cache-key=${ROOT_CACHE_KEY}-${CURRENT_MONTH}-${CURRENT_BRANCH}-${CURRENT_DAY}" >> $GITHUB_OUTPUT
- name: Cache Maven Repository
id: cache-maven
uses: actions/cache@v4
with:
path: ~/.m2/repository
# The cache is per branch but in case we don't find a branch for a given branch, we will get a cache from another branch.
key: ${{ steps.cache-key.outputs.m2-cache-key }}
restore-keys: |
${{ steps.cache-key.outputs.m2-monthly-branch-cache-key }}
${{ steps.cache-key.outputs.m2-monthly-cache-key }}
- name: Make maven wrapper executable
run: chmod +x mvnw
- name: Download dependencies
run: ./mvnw $COMMON_MAVEN_OPTS quarkus:go-offline
- name: Prepare release
run: ./mvnw $COMMON_MAVEN_OPTS release:prepare -DreleaseVersion=${CURRENT_VERSION} -DdevelopmentVersion=${NEXT_VERSION}

View file

@ -1,33 +0,0 @@
name: Validate release versions
on:
pull_request:
paths:
- '.github/project.yaml'
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout the code
uses: https://github.com/actions/checkout@v4
- name: Retrieve project metadata
uses: https://github.com/radcortez/project-metadata-action@main
id: metadata
with:
metadata-file-path: '.github/project.yaml'
local-file: true
- name: Validate current version
if: contains(steps.metadata.outputs.current-version, 'SNAPSHOT')
run: |
echo '::error::Cannot release a SNAPSHOT version.'
exit 1
- name: Validate next version
if: contains(steps.metadata.outputs.next-version, 'SNAPSHOT') == false
run: |
echo '::error::Next development version should be a SNAPSHOT version.'
exit 1

View file

@ -1,13 +1,14 @@
Quarkus Commons
===============
[![Java version](https://img.shields.io/badge/Java%20version-21-brightgreen)](https://openjdk.org/projects/jdk/21/)
[![Coverage](https://sonarqube.pub.basel.kvant.cloud/api/project_badges/measure?project=quarkus-commons&metric=coverage&token=sqb_b56d9ea175c7f51f522ce63acd7fe7807643ac9e)](https://sonarqube.pub.basel.kvant.cloud/dashboard?id=quarkus-commons)
[![Duplicated Lines (%)](https://sonarqube.pub.basel.kvant.cloud/api/project_badges/measure?project=quarkus-commons&metric=duplicated_lines_density&token=sqb_b56d9ea175c7f51f522ce63acd7fe7807643ac9e)](https://sonarqube.pub.basel.kvant.cloud/dashboard?id=quarkus-commons)
[![Quality Gate Status](https://sonarqube.pub.basel.kvant.cloud/api/project_badges/measure?project=quarkus-commons&metric=alert_status&token=sqb_b56d9ea175c7f51f522ce63acd7fe7807643ac9e)](https://sonarqube.pub.basel.kvant.cloud/dashboard?id=quarkus-commons)
[![Security Rating](https://sonarqube.pub.basel.kvant.cloud/api/project_badges/measure?project=quarkus-commons&metric=security_rating&token=sqb_b56d9ea175c7f51f522ce63acd7fe7807643ac9e)](https://sonarqube.pub.basel.kvant.cloud/dashboard?id=quarkus-commons)
[![Reliability Rating](https://sonarqube.pub.basel.kvant.cloud/api/project_badges/measure?project=quarkus-commons&metric=reliability_rating&token=sqb_b56d9ea175c7f51f522ce63acd7fe7807643ac9e)](https://sonarqube.pub.basel.kvant.cloud/dashboard?id=quarkus-commons)
[![Maintainability Rating](https://sonarqube.pub.basel.kvant.cloud/api/project_badges/measure?project=quarkus-commons&metric=sqale_rating&token=sqb_b56d9ea175c7f51f522ce63acd7fe7807643ac9e)](https://sonarqube.pub.basel.kvant.cloud/dashboard?id=quarkus-commons)
[![Java version](https://img.shields.io/badge/Java%20version-21-brightgreen?style=for-the-badge)](https://openjdk.org/projects/jdk/21/)
[![Quarkus version](https://img.shields.io/badge/Quarkus%20version-3.16.2-brightgreen?style=for-the-badge)](https://quarkus.io/guides/)
[![Maven Wrapper](https://img.shields.io/badge/Maven%20version-3.9.9-brightgreen?style=for-the-badge)](https://maven.apache.org/)
<!-- TOC -->
* [Quarkus Commons](#quarkus-commons)
* [Introduction](#introduction)
<!-- TOC -->
# Introduction
@ -16,11 +17,9 @@ that can be used by Quarkus applications.
The modules are:
* `quarkus-audit-tools`
* `quarkus-clock-service`
* `quarkus-json-service`
* `quarkus-message-digest-service`
* `quarkus-random-number-generator`
* `quarkus-tracing-service`
* `quarkus-uuid-generator`

2
hooks/pre-commit Normal file
View file

@ -0,0 +1,2 @@
#!/usr/bin/env bash
./mvnw spotless:apply

101
pom.xml
View file

@ -1,59 +1,52 @@
<?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">
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"
xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<groupId>ch.phoenix.oss</groupId>
<groupId>ch.phoenixtechnologies.quarkus</groupId>
<artifactId>quarkus-commons</artifactId>
<version>1.0.9-SNAPSHOT</version>
<version>1.0.1-SNAPSHOT</version>
<packaging>pom</packaging>
<modules>
<module>quarkus-audit-tools</module>
<module>quarkus-client-logger</module>
<module>quarkus-clock-service</module>
<module>quarkus-json-service</module>
<module>quarkus-message-digest-service</module>
<module>quarkus-random-number-generator</module>
<module>quarkus-tracing-service</module>
<module>quarkus-uuid-generator</module>
</modules>
<properties>
<!-- Quarkus properties -->
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
<quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
<quarkus.platform.version>3.25.0</quarkus.platform.version>
<!-- Plugin versions -->
<compiler-plugin.version>3.14.0</compiler-plugin.version>
<surefire-plugin.version>3.5.3</surefire-plugin.version>
<spotless-plugin.version>2.46.1</spotless-plugin.version>
<palantir-java-format.version>2.72.0</palantir-java-format.version>
<jacoco-plugin.version>0.8.13</jacoco-plugin.version> <!-- Match with version from Quarkus BOM -->
<jandex-plugin.version>3.4.0</jandex-plugin.version>
<release-plugin.version>3.1.1</release-plugin.version>
<source-plugin.version>3.3.1</source-plugin.version>
<!-- Build properties -->
<maven.compiler.release>21</maven.compiler.release>
<java.version>21</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<skipITs>true</skipITs>
<compiler-plugin.version>3.13.0</compiler-plugin.version>
<surefire-plugin.version>3.5.2</surefire-plugin.version>
<jacoco-plugin.version>0.8.12</jacoco-plugin.version>
<spotless-plugin.version>2.43.0</spotless-plugin.version>
<palantir-java-format.version>2.50.0</palantir-java-format.version>
<git-build-hook-plugin.version>3.5.0</git-build-hook-plugin.version>
<quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id>
<quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id>
<quarkus.platform.version>3.16.2</quarkus.platform.version>
<!-- Dependencies -->
<java-uuid-generator.version>5.1.0</java-uuid-generator.version>
<!-- Test dependencies -->
<assertj-core.version>3.27.3</assertj-core.version>
<assertj-core.version>3.26.3</assertj-core.version>
</properties>
<distributionManagement>
<repository>
<id>phoenix-oss</id>
<url>https://git.kvant.cloud/api/packages/phoenix-oss/maven</url>
<id>kvant</id>
<url>https://git.kvant.cloud/api/packages/phoenix/maven</url>
</repository>
<snapshotRepository>
<id>phoenix-oss</id>
<url>https://git.kvant.cloud/api/packages/phoenix-oss/maven</url>
<id>kvant</id>
<url>https://git.kvant.cloud/api/packages/phoenix/maven</url>
</snapshotRepository>
</distributionManagement>
@ -69,22 +62,11 @@
</dependencies>
</dependencyManagement>
<scm>
<connection>scm:git:ssh://git@git-ssh.kvant.cloud:2222/phoenix-oss/quarkus-commons.git</connection>
<developerConnection>scm:git:ssh://git@git-ssh.kvant.cloud:2222/phoenix-oss/quarkus-commons.git</developerConnection>
<url>https://git.kvant.cloud/phoenix-oss/quarkus-commons.git</url>
<tag>HEAD</tag>
</scm>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-cyclonedx</artifactId>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
@ -138,6 +120,7 @@
<compilerArgs>
<arg>-parameters</arg>
</compilerArgs>
<release>${java.version}</release>
</configuration>
</plugin>
<plugin>
@ -166,7 +149,7 @@
</executions>
<configuration>
<java>
<removeUnusedImports />
<removeUnusedImports/>
<palantirJavaFormat>
<version>${palantir-java-format.version}</version>
<style>PALANTIR</style>
@ -176,40 +159,18 @@
</configuration>
</plugin>
<plugin>
<groupId>io.smallrye</groupId>
<artifactId>jandex-maven-plugin</artifactId>
<version>${jandex-plugin.version}</version>
<executions>
<execution>
<id>make-index</id>
<goals>
<goal>jandex</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
<version>${release-plugin.version}</version>
<groupId>com.rudikershaw.gitbuildhook</groupId>
<artifactId>git-build-hook-maven-plugin</artifactId>
<version>${git-build-hook-plugin.version}</version>
<configuration>
<tagNameFormat>@{project.version}</tagNameFormat>
<checkModificationExcludes>mvnw</checkModificationExcludes>
<scmReleaseCommitComment>chore: release @{releaseLabel}</scmReleaseCommitComment>
<scmDevelopmentCommitComment>chore: prepare for next development iteration</scmDevelopmentCommitComment>
<autoVersionSubmodules>true</autoVersionSubmodules>
<installHooks>
<pre-commit>hooks/pre-commit</pre-commit>
</installHooks>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>${source-plugin.version}</version>
<executions>
<execution>
<id>attach-sources</id>
<phase>verify</phase>
<goals>
<goal>jar-no-fork</goal>
<goal>install</goal>
</goals>
</execution>
</executions>

View file

@ -1,88 +0,0 @@
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>ch.phoenix.oss</groupId>
<artifactId>quarkus-commons</artifactId>
<version>1.0.9-SNAPSHOT</version>
</parent>
<artifactId>quarkus-audit-tools</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>ch.phoenix.oss</groupId>
<artifactId>quarkus-tracing-service</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-envers</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-orm-panache</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-postgresql</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-flyway</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-flyway-postgresql</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-elytron-security-properties-file</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-config-yaml</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco-plugin.version}</version>
<executions>
<execution>
<id>jacoco-check</id>
<goals>
<goal>check</goal>
</goals>
<phase>test</phase>
<configuration>
<dataFile>${project.build.directory}/jacoco-quarkus.exec</dataFile>
<rules>
<rule>
<element>BUNDLE</element>
<limits>
<limit>
<counter>INSTRUCTION</counter>
<value>COVEREDRATIO</value>
<minimum>1</minimum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View file

@ -1,20 +0,0 @@
package ch.phoenix.oss.quarkus.commons.audit;
import jakarta.enterprise.inject.spi.CDI;
import org.hibernate.envers.RevisionListener;
public class AuditRevisionListener implements RevisionListener {
@Override
public void newRevision(Object revisionEntity) {
var provider = CDI.current().select(RevisionContextProvider.class).get();
var rev = (Revision) revisionEntity;
rev.actor = provider.getActor();
rev.traceId = provider.getTraceId();
rev.spanId = provider.getSpanId();
rev.requestId = provider.getRequestId();
rev.clientIp = provider.getClientIp();
rev.hostName = provider.getHostName();
}
}

View file

@ -1,30 +0,0 @@
package ch.phoenix.oss.quarkus.commons.audit;
import io.quarkus.hibernate.orm.panache.PanacheEntity;
import jakarta.persistence.Column;
import jakarta.persistence.MappedSuperclass;
import java.time.Instant;
import org.hibernate.envers.NotAudited;
/**
* The goal of this class is to have on the entity itself the exact same
* timestamps as the ones from revisions generated by envers. Because of that,
* we can't use @CreationTimestamp and @UpdateTimestamp, as those timestamp values
* are managed by different Hibernate classes, so the generated values will drift.
* <p>
* Manually setting these values to match envers revisions would be error-prone,
* verbose and tedious. So, the recommendation is to implement triggers on the
* audit tables which will update the main entity whenever a revision is created.
* An example of how to do that can be found in this module's integration tests.
*/
@MappedSuperclass
public abstract class AuditedPanacheEntity extends PanacheEntity {
@NotAudited
@Column(updatable = false)
public Instant createdAt;
@NotAudited
@Column(updatable = false)
public Instant lastUpdatedAt;
}

View file

@ -1,30 +0,0 @@
package ch.phoenix.oss.quarkus.commons.audit;
import io.quarkus.hibernate.orm.panache.PanacheEntityBase;
import jakarta.persistence.Column;
import jakarta.persistence.MappedSuperclass;
import java.time.Instant;
import org.hibernate.envers.NotAudited;
/**
* The goal of this class is to have on the entity itself the exact same
* timestamps as the ones from revisions generated by envers. Because of that,
* we can't use @CreationTimestamp and @UpdateTimestamp, as those timestamp values
* are managed by different Hibernate classes, so the generated values will drift.
* <p>
* Manually setting these values to match envers revisions would be error-prone,
* verbose and tedious. So, the recommendation is to implement triggers on the
* audit tables which will update the main entity whenever a revision is created.
* An example of how to do that can be found in this module's integration tests.
*/
@MappedSuperclass
public abstract class AuditedPanacheEntityBase extends PanacheEntityBase {
@NotAudited
@Column(updatable = false)
public Instant createdAt;
@NotAudited
@Column(updatable = false)
public Instant lastUpdatedAt;
}

View file

@ -1,67 +0,0 @@
package ch.phoenix.oss.quarkus.commons.audit;
import ch.phoenix.oss.quarkus.commons.tracing.TracingService;
import io.opentelemetry.instrumentation.annotations.WithSpan;
import io.quarkus.arc.DefaultBean;
import io.quarkus.arc.Unremovable;
import io.quarkus.logging.Log;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import java.net.InetAddress;
import java.net.UnknownHostException;
@Unremovable
@DefaultBean
@ApplicationScoped
class DefaultRevisionContextProvider implements RevisionContextProvider {
private static final String UNKNOWN = "unknown";
private final TracingService tracingService;
@Inject
DefaultRevisionContextProvider(TracingService tracingService) {
this.tracingService = tracingService;
}
@Override
@WithSpan
public String getActor() {
return tracingService.getActor();
}
@Override
@WithSpan
public String getTraceId() {
return tracingService.getTraceId();
}
@Override
@WithSpan
public String getSpanId() {
return tracingService.getSpanId();
}
@Override
@WithSpan
public String getRequestId() {
return tracingService.getRequestId();
}
@Override
@WithSpan
public String getClientIp() {
return tracingService.getClientIp();
}
@Override
@WithSpan
public String getHostName() {
try {
return InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
Log.error("Unable to determine host name", e);
return UNKNOWN;
}
}
}

View file

@ -1,58 +0,0 @@
package ch.phoenix.oss.quarkus.commons.audit;
import io.quarkus.hibernate.orm.panache.PanacheEntityBase;
import jakarta.persistence.*;
import java.time.Instant;
import java.util.Objects;
import org.hibernate.envers.RevisionEntity;
import org.hibernate.envers.RevisionNumber;
import org.hibernate.envers.RevisionTimestamp;
@Entity
@Table(name = "revinfo")
@RevisionEntity(AuditRevisionListener.class)
public class Revision extends PanacheEntityBase {
@Id
@GeneratedValue
@RevisionNumber
public long rev;
@RevisionTimestamp
@Column(nullable = false, updatable = false)
public Instant timestamp;
@Column(updatable = false)
public String actor;
@Column(updatable = false)
public String traceId;
@Column(updatable = false)
public String spanId;
@Column(updatable = false)
public String requestId;
@Column(updatable = false)
public String clientIp;
@Column(updatable = false)
public String hostName;
@Override
public boolean equals(Object o) {
if (!(o instanceof Revision that)) return false;
return rev == that.rev;
}
@Override
public int hashCode() {
return Objects.hashCode(rev);
}
@Override
public String toString() {
return "Revision{rev=" + rev + '}';
}
}

View file

@ -1,16 +0,0 @@
package ch.phoenix.oss.quarkus.commons.audit;
public interface RevisionContextProvider {
String getActor();
String getTraceId();
String getSpanId();
String getRequestId();
String getClientIp();
String getHostName();
}

View file

@ -1,136 +0,0 @@
package ch.phoenix.oss.quarkus.commons.audit;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.mockito.Mockito.when;
import ch.phoenix.oss.quarkus.commons.tracing.TracingService;
import io.quarkus.narayana.jta.QuarkusTransaction;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.junit.mockito.InjectSpy;
import jakarta.inject.Inject;
import jakarta.persistence.EntityManager;
import java.time.Instant;
import org.hibernate.envers.AuditReaderFactory;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@QuarkusTest
class AuditedPanacheEntityBaseTest {
@Inject
EntityManager entityManager;
@InjectSpy
TracingService tracingService;
@SuppressWarnings("Convert2MethodRef")
@BeforeEach
void setup() {
QuarkusTransaction.requiringNew().run(() -> TestEntity2.deleteAll());
}
@Test
void persistAndUpdate() {
when(tracingService.getRequestId()).thenReturn("00000000-0000-0000-0000-000000000000");
when(tracingService.getSpanId()).thenReturn("0000000000000000");
when(tracingService.getTraceId()).thenReturn("00000000000000000000000000000000");
when(tracingService.getActor()).thenReturn("unknown");
when(tracingService.getClientIp()).thenReturn("unknown");
var now = Instant.now();
QuarkusTransaction.requiringNew().run(() -> {
var entity = new TestEntity2("something");
entity.persist();
});
QuarkusTransaction.requiringNew().run(() -> {
var entity = TestEntity2.findBySomething("something");
assertAll(
() -> assertThat(entity.createdAt)
.as("createdAt should be after or equal to expected value")
.isAfterOrEqualTo(now),
() -> assertThat(entity.lastUpdatedAt)
.as("lastUpdatedAt should be equal to createdAt")
.isEqualTo(entity.createdAt));
var auditReader = AuditReaderFactory.get(entityManager);
var revisions = auditReader.getRevisions(TestEntity2.class, entity.id);
assertThat(revisions).hasSize(1);
var revInfo = entityManager.find(Revision.class, revisions.getFirst());
assertThat(revInfo).isNotNull();
assertAll(
() -> assertThat(revInfo.timestamp)
.as("revision timestamp should be equal to entity's createdAt timestamp")
.isEqualTo(entity.createdAt),
() -> assertThat(revInfo.actor)
.as("actor should match expected value")
.isEqualTo("unknown"),
() -> assertThat(revInfo.traceId)
.as("traceId should match expected value")
.isEqualTo("00000000000000000000000000000000"),
() -> assertThat(revInfo.spanId)
.as("spanId should match expected value")
.isEqualTo("0000000000000000"),
() -> assertThat(revInfo.requestId)
.as("requestId should match expected value")
.isEqualTo("00000000-0000-0000-0000-000000000000"),
() -> assertThat(revInfo.clientIp)
.as("clientIp should match expected value")
.isEqualTo("unknown"),
() -> assertThat(revInfo.hostName)
.as("hostName should not be blank")
.isNotBlank());
});
QuarkusTransaction.requiringNew().run(() -> {
var entity = TestEntity2.findBySomething("something");
entity.something = "else";
});
QuarkusTransaction.requiringNew().run(() -> {
var entity = TestEntity2.findBySomething("else");
assertAll(() -> assertThat(entity.createdAt)
.as("createdAt should be before lastUpdatedAt")
.isBefore(entity.lastUpdatedAt));
var auditReader = AuditReaderFactory.get(entityManager);
var revisions = auditReader.getRevisions(TestEntity2.class, entity.id);
assertThat(revisions).hasSize(2);
Revision revInfo = Revision.findById(revisions.getLast());
assertThat(revInfo).isNotNull();
assertAll(
() -> assertThat(revInfo.timestamp)
.as("revision timestamp should not be equal to entity's createdAt")
.isNotEqualTo(entity.createdAt),
() -> assertThat(revInfo.timestamp)
.as("revision timestamp should be equal to entity's lastUpdatedAt")
.isEqualTo(entity.lastUpdatedAt),
() -> assertThat(revInfo.actor)
.as("actor should match expected value")
.isEqualTo("unknown"),
() -> assertThat(revInfo.traceId)
.as("traceId should match expected value")
.isEqualTo("00000000000000000000000000000000"),
() -> assertThat(revInfo.spanId)
.as("spanId should match expected value")
.isEqualTo("0000000000000000"),
() -> assertThat(revInfo.requestId)
.as("requestId should match expected value")
.isEqualTo("00000000-0000-0000-0000-000000000000"),
() -> assertThat(revInfo.clientIp)
.as("clientIp should match expected value")
.isEqualTo("unknown"),
() -> assertThat(revInfo.hostName)
.as("hostName should not be blank")
.isNotBlank());
});
}
}

View file

@ -1,136 +0,0 @@
package ch.phoenix.oss.quarkus.commons.audit;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.mockito.Mockito.when;
import ch.phoenix.oss.quarkus.commons.tracing.TracingService;
import io.quarkus.narayana.jta.QuarkusTransaction;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.junit.mockito.InjectSpy;
import jakarta.inject.Inject;
import jakarta.persistence.EntityManager;
import java.time.Instant;
import org.hibernate.envers.AuditReaderFactory;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@QuarkusTest
class AuditedPanacheEntityTest {
@Inject
EntityManager entityManager;
@InjectSpy
TracingService tracingService;
@SuppressWarnings("Convert2MethodRef")
@BeforeEach
void setup() {
QuarkusTransaction.requiringNew().run(() -> TestEntity.deleteAll());
}
@Test
void persistAndUpdate() {
when(tracingService.getRequestId()).thenReturn("00000000-0000-0000-0000-000000000000");
when(tracingService.getSpanId()).thenReturn("0000000000000000");
when(tracingService.getTraceId()).thenReturn("00000000000000000000000000000000");
when(tracingService.getActor()).thenReturn("unknown");
when(tracingService.getClientIp()).thenReturn("unknown");
var now = Instant.now();
QuarkusTransaction.requiringNew().run(() -> {
var entity = new TestEntity("something");
entity.persist();
});
QuarkusTransaction.requiringNew().run(() -> {
var entity = TestEntity.findBySomething("something");
assertAll(
() -> assertThat(entity.createdAt)
.as("createdAt should be after or equal to expected value")
.isAfterOrEqualTo(now),
() -> assertThat(entity.lastUpdatedAt)
.as("lastUpdatedAt should be equal to createdAt")
.isEqualTo(entity.createdAt));
var auditReader = AuditReaderFactory.get(entityManager);
var revisions = auditReader.getRevisions(TestEntity.class, entity.id);
assertThat(revisions).hasSize(1);
var revInfo = entityManager.find(Revision.class, revisions.getFirst());
assertThat(revInfo).isNotNull();
assertAll(
() -> assertThat(revInfo.timestamp)
.as("revision timestamp should be equal to entity's createdAt timestamp")
.isEqualTo(entity.createdAt),
() -> assertThat(revInfo.actor)
.as("actor should match expected value")
.isEqualTo("unknown"),
() -> assertThat(revInfo.traceId)
.as("traceId should match expected value")
.isEqualTo("00000000000000000000000000000000"),
() -> assertThat(revInfo.spanId)
.as("spanId should match expected value")
.isEqualTo("0000000000000000"),
() -> assertThat(revInfo.requestId)
.as("requestId should match expected value")
.isEqualTo("00000000-0000-0000-0000-000000000000"),
() -> assertThat(revInfo.clientIp)
.as("clientIp should match expected value")
.isEqualTo("unknown"),
() -> assertThat(revInfo.hostName)
.as("hostName should not be blank")
.isNotBlank());
});
QuarkusTransaction.requiringNew().run(() -> {
var entity = TestEntity.findBySomething("something");
entity.something = "else";
});
QuarkusTransaction.requiringNew().run(() -> {
var entity = TestEntity.findBySomething("else");
assertAll(() -> assertThat(entity.createdAt)
.as("createdAt should be before lastUpdatedAt")
.isBefore(entity.lastUpdatedAt));
var auditReader = AuditReaderFactory.get(entityManager);
var revisions = auditReader.getRevisions(TestEntity.class, entity.id);
assertThat(revisions).hasSize(2);
Revision revInfo = Revision.findById(revisions.getLast());
assertThat(revInfo).isNotNull();
assertAll(
() -> assertThat(revInfo.timestamp)
.as("revision timestamp should not be equal to entity's createdAt")
.isNotEqualTo(entity.createdAt),
() -> assertThat(revInfo.timestamp)
.as("revision timestamp should be equal to entity's lastUpdatedAt")
.isEqualTo(entity.lastUpdatedAt),
() -> assertThat(revInfo.actor)
.as("actor should match expected value")
.isEqualTo("unknown"),
() -> assertThat(revInfo.traceId)
.as("traceId should match expected value")
.isEqualTo("00000000000000000000000000000000"),
() -> assertThat(revInfo.spanId)
.as("spanId should match expected value")
.isEqualTo("0000000000000000"),
() -> assertThat(revInfo.requestId)
.as("requestId should match expected value")
.isEqualTo("00000000-0000-0000-0000-000000000000"),
() -> assertThat(revInfo.clientIp)
.as("clientIp should match expected value")
.isEqualTo("unknown"),
() -> assertThat(revInfo.hostName)
.as("hostName should not be blank")
.isNotBlank());
});
}
}

View file

@ -1,31 +0,0 @@
package ch.phoenix.oss.quarkus.commons.audit;
import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
import static org.mockito.Mockito.mockStatic;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import java.net.InetAddress;
import java.net.UnknownHostException;
import org.junit.jupiter.api.Test;
@QuarkusTest
class DefaultRevisionContextProviderTest {
@Inject
DefaultRevisionContextProvider underTest;
@Test
void getHostName() {
assertThat(underTest.getHostName()).isNotBlank();
}
@Test
void getHostNameWhenUnknown() {
try (var inetMock = mockStatic(InetAddress.class)) {
inetMock.when(InetAddress::getLocalHost).thenThrow(new UnknownHostException("simulated failure"));
assertThat(underTest.getHostName()).isEqualTo("unknown");
}
}
}

View file

@ -1,51 +0,0 @@
package ch.phoenix.oss.quarkus.commons.audit;
import static org.assertj.core.api.Assertions.assertThat;
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;
@QuarkusTest
class RevisionTest {
@Test
void testEquals() {
var r1 = new Revision();
r1.rev = 1;
var r2 = new Revision();
r2.rev = 1;
var r3 = new Revision();
r3.rev = 2;
assertThat(r1)
.as("Revisions equality should should match expected value")
.isEqualTo(r1)
.isEqualTo(r2)
.isNotEqualTo(r3)
.isNotEqualTo(new Object());
}
@Test
void testHashCode() {
var r1 = new Revision();
r1.rev = 123;
var r2 = new Revision();
r2.rev = 123;
var r3 = new Revision();
r3.rev = 2;
assertThat(r1.hashCode()).isEqualTo(123).isEqualTo(r2.hashCode()).isNotEqualTo(r3.hashCode());
}
@Test
void testToString() {
var rev = new Revision();
rev.rev = 1;
assertThat(rev).as("Revision's toString should match expected value").hasToString("Revision{rev=1}");
}
}

View file

@ -1,23 +0,0 @@
package ch.phoenix.oss.quarkus.commons.audit;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import org.hibernate.envers.Audited;
@Entity
@Audited
@Table(name = "test_entity")
public class TestEntity extends AuditedPanacheEntity {
public String something;
public TestEntity() {}
public TestEntity(String something) {
this.something = something;
}
public static TestEntity findBySomething(String something) {
return find("something", something).singleResult();
}
}

View file

@ -1,29 +0,0 @@
package ch.phoenix.oss.quarkus.commons.audit;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import org.hibernate.envers.Audited;
@Entity
@Audited
@Table(name = "test_entity")
public class TestEntity2 extends AuditedPanacheEntityBase {
@Id
@GeneratedValue
public Long id;
public String something;
public TestEntity2() {}
public TestEntity2(String something) {
this.something = something;
}
public static TestEntity2 findBySomething(String something) {
return find("something", something).singleResult();
}
}

View file

@ -1,22 +0,0 @@
quarkus:
flyway:
migrate-at-start: true
datasource:
db-kind: postgresql
hibernate-orm:
sql-load-script: no-file
schema-management:
strategy: none
log:
sql: true
bind-parameters: true
hibernate-envers:
audit-strategy: org.hibernate.envers.strategy.internal.ValidityAuditStrategy
revision-listener: ch.phoenix.oss.quarkus.commons.audit.AuditRevisionListener
security:
users:
embedded:
enabled: true
plain-text: true
users:
jon: doe

View file

@ -1,46 +0,0 @@
create sequence revinfo_seq start with 1 increment by 50;
create sequence test_entity_seq start with 1 increment by 50;
create table revinfo
(
rev bigint not null,
timestamp timestamp(6) with time zone not null,
actor varchar(255),
spanId varchar(255),
traceId varchar(255),
requestId varchar(255),
clientIp varchar(255),
hostName varchar(255),
primary key (rev)
);
create table test_entity
(
id bigint primary key not null,
something varchar(255),
createdAt timestamp(6) with time zone,
lastUpdatedAt timestamp(6) with time zone
);
create table test_entity_aud
(
revtype smallint,
rev bigint not null,
revend bigint,
id bigint not null,
something varchar(255),
primary key (rev, id)
);
alter table if exists test_entity_aud
add constraint fk_rev__revinfo_rev
foreign key (rev)
references revinfo;
alter table if exists test_entity_aud
add constraint fk_revend__revinfo_rev
foreign key (revend)
references revinfo;

View file

@ -1,35 +0,0 @@
CREATE OR REPLACE FUNCTION trg_test_entity_aud_apply_rev()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $func$
DECLARE
ts TIMESTAMP;
BEGIN
-- fetch the exact revision timestamp from revinfo
SELECT r.timestamp
INTO ts
FROM revinfo r
WHERE r.rev = NEW.rev;
-- only set created_at once, when still NULL
UPDATE test_entity
SET createdAt = ts
WHERE id = NEW.id
AND createdAt IS NULL;
-- always bump last_updated_at
UPDATE test_entity
SET lastUpdatedAt = ts
WHERE id = NEW.id;
RETURN NULL;
END;
$func$;
DROP TRIGGER IF EXISTS trg_test_entity_aud_after_insert
ON test_entity_aud;
CREATE TRIGGER trg_test_entity_aud_after_insert
AFTER INSERT ON test_entity_aud
FOR EACH ROW
EXECUTE FUNCTION trg_test_entity_aud_apply_rev();

View file

@ -1,37 +0,0 @@
-- 1) Create or replace the trigger function
CREATE OR REPLACE FUNCTION trg_test_entity_aud_apply_rev()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $func$
DECLARE
ts TIMESTAMP;
BEGIN
-- fetch the exact revision timestamp from revinfo
SELECT r.timestamp
INTO ts
FROM revinfo r
WHERE r.rev = NEW.rev;
-- only set created_at once, when still NULL
UPDATE test_entity
SET created_at = ts
WHERE id = NEW.id
AND created_at IS NULL;
-- always bump last_updated_at
UPDATE test_entity
SET last_updated_at = ts
WHERE id = NEW.id;
RETURN NULL; -- AFTER trigger ignores return value
END;
$func$;
-- 2) Drop any existing trigger, then attach the new one
DROP TRIGGER IF EXISTS trg_test_entity_aud_after_insert
ON test_entity_aud;
CREATE TRIGGER trg_test_entity_aud_after_insert
AFTER INSERT ON test_entity_aud
FOR EACH ROW
EXECUTE FUNCTION trg_test_entity_aud_apply_rev();

View file

@ -1,65 +0,0 @@
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>ch.phoenix.oss</groupId>
<artifactId>quarkus-commons</artifactId>
<version>1.0.9-SNAPSHOT</version>
</parent>
<artifactId>quarkus-client-logger</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-client</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-config-yaml</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-jackson</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco-plugin.version}</version>
<executions>
<execution>
<id>jacoco-check</id>
<goals>
<goal>check</goal>
</goals>
<phase>test</phase>
<configuration>
<dataFile>${project.build.directory}/jacoco-quarkus.exec</dataFile>
<rules>
<rule>
<element>BUNDLE</element>
<limits>
<limit>
<counter>INSTRUCTION</counter>
<value>COVEREDRATIO</value>
<minimum>0.92</minimum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View file

@ -1,10 +0,0 @@
package ch.phoenix.oss.quarkus.commons.client.logger;
import org.eclipse.microprofile.config.spi.Converter;
public class LowerCaseStringConverter implements Converter<String> {
@Override
public String convert(String value) throws IllegalArgumentException, NullPointerException {
return value.toLowerCase();
}
}

View file

@ -1,119 +0,0 @@
package ch.phoenix.oss.quarkus.commons.client.logger;
import io.vertx.core.Handler;
import io.vertx.core.MultiMap;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpClientRequest;
import io.vertx.core.http.HttpClientResponse;
import jakarta.enterprise.context.Dependent;
import jakarta.inject.Inject;
import java.util.Map;
import java.util.Set;
import org.jboss.logging.Logger;
import org.jboss.resteasy.reactive.client.api.ClientLogger;
/**
* This is based on org.jboss.resteasy.reactive.client.logging.DefaultClientLogger,
* with the only change being that headers are redacted based on the Set provided
* by the configuration.
*/
@Dependent
public class RedactingClientLogger implements ClientLogger {
private static final Logger log = Logger.getLogger(RedactingClientLogger.class);
private static final String REDACTED_VALUE = "*****";
private final Set<String> redactedHeaders;
private int bodySize;
@Inject
public RedactingClientLogger(RedactingClientLoggerConfiguration configuration) {
this.redactedHeaders = configuration
.headers()
.redact()
.orElse(RedactingClientLoggerConfiguration.Headers.DEFAULT_REDACTED_HEADERS);
}
@Override
public void setBodySize(int bodySize) {
this.bodySize = bodySize;
}
@Override
public void logResponse(HttpClientResponse response, boolean redirect) {
if (!log.isDebugEnabled()) {
return;
}
//noinspection Convert2Lambda
response.bodyHandler(new Handler<>() {
@Override
public void handle(Buffer body) {
log.debugf(
"%s: %s %s, Status[%d %s], Headers[%s], Body:\n%s",
redirect ? "Redirect" : "Response",
response.request().getMethod(),
response.request().absoluteURI(),
response.statusCode(),
response.statusMessage(),
asString(response.headers()),
bodyToString(body));
}
});
}
@Override
public void logRequest(HttpClientRequest request, Buffer body, boolean omitBody) {
if (!log.isDebugEnabled()) {
return;
}
if (omitBody) {
log.debugf(
"Request: %s %s Headers[%s], Body omitted",
request.getMethod(), request.absoluteURI(), asString(request.headers()));
} else if (body == null || body.length() == 0) {
log.debugf(
"Request: %s %s Headers[%s], Empty body",
request.getMethod(), request.absoluteURI(), asString(request.headers()));
} else {
log.debugf(
"Request: %s %s Headers[%s], Body:\n%s",
request.getMethod(), request.absoluteURI(), asString(request.headers()), bodyToString(body));
}
}
private String bodyToString(Buffer body) {
if (body == null) {
return "";
} else if (bodySize <= 0) {
return body.toString();
} else {
String bodyAsString = body.toString();
return bodyAsString.substring(0, Math.min(bodySize, bodyAsString.length()));
}
}
private String asString(MultiMap headers) {
if (headers.isEmpty()) {
return "";
}
StringBuilder sb = new StringBuilder((headers.size() * (6 + 1 + 6))
+ (headers.size() - 1)); // this is a very rough estimate of a result like 'key1=value1 key2=value2'
boolean isFirst = true;
for (Map.Entry<String, String> entry : headers) {
if (isFirst) {
isFirst = false;
} else {
sb.append(' ');
}
var key = entry.getKey();
var value = redactedHeaders.contains(key.toLowerCase()) ? REDACTED_VALUE : entry.getValue();
sb.append(key).append('=').append(value);
}
return sb.toString();
}
}

View file

@ -1,20 +0,0 @@
package ch.phoenix.oss.quarkus.commons.client.logger;
import io.smallrye.config.ConfigMapping;
import io.smallrye.config.WithConverter;
import jakarta.ws.rs.core.HttpHeaders;
import java.util.Optional;
import java.util.Set;
@ConfigMapping(prefix = "phoenix.client-logger")
public interface RedactingClientLoggerConfiguration {
Headers headers();
interface Headers {
Set<String> DEFAULT_REDACTED_HEADERS = Set.of(HttpHeaders.AUTHORIZATION.toLowerCase());
Optional<Set<@WithConverter(LowerCaseStringConverter.class) String>> redact();
}
}

View file

@ -1,12 +0,0 @@
package ch.phoenix.oss.quarkus.commons.client.logger;
import io.quarkus.test.junit.QuarkusTestProfile;
import java.util.Map;
public class InfoLevelProfile implements QuarkusTestProfile {
@Override
public Map<String, String> getConfigOverrides() {
return Map.of("quarkus.log.category.\"ch.phoenix.oss.quarkus.commons.client.logger\".level", "INFO");
}
}

View file

@ -1,44 +0,0 @@
package ch.phoenix.oss.quarkus.commons.client.logger;
import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.junit.TestProfile;
import jakarta.inject.Inject;
import java.net.URI;
import java.util.Optional;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.junit.jupiter.api.Test;
@QuarkusTest
@TestProfile(InfoLevelProfile.class)
class InfoLevelTest {
@Inject
@RestClient
TestClient injectedClient;
TestClient builtClient = QuarkusRestClientBuilder.newBuilder()
.clientLogger(new RedactingClientLogger(() -> Optional::empty))
.baseUri(URI.create("http://localhost:8087"))
.build(TestClient.class);
@Test
void getWithInjectedClient() {
injectedClient.get("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "also redacted");
}
@Test
void getWithBuiltClientAndEmptyConfig() {
builtClient.get("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "not redacted");
}
@Test
void postWithInjectedClient() {
injectedClient.post("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "also redacted", "body");
}
@Test
void postWithBuiltClientAndEmptyConfig() {
builtClient.post("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "not redacted", "");
}
}

View file

@ -1,47 +0,0 @@
package ch.phoenix.oss.quarkus.commons.client.logger;
import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import java.net.URI;
import java.util.Optional;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.junit.jupiter.api.Test;
@QuarkusTest
class RedactingClientLoggerTest {
@Inject
@RestClient
TestClient injectedClient;
TestClient builtClient = QuarkusRestClientBuilder.newBuilder()
.clientLogger(new RedactingClientLogger(() -> Optional::empty))
.baseUri(URI.create("http://localhost:8087"))
.build(TestClient.class);
@Test
void getWithInjectedClient() {
injectedClient.get("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "also redacted");
}
@Test
void getWithBuiltClientAndEmptyConfig() {
builtClient.get("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "not redacted");
}
@Test
void postWithInjectedClient() {
injectedClient.post("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "also redacted", "body");
}
@Test
void postWithInjectedClientAndNullBody() {
injectedClient.post("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "also redacted", null);
}
@Test
void postWithBuiltClientAndEmptyConfig() {
builtClient.post("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "not redacted", "");
}
}

View file

@ -1,12 +0,0 @@
package ch.phoenix.oss.quarkus.commons.client.logger;
import io.quarkus.test.junit.QuarkusTestProfile;
import java.util.Map;
public class ScopeNoneProfile implements QuarkusTestProfile {
@Override
public Map<String, String> getConfigOverrides() {
return Map.of("quarkus.rest-client.logging.scope", "none");
}
}

View file

@ -1,44 +0,0 @@
package ch.phoenix.oss.quarkus.commons.client.logger;
import io.quarkus.rest.client.reactive.QuarkusRestClientBuilder;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.junit.TestProfile;
import jakarta.inject.Inject;
import java.net.URI;
import java.util.Optional;
import org.eclipse.microprofile.rest.client.inject.RestClient;
import org.junit.jupiter.api.Test;
@QuarkusTest
@TestProfile(ScopeNoneProfile.class)
class ScopeNoneTest {
@Inject
@RestClient
TestClient injectedClient;
TestClient builtClient = QuarkusRestClientBuilder.newBuilder()
.clientLogger(new RedactingClientLogger(() -> Optional::empty))
.baseUri(URI.create("http://localhost:8087"))
.build(TestClient.class);
@Test
void getWithInjectedClient() {
injectedClient.get("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "also redacted");
}
@Test
void getWithBuiltClientAndEmptyConfig() {
builtClient.get("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "not redacted");
}
@Test
void postWithInjectedClient() {
injectedClient.post("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "also redacted", "body");
}
@Test
void postWithBuiltClientAndEmptyConfig() {
builtClient.post("this will be redacted", "5c0d8e45-e402-4b71-8f84-24cc0cfd7eec", "not redacted", "");
}
}

View file

@ -1,28 +0,0 @@
package ch.phoenix.oss.quarkus.commons.client.logger;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
import org.eclipse.microprofile.rest.client.inject.RegisterRestClient;
@SuppressWarnings("UastIncorrectHttpHeaderInspection")
@RegisterRestClient(configKey = "test")
public interface TestClient {
@GET
@Path("/")
@Produces(MediaType.TEXT_PLAIN)
String get(
@HeaderParam("Authorization") String authorization,
@HeaderParam("X-Request-ID") String requestId,
@HeaderParam("X-Something-Else") String somethingElse);
@POST
@Path("/")
@Consumes(MediaType.TEXT_PLAIN)
@Produces(MediaType.TEXT_PLAIN)
String post(
@HeaderParam("Authorization") String authorization,
@HeaderParam("X-Request-ID") String requestId,
@HeaderParam("X-Something-Else") String somethingElse,
String body);
}

View file

@ -1,21 +0,0 @@
package ch.phoenix.oss.quarkus.commons.client.logger;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
@Path("/")
public class TestResource {
@GET
@Produces(MediaType.TEXT_PLAIN)
public String get() {
return "get";
}
@POST
@Consumes(MediaType.TEXT_PLAIN)
@Produces(MediaType.TEXT_PLAIN)
public String post(String body) {
return body;
}
}

View file

@ -1,20 +0,0 @@
quarkus:
http:
test-port: 8087
rest-client:
logging:
scope: request-response
body-limit: 10000
test:
url: http://localhost:${quarkus.http.test-port}
log:
category:
"ch.phoenix.oss.quarkus.commons.client.logger":
level: DEBUG
phoenix:
client-logger:
headers:
redact:
- AUTHORIZATION
- X-SOMETHING-ELSE

View file

@ -1,11 +1,13 @@
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>ch.phoenix.oss</groupId>
<groupId>ch.phoenixtechnologies.quarkus</groupId>
<artifactId>quarkus-commons</artifactId>
<version>1.0.9-SNAPSHOT</version>
<version>1.0.1-SNAPSHOT</version>
</parent>
<artifactId>quarkus-clock-service</artifactId>

View file

@ -1,19 +0,0 @@
package ch.phoenix.oss.quarkus.commons.clock;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
public interface ClockService {
Instant instant();
long currentTimeMillis();
LocalDate localDate();
LocalDateTime localDateTime();
ZonedDateTime zonedDateTime();
}

View file

@ -1,4 +1,4 @@
package ch.phoenix.oss.quarkus.commons.clock;
package ch.phoenixtechnologies.quarkus.commons.clock;
import io.quarkus.arc.DefaultBean;
import jakarta.enterprise.context.ApplicationScoped;

View file

@ -0,0 +1,10 @@
package ch.phoenixtechnologies.quarkus.commons.clock;
import java.time.Instant;
public interface ClockService {
Instant instant();
long currentTimeMillis();
}

View file

@ -1,8 +1,9 @@
package ch.phoenix.oss.quarkus.commons.clock;
package ch.phoenixtechnologies.quarkus.commons.clock;
import io.quarkus.arc.DefaultBean;
import jakarta.enterprise.context.ApplicationScoped;
import java.time.*;
import java.time.Clock;
import java.time.Instant;
@DefaultBean
@ApplicationScoped
@ -23,19 +24,4 @@ class ClockServiceImpl implements ClockService {
public long currentTimeMillis() {
return clock.millis();
}
@Override
public LocalDate localDate() {
return LocalDate.now(clock);
}
@Override
public LocalDateTime localDateTime() {
return LocalDateTime.now(clock);
}
@Override
public ZonedDateTime zonedDateTime() {
return ZonedDateTime.now(clock);
}
}

View file

@ -1,70 +0,0 @@
package ch.phoenix.oss.quarkus.commons.clock;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
import io.quarkus.test.InjectMock;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import java.time.*;
import org.junit.jupiter.api.Test;
@QuarkusTest
class ClockServiceImplTest {
@Inject
ClockService clockService;
@InjectMock
Clock clock;
@Test
void instant() {
var expected = Instant.ofEpochMilli(1729280640915L);
when(clock.instant()).thenReturn(expected);
assertThat(clockService.instant())
.as("Instant should match expected value")
.isEqualTo(expected);
}
@Test
void currentTimeMillis() {
var expected = 1729280640915L;
when(clock.millis()).thenReturn(expected);
assertThat(clockService.currentTimeMillis())
.as("Instant should match expected value")
.isEqualTo(expected);
}
@Test
void localDate() {
var instant = Instant.ofEpochMilli(1729280640915L);
when(clock.instant()).thenReturn(instant);
when(clock.getZone()).thenReturn(ZoneId.of("UTC"));
assertThat(clockService.localDate())
.as("LocalDate should match expected value")
.isEqualTo(LocalDate.parse("2024-10-18"));
}
@Test
void localDateTime() {
var instant = Instant.ofEpochMilli(1729280640915L);
when(clock.instant()).thenReturn(instant);
when(clock.getZone()).thenReturn(ZoneId.of("UTC"));
assertThat(clockService.localDateTime())
.as("LocalDateTime should match expected value")
.isEqualTo(LocalDateTime.parse("2024-10-18T19:44:00.915"));
}
@Test
void zonedDateTime() {
var instant = Instant.ofEpochMilli(1729280640915L);
when(clock.instant()).thenReturn(instant);
when(clock.getZone()).thenReturn(ZoneId.of("UTC"));
assertThat(clockService.zonedDateTime())
.as("ZonedDateTime should match expected value")
.isEqualTo(ZonedDateTime.parse("2024-10-18T19:44:00.915Z[UTC]"));
}
}

View file

@ -1,4 +1,4 @@
package ch.phoenix.oss.quarkus.commons.clock;
package ch.phoenixtechnologies.quarkus.commons.clock;
import static org.assertj.core.api.Assertions.assertThat;

View file

@ -0,0 +1,41 @@
package ch.phoenixtechnologies.quarkus.commons.clock;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;
import io.quarkus.test.InjectMock;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import java.time.Clock;
import java.time.Instant;
import org.junit.jupiter.api.Test;
@QuarkusTest
class ClockServiceImplTest {
@Inject
ClockService clockService;
@InjectMock
Clock clock;
@Test
void instant() {
var expected = Instant.ofEpochMilli(1729280640915L);
when(clock.instant()).thenReturn(expected);
assertThat(clockService.instant())
.as("Instant should match expected value")
.isEqualTo(expected);
}
@Test
void currentTimeMillis() {
var expected = 1729280640915L;
when(clock.millis()).thenReturn(expected);
assertThat(clockService.currentTimeMillis())
.as("Instant should match expected value")
.isEqualTo(expected);
}
}

View file

@ -1,11 +1,13 @@
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>ch.phoenix.oss</groupId>
<groupId>ch.phoenixtechnologies.quarkus</groupId>
<artifactId>quarkus-commons</artifactId>
<version>1.0.9-SNAPSHOT</version>
<version>1.0.1-SNAPSHOT</version>
</parent>
<artifactId>quarkus-json-service</artifactId>
@ -40,7 +42,7 @@
<limit>
<counter>INSTRUCTION</counter>
<value>COVEREDRATIO</value>
<minimum>1</minimum>
<minimum>0.83</minimum>
</limit>
</limits>
</rule>

View file

@ -1,4 +1,4 @@
package ch.phoenix.oss.quarkus.commons.json;
package ch.phoenixtechnologies.quarkus.commons.json;
import com.fasterxml.jackson.core.type.TypeReference;

View file

@ -1,4 +1,4 @@
package ch.phoenix.oss.quarkus.commons.json;
package ch.phoenixtechnologies.quarkus.commons.json;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;

View file

@ -1,4 +1,4 @@
package ch.phoenix.oss.quarkus.commons.json;
package ch.phoenixtechnologies.quarkus.commons.json;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@ -17,15 +17,6 @@ class JsonServiceImplTest {
record TestRecord(String name, int age) {}
static class CircularReference {
CircularReference reference;
@Override
public String toString() {
return "CircularReference";
}
}
@Inject
JsonServiceImpl jsonService;
@ -38,17 +29,6 @@ class JsonServiceImplTest {
assertThat(actual).as("Json should match expected value").isEqualTo(expected);
}
@Test
void toJsonWithCircularReference() {
var circularReference = new CircularReference();
circularReference.reference = circularReference;
assertThatThrownBy(() -> jsonService.toJson(circularReference))
.as("Should throw IllegalArgumentException when input is invalid")
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Unable to write object as json String: CircularReference");
}
@Test
void fromJsonWithClass() {
var json = "{\"name\":\"John Doe\",\"age\":30}";
@ -67,7 +47,7 @@ class JsonServiceImplTest {
.as("Should throw IllegalArgumentException when input is invalid")
.isInstanceOf(IllegalArgumentException.class)
.hasMessage(
"Unable to read object of class [ch.phoenix.oss.quarkus.commons.json.JsonServiceImplTest$TestRecord] from json String: {\"name\":\"John Doe\",\"age\":\"30\"");
"Unable to read object of class [ch.phoenixtechnologies.quarkus.commons.json.JsonServiceImplTest$TestRecord] from json String: {\"name\":\"John Doe\",\"age\":\"30\"");
}
@Test
@ -90,6 +70,6 @@ class JsonServiceImplTest {
.as("Should throw IllegalArgumentException when input is invalid")
.isInstanceOf(IllegalArgumentException.class)
.hasMessage(
"Unable to read object of type [java.util.List<ch.phoenix.oss.quarkus.commons.json.JsonServiceImplTest$TestRecord>] from json String: {\"name\":\"John Doe\",\"age\":30},{\"name\":\"Jane Doe\",\"age\":\"25\"}");
"Unable to read object of type [java.util.List<ch.phoenixtechnologies.quarkus.commons.json.JsonServiceImplTest$TestRecord>] from json String: {\"name\":\"John Doe\",\"age\":30},{\"name\":\"Jane Doe\",\"age\":\"25\"}");
}
}

View file

@ -1,11 +1,13 @@
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>ch.phoenix.oss</groupId>
<groupId>ch.phoenixtechnologies.quarkus</groupId>
<artifactId>quarkus-commons</artifactId>
<version>1.0.9-SNAPSHOT</version>
<version>1.0.1-SNAPSHOT</version>
</parent>
<artifactId>quarkus-message-digest-service</artifactId>

View file

@ -1,4 +1,4 @@
package ch.phoenix.oss.quarkus.commons.digest;
package ch.phoenixtechnologies.quarkus.commons.digest;
import io.smallrye.config.ConfigMapping;
import io.smallrye.config.WithDefault;

View file

@ -1,4 +1,4 @@
package ch.phoenix.oss.quarkus.commons.digest;
package ch.phoenixtechnologies.quarkus.commons.digest;
import java.nio.charset.Charset;
import java.security.MessageDigest;

View file

@ -1,4 +1,4 @@
package ch.phoenix.oss.quarkus.commons.digest;
package ch.phoenixtechnologies.quarkus.commons.digest;
import io.quarkus.arc.DefaultBean;
import jakarta.enterprise.context.ApplicationScoped;

View file

@ -1,4 +1,4 @@
package ch.phoenix.oss.quarkus.commons.digest;
package ch.phoenixtechnologies.quarkus.commons.digest;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;

View file

@ -1,11 +1,13 @@
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>ch.phoenix.oss</groupId>
<groupId>ch.phoenixtechnologies.quarkus</groupId>
<artifactId>quarkus-commons</artifactId>
<version>1.0.9-SNAPSHOT</version>
<version>1.0.1-SNAPSHOT</version>
</parent>
<artifactId>quarkus-random-number-generator</artifactId>

View file

@ -1,4 +1,4 @@
package ch.phoenix.oss.quarkus.commons.random;
package ch.phoenixtechnologies.quarkus.commons.random;
import java.math.BigInteger;
import java.util.Random;

View file

@ -1,4 +1,4 @@
package ch.phoenix.oss.quarkus.commons.random;
package ch.phoenixtechnologies.quarkus.commons.random;
import io.quarkus.arc.DefaultBean;
import jakarta.enterprise.context.ApplicationScoped;

View file

@ -1,4 +1,4 @@
package ch.phoenix.oss.quarkus.commons.random;
package ch.phoenixtechnologies.quarkus.commons.random;
import java.util.Random;

View file

@ -1,4 +1,4 @@
package ch.phoenix.oss.quarkus.commons.random;
package ch.phoenixtechnologies.quarkus.commons.random;
import io.quarkus.arc.DefaultBean;
import jakarta.enterprise.context.ApplicationScoped;

View file

@ -1,4 +1,4 @@
package ch.phoenix.oss.quarkus.commons.random;
package ch.phoenixtechnologies.quarkus.commons.random;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;

View file

@ -1,4 +1,4 @@
package ch.phoenix.oss.quarkus.commons.random;
package ch.phoenixtechnologies.quarkus.commons.random;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

View file

@ -1,78 +0,0 @@
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>ch.phoenix.oss</groupId>
<artifactId>quarkus-commons</artifactId>
<version>1.0.9-SNAPSHOT</version>
</parent>
<artifactId>quarkus-tracing-service</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-rest-jackson</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-opentelemetry</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-security</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-elytron-security-properties-file</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-config-yaml</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<version>${jacoco-plugin.version}</version>
<executions>
<execution>
<id>jacoco-check</id>
<goals>
<goal>check</goal>
</goals>
<phase>test</phase>
<configuration>
<dataFile>${project.build.directory}/jacoco-quarkus.exec</dataFile>
<rules>
<rule>
<element>BUNDLE</element>
<limits>
<limit>
<counter>INSTRUCTION</counter>
<value>COVEREDRATIO</value>
<minimum>0.95</minimum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View file

@ -1,10 +0,0 @@
package ch.phoenix.oss.quarkus.commons.tracing;
import org.eclipse.microprofile.config.spi.Converter;
public class LowerCaseStringConverter implements Converter<String> {
@Override
public String convert(String value) throws IllegalArgumentException, NullPointerException {
return value.toLowerCase();
}
}

View file

@ -1,58 +0,0 @@
package ch.phoenix.oss.quarkus.commons.tracing;
import io.smallrye.config.ConfigMapping;
import io.smallrye.config.WithConverter;
import io.smallrye.config.WithDefault;
import jakarta.ws.rs.core.HttpHeaders;
import java.util.Optional;
import java.util.Set;
@ConfigMapping(prefix = "phoenix.commons.tracing")
public interface TracingConfiguration {
RequestFilterConfiguration requestFilter();
interface RequestFilterConfiguration {
Headers headers();
interface Headers {
Set<String> DEFAULT_REDACTED = Set.of(HttpHeaders.AUTHORIZATION.toLowerCase());
/**
* Optional set of headers to redact when tracing. By default, redacts
* the 'Authorization' header.
*
* @return the set of headers to be redacted
*/
Optional<Set<@WithConverter(LowerCaseStringConverter.class) String>> redact();
}
Path path();
interface Path {
@WithDefault("false")
boolean includeRaw();
}
Query query();
interface Query {
Set<String> DEFAULT_REDACTED = Set.of("access_token", "refresh_token", "apikey");
@WithDefault("false")
boolean includeRaw();
/**
* Optional set of query params to redact when tracing. By default, redacts
* the following params: 'access_token', 'refresh_token' and 'apikey'.
*
* @return the set of query params to be redacted
*/
Optional<Set<@WithConverter(LowerCaseStringConverter.class) String>> redact();
}
}
}

View file

@ -1,18 +0,0 @@
package ch.phoenix.oss.quarkus.commons.tracing;
public class TracingConstants {
public static final String ACTOR = "actor";
public static final String ANONYMOUS = "anonymous";
public static final String REQUEST_ROUTE = "request.route";
public static final String REQUEST_METHOD = "request.method";
public static final String REQUEST_PATH_RAW = "request.path.raw";
public static final String REQUEST_PATH_PARAMS = "request.path.params";
public static final String REQUEST_QUERY_RAW = "request.query.raw";
public static final String REQUEST_QUERY_PARAMS = "request.query.params";
public static final String REQUEST_HEADERS = "request.headers";
public static final String REQUEST_CLIENT_IP = "request.client.ip";
public static final String SCHEDULER_JOB_NAME = "scheduler.job.name";
private TracingConstants() {}
}

View file

@ -1,151 +0,0 @@
package ch.phoenix.oss.quarkus.commons.tracing;
import io.quarkus.arc.Unremovable;
import io.quarkus.logging.Log;
import io.quarkus.security.identity.SecurityIdentity;
import io.vertx.ext.web.RoutingContext;
import jakarta.inject.Inject;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.container.ContainerRequestContext;
import jakarta.ws.rs.container.ResourceInfo;
import java.util.List;
import org.jboss.resteasy.reactive.server.ServerRequestFilter;
@Unremovable
public class TracingRequestFilter {
private static final String REDACTED_VALUE = "********";
private final RoutingContext routingContext;
private final TracingService tracingService;
private final SecurityIdentity securityIdentity;
private final TracingConfiguration configuration;
@Inject
public TracingRequestFilter(
RoutingContext routingContext,
TracingService tracingService,
SecurityIdentity securityIdentity,
TracingConfiguration configuration) {
this.routingContext = routingContext;
this.tracingService = tracingService;
this.securityIdentity = securityIdentity;
this.configuration = configuration;
}
@ServerRequestFilter
public void filter(ContainerRequestContext requestContext, ResourceInfo resourceInfo) {
if (securityIdentity.isAnonymous()) {
tracingService.trace(TracingConstants.ACTOR, TracingConstants.ANONYMOUS);
} else {
tracingService.trace(
TracingConstants.ACTOR, securityIdentity.getPrincipal().getName());
}
var method = requestContext.getMethod();
tracingService.trace(TracingConstants.REQUEST_METHOD, method);
var routePattern = getRoutePattern(resourceInfo);
tracingService.trace(TracingConstants.REQUEST_ROUTE, routePattern);
var uriInfo = requestContext.getUriInfo();
uriInfo.getPathParameters()
.forEach((key, value) ->
tracingService.trace(TracingConstants.REQUEST_PATH_PARAMS + '.' + key, joinStrings(value)));
if (configuration.requestFilter().path().includeRaw()) {
tracingService.trace(
TracingConstants.REQUEST_PATH_RAW, uriInfo.getAbsolutePath().getRawPath());
}
var redactedHeaders = configuration
.requestFilter()
.headers()
.redact()
.orElse(TracingConfiguration.RequestFilterConfiguration.Headers.DEFAULT_REDACTED);
requestContext.getHeaders().forEach((key, value) -> {
var lowerCaseKey = key.toLowerCase();
var property = TracingConstants.REQUEST_HEADERS + '.' + lowerCaseKey;
if (redactedHeaders.contains(lowerCaseKey)) {
tracingService.trace(property, REDACTED_VALUE);
} else {
tracingService.trace(property, joinStrings(value));
}
});
var redactedQueryParams = configuration
.requestFilter()
.query()
.redact()
.orElse(TracingConfiguration.RequestFilterConfiguration.Query.DEFAULT_REDACTED);
uriInfo.getQueryParameters().forEach((key, value) -> {
var lowerCaseKey = key.toLowerCase();
var property = TracingConstants.REQUEST_QUERY_PARAMS + '.' + lowerCaseKey;
if (redactedQueryParams.contains(lowerCaseKey)) {
tracingService.trace(property, REDACTED_VALUE);
} else {
tracingService.trace(property, joinStrings(value));
}
});
if (configuration.requestFilter().query().includeRaw()) {
var rawQuery = uriInfo.getRequestUri().getRawQuery();
if (rawQuery != null && !rawQuery.isBlank()) {
tracingService.trace(TracingConstants.REQUEST_QUERY_RAW, rawQuery);
}
}
tracingService.trace(
TracingConstants.REQUEST_CLIENT_IP,
routingContext.request().connection().remoteAddress(true).hostAddress());
Log.debugf("Incoming request: %s %s", method, uriInfo.getAbsolutePath().getRawPath());
}
private static String joinStrings(List<String> value) {
return String.join(", ", value);
}
private String getRoutePattern(ResourceInfo resourceInfo) {
String classPath = getPathValue(resourceInfo.getResourceClass().getAnnotation(Path.class));
String methodPath = getPathValue(resourceInfo.getResourceMethod().getAnnotation(Path.class));
if (!classPath.isEmpty()) {
if (methodPath.isEmpty()) {
return "/" + classPath;
} else {
return "/" + classPath + "/" + methodPath;
}
} else {
if (methodPath.isEmpty()) {
return "/";
} else {
return "/" + methodPath;
}
}
}
private static String getPathValue(Path path) {
if (path == null) {
return "";
}
return trimSlashes(path.value());
}
private static String trimSlashes(String segment) {
if (segment.isEmpty() || "/".equals(segment)) {
return "";
}
// Assuming that it's not possible for a segment to contain //,
// thus it's possible to avoid extra ifs and whiles, as well
// as the use of regexes
int start = (segment.charAt(0) == '/') ? 1 : 0;
int end = (segment.charAt(segment.length() - 1) == '/') ? segment.length() - 1 : segment.length();
return segment.substring(start, end);
}
}

View file

@ -1,24 +0,0 @@
package ch.phoenix.oss.quarkus.commons.tracing;
public interface TracingService {
void clearAll();
void trace(String key, Object value);
String getActor();
String getRequestPathRaw();
String getRequestMethod();
String getRequestId();
String getTraceId();
String getSpanId();
String getClientIp();
String getSchedulerJob();
}

View file

@ -1,69 +0,0 @@
package ch.phoenix.oss.quarkus.commons.tracing;
import io.opentelemetry.api.trace.Span;
import io.quarkus.arc.DefaultBean;
import io.quarkus.logging.Log;
import jakarta.enterprise.context.ApplicationScoped;
import org.jboss.logging.MDC;
@DefaultBean
@ApplicationScoped
class TracingServiceImpl implements TracingService {
private final Span span;
TracingServiceImpl(Span span) {
this.span = span;
}
@Override
public void clearAll() {
MDC.clear();
}
@Override
public void trace(final String key, final Object value) {
Log.tracef("tracing key=%s value=%s", key, value);
MDC.put(key, value);
}
@Override
public String getActor() {
return (String) MDC.get(TracingConstants.ACTOR);
}
@Override
public String getRequestPathRaw() {
return (String) MDC.get(TracingConstants.REQUEST_PATH_RAW);
}
@Override
public String getRequestMethod() {
return (String) MDC.get(TracingConstants.REQUEST_METHOD);
}
@Override
public String getRequestId() {
return (String) MDC.get(TracingConstants.REQUEST_HEADERS + ".x-request-id");
}
@Override
public String getTraceId() {
return span.getSpanContext().getTraceId();
}
@Override
public String getSpanId() {
return span.getSpanContext().getSpanId();
}
@Override
public String getClientIp() {
return (String) MDC.get(TracingConstants.REQUEST_CLIENT_IP);
}
@Override
public String getSchedulerJob() {
return (String) MDC.get(TracingConstants.SCHEDULER_JOB_NAME);
}
}

View file

@ -1,44 +0,0 @@
package ch.phoenix.oss.quarkus.commons.tracing;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.startsWith;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.junit.mockito.InjectSpy;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import org.junit.jupiter.api.Test;
@QuarkusTest
class ActorTest {
@InjectSpy
TracingService tracingService;
@Test
void getAuthenticated() {
var route = "/authenticated";
RestAssured.given()
.auth()
.basic("jon", "doe")
.accept(ContentType.TEXT)
.when()
.get(route)
.then()
.statusCode(200);
verify(tracingService).trace("actor", "jon");
verify(tracingService).trace("request.method", "GET");
verify(tracingService).trace("request.route", route);
verify(tracingService).trace("request.headers.accept", "text/plain");
verify(tracingService).trace("request.headers.accept-encoding", "gzip,deflate");
verify(tracingService).trace("request.headers.authorization", "********");
verify(tracingService).trace("request.headers.connection", "Keep-Alive");
verify(tracingService).trace(eq("request.headers.host"), startsWith("localhost:"));
verify(tracingService).trace(eq("request.headers.user-agent"), startsWith("Apache-HttpClient"));
verify(tracingService).trace("request.client.ip", "127.0.0.1");
verifyNoMoreInteractions(tracingService);
}
}

View file

@ -1,54 +0,0 @@
package ch.phoenix.oss.quarkus.commons.tracing;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.startsWith;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.junit.mockito.InjectSpy;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import org.junit.jupiter.api.Test;
@QuarkusTest
class QueryParamTest {
@InjectSpy
TracingService tracingService;
@Test
void traceQueryParams() {
var route = "/authenticated";
RestAssured.given()
.auth()
.basic("jon", "doe")
.accept(ContentType.TEXT)
.header("X-SOMETHING-ELSE", "whatever")
.queryParam("access_token", "api123")
.queryParam("refresh_token", "refresh123")
.queryParam("apikey", "apikey123")
.queryParam("grant_type", "authorization_code")
.when()
.get(route)
.then()
.statusCode(200);
verify(tracingService).trace("actor", "jon");
verify(tracingService).trace("request.method", "GET");
verify(tracingService).trace("request.route", route);
verify(tracingService).trace("request.headers.accept", "text/plain");
verify(tracingService).trace("request.headers.accept-encoding", "gzip,deflate");
verify(tracingService).trace("request.headers.authorization", "********");
verify(tracingService).trace("request.headers.connection", "Keep-Alive");
verify(tracingService).trace(eq("request.headers.host"), startsWith("localhost:"));
verify(tracingService).trace(eq("request.headers.user-agent"), startsWith("Apache-HttpClient"));
verify(tracingService).trace("request.headers.x-something-else", "whatever");
verify(tracingService).trace("request.query.params.access_token", "********");
verify(tracingService).trace("request.query.params.refresh_token", "********");
verify(tracingService).trace("request.query.params.apikey", "********");
verify(tracingService).trace("request.query.params.grant_type", "authorization_code");
verify(tracingService).trace("request.client.ip", "127.0.0.1");
verifyNoMoreInteractions(tracingService);
}
}

View file

@ -1,46 +0,0 @@
package ch.phoenix.oss.quarkus.commons.tracing;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.startsWith;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.junit.TestProfile;
import io.quarkus.test.junit.mockito.InjectSpy;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import org.junit.jupiter.api.Test;
@QuarkusTest
@TestProfile(Test2Profile.class)
class RawPathTest {
@InjectSpy
TracingService tracingService;
@Test
void getWithRawPath() {
var route = "/no-leading-and-trailing/{param}/{param2}";
RestAssured.given()
.accept(ContentType.TEXT)
.when()
.get(route, 1, 2)
.then()
.statusCode(200);
verify(tracingService).trace("actor", "anonymous");
verify(tracingService).trace("request.method", "GET");
verify(tracingService).trace("request.route", route);
verify(tracingService).trace("request.path.params.param", "1");
verify(tracingService).trace("request.path.params.param2", "2");
verify(tracingService).trace("request.path.raw", "/no-leading-and-trailing/1/2");
verify(tracingService).trace("request.headers.accept", "text/plain");
verify(tracingService).trace("request.headers.accept-encoding", "gzip,deflate");
verify(tracingService).trace("request.headers.connection", "Keep-Alive");
verify(tracingService).trace(eq("request.headers.host"), startsWith("localhost:"));
verify(tracingService).trace(eq("request.headers.user-agent"), startsWith("Apache-HttpClient"));
verify(tracingService).trace("request.client.ip", "127.0.0.1");
verifyNoMoreInteractions(tracingService);
}
}

View file

@ -1,61 +0,0 @@
package ch.phoenix.oss.quarkus.commons.tracing;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.startsWith;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.junit.TestProfile;
import io.quarkus.test.junit.mockito.InjectSpy;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import org.junit.jupiter.api.Test;
@QuarkusTest
@TestProfile(Test2Profile.class)
class RedactedTest {
@InjectSpy
TracingService tracingService;
@Test
void traceRedactedValues() {
var route = "/authenticated";
RestAssured.given()
.auth()
.basic("jon", "doe")
.accept(ContentType.TEXT)
.header("X-SOMETHING-ELSE", "whatever")
.queryParam("access_token", "api123")
.queryParam("refresh_token", "refresh123")
.queryParam("apikey", "apikey123")
.queryParam("grant_type", "authorization_code")
.when()
.get(route)
.then()
.statusCode(200);
verify(tracingService).trace("actor", "jon");
verify(tracingService).trace("request.method", "GET");
verify(tracingService).trace("request.route", route);
verify(tracingService).trace("request.path.raw", route);
verify(tracingService).trace("request.headers.accept", "text/plain");
verify(tracingService).trace("request.headers.accept-encoding", "gzip,deflate");
verify(tracingService).trace("request.headers.authorization", "********");
verify(tracingService).trace("request.headers.connection", "Keep-Alive");
verify(tracingService).trace(eq("request.headers.host"), startsWith("localhost:"));
verify(tracingService).trace(eq("request.headers.user-agent"), startsWith("Apache-HttpClient"));
verify(tracingService).trace("request.headers.x-something-else", "********");
verify(tracingService).trace("request.query.params.access_token", "********");
verify(tracingService).trace("request.query.params.refresh_token", "refresh123");
verify(tracingService).trace("request.query.params.apikey", "apikey123");
verify(tracingService).trace("request.query.params.grant_type", "authorization_code");
verify(tracingService)
.trace(
"request.query.raw",
"access_token=api123&refresh_token=refresh123&apikey=apikey123&grant_type=authorization_code");
verify(tracingService).trace("request.client.ip", "127.0.0.1");
verifyNoMoreInteractions(tracingService);
}
}

View file

@ -1,87 +0,0 @@
package ch.phoenix.oss.quarkus.commons.tracing;
import static org.junit.jupiter.params.provider.Arguments.arguments;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.startsWith;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.junit.mockito.InjectSpy;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import java.util.Map;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
@QuarkusTest
class RoutePatternTest {
@InjectSpy
TracingService tracingService;
static Stream<Arguments> get() {
return Stream.of(
arguments("/", Map.of()),
arguments("/leading-and-no-trailing", Map.of()),
arguments("/leading/{param}/{param2}", Map.of("param", "1", "param2", "2")),
arguments("/{param}/{param2}/trailing", Map.of("param", "1", "param2", "2")),
arguments("/leading-and-no-trailing/{param}", Map.of("param", "1")),
arguments("/leading-and-no-trailing/{param}/{param2}", Map.of("param", "1", "param2", "2")),
arguments("/leading-and-trailing", Map.of()),
arguments("/leading-and-trailing/{param}", Map.of("param", "1")),
arguments("/leading-and-trailing/{param}/{param2}", Map.of("param", "1", "param2", "2")),
arguments("/no-leading-and-no-trailing", Map.of()),
arguments("/no-leading-and-no-trailing/{param}", Map.of("param", "1")),
arguments("/no-leading-and-no-trailing/{param}/{param2}", Map.of("param", "1", "param2", "2")),
arguments("/no-leading-and-trailing", Map.of()),
arguments("/no-leading-and-trailing/{param}", Map.of("param", "1")),
arguments("/no-leading-and-trailing/{param}/{param2}", Map.of("param", "1", "param2", "2")));
}
@MethodSource
@ParameterizedTest
void get(String route, Map<String, String> pathParams) {
RestAssured.given()
.accept(ContentType.TEXT)
.when()
.get(route, pathParams)
.then()
.statusCode(200);
verify(tracingService).trace("actor", "anonymous");
verify(tracingService).trace("request.method", "GET");
verify(tracingService).trace("request.route", route);
pathParams.forEach((key, value) -> verify(tracingService).trace("request.path.params." + key, value));
verify(tracingService).trace("request.headers.accept", "text/plain");
verify(tracingService).trace("request.headers.accept-encoding", "gzip,deflate");
verify(tracingService).trace("request.headers.connection", "Keep-Alive");
verify(tracingService).trace(eq("request.headers.host"), startsWith("localhost:"));
verify(tracingService).trace(eq("request.headers.user-agent"), startsWith("Apache-HttpClient"));
verify(tracingService).trace("request.client.ip", "127.0.0.1");
verifyNoMoreInteractions(tracingService);
}
@Test
void postBlankResource() {
var route = "/";
RestAssured.given().accept(ContentType.TEXT).when().post(route).then().statusCode(200);
verify(tracingService).trace("actor", "anonymous");
verify(tracingService).trace("request.method", "POST");
verify(tracingService).trace("request.route", route);
verify(tracingService).trace("request.headers.accept", "text/plain");
verify(tracingService).trace("request.headers.accept-encoding", "gzip,deflate");
verify(tracingService).trace("request.headers.connection", "Keep-Alive");
verify(tracingService).trace("request.headers.content-length", "0");
verify(tracingService)
.trace("request.headers.content-type", "application/x-www-form-urlencoded; charset=ISO-8859-1");
verify(tracingService).trace(eq("request.headers.host"), startsWith("localhost:"));
verify(tracingService).trace(eq("request.headers.user-agent"), startsWith("Apache-HttpClient"));
verify(tracingService).trace("request.client.ip", "127.0.0.1");
verifyNoMoreInteractions(tracingService);
}
}

View file

@ -1,11 +0,0 @@
package ch.phoenix.oss.quarkus.commons.tracing;
import io.quarkus.test.junit.QuarkusTestProfile;
public class Test2Profile implements QuarkusTestProfile {
@Override
public String getConfigProfile() {
return "test2";
}
}

View file

@ -1,98 +0,0 @@
package ch.phoenix.oss.quarkus.commons.tracing;
import static org.assertj.core.api.Assertions.assertThat;
import io.opentelemetry.api.trace.Span;
import io.opentelemetry.instrumentation.annotations.WithSpan;
import io.quarkus.test.junit.QuarkusTest;
import jakarta.inject.Inject;
import org.jboss.logging.MDC;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@QuarkusTest
class TracingServiceImplTest {
@Inject
TracingService tracingService;
@Inject
Span span;
@BeforeEach
void setUp() {
MDC.clear();
}
@Test
void getActor() {
tracingService.trace("actor", "abc");
assertThat(tracingService.getActor())
.as("Actor should match expected value")
.isEqualTo("abc");
}
@Test
void getRequestPathRaw() {
tracingService.trace("request.path.raw", "/foo/bar");
assertThat(tracingService.getRequestPathRaw())
.as("Request Path Raw should match expected value")
.isEqualTo("/foo/bar");
}
@Test
void getRequestMethod() {
tracingService.trace("request.method", "GET");
assertThat(tracingService.getRequestMethod())
.as("Request Method should match expected value")
.isEqualTo("GET");
}
@Test
void getRequestId() {
tracingService.trace("request.headers.x-request-id", "ba458367-bfeb-46ba-87da-50b9343be8f9");
assertThat(tracingService.getRequestId())
.as("Request Id should match expected value")
.isEqualTo("ba458367-bfeb-46ba-87da-50b9343be8f9");
}
@Test
@WithSpan
void getTraceId() {
assertThat(tracingService.getTraceId())
.as("Request Trace Id should match expected value")
.isEqualTo(span.getSpanContext().getTraceId());
}
@Test
@WithSpan
void getSpanId() {
assertThat(tracingService.getSpanId())
.as("Request Span Id should match expected value")
.isEqualTo(span.getSpanContext().getSpanId());
}
@Test
void getClientIp() {
tracingService.trace("request.client.ip", "127.0.0.1");
assertThat(tracingService.getClientIp())
.as("Request Client Iü should match expected value")
.isEqualTo("127.0.0.1");
}
@Test
void getSchedulerJob() {
tracingService.trace("scheduler.job.name", "scheduler/abc");
assertThat(tracingService.getSchedulerJob())
.as("Scheduler Job Name should match expected value")
.isEqualTo("scheduler/abc");
}
@Test
void clearAll() {
tracingService.trace("aaa", "bbb");
assertThat(MDC.get("aaa")).isEqualTo("bbb");
tracingService.clearAll();
assertThat(MDC.get("aaa")).isNull();
}
}

View file

@ -1,15 +0,0 @@
package ch.phoenix.oss.quarkus.commons.tracing.resource;
import io.quarkus.security.Authenticated;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
@Authenticated
@Path("authenticated")
public class AuthenticatedResource {
@GET
public String get() {
return "Hello user";
}
}

View file

@ -1,25 +0,0 @@
package ch.phoenix.oss.quarkus.commons.tracing.resource;
import jakarta.annotation.security.PermitAll;
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;
@Path("")
@Produces(MediaType.TEXT_PLAIN)
public class BlankResource {
@GET
@PermitAll
public String get() {
return "get";
}
@POST
@Path("")
public String post() {
return "post";
}
}

View file

@ -1,29 +0,0 @@
package ch.phoenix.oss.quarkus.commons.tracing.resource;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path("/leading-and-no-trailing")
@Produces(MediaType.TEXT_PLAIN)
public class LeadingAndNoTrailingResource {
@GET
@Path("")
public String root() {
return "root";
}
@GET
@Path("/{param}")
public String singleParam(String param) {
return param;
}
@GET
@Path("/{param}/{param2}")
public String multiParam(String param, String param2) {
return param + "/" + param2;
}
}

View file

@ -1,29 +0,0 @@
package ch.phoenix.oss.quarkus.commons.tracing.resource;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path("/leading-and-trailing/")
@Produces(MediaType.TEXT_PLAIN)
public class LeadingAndTrailingResource {
@GET
@Path("/")
public String root() {
return "root";
}
@GET
@Path("/{param}/")
public String singleParam(String param) {
return param;
}
@GET
@Path("/{param}/{param2}/")
public String multiParam(String param, String param2) {
return param + "/" + param2;
}
}

View file

@ -1,29 +0,0 @@
package ch.phoenix.oss.quarkus.commons.tracing.resource;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path("no-leading-and-no-trailing")
@Produces(MediaType.TEXT_PLAIN)
public class NoLeadingAndNoTrailingResource {
@GET
@Path("")
public String root() {
return "root";
}
@GET
@Path("{param}")
public String singleParam(String param) {
return param;
}
@GET
@Path("{param}/{param2}")
public String multiParam(String param, String param2) {
return param + "/" + param2;
}
}

View file

@ -1,29 +0,0 @@
package ch.phoenix.oss.quarkus.commons.tracing.resource;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path("no-leading-and-trailing/")
@Produces(MediaType.TEXT_PLAIN)
public class NoLeadingAndTrailingResource {
@GET
@Path("/")
public String root() {
return "root";
}
@GET
@Path("{param}/")
public String singleParam(String param) {
return param;
}
@GET
@Path("{param}/{param2}/")
public String multiParam(String param, String param2) {
return param + "/" + param2;
}
}

View file

@ -1,23 +0,0 @@
package ch.phoenix.oss.quarkus.commons.tracing.resource;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
@Path("/")
@Produces(MediaType.TEXT_PLAIN)
public class SlashResource {
@GET
@Path("/leading/{param}/{param2}")
public String doubleLeading(int param, int param2) {
return "leading/" + param + "/" + param2;
}
@GET
@Path("{param}/{param2}/trailing/")
public String doubleTrailing(int param, int param2) {
return param + "/" + param2 + "/trailing";
}
}

View file

@ -1,39 +0,0 @@
quarkus:
http:
test-port: 0
log:
category:
"org.apache.http.wire":
level: DEBUG # set to DEBUG when RestAssured logs are necessary to understand test failures
otel:
traces:
exporter: none
security:
users:
embedded:
enabled: true
plain-text: true
users:
jon: doe
"%test2":
quarkus:
log:
min-level: TRACE
category:
"ch.phoenix.oss.quarkus.commons.tracing":
level: TRACE
phoenix:
commons:
tracing:
request-filter:
path:
include-raw: true
headers:
redact:
- AUTHORIZATION
- X-SOMETHING-ELSE
query:
include-raw: true
redact:
- ACCESS_TOKEN

View file

@ -1,11 +1,13 @@
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>ch.phoenix.oss</groupId>
<groupId>ch.phoenixtechnologies.quarkus</groupId>
<artifactId>quarkus-commons</artifactId>
<version>1.0.9-SNAPSHOT</version>
<version>1.0.1-SNAPSHOT</version>
</parent>
<artifactId>quarkus-uuid-generator</artifactId>

View file

@ -1,4 +1,4 @@
package ch.phoenix.oss.quarkus.commons.uuid;
package ch.phoenixtechnologies.quarkus.commons.uuid;
import java.util.Random;
import java.util.UUID;

View file

@ -1,4 +1,4 @@
package ch.phoenix.oss.quarkus.commons.uuid;
package ch.phoenixtechnologies.quarkus.commons.uuid;
import com.fasterxml.uuid.Generators;
import io.quarkus.arc.DefaultBean;

View file

@ -1,4 +1,4 @@
package ch.phoenix.oss.quarkus.commons.uuid;
package ch.phoenixtechnologies.quarkus.commons.uuid;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;