diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index a7664538..79ce0d33 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -15,9 +15,9 @@ body: attributes: label: "Checklist" options: - - label: "I am able to reproduce the bug with the [latest version](https://github.com/xdev-software/tci-base/releases/latest)" + - label: "I am able to reproduce the bug with the [latest version](https://github.com/xdev-software/tci/releases/latest)" required: true - - label: "I made sure that there are *no existing issues* - [open](https://github.com/xdev-software/tci-base/issues) or [closed](https://github.com/xdev-software/tci-base/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to." + - label: "I made sure that there are *no existing issues* - [open](https://github.com/xdev-software/tci/issues) or [closed](https://github.com/xdev-software/tci/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to." required: true - label: "I have taken the time to fill in all the required details. I understand that the bug report will be dismissed otherwise." required: true diff --git a/.github/ISSUE_TEMPLATE/enhancement.yml b/.github/ISSUE_TEMPLATE/enhancement.yml index 969cec6f..a056e968 100644 --- a/.github/ISSUE_TEMPLATE/enhancement.yml +++ b/.github/ISSUE_TEMPLATE/enhancement.yml @@ -13,7 +13,7 @@ body: attributes: label: "Checklist" options: - - label: "I made sure that there are *no existing issues* - [open](https://github.com/xdev-software/tci-base/issues) or [closed](https://github.com/xdev-software/tci-base/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to." + - label: "I made sure that there are *no existing issues* - [open](https://github.com/xdev-software/tci/issues) or [closed](https://github.com/xdev-software/tci/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to." required: true - label: "I have taken the time to fill in all the required details. I understand that the feature request will be dismissed otherwise." required: true diff --git a/.github/ISSUE_TEMPLATE/question.yml b/.github/ISSUE_TEMPLATE/question.yml index f925875d..cd9432fc 100644 --- a/.github/ISSUE_TEMPLATE/question.yml +++ b/.github/ISSUE_TEMPLATE/question.yml @@ -12,7 +12,7 @@ body: attributes: label: "Checklist" options: - - label: "I made sure that there are *no existing issues* - [open](https://github.com/xdev-software/tci-base/issues) or [closed](https://github.com/xdev-software/tci-base/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to." + - label: "I made sure that there are *no existing issues* - [open](https://github.com/xdev-software/tci/issues) or [closed](https://github.com/xdev-software/tci/issues?q=is%3Aissue+is%3Aclosed) - which I could contribute my information to." required: true - label: "I have taken the time to fill in all the required details. I understand that the question will be dismissed otherwise." required: true diff --git a/.github/workflows/check-build.yml b/.github/workflows/check-build.yml index 749c33e3..6ccad5d5 100644 --- a/.github/workflows/check-build.yml +++ b/.github/workflows/check-build.yml @@ -29,7 +29,7 @@ jobs: strategy: matrix: - java: [17, 21] + java: [21] distribution: [temurin] steps: @@ -70,7 +70,7 @@ jobs: strategy: matrix: - java: [17] + java: [21] distribution: [temurin] steps: @@ -93,7 +93,7 @@ jobs: strategy: matrix: - java: [17] + java: [21] distribution: [temurin] steps: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c183aa76..ba909285 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,9 +4,6 @@ on: push: branches: [ master ] -env: - PRIMARY_MAVEN_MODULE: ${{ github.event.repository.name }} - permissions: contents: write pull-requests: write @@ -21,7 +18,7 @@ jobs: - name: Set up JDK uses: actions/setup-java@v4 with: - java-version: '17' + java-version: '21' distribution: 'temurin' cache: 'maven' @@ -51,7 +48,7 @@ jobs: needs: [check-code] timeout-minutes: 10 outputs: - upload_url: ${{ steps.create_release.outputs.upload_url }} + upload_url: ${{ steps.create-release.outputs.upload_url }} steps: - uses: actions/checkout@v4 @@ -74,10 +71,9 @@ jobs: - name: Get version id: version run: | - version=$(../mvnw help:evaluate -Dexpression=project.version -q -DforceStdout) + version=$(./mvnw help:evaluate -Dexpression=project.version -q -DforceStdout) echo "release=$version" >> $GITHUB_OUTPUT echo "releasenumber=${version//[!0-9]/}" >> $GITHUB_OUTPUT - working-directory: ${{ env.PRIMARY_MAVEN_MODULE }} - name: Commit and Push run: | @@ -88,7 +84,7 @@ jobs: git push origin --tags - name: Create Release - id: create_release + id: create-release uses: shogo82148/actions-create-release@4661dc54f7b4b564074e9fbf73884d960de569a3 # v1 with: tag_name: v${{ steps.version.outputs.release }} @@ -102,8 +98,8 @@ jobs: Add the following lines to your pom: ```XML - software.xdev - ${{ env.PRIMARY_MAVEN_MODULE }} + software.xdev.tci + corresponding_module ${{ steps.version.outputs.release }} ``` @@ -124,7 +120,7 @@ jobs: - name: Set up JDK uses: actions/setup-java@v4 with: # running setup-java again overwrites the settings.xml - java-version: '17' + java-version: '21' distribution: 'temurin' server-id: sonatype-central-portal server-username: MAVEN_CENTRAL_USERNAME @@ -133,12 +129,17 @@ jobs: gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} - name: Publish to Central Portal - run: ../mvnw -B deploy -P publish-sonatype-central-portal -DskipTests + run: | + modules=("bom") + dependency_management_block=$(grep -ozP '(\r|\n|.)*<\/dependencyManagement>' 'bom/pom.xml' | tr -d '\0') + modules+=($(echo $dependency_management_block | grep -oP '(?<=)[^<]+')) + printf -v modules_joined '%s,' "${modules[@]}" + modules_arg=$(echo "${modules_joined%,}") + ./mvnw -B deploy -pl "$modules_arg" -am -T2C -P publish-sonatype-central-portal -DskipTests env: MAVEN_CENTRAL_USERNAME: ${{ secrets.SONATYPE_MAVEN_CENTRAL_PORTAL_USERNAME }} MAVEN_CENTRAL_TOKEN: ${{ secrets.SONATYPE_MAVEN_CENTRAL_PORTAL_TOKEN }} MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }} - working-directory: ${{ env.PRIMARY_MAVEN_MODULE }} publish-pages: runs-on: ubuntu-latest @@ -156,19 +157,27 @@ jobs: - name: Setup - Java uses: actions/setup-java@v4 with: - java-version: '17' + java-version: '21' distribution: 'temurin' cache: 'maven' - name: Build site - run: ../mvnw -B compile site -DskipTests -T2C - working-directory: ${{ env.PRIMARY_MAVEN_MODULE }} + run: ./mvnw -B compile site -DskipTests -T2C + + - name: Aggregate site + run: | + modules=($(grep -ozP '(?<=module>)[^<]+' 'pom.xml' | tr -d '\0')) + for m in "${modules[@]}" + do + echo "$m/target/site -> ./target/site/$m" + cp -r $m/target/site ./target/site/$m + done - name: Deploy to Github pages uses: peaceiris/actions-gh-pages@4f9cc6602d3f66b9c108549d475ec49e8ef4d45e # v4 with: github_token: ${{ secrets.GITHUB_TOKEN }} - publish_dir: ./${{ env.PRIMARY_MAVEN_MODULE }}/target/site + publish_dir: ./target/site force_orphan: true after-release: diff --git a/.github/workflows/run-integration-tests.yml b/.github/workflows/run-integration-tests.yml index 6903755e..167f24f4 100644 --- a/.github/workflows/run-integration-tests.yml +++ b/.github/workflows/run-integration-tests.yml @@ -46,7 +46,7 @@ jobs: - name: Test run: | ./mvnw -B test \ - -pl "tci-advanced-demo/${{ matrix.project }}" -am \ + -pl "advanced-demo/integration-tests/${{ matrix.project }}" -am \ -P run-it \ ${{ matrix.pre-start && '-Dinfra-pre-start.enabled=1 ' || '' }} \ ${{ matrix.parallel > 0 && format('-Djunit.jupiter.execution.parallel.enabled=true -Djunit.jupiter.execution.parallel.mode.default=concurrent -Djunit.jupiter.execution.parallel.mode.classes.default=concurrent -Djunit.jupiter.execution.parallel.config.strategy=fixed -Djunit.jupiter.execution.parallel.config.fixed.parallelism=2 -Djunit.jupiter.execution.parallel.config.fixed.max-pool-size={0} ', matrix.parallel) || '' }} @@ -63,5 +63,5 @@ jobs: uses: actions/upload-artifact@v4 with: name: test-fail-videos-${{ matrix.java }}-${{ env.PROJECT_NORMALIZED }}-${{ matrix.parallel }}-${{ matrix.pre-start }} - path: ${{ matrix.project }}/target/records + path: advanced-demo/integration-tests/${{ matrix.project }}/target/records if-no-files-found: ignore diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index df6dbb7e..d7b94a14 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -51,7 +51,7 @@ jobs: uses: actions/setup-java@v4 with: distribution: 'temurin' - java-version: 17 + java-version: 21 - name: Cache SonarCloud packages uses: actions/cache@v4 diff --git a/.github/workflows/test-deploy.yml b/.github/workflows/test-deploy.yml index 8a858912..b9ca7fce 100644 --- a/.github/workflows/test-deploy.yml +++ b/.github/workflows/test-deploy.yml @@ -17,7 +17,7 @@ jobs: uses: actions/setup-java@v4 with: # running setup-java again overwrites the settings.xml distribution: 'temurin' - java-version: '17' + java-version: '21' server-id: sonatype-central-portal server-username: MAVEN_CENTRAL_USERNAME server-password: MAVEN_CENTRAL_TOKEN @@ -25,8 +25,13 @@ jobs: gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} - name: Publish to Central Portal - run: ../mvnw -B deploy -P publish-sonatype-central-portal -DskipTests - working-directory: ${{ env.PRIMARY_MAVEN_MODULE }} + run: | + modules=("bom") + dependency_management_block=$(grep -ozP '(\r|\n|.)*<\/dependencyManagement>' 'bom/pom.xml' | tr -d '\0') + modules+=($(echo $dependency_management_block | grep -oP '(?<=)[^<]+')) + printf -v modules_joined '%s,' "${modules[@]}" + modules_arg=$(echo "${modules_joined%,}") + ./mvnw -B deploy -pl "$modules_arg" -am -T2C -P publish-sonatype-central-portal -DskipTests env: MAVEN_CENTRAL_USERNAME: ${{ secrets.SONATYPE_MAVEN_CENTRAL_PORTAL_USERNAME }} MAVEN_CENTRAL_TOKEN: ${{ secrets.SONATYPE_MAVEN_CENTRAL_PORTAL_TOKEN }} diff --git a/.gitignore b/.gitignore index 5c850540..14a1fb4d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,16 +1,12 @@ # Maven target/ -pom.xml.tag -pom.xml.releaseBackup -pom.xml.versionsBackup -pom.xml.next -release.properties dependency-reduced-pom.xml -buildNumber.properties -.mvn/timing.properties -# https://github.com/takari/maven-wrapper#usage-without-binary-jar + +# Maven Wrapper .mvn/wrapper/maven-wrapper.jar +# Maven Flatten Plugin +.flattened-pom.xml # Compiled class file *.class @@ -18,20 +14,12 @@ buildNumber.properties # Log file *.log -# BlueJ files -*.ctxt - -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - # Package/Binary Files don't belong into a git repo *.jar *.war -*.nar *.ear *.zip *.tar.gz -*.rar *.dll *.exe *.bin @@ -39,27 +27,11 @@ buildNumber.properties # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* -# JRebel -**/resources/rebel.xml -**/resources/rebel-remote.xml - -# eclispe stuff for root -/.settings/ -/.classpath -/.project - - -# eclispe stuff for modules -/*/.metadata/ -/*/.apt_generated_tests/ -/*/.settings/ -/*/.classpath -/*/.project -/*/RemoteSystemsTempFiles/ - -#custom -.flattened-pom.xml -.tern-project +# Eclipse +.metadata +.settings +.classpath +.project # == IntelliJ == *.iml diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ea66e28..ea1c14ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +# 2.0.0 + +> [!WARNING] +> This release contains breaking changes + +* ⚠ Renamed ``tci-base`` to ``tci`` +* ⚠ Moved maven coordinates ``software.xdev:tci-base`` to ``software.xdev.tci:base`` +* Added common modules, like selenium, db-jdbc-orm, mockserver, ... #208 + * Updated demo accordingly +* Added ``EnvironmentPerformance`` which currently tracks ``cpuSlownessFactor`` +* Refactoring and code cleanup + # 1.2.0 * [PreStart] Make it possible to "snapshot" containers and use these snapshots to speed up subsequent containers * Recommended for containers that highly depend on storage (e.g. databases) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e27d83dc..b9ab603b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -34,10 +34,10 @@ You should have the following things installed: * Ensure that the JDK/Java-Version is correct -## Releasing [![Build](https://img.shields.io/github/actions/workflow/status/xdev-software/tci-base/release.yml?branch=master)](https://github.com/xdev-software/tci-base/actions/workflows/release.yml) +## Releasing [![Build](https://img.shields.io/github/actions/workflow/status/xdev-software/tci/release.yml?branch=master)](https://github.com/xdev-software/tci/actions/workflows/release.yml) Before releasing: -* Consider doing a [test-deployment](https://github.com/xdev-software/tci-base/actions/workflows/test-deploy.yml?query=branch%3Adevelop) before actually releasing. +* Consider doing a [test-deployment](https://github.com/xdev-software/tci/actions/workflows/test-deploy.yml?query=branch%3Adevelop) before actually releasing. * Check the [changelog](CHANGELOG.md) If the ``develop`` is ready for release, create a pull request to the ``master``-Branch and merge the changes diff --git a/PERFORMANCE.md b/PERFORMANCE.md index d18d630e..504f481f 100644 --- a/PERFORMANCE.md +++ b/PERFORMANCE.md @@ -31,7 +31,7 @@ _As of: 2024-05-03_ ## Live -A live comparison using [GitHub actions is also available](https://github.com/xdev-software/tci-base/actions/workflows/run-integration-tests.yml). +A live comparison using [GitHub actions is also available](https://github.com/xdev-software/tci/actions/workflows/run-integration-tests.yml). > [!NOTE] > Format explanation of ``run-integration-tests (21, webapp-it, 2, true)`` diff --git a/README.md b/README.md index 554d0d1e..29287a16 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,34 @@ -[![Latest version](https://img.shields.io/maven-central/v/software.xdev/tci-base?logo=apache%20maven)](https://mvnrepository.com/artifact/software.xdev/tci-base) -[![Build](https://img.shields.io/github/actions/workflow/status/xdev-software/tci-base/check-build.yml?branch=develop)](https://github.com/xdev-software/tci-base/actions/workflows/check-build.yml?query=branch%3Adevelop) -[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=xdev-software_tci-base&metric=alert_status)](https://sonarcloud.io/dashboard?id=xdev-software_tci-base) -[![javadoc](https://javadoc.io/badge2/software.xdev/tci-base/javadoc.svg)](https://javadoc.io/doc/software.xdev/tci-base) +[![Latest version](https://img.shields.io/maven-central/v/software.xdev.tci/bom?logo=apache%20maven)](https://mvnrepository.com/artifact/software.xdev.tci/bom) +[![Build](https://img.shields.io/github/actions/workflow/status/xdev-software/tci/check-build.yml?branch=develop)](https://github.com/xdev-software/tci/actions/workflows/check-build.yml?query=branch%3Adevelop) +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=xdev-software_tci&metric=alert_status)](https://sonarcloud.io/dashboard?id=xdev-software_tci) +[![javadoc](https://javadoc.io/badge2/software.xdev.tci/base/javadoc.svg)](https://javadoc.io/doc/software.xdev.tci/base) -# Testcontainers Infrastructure (TCI) Framework Base +# Testcontainers Infrastructure (TCI) Framework -Basis Module for XDEV's Testcontainer Infrastructure Framework +Modules for XDEV's Testcontainer Infrastructure Framework -## Features -| Feature | Why? | Demo | -| --- | --- | --- | -| Easily create infrastructure using TCI[JD](https://javadoc.io/doc/software.xdev/tci-base/latest/software/xdev/tci/TCI.html) (TestContainer Infrastructure) templating + Factories for that | Makes writing and designing tests easier | [here](./tci-base-demo/src/test/java/software/xdev/tci/dummyinfra/) | -| [PreStarting mechanism](./tci-base/src/main/java/software/xdev/tci/factory/prestart/)[JD](https://javadoc.io/doc/software.xdev/tci-base/latest/software/xdev/tci/factory/prestart/PreStartableTCIFactory.html) for [additional performance](./PERFORMANCE.md) | Tries to run tests as fast as possible - with a few trade-offs | [here](./tci-base-demo/src/test/java/software/xdev/tci/factory/prestart/) | -| All started containers have a unique human-readable name | Easier identification when tracing or debugging | [here](./tci-base-demo/src/test/java/software/xdev/tci/safestart/) | -| An optimized [implementation of Network](./tci-base/src/main/java/software/xdev/tci/network/)[JD](https://javadoc.io/doc/software.xdev/tci-base/latest/software/xdev/tci/network/LazyNetwork.html) | Addresses various problems of the original implementation to speed up tests | [here](./tci-base-demo/src/test/java/software/xdev/tci/network/) | -| [Safe starting of named containers](./tci-base/src/main/java/software/xdev/tci/safestart/)[JD](https://javadoc.io/doc/software.xdev/tci-base/latest/software/xdev/tci/safestart/SafeNamedContainerStarter.html) | Ensures that a container doesn't enter a crash loop during retried startups | [here](./tci-base-demo/src/test/java/software/xdev/tci/safestart/) | -| [Container Leak detection](./tci-base/src/main/java/software/xdev/tci/leakdetection/)¹ | Prevents you from running out of resources | [here](./tci-base-demo/src/test/java/software/xdev/tci/leak/) | -| [Tracing](./tci-base/src/main/java/software/xdev/tci/tracing/)¹ | Makes finding bottlenecks and similar problems easier | | +## Modules -¹ = Active by default due to service loading +* [base](./base/) +* [bom](./bom/) +* [db-jdbc-orm](./db-jdbc-orm/) +* [jul-to-slf4j](./jul-to-slf4j/) +* [mockserver](./mockserver/) +* [oidc-server-mock](./oidc-server-mock/) +* [selenium](./selenium/) +* [spring-dao-support](./spring-dao-support/) ## Usage -Take a look at the [minimalistic demo](./tci-base-demo/) that showcases the components individually. -You may also checkout the [advanced demo](./tci-advanced-demo/) - a reference implementation of all features in a realistic project - to get a better feeling how this can be done. +You may checkout the [advanced demo](./advanced-demo/) - a reference implementation of most features in a realistic project - to get a better feeling how the project can be used. + +You can also have a look at the corresponding modules for usage instructions. > [!TIP] -> More detailed documentation is usually available in the corresponding [JavaDocs](https://javadoc.io/doc/software.xdev/tci-base). +> More detailed documentation is usually available in the corresponding [JavaDocs](https://javadoc.io/doc/software.xdev.tci/base). ## Installation -[Installation guide for the latest release](https://github.com/xdev-software/tci-base/releases/latest#Installation) +[Installation guide for the latest release](https://github.com/xdev-software/tci/releases/latest#Installation) ## Support If you need support as soon as possible and you can't wait for any pull request, feel free to use [our support](https://xdev.software/en/services/support). @@ -38,4 +37,4 @@ If you need support as soon as possible and you can't wait for any pull request, See the [contributing guide](./CONTRIBUTING.md) for detailed instructions on how to get started with our project. ## Dependencies and Licenses -View the [license of the current project](LICENSE) or the [summary including all dependencies](https://xdev-software.github.io/tci-base/dependencies) +View the [license of the current project](LICENSE) or the [summary including all dependencies](https://xdev-software.github.io/tci/dependencies) diff --git a/SECURITY.md b/SECURITY.md index ec7182f1..67392671 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,4 +2,4 @@ ## Reporting a Vulnerability -Please report a security vulnerability [on GitHub Security Advisories](https://github.com/xdev-software/tci-base/security/advisories/new). +Please report a security vulnerability [on GitHub Security Advisories](https://github.com/xdev-software/tci/security/advisories/new). diff --git a/tci-advanced-demo/.mvn/wrapper/maven-wrapper.properties b/advanced-demo/.mvn/wrapper/maven-wrapper.properties similarity index 100% rename from tci-advanced-demo/.mvn/wrapper/maven-wrapper.properties rename to advanced-demo/.mvn/wrapper/maven-wrapper.properties diff --git a/tci-advanced-demo/README.md b/advanced-demo/README.md similarity index 100% rename from tci-advanced-demo/README.md rename to advanced-demo/README.md diff --git a/tci-advanced-demo/_dev_infra/.gitignore b/advanced-demo/_dev_infra/.gitignore similarity index 100% rename from tci-advanced-demo/_dev_infra/.gitignore rename to advanced-demo/_dev_infra/.gitignore diff --git a/tci-advanced-demo/_dev_infra/README.md b/advanced-demo/_dev_infra/README.md similarity index 100% rename from tci-advanced-demo/_dev_infra/README.md rename to advanced-demo/_dev_infra/README.md diff --git a/tci-advanced-demo/_dev_infra/db.env.template b/advanced-demo/_dev_infra/db.env.template similarity index 100% rename from tci-advanced-demo/_dev_infra/db.env.template rename to advanced-demo/_dev_infra/db.env.template diff --git a/tci-advanced-demo/_dev_infra/docker-compose.yml b/advanced-demo/_dev_infra/docker-compose.yml similarity index 100% rename from tci-advanced-demo/_dev_infra/docker-compose.yml rename to advanced-demo/_dev_infra/docker-compose.yml diff --git a/tci-advanced-demo/_dev_infra/oidc-user-config.json.template b/advanced-demo/_dev_infra/oidc-user-config.json.template similarity index 100% rename from tci-advanced-demo/_dev_infra/oidc-user-config.json.template rename to advanced-demo/_dev_infra/oidc-user-config.json.template diff --git a/tci-advanced-demo/_resource_metrics/README.md b/advanced-demo/_resource_metrics/README.md similarity index 100% rename from tci-advanced-demo/_resource_metrics/README.md rename to advanced-demo/_resource_metrics/README.md diff --git a/tci-advanced-demo/_resource_metrics/docker-compose.yml b/advanced-demo/_resource_metrics/docker-compose.yml similarity index 100% rename from tci-advanced-demo/_resource_metrics/docker-compose.yml rename to advanced-demo/_resource_metrics/docker-compose.yml diff --git a/tci-advanced-demo/_resource_metrics/grafana/dashboards/node-exporter-full.json b/advanced-demo/_resource_metrics/grafana/dashboards/node-exporter-full.json similarity index 100% rename from tci-advanced-demo/_resource_metrics/grafana/dashboards/node-exporter-full.json rename to advanced-demo/_resource_metrics/grafana/dashboards/node-exporter-full.json diff --git a/tci-advanced-demo/_resource_metrics/grafana/provisioning/dashboards/local.yml b/advanced-demo/_resource_metrics/grafana/provisioning/dashboards/local.yml similarity index 100% rename from tci-advanced-demo/_resource_metrics/grafana/provisioning/dashboards/local.yml rename to advanced-demo/_resource_metrics/grafana/provisioning/dashboards/local.yml diff --git a/tci-advanced-demo/_resource_metrics/grafana/provisioning/datasources/datasource.yml b/advanced-demo/_resource_metrics/grafana/provisioning/datasources/datasource.yml similarity index 100% rename from tci-advanced-demo/_resource_metrics/grafana/provisioning/datasources/datasource.yml rename to advanced-demo/_resource_metrics/grafana/provisioning/datasources/datasource.yml diff --git a/tci-advanced-demo/_resource_metrics/prometheus/prometheus.yml b/advanced-demo/_resource_metrics/prometheus/prometheus.yml similarity index 100% rename from tci-advanced-demo/_resource_metrics/prometheus/prometheus.yml rename to advanced-demo/_resource_metrics/prometheus/prometheus.yml diff --git a/tci-advanced-demo/entities-metamodel/README.md b/advanced-demo/entities-metamodel/README.md similarity index 100% rename from tci-advanced-demo/entities-metamodel/README.md rename to advanced-demo/entities-metamodel/README.md diff --git a/tci-advanced-demo/entities-metamodel/pom.xml b/advanced-demo/entities-metamodel/pom.xml similarity index 97% rename from tci-advanced-demo/entities-metamodel/pom.xml rename to advanced-demo/entities-metamodel/pom.xml index 7a5d7cdf..72df8e52 100644 --- a/tci-advanced-demo/entities-metamodel/pom.xml +++ b/advanced-demo/entities-metamodel/pom.xml @@ -6,8 +6,8 @@ 4.0.0 software.xdev.tci.demo - tci-advanced-demo - 1.2.1-SNAPSHOT + advanced-demo + 2.0.0-SNAPSHOT entities-metamodel diff --git a/tci-advanced-demo/entities-metamodel/src/gen/java/software/xdev/tci/demo/entities/IdentifiableEntity_.java b/advanced-demo/entities-metamodel/src/gen/java/software/xdev/tci/demo/entities/IdentifiableEntity_.java similarity index 100% rename from tci-advanced-demo/entities-metamodel/src/gen/java/software/xdev/tci/demo/entities/IdentifiableEntity_.java rename to advanced-demo/entities-metamodel/src/gen/java/software/xdev/tci/demo/entities/IdentifiableEntity_.java diff --git a/tci-advanced-demo/entities-metamodel/src/gen/java/software/xdev/tci/demo/entities/Product_.java b/advanced-demo/entities-metamodel/src/gen/java/software/xdev/tci/demo/entities/Product_.java similarity index 100% rename from tci-advanced-demo/entities-metamodel/src/gen/java/software/xdev/tci/demo/entities/Product_.java rename to advanced-demo/entities-metamodel/src/gen/java/software/xdev/tci/demo/entities/Product_.java diff --git a/tci-advanced-demo/entities/README.md b/advanced-demo/entities/README.md similarity index 100% rename from tci-advanced-demo/entities/README.md rename to advanced-demo/entities/README.md diff --git a/tci-advanced-demo/entities/pom.xml b/advanced-demo/entities/pom.xml similarity index 90% rename from tci-advanced-demo/entities/pom.xml rename to advanced-demo/entities/pom.xml index 3c9beae8..f84d0ef1 100644 --- a/tci-advanced-demo/entities/pom.xml +++ b/advanced-demo/entities/pom.xml @@ -6,8 +6,8 @@ 4.0.0 software.xdev.tci.demo - tci-advanced-demo - 1.2.1-SNAPSHOT + advanced-demo + 2.0.0-SNAPSHOT entities diff --git a/tci-advanced-demo/entities/src/main/java/software/xdev/tci/demo/entities/IdentifiableEntity.java b/advanced-demo/entities/src/main/java/software/xdev/tci/demo/entities/IdentifiableEntity.java similarity index 100% rename from tci-advanced-demo/entities/src/main/java/software/xdev/tci/demo/entities/IdentifiableEntity.java rename to advanced-demo/entities/src/main/java/software/xdev/tci/demo/entities/IdentifiableEntity.java diff --git a/tci-advanced-demo/entities/src/main/java/software/xdev/tci/demo/entities/Product.java b/advanced-demo/entities/src/main/java/software/xdev/tci/demo/entities/Product.java similarity index 100% rename from tci-advanced-demo/entities/src/main/java/software/xdev/tci/demo/entities/Product.java rename to advanced-demo/entities/src/main/java/software/xdev/tci/demo/entities/Product.java diff --git a/tci-advanced-demo/persistence-it/README.md b/advanced-demo/integration-tests/persistence-it/README.md similarity index 100% rename from tci-advanced-demo/persistence-it/README.md rename to advanced-demo/integration-tests/persistence-it/README.md diff --git a/tci-advanced-demo/persistence-it/pom.xml b/advanced-demo/integration-tests/persistence-it/pom.xml similarity index 83% rename from tci-advanced-demo/persistence-it/pom.xml rename to advanced-demo/integration-tests/persistence-it/pom.xml index 0e31cd2d..adc45ce3 100644 --- a/tci-advanced-demo/persistence-it/pom.xml +++ b/advanced-demo/integration-tests/persistence-it/pom.xml @@ -5,9 +5,9 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 4.0.0 - software.xdev.tci.demo - tci-advanced-demo - 1.2.1-SNAPSHOT + software.xdev.tci.demo.it + integration-tests + 2.0.0-SNAPSHOT persistence-it @@ -24,11 +24,17 @@ - software.xdev.tci.demo + software.xdev.tci.demo.it tci-db test + + software.xdev.tci + spring-dao-support + test + + org.apache.logging.log4j log4j-core diff --git a/tci-advanced-demo/persistence-it/src/test/java/software/xdev/tci/demo/persistence/base/BaseTest.java b/advanced-demo/integration-tests/persistence-it/src/test/java/software/xdev/tci/demo/persistence/base/BaseTest.java similarity index 83% rename from tci-advanced-demo/persistence-it/src/test/java/software/xdev/tci/demo/persistence/base/BaseTest.java rename to advanced-demo/integration-tests/persistence-it/src/test/java/software/xdev/tci/demo/persistence/base/BaseTest.java index 34dd6931..c0448a56 100644 --- a/tci-advanced-demo/persistence-it/src/test/java/software/xdev/tci/demo/persistence/base/BaseTest.java +++ b/advanced-demo/integration-tests/persistence-it/src/test/java/software/xdev/tci/demo/persistence/base/BaseTest.java @@ -9,6 +9,7 @@ import java.util.WeakHashMap; import java.util.concurrent.CompletableFuture; import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.extension.AfterEachCallback; @@ -21,10 +22,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import software.xdev.tci.dao.DAOInjector; +import software.xdev.tci.db.persistence.TransactionExecutor; import software.xdev.tci.demo.persistence.FlywayInfo; +import software.xdev.tci.demo.persistence.jpa.dao.BaseDAO; +import software.xdev.tci.demo.persistence.jpa.dao.TransactionReflector; import software.xdev.tci.demo.tci.db.DBTCI; import software.xdev.tci.demo.tci.db.factory.DBTCIFactory; -import software.xdev.tci.demo.tci.util.ContainerLoggingUtil; import software.xdev.tci.factory.registry.TCIFactoryRegistry; import software.xdev.tci.leakdetection.LeakDetectionAsyncReaper; import software.xdev.tci.tracing.TCITracer; @@ -42,7 +46,26 @@ abstract class BaseTest static final TCITracer.Timed TRACE_START_INFRA_MIGRATE_DB = new TCITracer.Timed(); static final TCITracer.Timed TRACE_START_INFRA_CHECK_EMC = new TCITracer.Timed(); - static final DAOInjector DAO_INJECTOR = new DAOInjector(); + static final DAOInjector DAO_INJECTOR = new DAOInjector( + BaseDAO.class, + () -> BaseDAO.class.getDeclaredField("em"), + (self, fields, dao, em) -> fields.stream() + .filter(f -> TransactionReflector.class.equals(f.getType())) + .forEach(f -> self.setIntoField( + dao, f, new TransactionReflector() + { + @Override + public void runWithTransaction(final Runnable runnable) + { + new TransactionExecutor(em).execWithTransaction(runnable); + } + + @Override + public T runWithTransaction(final Supplier supplier) + { + return new TransactionExecutor(em).execWithTransaction(supplier); + } + }))); static final DBTCIFactory DB_INFRA_FACTORY = new DBTCIFactory(false); @@ -51,8 +74,6 @@ abstract class BaseTest @BeforeAll public static void setup() { - ContainerLoggingUtil.redirectJULtoSLF4J(); - TCIFactoryRegistry.instance().warmUp(); } @@ -122,10 +143,8 @@ protected void stopInfra() { if(this.dbInfra != null) { - this.dbInfra.logDataBaseInfo(); - - final DBTCI dbInfra = this.dbInfra; - REAP_CFS.add(CompletableFuture.runAsync(dbInfra::stop)); + final DBTCI fDbInfra = this.dbInfra; + REAP_CFS.add(CompletableFuture.runAsync(fDbInfra::stop)); this.dbInfra = null; } diff --git a/tci-advanced-demo/persistence-it/src/test/java/software/xdev/tci/demo/persistence/base/InfraPerCaseTest.java b/advanced-demo/integration-tests/persistence-it/src/test/java/software/xdev/tci/demo/persistence/base/InfraPerCaseTest.java similarity index 100% rename from tci-advanced-demo/persistence-it/src/test/java/software/xdev/tci/demo/persistence/base/InfraPerCaseTest.java rename to advanced-demo/integration-tests/persistence-it/src/test/java/software/xdev/tci/demo/persistence/base/InfraPerCaseTest.java diff --git a/tci-advanced-demo/persistence-it/src/test/java/software/xdev/tci/demo/persistence/base/InfraPerClassTest.java b/advanced-demo/integration-tests/persistence-it/src/test/java/software/xdev/tci/demo/persistence/base/InfraPerClassTest.java similarity index 100% rename from tci-advanced-demo/persistence-it/src/test/java/software/xdev/tci/demo/persistence/base/InfraPerClassTest.java rename to advanced-demo/integration-tests/persistence-it/src/test/java/software/xdev/tci/demo/persistence/base/InfraPerClassTest.java diff --git a/tci-advanced-demo/persistence-it/src/test/java/software/xdev/tci/demo/persistence/cases/ProductReadonlyTest.java b/advanced-demo/integration-tests/persistence-it/src/test/java/software/xdev/tci/demo/persistence/cases/ProductReadonlyTest.java similarity index 100% rename from tci-advanced-demo/persistence-it/src/test/java/software/xdev/tci/demo/persistence/cases/ProductReadonlyTest.java rename to advanced-demo/integration-tests/persistence-it/src/test/java/software/xdev/tci/demo/persistence/cases/ProductReadonlyTest.java diff --git a/tci-advanced-demo/persistence-it/src/test/java/software/xdev/tci/demo/persistence/cases/ProductTest.java b/advanced-demo/integration-tests/persistence-it/src/test/java/software/xdev/tci/demo/persistence/cases/ProductTest.java similarity index 100% rename from tci-advanced-demo/persistence-it/src/test/java/software/xdev/tci/demo/persistence/cases/ProductTest.java rename to advanced-demo/integration-tests/persistence-it/src/test/java/software/xdev/tci/demo/persistence/cases/ProductTest.java diff --git a/tci-advanced-demo/persistence-it/src/test/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener b/advanced-demo/integration-tests/persistence-it/src/test/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener similarity index 100% rename from tci-advanced-demo/persistence-it/src/test/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener rename to advanced-demo/integration-tests/persistence-it/src/test/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener diff --git a/tci-advanced-demo/persistence-it/src/test/resources/META-INF/services/software.xdev.tci.leakdetection.LeakDetectionAsyncReaper b/advanced-demo/integration-tests/persistence-it/src/test/resources/META-INF/services/software.xdev.tci.leakdetection.LeakDetectionAsyncReaper similarity index 100% rename from tci-advanced-demo/persistence-it/src/test/resources/META-INF/services/software.xdev.tci.leakdetection.LeakDetectionAsyncReaper rename to advanced-demo/integration-tests/persistence-it/src/test/resources/META-INF/services/software.xdev.tci.leakdetection.LeakDetectionAsyncReaper diff --git a/tci-advanced-demo/persistence-it/src/test/resources/init/extended/V1.1.1_InitData.sql b/advanced-demo/integration-tests/persistence-it/src/test/resources/init/extended/V1.1.1_InitData.sql similarity index 100% rename from tci-advanced-demo/persistence-it/src/test/resources/init/extended/V1.1.1_InitData.sql rename to advanced-demo/integration-tests/persistence-it/src/test/resources/init/extended/V1.1.1_InitData.sql diff --git a/tci-advanced-demo/persistence-it/src/test/resources/log4j2-test.xml b/advanced-demo/integration-tests/persistence-it/src/test/resources/log4j2-test.xml similarity index 100% rename from tci-advanced-demo/persistence-it/src/test/resources/log4j2-test.xml rename to advanced-demo/integration-tests/persistence-it/src/test/resources/log4j2-test.xml diff --git a/advanced-demo/integration-tests/pom.xml b/advanced-demo/integration-tests/pom.xml new file mode 100644 index 00000000..392ec5a8 --- /dev/null +++ b/advanced-demo/integration-tests/pom.xml @@ -0,0 +1,100 @@ + + + 4.0.0 + + + software.xdev.tci.demo + advanced-demo + 2.0.0-SNAPSHOT + + + software.xdev.tci.demo.it + integration-tests + 2.0.0-SNAPSHOT + pom + + + persistence-it + tci-db + tci-webapp + webapp-it + + + + + + + software.xdev.tci.demo.it + tci-db + 2.0.0-SNAPSHOT + + + + software.xdev.tci.demo.it + tci-webapp + 2.0.0-SNAPSHOT + + + + software.xdev.tci + base + ${project.version} + + + software.xdev.tci + db-jdbc-orm + ${project.version} + + + software.xdev.tci + oidc-server-mock + ${project.version} + + + software.xdev.tci + selenium + ${project.version} + + + software.xdev.tci + spring-dao-support + ${project.version} + + + + + org.seleniumhq.selenium + selenium-dependencies-bom + 4.34.0 + pom + import + + + + + + org.junit + junit-bom + 5.13.2 + pom + import + + + + + software.xdev + testcontainers-advanced-imagebuilder + 2.0.1 + + + + + org.javassist + javassist + 3.30.2-GA + + + + diff --git a/tci-advanced-demo/tci-db/Dockerfile b/advanced-demo/integration-tests/tci-db/Dockerfile similarity index 100% rename from tci-advanced-demo/tci-db/Dockerfile rename to advanced-demo/integration-tests/tci-db/Dockerfile diff --git a/tci-advanced-demo/tci-db/README.md b/advanced-demo/integration-tests/tci-db/README.md similarity index 100% rename from tci-advanced-demo/tci-db/README.md rename to advanced-demo/integration-tests/tci-db/README.md diff --git a/tci-advanced-demo/tci-db/pom.xml b/advanced-demo/integration-tests/tci-db/pom.xml similarity index 83% rename from tci-advanced-demo/tci-db/pom.xml rename to advanced-demo/integration-tests/tci-db/pom.xml index 0607eb57..20f6b7b2 100644 --- a/tci-advanced-demo/tci-db/pom.xml +++ b/advanced-demo/integration-tests/tci-db/pom.xml @@ -5,20 +5,21 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 4.0.0 - software.xdev.tci.demo - tci-advanced-demo - 1.2.1-SNAPSHOT + software.xdev.tci.demo.it + integration-tests + 2.0.0-SNAPSHOT tci-db software.xdev.tci.demo - tci-testcontainers + persistence + - software.xdev.tci.demo - persistence + software.xdev.tci + db-jdbc-orm diff --git a/advanced-demo/integration-tests/tci-db/src/main/java/software/xdev/tci/demo/tci/db/DBTCI.java b/advanced-demo/integration-tests/tci-db/src/main/java/software/xdev/tci/demo/tci/db/DBTCI.java new file mode 100644 index 00000000..0e4c0589 --- /dev/null +++ b/advanced-demo/integration-tests/tci-db/src/main/java/software/xdev/tci/demo/tci/db/DBTCI.java @@ -0,0 +1,89 @@ +package software.xdev.tci.demo.tci.db; + +import java.sql.Driver; +import java.sql.SQLException; + +import javax.sql.DataSource; + +import jakarta.persistence.Entity; + +import org.mariadb.jdbc.MariaDbDataSource; + +import software.xdev.tci.db.BaseDBTCI; +import software.xdev.tci.db.persistence.EntityManagerControllerFactory; +import software.xdev.tci.db.persistence.classfinder.CachedEntityAnnotatedClassNameFinder; +import software.xdev.tci.demo.persistence.FlywayInfo; +import software.xdev.tci.demo.persistence.FlywayMigration; +import software.xdev.tci.demo.persistence.config.DefaultJPAConfig; +import software.xdev.tci.demo.tci.db.containers.DBContainer; + + +public class DBTCI extends BaseDBTCI +{ + public static final String DB_DATABASE = "test"; + public static final String DB_USERNAME = "test"; + @SuppressWarnings("java:S2068") // This is a test calm down + public static final String DB_PASSWORD = "test"; + + public DBTCI( + final DBContainer container, + final String networkAlias, + final boolean migrateAndInitializeEMC) + { + super( + container, + networkAlias, + migrateAndInitializeEMC, + () -> new EntityManagerControllerFactory(new CachedEntityAnnotatedClassNameFinder( + DefaultJPAConfig.ENTITY_PACKAGE, + Entity.class))); + this.withDatabase(DB_DATABASE) + .withUsername(DB_USERNAME) + .withPassword(DB_PASSWORD); + } + + @Override + protected void execInitialDatabaseMigration() + { + this.migrateDatabase(FlywayInfo.FLYWAY_LOOKUP_STRUCTURE); + } + + public static String getInternalJDBCUrl(final String networkAlias) + { + return "jdbc:mariadb://" + networkAlias + ":" + DBContainer.PORT + "/" + DB_DATABASE; + } + + @Override + protected Class driverClazz() + { + return org.mariadb.jdbc.Driver.class; + } + + @Override + @SuppressWarnings("java:S6437") // This is a test calm down + public DataSource createDataSource() + { + final MariaDbDataSource dataSource = new MariaDbDataSource(); + try + { + dataSource.setUser(this.username); + dataSource.setPassword(this.password); + dataSource.setUrl(this.getExternalJDBCUrl()); + } + catch(final SQLException e) + { + throw new IllegalStateException("Invalid container setup", e); + } + return dataSource; + } + + @Override + public void migrateDatabase(final String... locations) + { + new FlywayMigration().migrate(conf -> + { + conf.dataSource(this.createDataSource()); + conf.locations(locations); + }); + } +} diff --git a/tci-advanced-demo/tci-db/src/main/java/software/xdev/tci/demo/tci/db/containers/DBContainer.java b/advanced-demo/integration-tests/tci-db/src/main/java/software/xdev/tci/demo/tci/db/containers/DBContainer.java similarity index 93% rename from tci-advanced-demo/tci-db/src/main/java/software/xdev/tci/demo/tci/db/containers/DBContainer.java rename to advanced-demo/integration-tests/tci-db/src/main/java/software/xdev/tci/demo/tci/db/containers/DBContainer.java index 40be5f6a..d9ad7029 100644 --- a/tci-advanced-demo/tci-db/src/main/java/software/xdev/tci/demo/tci/db/containers/DBContainer.java +++ b/advanced-demo/integration-tests/tci-db/src/main/java/software/xdev/tci/demo/tci/db/containers/DBContainer.java @@ -3,6 +3,8 @@ import org.testcontainers.containers.wait.strategy.WaitStrategy; import org.testcontainers.utility.DockerImageName; +import software.xdev.tci.db.containers.WaitableJDBCContainer; + public class DBContainer extends MariaDBContainer diff --git a/tci-advanced-demo/tci-db/src/main/java/software/xdev/tci/demo/tci/db/containers/DBContainerBuilder.java b/advanced-demo/integration-tests/tci-db/src/main/java/software/xdev/tci/demo/tci/db/containers/DBContainerBuilder.java similarity index 92% rename from tci-advanced-demo/tci-db/src/main/java/software/xdev/tci/demo/tci/db/containers/DBContainerBuilder.java rename to advanced-demo/integration-tests/tci-db/src/main/java/software/xdev/tci/demo/tci/db/containers/DBContainerBuilder.java index 6a69bca7..00545b95 100644 --- a/tci-advanced-demo/tci-db/src/main/java/software/xdev/tci/demo/tci/db/containers/DBContainerBuilder.java +++ b/advanced-demo/integration-tests/tci-db/src/main/java/software/xdev/tci/demo/tci/db/containers/DBContainerBuilder.java @@ -35,12 +35,12 @@ public static synchronized String getBuiltImageName() final AdvancedImageFromDockerFile builder = new AdvancedImageFromDockerFile("webapp-db", false) .withLoggerForBuild(LOG_CONTAINER_BUILD) - .withBaseDirRelativeIgnoreFile(null) - .withAdditionalIgnoreLines( + .withPostGitIgnoreLines( // Ignore everything "**") .withDockerFilePath(Paths.get("../tci-db/Dockerfile")) - .withBaseDir(Paths.get("../")); + .withBaseDir(Paths.get("../")) + .withBaseDirRelativeIgnoreFile(null); try { diff --git a/tci-advanced-demo/tci-db/src/main/java/software/xdev/tci/demo/tci/db/containers/MariaDBContainer.java b/advanced-demo/integration-tests/tci-db/src/main/java/software/xdev/tci/demo/tci/db/containers/MariaDBContainer.java similarity index 100% rename from tci-advanced-demo/tci-db/src/main/java/software/xdev/tci/demo/tci/db/containers/MariaDBContainer.java rename to advanced-demo/integration-tests/tci-db/src/main/java/software/xdev/tci/demo/tci/db/containers/MariaDBContainer.java diff --git a/tci-advanced-demo/tci-db/src/main/java/software/xdev/tci/demo/tci/db/datageneration/AbstractDBDataGenerator.java b/advanced-demo/integration-tests/tci-db/src/main/java/software/xdev/tci/demo/tci/db/datageneration/AbstractDBDataGenerator.java similarity index 54% rename from tci-advanced-demo/tci-db/src/main/java/software/xdev/tci/demo/tci/db/datageneration/AbstractDBDataGenerator.java rename to advanced-demo/integration-tests/tci-db/src/main/java/software/xdev/tci/demo/tci/db/datageneration/AbstractDBDataGenerator.java index 56090435..b44fa2aa 100644 --- a/tci-advanced-demo/tci-db/src/main/java/software/xdev/tci/demo/tci/db/datageneration/AbstractDBDataGenerator.java +++ b/advanced-demo/integration-tests/tci-db/src/main/java/software/xdev/tci/demo/tci/db/datageneration/AbstractDBDataGenerator.java @@ -1,18 +1,17 @@ package software.xdev.tci.demo.tci.db.datageneration; -import java.time.LocalDate; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; -import java.util.Objects; import java.util.function.Function; import jakarta.persistence.EntityManager; +import software.xdev.tci.db.datageneration.BaseDBDataGenerator; +import software.xdev.tci.db.persistence.TransactionExecutor; import software.xdev.tci.demo.entities.IdentifiableEntity; import software.xdev.tci.demo.persistence.jpa.dao.BaseEntityDAO; -import software.xdev.tci.demo.tci.db.persistence.TransactionExecutor; /** @@ -20,55 +19,16 @@ * * @author AB */ -public abstract class AbstractDBDataGenerator implements DataGenerator +public abstract class AbstractDBDataGenerator extends BaseDBDataGenerator { - private final EntityManager em; - private final TransactionExecutor transactor; - protected AbstractDBDataGenerator(final EntityManager em) { - this(em, null); + super(em); } protected AbstractDBDataGenerator(final EntityManager em, final TransactionExecutor transactor) { - this.em = Objects.requireNonNull(em, "EntityManager can't be null!"); - this.transactor = transactor != null ? transactor : new TransactionExecutor(em); - } - - /** - * Returns the {@link EntityManager}-Instance of this generator, which can be used to save data. - */ - protected EntityManager em() - { - return this.em; - } - - /** - * Returns the {@link TransactionExecutor}-Instance of this generator, which can be used to save data with a - * transaction. - */ - protected TransactionExecutor transactor() - { - return this.transactor; - } - - /** - * Returns a {@link LocalDate} in the past. By default 01.01.1970 is used. - */ - @SuppressWarnings("checkstyle:MagicNumber") - public LocalDate getLocalDateInPast() - { - return LocalDate.of(1970, 1, 1); - } - - /** - * Returns a {@link LocalDate} in the past. - */ - @SuppressWarnings("checkstyle:MagicNumber") - public LocalDate getLocalDateInFuture() - { - return LocalDate.of(3000, 1, 1).plusYears(1); + super(em, transactor); } @SafeVarargs diff --git a/advanced-demo/integration-tests/tci-db/src/main/java/software/xdev/tci/demo/tci/db/factory/DBTCIFactory.java b/advanced-demo/integration-tests/tci-db/src/main/java/software/xdev/tci/demo/tci/db/factory/DBTCIFactory.java new file mode 100644 index 00000000..c5a6c63b --- /dev/null +++ b/advanced-demo/integration-tests/tci-db/src/main/java/software/xdev/tci/demo/tci/db/factory/DBTCIFactory.java @@ -0,0 +1,28 @@ +package software.xdev.tci.demo.tci.db.factory; + +import software.xdev.tci.db.factory.BaseDBTCIFactory; +import software.xdev.tci.demo.tci.db.DBTCI; +import software.xdev.tci.demo.tci.db.containers.DBContainer; +import software.xdev.tci.demo.tci.db.containers.DBContainerBuilder; +import software.xdev.tci.factory.prestart.snapshoting.CommitedImageSnapshotManager; +import software.xdev.tci.misc.ContainerMemory; + + +public class DBTCIFactory extends BaseDBTCIFactory +{ + public DBTCIFactory() + { + this(true); + } + + @SuppressWarnings("resource") + public DBTCIFactory(final boolean migrateAndInitializeEMC) + { + super( + (c, n) -> new DBTCI(c, n, migrateAndInitializeEMC), + () -> new DBContainer(DBContainerBuilder.getBuiltImageName()) + .withDatabaseName(DBTCI.DB_DATABASE) + .withCreateContainerCmdModifier(cmd -> cmd.getHostConfig().withMemory(ContainerMemory.M512M))); + this.withSnapshotManager(new CommitedImageSnapshotManager("/var/lib/mysql")); + } +} diff --git a/tci-advanced-demo/tci-webapp/Dockerfile b/advanced-demo/integration-tests/tci-webapp/Dockerfile similarity index 81% rename from tci-advanced-demo/tci-webapp/Dockerfile rename to advanced-demo/integration-tests/tci-webapp/Dockerfile index eedd0c4b..302adbf2 100644 --- a/tci-advanced-demo/tci-webapp/Dockerfile +++ b/advanced-demo/integration-tests/tci-webapp/Dockerfile @@ -1,3 +1,4 @@ +# syntax=docker/dockerfile:1-labs # Note: This Dockerfile is used by the integration tests for compiling the app when there was no Image was supplied ARG JAVA_VERSION=21 FROM eclipse-temurin:${JAVA_VERSION}-jre-alpine AS jre-base @@ -41,7 +42,18 @@ RUN apk add --no-cache git WORKDIR /builder -# Copying is prepared by ITC +# Copy & Cache wrapper +COPY --parents mvnw .mvn/** ./ +RUN ./mvnw --version + +# Copy & Cache poms/dependencies +COPY --parents **/pom.xml ./ +# Resolve jars so that they can be cached and don't need to be downloaded when a Java file changes +ARG MAVEN_GO_OFFLINE_COMMAND='./mvnw -B dependency:go-offline -pl webapp -am -Pprod,dev-log -DincludeScope=runtime -T2C' +RUN echo "Executing '$MAVEN_GO_OFFLINE_COMMAND'" +RUN ${MAVEN_GO_OFFLINE_COMMAND} + +# Copying all other files COPY . ./ # A valid Git repo is required for the build @@ -51,10 +63,10 @@ RUN git config --global user.email "dynamic@build.local" \ && git add . \ && git commit -m "Init commit" -ARG mavenbuildcmd='./mvnw -B clean package -pl "webapp" -am -Pprod,dev-log -T2C -Dmaven.test.skip' +ARG MAVEN_BUILD_COMMAND='./mvnw -B package -pl webapp -am -Pprod,dev-log -T2C -Dmaven.test.skip' +RUN echo "Executing '$MAVEN_BUILD_COMMAND'" +RUN ${MAVEN_BUILD_COMMAND} -RUN echo "Executing '$mavenbuildcmd'" -RUN ${mavenbuildcmd} # See also https://docs.spring.io/spring-boot/reference/packaging/container-images/dockerfiles.html for further information RUN mv webapp/target/webapp.jar app.jar \ && java -Djarmode=tools -jar app.jar extract --layers --destination extracted diff --git a/tci-advanced-demo/tci-webapp/README.md b/advanced-demo/integration-tests/tci-webapp/README.md similarity index 100% rename from tci-advanced-demo/tci-webapp/README.md rename to advanced-demo/integration-tests/tci-webapp/README.md diff --git a/tci-advanced-demo/tci-webapp/pom.xml b/advanced-demo/integration-tests/tci-webapp/pom.xml similarity index 71% rename from tci-advanced-demo/tci-webapp/pom.xml rename to advanced-demo/integration-tests/tci-webapp/pom.xml index beae18ff..f2892020 100644 --- a/tci-advanced-demo/tci-webapp/pom.xml +++ b/advanced-demo/integration-tests/tci-webapp/pom.xml @@ -5,16 +5,16 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 4.0.0 - software.xdev.tci.demo - tci-advanced-demo - 1.2.1-SNAPSHOT + software.xdev.tci.demo.it + integration-tests + 2.0.0-SNAPSHOT tci-webapp - software.xdev.tci.demo - tci-testcontainers + software.xdev.tci + base software.xdev diff --git a/tci-advanced-demo/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/WebAppTCI.java b/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/WebAppTCI.java similarity index 100% rename from tci-advanced-demo/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/WebAppTCI.java rename to advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/WebAppTCI.java diff --git a/tci-advanced-demo/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainer.java b/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainer.java similarity index 100% rename from tci-advanced-demo/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainer.java rename to advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainer.java diff --git a/tci-advanced-demo/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainerBuilder.java b/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainerBuilder.java similarity index 74% rename from tci-advanced-demo/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainerBuilder.java rename to advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainerBuilder.java index 1638bcdc..78c6c094 100644 --- a/tci-advanced-demo/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainerBuilder.java +++ b/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/containers/WebAppContainerBuilder.java @@ -8,6 +8,7 @@ import org.slf4j.LoggerFactory; import software.xdev.testcontainers.imagebuilder.AdvancedImageFromDockerFile; +import software.xdev.testcontainers.imagebuilder.compat.DockerfileCOPYParentsEmulator; @SuppressWarnings("PMD.MoreThanOneLogger") @@ -35,31 +36,31 @@ public static synchronized String getBuiltImageName() final AdvancedImageFromDockerFile builder = new AdvancedImageFromDockerFile("webapp-it-local", false) .withLoggerForBuild(LOG_CONTAINER_BUILD) - .withAdditionalIgnoreLines( + .withPostGitIgnoreLines( // Ignore git-folder, as it will be provided in the Dockerfile ".git/**", // Ignore other unused folders and extensions - ".iml", - ".cmd", - ".md", - "target/**", - ".config/**", - ".idea/**", + "*.iml", + "*.cmd", + "*.md", "_dev_infra/**", "_resource_metrics/**", // Ignore other Dockerfiles (our required file will always be transferred) "Dockerfile", // Ignore not required test-modules that may have changed // sources only - otherwise the parent pom doesn't find the resources - "tci-*/src/**", - "*-it/src/**", - "**/src/test", + "integration-tests/*/src/**", + "**/src/test/**", // Ignore resources that are just used for development - "webapp/src/main/resources-dev/**") - .withDockerFilePath(Paths.get("../tci-webapp/Dockerfile")) - .withBaseDir(Paths.get("../")) + "webapp/src/main/resources-dev/**", + // Most files from these folders need to be ignored -> Down there for highest prio + "node_modules", + "target") + .withDockerFilePath(Paths.get("../../integration-tests/tci-webapp/Dockerfile")) + .withBaseDir(Paths.get("../../")) // File is in root directory - we can't access it - .withBaseDirRelativeIgnoreFile(null); + .withBaseDirRelativeIgnoreFile(null) + .withDockerFileLinesModifier(new DockerfileCOPYParentsEmulator()); try { diff --git a/tci-advanced-demo/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/factory/WebAppTCIFactory.java b/advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/factory/WebAppTCIFactory.java similarity index 100% rename from tci-advanced-demo/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/factory/WebAppTCIFactory.java rename to advanced-demo/integration-tests/tci-webapp/src/main/java/software/xdev/tci/demo/tci/webapp/factory/WebAppTCIFactory.java diff --git a/tci-advanced-demo/webapp-it/README.md b/advanced-demo/integration-tests/webapp-it/README.md similarity index 100% rename from tci-advanced-demo/webapp-it/README.md rename to advanced-demo/integration-tests/webapp-it/README.md diff --git a/tci-advanced-demo/webapp-it/pom.xml b/advanced-demo/integration-tests/webapp-it/pom.xml similarity index 79% rename from tci-advanced-demo/webapp-it/pom.xml rename to advanced-demo/integration-tests/webapp-it/pom.xml index ad0eb566..16d0f440 100644 --- a/tci-advanced-demo/webapp-it/pom.xml +++ b/advanced-demo/integration-tests/webapp-it/pom.xml @@ -5,9 +5,9 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> 4.0.0 - software.xdev.tci.demo - tci-advanced-demo - 1.2.1-SNAPSHOT + software.xdev.tci.demo.it + integration-tests + 2.0.0-SNAPSHOT webapp-it @@ -18,22 +18,22 @@ - software.xdev.tci.demo + software.xdev.tci.demo.it tci-db test - software.xdev.tci.demo - tci-oidc + software.xdev.tci + oidc-server-mock test - software.xdev.tci.demo - tci-selenium + software.xdev.tci + selenium test - software.xdev.tci.demo + software.xdev.tci.demo.it tci-webapp test diff --git a/tci-advanced-demo/webapp-it/src/test/java/software/xdev/tci/demo/webapp/base/BaseTest.java b/advanced-demo/integration-tests/webapp-it/src/test/java/software/xdev/tci/demo/webapp/base/BaseTest.java similarity index 89% rename from tci-advanced-demo/webapp-it/src/test/java/software/xdev/tci/demo/webapp/base/BaseTest.java rename to advanced-demo/integration-tests/webapp-it/src/test/java/software/xdev/tci/demo/webapp/base/BaseTest.java index 9de074cb..77a2ebcc 100644 --- a/tci-advanced-demo/webapp-it/src/test/java/software/xdev/tci/demo/webapp/base/BaseTest.java +++ b/advanced-demo/integration-tests/webapp-it/src/test/java/software/xdev/tci/demo/webapp/base/BaseTest.java @@ -26,18 +26,17 @@ import software.xdev.tci.TCI; import software.xdev.tci.demo.tci.db.DBTCI; import software.xdev.tci.demo.tci.db.factory.DBTCIFactory; -import software.xdev.tci.demo.tci.oidc.OIDCTCI; -import software.xdev.tci.demo.tci.oidc.factory.OIDCTCIFactory; -import software.xdev.tci.demo.tci.selenium.BrowserTCI; -import software.xdev.tci.demo.tci.selenium.TestBrowser; -import software.xdev.tci.demo.tci.selenium.factory.BrowsersTCIFactory; -import software.xdev.tci.demo.tci.selenium.testbase.SeleniumIntegrationTestExtension; -import software.xdev.tci.demo.tci.util.ContainerLoggingUtil; import software.xdev.tci.demo.tci.webapp.WebAppTCI; import software.xdev.tci.demo.tci.webapp.factory.WebAppTCIFactory; import software.xdev.tci.factory.registry.TCIFactoryRegistry; import software.xdev.tci.leakdetection.LeakDetectionAsyncReaper; import software.xdev.tci.network.LazyNetworkPool; +import software.xdev.tci.oidc.OIDCTCI; +import software.xdev.tci.oidc.factory.OIDCTCIFactory; +import software.xdev.tci.selenium.BrowserTCI; +import software.xdev.tci.selenium.TestBrowser; +import software.xdev.tci.selenium.factory.BrowsersTCIFactory; +import software.xdev.tci.selenium.testbase.SeleniumRecordingExtension; import software.xdev.tci.tracing.TCITracer; @@ -84,8 +83,6 @@ abstract class BaseTest implements IntegrationTestDefaults @BeforeAll public static void setup() { - ContainerLoggingUtil.redirectJULtoSLF4J(); - LAZY_NETWORK_POOL.managePoolAsync(); TCIFactoryRegistry.instance().warmUp(); @@ -194,19 +191,19 @@ protected void stopWebDriver() return; } - final RemoteWebDriver remoteWebDriver = this.remoteWebDriver; - final BrowserTCI browserInfra = this.browserInfra; + final RemoteWebDriver fRemoteWebDriver = this.remoteWebDriver; + final BrowserTCI fBrowserInfra = this.browserInfra; REAP_CFS.add(CompletableFuture.runAsync(() -> { try { - if(remoteWebDriver != null && remoteWebDriver.getSessionId() != null) + if(fRemoteWebDriver != null && fRemoteWebDriver.getSessionId() != null) { LOG.info("Quiting remoteWebDriver"); - remoteWebDriver.quit(); + fRemoteWebDriver.quit(); } - browserInfra.stop(); + fBrowserInfra.stop(); } catch(final Exception ex) { @@ -222,25 +219,25 @@ protected void stopEverything() { LOG.info("Shutting down"); - final WebAppTCI appInfra = this.appInfra; - final OIDCTCI oidcInfra = this.oidcInfra; - final DBTCI dbInfra = this.dbInfra; + final WebAppTCI fAppInfra = this.appInfra; + final OIDCTCI fOidcInfra = this.oidcInfra; + final DBTCI fDbInfra = this.dbInfra; - final Network network = this.network; + final Network fNetwork = this.network; REAP_CFS.add(CompletableFuture.runAsync(() -> { try { Stream.concat( Stream.of(this::stopWebDriver), - Stream.of(appInfra, oidcInfra, dbInfra) + Stream.of(fAppInfra, fOidcInfra, fDbInfra) .filter(Objects::nonNull) .map(tci -> tci::stop)) .map(CompletableFuture::runAsync) .toList() // collect so everything is getting executed async .forEach(CompletableFuture::join); - Optional.ofNullable(network).ifPresent(Network::close); + Optional.ofNullable(fNetwork).ifPresent(Network::close); } catch(final Exception ex) { @@ -291,7 +288,7 @@ public String getWebAppBaseUrl() } public static class WebclientTCSTSeleniumIntegrationTestExtension - extends SeleniumIntegrationTestExtension + extends SeleniumRecordingExtension implements BeforeTestExecutionCallback { private static final Logger LOG = LoggerFactory.getLogger(WebclientTCSTSeleniumIntegrationTestExtension.class); diff --git a/tci-advanced-demo/webapp-it/src/test/java/software/xdev/tci/demo/webapp/base/InfraPerCaseTest.java b/advanced-demo/integration-tests/webapp-it/src/test/java/software/xdev/tci/demo/webapp/base/InfraPerCaseTest.java similarity index 100% rename from tci-advanced-demo/webapp-it/src/test/java/software/xdev/tci/demo/webapp/base/InfraPerCaseTest.java rename to advanced-demo/integration-tests/webapp-it/src/test/java/software/xdev/tci/demo/webapp/base/InfraPerCaseTest.java diff --git a/tci-advanced-demo/webapp-it/src/test/java/software/xdev/tci/demo/webapp/base/InfraPerClassTest.java b/advanced-demo/integration-tests/webapp-it/src/test/java/software/xdev/tci/demo/webapp/base/InfraPerClassTest.java similarity index 100% rename from tci-advanced-demo/webapp-it/src/test/java/software/xdev/tci/demo/webapp/base/InfraPerClassTest.java rename to advanced-demo/integration-tests/webapp-it/src/test/java/software/xdev/tci/demo/webapp/base/InfraPerClassTest.java diff --git a/tci-advanced-demo/webapp-it/src/test/java/software/xdev/tci/demo/webapp/base/IntegrationTestDefaults.java b/advanced-demo/integration-tests/webapp-it/src/test/java/software/xdev/tci/demo/webapp/base/IntegrationTestDefaults.java similarity index 98% rename from tci-advanced-demo/webapp-it/src/test/java/software/xdev/tci/demo/webapp/base/IntegrationTestDefaults.java rename to advanced-demo/integration-tests/webapp-it/src/test/java/software/xdev/tci/demo/webapp/base/IntegrationTestDefaults.java index f83bd5ed..419cb5e1 100644 --- a/tci-advanced-demo/webapp-it/src/test/java/software/xdev/tci/demo/webapp/base/IntegrationTestDefaults.java +++ b/advanced-demo/integration-tests/webapp-it/src/test/java/software/xdev/tci/demo/webapp/base/IntegrationTestDefaults.java @@ -11,6 +11,7 @@ import org.openqa.selenium.support.ui.WebDriverWait; +@SuppressWarnings("java:S119") public interface IntegrationTestDefaults { @SuppressWarnings("unchecked") diff --git a/tci-advanced-demo/webapp-it/src/test/java/software/xdev/tci/demo/webapp/cases/LoginOIDCTest.java b/advanced-demo/integration-tests/webapp-it/src/test/java/software/xdev/tci/demo/webapp/cases/LoginOIDCTest.java similarity index 95% rename from tci-advanced-demo/webapp-it/src/test/java/software/xdev/tci/demo/webapp/cases/LoginOIDCTest.java rename to advanced-demo/integration-tests/webapp-it/src/test/java/software/xdev/tci/demo/webapp/cases/LoginOIDCTest.java index c022ab0d..73487d3a 100644 --- a/tci-advanced-demo/webapp-it/src/test/java/software/xdev/tci/demo/webapp/cases/LoginOIDCTest.java +++ b/advanced-demo/integration-tests/webapp-it/src/test/java/software/xdev/tci/demo/webapp/cases/LoginOIDCTest.java @@ -5,8 +5,8 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; -import software.xdev.tci.demo.tci.selenium.TestBrowser; import software.xdev.tci.demo.webapp.base.InfraPerCaseTest; +import software.xdev.tci.selenium.TestBrowser; class LoginOIDCTest extends InfraPerCaseTest diff --git a/tci-advanced-demo/webapp-it/src/test/java/software/xdev/tci/demo/webapp/cases/LongButLittleResourceUsageTest.java b/advanced-demo/integration-tests/webapp-it/src/test/java/software/xdev/tci/demo/webapp/cases/LongButLittleResourceUsageTest.java similarity index 94% rename from tci-advanced-demo/webapp-it/src/test/java/software/xdev/tci/demo/webapp/cases/LongButLittleResourceUsageTest.java rename to advanced-demo/integration-tests/webapp-it/src/test/java/software/xdev/tci/demo/webapp/cases/LongButLittleResourceUsageTest.java index e6e798db..23106b23 100644 --- a/tci-advanced-demo/webapp-it/src/test/java/software/xdev/tci/demo/webapp/cases/LongButLittleResourceUsageTest.java +++ b/advanced-demo/integration-tests/webapp-it/src/test/java/software/xdev/tci/demo/webapp/cases/LongButLittleResourceUsageTest.java @@ -8,8 +8,8 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import software.xdev.tci.demo.tci.selenium.TestBrowser; import software.xdev.tci.demo.webapp.base.InfraPerCaseTest; +import software.xdev.tci.selenium.TestBrowser; // These tests were written to show the advantages of PreStarting diff --git a/tci-advanced-demo/webapp-it/src/test/java/software/xdev/tci/demo/webapp/cases/ProductTest.java b/advanced-demo/integration-tests/webapp-it/src/test/java/software/xdev/tci/demo/webapp/cases/ProductTest.java similarity index 97% rename from tci-advanced-demo/webapp-it/src/test/java/software/xdev/tci/demo/webapp/cases/ProductTest.java rename to advanced-demo/integration-tests/webapp-it/src/test/java/software/xdev/tci/demo/webapp/cases/ProductTest.java index 4f29ab47..f4fe93a3 100644 --- a/tci-advanced-demo/webapp-it/src/test/java/software/xdev/tci/demo/webapp/cases/ProductTest.java +++ b/advanced-demo/integration-tests/webapp-it/src/test/java/software/xdev/tci/demo/webapp/cases/ProductTest.java @@ -10,9 +10,9 @@ import org.rnorth.ducttape.unreliables.Unreliables; import software.xdev.tci.demo.persistence.jpa.dao.ProductDAO; -import software.xdev.tci.demo.tci.selenium.TestBrowser; import software.xdev.tci.demo.webapp.base.InfraPerCaseTest; import software.xdev.tci.demo.webapp.datageneration.ProductDG; +import software.xdev.tci.selenium.TestBrowser; class ProductTest extends InfraPerCaseTest diff --git a/tci-advanced-demo/webapp-it/src/test/java/software/xdev/tci/demo/webapp/datageneration/ProductDG.java b/advanced-demo/integration-tests/webapp-it/src/test/java/software/xdev/tci/demo/webapp/datageneration/ProductDG.java similarity index 100% rename from tci-advanced-demo/webapp-it/src/test/java/software/xdev/tci/demo/webapp/datageneration/ProductDG.java rename to advanced-demo/integration-tests/webapp-it/src/test/java/software/xdev/tci/demo/webapp/datageneration/ProductDG.java diff --git a/tci-advanced-demo/webapp-it/src/test/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener b/advanced-demo/integration-tests/webapp-it/src/test/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener similarity index 100% rename from tci-advanced-demo/webapp-it/src/test/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener rename to advanced-demo/integration-tests/webapp-it/src/test/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener diff --git a/tci-advanced-demo/webapp-it/src/test/resources/META-INF/services/software.xdev.tci.leakdetection.LeakDetectionAsyncReaper b/advanced-demo/integration-tests/webapp-it/src/test/resources/META-INF/services/software.xdev.tci.leakdetection.LeakDetectionAsyncReaper similarity index 100% rename from tci-advanced-demo/webapp-it/src/test/resources/META-INF/services/software.xdev.tci.leakdetection.LeakDetectionAsyncReaper rename to advanced-demo/integration-tests/webapp-it/src/test/resources/META-INF/services/software.xdev.tci.leakdetection.LeakDetectionAsyncReaper diff --git a/tci-advanced-demo/webapp-it/src/test/resources/log4j2-test.xml b/advanced-demo/integration-tests/webapp-it/src/test/resources/log4j2-test.xml similarity index 100% rename from tci-advanced-demo/webapp-it/src/test/resources/log4j2-test.xml rename to advanced-demo/integration-tests/webapp-it/src/test/resources/log4j2-test.xml diff --git a/tci-advanced-demo/mvnw b/advanced-demo/mvnw similarity index 100% rename from tci-advanced-demo/mvnw rename to advanced-demo/mvnw diff --git a/tci-advanced-demo/mvnw.cmd b/advanced-demo/mvnw.cmd similarity index 100% rename from tci-advanced-demo/mvnw.cmd rename to advanced-demo/mvnw.cmd diff --git a/tci-advanced-demo/persistence/README.md b/advanced-demo/persistence/README.md similarity index 100% rename from tci-advanced-demo/persistence/README.md rename to advanced-demo/persistence/README.md diff --git a/tci-advanced-demo/persistence/pom.xml b/advanced-demo/persistence/pom.xml similarity index 96% rename from tci-advanced-demo/persistence/pom.xml rename to advanced-demo/persistence/pom.xml index c7dba146..2c4ac4bd 100644 --- a/tci-advanced-demo/persistence/pom.xml +++ b/advanced-demo/persistence/pom.xml @@ -6,8 +6,8 @@ 4.0.0 software.xdev.tci.demo - tci-advanced-demo - 1.2.1-SNAPSHOT + advanced-demo + 2.0.0-SNAPSHOT persistence diff --git a/tci-advanced-demo/persistence/src/main/java/org/flywaydb/database/mysql/mariadb/MariaDBDatabase.java b/advanced-demo/persistence/src/main/java/org/flywaydb/database/mysql/mariadb/MariaDBDatabase.java similarity index 100% rename from tci-advanced-demo/persistence/src/main/java/org/flywaydb/database/mysql/mariadb/MariaDBDatabase.java rename to advanced-demo/persistence/src/main/java/org/flywaydb/database/mysql/mariadb/MariaDBDatabase.java diff --git a/tci-advanced-demo/persistence/src/main/java/software/xdev/tci/demo/persistence/FlywayInfo.java b/advanced-demo/persistence/src/main/java/software/xdev/tci/demo/persistence/FlywayInfo.java similarity index 100% rename from tci-advanced-demo/persistence/src/main/java/software/xdev/tci/demo/persistence/FlywayInfo.java rename to advanced-demo/persistence/src/main/java/software/xdev/tci/demo/persistence/FlywayInfo.java diff --git a/tci-advanced-demo/persistence/src/main/java/software/xdev/tci/demo/persistence/FlywayMigration.java b/advanced-demo/persistence/src/main/java/software/xdev/tci/demo/persistence/FlywayMigration.java similarity index 96% rename from tci-advanced-demo/persistence/src/main/java/software/xdev/tci/demo/persistence/FlywayMigration.java rename to advanced-demo/persistence/src/main/java/software/xdev/tci/demo/persistence/FlywayMigration.java index cdfcf556..3798f0e6 100644 --- a/tci-advanced-demo/persistence/src/main/java/software/xdev/tci/demo/persistence/FlywayMigration.java +++ b/advanced-demo/persistence/src/main/java/software/xdev/tci/demo/persistence/FlywayMigration.java @@ -48,11 +48,6 @@ protected void migrate(final FluentConfiguration flywayConfig) final Flyway flyway = flywayConfig.load(); LOG.debug("Finished loading of flyway instance"); - if(flyway.info().current() == null) - { - LOG.info("Detected fresh database instance!"); - } - LOG.debug("Starting DB-Migration"); final long migrateStartTime = System.currentTimeMillis(); diff --git a/tci-advanced-demo/persistence/src/main/java/software/xdev/tci/demo/persistence/config/DefaultJPAConfig.java b/advanced-demo/persistence/src/main/java/software/xdev/tci/demo/persistence/config/DefaultJPAConfig.java similarity index 100% rename from tci-advanced-demo/persistence/src/main/java/software/xdev/tci/demo/persistence/config/DefaultJPAConfig.java rename to advanced-demo/persistence/src/main/java/software/xdev/tci/demo/persistence/config/DefaultJPAConfig.java diff --git a/tci-advanced-demo/persistence/src/main/java/software/xdev/tci/demo/persistence/jpa/dao/BaseDAO.java b/advanced-demo/persistence/src/main/java/software/xdev/tci/demo/persistence/jpa/dao/BaseDAO.java similarity index 100% rename from tci-advanced-demo/persistence/src/main/java/software/xdev/tci/demo/persistence/jpa/dao/BaseDAO.java rename to advanced-demo/persistence/src/main/java/software/xdev/tci/demo/persistence/jpa/dao/BaseDAO.java diff --git a/tci-advanced-demo/persistence/src/main/java/software/xdev/tci/demo/persistence/jpa/dao/BaseEntityDAO.java b/advanced-demo/persistence/src/main/java/software/xdev/tci/demo/persistence/jpa/dao/BaseEntityDAO.java similarity index 100% rename from tci-advanced-demo/persistence/src/main/java/software/xdev/tci/demo/persistence/jpa/dao/BaseEntityDAO.java rename to advanced-demo/persistence/src/main/java/software/xdev/tci/demo/persistence/jpa/dao/BaseEntityDAO.java diff --git a/tci-advanced-demo/persistence/src/main/java/software/xdev/tci/demo/persistence/jpa/dao/ProductDAO.java b/advanced-demo/persistence/src/main/java/software/xdev/tci/demo/persistence/jpa/dao/ProductDAO.java similarity index 100% rename from tci-advanced-demo/persistence/src/main/java/software/xdev/tci/demo/persistence/jpa/dao/ProductDAO.java rename to advanced-demo/persistence/src/main/java/software/xdev/tci/demo/persistence/jpa/dao/ProductDAO.java diff --git a/tci-advanced-demo/persistence/src/main/java/software/xdev/tci/demo/persistence/jpa/dao/TransactionReflector.java b/advanced-demo/persistence/src/main/java/software/xdev/tci/demo/persistence/jpa/dao/TransactionReflector.java similarity index 100% rename from tci-advanced-demo/persistence/src/main/java/software/xdev/tci/demo/persistence/jpa/dao/TransactionReflector.java rename to advanced-demo/persistence/src/main/java/software/xdev/tci/demo/persistence/jpa/dao/TransactionReflector.java diff --git a/tci-advanced-demo/persistence/src/main/java/software/xdev/tci/demo/persistence/util/DisableHibernateFormatMapper.java b/advanced-demo/persistence/src/main/java/software/xdev/tci/demo/persistence/util/DisableHibernateFormatMapper.java similarity index 100% rename from tci-advanced-demo/persistence/src/main/java/software/xdev/tci/demo/persistence/util/DisableHibernateFormatMapper.java rename to advanced-demo/persistence/src/main/java/software/xdev/tci/demo/persistence/util/DisableHibernateFormatMapper.java diff --git a/tci-advanced-demo/persistence/src/main/resources/flyway/structure/B1.1_Init.sql b/advanced-demo/persistence/src/main/resources/flyway/structure/B1.1_Init.sql similarity index 100% rename from tci-advanced-demo/persistence/src/main/resources/flyway/structure/B1.1_Init.sql rename to advanced-demo/persistence/src/main/resources/flyway/structure/B1.1_Init.sql diff --git a/tci-advanced-demo/pom.xml b/advanced-demo/pom.xml similarity index 70% rename from tci-advanced-demo/pom.xml rename to advanced-demo/pom.xml index 8bd636b3..f464be28 100644 --- a/tci-advanced-demo/pom.xml +++ b/advanced-demo/pom.xml @@ -5,8 +5,8 @@ 4.0.0 software.xdev.tci.demo - tci-advanced-demo - 1.2.1-SNAPSHOT + advanced-demo + 2.0.0-SNAPSHOT pom @@ -21,21 +21,15 @@ UTF-8 UTF-8 - 3.5.0 + 3.5.3 entities entities-metamodel + integration-tests persistence - persistence-it - tci-db - tci-oidc - tci-selenium - tci-testcontainers - tci-webapp webapp - webapp-it @@ -44,64 +38,19 @@ software.xdev.tci.demo entities - 1.2.1-SNAPSHOT + 2.0.0-SNAPSHOT software.xdev.tci.demo entities-metamodel - 1.2.1-SNAPSHOT + 2.0.0-SNAPSHOT software.xdev.tci.demo persistence - 1.2.1-SNAPSHOT - - - - software.xdev.tci.demo - tci-db - 1.2.1-SNAPSHOT - - - - software.xdev.tci.demo - tci-oidc - 1.2.1-SNAPSHOT - - - - software.xdev.tci.demo - tci-selenium - 1.2.1-SNAPSHOT - - - - software.xdev.tci.demo - tci-webapp - 1.2.1-SNAPSHOT - - - - software.xdev.tci.demo - tci-testcontainers - 1.2.1-SNAPSHOT - - - - software.xdev - tci-base - ${project.version} - - - - - org.seleniumhq.selenium - selenium-dependencies-bom - 4.33.0 - pom - import + 2.0.0-SNAPSHOT @@ -121,31 +70,7 @@ org.junit junit-bom - 5.13.1 - pom - import - - - - - software.xdev - testcontainers-selenium - 1.2.2 - - - software.xdev - testcontainers-junit4-mock - 1.0.2 - - - software.xdev - testcontainers-advanced-imagebuilder - 1.2.0 - - - org.testcontainers - testcontainers-bom - 1.21.1 + 5.13.2 pom import @@ -154,7 +79,7 @@ org.mariadb.jdbc mariadb-java-client - 3.5.3 + 3.5.4 @@ -169,13 +94,6 @@ 1.1.0 - - - org.javassist - javassist - 3.30.2-GA - - @@ -278,7 +196,7 @@ com.puppycrawl.tools checkstyle - 10.25.0 + 10.26.1 @@ -303,7 +221,7 @@ org.apache.maven.plugins maven-pmd-plugin - 3.26.0 + 3.27.0 true true @@ -318,12 +236,12 @@ net.sourceforge.pmd pmd-core - 7.14.0 + 7.15.0 net.sourceforge.pmd pmd-java - 7.14.0 + 7.15.0 diff --git a/tci-advanced-demo/webapp/Dockerfile b/advanced-demo/webapp/Dockerfile similarity index 100% rename from tci-advanced-demo/webapp/Dockerfile rename to advanced-demo/webapp/Dockerfile diff --git a/tci-advanced-demo/webapp/README.md b/advanced-demo/webapp/README.md similarity index 100% rename from tci-advanced-demo/webapp/README.md rename to advanced-demo/webapp/README.md diff --git a/tci-advanced-demo/webapp/pom.xml b/advanced-demo/webapp/pom.xml similarity index 98% rename from tci-advanced-demo/webapp/pom.xml rename to advanced-demo/webapp/pom.xml index 7ca55913..0a19b548 100644 --- a/tci-advanced-demo/webapp/pom.xml +++ b/advanced-demo/webapp/pom.xml @@ -6,8 +6,8 @@ 4.0.0 software.xdev.tci.demo - tci-advanced-demo - 1.2.1-SNAPSHOT + advanced-demo + 2.0.0-SNAPSHOT webapp diff --git a/tci-advanced-demo/webapp/src/main/java/software/xdev/tci/demo/Application.java b/advanced-demo/webapp/src/main/java/software/xdev/tci/demo/Application.java similarity index 100% rename from tci-advanced-demo/webapp/src/main/java/software/xdev/tci/demo/Application.java rename to advanced-demo/webapp/src/main/java/software/xdev/tci/demo/Application.java diff --git a/tci-advanced-demo/webapp/src/main/java/software/xdev/tci/demo/DemoJPAConfig.java b/advanced-demo/webapp/src/main/java/software/xdev/tci/demo/DemoJPAConfig.java similarity index 100% rename from tci-advanced-demo/webapp/src/main/java/software/xdev/tci/demo/DemoJPAConfig.java rename to advanced-demo/webapp/src/main/java/software/xdev/tci/demo/DemoJPAConfig.java diff --git a/tci-advanced-demo/webapp/src/main/java/software/xdev/tci/demo/api/controller/MeController.java b/advanced-demo/webapp/src/main/java/software/xdev/tci/demo/api/controller/MeController.java similarity index 100% rename from tci-advanced-demo/webapp/src/main/java/software/xdev/tci/demo/api/controller/MeController.java rename to advanced-demo/webapp/src/main/java/software/xdev/tci/demo/api/controller/MeController.java diff --git a/tci-advanced-demo/webapp/src/main/java/software/xdev/tci/demo/api/controller/ProductController.java b/advanced-demo/webapp/src/main/java/software/xdev/tci/demo/api/controller/ProductController.java similarity index 100% rename from tci-advanced-demo/webapp/src/main/java/software/xdev/tci/demo/api/controller/ProductController.java rename to advanced-demo/webapp/src/main/java/software/xdev/tci/demo/api/controller/ProductController.java diff --git a/tci-advanced-demo/webapp/src/main/java/software/xdev/tci/demo/api/services/ProductService.java b/advanced-demo/webapp/src/main/java/software/xdev/tci/demo/api/services/ProductService.java similarity index 100% rename from tci-advanced-demo/webapp/src/main/java/software/xdev/tci/demo/api/services/ProductService.java rename to advanced-demo/webapp/src/main/java/software/xdev/tci/demo/api/services/ProductService.java diff --git a/tci-advanced-demo/webapp/src/main/java/software/xdev/tci/demo/init/DemoFlywayConfigurationCustomizer.java b/advanced-demo/webapp/src/main/java/software/xdev/tci/demo/init/DemoFlywayConfigurationCustomizer.java similarity index 100% rename from tci-advanced-demo/webapp/src/main/java/software/xdev/tci/demo/init/DemoFlywayConfigurationCustomizer.java rename to advanced-demo/webapp/src/main/java/software/xdev/tci/demo/init/DemoFlywayConfigurationCustomizer.java diff --git a/tci-advanced-demo/webapp/src/main/java/software/xdev/tci/demo/security/AppPublicStatelessPathsProvider.java b/advanced-demo/webapp/src/main/java/software/xdev/tci/demo/security/AppPublicStatelessPathsProvider.java similarity index 100% rename from tci-advanced-demo/webapp/src/main/java/software/xdev/tci/demo/security/AppPublicStatelessPathsProvider.java rename to advanced-demo/webapp/src/main/java/software/xdev/tci/demo/security/AppPublicStatelessPathsProvider.java diff --git a/tci-advanced-demo/webapp/src/main/java/software/xdev/tci/demo/security/AuthUserService.java b/advanced-demo/webapp/src/main/java/software/xdev/tci/demo/security/AuthUserService.java similarity index 100% rename from tci-advanced-demo/webapp/src/main/java/software/xdev/tci/demo/security/AuthUserService.java rename to advanced-demo/webapp/src/main/java/software/xdev/tci/demo/security/AuthUserService.java diff --git a/tci-advanced-demo/webapp/src/main/java/software/xdev/tci/demo/security/MainWebSecurity.java b/advanced-demo/webapp/src/main/java/software/xdev/tci/demo/security/MainWebSecurity.java similarity index 100% rename from tci-advanced-demo/webapp/src/main/java/software/xdev/tci/demo/security/MainWebSecurity.java rename to advanced-demo/webapp/src/main/java/software/xdev/tci/demo/security/MainWebSecurity.java diff --git a/tci-advanced-demo/webapp/src/main/java/software/xdev/tci/demo/security/csp/AppCSPProvider.java b/advanced-demo/webapp/src/main/java/software/xdev/tci/demo/security/csp/AppCSPProvider.java similarity index 100% rename from tci-advanced-demo/webapp/src/main/java/software/xdev/tci/demo/security/csp/AppCSPProvider.java rename to advanced-demo/webapp/src/main/java/software/xdev/tci/demo/security/csp/AppCSPProvider.java diff --git a/tci-advanced-demo/webapp/src/main/resources-dev-log/application-add-log.yml b/advanced-demo/webapp/src/main/resources-dev-log/application-add-log.yml similarity index 100% rename from tci-advanced-demo/webapp/src/main/resources-dev-log/application-add-log.yml rename to advanced-demo/webapp/src/main/resources-dev-log/application-add-log.yml diff --git a/tci-advanced-demo/webapp/src/main/resources-dev/application-add.yml b/advanced-demo/webapp/src/main/resources-dev/application-add.yml similarity index 100% rename from tci-advanced-demo/webapp/src/main/resources-dev/application-add.yml rename to advanced-demo/webapp/src/main/resources-dev/application-add.yml diff --git a/tci-advanced-demo/webapp/src/main/resources-dev/connectionless-start.yml b/advanced-demo/webapp/src/main/resources-dev/connectionless-start.yml similarity index 100% rename from tci-advanced-demo/webapp/src/main/resources-dev/connectionless-start.yml rename to advanced-demo/webapp/src/main/resources-dev/connectionless-start.yml diff --git a/tci-advanced-demo/webapp/src/main/resources-prod/application-add-log.yml b/advanced-demo/webapp/src/main/resources-prod/application-add-log.yml similarity index 100% rename from tci-advanced-demo/webapp/src/main/resources-prod/application-add-log.yml rename to advanced-demo/webapp/src/main/resources-prod/application-add-log.yml diff --git a/tci-advanced-demo/webapp/src/main/resources/META-INF/resources/assets/XDEV_LOGO.svg b/advanced-demo/webapp/src/main/resources/META-INF/resources/assets/XDEV_LOGO.svg similarity index 100% rename from tci-advanced-demo/webapp/src/main/resources/META-INF/resources/assets/XDEV_LOGO.svg rename to advanced-demo/webapp/src/main/resources/META-INF/resources/assets/XDEV_LOGO.svg diff --git a/tci-advanced-demo/webapp/src/main/resources/META-INF/resources/favicon.ico b/advanced-demo/webapp/src/main/resources/META-INF/resources/favicon.ico similarity index 100% rename from tci-advanced-demo/webapp/src/main/resources/META-INF/resources/favicon.ico rename to advanced-demo/webapp/src/main/resources/META-INF/resources/favicon.ico diff --git a/tci-advanced-demo/webapp/src/main/resources/META-INF/resources/index.html b/advanced-demo/webapp/src/main/resources/META-INF/resources/index.html similarity index 100% rename from tci-advanced-demo/webapp/src/main/resources/META-INF/resources/index.html rename to advanced-demo/webapp/src/main/resources/META-INF/resources/index.html diff --git a/tci-advanced-demo/webapp/src/main/resources/META-INF/resources/lib/bootstrap-5.3.3.bundle.min.js b/advanced-demo/webapp/src/main/resources/META-INF/resources/lib/bootstrap-5.3.3.bundle.min.js similarity index 100% rename from tci-advanced-demo/webapp/src/main/resources/META-INF/resources/lib/bootstrap-5.3.3.bundle.min.js rename to advanced-demo/webapp/src/main/resources/META-INF/resources/lib/bootstrap-5.3.3.bundle.min.js diff --git a/tci-advanced-demo/webapp/src/main/resources/META-INF/resources/lib/bootstrap-5.3.3.min.css b/advanced-demo/webapp/src/main/resources/META-INF/resources/lib/bootstrap-5.3.3.min.css similarity index 100% rename from tci-advanced-demo/webapp/src/main/resources/META-INF/resources/lib/bootstrap-5.3.3.min.css rename to advanced-demo/webapp/src/main/resources/META-INF/resources/lib/bootstrap-5.3.3.min.css diff --git a/tci-advanced-demo/webapp/src/main/resources/META-INF/resources/lib/bootstrap.bundle.min.js.map b/advanced-demo/webapp/src/main/resources/META-INF/resources/lib/bootstrap.bundle.min.js.map similarity index 100% rename from tci-advanced-demo/webapp/src/main/resources/META-INF/resources/lib/bootstrap.bundle.min.js.map rename to advanced-demo/webapp/src/main/resources/META-INF/resources/lib/bootstrap.bundle.min.js.map diff --git a/tci-advanced-demo/webapp/src/main/resources/META-INF/resources/lib/theme.css b/advanced-demo/webapp/src/main/resources/META-INF/resources/lib/theme.css similarity index 100% rename from tci-advanced-demo/webapp/src/main/resources/META-INF/resources/lib/theme.css rename to advanced-demo/webapp/src/main/resources/META-INF/resources/lib/theme.css diff --git a/tci-advanced-demo/webapp/src/main/resources/META-INF/resources/lib/theme.js b/advanced-demo/webapp/src/main/resources/META-INF/resources/lib/theme.js similarity index 100% rename from tci-advanced-demo/webapp/src/main/resources/META-INF/resources/lib/theme.js rename to advanced-demo/webapp/src/main/resources/META-INF/resources/lib/theme.js diff --git a/tci-advanced-demo/webapp/src/main/resources/META-INF/resources/robots.txt b/advanced-demo/webapp/src/main/resources/META-INF/resources/robots.txt similarity index 100% rename from tci-advanced-demo/webapp/src/main/resources/META-INF/resources/robots.txt rename to advanced-demo/webapp/src/main/resources/META-INF/resources/robots.txt diff --git a/tci-advanced-demo/webapp/src/main/resources/application.yml b/advanced-demo/webapp/src/main/resources/application.yml similarity index 100% rename from tci-advanced-demo/webapp/src/main/resources/application.yml rename to advanced-demo/webapp/src/main/resources/application.yml diff --git a/tci-advanced-demo/webapp/src/main/resources/banner.txt b/advanced-demo/webapp/src/main/resources/banner.txt similarity index 100% rename from tci-advanced-demo/webapp/src/main/resources/banner.txt rename to advanced-demo/webapp/src/main/resources/banner.txt diff --git a/tci-base-demo/README.md b/base-demo/README.md similarity index 100% rename from tci-base-demo/README.md rename to base-demo/README.md diff --git a/tci-base-demo/pom.xml b/base-demo/pom.xml similarity index 86% rename from tci-base-demo/pom.xml rename to base-demo/pom.xml index 6fd06908..9daecb30 100644 --- a/tci-base-demo/pom.xml +++ b/base-demo/pom.xml @@ -5,13 +5,13 @@ 4.0.0 - software.xdev - tci-base-root - 1.2.1-SNAPSHOT + software.xdev.tci + root + 2.0.0-SNAPSHOT - tci-base-demo - 1.2.1-SNAPSHOT + base-demo + 2.0.0-SNAPSHOT jar @@ -29,8 +29,8 @@ - software.xdev - tci-base + software.xdev.tci + base ${project.version} test @@ -38,7 +38,7 @@ org.junit.jupiter junit-jupiter - 5.13.1 + 5.13.2 test diff --git a/tci-base-demo/src/test/java/software/xdev/tci/dummyinfra/DummyTCI.java b/base-demo/src/test/java/software/xdev/tci/dummyinfra/DummyTCI.java similarity index 100% rename from tci-base-demo/src/test/java/software/xdev/tci/dummyinfra/DummyTCI.java rename to base-demo/src/test/java/software/xdev/tci/dummyinfra/DummyTCI.java diff --git a/tci-base-demo/src/test/java/software/xdev/tci/dummyinfra/containers/DummyContainer.java b/base-demo/src/test/java/software/xdev/tci/dummyinfra/containers/DummyContainer.java similarity index 100% rename from tci-base-demo/src/test/java/software/xdev/tci/dummyinfra/containers/DummyContainer.java rename to base-demo/src/test/java/software/xdev/tci/dummyinfra/containers/DummyContainer.java diff --git a/tci-base-demo/src/test/java/software/xdev/tci/dummyinfra/factory/DummyTCIFactory.java b/base-demo/src/test/java/software/xdev/tci/dummyinfra/factory/DummyTCIFactory.java similarity index 100% rename from tci-base-demo/src/test/java/software/xdev/tci/dummyinfra/factory/DummyTCIFactory.java rename to base-demo/src/test/java/software/xdev/tci/dummyinfra/factory/DummyTCIFactory.java diff --git a/tci-base-demo/src/test/java/software/xdev/tci/factory/prestart/PreStartDemoTest.java b/base-demo/src/test/java/software/xdev/tci/factory/prestart/PreStartDemoTest.java similarity index 100% rename from tci-base-demo/src/test/java/software/xdev/tci/factory/prestart/PreStartDemoTest.java rename to base-demo/src/test/java/software/xdev/tci/factory/prestart/PreStartDemoTest.java diff --git a/tci-base-demo/src/test/java/software/xdev/tci/leak/LeakTest.java b/base-demo/src/test/java/software/xdev/tci/leak/LeakTest.java similarity index 94% rename from tci-base-demo/src/test/java/software/xdev/tci/leak/LeakTest.java rename to base-demo/src/test/java/software/xdev/tci/leak/LeakTest.java index a3f5bd2f..9ac1c7fa 100644 --- a/tci-base-demo/src/test/java/software/xdev/tci/leak/LeakTest.java +++ b/base-demo/src/test/java/software/xdev/tci/leak/LeakTest.java @@ -14,7 +14,7 @@ class LeakTest static final DummyTCIFactory DUMMY_FACTORY = new DummyTCIFactory(); - @SuppressWarnings({"java:S2699"}) + @SuppressWarnings({"java:S2699", "java:S125"}) @Test void createLeak() { diff --git a/tci-base-demo/src/test/java/software/xdev/tci/network/LazyNetworkTest.java b/base-demo/src/test/java/software/xdev/tci/network/LazyNetworkTest.java similarity index 100% rename from tci-base-demo/src/test/java/software/xdev/tci/network/LazyNetworkTest.java rename to base-demo/src/test/java/software/xdev/tci/network/LazyNetworkTest.java diff --git a/tci-base-demo/src/test/java/software/xdev/tci/portfixation/PortFixationLiveTest.java b/base-demo/src/test/java/software/xdev/tci/portfixation/PortFixationLiveTest.java similarity index 100% rename from tci-base-demo/src/test/java/software/xdev/tci/portfixation/PortFixationLiveTest.java rename to base-demo/src/test/java/software/xdev/tci/portfixation/PortFixationLiveTest.java diff --git a/tci-base-demo/src/test/java/software/xdev/tci/safestart/SafeNamedContainerStarterTest.java b/base-demo/src/test/java/software/xdev/tci/safestart/SafeNamedContainerStarterTest.java similarity index 100% rename from tci-base-demo/src/test/java/software/xdev/tci/safestart/SafeNamedContainerStarterTest.java rename to base-demo/src/test/java/software/xdev/tci/safestart/SafeNamedContainerStarterTest.java diff --git a/tci-base-demo/src/test/resources/logback.xml b/base-demo/src/test/resources/logback.xml similarity index 100% rename from tci-base-demo/src/test/resources/logback.xml rename to base-demo/src/test/resources/logback.xml diff --git a/base/README.md b/base/README.md new file mode 100644 index 00000000..2479ace9 --- /dev/null +++ b/base/README.md @@ -0,0 +1,20 @@ +# Base Module + +Base for other modules and core components. + +## Features + +| Feature | Why? | Demo | +| --- | --- | --- | +| Easily create infrastructure using TCI[JD](https://javadoc.io/doc/software.xdev/base/latest/software/xdev/tci/TCI.html) (TestContainer Infrastructure) templating + Factories for that | Makes writing and designing tests easier | [here](../base-demo/src/test/java/software/xdev/tci/dummyinfra/) | +| [PreStarting mechanism](./base/src/main/java/software/xdev/tci/factory/prestart/)[JD](https://javadoc.io/doc/software.xdev/base/latest/software/xdev/tci/factory/prestart/PreStartableTCIFactory.html) for [additional performance](../PERFORMANCE.md) | Tries to run tests as fast as possible - with a few trade-offs | [here](../base-demo/src/test/java/software/xdev/tci/factory/prestart/) | +| All started containers have a unique human-readable name | Easier identification when tracing or debugging | [here](../base-demo/src/test/java/software/xdev/tci/safestart/) | +| An optimized [implementation of Network](.´./base/src/main/java/software/xdev/tci/network/)[JD](https://javadoc.io/doc/software.xdev/base/latest/software/xdev/tci/network/LazyNetwork.html) | Addresses various problems of the original implementation to speed up tests | [here](../base-demo/src/test/java/software/xdev/tci/network/) | +| [Safe starting of named containers](./base/src/main/java/software/xdev/tci/safestart/)[JD](https://javadoc.io/doc/software.xdev/base/latest/software/xdev/tci/safestart/SafeNamedContainerStarter.html) | Ensures that a container doesn't enter a crash loop during retried startups | [here](../base-demo/src/test/java/software/xdev/tci/safestart/) | +| [Container Leak detection](./base/src/main/java/software/xdev/tci/leakdetection/)¹ | Prevents you from running out of resources | [here](../base-demo/src/test/java/software/xdev/tci/leak/) | +| [Tracing](../base/src/main/java/software/xdev/tci/tracing/)¹ | Makes finding bottlenecks and similar problems easier | | + +¹ = Active by default due to service loading + +## Usage +Take a look at the [minimalistic demo](../base-demo/) that showcases the components individually. diff --git a/tci-base/pom.xml b/base/pom.xml similarity index 84% rename from tci-base/pom.xml rename to base/pom.xml index 9cd7eeb7..90e6ef67 100644 --- a/tci-base/pom.xml +++ b/base/pom.xml @@ -4,18 +4,18 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - software.xdev - tci-base - 1.2.1-SNAPSHOT + software.xdev.tci + base + 2.0.0-SNAPSHOT jar - tci-base - tci-base - https://github.com/xdev-software/tci-base + base + TCI - base + https://github.com/xdev-software/tci - https://github.com/xdev-software/tci-base - scm:git:https://github.com/xdev-software/tci-base.git + https://github.com/xdev-software/tci + scm:git:https://github.com/xdev-software/tci.git 2024 @@ -60,7 +60,7 @@ org.testcontainers testcontainers compile - 1.21.1 + 1.21.3 @@ -81,14 +81,14 @@ org.junit.platform junit-platform-launcher compile - 1.13.1 + 1.13.2 org.junit.jupiter junit-jupiter - 5.13.1 + 5.13.2 test @@ -148,6 +148,28 @@ org.apache.maven.plugins maven-compiler-plugin 3.14.0 + + + compile-java-17 + + compile + + + + compile-java-21 + compile + + compile + + + 21 + + ${project.basedir}/src/main/java21 + + true + + + ${maven.compiler.release} @@ -155,6 +177,18 @@ + + org.apache.maven.plugins + maven-jar-plugin + 3.4.2 + + + + true + + + + org.apache.maven.plugins maven-javadoc-plugin @@ -216,7 +250,7 @@ org.codehaus.mojo flatten-maven-plugin - 1.7.0 + 1.7.1 ossrh @@ -256,7 +290,7 @@ org.sonatype.central central-publishing-maven-plugin - 0.7.0 + 0.8.0 true sonatype-central-portal @@ -278,7 +312,7 @@ com.puppycrawl.tools checkstyle - 10.25.0 + 10.26.1 @@ -303,7 +337,7 @@ org.apache.maven.plugins maven-pmd-plugin - 3.26.0 + 3.27.0 true true @@ -315,12 +349,12 @@ net.sourceforge.pmd pmd-core - 7.14.0 + 7.15.0 net.sourceforge.pmd pmd-java - 7.14.0 + 7.15.0 diff --git a/tci-base/src/main/java/software/xdev/tci/TCI.java b/base/src/main/java/software/xdev/tci/TCI.java similarity index 100% rename from tci-base/src/main/java/software/xdev/tci/TCI.java rename to base/src/main/java/software/xdev/tci/TCI.java diff --git a/base/src/main/java/software/xdev/tci/envperf/EnvironmentPerformance.java b/base/src/main/java/software/xdev/tci/envperf/EnvironmentPerformance.java new file mode 100644 index 00000000..75bfb4f2 --- /dev/null +++ b/base/src/main/java/software/xdev/tci/envperf/EnvironmentPerformance.java @@ -0,0 +1,43 @@ +/* + * Copyright © 2024 XDEV Software (https://xdev.software) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package software.xdev.tci.envperf; + +import software.xdev.tci.envperf.impl.TCIEnvironmentPerformance; +import software.xdev.tci.serviceloading.TCIServiceLoader; + + +/** + * Describes the Performance of the Environment where TCI is running. + */ +public final class EnvironmentPerformance +{ + /** + * @see TCIEnvironmentPerformance#cpuSlownessFactor() + */ + public static int cpuSlownessFactor() + { + return impl().cpuSlownessFactor(); + } + + public static TCIEnvironmentPerformance impl() + { + return TCIServiceLoader.instance().service(TCIEnvironmentPerformance.class); + } + + private EnvironmentPerformance() + { + } +} diff --git a/base/src/main/java/software/xdev/tci/envperf/impl/DefaultTCIEnvironmentPerformance.java b/base/src/main/java/software/xdev/tci/envperf/impl/DefaultTCIEnvironmentPerformance.java new file mode 100644 index 00000000..a1743afc --- /dev/null +++ b/base/src/main/java/software/xdev/tci/envperf/impl/DefaultTCIEnvironmentPerformance.java @@ -0,0 +1,66 @@ +/* + * Copyright © 2024 XDEV Software (https://xdev.software) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package software.xdev.tci.envperf.impl; + +import java.util.Optional; + +import org.slf4j.LoggerFactory; + + +public class DefaultTCIEnvironmentPerformance implements TCIEnvironmentPerformance +{ + public static final String ENV_SLOWNESS_FACTOR = "TCI_SLOWNESS_FACTOR"; + public static final String PROPERTY_SLOWNESS_FACTOR = "tci.slowness.factor"; + + protected Integer slownessFactor; + + /** + * Describes the performance of the underlying environment.
Higher values indicate a slower environment.
+ * Guideline is a follows: + *
    + *
  • 1 equals a standard developer machine CPU with roughly 16T 3GHz or better
  • + *
  • for a Raspberry PI 5 with 4T 2.4GHz a value of roughly 3 should be chosen
  • + *
+ * The default value is 1.
Min=1, Max=10 + */ + @Override + public int cpuSlownessFactor() + { + if(this.slownessFactor == null) + { + this.slownessFactor = this.parseSlownessFactor(); + } + return this.slownessFactor; + } + + protected synchronized int parseSlownessFactor() + { + return Optional.ofNullable(System.getenv(ENV_SLOWNESS_FACTOR)) + .or(() -> Optional.ofNullable(System.getProperty(PROPERTY_SLOWNESS_FACTOR))) + .map(v -> { + try + { + return Math.min(Math.max(Integer.parseInt(v), 1), 10); + } + catch(final Exception e) + { + LoggerFactory.getLogger(this.getClass()).error("Unable to parse", e); + return null; + } + }) + .orElse(1); + } +} diff --git a/base/src/main/java/software/xdev/tci/envperf/impl/TCIEnvironmentPerformance.java b/base/src/main/java/software/xdev/tci/envperf/impl/TCIEnvironmentPerformance.java new file mode 100644 index 00000000..110aff5c --- /dev/null +++ b/base/src/main/java/software/xdev/tci/envperf/impl/TCIEnvironmentPerformance.java @@ -0,0 +1,30 @@ +/* + * Copyright © 2024 XDEV Software (https://xdev.software) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package software.xdev.tci.envperf.impl; + +public interface TCIEnvironmentPerformance +{ + /** + * Describes the performance of the underlying environment.
Higher values indicate a slower environment.
+ * Guideline is a follows: + *
    + *
  • 1 equals a standard developer machine CPU with roughly 16T 3GHz or better
  • + *
  • for a Raspberry PI 5 with 4T 2.4GHz a value of roughly 3 should be chosen
  • + *
+ * The default value is 1.
Min=1, Max=10 + */ + int cpuSlownessFactor(); +} diff --git a/tci-base/src/main/java/software/xdev/tci/factory/BaseTCIFactory.java b/base/src/main/java/software/xdev/tci/factory/BaseTCIFactory.java similarity index 100% rename from tci-base/src/main/java/software/xdev/tci/factory/BaseTCIFactory.java rename to base/src/main/java/software/xdev/tci/factory/BaseTCIFactory.java diff --git a/tci-base/src/main/java/software/xdev/tci/factory/TCIFactory.java b/base/src/main/java/software/xdev/tci/factory/TCIFactory.java similarity index 100% rename from tci-base/src/main/java/software/xdev/tci/factory/TCIFactory.java rename to base/src/main/java/software/xdev/tci/factory/TCIFactory.java diff --git a/tci-base/src/main/java/software/xdev/tci/factory/ondemand/OnDemandTCIFactory.java b/base/src/main/java/software/xdev/tci/factory/ondemand/OnDemandTCIFactory.java similarity index 100% rename from tci-base/src/main/java/software/xdev/tci/factory/ondemand/OnDemandTCIFactory.java rename to base/src/main/java/software/xdev/tci/factory/ondemand/OnDemandTCIFactory.java diff --git a/tci-base/src/main/java/software/xdev/tci/factory/prestart/PreStartableTCIFactory.java b/base/src/main/java/software/xdev/tci/factory/prestart/PreStartableTCIFactory.java similarity index 97% rename from tci-base/src/main/java/software/xdev/tci/factory/prestart/PreStartableTCIFactory.java rename to base/src/main/java/software/xdev/tci/factory/prestart/PreStartableTCIFactory.java index 16208e3a..eb7e8201 100644 --- a/tci-base/src/main/java/software/xdev/tci/factory/prestart/PreStartableTCIFactory.java +++ b/base/src/main/java/software/xdev/tci/factory/prestart/PreStartableTCIFactory.java @@ -51,7 +51,7 @@ *

What is PreStarting?

*

* When running tests usually there are certain times when the available resources are barely utilized:
- * + * *

*

* PreStarting uses a "cached" pool of infrastructure and tries to utilize these idle times to fill/replenish this @@ -483,22 +483,26 @@ public void close() GlobalPreStartCoordinator.instance().unregister(this); } this.executorService.shutdown(); - final List> stopCFs = this.preStartQueue.stream() - .map(i -> CompletableFuture.runAsync(() -> - { - try - { - i.infra().stop(); - } - catch(final Exception e) + + if(this.preStartQueue != null) + { + final List> stopCFs = this.preStartQueue.stream() + .map(i -> CompletableFuture.runAsync(() -> { - this.log().warn("[{}] Failed to shutdown infra", this.name, e); - } - })) - .toList(); - stopCFs.forEach(CompletableFuture::join); - // De-Ref for GC - this.preStartQueue.clear(); + try + { + i.infra().stop(); + } + catch(final Exception e) + { + this.log().warn("[{}] Failed to shutdown infra", this.name, e); + } + })) + .toList(); + stopCFs.forEach(CompletableFuture::join); + // De-Ref for GC + this.preStartQueue.clear(); + } super.close(); } diff --git a/tci-base/src/main/java/software/xdev/tci/factory/prestart/config/DefaultPreStartConfig.java b/base/src/main/java/software/xdev/tci/factory/prestart/config/DefaultPreStartConfig.java similarity index 100% rename from tci-base/src/main/java/software/xdev/tci/factory/prestart/config/DefaultPreStartConfig.java rename to base/src/main/java/software/xdev/tci/factory/prestart/config/DefaultPreStartConfig.java diff --git a/tci-base/src/main/java/software/xdev/tci/factory/prestart/config/PreStartConfig.java b/base/src/main/java/software/xdev/tci/factory/prestart/config/PreStartConfig.java similarity index 100% rename from tci-base/src/main/java/software/xdev/tci/factory/prestart/config/PreStartConfig.java rename to base/src/main/java/software/xdev/tci/factory/prestart/config/PreStartConfig.java diff --git a/tci-base/src/main/java/software/xdev/tci/factory/prestart/coordinator/DefaultGlobalPreStartCoordinator.java b/base/src/main/java/software/xdev/tci/factory/prestart/coordinator/DefaultGlobalPreStartCoordinator.java similarity index 100% rename from tci-base/src/main/java/software/xdev/tci/factory/prestart/coordinator/DefaultGlobalPreStartCoordinator.java rename to base/src/main/java/software/xdev/tci/factory/prestart/coordinator/DefaultGlobalPreStartCoordinator.java diff --git a/tci-base/src/main/java/software/xdev/tci/factory/prestart/coordinator/GlobalPreStartCoordinator.java b/base/src/main/java/software/xdev/tci/factory/prestart/coordinator/GlobalPreStartCoordinator.java similarity index 100% rename from tci-base/src/main/java/software/xdev/tci/factory/prestart/coordinator/GlobalPreStartCoordinator.java rename to base/src/main/java/software/xdev/tci/factory/prestart/coordinator/GlobalPreStartCoordinator.java diff --git a/tci-base/src/main/java/software/xdev/tci/factory/prestart/coordinator/endingdetector/PreStartTestEndingDetector.java b/base/src/main/java/software/xdev/tci/factory/prestart/coordinator/endingdetector/PreStartTestEndingDetector.java similarity index 100% rename from tci-base/src/main/java/software/xdev/tci/factory/prestart/coordinator/endingdetector/PreStartTestEndingDetector.java rename to base/src/main/java/software/xdev/tci/factory/prestart/coordinator/endingdetector/PreStartTestEndingDetector.java diff --git a/tci-base/src/main/java/software/xdev/tci/factory/prestart/loadbalancing/DefaultDockerLoadMonitor.java b/base/src/main/java/software/xdev/tci/factory/prestart/loadbalancing/DefaultDockerLoadMonitor.java similarity index 92% rename from tci-base/src/main/java/software/xdev/tci/factory/prestart/loadbalancing/DefaultDockerLoadMonitor.java rename to base/src/main/java/software/xdev/tci/factory/prestart/loadbalancing/DefaultDockerLoadMonitor.java index fc5d960a..4bdef053 100644 --- a/tci-base/src/main/java/software/xdev/tci/factory/prestart/loadbalancing/DefaultDockerLoadMonitor.java +++ b/base/src/main/java/software/xdev/tci/factory/prestart/loadbalancing/DefaultDockerLoadMonitor.java @@ -15,8 +15,6 @@ */ package software.xdev.tci.factory.prestart.loadbalancing; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; @@ -36,6 +34,7 @@ import com.github.dockerjava.zerodep.shaded.org.apache.hc.core5.http.HttpStatus; +import software.xdev.tci.misc.http.HttpClientCloser; import software.xdev.tci.safestart.SafeNamedContainerStarter; @@ -168,16 +167,7 @@ public void close() this.scrapeExecutor.shutdown(); } - // Shutdown is only supported in Java 21+ - try - { - final Method mClose = HttpClient.class.getDeclaredMethod("close"); - mClose.invoke(this.httpClient); - } - catch(final NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) - { - LOG.debug("Unable to close HttpClient. Likely running on Java < 21 where method does not exist", ex); - } + HttpClientCloser.close(this.httpClient); this.nodeExporterContainer.stop(); } diff --git a/tci-base/src/main/java/software/xdev/tci/factory/prestart/loadbalancing/LoadMonitor.java b/base/src/main/java/software/xdev/tci/factory/prestart/loadbalancing/LoadMonitor.java similarity index 100% rename from tci-base/src/main/java/software/xdev/tci/factory/prestart/loadbalancing/LoadMonitor.java rename to base/src/main/java/software/xdev/tci/factory/prestart/loadbalancing/LoadMonitor.java diff --git a/tci-base/src/main/java/software/xdev/tci/factory/prestart/loadbalancing/NodeExporterContainer.java b/base/src/main/java/software/xdev/tci/factory/prestart/loadbalancing/NodeExporterContainer.java similarity index 100% rename from tci-base/src/main/java/software/xdev/tci/factory/prestart/loadbalancing/NodeExporterContainer.java rename to base/src/main/java/software/xdev/tci/factory/prestart/loadbalancing/NodeExporterContainer.java diff --git a/tci-base/src/main/java/software/xdev/tci/factory/prestart/snapshoting/CommitedImageSnapshotManager.java b/base/src/main/java/software/xdev/tci/factory/prestart/snapshoting/CommitedImageSnapshotManager.java similarity index 100% rename from tci-base/src/main/java/software/xdev/tci/factory/prestart/snapshoting/CommitedImageSnapshotManager.java rename to base/src/main/java/software/xdev/tci/factory/prestart/snapshoting/CommitedImageSnapshotManager.java diff --git a/tci-base/src/main/java/software/xdev/tci/factory/prestart/snapshoting/SetImageIntoContainer.java b/base/src/main/java/software/xdev/tci/factory/prestart/snapshoting/SetImageIntoContainer.java similarity index 100% rename from tci-base/src/main/java/software/xdev/tci/factory/prestart/snapshoting/SetImageIntoContainer.java rename to base/src/main/java/software/xdev/tci/factory/prestart/snapshoting/SetImageIntoContainer.java diff --git a/tci-base/src/main/java/software/xdev/tci/factory/prestart/snapshoting/SnapshotManager.java b/base/src/main/java/software/xdev/tci/factory/prestart/snapshoting/SnapshotManager.java similarity index 100% rename from tci-base/src/main/java/software/xdev/tci/factory/prestart/snapshoting/SnapshotManager.java rename to base/src/main/java/software/xdev/tci/factory/prestart/snapshoting/SnapshotManager.java diff --git a/tci-base/src/main/java/software/xdev/tci/factory/registry/DefaultTCIFactoryRegistry.java b/base/src/main/java/software/xdev/tci/factory/registry/DefaultTCIFactoryRegistry.java similarity index 100% rename from tci-base/src/main/java/software/xdev/tci/factory/registry/DefaultTCIFactoryRegistry.java rename to base/src/main/java/software/xdev/tci/factory/registry/DefaultTCIFactoryRegistry.java diff --git a/tci-base/src/main/java/software/xdev/tci/factory/registry/TCIFactoryRegistry.java b/base/src/main/java/software/xdev/tci/factory/registry/TCIFactoryRegistry.java similarity index 100% rename from tci-base/src/main/java/software/xdev/tci/factory/registry/TCIFactoryRegistry.java rename to base/src/main/java/software/xdev/tci/factory/registry/TCIFactoryRegistry.java diff --git a/tci-base/src/main/java/software/xdev/tci/leakdetection/LeakDetectionAsyncReaper.java b/base/src/main/java/software/xdev/tci/leakdetection/LeakDetectionAsyncReaper.java similarity index 100% rename from tci-base/src/main/java/software/xdev/tci/leakdetection/LeakDetectionAsyncReaper.java rename to base/src/main/java/software/xdev/tci/leakdetection/LeakDetectionAsyncReaper.java diff --git a/tci-base/src/main/java/software/xdev/tci/leakdetection/TCILeakAgent.java b/base/src/main/java/software/xdev/tci/leakdetection/TCILeakAgent.java similarity index 100% rename from tci-base/src/main/java/software/xdev/tci/leakdetection/TCILeakAgent.java rename to base/src/main/java/software/xdev/tci/leakdetection/TCILeakAgent.java diff --git a/tci-base/src/main/java/software/xdev/tci/leakdetection/config/DefaultLeakDetectionConfig.java b/base/src/main/java/software/xdev/tci/leakdetection/config/DefaultLeakDetectionConfig.java similarity index 100% rename from tci-base/src/main/java/software/xdev/tci/leakdetection/config/DefaultLeakDetectionConfig.java rename to base/src/main/java/software/xdev/tci/leakdetection/config/DefaultLeakDetectionConfig.java diff --git a/tci-base/src/main/java/software/xdev/tci/leakdetection/config/LeakDetectionConfig.java b/base/src/main/java/software/xdev/tci/leakdetection/config/LeakDetectionConfig.java similarity index 100% rename from tci-base/src/main/java/software/xdev/tci/leakdetection/config/LeakDetectionConfig.java rename to base/src/main/java/software/xdev/tci/leakdetection/config/LeakDetectionConfig.java diff --git a/tci-base/src/main/java/software/xdev/tci/misc/ContainerMemory.java b/base/src/main/java/software/xdev/tci/misc/ContainerMemory.java similarity index 100% rename from tci-base/src/main/java/software/xdev/tci/misc/ContainerMemory.java rename to base/src/main/java/software/xdev/tci/misc/ContainerMemory.java diff --git a/base/src/main/java/software/xdev/tci/misc/http/HttpClientCloser.java b/base/src/main/java/software/xdev/tci/misc/http/HttpClientCloser.java new file mode 100644 index 00000000..965cd65e --- /dev/null +++ b/base/src/main/java/software/xdev/tci/misc/http/HttpClientCloser.java @@ -0,0 +1,32 @@ +/* + * Copyright © 2024 XDEV Software (https://xdev.software) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package software.xdev.tci.misc.http; + +import java.net.http.HttpClient; + + +public final class HttpClientCloser +{ + @SuppressWarnings("unused") + public static void close(final HttpClient httpClient) + { + // Java 17 and below: Do nothing because close method does not exist + } + + private HttpClientCloser() + { + } +} diff --git a/tci-base/src/main/java/software/xdev/tci/network/LazyNetwork.java b/base/src/main/java/software/xdev/tci/network/LazyNetwork.java similarity index 99% rename from tci-base/src/main/java/software/xdev/tci/network/LazyNetwork.java rename to base/src/main/java/software/xdev/tci/network/LazyNetwork.java index 8fbc7689..e694ea15 100644 --- a/tci-base/src/main/java/software/xdev/tci/network/LazyNetwork.java +++ b/base/src/main/java/software/xdev/tci/network/LazyNetwork.java @@ -315,6 +315,7 @@ public int getDeleteNetworkOnCloseTries() */ @Deprecated(forRemoval = true) @Override + @SuppressWarnings("java:S1133") public Statement apply(final Statement base, final Description description) { return null; diff --git a/tci-base/src/main/java/software/xdev/tci/network/LazyNetworkPool.java b/base/src/main/java/software/xdev/tci/network/LazyNetworkPool.java similarity index 100% rename from tci-base/src/main/java/software/xdev/tci/network/LazyNetworkPool.java rename to base/src/main/java/software/xdev/tci/network/LazyNetworkPool.java diff --git a/tci-base/src/main/java/software/xdev/tci/portfixation/AdditionalPortsForFixedExposingContainer.java b/base/src/main/java/software/xdev/tci/portfixation/AdditionalPortsForFixedExposingContainer.java similarity index 100% rename from tci-base/src/main/java/software/xdev/tci/portfixation/AdditionalPortsForFixedExposingContainer.java rename to base/src/main/java/software/xdev/tci/portfixation/AdditionalPortsForFixedExposingContainer.java diff --git a/tci-base/src/main/java/software/xdev/tci/portfixation/PortFixation.java b/base/src/main/java/software/xdev/tci/portfixation/PortFixation.java similarity index 100% rename from tci-base/src/main/java/software/xdev/tci/portfixation/PortFixation.java rename to base/src/main/java/software/xdev/tci/portfixation/PortFixation.java diff --git a/tci-base/src/main/java/software/xdev/tci/safestart/SafeNamedContainerStarter.java b/base/src/main/java/software/xdev/tci/safestart/SafeNamedContainerStarter.java similarity index 100% rename from tci-base/src/main/java/software/xdev/tci/safestart/SafeNamedContainerStarter.java rename to base/src/main/java/software/xdev/tci/safestart/SafeNamedContainerStarter.java diff --git a/tci-base/src/main/java/software/xdev/tci/serviceloading/TCIProviderPriority.java b/base/src/main/java/software/xdev/tci/serviceloading/TCIProviderPriority.java similarity index 100% rename from tci-base/src/main/java/software/xdev/tci/serviceloading/TCIProviderPriority.java rename to base/src/main/java/software/xdev/tci/serviceloading/TCIProviderPriority.java diff --git a/tci-base/src/main/java/software/xdev/tci/serviceloading/TCIServiceLoader.java b/base/src/main/java/software/xdev/tci/serviceloading/TCIServiceLoader.java similarity index 100% rename from tci-base/src/main/java/software/xdev/tci/serviceloading/TCIServiceLoader.java rename to base/src/main/java/software/xdev/tci/serviceloading/TCIServiceLoader.java diff --git a/tci-base/src/main/java/software/xdev/tci/tracing/TCITracer.java b/base/src/main/java/software/xdev/tci/tracing/TCITracer.java similarity index 100% rename from tci-base/src/main/java/software/xdev/tci/tracing/TCITracer.java rename to base/src/main/java/software/xdev/tci/tracing/TCITracer.java diff --git a/tci-base/src/main/java/software/xdev/tci/tracing/TCITracingAgent.java b/base/src/main/java/software/xdev/tci/tracing/TCITracingAgent.java similarity index 100% rename from tci-base/src/main/java/software/xdev/tci/tracing/TCITracingAgent.java rename to base/src/main/java/software/xdev/tci/tracing/TCITracingAgent.java diff --git a/tci-base/src/main/java/software/xdev/tci/tracing/config/DefaultTracingConfig.java b/base/src/main/java/software/xdev/tci/tracing/config/DefaultTracingConfig.java similarity index 100% rename from tci-base/src/main/java/software/xdev/tci/tracing/config/DefaultTracingConfig.java rename to base/src/main/java/software/xdev/tci/tracing/config/DefaultTracingConfig.java diff --git a/tci-base/src/main/java/software/xdev/tci/tracing/config/TracingConfig.java b/base/src/main/java/software/xdev/tci/tracing/config/TracingConfig.java similarity index 100% rename from tci-base/src/main/java/software/xdev/tci/tracing/config/TracingConfig.java rename to base/src/main/java/software/xdev/tci/tracing/config/TracingConfig.java diff --git a/base/src/main/java21/software/xdev/tci/misc/http/HttpClientCloser.java b/base/src/main/java21/software/xdev/tci/misc/http/HttpClientCloser.java new file mode 100644 index 00000000..2e9b6883 --- /dev/null +++ b/base/src/main/java21/software/xdev/tci/misc/http/HttpClientCloser.java @@ -0,0 +1,33 @@ +/* + * Copyright © 2024 XDEV Software (https://xdev.software) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package software.xdev.tci.misc.http; + +import java.net.http.HttpClient; + + +public final class HttpClientCloser +{ + @SuppressWarnings("unused") + public static void close(final HttpClient httpClient) + { + // Java 21+: close it + httpClient.close(); + } + + private HttpClientCloser() + { + } +} diff --git a/tci-base/src/main/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener b/base/src/main/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener similarity index 100% rename from tci-base/src/main/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener rename to base/src/main/resources/META-INF/services/org.junit.platform.launcher.TestExecutionListener diff --git a/base/src/main/resources/META-INF/services/software.xdev.tci.envperf.impl.TCIEnvironmentPerformance b/base/src/main/resources/META-INF/services/software.xdev.tci.envperf.impl.TCIEnvironmentPerformance new file mode 100644 index 00000000..df079c0c --- /dev/null +++ b/base/src/main/resources/META-INF/services/software.xdev.tci.envperf.impl.TCIEnvironmentPerformance @@ -0,0 +1 @@ +software.xdev.tci.envperf.impl.DefaultTCIEnvironmentPerformance diff --git a/tci-base/src/main/resources/META-INF/services/software.xdev.tci.factory.prestart.config.PreStartConfig b/base/src/main/resources/META-INF/services/software.xdev.tci.factory.prestart.config.PreStartConfig similarity index 100% rename from tci-base/src/main/resources/META-INF/services/software.xdev.tci.factory.prestart.config.PreStartConfig rename to base/src/main/resources/META-INF/services/software.xdev.tci.factory.prestart.config.PreStartConfig diff --git a/tci-base/src/main/resources/META-INF/services/software.xdev.tci.factory.prestart.coordinator.GlobalPreStartCoordinator b/base/src/main/resources/META-INF/services/software.xdev.tci.factory.prestart.coordinator.GlobalPreStartCoordinator similarity index 100% rename from tci-base/src/main/resources/META-INF/services/software.xdev.tci.factory.prestart.coordinator.GlobalPreStartCoordinator rename to base/src/main/resources/META-INF/services/software.xdev.tci.factory.prestart.coordinator.GlobalPreStartCoordinator diff --git a/tci-base/src/main/resources/META-INF/services/software.xdev.tci.factory.prestart.loadbalancing.LoadMonitor b/base/src/main/resources/META-INF/services/software.xdev.tci.factory.prestart.loadbalancing.LoadMonitor similarity index 100% rename from tci-base/src/main/resources/META-INF/services/software.xdev.tci.factory.prestart.loadbalancing.LoadMonitor rename to base/src/main/resources/META-INF/services/software.xdev.tci.factory.prestart.loadbalancing.LoadMonitor diff --git a/tci-base/src/main/resources/META-INF/services/software.xdev.tci.factory.registry.TCIFactoryRegistry b/base/src/main/resources/META-INF/services/software.xdev.tci.factory.registry.TCIFactoryRegistry similarity index 100% rename from tci-base/src/main/resources/META-INF/services/software.xdev.tci.factory.registry.TCIFactoryRegistry rename to base/src/main/resources/META-INF/services/software.xdev.tci.factory.registry.TCIFactoryRegistry diff --git a/tci-base/src/main/resources/META-INF/services/software.xdev.tci.leakdetection.config.LeakDetectionConfig b/base/src/main/resources/META-INF/services/software.xdev.tci.leakdetection.config.LeakDetectionConfig similarity index 100% rename from tci-base/src/main/resources/META-INF/services/software.xdev.tci.leakdetection.config.LeakDetectionConfig rename to base/src/main/resources/META-INF/services/software.xdev.tci.leakdetection.config.LeakDetectionConfig diff --git a/tci-base/src/main/resources/META-INF/services/software.xdev.tci.tracing.config.TracingConfig b/base/src/main/resources/META-INF/services/software.xdev.tci.tracing.config.TracingConfig similarity index 100% rename from tci-base/src/main/resources/META-INF/services/software.xdev.tci.tracing.config.TracingConfig rename to base/src/main/resources/META-INF/services/software.xdev.tci.tracing.config.TracingConfig diff --git a/tci-base/src/test/java/software/xdev/tci/commitedimage/SetImageIntoContainerTest.java b/base/src/test/java/software/xdev/tci/commitedimage/SetImageIntoContainerTest.java similarity index 100% rename from tci-base/src/test/java/software/xdev/tci/commitedimage/SetImageIntoContainerTest.java rename to base/src/test/java/software/xdev/tci/commitedimage/SetImageIntoContainerTest.java diff --git a/tci-base/src/test/java/software/xdev/tci/portfixation/PortFixationTest.java b/base/src/test/java/software/xdev/tci/portfixation/PortFixationTest.java similarity index 100% rename from tci-base/src/test/java/software/xdev/tci/portfixation/PortFixationTest.java rename to base/src/test/java/software/xdev/tci/portfixation/PortFixationTest.java diff --git a/bom/README.md b/bom/README.md new file mode 100644 index 00000000..9d41cdb8 --- /dev/null +++ b/bom/README.md @@ -0,0 +1,16 @@ +# BOM - [Bill of Materials](https://maven.apache.org/guides/introduction/introduction-to-dependency-mechanism.html#Bill_of_Materials_.28BOM.29_POMs) + +Add it like this +```xml + + + + software.xdev.tci + bom + ... + pom + import + + + +``` \ No newline at end of file diff --git a/bom/pom.xml b/bom/pom.xml new file mode 100644 index 00000000..d670921a --- /dev/null +++ b/bom/pom.xml @@ -0,0 +1,164 @@ + + + 4.0.0 + + software.xdev.sse + bom + 2.0.0-SNAPSHOT + pom + + bom + TCI - BOM + https://github.com/xdev-software/tci + + + https://github.com/xdev-software/tci + scm:git:https://github.com/xdev-software/tci.git + + + 2025 + + + XDEV Software + https://xdev.software + + + + + XDEV Software + XDEV Software + https://xdev.software + + + + + + Apache-2.0 + https://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + UTF-8 + UTF-8 + + + + + + software.xdev.tci + base + 2.0.0-SNAPSHOT + + + software.xdev.tci + db-jdbc-orm + 2.0.0-SNAPSHOT + + + software.xdev.tci + jul-to-slf4j + 2.0.0-SNAPSHOT + + + software.xdev.tci + mockserver + 2.0.0-SNAPSHOT + + + software.xdev.tci + oidc-server-mock + 2.0.0-SNAPSHOT + + + software.xdev.tci + selenium + 2.0.0-SNAPSHOT + + + software.xdev.tci + spring-dao-support + 2.0.0-SNAPSHOT + + + + + + + + + org.apache.maven.plugins + maven-site-plugin + 4.0.0-M16 + + + org.apache.maven.plugins + maven-project-info-reports-plugin + 3.9.0 + + + + + + + publish-sonatype-central-portal + + + + org.codehaus.mojo + flatten-maven-plugin + 1.7.1 + + bom + + + + flatten + process-resources + + flatten + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 3.2.7 + + + sign-artifacts + verify + + sign + + + + + + --pinentry-mode + loopback + + + + + + + + org.sonatype.central + central-publishing-maven-plugin + 0.8.0 + true + + sonatype-central-portal + true + + + + + + + diff --git a/db-jdbc-orm/README.md b/db-jdbc-orm/README.md new file mode 100644 index 00000000..aa95430f --- /dev/null +++ b/db-jdbc-orm/README.md @@ -0,0 +1,8 @@ +# DB JDBC ORM + +Common TCI code for Database info. + +## Features +* Improved JDBC Container Waiting Strategy +* Datageneration Template +* Predefined support for creation of EntityManager and Entity discovery diff --git a/db-jdbc-orm/pom.xml b/db-jdbc-orm/pom.xml new file mode 100644 index 00000000..5931e8d3 --- /dev/null +++ b/db-jdbc-orm/pom.xml @@ -0,0 +1,347 @@ + + + 4.0.0 + + software.xdev.tci + db-jdbc-orm + 2.0.0-SNAPSHOT + jar + + db-jdbc-orm + TCI - db-jdbc-orm + https://github.com/xdev-software/tci + + + https://github.com/xdev-software/tci + scm:git:https://github.com/xdev-software/tci.git + + + 2025 + + + XDEV Software + https://xdev.software + + + + + XDEV Software + XDEV Software + https://xdev.software + + + + + + Apache-2.0 + https://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + 17 + ${javaVersion} + + UTF-8 + UTF-8 + + + + + software.xdev.tci + base + 2.0.0-SNAPSHOT + + + + org.testcontainers + jdbc + 1.21.3 + + + + jakarta.persistence + jakarta.persistence-api + 3.2.0 + + + + org.springframework + spring-core + 6.2.8 + + + org.springframework + spring-orm + 6.2.8 + + + + org.hibernate.orm + hibernate-core + 6.6.19.Final + + + org.hibernate.orm + hibernate-hikaricp + 6.6.19.Final + + + + + org.junit.jupiter + junit-jupiter + 5.13.2 + test + + + org.slf4j + slf4j-simple + 2.0.17 + test + + + + + + + + org.apache.maven.plugins + maven-site-plugin + 4.0.0-M16 + + + org.apache.maven.plugins + maven-project-info-reports-plugin + 3.9.0 + + + + + + com.mycila + license-maven-plugin + 5.0.0 + + + ${project.organization.url} + + + +

com/mycila/maven/plugin/license/templates/APACHE-2.txt
+ + src/main/java/** + src/test/java/** + + + +
+ + + first + + format + + process-sources + + +
+ + + org.apache.maven.plugins + maven-compiler-plugin + 3.14.0 + + ${maven.compiler.release} + + -proc:none + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.11.2 + + + attach-javadocs + package + + jar + + + + + true + none + + + + org.apache.maven.plugins + maven-source-plugin + 3.3.1 + + + attach-sources + package + + jar-no-fork + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.5.3 + + + + + + ignore-service-loading + + + + src/main/resources + + META-INF/services/** + + + + + + + publish-sonatype-central-portal + + + + org.codehaus.mojo + flatten-maven-plugin + 1.7.1 + + ossrh + + + + flatten + process-resources + + flatten + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 3.2.7 + + + sign-artifacts + verify + + sign + + + + + + --pinentry-mode + loopback + + + + + + + + org.sonatype.central + central-publishing-maven-plugin + 0.8.0 + true + + sonatype-central-portal + true + + + + + + + checkstyle + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 3.6.0 + + + com.puppycrawl.tools + checkstyle + 10.26.1 + + + + ../.config/checkstyle/checkstyle.xml + true + + + + + check + + + + + + + + + pmd + + + + org.apache.maven.plugins + maven-pmd-plugin + 3.27.0 + + true + true + + ../.config/pmd/java/ruleset.xml + + + + + net.sourceforge.pmd + pmd-core + 7.15.0 + + + net.sourceforge.pmd + pmd-java + 7.15.0 + + + + + + + + + + org.apache.maven.plugins + maven-jxr-plugin + 3.6.0 + + + + + + diff --git a/db-jdbc-orm/src/main/java/software/xdev/tci/db/BaseDBTCI.java b/db-jdbc-orm/src/main/java/software/xdev/tci/db/BaseDBTCI.java new file mode 100644 index 00000000..2cefad40 --- /dev/null +++ b/db-jdbc-orm/src/main/java/software/xdev/tci/db/BaseDBTCI.java @@ -0,0 +1,220 @@ +/* + * Copyright © 2025 XDEV Software (https://xdev.software) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package software.xdev.tci.db; + +import java.sql.Driver; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.WeakHashMap; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import javax.sql.DataSource; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.EntityManagerFactory; + +import org.hibernate.cfg.PersistenceSettings; +import org.hibernate.hikaricp.internal.HikariCPConnectionProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.JdbcDatabaseContainer; + +import software.xdev.tci.TCI; +import software.xdev.tci.db.persistence.EntityManagerController; +import software.xdev.tci.db.persistence.EntityManagerControllerFactory; +import software.xdev.tci.db.persistence.hibernate.CachingStandardScanner; + + +public abstract class BaseDBTCI> extends TCI +{ + protected static final Map, Logger> LOGGER_CACHE = Collections.synchronizedMap(new WeakHashMap<>()); + + public static final String DEFAULT_DATABASE = "testdb"; + public static final String DEFAULT_USERNAME = "testuser"; + @SuppressWarnings("java:S2068") // This is only for tests + public static final String DEFAULT_PASSWORD = "testpw"; + + protected final boolean migrateAndInitializeEMC; + protected final Supplier emcFactorySupplier; + protected final Logger logger; + + protected String database = DEFAULT_DATABASE; + protected String username = DEFAULT_USERNAME; + @SuppressWarnings("java:S2068") // This is only for tests + protected String password = DEFAULT_PASSWORD; + + protected EntityManagerController emc; + + protected BaseDBTCI( + final C container, + final String networkAlias, + final boolean migrateAndInitializeEMC, + final Supplier emcFactorySupplier) + { + super(container, networkAlias); + this.migrateAndInitializeEMC = migrateAndInitializeEMC; + this.emcFactorySupplier = emcFactorySupplier; + + this.logger = LOGGER_CACHE.computeIfAbsent(this.getClass(), LoggerFactory::getLogger); + } + + @Override + public void start(final String containerName) + { + super.start(containerName); + if(this.migrateAndInitializeEMC) + { + // Do basic migrations async + this.log().debug("Running migration to basic structure"); + this.execInitialDatabaseMigration(); + this.log().info("Migration executed"); + + // Create EMC in background to improve performance (~5s) + this.log().debug("Initializing EntityManagerController..."); + this.getEMC(); + this.log().info("Initialized EntityManagerController"); + } + } + + @Override + public void stop() + { + if(this.emc != null) + { + try + { + this.emc.close(); + } + catch(final Exception ex) + { + this.log().warn("Failed to close EntityManagerController", ex); + } + this.emc = null; + } + super.stop(); + } + + public EntityManagerController getEMC() + { + if(this.emc == null) + { + this.initEMCIfRequired(); + } + + return this.emc; + } + + protected abstract Class driverClazz(); + + protected synchronized void initEMCIfRequired() + { + if(this.emc != null) + { + return; + } + + final EntityManagerControllerFactory emcFactory = this.emcFactorySupplier.get(); + this.emc = emcFactory + .withDriverFullClassName(this.driverClazz().getName()) + // Use production-ready pool; otherwise Hibernate warnings occur + .withConnectionProviderClassName(HikariCPConnectionProvider.class.getName()) + .withJdbcUrl(this.getExternalJDBCUrl()) + .withUsername(this.username) + .withPassword(this.password) + .withAdditionalConfig(Map.ofEntries( + // Use caching scanner to massively improve performance (this way the scanning only happens once) + Map.entry(PersistenceSettings.SCANNER, CachingStandardScanner.instance()) + )) + .build(); + } + + public String getExternalJDBCUrl() + { + return this.getContainer().getJdbcUrl(); + } + + /** + * Creates a new {@link EntityManager} with an internal {@link EntityManagerFactory}, which can be used to load and + * save data in the database for the test. + * + *

+ * It may be a good idea to close the EntityManager, when you're finished with it. + *

+ *

+ * All created EntityManager are automatically cleaned up when the test is finished. + *

+ * + * @return EntityManager + */ + public EntityManager createEntityManager() + { + return this.getEMC().createEntityManager(); + } + + public void useNewEntityManager(final Consumer action) + { + try(final EntityManager em = this.createEntityManager()) + { + action.accept(em); + } + } + + @SuppressWarnings("java:S6437") // Only done for test + public abstract DataSource createDataSource(); + + protected abstract void execInitialDatabaseMigration(); + + public void migrateDatabase(final Collection locations) + { + this.migrateDatabase(locations.toArray(String[]::new)); + } + + public abstract void migrateDatabase(final String... locations); + + protected Logger log() + { + return this.logger; + } + + // region Configure + + public BaseDBTCI withDatabase(final String database) + { + this.database = database; + return this; + } + + public BaseDBTCI withUsername(final String username) + { + this.username = username; + return this; + } + + public BaseDBTCI withPassword(final String password) + { + this.password = password; + return this; + } + + public boolean isMigrateAndInitializeEMC() + { + return this.migrateAndInitializeEMC; + } + + // endregion +} diff --git a/db-jdbc-orm/src/main/java/software/xdev/tci/db/containers/TestQueryStringAccessor.java b/db-jdbc-orm/src/main/java/software/xdev/tci/db/containers/TestQueryStringAccessor.java new file mode 100644 index 00000000..1d005f9e --- /dev/null +++ b/db-jdbc-orm/src/main/java/software/xdev/tci/db/containers/TestQueryStringAccessor.java @@ -0,0 +1,51 @@ +/* + * Copyright © 2025 XDEV Software (https://xdev.software) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package software.xdev.tci.db.containers; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import org.testcontainers.containers.JdbcDatabaseContainer; + + +public final class TestQueryStringAccessor +{ + @SuppressWarnings("java:S3011") + public static String testQueryString(final JdbcDatabaseContainer container) + throws InvocationTargetException, IllegalAccessException + { + Class currentClass = container.getClass(); + while(currentClass != null && !JdbcDatabaseContainer.class.equals(currentClass)) + { + try + { + final Method mGetTestQueryString = currentClass.getDeclaredMethod("getTestQueryString"); + mGetTestQueryString.setAccessible(true); + return (String)mGetTestQueryString.invoke(container); + } + catch(final NoSuchMethodException ignored) + { + // Skip + } + currentClass = currentClass.getSuperclass(); + } + return null; + } + + private TestQueryStringAccessor() + { + } +} diff --git a/tci-advanced-demo/tci-db/src/main/java/software/xdev/tci/demo/tci/db/containers/WaitableJDBCContainer.java b/db-jdbc-orm/src/main/java/software/xdev/tci/db/containers/WaitableJDBCContainer.java similarity index 51% rename from tci-advanced-demo/tci-db/src/main/java/software/xdev/tci/demo/tci/db/containers/WaitableJDBCContainer.java rename to db-jdbc-orm/src/main/java/software/xdev/tci/db/containers/WaitableJDBCContainer.java index c0c456bc..432dd0ba 100644 --- a/tci-advanced-demo/tci-db/src/main/java/software/xdev/tci/demo/tci/db/containers/WaitableJDBCContainer.java +++ b/db-jdbc-orm/src/main/java/software/xdev/tci/db/containers/WaitableJDBCContainer.java @@ -1,7 +1,21 @@ -package software.xdev.tci.demo.tci.db.containers; +/* + * Copyright © 2025 XDEV Software (https://xdev.software) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package software.xdev.tci.db.containers; import java.sql.Connection; -import java.sql.Statement; import java.util.concurrent.TimeUnit; import org.rnorth.ducttape.TimeoutException; @@ -16,14 +30,12 @@ import org.testcontainers.containers.wait.strategy.WaitStrategyTarget; -interface WaitableJDBCContainer extends WaitStrategyTarget +public interface WaitableJDBCContainer extends WaitStrategyTarget { default WaitStrategy completeJDBCWaitStrategy() { return new WaitAllStrategy() - // First wait for ports to be accessible .withStrategy(Wait.defaultWaitStrategy()) - // then check if a JDBC connection (requires more resources) is possible .withStrategy(new JDBCWaitStrategy()); } @@ -38,8 +50,6 @@ default void waitUntilContainerStarted() } } - String getTestQueryString(); - /** * @apiNote Assumes that the container is already started */ @@ -58,8 +68,7 @@ public JDBCWaitStrategy() @Override protected void waitUntilReady() { - if(!(this.waitStrategyTarget instanceof final JdbcDatabaseContainer container - && this.waitStrategyTarget instanceof final WaitableJDBCContainer waitableJDBCContainer)) + if(!(this.waitStrategyTarget instanceof final JdbcDatabaseContainer container)) { throw new IllegalArgumentException( "Container must implement JdbcDatabaseContainer and WaitableJDBCContainer"); @@ -67,17 +76,7 @@ protected void waitUntilReady() try { - Unreliables.retryUntilTrue( - (int)this.startupTimeout.getSeconds(), - TimeUnit.SECONDS, - () -> this.getRateLimiter().getWhenReady(() -> { - try(final Connection connection = container.createConnection(""); - final Statement statement = connection.createStatement()) - { - return statement.execute(waitableJDBCContainer.getTestQueryString()); - } - }) - ); + this.waitUntilJDBCValid(container); } catch(final TimeoutException e) { @@ -87,5 +86,37 @@ protected void waitUntilReady() + "), please check container logs"); } } + + protected void waitUntilJDBCValid(final JdbcDatabaseContainer container) + { + Unreliables.retryUntilTrue( + (int)this.startupTimeout.getSeconds(), + TimeUnit.SECONDS, + // Rate limit creation of connections as this is quite an expensive operation + () -> this.getRateLimiter().getWhenReady(() -> { + try(final Connection connection = container.createConnection("")) + { + return this.waitUntilJDBCConnectionValidated(container, connection); + } + }) + ); + } + + @SuppressWarnings("unused") // Variable might be used by extension + protected boolean waitUntilJDBCConnectionValidated( + final JdbcDatabaseContainer container, + final Connection connection) + { + return Unreliables.retryUntilSuccess( + (int)this.startupTimeout.getSeconds(), + TimeUnit.SECONDS, + () -> this.validateJDBCConnection(connection)); + } + + @SuppressWarnings("java:S112") + protected boolean validateJDBCConnection(final Connection connection) throws Exception + { + return connection.isValid(10); + } } } diff --git a/db-jdbc-orm/src/main/java/software/xdev/tci/db/datageneration/BaseDBDataGenerator.java b/db-jdbc-orm/src/main/java/software/xdev/tci/db/datageneration/BaseDBDataGenerator.java new file mode 100644 index 00000000..2b57d6dc --- /dev/null +++ b/db-jdbc-orm/src/main/java/software/xdev/tci/db/datageneration/BaseDBDataGenerator.java @@ -0,0 +1,83 @@ +/* + * Copyright © 2025 XDEV Software (https://xdev.software) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package software.xdev.tci.db.datageneration; + +import java.time.LocalDate; +import java.util.Objects; + +import jakarta.persistence.EntityManager; + +import software.xdev.tci.db.persistence.TransactionExecutor; + + +/** + * Base class for all data generators. Holds an {@link EntityManager} and a {@link TransactionExecutor} to save data. + * + * @implNote Due to generics save Methods need to be implemented downstream + */ +public abstract class BaseDBDataGenerator implements DataGenerator +{ + protected final EntityManager em; + protected final TransactionExecutor transactor; + + protected BaseDBDataGenerator(final EntityManager em) + { + this(em, null); + } + + protected BaseDBDataGenerator(final EntityManager em, final TransactionExecutor transactor) + { + this.em = Objects.requireNonNull(em, "EntityManager can't be null!"); + this.transactor = transactor != null ? transactor : new TransactionExecutor(em); + } + + /** + * Returns the {@link EntityManager}-Instance of this generator, which can be used to save data. + */ + @SuppressWarnings("java:S1845") // Record style access + protected EntityManager em() + { + return this.em; + } + + /** + * Returns the {@link TransactionExecutor}-Instance of this generator, which can be used to save data with a + * transaction. + */ + @SuppressWarnings("java:S1845") // Record style access + protected TransactionExecutor transactor() + { + return this.transactor; + } + + /** + * Returns a {@link LocalDate} in the past. By default, 1970-01-01. + */ + @SuppressWarnings("checkstyle:MagicNumber") + public LocalDate getLocalDateInPast() + { + return LocalDate.of(1970, 1, 1); + } + + /** + * Returns a {@link LocalDate} in the future. By default, 3000-01-01. + */ + @SuppressWarnings("checkstyle:MagicNumber") + public LocalDate getLocalDateInFuture() + { + return LocalDate.of(3000, 1, 1).plusYears(1); + } +} diff --git a/db-jdbc-orm/src/main/java/software/xdev/tci/db/datageneration/DataGenerator.java b/db-jdbc-orm/src/main/java/software/xdev/tci/db/datageneration/DataGenerator.java new file mode 100644 index 00000000..7326fd26 --- /dev/null +++ b/db-jdbc-orm/src/main/java/software/xdev/tci/db/datageneration/DataGenerator.java @@ -0,0 +1,21 @@ +/* + * Copyright © 2025 XDEV Software (https://xdev.software) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package software.xdev.tci.db.datageneration; + +public interface DataGenerator +{ + // just marker +} diff --git a/db-jdbc-orm/src/main/java/software/xdev/tci/db/factory/BaseDBTCIFactory.java b/db-jdbc-orm/src/main/java/software/xdev/tci/db/factory/BaseDBTCIFactory.java new file mode 100644 index 00000000..549c1569 --- /dev/null +++ b/db-jdbc-orm/src/main/java/software/xdev/tci/db/factory/BaseDBTCIFactory.java @@ -0,0 +1,115 @@ +/* + * Copyright © 2025 XDEV Software (https://xdev.software) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package software.xdev.tci.db.factory; + +import java.sql.Connection; +import java.util.Objects; +import java.util.concurrent.TimeUnit; +import java.util.function.BiFunction; +import java.util.function.Supplier; + +import org.rnorth.ducttape.unreliables.Unreliables; +import org.testcontainers.containers.JdbcDatabaseContainer; + +import software.xdev.tci.db.BaseDBTCI; +import software.xdev.tci.db.containers.TestQueryStringAccessor; +import software.xdev.tci.envperf.EnvironmentPerformance; +import software.xdev.tci.factory.prestart.PreStartableTCIFactory; +import software.xdev.tci.factory.prestart.config.PreStartConfig; + + +public abstract class BaseDBTCIFactory, I extends BaseDBTCI> + extends PreStartableTCIFactory +{ + protected BaseDBTCIFactory( + final BiFunction infraBuilder, + final Supplier containerBuilder) + { + super(infraBuilder, containerBuilder, "db", "container.db", "DB"); + } + + protected BaseDBTCIFactory( + final BiFunction infraBuilder, + final Supplier containerBuilder, + final PreStartConfig config, + final Timeouts timeouts) + { + super(infraBuilder, containerBuilder, "db", "container.db", "DB", config, timeouts); + } + + protected BaseDBTCIFactory( + final BiFunction infraBuilder, + final Supplier containerBuilder, + final String containerBaseName, + final String containerLoggerName, + final String name) + { + super(infraBuilder, containerBuilder, containerBaseName, containerLoggerName, name); + } + + protected BaseDBTCIFactory( + final BiFunction infraBuilder, + final Supplier containerBuilder, + final String containerBaseName, + final String containerLoggerName, + final String name, + final PreStartConfig config, + final Timeouts timeouts) + { + super(infraBuilder, containerBuilder, containerBaseName, containerLoggerName, name, config, timeouts); + } + + @Override + protected void postProcessNew(final I infra) + { + // Docker needs a few milliseconds (usually less than 100) to reconfigure its networks + // In the meantime existing connections might fail if we proceed immediately + // So let's wait a moment here until everything is working + Unreliables.retryUntilSuccess( + 10 + EnvironmentPerformance.cpuSlownessFactor() * 2, + TimeUnit.SECONDS, + () -> { + try(final Connection con = infra.createDataSource().getConnection()) + { + con.isValid(10); + } + + if(infra.isMigrateAndInitializeEMC()) + { + // Check EMC + infra.useNewEntityManager(em -> em + .createNativeQuery(Objects.requireNonNullElse( + this.getTestQueryStringForEntityManager(infra), + "SELECT 1")) + .getResultList()); + } + return null; + }); + } + + protected String getTestQueryStringForEntityManager(final I infra) + { + try + { + return TestQueryStringAccessor.testQueryString(infra.getContainer()); + } + catch(final Exception ex) + { + this.log().warn("Failed to get test query string", ex); + return null; + } + } +} diff --git a/db-jdbc-orm/src/main/java/software/xdev/tci/db/persistence/EntityManagerController.java b/db-jdbc-orm/src/main/java/software/xdev/tci/db/persistence/EntityManagerController.java new file mode 100644 index 00000000..58ded1c9 --- /dev/null +++ b/db-jdbc-orm/src/main/java/software/xdev/tci/db/persistence/EntityManagerController.java @@ -0,0 +1,102 @@ +/* + * Copyright © 2025 XDEV Software (https://xdev.software) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package software.xdev.tci.db.persistence; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.EntityManagerFactory; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +/** + * Handles the creation and destruction of {@link EntityManager}s. + *

+ * This should only be used when a {@link EntityManager} has to be created manually, e.g. during tests. + */ +public class EntityManagerController implements AutoCloseable +{ + private static final Logger LOG = LoggerFactory.getLogger(EntityManagerController.class); + + protected final List activeEms = Collections.synchronizedList(new ArrayList<>()); + protected final EntityManagerFactory emf; + + public EntityManagerController(final EntityManagerFactory emf) + { + this.emf = Objects.requireNonNull(emf); + } + + /** + * Creates a new {@link EntityManager} with an internal {@link EntityManagerFactory}, which can be used to load and + * save data in the database. + * + *

+ * It may be a good idea to close the EntityManager, when you're finished with it. + *

+ *

+ * All created EntityManager are automatically cleaned up once {@link #close()} is called. + *

+ * + * @return EntityManager + */ + public EntityManager createEntityManager() + { + final EntityManager em = this.emf.createEntityManager(); + this.activeEms.add(em); + + return em; + } + + @Override + public void close() + { + LOG.info("Shutting down resources"); + this.activeEms.forEach(em -> + { + try + { + if(em.getTransaction() != null && em.getTransaction().isActive()) + { + em.getTransaction().rollback(); + } + em.close(); + } + catch(final Exception e) + { + LOG.warn("Unable to close EntityManager", e); + } + }); + + LOG.info("Cleared {}x EntityManagers", this.activeEms.size()); + + this.activeEms.clear(); + + try + { + this.emf.close(); + LOG.info("Released EntityManagerFactory"); + } + catch(final Exception e) + { + LOG.error("Failed to release EntityManagerFactory", e); + } + } +} diff --git a/db-jdbc-orm/src/main/java/software/xdev/tci/db/persistence/EntityManagerControllerFactory.java b/db-jdbc-orm/src/main/java/software/xdev/tci/db/persistence/EntityManagerControllerFactory.java new file mode 100644 index 00000000..22912444 --- /dev/null +++ b/db-jdbc-orm/src/main/java/software/xdev/tci/db/persistence/EntityManagerControllerFactory.java @@ -0,0 +1,185 @@ +/* + * Copyright © 2025 XDEV Software (https://xdev.software) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package software.xdev.tci.db.persistence; + +import static java.util.Map.entry; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.URL; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.function.Supplier; + +import jakarta.persistence.spi.ClassTransformer; +import jakarta.persistence.spi.PersistenceUnitTransactionType; + +import org.hibernate.cfg.JdbcSettings; +import org.hibernate.jpa.HibernatePersistenceProvider; +import org.springframework.orm.jpa.persistenceunit.MutablePersistenceUnitInfo; + +import software.xdev.tci.db.persistence.hibernate.DisableHibernateFormatMapper; + + +public class EntityManagerControllerFactory +{ + protected Supplier> entityClassesFinder; + protected String driverFullClassName; + protected String connectionProviderClassName; + protected String persistenceUnitName = "Test"; + protected String jdbcUrl; + protected String username; + protected String password; + protected Map additionalConfig; + + protected boolean disableHibernateFormatter = true; + + public EntityManagerControllerFactory() + { + } + + public EntityManagerControllerFactory(final Supplier> entityClassesFinder) + { + this.withEntityClassesFinder(entityClassesFinder); + } + + public EntityManagerControllerFactory withEntityClassesFinder(final Supplier> entityClassesFinder) + { + this.entityClassesFinder = entityClassesFinder; + return this; + } + + public EntityManagerControllerFactory withDriverFullClassName(final String driverFullClassName) + { + this.driverFullClassName = driverFullClassName; + return this; + } + + public EntityManagerControllerFactory withConnectionProviderClassName(final String connectionProviderClassName) + { + this.connectionProviderClassName = connectionProviderClassName; + return this; + } + + public EntityManagerControllerFactory withPersistenceUnitName(final String persistenceUnitName) + { + this.persistenceUnitName = persistenceUnitName; + return this; + } + + public EntityManagerControllerFactory withJdbcUrl(final String jdbcUrl) + { + this.jdbcUrl = jdbcUrl; + return this; + } + + public EntityManagerControllerFactory withUsername(final String username) + { + this.username = username; + return this; + } + + public EntityManagerControllerFactory withPassword(final String password) + { + this.password = password; + return this; + } + + public EntityManagerControllerFactory withAdditionalConfig(final Map additionalConfig) + { + this.additionalConfig = additionalConfig; + return this; + } + + public EntityManagerControllerFactory withDisableHibernateFormatter(final boolean disableHibernateFormatter) + { + this.disableHibernateFormatter = disableHibernateFormatter; + return this; + } + + protected MutablePersistenceUnitInfo createBasicMutablePersistenceUnitInfo() + { + final MutablePersistenceUnitInfo persistenceUnitInfo = new MutablePersistenceUnitInfo() + { + @Override + public void addTransformer(final ClassTransformer classTransformer) + { + // Do nothing + } + + @Override + public ClassLoader getNewTempClassLoader() + { + return null; + } + }; + persistenceUnitInfo.setTransactionType(PersistenceUnitTransactionType.RESOURCE_LOCAL); + persistenceUnitInfo.setPersistenceUnitName(this.persistenceUnitName); + persistenceUnitInfo.setPersistenceProviderClassName(HibernatePersistenceProvider.class.getName()); + return persistenceUnitInfo; + } + + protected Collection jarFileUrlsToAdd() throws IOException + { + return Collections.list(EntityManagerController.class + .getClassLoader() + .getResources("")); + } + + protected Map defaultPropertiesMap() + { + return new HashMap<>(Map.ofEntries( + entry(JdbcSettings.JAKARTA_JDBC_DRIVER, this.driverFullClassName), + entry(JdbcSettings.JAKARTA_JDBC_URL, this.jdbcUrl), + entry(JdbcSettings.JAKARTA_JDBC_USER, this.username), + entry(JdbcSettings.JAKARTA_JDBC_PASSWORD, this.password) + )); + } + + public EntityManagerController build() + { + final MutablePersistenceUnitInfo persistenceUnitInfo = this.createBasicMutablePersistenceUnitInfo(); + if(this.entityClassesFinder != null) + { + persistenceUnitInfo.getManagedClassNames().addAll(this.entityClassesFinder.get()); + } + try + { + this.jarFileUrlsToAdd().forEach(persistenceUnitInfo::addJarFileUrl); + } + catch(final IOException ioe) + { + throw new UncheckedIOException(ioe); + } + + final Map properties = this.defaultPropertiesMap(); + Optional.ofNullable(this.connectionProviderClassName) + .ifPresent(p -> properties.put(JdbcSettings.CONNECTION_PROVIDER, this.connectionProviderClassName)); + if(this.disableHibernateFormatter) + { + properties.putAll(DisableHibernateFormatMapper.properties()); + } + properties.putAll(this.additionalConfig); + return new EntityManagerController( + new HibernatePersistenceProvider().createContainerEntityManagerFactory( + persistenceUnitInfo, + properties)); + } +} diff --git a/tci-advanced-demo/tci-db/src/main/java/software/xdev/tci/demo/tci/db/persistence/TransactionExecutor.java b/db-jdbc-orm/src/main/java/software/xdev/tci/db/persistence/TransactionExecutor.java similarity index 60% rename from tci-advanced-demo/tci-db/src/main/java/software/xdev/tci/demo/tci/db/persistence/TransactionExecutor.java rename to db-jdbc-orm/src/main/java/software/xdev/tci/db/persistence/TransactionExecutor.java index 10866a5a..8fcba29d 100644 --- a/tci-advanced-demo/tci-db/src/main/java/software/xdev/tci/demo/tci/db/persistence/TransactionExecutor.java +++ b/db-jdbc-orm/src/main/java/software/xdev/tci/db/persistence/TransactionExecutor.java @@ -1,4 +1,19 @@ -package software.xdev.tci.demo.tci.db.persistence; +/* + * Copyright © 2025 XDEV Software (https://xdev.software) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package software.xdev.tci.db.persistence; import java.util.Objects; import java.util.function.Supplier; diff --git a/tci-advanced-demo/tci-db/src/main/java/software/xdev/tci/demo/tci/db/persistence/AnnotatedClassFinder.java b/db-jdbc-orm/src/main/java/software/xdev/tci/db/persistence/classfinder/AnnotatedClassFinder.java similarity index 65% rename from tci-advanced-demo/tci-db/src/main/java/software/xdev/tci/demo/tci/db/persistence/AnnotatedClassFinder.java rename to db-jdbc-orm/src/main/java/software/xdev/tci/db/persistence/classfinder/AnnotatedClassFinder.java index fe34afbc..8a9c87f0 100644 --- a/tci-advanced-demo/tci-db/src/main/java/software/xdev/tci/demo/tci/db/persistence/AnnotatedClassFinder.java +++ b/db-jdbc-orm/src/main/java/software/xdev/tci/db/persistence/classfinder/AnnotatedClassFinder.java @@ -1,4 +1,19 @@ -package software.xdev.tci.demo.tci.db.persistence; +/* + * Copyright © 2025 XDEV Software (https://xdev.software) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package software.xdev.tci.db.persistence.classfinder; import java.io.IOException; import java.io.UncheckedIOException; @@ -17,14 +32,10 @@ import org.springframework.util.SystemPropertyUtils; -public final class AnnotatedClassFinder +public class AnnotatedClassFinder { - private AnnotatedClassFinder() - { - } - @SuppressWarnings({"java:S1452", "java:S4968"}) // Returned so by stream - public static List> find( + public List> find( final String basePackage, final Class annotationClazz) { @@ -35,12 +46,14 @@ public static List> find( { return Stream.of(resourcePatternResolver.getResources( ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX - + resolveBasePackage(basePackage) + "/" + "**/*.class")) + + this.resolveBasePackage(basePackage) + "/" + "**/*.class")) .filter(Resource::isReadable) .map(resource -> { try { - return getIfIsCandidate(metadataReaderFactory.getMetadataReader(resource), annotationClazz); + return this.getIfIsCandidate( + metadataReaderFactory.getMetadataReader(resource), + annotationClazz); } catch(final IOException e) { @@ -56,12 +69,12 @@ public static List> find( } } - private static String resolveBasePackage(final String basePackage) + protected String resolveBasePackage(final String basePackage) { return ClassUtils.convertClassNameToResourcePath(SystemPropertyUtils.resolvePlaceholders(basePackage)); } - private static Class getIfIsCandidate( + protected Class getIfIsCandidate( final MetadataReader metadataReader, final Class annotationClazz) { diff --git a/db-jdbc-orm/src/main/java/software/xdev/tci/db/persistence/classfinder/CachedEntityAnnotatedClassNameFinder.java b/db-jdbc-orm/src/main/java/software/xdev/tci/db/persistence/classfinder/CachedEntityAnnotatedClassNameFinder.java new file mode 100644 index 00000000..8ea09fc4 --- /dev/null +++ b/db-jdbc-orm/src/main/java/software/xdev/tci/db/persistence/classfinder/CachedEntityAnnotatedClassNameFinder.java @@ -0,0 +1,52 @@ +/* + * Copyright © 2025 XDEV Software (https://xdev.software) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package software.xdev.tci.db.persistence.classfinder; + +import java.lang.annotation.Annotation; +import java.util.Set; +import java.util.function.Supplier; +import java.util.stream.Collectors; + + +public class CachedEntityAnnotatedClassNameFinder implements Supplier> +{ + protected final Supplier classFinderProvider = AnnotatedClassFinder::new; + protected final String basePackage; + protected final Class annotationClazz; + protected Set cache; + + public CachedEntityAnnotatedClassNameFinder( + final String basePackage, + final Class annotationClazz) + { + this.basePackage = basePackage; + this.annotationClazz = annotationClazz; + } + + @Override + public Set get() + { + if(this.cache == null) + { + this.cache = this.classFinderProvider.get() + .find(this.basePackage, this.annotationClazz) + .stream() + .map(Class::getName) + .collect(Collectors.toSet()); + } + return this.cache; + } +} diff --git a/tci-advanced-demo/tci-db/src/main/java/software/xdev/tci/demo/tci/db/persistence/hibernate/CachingStandardScanner.java b/db-jdbc-orm/src/main/java/software/xdev/tci/db/persistence/hibernate/CachingStandardScanner.java similarity index 72% rename from tci-advanced-demo/tci-db/src/main/java/software/xdev/tci/demo/tci/db/persistence/hibernate/CachingStandardScanner.java rename to db-jdbc-orm/src/main/java/software/xdev/tci/db/persistence/hibernate/CachingStandardScanner.java index 3d6eb857..3b56f52b 100644 --- a/tci-advanced-demo/tci-db/src/main/java/software/xdev/tci/demo/tci/db/persistence/hibernate/CachingStandardScanner.java +++ b/db-jdbc-orm/src/main/java/software/xdev/tci/db/persistence/hibernate/CachingStandardScanner.java @@ -1,4 +1,19 @@ -package software.xdev.tci.demo.tci.db.persistence.hibernate; +/* + * Copyright © 2025 XDEV Software (https://xdev.software) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package software.xdev.tci.db.persistence.hibernate; import java.net.URL; import java.util.List; @@ -12,6 +27,7 @@ import org.hibernate.boot.archive.scan.spi.ScanResult; +@SuppressWarnings("java:S6548") public class CachingStandardScanner extends StandardScanner { private static CachingStandardScanner instance; diff --git a/db-jdbc-orm/src/main/java/software/xdev/tci/db/persistence/hibernate/DisableHibernateFormatMapper.java b/db-jdbc-orm/src/main/java/software/xdev/tci/db/persistence/hibernate/DisableHibernateFormatMapper.java new file mode 100644 index 00000000..638ae292 --- /dev/null +++ b/db-jdbc-orm/src/main/java/software/xdev/tci/db/persistence/hibernate/DisableHibernateFormatMapper.java @@ -0,0 +1,70 @@ +/* + * Copyright © 2025 XDEV Software (https://xdev.software) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package software.xdev.tci.db.persistence.hibernate; + +import java.util.Map; + +import org.hibernate.cfg.MappingSettings; +import org.hibernate.type.descriptor.WrapperOptions; +import org.hibernate.type.descriptor.java.JavaType; +import org.hibernate.type.format.FormatMapper; + + +/** + * Under normal circumstances Hibernate tries to automatically look up a formatMapper for JSON and XML. + *

+ * There are multiple problems with this: + *

    + *
  • It tries to use Jackson for XML which might not be configured -> CRASH
  • + *
  • Storing XML, JSON or any other data structure inside a RELATIONAL DATABASE is idiotic
  • + *
  • Lookup slows down boot
  • + *
+ * + * @since Hibernate 6.3 + */ +public final class DisableHibernateFormatMapper +{ + private DisableHibernateFormatMapper() + { + } + + public static Map properties() + { + return Map.ofEntries( + Map.entry(MappingSettings.XML_MAPPING_ENABLED, false), + Map.entry(MappingSettings.JSON_FORMAT_MAPPER, new NoOpFormatMapper()), + Map.entry(MappingSettings.XML_FORMAT_MAPPER, new NoOpFormatMapper()) + ); + } + + public static class NoOpFormatMapper implements FormatMapper + { + @Override + public T fromString( + final CharSequence charSequence, + final JavaType javaType, + final WrapperOptions wrapperOptions) + { + throw new UnsupportedOperationException(); + } + + @Override + public String toString(final T value, final JavaType javaType, final WrapperOptions wrapperOptions) + { + throw new UnsupportedOperationException(); + } + } +} diff --git a/db-jdbc-orm/src/test/java/software/xdev/tci/db/containers/TestQueryStringAccessorTest.java b/db-jdbc-orm/src/test/java/software/xdev/tci/db/containers/TestQueryStringAccessorTest.java new file mode 100644 index 00000000..2e97f4cb --- /dev/null +++ b/db-jdbc-orm/src/test/java/software/xdev/tci/db/containers/TestQueryStringAccessorTest.java @@ -0,0 +1,91 @@ +/* + * Copyright © 2025 XDEV Software (https://xdev.software) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package software.xdev.tci.db.containers; + +import java.util.UUID; +import java.util.concurrent.Future; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.testcontainers.containers.JdbcDatabaseContainer; +import org.testcontainers.images.RemoteDockerImage; +import org.testcontainers.utility.DockerImageName; + + +class TestQueryStringAccessorTest +{ + @ParameterizedTest + @MethodSource + void checkIfAccessorWorks(final String expected, final String provided) throws Exception + { + final MockJDBCContainer container = new MockJDBCContainer( + new RemoteDockerImage(DockerImageName.parse("test" + UUID.randomUUID())) + .withImagePullPolicy(ignored -> false), provided); + Assertions.assertEquals(expected, TestQueryStringAccessor.testQueryString(container)); + } + + static Stream checkIfAccessorWorks() + { + return Stream.of( + Arguments.of("SELECT 123", "SELECT 123"), + Arguments.of(null, null) + ); + } + + static class MockJDBCContainer extends JdbcDatabaseContainer + { + private final String testQueryString; + + public MockJDBCContainer(final Future image, final String testQueryString) + { + super(image); + this.testQueryString = testQueryString; + } + + @Override + public String getDriverClassName() + { + return ""; + } + + @Override + public String getJdbcUrl() + { + return ""; + } + + @Override + public String getUsername() + { + return ""; + } + + @Override + public String getPassword() + { + return ""; + } + + @Override + protected String getTestQueryString() + { + return this.testQueryString; + } + } +} diff --git a/jul-to-slf4j/README.md b/jul-to-slf4j/README.md new file mode 100644 index 00000000..6f57b3ad --- /dev/null +++ b/jul-to-slf4j/README.md @@ -0,0 +1,3 @@ +# JUL to SLF4J + +Logging Adapter to redirect JUL to SLF4J diff --git a/jul-to-slf4j/pom.xml b/jul-to-slf4j/pom.xml new file mode 100644 index 00000000..f77d173f --- /dev/null +++ b/jul-to-slf4j/pom.xml @@ -0,0 +1,299 @@ + + + 4.0.0 + + software.xdev.tci + jul-to-slf4j + 2.0.0-SNAPSHOT + jar + + jul-to-slf4j + TCI - jul-to-slf4j + https://github.com/xdev-software/tci + + + https://github.com/xdev-software/tci + scm:git:https://github.com/xdev-software/tci.git + + + 2025 + + + XDEV Software + https://xdev.software + + + + + XDEV Software + XDEV Software + https://xdev.software + + + + + + Apache-2.0 + https://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + 17 + ${javaVersion} + + UTF-8 + UTF-8 + + + + + org.slf4j + jul-to-slf4j + 2.0.17 + + + + + + + + org.apache.maven.plugins + maven-site-plugin + 4.0.0-M16 + + + org.apache.maven.plugins + maven-project-info-reports-plugin + 3.9.0 + + + + + + com.mycila + license-maven-plugin + 5.0.0 + + + ${project.organization.url} + + + +
com/mycila/maven/plugin/license/templates/APACHE-2.txt
+ + src/main/java/** + src/test/java/** + +
+
+
+ + + first + + format + + process-sources + + +
+ + + org.apache.maven.plugins + maven-compiler-plugin + 3.14.0 + + ${maven.compiler.release} + + -proc:none + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.11.2 + + + attach-javadocs + package + + jar + + + + + true + none + + + + org.apache.maven.plugins + maven-source-plugin + 3.3.1 + + + attach-sources + package + + jar-no-fork + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.5.3 + +
+
+ + + ignore-service-loading + + + + src/main/resources + + META-INF/services/** + + + + + + + publish-sonatype-central-portal + + + + org.codehaus.mojo + flatten-maven-plugin + 1.7.1 + + ossrh + + + + flatten + process-resources + + flatten + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 3.2.7 + + + sign-artifacts + verify + + sign + + + + + + --pinentry-mode + loopback + + + + + + + + org.sonatype.central + central-publishing-maven-plugin + 0.8.0 + true + + sonatype-central-portal + true + + + + + + + checkstyle + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 3.6.0 + + + com.puppycrawl.tools + checkstyle + 10.26.1 + + + + ../.config/checkstyle/checkstyle.xml + true + + + + + check + + + + + + + + + pmd + + + + org.apache.maven.plugins + maven-pmd-plugin + 3.27.0 + + true + true + + ../.config/pmd/java/ruleset.xml + + + + + net.sourceforge.pmd + pmd-core + 7.15.0 + + + net.sourceforge.pmd + pmd-java + 7.15.0 + + + + + + + + + + org.apache.maven.plugins + maven-jxr-plugin + 3.6.0 + + + + + +
diff --git a/jul-to-slf4j/src/main/java/software/xdev/tci/logging/JULtoSLF4JRedirector.java b/jul-to-slf4j/src/main/java/software/xdev/tci/logging/JULtoSLF4JRedirector.java new file mode 100644 index 00000000..be2d0675 --- /dev/null +++ b/jul-to-slf4j/src/main/java/software/xdev/tci/logging/JULtoSLF4JRedirector.java @@ -0,0 +1,54 @@ +/* + * Copyright © 2025 XDEV Software (https://xdev.software) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package software.xdev.tci.logging; + +import org.slf4j.LoggerFactory; +import org.slf4j.bridge.SLF4JBridgeHandler; + + +public class JULtoSLF4JRedirector +{ + static final JULtoSLF4JRedirector INSTANCE = new JULtoSLF4JRedirector(); + + protected boolean installed; + + protected JULtoSLF4JRedirector() + { + } + + protected void redirectInternal() + { + if(this.installed) + { + return; + } + if(SLF4JBridgeHandler.isInstalled()) + { + this.installed = true; + return; + } + + LoggerFactory.getLogger(JULtoSLF4JRedirector.class).debug("Installing SLF4JBridgeHandler"); + SLF4JBridgeHandler.removeHandlersForRootLogger(); + SLF4JBridgeHandler.install(); + this.installed = true; + } + + public static void redirect() + { + INSTANCE.redirectInternal(); + } +} diff --git a/mockserver/README.md b/mockserver/README.md new file mode 100644 index 00000000..0eab3026 --- /dev/null +++ b/mockserver/README.md @@ -0,0 +1,5 @@ +# Mockserver + +TCI implementation for [Mockserver](https://github.com/xdev-software/mockserver-neolight). + +Note that the classes are abstract and require extension for your corresponding implementation. diff --git a/mockserver/pom.xml b/mockserver/pom.xml new file mode 100644 index 00000000..3ae81cc5 --- /dev/null +++ b/mockserver/pom.xml @@ -0,0 +1,312 @@ + + + 4.0.0 + + software.xdev.tci + mockserver + 2.0.0-SNAPSHOT + jar + + mockserver + TCI - mockserver + https://github.com/xdev-software/tci + + + https://github.com/xdev-software/tci + scm:git:https://github.com/xdev-software/tci.git + + + 2025 + + + XDEV Software + https://xdev.software + + + + + XDEV Software + XDEV Software + https://xdev.software + + + + + + Apache-2.0 + https://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + 17 + ${javaVersion} + + UTF-8 + UTF-8 + + + + + software.xdev.tci + base + 2.0.0-SNAPSHOT + + + + software.xdev.mockserver + testcontainers + 1.0.19 + compile + + + + software.xdev.mockserver + client + 1.0.19 + + + + + + + + org.apache.maven.plugins + maven-site-plugin + 4.0.0-M16 + + + org.apache.maven.plugins + maven-project-info-reports-plugin + 3.9.0 + + + + + + com.mycila + license-maven-plugin + 5.0.0 + + + ${project.organization.url} + + + +
com/mycila/maven/plugin/license/templates/APACHE-2.txt
+ + src/main/java/** + src/test/java/** + +
+
+
+ + + first + + format + + process-sources + + +
+ + + org.apache.maven.plugins + maven-compiler-plugin + 3.14.0 + + ${maven.compiler.release} + + -proc:none + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.11.2 + + + attach-javadocs + package + + jar + + + + + true + none + + + + org.apache.maven.plugins + maven-source-plugin + 3.3.1 + + + attach-sources + package + + jar-no-fork + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.5.3 + +
+
+ + + ignore-service-loading + + + + src/main/resources + + META-INF/services/** + + + + + + + publish-sonatype-central-portal + + + + org.codehaus.mojo + flatten-maven-plugin + 1.7.1 + + ossrh + + + + flatten + process-resources + + flatten + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 3.2.7 + + + sign-artifacts + verify + + sign + + + + + + --pinentry-mode + loopback + + + + + + + + org.sonatype.central + central-publishing-maven-plugin + 0.8.0 + true + + sonatype-central-portal + true + + + + + + + checkstyle + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 3.6.0 + + + com.puppycrawl.tools + checkstyle + 10.26.1 + + + + ../.config/checkstyle/checkstyle.xml + true + + + + + check + + + + + + + + + pmd + + + + org.apache.maven.plugins + maven-pmd-plugin + 3.27.0 + + true + true + + ../.config/pmd/java/ruleset.xml + + + + + net.sourceforge.pmd + pmd-core + 7.15.0 + + + net.sourceforge.pmd + pmd-java + 7.15.0 + + + + + + + + + + org.apache.maven.plugins + maven-jxr-plugin + 3.6.0 + + + + + +
diff --git a/mockserver/src/main/java/software/xdev/tci/mockserver/MockServerTCI.java b/mockserver/src/main/java/software/xdev/tci/mockserver/MockServerTCI.java new file mode 100644 index 00000000..8a3231b0 --- /dev/null +++ b/mockserver/src/main/java/software/xdev/tci/mockserver/MockServerTCI.java @@ -0,0 +1,50 @@ +/* + * Copyright © 2025 XDEV Software (https://xdev.software) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package software.xdev.tci.mockserver; + +import software.xdev.mockserver.client.MockServerClient; +import software.xdev.tci.TCI; +import software.xdev.testcontainers.mockserver.containers.MockServerContainer; + + +public abstract class MockServerTCI extends TCI +{ + protected MockServerClient client; + + protected MockServerTCI(final MockServerContainer container, final String networkAlias) + { + super(container, networkAlias); + } + + @Override + public void start(final String containerName) + { + super.start(containerName); + this.client = new MockServerClient(this.getContainer().getHost(), this.getContainer().getServerPort()); + } + + @Override + public void stop() + { + this.client = null; + super.stop(); + } + + public MockServerClient getClient() + { + return this.client; + } +} diff --git a/mockserver/src/main/java/software/xdev/tci/mockserver/factory/MockServerTCIFactory.java b/mockserver/src/main/java/software/xdev/tci/mockserver/factory/MockServerTCIFactory.java new file mode 100644 index 00000000..d3d8b25e --- /dev/null +++ b/mockserver/src/main/java/software/xdev/tci/mockserver/factory/MockServerTCIFactory.java @@ -0,0 +1,71 @@ +/* + * Copyright © 2025 XDEV Software (https://xdev.software) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package software.xdev.tci.mockserver.factory; + +import java.util.List; +import java.util.function.BiFunction; +import java.util.function.Supplier; + +import org.testcontainers.containers.Network; + +import software.xdev.tci.factory.ondemand.OnDemandTCIFactory; +import software.xdev.tci.misc.ContainerMemory; +import software.xdev.tci.mockserver.MockServerTCI; +import software.xdev.testcontainers.mockserver.containers.MockServerContainer; + + +public abstract class MockServerTCIFactory + extends OnDemandTCIFactory +{ + protected MockServerTCIFactory( + final BiFunction infraBuilder, + final Supplier containerBuilder, + final String containerBaseName, + final String containerLoggerName) + { + super(infraBuilder, containerBuilder, containerBaseName, containerLoggerName); + } + + @SuppressWarnings("resource") + protected MockServerTCIFactory( + final BiFunction infraBuilder, + final String additionalContainerBaseName, + final String additionalLoggerName) + { + super( + infraBuilder, + () -> new MockServerContainer() + .withCreateContainerCmdModifier(cmd -> cmd.getHostConfig().withMemory(ContainerMemory.M512M)), + "mockserver-" + additionalContainerBaseName, + "container.mockserver." + additionalLoggerName); + } + + public I getNew( + final Network network, + final String name, + final String... networkAliases) + { + return this.getNew( + network, c -> c.withNetworkAliases(networkAliases) + .setLogConsumers(List.of(getLogConsumer(this.containerLoggerName + "." + name)))); + } + + @Override + public I getNew(final Network network) + { + throw new UnsupportedOperationException(); + } +} diff --git a/mockserver/src/main/java/software/xdev/tci/mockserver/factory/PreStartableMockServerTCIFactory.java b/mockserver/src/main/java/software/xdev/tci/mockserver/factory/PreStartableMockServerTCIFactory.java new file mode 100644 index 00000000..14d0346b --- /dev/null +++ b/mockserver/src/main/java/software/xdev/tci/mockserver/factory/PreStartableMockServerTCIFactory.java @@ -0,0 +1,44 @@ +/* + * Copyright © 2025 XDEV Software (https://xdev.software) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package software.xdev.tci.mockserver.factory; + +import java.util.function.BiFunction; + +import software.xdev.tci.factory.prestart.PreStartableTCIFactory; +import software.xdev.tci.misc.ContainerMemory; +import software.xdev.tci.mockserver.MockServerTCI; +import software.xdev.testcontainers.mockserver.containers.MockServerContainer; + + +public abstract class PreStartableMockServerTCIFactory + extends PreStartableTCIFactory +{ + @SuppressWarnings("resource") + protected PreStartableMockServerTCIFactory( + final BiFunction infraBuilder, + final String additionalContainerBaseName, + final String additionalLoggerName, + final String prestartName) + { + super( + infraBuilder, + () -> new MockServerContainer() + .withCreateContainerCmdModifier(cmd -> cmd.getHostConfig().withMemory(ContainerMemory.M512M)), + "mockserver-" + additionalContainerBaseName, + "container.mockserver." + additionalLoggerName, + prestartName); + } +} diff --git a/oidc-server-mock/README.md b/oidc-server-mock/README.md new file mode 100644 index 00000000..daef09e0 --- /dev/null +++ b/oidc-server-mock/README.md @@ -0,0 +1,4 @@ +# OIDC Server Mock + +TCI for [OIDC Server Mock](https://github.com/xdev-software/oidc-server-mock). + diff --git a/oidc-server-mock/pom.xml b/oidc-server-mock/pom.xml new file mode 100644 index 00000000..f4110454 --- /dev/null +++ b/oidc-server-mock/pom.xml @@ -0,0 +1,305 @@ + + + 4.0.0 + + software.xdev.tci + oidc-server-mock + 2.0.0-SNAPSHOT + jar + + oidc-server-mock + TCI - oidc-server-mock + https://github.com/xdev-software/tci + + + https://github.com/xdev-software/tci + scm:git:https://github.com/xdev-software/tci.git + + + 2025 + + + XDEV Software + https://xdev.software + + + + + XDEV Software + XDEV Software + https://xdev.software + + + + + + Apache-2.0 + https://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + 17 + ${javaVersion} + + UTF-8 + UTF-8 + + + + + software.xdev.tci + base + 2.0.0-SNAPSHOT + + + + org.apache.httpcomponents.client5 + httpclient5 + 5.5 + + + + + + + + org.apache.maven.plugins + maven-site-plugin + 4.0.0-M16 + + + org.apache.maven.plugins + maven-project-info-reports-plugin + 3.9.0 + + + + + + com.mycila + license-maven-plugin + 5.0.0 + + + ${project.organization.url} + + + +
com/mycila/maven/plugin/license/templates/APACHE-2.txt
+ + src/main/java/** + src/test/java/** + +
+
+
+ + + first + + format + + process-sources + + +
+ + + org.apache.maven.plugins + maven-compiler-plugin + 3.14.0 + + ${maven.compiler.release} + + -proc:none + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.11.2 + + + attach-javadocs + package + + jar + + + + + true + none + + + + org.apache.maven.plugins + maven-source-plugin + 3.3.1 + + + attach-sources + package + + jar-no-fork + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.5.3 + +
+
+ + + ignore-service-loading + + + + src/main/resources + + META-INF/services/** + + + + + + + publish-sonatype-central-portal + + + + org.codehaus.mojo + flatten-maven-plugin + 1.7.1 + + ossrh + + + + flatten + process-resources + + flatten + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 3.2.7 + + + sign-artifacts + verify + + sign + + + + + + --pinentry-mode + loopback + + + + + + + + org.sonatype.central + central-publishing-maven-plugin + 0.8.0 + true + + sonatype-central-portal + true + + + + + + + checkstyle + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 3.6.0 + + + com.puppycrawl.tools + checkstyle + 10.26.1 + + + + ../.config/checkstyle/checkstyle.xml + true + + + + + check + + + + + + + + + pmd + + + + org.apache.maven.plugins + maven-pmd-plugin + 3.27.0 + + true + true + + ../.config/pmd/java/ruleset.xml + + + + + net.sourceforge.pmd + pmd-core + 7.15.0 + + + net.sourceforge.pmd + pmd-java + 7.15.0 + + + + + + + + + + org.apache.maven.plugins + maven-jxr-plugin + 3.6.0 + + + + + +
diff --git a/tci-advanced-demo/tci-oidc/src/main/java/software/xdev/tci/demo/tci/oidc/OIDCTCI.java b/oidc-server-mock/src/main/java/software/xdev/tci/oidc/OIDCTCI.java similarity index 56% rename from tci-advanced-demo/tci-oidc/src/main/java/software/xdev/tci/demo/tci/oidc/OIDCTCI.java rename to oidc-server-mock/src/main/java/software/xdev/tci/oidc/OIDCTCI.java index f8ca2229..a68cf016 100644 --- a/tci-advanced-demo/tci-oidc/src/main/java/software/xdev/tci/demo/tci/oidc/OIDCTCI.java +++ b/oidc-server-mock/src/main/java/software/xdev/tci/oidc/OIDCTCI.java @@ -1,4 +1,19 @@ -package software.xdev.tci.demo.tci.oidc; +/* + * Copyright © 2025 XDEV Software (https://xdev.software) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package software.xdev.tci.oidc; import java.io.IOException; import java.io.UncheckedIOException; @@ -23,20 +38,27 @@ import org.rnorth.ducttape.unreliables.Unreliables; import software.xdev.tci.TCI; -import software.xdev.tci.demo.tci.oidc.containers.OIDCServerContainer; +import software.xdev.tci.envperf.EnvironmentPerformance; +import software.xdev.tci.misc.http.HttpClientCloser; +import software.xdev.tci.oidc.containers.OIDCServerContainer; public class OIDCTCI extends TCI { protected static final Duration DEFAULT_TIMEOUT = Duration.ofSeconds(30); - public static final String DEFAULT_DOMAIN = "example.org"; public static final String CLIENT_ID = OIDCServerContainer.DEFAULT_CLIENT_ID; public static final String CLIENT_SECRET = OIDCServerContainer.DEFAULT_CLIENT_SECRET; - public static final String DEFAULT_USER_EMAIL = "test@" + DEFAULT_DOMAIN; - public static final String DEFAULT_USER_NAME = "Testuser"; - public static final String DEFAULT_USER_PASSWORD = "pwd"; + protected static final String DEFAULT_DOMAIN = "example.local"; + protected static final String DEFAULT_USER_EMAIL = "test@" + DEFAULT_DOMAIN; + protected static final String DEFAULT_USER_NAME = "Testuser"; + protected static final String DEFAULT_USER_PASSWORD = "pwd"; + + protected boolean shouldAddDefaultUser = true; + protected String defaultUserEmail = DEFAULT_USER_EMAIL; + protected String defaultUserName = DEFAULT_USER_NAME; + protected String defaultUserPassword = DEFAULT_USER_PASSWORD; public OIDCTCI(final OIDCServerContainer container, final String networkAlias) { @@ -47,25 +69,28 @@ public OIDCTCI(final OIDCServerContainer container, final String networkAlias) public void start(final String containerName) { super.start(containerName); - this.addUser(DEFAULT_USER_EMAIL, DEFAULT_USER_NAME, DEFAULT_USER_PASSWORD); + if(this.shouldAddDefaultUser) + { + this.addUser(this.getDefaultUserEmail(), this.getDefaultUserName(), this.getDefaultUserPassword()); + } - // Otherwise app server response may time out as initial requests needs a few seconds + // Warm up; Otherwise slow initial response may cause a timeout during tests this.warmUpWellKnownJWKsEndpoint(); } public String getDefaultUserEmail() { - return DEFAULT_USER_EMAIL; + return this.defaultUserEmail; } public String getDefaultUserName() { - return DEFAULT_USER_NAME; + return this.defaultUserName; } public String getDefaultUserPassword() { - return DEFAULT_USER_PASSWORD; + return this.defaultUserPassword; } public static String getInternalHttpBaseEndPoint(final String networkAlias) @@ -83,23 +108,30 @@ public String getExternalHttpBaseEndPoint() return this.getContainer().getExternalHttpBaseEndPoint(); } + @SuppressWarnings("PMD.UseTryWithResources") // Java 17 support public void warmUpWellKnownJWKsEndpoint() { - // NOTE: ON JDK 21+ you should close this! + final int slownessFactor = EnvironmentPerformance.cpuSlownessFactor(); final HttpClient httpClient = HttpClient.newBuilder() - .connectTimeout(Duration.ofSeconds(2L)) + .connectTimeout(Duration.ofSeconds(1L + slownessFactor)) .build(); - Unreliables.retryUntilSuccess( - 5, - () -> - httpClient.send( + try + { + Unreliables.retryUntilSuccess( + 5 + slownessFactor, + () -> httpClient.send( HttpRequest.newBuilder(URI.create( this.getExternalHttpBaseEndPoint() + "/.well-known/openid-configuration/jwks")) - .timeout(Duration.ofSeconds(10L)) + .timeout(Duration.ofSeconds(10L + slownessFactor * 5L)) .GET() .build(), HttpResponse.BodyHandlers.discarding())); + } + finally + { + HttpClientCloser.close(httpClient); + } } public void addUser( @@ -107,18 +139,9 @@ public void addUser( final String name, final String pw) { - addUser(this.getContainer(), email, name, pw); - } - - protected static void addUser( - final OIDCServerContainer container, - final String email, - final String name, - final String pw) - { - try(final CloseableHttpClient client = createDefaultHttpClient()) + try(final CloseableHttpClient client = this.createDefaultHttpClient()) { - final HttpPost post = new HttpPost(container.getExternalHttpBaseEndPoint() + "/api/v1/user"); + final HttpPost post = new HttpPost(this.getContainer().getExternalHttpBaseEndPoint() + "/api/v1/user"); post.setEntity(new StringEntity(""" { "SubjectId":"%s", @@ -161,7 +184,7 @@ protected static void addUser( } } - protected static CloseableHttpClient createDefaultHttpClient() + protected CloseableHttpClient createDefaultHttpClient() { return HttpClientBuilder.create() .setConnectionManager(PoolingHttpClientConnectionManagerBuilder.create() @@ -172,4 +195,32 @@ protected static CloseableHttpClient createDefaultHttpClient() .build()) .build(); } + + // region Configure + + protected OIDCTCI withShouldAddDefaultUser(final boolean shouldAddDefaultUser) + { + this.shouldAddDefaultUser = shouldAddDefaultUser; + return this; + } + + public OIDCTCI withDefaultUserEmail(final String defaultUserEmail) + { + this.defaultUserEmail = defaultUserEmail; + return this; + } + + public OIDCTCI withDefaultUserName(final String defaultUserName) + { + this.defaultUserName = defaultUserName; + return this; + } + + public OIDCTCI withDefaultUserPassword(final String defaultUserPassword) + { + this.defaultUserPassword = defaultUserPassword; + return this; + } + + // endregion } diff --git a/oidc-server-mock/src/main/java/software/xdev/tci/oidc/containers/OIDCServerContainer.java b/oidc-server-mock/src/main/java/software/xdev/tci/oidc/containers/OIDCServerContainer.java new file mode 100644 index 00000000..4acef4b1 --- /dev/null +++ b/oidc-server-mock/src/main/java/software/xdev/tci/oidc/containers/OIDCServerContainer.java @@ -0,0 +1,168 @@ +/* + * Copyright © 2025 XDEV Software (https://xdev.software) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package software.xdev.tci.oidc.containers; + +import java.util.List; +import java.util.stream.Collectors; + +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.utility.DockerImageName; + + +@SuppressWarnings("java:S2160") +public class OIDCServerContainer extends GenericContainer +{ + public static final int PORT = 8080; + + public static final String DEFAULT_CLIENT_ID = "client-id1"; + public static final String DEFAULT_CLIENT_SECRET = "client-secret1"; + + protected String clientId = DEFAULT_CLIENT_ID; + protected String clientSecret = DEFAULT_CLIENT_SECRET; + protected List additionalAllowedScopes; + + public OIDCServerContainer() + { + super(DockerImageName.parse("xdevsoftware/oidc-server-mock:1")); + this.addExposedPort(PORT); + } + + public OIDCServerContainer withDefaultEnvConfig() + { + this.addEnv("ASPNETCORE_ENVIRONMENT", "Development"); + this.addEnv("ASPNET_SERVICES_OPTIONS_INLINE", this.buildAspnetServicesOptionsInline()); + this.addEnv("SERVER_OPTIONS_INLINE", this.buildServerOptionsInline()); + this.addEnv("LOGIN_OPTIONS_INLINE", this.buildLoginOptionsInline()); + this.addEnv("LOGOUT_OPTIONS_INLINE", this.buildLogoutOptionsInline()); + this.addEnv("CLIENTS_CONFIGURATION_INLINE", this.buildClientsConfigurationInline()); + return this.self(); + } + + protected String buildAspnetServicesOptionsInline() + { + return """ + { + "ForwardedHeadersOptions": { + "ForwardedHeaders" : "All" + } + } + """; + } + + protected String buildServerOptionsInline() + { + return """ + { + "AccessTokenJwtType": "JWT", + "Discovery": { + "ShowKeySet": true + }, + "Authentication": { + "CookieSameSiteMode": "Lax", + "CheckSessionCookieSameSiteMode": "Lax" + } + } + """; + } + + protected String buildLoginOptionsInline() + { + return """ + { + "AllowRememberLogin": false + } + """; + } + + protected String buildLogoutOptionsInline() + { + return """ + { + "AutomaticRedirectAfterSignOut": true + } + """; + } + + protected String buildClientsConfigurationInline() + { + return """ + [ + { + "ClientId": "%s", + "ClientSecrets": [ + "%s" + ], + "Description": "TimelineDesc", + "AllowedGrantTypes": [ + "authorization_code", + "refresh_token" + ], + "RedirectUris": [ + "*" + ], + "AllowedScopes": [ + "openid", + "profile", + "email", + "offline_access" + %s + ], + "AlwaysIncludeUserClaimsInIdToken": true, + "AllowOfflineAccess": true, + "RequirePkce": false + } + ] + """.formatted( + this.clientId, + this.clientSecret, + this.additionalAllowedScopes != null && !this.additionalAllowedScopes.isEmpty() + ? ("," + this.additionalAllowedScopes.stream() + .map(s -> "\"" + s + "\"") + .collect(Collectors.joining(","))) + : ""); + } + + public String getExternalHttpBaseEndPoint() + { + // noinspection HttpUrlsUsage + return "http://" + + this.getHost() + + ":" + + this.getMappedPort(PORT); + } + + // region Config + + public OIDCServerContainer withClientId(final String clientId) + { + this.clientId = clientId; + return this; + } + + public OIDCServerContainer withClientSecret(final String clientSecret) + { + this.clientSecret = clientSecret; + return this; + } + + public OIDCServerContainer withAdditionalAllowedScopes(final List additionalAllowedScopes) + { + this.additionalAllowedScopes = additionalAllowedScopes; + return this; + } + + // endregion +} diff --git a/oidc-server-mock/src/main/java/software/xdev/tci/oidc/factory/OIDCTCIFactory.java b/oidc-server-mock/src/main/java/software/xdev/tci/oidc/factory/OIDCTCIFactory.java new file mode 100644 index 00000000..68461c2d --- /dev/null +++ b/oidc-server-mock/src/main/java/software/xdev/tci/oidc/factory/OIDCTCIFactory.java @@ -0,0 +1,111 @@ +/* + * Copyright © 2025 XDEV Software (https://xdev.software) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package software.xdev.tci.oidc.factory; + +import java.time.Duration; +import java.util.function.BiFunction; +import java.util.function.Supplier; + +import org.apache.hc.core5.http.HttpStatus; +import org.testcontainers.containers.wait.strategy.HostPortWaitStrategy; +import org.testcontainers.containers.wait.strategy.HttpWaitStrategy; +import org.testcontainers.containers.wait.strategy.WaitAllStrategy; + +import software.xdev.tci.envperf.EnvironmentPerformance; +import software.xdev.tci.factory.prestart.PreStartableTCIFactory; +import software.xdev.tci.factory.prestart.config.PreStartConfig; +import software.xdev.tci.misc.ContainerMemory; +import software.xdev.tci.oidc.OIDCTCI; +import software.xdev.tci.oidc.containers.OIDCServerContainer; + + +public class OIDCTCIFactory extends PreStartableTCIFactory +{ + private static final String DEFAULT_CONTAINER_LOGGER_NAME = "container.oidc"; + private static final String DEFAULT_CONTAINER_BASE_NAME = "oidc"; + private static final String DEFAULT_NAME = "OIDC"; + + public OIDCTCIFactory( + final BiFunction infraBuilder, + final Supplier containerBuilder) + { + super(infraBuilder, containerBuilder, DEFAULT_CONTAINER_BASE_NAME, DEFAULT_CONTAINER_LOGGER_NAME, + DEFAULT_NAME); + } + + public OIDCTCIFactory( + final BiFunction infraBuilder, + final Supplier containerBuilder, + final PreStartConfig config, + final Timeouts timeouts) + { + super( + infraBuilder, containerBuilder, + DEFAULT_CONTAINER_BASE_NAME, DEFAULT_CONTAINER_LOGGER_NAME, DEFAULT_NAME, config, timeouts); + } + + public OIDCTCIFactory( + final BiFunction infraBuilder, + final Supplier containerBuilder, + final String containerBaseName, + final String containerLoggerName, + final String name) + { + super(infraBuilder, containerBuilder, containerBaseName, containerLoggerName, name); + } + + public OIDCTCIFactory( + final BiFunction infraBuilder, + final Supplier containerBuilder, + final String containerBaseName, + final String containerLoggerName, + final String name, + final PreStartConfig config, + final Timeouts timeouts) + { + super(infraBuilder, containerBuilder, containerBaseName, containerLoggerName, name, config, timeouts); + } + + public OIDCTCIFactory() + { + super( + OIDCTCI::new, + OIDCTCIFactory::createDefaultContainer, + DEFAULT_CONTAINER_BASE_NAME, + DEFAULT_CONTAINER_LOGGER_NAME, + DEFAULT_NAME); + } + + @SuppressWarnings({"resource", "checkstyle:MagicNumber"}) + protected static OIDCServerContainer createDefaultContainer() + { + return new OIDCServerContainer() + .withCreateContainerCmdModifier(cmd -> cmd.getHostConfig().withMemory(ContainerMemory.M512M)) + .waitingFor( + new WaitAllStrategy() + .withStartupTimeout(Duration.ofSeconds(40L + 20L * EnvironmentPerformance.cpuSlownessFactor())) + .withStrategy(new HostPortWaitStrategy()) + .withStrategy( + new HttpWaitStrategy() + .forPort(OIDCServerContainer.PORT) + .forPath("/") + .forStatusCode(HttpStatus.SC_OK) + .withReadTimeout(Duration.ofSeconds(10)) + ) + ) + .withDefaultEnvConfig(); + } +} diff --git a/pom.xml b/pom.xml index 6f9afdaf..0bfe8aaa 100644 --- a/pom.xml +++ b/pom.xml @@ -4,9 +4,9 @@ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - software.xdev - tci-base-root - 1.2.1-SNAPSHOT + software.xdev.tci + root + 2.0.0-SNAPSHOT pom @@ -15,9 +15,18 @@ - tci-base - tci-base-demo - tci-advanced-demo + base + bom + db-jdbc-orm + jul-to-slf4j + mockserver + oidc-server-mock + selenium + spring-dao-support + + + base-demo + advanced-demo @@ -46,7 +55,7 @@ com.puppycrawl.tools checkstyle - 10.25.0 + 10.26.1 @@ -71,7 +80,7 @@ org.apache.maven.plugins maven-pmd-plugin - 3.26.0 + 3.27.0 true true @@ -83,12 +92,12 @@ net.sourceforge.pmd pmd-core - 7.14.0 + 7.15.0 net.sourceforge.pmd pmd-java - 7.14.0 + 7.15.0 diff --git a/renovate.json5 b/renovate.json5 index 2d1edde4..2a9899bb 100644 --- a/renovate.json5 +++ b/renovate.json5 @@ -4,7 +4,7 @@ "packageRules": [ { "description": "Ignore project internal dependencies", - "packagePattern": "^software.xdev:tci-base", + "packagePattern": "^software.xdev.tci", "datasources": [ "maven" ], @@ -72,6 +72,16 @@ "maven" ], "groupName": "org.junit" + }, + { + "description": "Group org.hibernate.orm", + "matchPackagePatterns": [ + "^org.hibernate.orm" + ], + "datasources": [ + "maven" + ], + "groupName": "org.hibernate.orm" } ] } diff --git a/selenium/README.md b/selenium/README.md new file mode 100644 index 00000000..3b14ee10 --- /dev/null +++ b/selenium/README.md @@ -0,0 +1,11 @@ +# Selenium + +TCI for Selenium. + +## Features + +* All improvements from [xdev-software/testcontainers-selenium](https://github.com/xdev-software/testcontainers-selenium/) +* Predefined browsers (Firefox, Chromium) +* NoVNC Support +* Full scale support for recording videos +* Browser Logs can be enabled if required diff --git a/selenium/pom.xml b/selenium/pom.xml new file mode 100644 index 00000000..b33c016e --- /dev/null +++ b/selenium/pom.xml @@ -0,0 +1,354 @@ + + + 4.0.0 + + software.xdev.tci + selenium + 2.0.0-SNAPSHOT + jar + + selenium + TCI - selenium + https://github.com/xdev-software/tci + + + https://github.com/xdev-software/tci + scm:git:https://github.com/xdev-software/tci.git + + + 2025 + + + XDEV Software + https://xdev.software + + + + + XDEV Software + XDEV Software + https://xdev.software + + + + + + Apache-2.0 + https://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + 17 + ${javaVersion} + + UTF-8 + UTF-8 + + + + + + org.seleniumhq.selenium + selenium-dependencies-bom + 4.34.0 + pom + import + + + + + + + software.xdev.tci + base + 2.0.0-SNAPSHOT + + + software.xdev.tci + jul-to-slf4j + 2.0.0-SNAPSHOT + + + + software.xdev + testcontainers-selenium + 1.2.4 + + + + org.junit.jupiter + junit-jupiter-api + 5.13.2 + compile + + + + + org.seleniumhq.selenium + selenium-remote-driver + + + + io.opentelemetry + * + + + + + org.seleniumhq.selenium + selenium-support + + + org.seleniumhq.selenium + selenium-firefox-driver + + + org.seleniumhq.selenium + selenium-chrome-driver + + + + + + + + org.apache.maven.plugins + maven-site-plugin + 4.0.0-M16 + + + org.apache.maven.plugins + maven-project-info-reports-plugin + 3.9.0 + + + + + + com.mycila + license-maven-plugin + 5.0.0 + + + ${project.organization.url} + + + +
com/mycila/maven/plugin/license/templates/APACHE-2.txt
+ + src/main/java/** + src/test/java/** + +
+
+
+ + + first + + format + + process-sources + + +
+ + + org.apache.maven.plugins + maven-compiler-plugin + 3.14.0 + + ${maven.compiler.release} + + -proc:none + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.11.2 + + + attach-javadocs + package + + jar + + + + + true + none + + + + org.apache.maven.plugins + maven-source-plugin + 3.3.1 + + + attach-sources + package + + jar-no-fork + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.5.3 + +
+
+ + + ignore-service-loading + + + + src/main/resources + + META-INF/services/** + + + + + + + publish-sonatype-central-portal + + + + org.codehaus.mojo + flatten-maven-plugin + 1.7.1 + + ossrh + + + + flatten + process-resources + + flatten + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 3.2.7 + + + sign-artifacts + verify + + sign + + + + + + --pinentry-mode + loopback + + + + + + + + org.sonatype.central + central-publishing-maven-plugin + 0.8.0 + true + + sonatype-central-portal + true + + + + + + + checkstyle + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 3.6.0 + + + com.puppycrawl.tools + checkstyle + 10.26.1 + + + + ../.config/checkstyle/checkstyle.xml + true + + + + + check + + + + + + + + + pmd + + + + org.apache.maven.plugins + maven-pmd-plugin + 3.27.0 + + true + true + + ../.config/pmd/java/ruleset.xml + + + + + net.sourceforge.pmd + pmd-core + 7.15.0 + + + net.sourceforge.pmd + pmd-java + 7.15.0 + + + + + + + + + + org.apache.maven.plugins + maven-jxr-plugin + 3.6.0 + + + + + +
diff --git a/selenium/src/main/java/software/xdev/tci/selenium/BrowserTCI.java b/selenium/src/main/java/software/xdev/tci/selenium/BrowserTCI.java new file mode 100644 index 00000000..b19373af --- /dev/null +++ b/selenium/src/main/java/software/xdev/tci/selenium/BrowserTCI.java @@ -0,0 +1,379 @@ +/* + * Copyright © 2025 XDEV Software (https://xdev.software) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package software.xdev.tci.selenium; + +import java.time.Duration; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.openqa.selenium.MutableCapabilities; +import org.openqa.selenium.WebDriver; +import org.openqa.selenium.bidi.log.LogLevel; +import org.openqa.selenium.bidi.log.StackTrace; +import org.openqa.selenium.bidi.module.LogInspector; +import org.openqa.selenium.remote.Augmenter; +import org.openqa.selenium.remote.HttpCommandExecutor; +import org.openqa.selenium.remote.RemoteWebDriver; +import org.openqa.selenium.remote.http.ClientConfig; +import org.openqa.selenium.remote.http.HttpClient; +import org.rnorth.ducttape.timeouts.Timeouts; +import org.rnorth.ducttape.unreliables.Unreliables; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.lifecycle.TestDescription; + +import software.xdev.tci.TCI; +import software.xdev.tci.envperf.EnvironmentPerformance; +import software.xdev.tci.logging.JULtoSLF4JRedirector; +import software.xdev.tci.selenium.containers.SeleniumBrowserWebDriverContainer; + + +public class BrowserTCI extends TCI +{ + private static final Logger LOG = LoggerFactory.getLogger(BrowserTCI.class); + public static final Pattern IP_PORT_EXTRACTOR = Pattern.compile("(.*\\/\\/)([0-9a-f\\:\\.]*):(\\d*)(\\/.*)"); + public static final Set CAPS_TO_PATCH_ADDRESS = Set.of("webSocketUrl", "se:cdp"); + + protected final MutableCapabilities capabilities; + + // Use the new world by default + // https://www.selenium.dev/documentation/webdriver/bidi + protected boolean bidiEnabled = true; + + // Disables the (not standardized) Chrome Dev Tools (CDP) protocol (when bidi is enabled). + // CDP requires additional maven dependencies (e.g. selenium-devtools-v137) that are + // NOT present and result in a warning. + protected boolean deactivateCDPIfPossible = true; + + protected ClientConfig clientConfig = ClientConfig.defaultConfig(); + protected int webDriverRetryCount = 2; + protected int webDriverRetrySec = 30; + protected Consumer browserConsoleLogConsumer; + protected Set browserConsoleLogLevels; + + protected RemoteWebDriver webDriver; + protected LogInspector logInspector; + + public BrowserTCI( + final SeleniumBrowserWebDriverContainer container, + final String networkAlias, + final MutableCapabilities capabilities) + { + super(container, networkAlias); + this.capabilities = capabilities; + } + + public BrowserTCI withBidiEnabled(final boolean bidiEnabled) + { + this.bidiEnabled = bidiEnabled; + return this; + } + + public BrowserTCI withDeactivateCDPIfPossible(final boolean deactivateCDPIfPossible) + { + this.deactivateCDPIfPossible = deactivateCDPIfPossible; + return this; + } + + public BrowserTCI withClientConfig(final ClientConfig clientConfig) + { + this.clientConfig = Objects.requireNonNull(clientConfig); + return this; + } + + public BrowserTCI withWebDriverRetryCount(final int webDriverRetryCount) + { + this.webDriverRetryCount = Math.min(Math.max(webDriverRetryCount, 2), 10); + return this; + } + + public BrowserTCI withWebDriverRetrySec(final int webDriverRetrySec) + { + this.webDriverRetrySec = Math.min(Math.max(webDriverRetrySec, 10), 10 * 60); + return this; + } + + public BrowserTCI withBrowserConsoleLog( + final Consumer browserConsoleLogConsumer, + final Set browserConsoleLogLevels) + { + this.browserConsoleLogConsumer = browserConsoleLogConsumer; + this.browserConsoleLogLevels = browserConsoleLogLevels; + return this; + } + + @Override + public void start(final String containerName) + { + super.start(containerName); + + // Selenium uses JUL + JULtoSLF4JRedirector.redirect(); + + this.initWebDriver(); + } + + @SuppressWarnings("checkstyle:MagicNumber") + protected void initWebDriver() + { + LOG.debug("Initializing WebDriver"); + final AtomicInteger retryCounter = new AtomicInteger(1); + this.webDriver = Unreliables.retryUntilSuccess( + this.webDriverRetryCount, + () -> this.tryCreateWebDriver(retryCounter)); + + // Default timeout is 5m? -> Single test failure causes up to 10m delays + // https://w3c.github.io/webdriver/#timeouts + this.webDriver.manage().timeouts().pageLoadTimeout( + Duration.ofSeconds(40L + 20L * EnvironmentPerformance.cpuSlownessFactor())); + + // Maximize window + this.webDriver.manage().window().maximize(); + + this.installBrowserLogInspector(); + } + + protected RemoteWebDriver tryCreateWebDriver(final AtomicInteger retryCounter) + { + final ClientConfig config = + this.clientConfig.baseUri(this.getContainer().getSeleniumAddressURI()); + final int tryCount = retryCounter.getAndIncrement(); + LOG.info( + "Creating new WebDriver [retryCount={},retrySec={},clientConfig={}] Try #{}", + this.webDriverRetryCount, + this.webDriverRetrySec, + config, + tryCount); + + final HttpClient.Factory factory = HttpCommandExecutor.getDefaultClientFactory(); + @SuppressWarnings("java:S2095") // Handled by Selenium when quit is called + final HttpClient client = factory.createClient(config); + + final HttpCommandExecutor commandExecutor = new HttpCommandExecutor( + Map.of(), + config, + // Constructor without factory does not exist... + ignored -> client); + + try + { + return Timeouts.getWithTimeout( + this.webDriverRetrySec, + TimeUnit.SECONDS, + () -> { + this.capabilities.setCapability("webSocketUrl", this.bidiEnabled ? true : null); + if(this.bidiEnabled && this.deactivateCDPIfPossible) + { + this.modifyCapsDisableCDP(); + } + + final RemoteWebDriver driver = + new RemoteWebDriver(commandExecutor, this.capabilities); + if(!this.bidiEnabled) + { + return driver; + } + + if(this.deactivateCDPIfPossible) + { + this.disableCDP(driver); + } + + // Create BiDi able driver + this.fixCapAddress(driver); + + final Augmenter augmenter = new Augmenter(); + final WebDriver augmentedWebDriver = augmenter.augment(driver); + + return (RemoteWebDriver)augmentedWebDriver; + }); + } + catch(final RuntimeException rex) + { + // Cancel further communication and abort all connections + try + { + LOG.warn("Encounter problem in try #{} - Terminating communication", tryCount); + client.close(); + factory.cleanupIdleClients(); + } + catch(final Exception ex) + { + LOG.warn("Failed to cleanup try #{}", tryCount, ex); + } + + throw rex; + } + } + + protected void modifyCapsDisableCDP() + { + this.capabilities.setCapability("se:cdpEnabled", Boolean.FALSE.toString()); + } + + protected void disableCDP(final RemoteWebDriver originalDriver) + { + if(!(originalDriver.getCapabilities() instanceof final MutableCapabilities mutableCapabilities)) + { + return; + } + mutableCapabilities.setCapability("se:cdp", (Object)null); + } + + protected Set getCapsToPatchAddress() + { + return CAPS_TO_PATCH_ADDRESS; + } + + /** + * Tries to fix the capabilities, e.g. wrong URLs that must be translated so that communication with the container + * works + */ + protected void fixCapAddress(final RemoteWebDriver originalDriver) + { + if(!(originalDriver.getCapabilities() instanceof final MutableCapabilities mutableCapabilities)) + { + return; + } + + for(final String capabilityName : this.getCapsToPatchAddress()) + { + if(mutableCapabilities.getCapability(capabilityName) instanceof final String cdpCap) + { + final Matcher matcher = IP_PORT_EXTRACTOR.matcher(cdpCap); + if(matcher.find()) + { + final String newValue = matcher.group(1) + + this.getContainer().getHost() + + ":" + + this.getContainer().getMappedPort(Integer.parseInt(matcher.group(3))) + + matcher.group(4); + mutableCapabilities.setCapability( + capabilityName, + newValue); + LOG.debug("Patched cap '{}': '{}' -> '{}'", capabilityName, cdpCap, newValue); + } + } + } + } + + protected void installBrowserLogInspector() + { + if(this.browserConsoleLogConsumer == null || !this.bidiEnabled) + { + if(this.browserConsoleLogConsumer != null) + { + LOG.warn("Browser Console Log Consumer is present but BiDi is disabled"); + } + return; + } + + LOG.info("Installing Log Inspector"); + + this.logInspector = new LogInspector(this.webDriver); + final String name = this.getContainer().getContainerNameCleaned(); + this.logInspector.onConsoleEntry(c -> { + if(this.browserConsoleLogLevels == null || this.browserConsoleLogLevels.contains(c.getLevel())) + { + this.browserConsoleLogConsumer.accept( + "[" + name + "] " + + c.getLevel().name().toUpperCase() + " " + + c.getText() + + Optional.ofNullable(c.getStackTrace()) // Note: 2024-11 Stacktrace is not present in FF + .map(StackTrace::getCallFrames) + .map(cf -> "\n" + cf.stream() + .map(s -> "-> " + + s.getFunctionName() + + "@" + s.getUrl() + + " L" + s.getLineNumber() + + "C" + s.getColumnNumber()).collect( + Collectors.joining("\n"))) + .orElse("")); + } + }); + } + + public Optional getVncAddress() + { + return Optional.ofNullable(this.getContainer().getVncAddress()); + } + + public Optional getNoVncAddress() + { + return Optional.ofNullable(this.getContainer().getNoVncAddress()); + } + + public RemoteWebDriver getWebDriver() + { + return this.webDriver; + } + + public void afterTest(final TestDescription description, final Optional throwable) + { + if(this.getContainer() != null) + { + this.getContainer().afterTest(description, throwable); + } + } + + @Override + public void stop() + { + if(this.logInspector != null) + { + final long startMs = System.currentTimeMillis(); + try + { + this.logInspector.close(); + } + catch(final Exception e) + { + LOG.warn("Failed to stop logInspector", e); + } + finally + { + LOG.debug("Stopping logInspector driver took {}ms", System.currentTimeMillis() - startMs); + } + this.logInspector = null; + } + if(this.webDriver != null) + { + final long startMs = System.currentTimeMillis(); + try + { + this.webDriver.quit(); + } + catch(final Exception e) + { + LOG.warn("Failed to quit the driver", e); + } + finally + { + LOG.debug("Quiting driver took {}ms", System.currentTimeMillis() - startMs); + } + this.webDriver = null; + } + super.stop(); + } +} diff --git a/selenium/src/main/java/software/xdev/tci/selenium/TestBrowser.java b/selenium/src/main/java/software/xdev/tci/selenium/TestBrowser.java new file mode 100644 index 00000000..39e0884f --- /dev/null +++ b/selenium/src/main/java/software/xdev/tci/selenium/TestBrowser.java @@ -0,0 +1,51 @@ +/* + * Copyright © 2025 XDEV Software (https://xdev.software) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package software.xdev.tci.selenium; + +import java.util.function.Supplier; + +import org.openqa.selenium.MutableCapabilities; +import org.openqa.selenium.chrome.ChromeOptions; +import org.openqa.selenium.firefox.FirefoxOptions; +import org.openqa.selenium.firefox.FirefoxProfile; + + +public enum TestBrowser +{ + FIREFOX(() -> { + final FirefoxOptions firefoxOptions = new FirefoxOptions(); + + final FirefoxProfile firefoxProfile = new FirefoxProfile(); + // Allows to type into console without an annoying SELF XSS popup + firefoxProfile.setPreference("devtools.selfxss.count", "100"); + firefoxOptions.setProfile(firefoxProfile); + + return firefoxOptions; + }), + CHROME(ChromeOptions::new); + + private final Supplier capabilityFactory; + + TestBrowser(final Supplier driverFactory) + { + this.capabilityFactory = driverFactory; + } + + public Supplier getCapabilityFactory() + { + return this.capabilityFactory; + } +} diff --git a/tci-advanced-demo/tci-selenium/src/main/java/software/xdev/tci/demo/tci/selenium/containers/SeleniumBrowserWebDriverContainer.java b/selenium/src/main/java/software/xdev/tci/selenium/containers/SeleniumBrowserWebDriverContainer.java similarity index 55% rename from tci-advanced-demo/tci-selenium/src/main/java/software/xdev/tci/demo/tci/selenium/containers/SeleniumBrowserWebDriverContainer.java rename to selenium/src/main/java/software/xdev/tci/selenium/containers/SeleniumBrowserWebDriverContainer.java index fec6e8f4..62895dc4 100644 --- a/tci-advanced-demo/tci-selenium/src/main/java/software/xdev/tci/demo/tci/selenium/containers/SeleniumBrowserWebDriverContainer.java +++ b/selenium/src/main/java/software/xdev/tci/selenium/containers/SeleniumBrowserWebDriverContainer.java @@ -1,4 +1,19 @@ -package software.xdev.tci.demo.tci.selenium.containers; +/* + * Copyright © 2025 XDEV Software (https://xdev.software) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package software.xdev.tci.selenium.containers; import java.util.HashSet; import java.util.Map; @@ -20,13 +35,31 @@ public class SeleniumBrowserWebDriverContainer public SeleniumBrowserWebDriverContainer(final Capabilities capabilities) { super( - capabilities, - Map.of( + capabilities, Map.of( + // Limit to the core (and most open source) browsers by distinct engines + // 1. Firefox uses Gecko BrowserType.FIREFOX, FIREFOX_IMAGE, - // Chrome has no ARM64 image (Why Google?) -> Use chromium instead + // 2. Everything else is running Chromium/Blink + // Chrome has no ARM64 image (embarrassing) -> Use chromium instead // https://github.com/SeleniumHQ/docker-selenium/discussions/2379 - BrowserType.CHROME, DockerImageName.parse("selenium/standalone-chromium")) - ); + BrowserType.CHROME, CHROMIUM_IMAGE + // 3. Safari/Webkit is N/A because Apple is doing Apple stuff + // https://github.com/SeleniumHQ/docker-selenium/issues/1635 + // 4. IE/Trident/EdgeHTML is dead + // 5. Everything else is irrelevant + )); + } + + public SeleniumBrowserWebDriverContainer( + final Capabilities capabilities, + final Map browserDockerImages) + { + super(capabilities, browserDockerImages); + } + + public SeleniumBrowserWebDriverContainer(final DockerImageName dockerImageName) + { + super(dockerImageName); } @Override diff --git a/selenium/src/main/java/software/xdev/tci/selenium/factory/BrowserTCIFactory.java b/selenium/src/main/java/software/xdev/tci/selenium/factory/BrowserTCIFactory.java new file mode 100644 index 00000000..f35eb6fd --- /dev/null +++ b/selenium/src/main/java/software/xdev/tci/selenium/factory/BrowserTCIFactory.java @@ -0,0 +1,199 @@ +/* + * Copyright © 2025 XDEV Software (https://xdev.software) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package software.xdev.tci.selenium.factory; + +import static software.xdev.tci.envperf.EnvironmentPerformance.cpuSlownessFactor; + +import java.time.Duration; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import org.openqa.selenium.MutableCapabilities; +import org.openqa.selenium.bidi.log.LogLevel; +import org.openqa.selenium.remote.http.ClientConfig; +import org.rnorth.ducttape.unreliables.Unreliables; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.wait.strategy.HostPortWaitStrategy; +import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy; +import org.testcontainers.containers.wait.strategy.WaitAllStrategy; + +import software.xdev.tci.factory.prestart.PreStartableTCIFactory; +import software.xdev.tci.factory.prestart.config.PreStartConfig; +import software.xdev.tci.misc.ContainerMemory; +import software.xdev.tci.selenium.BrowserTCI; +import software.xdev.tci.selenium.containers.SeleniumBrowserWebDriverContainer; +import software.xdev.tci.selenium.factory.config.BrowserTCIFactoryConfig; +import software.xdev.tci.serviceloading.TCIServiceLoader; +import software.xdev.testcontainers.selenium.containers.recorder.SeleniumRecordingContainer; + + +public class BrowserTCIFactory extends PreStartableTCIFactory +{ + protected final String browserName; + + public BrowserTCIFactory(final MutableCapabilities capabilities) + { + this(capabilities, TCIServiceLoader.instance().service(BrowserTCIFactoryConfig.class)); + } + + @SuppressWarnings({"resource", "checkstyle:MagicNumber"}) + public BrowserTCIFactory(final MutableCapabilities capabilities, final BrowserTCIFactoryConfig config) + { + super( + (c, na) -> new BrowserTCI(c, na, capabilities) + .withBidiEnabled(config.bidiEnabled()) + .withDeactivateCDPIfPossible(config.deactivateCdpIfPossible()) + .withClientConfig(ClientConfig.defaultConfig() + .readTimeout(Duration.ofSeconds(60 + cpuSlownessFactor() * 10L))) + .withWebDriverRetryCount(Math.max(Math.min(cpuSlownessFactor(), 5), 1)) + .withWebDriverRetrySec(25 + cpuSlownessFactor() * 5) + .withBrowserConsoleLog( + logBrowserConsoleConsumer(config.browserConsoleLogLevel()), + config.browserConsoleLogLevel().logLevels()), + () -> new SeleniumBrowserWebDriverContainer(capabilities) + .withStartRecordingContainerManually(true) + .withRecordingDirectory(config.dirForRecords()) + .withRecordingMode(config.recordingMode()) + // 2024-04 VNC is no longer required when recording + .withDisableVNC(!config.vncEnabled()) + .withEnableNoVNC(config.vncEnabled()) + .withRecordingContainerSupplier(t -> new SeleniumRecordingContainer(t) + .withFrameRate(10) + .withLogConsumer(getLogConsumer("container.browserrecorder." + capabilities.getBrowserName())) + .withCreateContainerCmdModifier(cmd -> cmd.getHostConfig().withMemory(ContainerMemory.M512M))) + // Without that a mount volume dialog shows up + // https://github.com/testcontainers/testcontainers-java/issues/1670 + .withSharedMemorySize(ContainerMemory.M2G) + .withCreateContainerCmdModifier(cmd -> cmd.getHostConfig().withMemory(ContainerMemory.M1G)) + .withEnv("SE_SCREEN_WIDTH", "1600") + .withEnv("SE_SCREEN_HEIGHT", "900") + // By default after 5 mins the session is killed and you can't use the container anymore. Cool or? + // https://github.com/SeleniumHQ/docker-selenium?tab=readme-ov-file#grid-url-and-session-timeout + .withEnv("SE_NODE_SESSION_TIMEOUT", "3600") + // Disable local tracing, as we don't need it + // https://github.com/SeleniumHQ/docker-selenium/issues/2355 + .withEnv("SE_ENABLE_TRACING", "false") + // Some (AWS) CPUs are completely overloaded with the default 15s timeout -> increase it + .waitingFor(new WaitAllStrategy() + .withStrategy(new LogMessageWaitStrategy() + .withRegEx(".*(Started Selenium Standalone).*\n") + .withStartupTimeout(Duration.ofSeconds(30 + 20L * cpuSlownessFactor()))) + .withStrategy(new HostPortWaitStrategy()) + .withStartupTimeout(Duration.ofSeconds(30 + 20L * cpuSlownessFactor()))), + "selenium-" + capabilities.getBrowserName().toLowerCase(), + "container.browserwebdriver." + capabilities.getBrowserName(), + "Browser-" + capabilities.getBrowserName()); + this.browserName = capabilities.getBrowserName(); + } + + public BrowserTCIFactory( + final BiFunction infraBuilder, + final Supplier containerBuilder, + final String containerBaseName, + final String containerLoggerName, + final String name, + final String browserName) + { + super(infraBuilder, containerBuilder, containerBaseName, containerLoggerName, name); + this.browserName = browserName; + } + + @SuppressWarnings("java:S107") + public BrowserTCIFactory( + final BiFunction infraBuilder, + final Supplier containerBuilder, + final String containerBaseName, + final String containerLoggerName, + final String name, + final PreStartConfig config, + final Timeouts timeouts, + final String browserName) + { + super(infraBuilder, containerBuilder, containerBaseName, containerLoggerName, name, config, timeouts); + this.browserName = browserName; + } + + protected static Consumer logBrowserConsoleConsumer(final BrowserConsoleLogLevel level) + { + if(!level.active()) + { + return null; + } + final Logger logger = LoggerFactory.getLogger("container.browser.console"); + if(!logger.isInfoEnabled()) + { + return null; + } + return logger::info; + } + + @Override + protected void postProcessNew(final BrowserTCI infra) + { + // Start recording container here otherwise there is a lot of blank video + final CompletableFuture cfStartRecorder = + CompletableFuture.runAsync(() -> infra.getContainer().startRecordingContainer()); + + // Docker needs a few milliseconds (usually less than 100) to reconfigure its networks + // In the meantime existing connections might fail if we go on immediately + // So let's wait a moment here until everything is fine + Unreliables.retryUntilSuccess( + 10, + TimeUnit.SECONDS, + () -> infra.getWebDriver().getCurrentUrl()); + + cfStartRecorder.join(); + } + + @Override + public String getFactoryName() + { + return super.getFactoryName() + "-" + this.browserName; + } + + public enum BrowserConsoleLogLevel + { + OFF(Set.of()), + ERROR(Set.of(LogLevel.ERROR)), + WARN(Set.of(LogLevel.WARNING, LogLevel.ERROR)), + INFO(Set.of(LogLevel.INFO, LogLevel.WARNING, LogLevel.ERROR)), + DEBUG(Set.of(LogLevel.DEBUG, LogLevel.INFO, LogLevel.WARNING, LogLevel.ERROR)), + ALL(null); + + // Selenium's LogLevel can't be compared (order is random) -> use Set + private final Set logLevels; + + BrowserConsoleLogLevel(final Set logLevels) + { + this.logLevels = logLevels; + } + + public Set logLevels() + { + return this.logLevels; + } + + public boolean active() + { + return this.logLevels() == null || !this.logLevels().isEmpty(); + } + } +} diff --git a/tci-advanced-demo/tci-selenium/src/main/java/software/xdev/tci/demo/tci/selenium/factory/BrowsersTCIFactory.java b/selenium/src/main/java/software/xdev/tci/selenium/factory/BrowsersTCIFactory.java similarity index 56% rename from tci-advanced-demo/tci-selenium/src/main/java/software/xdev/tci/demo/tci/selenium/factory/BrowsersTCIFactory.java rename to selenium/src/main/java/software/xdev/tci/selenium/factory/BrowsersTCIFactory.java index 9f155921..c50e98cb 100644 --- a/tci-advanced-demo/tci-selenium/src/main/java/software/xdev/tci/demo/tci/selenium/factory/BrowsersTCIFactory.java +++ b/selenium/src/main/java/software/xdev/tci/selenium/factory/BrowsersTCIFactory.java @@ -1,4 +1,19 @@ -package software.xdev.tci.demo.tci.selenium.factory; +/* + * Copyright © 2025 XDEV Software (https://xdev.software) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package software.xdev.tci.selenium.factory; import java.util.Arrays; import java.util.Collection; @@ -10,31 +25,42 @@ import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; import java.util.stream.Collectors; +import java.util.stream.Stream; -import org.openqa.selenium.Capabilities; +import org.openqa.selenium.MutableCapabilities; import org.slf4j.LoggerFactory; import org.testcontainers.containers.Network; import org.testcontainers.images.RemoteDockerImage; -import software.xdev.tci.demo.tci.selenium.BrowserTCI; -import software.xdev.tci.demo.tci.selenium.TestBrowser; -import software.xdev.tci.demo.tci.selenium.containers.SeleniumBrowserWebDriverContainer; import software.xdev.tci.factory.TCIFactory; +import software.xdev.tci.logging.JULtoSLF4JRedirector; +import software.xdev.tci.selenium.BrowserTCI; +import software.xdev.tci.selenium.TestBrowser; +import software.xdev.tci.selenium.containers.SeleniumBrowserWebDriverContainer; import software.xdev.tci.tracing.TCITracer; import software.xdev.testcontainers.selenium.containers.recorder.SeleniumRecordingContainer; public class BrowsersTCIFactory implements TCIFactory { - private final Map browserFactories = Collections.synchronizedMap(new HashMap<>()); - private boolean alreadyWarmedUp; + protected final Map browserFactories = Collections.synchronizedMap(new HashMap<>()); + protected boolean alreadyWarmedUp; public BrowsersTCIFactory() { - Arrays.stream(TestBrowser.values()) + this(Arrays.stream(TestBrowser.values()) .map(TestBrowser::getCapabilityFactory) - .map(Supplier::get) - .forEach(cap -> this.browserFactories.put(cap.getBrowserName(), new BrowserTCIFactory(cap))); + .map(Supplier::get)); + } + + public BrowsersTCIFactory(final Stream caps) + { + caps.forEach(cap -> this.browserFactories.put(cap.getBrowserName(), new BrowserTCIFactory(cap))); + } + + public BrowsersTCIFactory(final Map browserFactories) + { + this.browserFactories.putAll(browserFactories); } @Override @@ -55,6 +81,9 @@ protected synchronized void warmUpInternal() return; } + // Selenium uses JUL + JULtoSLF4JRedirector.redirect(); + this.browserFactories.values().forEach(BrowserTCIFactory::warmUp); // Pull video recorder @@ -73,7 +102,10 @@ protected synchronized void warmUpInternal() } @SuppressWarnings("resource") - public BrowserTCI getNew(final Capabilities capabilities, final Network network, final String... networkAliases) + public BrowserTCI getNew( + final MutableCapabilities capabilities, + final Network network, + final String... networkAliases) { return this.browserFactories.computeIfAbsent( capabilities.getBrowserName(), diff --git a/selenium/src/main/java/software/xdev/tci/selenium/factory/config/BrowserTCIFactoryConfig.java b/selenium/src/main/java/software/xdev/tci/selenium/factory/config/BrowserTCIFactoryConfig.java new file mode 100644 index 00000000..f83f64f0 --- /dev/null +++ b/selenium/src/main/java/software/xdev/tci/selenium/factory/config/BrowserTCIFactoryConfig.java @@ -0,0 +1,37 @@ +/* + * Copyright © 2025 XDEV Software (https://xdev.software) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package software.xdev.tci.selenium.factory.config; + +import java.nio.file.Path; + +import software.xdev.tci.selenium.factory.BrowserTCIFactory; +import software.xdev.testcontainers.selenium.containers.browser.BrowserWebDriverContainer; + + +public interface BrowserTCIFactoryConfig +{ + BrowserWebDriverContainer.RecordingMode recordingMode(); + + Path dirForRecords(); + + boolean vncEnabled(); + + boolean bidiEnabled(); + + boolean deactivateCdpIfPossible(); + + BrowserTCIFactory.BrowserConsoleLogLevel browserConsoleLogLevel(); +} diff --git a/selenium/src/main/java/software/xdev/tci/selenium/factory/config/DefaultBrowserTCIFactoryConfig.java b/selenium/src/main/java/software/xdev/tci/selenium/factory/config/DefaultBrowserTCIFactoryConfig.java new file mode 100644 index 00000000..3ffb63d2 --- /dev/null +++ b/selenium/src/main/java/software/xdev/tci/selenium/factory/config/DefaultBrowserTCIFactoryConfig.java @@ -0,0 +1,141 @@ +/* + * Copyright © 2025 XDEV Software (https://xdev.software) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package software.xdev.tci.selenium.factory.config; + +import java.nio.file.Path; +import java.util.Locale; +import java.util.Optional; +import java.util.stream.Stream; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import software.xdev.tci.selenium.factory.BrowserTCIFactory; +import software.xdev.testcontainers.selenium.containers.browser.BrowserWebDriverContainer; + + +public class DefaultBrowserTCIFactoryConfig implements BrowserTCIFactoryConfig +{ + private static final Logger LOG = LoggerFactory.getLogger(DefaultBrowserTCIFactoryConfig.class); + + public static final String RECORD_MODE = "recordMode"; + public static final String RECORD_DIR = "recordDir"; + public static final String VNC_ENABLED = "vncEnabled"; + public static final String BIDI_ENABLED = "bidiEnabled"; + public static final String DEACTIVATE_CDP_IF_POSSIBLE = "deactivateCdpIfPossible"; + public static final String BROWSER_CONSOLE_LOG_LEVEL = "browserConsoleLogLevel"; + + public static final String DEFAULT_RECORD_DIR = "target/records"; + + protected BrowserWebDriverContainer.RecordingMode systemRecordingMode; + protected Path dirForRecords; + protected Boolean vncEnabled; + protected Boolean bidiEnabled; + protected Boolean deactivateCdpIfPossible; + protected BrowserTCIFactory.BrowserConsoleLogLevel browserConsoleLogLevel; + + protected Optional resolve(final String propertyName) + { + final String fullPropertyName = "tci.selenium." + propertyName; + return Optional.ofNullable(System.getenv(fullPropertyName + .replace(".", "_") + .toUpperCase(Locale.ENGLISH))) + .or(() -> Optional.ofNullable(System.getProperty(fullPropertyName))); + } + + protected boolean resolveBool(final String propertyName, final boolean defaultVal) + { + return this.resolve(propertyName) + .map(s -> "1".equals(s) || Boolean.parseBoolean(s)) + .orElse(defaultVal); + } + + @Override + public BrowserWebDriverContainer.RecordingMode recordingMode() + { + if(this.systemRecordingMode == null) + { + final String resolvedRecordMode = this.resolve(RECORD_MODE).orElse(null); + this.systemRecordingMode = Stream.of(BrowserWebDriverContainer.RecordingMode.values()) + .filter(rm -> rm.toString().equals(resolvedRecordMode)) + .findFirst() + .orElse(BrowserWebDriverContainer.RecordingMode.RECORD_FAILING); + LOG.info("Default Recording Mode='{}'", this.systemRecordingMode); + } + return this.systemRecordingMode; + } + + @Override + public Path dirForRecords() + { + if(this.dirForRecords == null) + { + this.dirForRecords = Path.of(this.resolve(RECORD_DIR).orElse(DEFAULT_RECORD_DIR)); + final boolean wasCreated = this.dirForRecords.toFile().mkdirs(); + LOG.info( + "Default Directory for records='{}', created={}", this.dirForRecords.toAbsolutePath(), + wasCreated); + } + + return this.dirForRecords; + } + + @Override + public boolean vncEnabled() + { + if(this.vncEnabled == null) + { + this.vncEnabled = this.resolveBool(VNC_ENABLED, false); + LOG.info("VNC enabled={}", this.vncEnabled); + } + return this.vncEnabled; + } + + @Override + public boolean bidiEnabled() + { + if(this.bidiEnabled == null) + { + this.bidiEnabled = this.resolveBool(BIDI_ENABLED, true); + LOG.info("BiDi enabled={}", this.bidiEnabled); + } + return this.bidiEnabled; + } + + @Override + public boolean deactivateCdpIfPossible() + { + if(this.deactivateCdpIfPossible == null) + { + this.deactivateCdpIfPossible = this.resolveBool(DEACTIVATE_CDP_IF_POSSIBLE, true); + LOG.info("DeactivateCDPIfPossible={}", this.deactivateCdpIfPossible); + } + return this.deactivateCdpIfPossible; + } + + @Override + public BrowserTCIFactory.BrowserConsoleLogLevel browserConsoleLogLevel() + { + if(this.browserConsoleLogLevel == null) + { + this.browserConsoleLogLevel = this.resolve(BROWSER_CONSOLE_LOG_LEVEL) + .map(BrowserTCIFactory.BrowserConsoleLogLevel::valueOf) + .orElse(BrowserTCIFactory.BrowserConsoleLogLevel.ERROR); + LOG.info("BrowserConsoleLogLevel={}", this.browserConsoleLogLevel); + } + return this.browserConsoleLogLevel; + } +} diff --git a/selenium/src/main/java/software/xdev/tci/selenium/testbase/SeleniumRecordingExtension.java b/selenium/src/main/java/software/xdev/tci/selenium/testbase/SeleniumRecordingExtension.java new file mode 100644 index 00000000..21d2b831 --- /dev/null +++ b/selenium/src/main/java/software/xdev/tci/selenium/testbase/SeleniumRecordingExtension.java @@ -0,0 +1,84 @@ +/* + * Copyright © 2025 XDEV Software (https://xdev.software) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package software.xdev.tci.selenium.testbase; + +import java.util.Objects; +import java.util.function.Function; + +import org.junit.jupiter.api.extension.AfterTestExecutionCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.lifecycle.TestDescription; + +import software.xdev.tci.selenium.BrowserTCI; + + +public abstract class SeleniumRecordingExtension implements AfterTestExecutionCallback +{ + private static final Logger LOG = LoggerFactory.getLogger(SeleniumRecordingExtension.class); + + protected final Function tciExtractor; + + protected SeleniumRecordingExtension( + final Function tciExtractor) + { + this.tciExtractor = Objects.requireNonNull(tciExtractor); + } + + @Override + public void afterTestExecution(final ExtensionContext context) throws Exception + { + final BrowserTCI browserTCI = this.tciExtractor.apply(context); + if(browserTCI != null) + { + LOG.debug("Trying to capture video"); + + browserTCI.afterTest( + new TestDescription() + { + @Override + public String getTestId() + { + return this.getFilesystemFriendlyName(); + } + + @SuppressWarnings("checkstyle:MagicNumber") + @Override + public String getFilesystemFriendlyName() + { + final String testClassName = + this.cleanForFilename(context.getRequiredTestClass().getSimpleName()); + final String displayName = this.cleanForFilename(context.getDisplayName()); + return System.currentTimeMillis() + + "_" + + testClassName + + "_" + // Cut off otherwise file name is too long + + displayName.substring(0, Math.min(displayName.length(), 200)); + } + + private String cleanForFilename(final String str) + { + return str.replace(' ', '_') + .replaceAll("[^A-Za-z0-9#_-]", "") + .toLowerCase(); + } + }, context.getExecutionException()); + } + LOG.debug("AfterTestExecution done"); + } +} diff --git a/selenium/src/main/resources/META-INF/services/software.xdev.tci.selenium.factory.config.BrowserTCIFactoryConfig b/selenium/src/main/resources/META-INF/services/software.xdev.tci.selenium.factory.config.BrowserTCIFactoryConfig new file mode 100644 index 00000000..39a85b4e --- /dev/null +++ b/selenium/src/main/resources/META-INF/services/software.xdev.tci.selenium.factory.config.BrowserTCIFactoryConfig @@ -0,0 +1 @@ +software.xdev.tci.selenium.factory.config.DefaultBrowserTCIFactoryConfig diff --git a/spring-dao-support/README.md b/spring-dao-support/README.md new file mode 100644 index 00000000..bd110785 --- /dev/null +++ b/spring-dao-support/README.md @@ -0,0 +1,3 @@ +# Spring DAO Support + +Provides support for injecting DAO using Spring. diff --git a/spring-dao-support/pom.xml b/spring-dao-support/pom.xml new file mode 100644 index 00000000..1b6c70c2 --- /dev/null +++ b/spring-dao-support/pom.xml @@ -0,0 +1,319 @@ + + + 4.0.0 + + software.xdev.tci + spring-dao-support + 2.0.0-SNAPSHOT + jar + + spring-dao-support + TCI - spring-dao-support + https://github.com/xdev-software/tci + + + https://github.com/xdev-software/tci + scm:git:https://github.com/xdev-software/tci.git + + + 2025 + + + XDEV Software + https://xdev.software + + + + + XDEV Software + XDEV Software + https://xdev.software + + + + + + Apache-2.0 + https://www.apache.org/licenses/LICENSE-2.0.txt + repo + + + + + 17 + ${javaVersion} + + UTF-8 + UTF-8 + + + + + software.xdev.tci + db-jdbc-orm + 2.0.0-SNAPSHOT + + + + org.javassist + javassist + 3.30.2-GA + + + + + org.junit.jupiter + junit-jupiter + 5.13.2 + test + + + org.slf4j + slf4j-simple + 2.0.17 + test + + + + + + + + org.apache.maven.plugins + maven-site-plugin + 4.0.0-M16 + + + org.apache.maven.plugins + maven-project-info-reports-plugin + 3.9.0 + + + + + + com.mycila + license-maven-plugin + 5.0.0 + + + ${project.organization.url} + + + +
com/mycila/maven/plugin/license/templates/APACHE-2.txt
+ + src/main/java/** + src/test/java/** + +
+
+
+ + + first + + format + + process-sources + + +
+ + + org.apache.maven.plugins + maven-compiler-plugin + 3.14.0 + + ${maven.compiler.release} + + -proc:none + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.11.2 + + + attach-javadocs + package + + jar + + + + + true + none + + + + org.apache.maven.plugins + maven-source-plugin + 3.3.1 + + + attach-sources + package + + jar-no-fork + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.5.3 + +
+
+ + + ignore-service-loading + + + + src/main/resources + + META-INF/services/** + + + + + + + publish-sonatype-central-portal + + + + org.codehaus.mojo + flatten-maven-plugin + 1.7.1 + + ossrh + + + + flatten + process-resources + + flatten + + + + + + org.apache.maven.plugins + maven-gpg-plugin + 3.2.7 + + + sign-artifacts + verify + + sign + + + + + + --pinentry-mode + loopback + + + + + + + + org.sonatype.central + central-publishing-maven-plugin + 0.8.0 + true + + sonatype-central-portal + true + + + + + + + checkstyle + + + + org.apache.maven.plugins + maven-checkstyle-plugin + 3.6.0 + + + com.puppycrawl.tools + checkstyle + 10.26.1 + + + + ../.config/checkstyle/checkstyle.xml + true + + + + + check + + + + + + + + + pmd + + + + org.apache.maven.plugins + maven-pmd-plugin + 3.27.0 + + true + true + + ../.config/pmd/java/ruleset.xml + + + + + net.sourceforge.pmd + pmd-core + 7.15.0 + + + net.sourceforge.pmd + pmd-java + 7.15.0 + + + + + + + + + + org.apache.maven.plugins + maven-jxr-plugin + 3.6.0 + + + + + +
diff --git a/tci-advanced-demo/persistence-it/src/test/java/software/xdev/tci/demo/persistence/base/DAOInjector.java b/spring-dao-support/src/main/java/software/xdev/tci/dao/DAOInjector.java similarity index 61% rename from tci-advanced-demo/persistence-it/src/test/java/software/xdev/tci/demo/persistence/base/DAOInjector.java rename to spring-dao-support/src/main/java/software/xdev/tci/dao/DAOInjector.java index d725a290..05e527ea 100644 --- a/tci-advanced-demo/persistence-it/src/test/java/software/xdev/tci/demo/persistence/base/DAOInjector.java +++ b/spring-dao-support/src/main/java/software/xdev/tci/dao/DAOInjector.java @@ -1,4 +1,19 @@ -package software.xdev.tci.demo.persistence.base; +/* + * Copyright © 2025 XDEV Software (https://xdev.software) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package software.xdev.tci.dao; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; @@ -6,6 +21,7 @@ import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.HashSet; +import java.util.Objects; import java.util.Set; import java.util.function.Supplier; @@ -18,9 +34,7 @@ import javassist.util.proxy.MethodHandler; import javassist.util.proxy.ProxyFactory; -import software.xdev.tci.demo.persistence.jpa.dao.BaseDAO; -import software.xdev.tci.demo.persistence.jpa.dao.TransactionReflector; -import software.xdev.tci.demo.tci.db.persistence.TransactionExecutor; +import software.xdev.tci.db.persistence.TransactionExecutor; /** @@ -33,6 +47,20 @@ public class DAOInjector { private static final Logger LOG = LoggerFactory.getLogger(DAOInjector.class); + protected final Class baseDAOClazz; + protected final ThrowingFieldSupplier fEntityManagerInBaseDAOSupplier; + protected final HandleSpecialFields handleSpecialFields; + + public DAOInjector( + final Class baseDAOClazz, + final ThrowingFieldSupplier fEntityManagerInBaseDAOSupplier, + final HandleSpecialFields handleSpecialFields) + { + this.baseDAOClazz = Objects.requireNonNull(baseDAOClazz); + this.fEntityManagerInBaseDAOSupplier = Objects.requireNonNull(fEntityManagerInBaseDAOSupplier); + this.handleSpecialFields = handleSpecialFields; + } + public void doInjections( final Class clazz, final Supplier emSupplier, @@ -40,7 +68,7 @@ public void doInjections( { this.collectAllDeclaredFields(clazz).stream() .filter(field -> field.isAnnotationPresent(Autowired.class)) - .filter(field -> BaseDAO.class.isAssignableFrom(field.getType())) + .filter(field -> this.baseDAOClazz.isAssignableFrom(field.getType())) .forEach(field -> { final Class fieldType = field.getType(); final EntityManager em = emSupplier.get(); @@ -52,30 +80,17 @@ public void doInjections( ? this.createProxiedDAO(fieldType, em, originalDAO) : originalDAO; - this.collectAllDeclaredFields(fieldType) - .stream() - .filter(f -> TransactionReflector.class.equals(f.getType())) - .forEach(f -> this.setIntoField(dao, f, new TransactionReflector() - { - @Override - public void runWithTransaction(final Runnable runnable) - { - new TransactionExecutor(em).execWithTransaction(runnable); - } - - @Override - public T runWithTransaction(final Supplier supplier) - { - return new TransactionExecutor(em).execWithTransaction(supplier); - } - })); + if(this.handleSpecialFields != null) + { + this.handleSpecialFields.handle(this, this.collectAllDeclaredFields(fieldType), dao, em); + } this.setIntoField(instanceSupplier.get(), field, dao); }); } @SuppressWarnings("PMD.PreserveStackTrace") - private Object createProxiedDAO(final Class fieldType, final EntityManager em, final Object original) + protected Object createProxiedDAO(final Class fieldType, final EntityManager em, final Object original) { // java.lang.reflect.Proxy only proxies interfaces and doesn't work here! // https://stackoverflow.com/a/3292208 @@ -92,7 +107,7 @@ private Object createProxiedDAO(final Class fieldType, final EntityManager em } catch(final IllegalAccessException | InvocationTargetException e) { - throw new RuntimeException(e); + throw new IllegalStateException("Failed to invoke", e); } }; if(em.getTransaction().isActive()) @@ -114,19 +129,19 @@ private Object createProxiedDAO(final Class fieldType, final EntityManager em { return this.setIntoField( factory.create(new Class[0], new Object[0], methodHandler), - BaseDAO.class.getDeclaredField("em"), + this.fEntityManagerInBaseDAOSupplier.field(), em); } - catch(final NoSuchMethodException | InstantiationException | IllegalAccessException - | InvocationTargetException | NoSuchFieldException e2) + catch(final NoSuchFieldException | NoSuchMethodException | InstantiationException | IllegalAccessException + | InvocationTargetException e2) { - throw new RuntimeException("Failed to proxy dao", e2); + throw new IllegalStateException("Failed to proxy dao", e2); } } } @SuppressWarnings("PMD.PreserveStackTrace") - private Object createDAO(final Class fieldType, final EntityManager em) + protected Object createDAO(final Class fieldType, final EntityManager em) { try { @@ -142,7 +157,7 @@ private Object createDAO(final Class fieldType, final EntityManager em) { return this.setIntoField( fieldType.getConstructor().newInstance(), - BaseDAO.class.getDeclaredField("em"), + this.fEntityManagerInBaseDAOSupplier.field(), em); } catch(final NoSuchFieldException | NoSuchMethodException | InstantiationException @@ -153,7 +168,7 @@ private Object createDAO(final Class fieldType, final EntityManager em) } } - private Set collectAllDeclaredFields(final Class clazz) + protected Set collectAllDeclaredFields(final Class clazz) { final Set fields = new HashSet<>(); Class currentClazz = clazz; @@ -165,7 +180,7 @@ private Set collectAllDeclaredFields(final Class clazz) return fields; } - private Set collectAllDeclaredMethods(final Class clazz) + protected Set collectAllDeclaredMethods(final Class clazz) { final Set methods = new HashSet<>(); Class currentClazz = clazz; @@ -177,7 +192,8 @@ private Set collectAllDeclaredMethods(final Class clazz) return methods; } - private Object setIntoField(final Object instance, final Field field, final Object newValue) + @SuppressWarnings("java:S3011") + public Object setIntoField(final Object instance, final Field field, final Object newValue) { field.setAccessible(true); try @@ -186,8 +202,21 @@ private Object setIntoField(final Object instance, final Field field, final Obje } catch(final IllegalAccessException iae) { - throw new RuntimeException(iae); + throw new IllegalStateException("Failed to set into field", iae); } return instance; } + + @FunctionalInterface + public interface ThrowingFieldSupplier + { + Field field() throws NoSuchFieldException; + } + + + @FunctionalInterface + public interface HandleSpecialFields + { + void handle(DAOInjector self, Set fields, Object dao, EntityManager em); + } } diff --git a/tci-advanced-demo/tci-db/src/main/java/software/xdev/tci/demo/tci/db/DBTCI.java b/tci-advanced-demo/tci-db/src/main/java/software/xdev/tci/demo/tci/db/DBTCI.java deleted file mode 100644 index 65e82977..00000000 --- a/tci-advanced-demo/tci-db/src/main/java/software/xdev/tci/demo/tci/db/DBTCI.java +++ /dev/null @@ -1,267 +0,0 @@ -package software.xdev.tci.demo.tci.db; - -import java.sql.Connection; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.Collection; -import java.util.Map; -import java.util.Optional; -import java.util.function.Consumer; -import java.util.stream.Collectors; -import java.util.stream.IntStream; - -import jakarta.persistence.EntityManager; -import jakarta.persistence.EntityManagerFactory; - -import org.hibernate.cfg.PersistenceSettings; -import org.hibernate.hikaricp.internal.HikariCPConnectionProvider; -import org.mariadb.jdbc.Driver; -import org.mariadb.jdbc.MariaDbDataSource; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.testcontainers.containers.GenericContainer; - -import software.xdev.tci.TCI; -import software.xdev.tci.demo.persistence.FlywayInfo; -import software.xdev.tci.demo.persistence.FlywayMigration; -import software.xdev.tci.demo.tci.db.containers.DBContainer; -import software.xdev.tci.demo.tci.db.persistence.EntityManagerController; -import software.xdev.tci.demo.tci.db.persistence.hibernate.CachingStandardScanner; - - -public class DBTCI extends TCI -{ - private static final Logger LOG = LoggerFactory.getLogger(DBTCI.class); - - public static final String DB_DATABASE = "test"; - public static final String DB_USERNAME = "test"; - @SuppressWarnings("java:S2068") // This is a test calm down - public static final String DB_PASSWORD = "test"; - - private final boolean migrateAndInitializeEMC; - - protected EntityManagerController emc; - - public DBTCI( - final DBContainer container, - final String networkAlias, - final boolean migrateAndInitializeEMC) - { - super(container, networkAlias); - this.migrateAndInitializeEMC = migrateAndInitializeEMC; - } - - public boolean isMigrateAndInitializeEMC() - { - return this.migrateAndInitializeEMC; - } - - @Override - public void start(final String containerName) - { - super.start(containerName); - if(this.migrateAndInitializeEMC) - { - // Do basic migrations async - LOG.debug("Running migration to basic structure"); - this.migrateDatabase(FlywayInfo.FLYWAY_LOOKUP_STRUCTURE); - LOG.info("Migration executed"); - - // Create EMC in background to improve performance (~5s) - LOG.debug("Initializing EntityManagerController..."); - this.getEMC(); - LOG.info("Initialized EntityManagerController"); - } - } - - @Override - public void stop() - { - if(this.emc != null) - { - try - { - this.emc.close(); - } - catch(final Exception ex) - { - LOG.warn("Failed to close EntityManagerController", ex); - } - this.emc = null; - } - super.stop(); - } - - public EntityManagerController getEMC() - { - if(this.emc == null) - { - this.initEMCIfRequired(); - } - - return this.emc; - } - - protected synchronized void initEMCIfRequired() - { - if(this.emc == null) - { - this.emc = EntityManagerController.createForStandalone( - Driver.class.getName(), - // Use production-ready pool otherwise Hibernate warnings occur - HikariCPConnectionProvider.class.getName(), - this.getExternalJDBCUrl(), - DB_USERNAME, - DB_PASSWORD, - Map.ofEntries( - // Use caching scanner to massively improve performance (this way the scanning only happens once) - Map.entry(PersistenceSettings.SCANNER, CachingStandardScanner.instance()) - ) - ); - } - } - - public static String getInternalJDBCUrl(final String networkAlias) - { - return "jdbc:mariadb://" + networkAlias + ":" + DBContainer.PORT + "/" + DB_DATABASE; - } - - public String getExternalJDBCUrl() - { - return this.getContainer().getJdbcUrl(); - } - - /** - * Creates a new {@link EntityManager} with an internal {@link EntityManagerFactory}, which can be used to load and - * save data in the database for the test. - * - *

- * It may be a good idea to close the EntityManager, when you're finished with it. - *

- *

- * All created EntityManager are automatically cleaned up when the test is finished. - *

- * - * @return EntityManager - */ - public EntityManager createEntityManager() - { - return this.getEMC().createEntityManager(); - } - - public void useNewEntityManager(final Consumer action) - { - try(final EntityManager em = this.createEntityManager()) - { - action.accept(em); - } - } - - @SuppressWarnings("java:S6437") // This is a test calm down - public MariaDbDataSource createDataSource() - { - final MariaDbDataSource dataSource = new MariaDbDataSource(); - try - { - dataSource.setUser(DB_USERNAME); - dataSource.setPassword(DB_PASSWORD); - dataSource.setUrl(this.getExternalJDBCUrl()); - } - catch(final SQLException e) - { - throw new IllegalStateException("Invalid container setup", e); - } - return dataSource; - } - - public void migrateDatabase(final Collection locations) - { - this.migrateDatabase(locations.toArray(String[]::new)); - } - - public void migrateDatabase(final String... locations) - { - new FlywayMigration().migrate(conf -> - { - conf.dataSource(this.createDataSource()); - conf.locations(locations); - }); - } - - @SuppressWarnings("PMD.InvalidLogMessageFormat") // % is not used for formatting - public void logDataBaseInfo() - { - if(Optional.ofNullable(this.getContainer()).map(GenericContainer::getContainerId).isPresent()) - { - return; - } - - final Logger logger = LoggerFactory.getLogger(DBTCI.class.getName() + ".sqlstatus"); - if(!logger.isInfoEnabled()) - { - return; - } - - logger.info("===== SERVER INFO ====="); - try(final Connection conn = this.createDataSource().getConnection(); - final Statement stmt = conn.createStatement()) - { - logger.info("> SHOW PROCESSLIST <"); - this.printDatabaseInfo(stmt.executeQuery("SHOW PROCESSLIST"), logger); - - if(logger.isTraceEnabled()) - { - logger.info("> SHOW STATUS (TRACE) <"); - this.printDatabaseInfo(stmt.executeQuery("SHOW STATUS"), logger); - } - else if(logger.isDebugEnabled()) - { - logger.info("> SHOW STATUS LIKE '%onn%' (DEBUG) <"); - this.printDatabaseInfo(stmt.executeQuery("SHOW STATUS LIKE '%onn%'"), logger); - } - } - catch(final Exception ex) - { - logger.warn("Unable to print database info", ex); - } - finally - { - logger.info("======================="); - } - } - - @SuppressWarnings("java:S2629") // is already checked when invoked - private void printDatabaseInfo(final ResultSet rs, final Logger logger) throws SQLException - { - final int colCount = rs.getMetaData().getColumnCount(); - - logger.info( - IntStream.range(1, colCount + 1).mapToObj(id -> - { - try - { - return rs.getMetaData().getColumnName(id); - } - catch(final SQLException e) - { - return "--Exception--"; - } - }).collect(Collectors.joining(","))); - while(rs.next()) - { - logger.info( - IntStream.range(1, colCount + 1).mapToObj(id -> - { - try - { - return rs.getString(id); - } - catch(final SQLException e) - { - return "--Exception--"; - } - }).collect(Collectors.joining(","))); - } - } -} diff --git a/tci-advanced-demo/tci-db/src/main/java/software/xdev/tci/demo/tci/db/datageneration/DataGenerator.java b/tci-advanced-demo/tci-db/src/main/java/software/xdev/tci/demo/tci/db/datageneration/DataGenerator.java deleted file mode 100644 index 3a5bb8aa..00000000 --- a/tci-advanced-demo/tci-db/src/main/java/software/xdev/tci/demo/tci/db/datageneration/DataGenerator.java +++ /dev/null @@ -1,6 +0,0 @@ -package software.xdev.tci.demo.tci.db.datageneration; - -public interface DataGenerator -{ - // just marker -} diff --git a/tci-advanced-demo/tci-db/src/main/java/software/xdev/tci/demo/tci/db/factory/DBTCIFactory.java b/tci-advanced-demo/tci-db/src/main/java/software/xdev/tci/demo/tci/db/factory/DBTCIFactory.java deleted file mode 100644 index b58d2684..00000000 --- a/tci-advanced-demo/tci-db/src/main/java/software/xdev/tci/demo/tci/db/factory/DBTCIFactory.java +++ /dev/null @@ -1,63 +0,0 @@ -package software.xdev.tci.demo.tci.db.factory; - -import java.sql.Connection; -import java.sql.Statement; -import java.util.concurrent.TimeUnit; - -import org.rnorth.ducttape.unreliables.Unreliables; - -import software.xdev.tci.demo.tci.db.DBTCI; -import software.xdev.tci.demo.tci.db.containers.DBContainer; -import software.xdev.tci.demo.tci.db.containers.DBContainerBuilder; -import software.xdev.tci.factory.prestart.PreStartableTCIFactory; -import software.xdev.tci.factory.prestart.snapshoting.CommitedImageSnapshotManager; -import software.xdev.tci.misc.ContainerMemory; - - -public class DBTCIFactory extends PreStartableTCIFactory -{ - public DBTCIFactory() - { - this(true); - } - - @SuppressWarnings("resource") - public DBTCIFactory(final boolean migrateAndInitializeEMC) - { - super( - (c, n) -> new DBTCI(c, n, migrateAndInitializeEMC), - () -> new DBContainer(DBContainerBuilder.getBuiltImageName()) - .withDatabaseName(DBTCI.DB_DATABASE) - .withCreateContainerCmdModifier(cmd -> cmd.getHostConfig().withMemory(ContainerMemory.M512M)), - "db-mariadb", - "container.db", - "DB"); - this.withSnapshotManager(new CommitedImageSnapshotManager("/var/lib/mysql")); - } - - @Override - protected void postProcessNew(final DBTCI infra) - { - // Docker needs a few milliseconds (usually less than 100) to reconfigure its networks - // In the meantime existing connections might fail if we go on immediately - // So let's wait a moment here until everything is fine - Unreliables.retryUntilSuccess( - 10, - TimeUnit.SECONDS, - () -> { - final String testQuery = infra.getContainer().getTestQueryString(); - try(final Connection con = infra.createDataSource().getConnection(); - final Statement statement = con.createStatement()) - { - statement.executeQuery(testQuery).getMetaData(); - } - - if(infra.isMigrateAndInitializeEMC()) - { - // Check EMC if pool connections work - infra.useNewEntityManager(em -> em.createNativeQuery(testQuery).getResultList()); - } - return null; - }); - } -} diff --git a/tci-advanced-demo/tci-db/src/main/java/software/xdev/tci/demo/tci/db/persistence/EntityManagerController.java b/tci-advanced-demo/tci-db/src/main/java/software/xdev/tci/demo/tci/db/persistence/EntityManagerController.java deleted file mode 100644 index 626b3c03..00000000 --- a/tci-advanced-demo/tci-db/src/main/java/software/xdev/tci/demo/tci/db/persistence/EntityManagerController.java +++ /dev/null @@ -1,189 +0,0 @@ -package software.xdev.tci.demo.tci.db.persistence; - -import static java.util.Map.entry; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.stream.Collectors; - -import jakarta.persistence.Entity; -import jakarta.persistence.EntityManager; -import jakarta.persistence.EntityManagerFactory; -import jakarta.persistence.spi.ClassTransformer; -import jakarta.persistence.spi.PersistenceUnitTransactionType; - -import org.hibernate.cfg.JdbcSettings; -import org.hibernate.jpa.HibernatePersistenceProvider; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.orm.jpa.persistenceunit.MutablePersistenceUnitInfo; - -import software.xdev.tci.demo.persistence.config.DefaultJPAConfig; -import software.xdev.tci.demo.persistence.util.DisableHibernateFormatMapper; - - -/** - * Handles the creation and destruction of {@link EntityManager}s. - *

- * This should only be used when a {@link EntityManager} has to be created manually, e.g. when not running on an - * AppServer. - */ -public class EntityManagerController implements AutoCloseable -{ - private static final Logger LOG = LoggerFactory.getLogger(EntityManagerController.class); - - private static Set cachedEntityClassNames; - - protected final List activeEms = Collections.synchronizedList(new ArrayList<>()); - protected final EntityManagerFactory emf; - - public EntityManagerController(final EntityManagerFactory emf) - { - this.emf = Objects.requireNonNull(emf); - } - - /** - * Creates a new {@link EntityManager} with an internal {@link EntityManagerFactory}, which can be used to load and - * save data in the database. - * - *

- * It may be a good idea to close the EntityManager, when you're finished with it. - *

- *

- * All created EntityManager are automatically cleaned up once {@link #close()} is called. - *

- * - * @return EntityManager - */ - public EntityManager createEntityManager() - { - final EntityManager em = this.emf.createEntityManager(); - this.activeEms.add(em); - - return em; - } - - @Override - public void close() - { - LOG.debug("Shutting down resources"); - this.activeEms.forEach(em -> - { - try - { - if(em.getTransaction() != null && em.getTransaction().isActive()) - { - em.getTransaction().rollback(); - } - em.close(); - } - catch(final Exception e) - { - LOG.warn("Unable to close EntityManager", e); - } - }); - - LOG.debug("Cleared {}x EntityManagers", this.activeEms.size()); - - this.activeEms.clear(); - - try - { - this.emf.close(); - LOG.debug("Released EntityManagerFactory"); - } - catch(final Exception e) - { - LOG.error("Failed to release EntityManagerFactory", e); - } - } - - public static EntityManagerController createForStandalone( - final String driverFullClassName, - final String connectionProviderClassName, - final String jdbcUrl, - final String username, - final String password, - final Map additionalConfig - ) - { - return createForStandalone( - driverFullClassName, - connectionProviderClassName, - "Test", - jdbcUrl, - username, - password, - additionalConfig); - } - - public static EntityManagerController createForStandalone( - final String driverFullClassName, - final String connectionProviderClassName, - final String persistenceUnitName, - final String jdbcUrl, - final String username, - final String password, - final Map additionalConfig - ) - { - final MutablePersistenceUnitInfo persistenceUnitInfo = new MutablePersistenceUnitInfo() - { - @Override - public void addTransformer(final ClassTransformer classTransformer) - { - // Do nothing - } - - @Override - public ClassLoader getNewTempClassLoader() - { - return null; - } - }; - persistenceUnitInfo.setTransactionType(PersistenceUnitTransactionType.RESOURCE_LOCAL); - persistenceUnitInfo.setPersistenceUnitName(persistenceUnitName); - persistenceUnitInfo.setPersistenceProviderClassName(HibernatePersistenceProvider.class.getName()); - if(cachedEntityClassNames == null) - { - cachedEntityClassNames = AnnotatedClassFinder.find(DefaultJPAConfig.ENTITY_PACKAGE, Entity.class) - .stream() - .map(Class::getName) - .collect(Collectors.toSet()); - } - try - { - Collections.list(EntityManagerController.class - .getClassLoader() - .getResources("")) - .forEach(persistenceUnitInfo::addJarFileUrl); - } - catch(final IOException ioe) - { - throw new UncheckedIOException(ioe); - } - - final Map properties = new HashMap<>(Map.ofEntries( - entry(JdbcSettings.JAKARTA_JDBC_DRIVER, driverFullClassName), - entry(JdbcSettings.JAKARTA_JDBC_URL, jdbcUrl), - entry(JdbcSettings.JAKARTA_JDBC_USER, username), - entry(JdbcSettings.JAKARTA_JDBC_PASSWORD, password) - )); - Optional.ofNullable(connectionProviderClassName) - .ifPresent(p -> properties.put(JdbcSettings.CONNECTION_PROVIDER, connectionProviderClassName)); - properties.putAll(DisableHibernateFormatMapper.properties()); - return new EntityManagerController( - new HibernatePersistenceProvider() - .createContainerEntityManagerFactory( - persistenceUnitInfo, - properties)); - } -} diff --git a/tci-advanced-demo/tci-oidc/README.md b/tci-advanced-demo/tci-oidc/README.md deleted file mode 100644 index b2cd739c..00000000 --- a/tci-advanced-demo/tci-oidc/README.md +++ /dev/null @@ -1 +0,0 @@ -This module contains the TCI for the OIDC server diff --git a/tci-advanced-demo/tci-oidc/pom.xml b/tci-advanced-demo/tci-oidc/pom.xml deleted file mode 100644 index 113df563..00000000 --- a/tci-advanced-demo/tci-oidc/pom.xml +++ /dev/null @@ -1,25 +0,0 @@ - - - 4.0.0 - - software.xdev.tci.demo - tci-advanced-demo - 1.2.1-SNAPSHOT - - tci-oidc - - - - software.xdev.tci.demo - tci-testcontainers - - - - org.apache.httpcomponents.client5 - httpclient5 - - - diff --git a/tci-advanced-demo/tci-oidc/src/main/java/software/xdev/tci/demo/tci/oidc/containers/OIDCServerContainer.java b/tci-advanced-demo/tci-oidc/src/main/java/software/xdev/tci/demo/tci/oidc/containers/OIDCServerContainer.java deleted file mode 100644 index 69c05708..00000000 --- a/tci-advanced-demo/tci-oidc/src/main/java/software/xdev/tci/demo/tci/oidc/containers/OIDCServerContainer.java +++ /dev/null @@ -1,90 +0,0 @@ -package software.xdev.tci.demo.tci.oidc.containers; - -import org.testcontainers.containers.GenericContainer; -import org.testcontainers.utility.DockerImageName; - - -public class OIDCServerContainer extends GenericContainer -{ - public static final int PORT = 8080; - - public static final String DEFAULT_CLIENT_ID = "client-id1"; - public static final String DEFAULT_CLIENT_SECRET = "client-secret1"; - - public OIDCServerContainer() - { - super(DockerImageName.parse("xdevsoftware/oidc-server-mock:1")); - this.addExposedPort(PORT); - } - - public OIDCServerContainer withDefaultEnvConfig() - { - this.addEnv("ASPNETCORE_ENVIRONMENT", "Development"); - this.addEnv("ASPNET_SERVICES_OPTIONS_INLINE", """ - { - "ForwardedHeadersOptions": { - "ForwardedHeaders" : "All" - } - } - """); - this.addEnv("SERVER_OPTIONS_INLINE", """ - { - "AccessTokenJwtType": "JWT", - "Discovery": { - "ShowKeySet": true - }, - "Authentication": { - "CookieSameSiteMode": "Lax", - "CheckSessionCookieSameSiteMode": "Lax" - } - } - """); - this.addEnv("LOGIN_OPTIONS_INLINE", """ - { - "AllowRememberLogin": false - } - """); - this.addEnv("LOGOUT_OPTIONS_INLINE", """ - { - "AutomaticRedirectAfterSignOut": true - } - """); - this.addEnv("CLIENTS_CONFIGURATION_INLINE", """ - [ - { - "ClientId": "%s", - "ClientSecrets": [ - "%s" - ], - "Description": "Desc", - "AllowedGrantTypes": [ - "authorization_code", - "refresh_token" - ], - "RedirectUris": [ - "*" - ], - "AllowedScopes": [ - "openid", - "profile", - "email", - "offline_access" - ], - "AlwaysIncludeUserClaimsInIdToken": true, - "AllowOfflineAccess": true, - "RequirePkce": false - } - ] - """.formatted(DEFAULT_CLIENT_ID, DEFAULT_CLIENT_SECRET)); - return this.self(); - } - - public String getExternalHttpBaseEndPoint() - { - // noinspection HttpUrlsUsage - return "http://" - + this.getHost() - + ":" - + this.getMappedPort(PORT); - } -} diff --git a/tci-advanced-demo/tci-oidc/src/main/java/software/xdev/tci/demo/tci/oidc/factory/OIDCTCIFactory.java b/tci-advanced-demo/tci-oidc/src/main/java/software/xdev/tci/demo/tci/oidc/factory/OIDCTCIFactory.java deleted file mode 100644 index 3f180beb..00000000 --- a/tci-advanced-demo/tci-oidc/src/main/java/software/xdev/tci/demo/tci/oidc/factory/OIDCTCIFactory.java +++ /dev/null @@ -1,42 +0,0 @@ -package software.xdev.tci.demo.tci.oidc.factory; - -import java.time.Duration; - -import org.apache.hc.core5.http.HttpStatus; -import org.testcontainers.containers.wait.strategy.HostPortWaitStrategy; -import org.testcontainers.containers.wait.strategy.HttpWaitStrategy; -import org.testcontainers.containers.wait.strategy.WaitAllStrategy; - -import software.xdev.tci.demo.tci.oidc.OIDCTCI; -import software.xdev.tci.demo.tci.oidc.containers.OIDCServerContainer; -import software.xdev.tci.factory.prestart.PreStartableTCIFactory; -import software.xdev.tci.misc.ContainerMemory; - - -public class OIDCTCIFactory extends PreStartableTCIFactory -{ - @SuppressWarnings("resource") - public OIDCTCIFactory() - { - super( - OIDCTCI::new, - () -> new OIDCServerContainer() - .withCreateContainerCmdModifier(cmd -> cmd.getHostConfig().withMemory(ContainerMemory.M512M)) - .waitingFor( - new WaitAllStrategy() - .withStartupTimeout(Duration.ofMinutes(1)) - .withStrategy(new HostPortWaitStrategy()) - .withStrategy( - new HttpWaitStrategy() - .forPort(OIDCServerContainer.PORT) - .forPath("/") - .forStatusCode(HttpStatus.SC_OK) - .withReadTimeout(Duration.ofSeconds(10)) - ) - ) - .withDefaultEnvConfig(), - "oidc", - "container.oidc", - "OIDC"); - } -} diff --git a/tci-advanced-demo/tci-selenium/README.md b/tci-advanced-demo/tci-selenium/README.md deleted file mode 100644 index 8fd1a472..00000000 --- a/tci-advanced-demo/tci-selenium/README.md +++ /dev/null @@ -1,5 +0,0 @@ -This module contains the TCI for Selenium. - -Noteworthy contains: -* multiple Browsers (Firefox + Chrome - all other Browsers are nowadays derivates of them) and a mechanism to handle them correctly. -* an optional extension that automatically creates videos of e.g. test failures diff --git a/tci-advanced-demo/tci-selenium/pom.xml b/tci-advanced-demo/tci-selenium/pom.xml deleted file mode 100644 index 52a27cfa..00000000 --- a/tci-advanced-demo/tci-selenium/pom.xml +++ /dev/null @@ -1,56 +0,0 @@ - - - 4.0.0 - - software.xdev.tci.demo - tci-advanced-demo - 1.2.1-SNAPSHOT - - tci-selenium - - - - software.xdev.tci.demo - tci-testcontainers - - - - software.xdev - testcontainers-selenium - - - - org.junit.jupiter - junit-jupiter-api - compile - - - - - org.seleniumhq.selenium - selenium-remote-driver - - - - io.opentelemetry - * - - - - - org.seleniumhq.selenium - selenium-support - - - org.seleniumhq.selenium - selenium-firefox-driver - - - org.seleniumhq.selenium - selenium-chrome-driver - - - diff --git a/tci-advanced-demo/tci-selenium/src/main/java/software/xdev/tci/demo/tci/selenium/BrowserTCI.java b/tci-advanced-demo/tci-selenium/src/main/java/software/xdev/tci/demo/tci/selenium/BrowserTCI.java deleted file mode 100644 index d3741086..00000000 --- a/tci-advanced-demo/tci-selenium/src/main/java/software/xdev/tci/demo/tci/selenium/BrowserTCI.java +++ /dev/null @@ -1,178 +0,0 @@ -package software.xdev.tci.demo.tci.selenium; - -import static java.util.Collections.emptyMap; - -import java.time.Duration; -import java.util.Objects; -import java.util.Optional; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - -import org.openqa.selenium.Capabilities; -import org.openqa.selenium.remote.HttpCommandExecutor; -import org.openqa.selenium.remote.RemoteWebDriver; -import org.openqa.selenium.remote.http.ClientConfig; -import org.openqa.selenium.remote.http.HttpClient; -import org.rnorth.ducttape.timeouts.Timeouts; -import org.rnorth.ducttape.unreliables.Unreliables; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.testcontainers.lifecycle.TestDescription; - -import software.xdev.tci.TCI; -import software.xdev.tci.demo.tci.selenium.containers.SeleniumBrowserWebDriverContainer; - - -public class BrowserTCI extends TCI -{ - private static final Logger LOG = LoggerFactory.getLogger(BrowserTCI.class); - - protected Capabilities capabilities; - protected RemoteWebDriver webDriver; - protected ClientConfig clientConfig = ClientConfig.defaultConfig(); - protected int webDriverRetryCount = 2; - protected int webDriverRetrySec = 30; - - public BrowserTCI( - final SeleniumBrowserWebDriverContainer container, - final String networkAlias, - final Capabilities capabilities) - { - super(container, networkAlias); - this.capabilities = capabilities; - } - - public BrowserTCI withClientConfig(final ClientConfig clientConfig) - { - this.clientConfig = Objects.requireNonNull(clientConfig); - return this; - } - - public BrowserTCI withWebDriverRetryCount(final int webDriverRetryCount) - { - this.webDriverRetryCount = Math.min(Math.max(webDriverRetryCount, 2), 10); - return this; - } - - public BrowserTCI withWebDriverRetrySec(final int webDriverRetrySec) - { - this.webDriverRetrySec = Math.min(Math.max(webDriverRetrySec, 10), 10 * 60); - return this; - } - - @Override - public void start(final String containerName) - { - super.start(containerName); - - this.initWebDriver(); - } - - @SuppressWarnings("checkstyle:MagicNumber") - protected void initWebDriver() - { - LOG.debug("Initializing WebDriver"); - final AtomicInteger retryCounter = new AtomicInteger(1); - this.webDriver = Unreliables.retryUntilSuccess( - this.webDriverRetryCount, - () -> { - final ClientConfig config = - this.clientConfig.baseUri(this.getContainer().getSeleniumAddressURI()); - final int tryCount = retryCounter.getAndIncrement(); - LOG.info( - "Creating new WebDriver [retryCount={},retrySec={},clientConfig={}] Try #{}", - this.webDriverRetryCount, - this.webDriverRetrySec, - config, - tryCount); - - final HttpClient.Factory factory = HttpCommandExecutor.getDefaultClientFactory(); - final HttpClient client = factory.createClient(config); - final HttpCommandExecutor commandExecutor = new HttpCommandExecutor( - emptyMap(), - config, - // Constructor without factory does not exist... - x -> client); - - try - { - return Timeouts.getWithTimeout( - this.webDriverRetrySec, - TimeUnit.SECONDS, - () -> new RemoteWebDriver(commandExecutor, this.capabilities)); - } - catch(final RuntimeException rex) - { - // Cancel further communication and abort all connections - try - { - LOG.warn("Encounter problem in try #{} - Terminating communication", tryCount); - client.close(); - factory.cleanupIdleClients(); - } - catch(final Exception ex) - { - LOG.warn("Failed to cleanup try #{}", tryCount, ex); - } - - throw rex; - } - }); - - // Default timeout is 5m? -> Single test failure causes up to 10m delays (replay must also be saved!) - // https://w3c.github.io/webdriver/#timeouts - this.webDriver.manage().timeouts().pageLoadTimeout(Duration.ofSeconds(60)); - - // Maximize window - this.webDriver.manage().window().maximize(); - } - - public Optional getVncAddress() - { - return Optional.ofNullable(this.getContainer().getVncAddress()); - } - - public Optional getNoVncAddress() - { - return Optional.ofNullable(this.getContainer().getNoVncAddress()); - } - - public RemoteWebDriver getWebDriver() - { - return this.webDriver; - } - - public void afterTest(final TestDescription description, final Optional throwable) - { - if(this.getContainer() != null) - { - this.getContainer().afterTest(description, throwable); - } - } - - @Override - public void stop() - { - if(this.webDriver != null) - { - final long startTime = System.currentTimeMillis(); - try - { - this.webDriver.quit(); - } - catch(final Exception e) - { - LOG.warn("Failed to quit the driver", e); - } - finally - { - if(LOG.isDebugEnabled()) - { - LOG.debug("Quiting driver took {}ms", System.currentTimeMillis() - startTime); - } - } - this.webDriver = null; - } - super.stop(); - } -} diff --git a/tci-advanced-demo/tci-selenium/src/main/java/software/xdev/tci/demo/tci/selenium/TestBrowser.java b/tci-advanced-demo/tci-selenium/src/main/java/software/xdev/tci/demo/tci/selenium/TestBrowser.java deleted file mode 100644 index 650752ac..00000000 --- a/tci-advanced-demo/tci-selenium/src/main/java/software/xdev/tci/demo/tci/selenium/TestBrowser.java +++ /dev/null @@ -1,36 +0,0 @@ -package software.xdev.tci.demo.tci.selenium; - -import java.util.function.Supplier; - -import org.openqa.selenium.Capabilities; -import org.openqa.selenium.chrome.ChromeOptions; -import org.openqa.selenium.firefox.FirefoxOptions; -import org.openqa.selenium.firefox.FirefoxProfile; - - -public enum TestBrowser -{ - FIREFOX(() -> { - final FirefoxOptions firefoxOptions = new FirefoxOptions(); - - final FirefoxProfile firefoxProfile = new FirefoxProfile(); - // Allows to type into console without an annoying SELF XSS popup - firefoxProfile.setPreference("devtools.selfxss.count", "100"); - firefoxOptions.setProfile(firefoxProfile); - - return firefoxOptions; - }), - CHROME(ChromeOptions::new); - - private final Supplier capabilityFactory; - - TestBrowser(final Supplier driverFactory) - { - this.capabilityFactory = driverFactory; - } - - public Supplier getCapabilityFactory() - { - return this.capabilityFactory; - } -} diff --git a/tci-advanced-demo/tci-selenium/src/main/java/software/xdev/tci/demo/tci/selenium/factory/BrowserTCIFactory.java b/tci-advanced-demo/tci-selenium/src/main/java/software/xdev/tci/demo/tci/selenium/factory/BrowserTCIFactory.java deleted file mode 100644 index 0f8b73ea..00000000 --- a/tci-advanced-demo/tci-selenium/src/main/java/software/xdev/tci/demo/tci/selenium/factory/BrowserTCIFactory.java +++ /dev/null @@ -1,155 +0,0 @@ -package software.xdev.tci.demo.tci.selenium.factory; - -import java.nio.file.Path; -import java.time.Duration; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; -import java.util.stream.Stream; - -import org.openqa.selenium.Capabilities; -import org.openqa.selenium.remote.http.ClientConfig; -import org.rnorth.ducttape.unreliables.Unreliables; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.testcontainers.containers.wait.strategy.HostPortWaitStrategy; -import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy; -import org.testcontainers.containers.wait.strategy.WaitAllStrategy; - -import software.xdev.tci.demo.tci.selenium.BrowserTCI; -import software.xdev.tci.demo.tci.selenium.containers.SeleniumBrowserWebDriverContainer; -import software.xdev.tci.factory.prestart.PreStartableTCIFactory; -import software.xdev.tci.misc.ContainerMemory; -import software.xdev.testcontainers.selenium.containers.browser.BrowserWebDriverContainer; -import software.xdev.testcontainers.selenium.containers.recorder.SeleniumRecordingContainer; - - -class BrowserTCIFactory extends PreStartableTCIFactory -{ - private static final Logger LOG = LoggerFactory.getLogger(BrowserTCIFactory.class); - - public static final String PROPERTY_RECORD_MODE = "recordMode"; - public static final String PROPERTY_RECORD_DIR = "recordDir"; - public static final String PROPERTY_VNC_ENABLED = "vncEnabled"; - - public static final String DEFAULT_RECORD_DIR = "target/records"; - - protected final String browserName; - - /* - * Constants (set by JVM-Property or default value)
- * Only call corresponding methods and don't access the fields directly! - */ - protected static BrowserWebDriverContainer.RecordingMode systemRecordingMode; - protected static Path dirForRecords; - protected static Boolean vncEnabled; - - @SuppressWarnings({"resource", "checkstyle:MagicNumber"}) - public BrowserTCIFactory(final Capabilities capabilities) - { - super( - (c, na) -> new BrowserTCI(c, na, capabilities) - .withClientConfig(ClientConfig.defaultConfig() - .readTimeout(Duration.ofSeconds(60))), - () -> new SeleniumBrowserWebDriverContainer(capabilities) - .withStartRecordingContainerManually(true) - .withRecordingDirectory(getDefaultDirForRecords()) - .withRecordingMode(getDefaultRecordingMode()) - .withDisableVNC(!isVNCEnabled()) - .withEnableNoVNC(isVNCEnabled()) - .withRecordingContainerSupplier(t -> new SeleniumRecordingContainer(t) - .withFrameRate(10) - .withLogConsumer(getLogConsumer("container.browserrecorder." + capabilities.getBrowserName())) - .withCreateContainerCmdModifier(cmd -> cmd.getHostConfig().withMemory(ContainerMemory.M512M))) - // Without that a mount volume dialog shows up - // https://github.com/testcontainers/testcontainers-java/issues/1670 - .withSharedMemorySize(ContainerMemory.M2G) - .withCreateContainerCmdModifier(cmd -> cmd.getHostConfig().withMemory(ContainerMemory.M1G)) - .withEnv("SE_SCREEN_WIDTH", "1600") - .withEnv("SE_SCREEN_HEIGHT", "900") - // By default after 5 mins the session is killed and you can't use the container anymore. Cool or? - // https://github.com/SeleniumHQ/docker-selenium?tab=readme-ov-file#grid-url-and-session-timeout - .withEnv("SE_NODE_SESSION_TIMEOUT", "3600") - // AWS's Raspberry Pi-sized CPUs are completely overloaded with the default 15s timeout so increase it - .waitingFor(new WaitAllStrategy() - .withStrategy(new LogMessageWaitStrategy() - .withRegEx(".*(Started Selenium Standalone).*\n") - .withStartupTimeout(Duration.ofMinutes(1))) - .withStrategy(new HostPortWaitStrategy()) - .withStartupTimeout(Duration.ofMinutes(1))), - "selenium-" + capabilities.getBrowserName().toLowerCase(), - "container.browserwebdriver." + capabilities.getBrowserName(), - "Browser-" + capabilities.getBrowserName()); - this.browserName = capabilities.getBrowserName(); - } - - @Override - protected void postProcessNew(final BrowserTCI infra) - { - // Start recording container here otherwise there is a lot of blank video - final CompletableFuture cfStartRecorder = - CompletableFuture.runAsync(() -> infra.getContainer().startRecordingContainer()); - - // Docker needs a few milliseconds (usually less than 100) to reconfigure its networks - // In the meantime existing connections might fail if we go on immediately - // So let's wait a moment here until everything is fine - Unreliables.retryUntilSuccess( - 10, - TimeUnit.SECONDS, - () -> infra.getWebDriver().getCurrentUrl()); - - cfStartRecorder.join(); - } - - @Override - public String getFactoryName() - { - return super.getFactoryName() + "-" + this.browserName; - } - - protected static synchronized BrowserWebDriverContainer.RecordingMode getDefaultRecordingMode() - { - if(systemRecordingMode != null) - { - return systemRecordingMode; - } - - final String propRecordMode = System.getProperty(PROPERTY_RECORD_MODE); - systemRecordingMode = Stream.of(BrowserWebDriverContainer.RecordingMode.values()) - .filter(rm -> rm.toString().equals(propRecordMode)) - .findFirst() - .orElse(BrowserWebDriverContainer.RecordingMode.RECORD_FAILING); - LOG.info("Default Recording Mode='{}'", systemRecordingMode); - - return systemRecordingMode; - } - - protected static synchronized Path getDefaultDirForRecords() - { - if(dirForRecords != null) - { - return dirForRecords; - } - - dirForRecords = Path.of(System.getProperty(PROPERTY_RECORD_DIR, DEFAULT_RECORD_DIR)); - final boolean wasCreated = dirForRecords.toFile().mkdirs(); - LOG.info("Default Directory for records='{}', created={}", dirForRecords.toAbsolutePath(), wasCreated); - - return dirForRecords; - } - - protected static synchronized boolean isVNCEnabled() - { - if(vncEnabled != null) - { - return vncEnabled; - } - - vncEnabled = Optional.ofNullable(System.getProperty(PROPERTY_VNC_ENABLED)) - .map(s -> "1".equals(s) || Boolean.parseBoolean(s)) - .orElse(false); - LOG.info("VNC enabled={}", vncEnabled); - - return vncEnabled; - } -} diff --git a/tci-advanced-demo/tci-selenium/src/main/java/software/xdev/tci/demo/tci/selenium/testbase/SeleniumIntegrationTestExtension.java b/tci-advanced-demo/tci-selenium/src/main/java/software/xdev/tci/demo/tci/selenium/testbase/SeleniumIntegrationTestExtension.java deleted file mode 100644 index 81c0b573..00000000 --- a/tci-advanced-demo/tci-selenium/src/main/java/software/xdev/tci/demo/tci/selenium/testbase/SeleniumIntegrationTestExtension.java +++ /dev/null @@ -1,73 +0,0 @@ -package software.xdev.tci.demo.tci.selenium.testbase; - -import java.util.Objects; -import java.util.function.Function; - -import org.junit.jupiter.api.extension.AfterTestExecutionCallback; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.testcontainers.lifecycle.TestDescription; - -import software.xdev.tci.demo.tci.selenium.BrowserTCI; - - -/** - * Extension for Selenium integration tests that creates records for tests if required - */ -public abstract class SeleniumIntegrationTestExtension implements AfterTestExecutionCallback -{ - private static final Logger LOG = LoggerFactory.getLogger(SeleniumIntegrationTestExtension.class); - - protected final Function tciExtractor; - - protected SeleniumIntegrationTestExtension( - final Function tciExtractor) - { - this.tciExtractor = Objects.requireNonNull(tciExtractor); - } - - @Override - public void afterTestExecution(final ExtensionContext context) throws Exception - { - final BrowserTCI browserTCI = this.tciExtractor.apply(context); - if(browserTCI != null) - { - // Wait a moment, so everything is safe on tape - Thread.sleep(100); - - LOG.debug("Trying to capture video"); - - browserTCI.afterTest(new TestDescription() - { - @Override - public String getTestId() - { - return this.getFilesystemFriendlyName(); - } - - @SuppressWarnings("checkstyle:MagicNumber") - @Override - public String getFilesystemFriendlyName() - { - final String testClassName = this.cleanForFilename(context.getRequiredTestClass().getSimpleName()); - final String displayName = this.cleanForFilename(context.getDisplayName()); - return System.currentTimeMillis() - + "_" - + testClassName - + "_" - // Cut off otherwise file name is too long - + displayName.substring(0, Math.min(displayName.length(), 200)); - } - - private String cleanForFilename(final String str) - { - return str.replace(' ', '_') - .replaceAll("[^A-Za-z0-9#_-]", "") - .toLowerCase(); - } - }, context.getExecutionException()); - } - LOG.debug("AfterTestExecution done"); - } -} diff --git a/tci-advanced-demo/tci-testcontainers/README.md b/tci-advanced-demo/tci-testcontainers/README.md deleted file mode 100644 index 4b65b1c9..00000000 --- a/tci-advanced-demo/tci-testcontainers/README.md +++ /dev/null @@ -1,2 +0,0 @@ -This module is the base module for all other TCI modules. -It contains a few shared classes for e.g. correctly redirecting logging. diff --git a/tci-advanced-demo/tci-testcontainers/pom.xml b/tci-advanced-demo/tci-testcontainers/pom.xml deleted file mode 100644 index 53f20783..00000000 --- a/tci-advanced-demo/tci-testcontainers/pom.xml +++ /dev/null @@ -1,26 +0,0 @@ - - - 4.0.0 - - software.xdev.tci.demo - tci-advanced-demo - 1.2.1-SNAPSHOT - - tci-testcontainers - - - - software.xdev - tci-base - - - - - org.slf4j - jul-to-slf4j - - - diff --git a/tci-advanced-demo/tci-testcontainers/src/main/java/software/xdev/tci/demo/tci/util/ContainerLoggingUtil.java b/tci-advanced-demo/tci-testcontainers/src/main/java/software/xdev/tci/demo/tci/util/ContainerLoggingUtil.java deleted file mode 100644 index 4bd9dc34..00000000 --- a/tci-advanced-demo/tci-testcontainers/src/main/java/software/xdev/tci/demo/tci/util/ContainerLoggingUtil.java +++ /dev/null @@ -1,35 +0,0 @@ -package software.xdev.tci.demo.tci.util; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.slf4j.bridge.SLF4JBridgeHandler; - - -public class ContainerLoggingUtil -{ - private static final Logger LOG = LoggerFactory.getLogger(ContainerLoggingUtil.class); - - static final ContainerLoggingUtil INSTANCE = new ContainerLoggingUtil(); - - ContainerLoggingUtil() - { - // No impl - } - - public static void redirectJULtoSLF4J() - { - INSTANCE.redirectJULtoSLF4JInt(); - } - - void redirectJULtoSLF4JInt() - { - if(SLF4JBridgeHandler.isInstalled()) - { - return; - } - - LOG.debug("Installing SLF4JBridgeHandler"); - SLF4JBridgeHandler.removeHandlersForRootLogger(); - SLF4JBridgeHandler.install(); - } -}